S.l.e!ep.¢%

像打了激速一样,以四倍的速度运转,开心的工作
简单、开放、平等的公司文化;尊重个性、自由与个人价值;
posts - 1098, comments - 335, trackbacks - 0, articles - 1
  C++博客 :: 首页 :: 新随笔 :: 联系 :: 聚合  :: 管理

如何用WinDbg定位内存泄露?

Posted on 2009-03-27 21:38 S.l.e!ep.¢% 阅读(6396) 评论(1)  编辑 收藏 引用 所属分类: WinDbg
如何用WinDbg定位内存泄露?
 2008-08-04, 10:35 上午 Reply Quote
我用最近用VC在做一个应用程序,其中使用到了一些其他人员开发的DLL,当我在VC中运行该程序并退出时,输出窗口中显示很多Object Dump,双击这些信息也不能定位到我的源码当中,我怀疑这些内存泄漏来自于我调用的DLL中,所以我向请教一下如何通过WinDbg来定位内存泄漏是发生在我的程序中还是DLL中?谢谢!
IP 地址: 已记录   报告
Raymond 离线,最后访问时间: 2009-3-11 12:55:32 Raymond

无等级
注册: 2005-12-19
发 贴: 298
Re: 如何用WinDbg定位内存泄露?
 2008-08-04, 12:42 下午 Reply Quote
1)你能把你看到的转储信息(Dump)复制一些过来么?
2)如果你有《软件调试》这本书,那么请阅读一下第23章,特别是以下几个小节:
23.7.2 DH和UMDH工具 668
23.7.3 定位内存泄漏 668
23.14 堆块转储 700
23.15 泄漏转储 704
23.15.1 _CrtDumpMemoryLeaks 704
23.15.2 何时调用 705
23.15.3 定位导致泄漏的源代码 706
IP 地址: 已记录   报告
zxh65 离线,最后访问时间: 2008-8-7 23:03:54 zxh65

无等级
注册: 2008-08-03
发 贴: 4
Re: 如何用WinDbg定位内存泄露?
 2008-08-04, 13:05 下午 Reply Quote
谢谢Raymond!
因为转储信息比较多,我这里就贴一小部分!
{152} normal block at 0x01078320, 12 bytes long.
Data: 08 79 65 00 48 82 07 01 00 00 00 00
{151} normal block at 0x010782C0, 36 bytes long.
Data: CD CD CD CD CD CD CD CD 00 00 00 00 00 00 00 00
{150} normal block at 0x01078248, 56 bytes long.
Data: F4 77 65 00 00 00 00 00 68 71 07 01 90 3A 09 10
{149} normal block at 0x01077208, 4096 bytes long.
Data: 48 82 07 01 68 83 07 01 88 84 07 01 A8 85 07 01
{148} normal block at 0x010771C8, 4 bytes long.
Data: CD CD CD CD
{147} normal block at 0x01077168, 32 bytes long.
Data: 40 79 65 00 01 00 00 00 00 00 00 00 00 00 00 00
{146} normal block at 0x010770D0, 92 bytes long.
Data: 4C 76 65 00 D8 55 15 00 FF FF FF FF 00 00 00 00
{145} normal block at 0x01073B10, 8 bytes long.
Data: 10 3B 07 01 CD CD CD CD
{144} normal block at 0x01077070, 32 bytes long.
Data: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
{143} normal block at 0x01077018, 28 bytes long.
Data: 50 54 15 00 FF FF FF FF 00 00 00 00 00 00 00 00
{142} normal block at 0x01073A90, 64 bytes long.
Data: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
{141} normal block at 0x010739A0, 180 bytes long.
Data: C4 0B 13 10 90 3A 07 01 00 00 00 00 A4 79 1A 10
{138} normal block at 0x010737D8, 6 bytes long.
Data: 00 00 00 00 00 00
{137} normal block at 0x01073790, 8 bytes long.
Data: <H7> 48 37 07 01 CD CD CD CD
{136} normal block at 0x01073748, 8 bytes long.
Data: 90 37 07 01 18 5E 1A 10
{135} normal block at 0x010736E8, 32 bytes long.
Data: 20 79 12 10 A8 53 15 00 FF FF FF FF 00 00 00 00
{134} normal block at 0x010736A0, 12 bytes long.
Data: 90 37 07 01 01 00 00 00 A4 79 1A 10
最近正在看你的《软件调试》,只是我才看到第四章,晚上回去我先看看23章!
另外,谢谢你写《软件调试》这本书,难得看到国人写出这么好的书,我一定好好把书读透,如果有什么疑问还要向你请教!
IP 地址: 已记录   报告
Raymond 离线,最后访问时间: 2009-3-11 12:55:32 Raymond

