使用windbg调试器定位程序的内存泄漏

windbg是windows平台下功能强大且轻量级的调试器,支持调试C/C++、C#等。之前在定位差压总控台的崩溃bug时,用到了windbg,功能比较强大,但是用起来比较费劲,需要记忆常用的命令,最重要的是界面比较丑。

使用windbg最关键的是符号的配置,符号文件是链接程序过程中,生成的附件文件,其内容包含全局变量,局部变量,函数名称和入口地址,源代码行号等,符号对于调试应用程序非常重要。

使用VS编译程序时,默认生成了以pdb结尾的符号文件。对于windows公共的系统程序(exe,动态库dll,sys等)的符号,可以通过微软的公共符号服务器(https://msdl.microsoft.com/download/symbols)获取,当然这些符号是去除了核心的数据结构信息后才公开的。

使用VS2008(或其他的VS版本)编写一个包含内存泄漏问题的C++程序。代码如下:

#include

#include

using namespace std;

void memleak()

{

char *p = new char[1000];

}

int main()

{

long long leakBytes = 0;

while(1)

{

memleak();

leakBytes += 1000;

cout << "leak bytes = " << leakBytes << '\n';

Sleep(1000);

}

return 0;

}

每次调用memleak函数,均申请了1000字节的动态内存,Debug模式下编译生成后,在Debug目录下,生成了3个文件memleak.exe、memleak.ilk和memleak.pdb。其中memleak.pdb是符号文件,包含了源代码行号等相关信息。

使用windbg调试器定位程序的内存泄漏_第1张图片

 

为了观察内存泄漏的源代码具体位置,需要对memleak.exe开启用户栈回溯(user stack trace)。使用gflags工具(位于windbg安装目录下),以管理员权限运行下面的命令:

gflags.exe /i memleak.exe +ust

生成的memleak是32位,打开对应版本的windbg调试器。

1,使用windbg打开可执行文件memleak.exe,程序会在入口处中断。

使用windbg调试器定位程序的内存泄漏_第2张图片

 

2,配置符号服务器和符号文件。使用快捷键ctrl+s打开配置符号的窗口。勾选Reload,保存后会重新加载符号。这里配置为srv*c:\symbols*http://msdl.microsoft.com/download/symbols;F:\sourcecode_lht\memleak\Debug,分别是微软的符号服务器和符号文件memleak.pdb的路径。

使用windbg调试器定位程序的内存泄漏_第3张图片

 

3,点击Debug->Go继续运行,程序开始执行,使用ctrl+break中断程序。使用!heap -s命令查看堆信息,点击Debug->Go继续运行一段时间,动态申请的内存会增加,再次使用ctrl+break中断程序,使用!heap -s查看堆信息,对比两次输出结果的不同。

使用windbg调试器定位程序的内存泄漏_第4张图片

 

4,在堆地址为031b0000处,提交的字节数明显增加,从148K增加到220K,即是因为程序一直在申请动态堆内存。

使用!heap -stat -h 031b0000查看增长的堆的信息。发现堆块大小为40c的有ba个,占整个堆的92.82%。

使用windbg调试器定位程序的内存泄漏_第5张图片

 

使用!heap -flt s 40c过滤掉其他的堆块,只显示大小为40c的堆块信息。

使用windbg调试器定位程序的内存泄漏_第6张图片

 

这些堆块的状态均为busy,表示没有进行释放。查看入口地址为031b5140的堆块信息。windbg会显示该堆块的函数调用栈。如下图所示,从下往上,main函数调用了memleak,而在memleak中调用new运算符,最终ntdll模块调用RtlAllocateHeap来分配动态内存。至此就定位到了存在内存泄漏的位置。

!heap -p -a 031b5140

使用windbg调试器定位程序的内存泄漏_第7张图片

 

其他:

实际在使用windbg定位应用程序内存泄漏时,有很多限制。例如需要Debug版本的程序和符号文件(VC外的其他编译器似乎都不生成pdb格式的符号文件)等,应用程序较复杂时,动态申请的不同大小的堆块较多,使用windbg也难以寻找。

对于偶发和隐式的内存泄漏问题,可以让程序在windbg调试器下长时间运行,细致观察前后两次堆块提交大小的不同。

gflags.exe开启对应用程序的用户栈回溯后,应用程序运行时会占用比未开启时更多的内存,定位完成后,可以使用gflags.exe /i memleak -ust关闭用户栈回溯。

参考链接:

https://www.codeproject.com/articles/31382/memory-leak-detection-using-windbg

https://docs.microsoft.com/zh-cn/windows-hardware/drivers/debugger/getting-started-with-windows-debugging

你可能感兴趣的:(windbg)