无等级
注册: 2005-12-19
发 贴: 298
Re: 如何用WinDbg定位内存泄露?
 2008-08-04, 21:09 下午 Reply Quote
《软件调试》的702页介绍了堆块转储的细节,取其中的一个堆块为例:
{137} normal block at 0x01073790, 8 bytes long.
Data: <H7> 48 37 07 01 CD CD CD CD
137是堆块的分配序号,堆块的类型为普通堆块,位置为0x01073790,用户区长度为8字节。
Data后为用户数据区的前16字节,因为这个数据区只有8字节,所以即所有8字节。<H7>是这8字节的ASCII码显示(其它6字节为不可显示的ASCII码)。其中的CD CD CD CD 是CRT在分配堆块时自动填充的固定内容(《软件调试》P696)。这说明这8个字节的堆块,应用程序使用过前4个字节,后四个字节没有使用过。

对于这样的内存泄漏,有很多种办法,你首先可以试一下23.13.1节介绍的内存分配序号断点,也就是重新运行程序,在尽可能早的时候(比如入口),设置序号断点(将_crtBreakAlloc变量设置为要中断的序号,比如137),让CRT分配到指定的内存块时中断。中断下来后,可以根据栈回溯判断是哪个模块在分配内存,记录后,再退出程序,如果这个堆块仍出现在转储列表中,那么刚才那个模块便值得怀疑了。

如果你有被怀疑的模块源代码,那么可以使用23.15.3节介绍的方法让堆块转储信息中包含源程序的文件名和行号,即下面的样子:
C:\dig\dbg\author\code\chap23\MemLeak\MemLeak.cpp(22) : {74} normal block at 0x00371000, 20 bytes long.
Data: CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD




IP 地址: 已记录   报告
zxh65 离线,最后访问时间: 2008-8-7 23:03:54 zxh65

无等级
注册: 2008-08-03
发 贴: 4
设置序号断点还不能定位
 2008-08-05, 10:15 上午 Reply Quote
Raymond:感谢你的回复,我翻阅了23.13.1节,在程序入口处设置了序号断点,但我的程序并没有在序号断点处中断,我的判断如下,不知道是否正确,还望指正!
我程序是使用lib文件隐式链接DLL的,当我在VC中按F11到程序入口的时候各DLL已经载入完成,如果序号所对应内存的分配的确是在某DLL的DllMain中的话此时再设置序号断点已经晚了,所以最终序号断点没有中断。不知道我这样的分析是否正确?如果正确我该如何将序号断点的设置再进一步提前到在DLL加载之前呢?
IP 地址: 已记录   报告
neilhsu 离线,最后访问时间: 2009-3-10 18:23:13 手语

无等级
注册: 2008-06-06
发 贴: 60
Wink [;)] Re: 如何用WinDbg定位内存泄露?
 2008-08-05, 10:25 上午 Reply Quote

一般我用两种方法:

1. 用Debug Diagnostic Tool的Leak监测分析功能,注意配置好PDB文件。

   DebugDiag 会生成完整的Leak Report。看看帮助很方便。

2. 用WinDBG的!heap扩展命令。注意要为你的程序打开Normal PageHeap。

   然后当内存出现明显泄漏时用 !heap -l 命令分析内存。-l 参数使用类似Java/C#的Garbage Collection算法,这样能找到大部分在程序中没有被引用的HeapBlock。

   这是一个示例输出:

0:011> !heap -l
Searching the memory for potential unreachable busy blocks.
......
Heap 017a0000
Scanning VM ...
Scanning references from 3586 busy blocks (0 MBytes) ...
Entry           User         Heap            Segment        Size  PrevSize  Unused    Flags
-----------------------------------------------------------------------------
00253198  002531a0  00250000  00250000        b8        78        14  busy extra
00253250  00253258  00250000  00250000        78        b8        13  busy extra
00286a38  00286a40  00250000  00250000        b8        b8        15  busy extra
00286af0  00286af8  00250000  00250000        b8        b8        15  busy extra
00286ba8  00286bb0  00250000  00250000        b8        b8        15  busy extra
00286c60  00286c68  00250000  00250000        b8        b8        15  busy extra
00286d18  00286d20  00250000  00250000        b8        b8        15  busy extra
00286dd0  00286dd8  00250000  00250000        b8        b8        15  busy extra

   找到最常出现的Size值(这里是b8),一般就是持续泄漏的内存块大小。随便选一行,记下Entry地址(比如00286a3)。

   dt _DPH_BLOCK_INFORMATION 00286a3 + 8   //8 是HeapEntry结构的大小,跟在其后的就是PageHeap meta data,结构名是_DPH_BLOCK_INFORMATION.

   0:011> dt _DPH_BLOCK_INFORMATION 00286a3 + 8
ntdll!_DPH_BLOCK_INFORMATION
   +0x000 StartStamp       : 0xabcdaaaa
   +0x004 Heap             : 0x80151000
   +0x008 RequestedSize    : 0x7b
   +0x00c ActualSize       : 0xa3
   +0x010 FreeQueue        : _LIST_ENTRY [ 0x2e - 0x0 ]
   +0x010 TraceIndex       : 0x2e
   +0x018 StackTrace       : 0x00357140
   +0x01c EndStamp         : 0xdcbaaaaa

   看到StackTrace那行,这是相应的user mode stack trace database的地址。

0:011> dds 0x00357140
00357140  abcdaaaa
......
00357160  7c949d18 ntdll!RtlAllocateHeapSlowly+0x44
00357164  7c91b298 ntdll!RtlAllocateHeap+0xe64
00357168  004017fe 06_DebugDiag_MemoryLeak!MyHeapAlloc+0x1e [g:\debugging101\projects\06_debugdiag_memoryleak\06_debugdiag_memoryleak\06_debugdiag_memoryleak.cpp @ 11]
0035716c  0040182b 06_DebugDiag_MemoryLeak!WorkerThread+0x1b [g:\debugging101\projects\06_debugdiag_memoryleak\06_debugdiag_memoryleak\06_debugdiag_memoryleak.cpp @ 27]
00357170  7c80b683 kernel32!BaseThreadStart+0x37

   这就是上次通过Heap Manager函数操作这个HeapBlock的StackTrace,一般也就是分配这个Block的地方。

   希望可以帮到你。


鸿鹄安知燕雀之志
IP 地址: 已记录   报告
Raymond 离线,最后访问时间: 2009-3-11 12:55:32 Raymond

无等级
注册: 2005-12-19
发 贴: 298
Re: 如何用WinDbg定位内存泄露?
 2008-08-05, 12:48 下午 Reply Quote
1)如果你觉得main/winmain入口还不够早,那么可以使用WinDBG,在进程的初始断点命中后,启用模块加载事件(debug > event filters > load module),然后在每个CRT DLL加载后设置序号断点。
2)被调试进程中可能有几个CRT实例,应该修改所有实例的_crtBreakAlloc变量,才能保证不会遗漏。以myapp.exe为例,myapp内可能有一份静态链接进来的CRT,即myapp!_crtBreakAlloc。而MSVCRT60.DLL这样的模块中也有CRT实例。

手语说的在Win32堆这一层次来分析也是可行的,而且把握更大,但是就要求你先要熟悉一下Win32堆的常识,《软件调试》23章的前10节做了详细的介绍。
IP 地址: 已记录   报告
zxh65 离线,最后访问时间: 2008-8-7 23:03:54 zxh65

无等级
注册: 2008-08-03
发 贴: 4
Re: 如何用WinDbg定位内存泄露?
 2008-08-07, 10:15 上午 Reply Quote
To Raymond:
你的回复中我有两点疑问,特再次请教!
1、在WinDbg中启用模块加载事件有两个选项,一个是Execution,另一个是Continue。我试了一下,将Execution选项设为Enable时,重新在WinDbg我的程序在其加载每个DLL时都会中断;而Continue选项无论设置哪个值都没有明显影响。我看了一下帮助,说这两个选项分别用来改变中断状态(break status)和处理状态(handling status),我不知道上述两个状态尤其是第二个状态的作用是什么?
2、在WinDbg中如何设置序号断点?
To 手语:
你提供的两个方法我都还没用过,我最近正在按照Raymond的提示在看《软件调试》的相关章节,感谢你的帮助!
IP 地址: 已记录   报告
Raymond 离线,最后访问时间: 2009-3-11 12:55:32 Raymond

无等级
注册: 2005-12-19
发 贴: 298
Re: 如何用WinDbg定位内存泄露?
 2008-08-07, 12:50 下午 Reply Quote
ZXH,
Continue选项只对异常类事件有意义(参见《软件调试》30.9.3节,P48)。
WinDBG中可以直接使用ed命令来编辑_crtBreakAlloc变量(《软件调试》P698),例如你要编辑静态链接到myapp.exe中的CRT实例,那么便执行:ed myapp!__crtBreakAlloc 。

Feedback

# re: 如何用WinDbg定位内存泄露?   回复  更多评论   

2011-08-24 11:11 by sghcpt
To S.l.e!ep.¢%
我在网上看到你的提问关于“如何用WinDbg定位内存泄露? ”,我也遇到这样的情况,我的程序运行一段时间会出现错误,但不知道是我的代码有问题还是别人的dll库有问,想请问你一下,你上面的问题处理好了吗?是怎样处理的?

只有注册用户登录后才能发表评论。
【推荐】超50万行VC++源码: 大型组态工控、电力仿真CAD与GIS源码库
网站导航: 博客园   IT新闻   BlogJava   知识库   博问   管理