如何使用 UMDH 分析进程内存泄漏

如何使用 UMDH 分析进程内存泄漏

下面使用到的工具umdh.exegflags.exe,都在windbg.exe同目录下,同属于Windows 调试工具集

UMDH(User-Mode Dump Heap)使用步骤:

  1. 设置调试符号路径
    设置调试符号路径,有两种不同的方法:
    (1). 在系统环境变量中添加_NT_SYMBOL_PATH变量,值为c:\mysymbols;srv*c:\mycache*https://msdl.microsoft.com/download/symbols
    (2). 也可以直接在 cmd 里面运行set _NT_SYMBOL_PATH=c:\mysymbols;srv*c:\mycache*https://msdl.microsoft.com/download/symbols
    其中,c:\mysymbols为自己软件的符号表路径。
    注:
  • c:\mycache为 Windows 系统模块符号表的下载存储路径,可以自定义。
  • 如果使用set _NT_SYMBOL_PATH=***方式,cmd 窗口不要关闭,后面运行umdh.exe"也必须在这个窗口,如果重新打开一个cmd,则前面设置的_NT_SYMBOL_PATH值会丢失。
  1. 退出欲分析内存泄漏的目标进程

  2. 使用 gflags 给目标进程添加ust设置,也可双击运行 gflags.exe 打开图形界面进行设置
    "C:\Program Files (x86)\Windows Kits\10\Debuggers\x86\gflags.exe" -i ConsoleApplication1.exe +ust

  3. 重启目标进程

  4. 等目标进程初始化完成后,抓取初始内存快照
    "C:\Program Files (x86)\Windows Kits\10\Debuggers\x86\umdh.exe" -pn:ConsoleApplication1.exe -f:1.log
    注意
    64 位 umdh.exe 不能用于 32 位进程。

  5. 运行一段时间后,抓取当前内存快照
    "C:\Program Files (x86)\Windows Kits\10\Debuggers\x86\umdh.exe" -pn:ConsoleApplication1.exe -f:2.log

  6. 对比两次内存快照,生成内存泄漏分析结果
    "C:\Program Files (x86)\Windows Kits\10\Debuggers\x86\umdh.exe" -d 1.log 2.log -f:result.txt

注意比较结果里面的的数字是十六进制,参考 Interpreting a Log Comparison

  1. 使用 gflags 取消目标进程的 ust 设置
    "C:\Program Files (x86)\Windows Kits\10\Debuggers\x86\gflags.exe" -i ConsoleApplication1.exe -ust

一个 demo 程序的分析结果

demo 程序代码:

#include <iostream>
#include <windows.h>

int main()
{
    std::cout << "snapshot1 \n";
    Sleep(10 * 1000);  // 这里抓取一次内存快照
    for (int i = 0; i < 15; ++i)
    {
        void* p = malloc(100);
    }
    std::cout << "snapshot2 \n";
    Sleep(10 * 1000);  // 这里抓取一次内存快照
}

两次内存快照分析结果:

// Debug library initialized ...
5C0000-5C6FFF DBGHELP: ConsoleApplication1 - private symbols & lines 
        .\ConsoleApplication1.pdb
77810000-779B3FFF DBGHELP: ntdll - export symbols
764F0000-765DFFFF DBGHELP: KERNEL32 - export symbols
76E50000-77089FFF DBGHELP: KERNELBASE - export symbols
55DE0000-55E1EFFF DBGHELP: InfraCore32 - export symbols
66770000-667D2FFF DBGHELP: ummon - export symbols
76620000-7669BFFF DBGHELP: ADVAPI32 - export symbols
76110000-761CEFFF DBGHELP: msvcrt - export symbols
75C20000-75C95FFF DBGHELP: sechost - export symbols
769D0000-76A8EFFF DBGHELP: RPCRT4 - export symbols
76AB0000-76C4BFFF DBGHELP: USER32 - export symbols
76480000-76497FFF DBGHELP: win32u - export symbols
777D0000-777F3FFF DBGHELP: GDI32 - export symbols
770C0000-771A4FFF DBGHELP: gdi32full - export symbols
76400000-7647AFFF DBGHELP: msvcp_win - export symbols
75CA0000-75DBFFFF DBGHELP: ucrtbase - export symbols
76C50000-76D4FFFF DBGHELP: CRYPT32 - export symbols
76310000-763F2FFF DBGHELP: ole32 - export symbols
76750000-769CFFFF DBGHELP: combase - export symbols
75DC0000-75E55FFF DBGHELP: OLEAUT32 - export symbols
70AD0000-70AE4FFF DBGHELP: VCRUNTIME140 - export symbols
6BC60000-6BCCCFFF DBGHELP: MSVCP140 - export symbols
77090000-770B4FFF DBGHELP: IMM32 - export symbols
7C210000-7C2DAFFF DBGHELP: rcscanner32 - no symbols loaded
75BD0000-75C14FFF DBGHELP: SHLWAPI - export symbols
77210000-777C5FFF DBGHELP: SHELL32 - export symbols
72AF0000-72B42FFF DBGHELP: OLEACC - export symbols
73E20000-73E27FFF DBGHELP: VERSION - export symbols
782E0000-7838AFFF DBGHELP: bdsecdll32 - no symbols loaded
75B60000-75BC2FFF DBGHELP: WS2_32 - export symbols
72980000-72AE6FFF DBGHELP: gdiplus - export symbols
70D50000-70D62FFF DBGHELP: CRYPTSP - export symbols
70DE0000-70E0EFFF DBGHELP: rsaenh - export symbols
762F0000-76308FFF DBGHELP: bcrypt - export symbols
6FEF0000-6FEF9FFF DBGHELP: CRYPTBASE - export symbols
75F40000-75F9EFFF DBGHELP: bcryptPrimitives - export symbols
//                                                                          
// Each log entry has the following syntax:                                 
//                                                                          
// + BYTES_DELTA (NEW_BYTES - OLD_BYTES) NEW_COUNT allocs BackTrace TRACEID 
// + COUNT_DELTA (NEW_COUNT - OLD_COUNT) BackTrace TRACEID allocations      
//     ... stack trace ...                                                  
//                                                                          
// where:                                                                   
//                                                                          
//     BYTES_DELTA - increase in bytes between before and after log         
//     NEW_BYTES - bytes in after log                                       
//     OLD_BYTES - bytes in before log                                      
//     COUNT_DELTA - increase in allocations between before and after log   
//     NEW_COUNT - number of allocations in after log                       
//     OLD_COUNT - number of allocations in before log                      
//     TRACEID - decimal index of the stack trace in the trace database     
//         (can be used to search for allocation instances in the original  
//         UMDH logs).                                                      
//                                                                          


+    4096 (   4096 -      0)      1 allocs	BackTrace509C798
+       1 (      1 -      0)	BackTrace509C798	allocations

	ntdll!RtlWalkHeap+194
	ntdll!RtlAllocateHeap+10DC
	ntdll!RtlAllocateHeap+3E
	ucrtbase!malloc_base+26
	ucrtbase!write+34E
	ucrtbase!fwrite+CA
	ucrtbase!fwrite+7F
	ucrtbase!fwrite+4E
	MSVCP140!std::basic_streambuf<char,std::char_traits<char> >::xsgetn+1BD
	MSVCP140!std::basic_streambuf<wchar_t,std::char_traits<wchar_t> >::sputn+21
	ConsoleApplication1!std::operator<<<std::char_traits<char> >+143 (C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\VC\Tools\MSVC\14.29.30037\include\ostream, 756)
	ConsoleApplication1!main+2F (C:\Users\xxx\Desktop\ConsoleApplication1\ConsoleApplication1\ConsoleApplication1.cpp, 15)
	KERNEL32!BaseThreadInitThunk+19
	InfraCore32!IfAddHook+197C9
	ntdll!RtlGetAppContainerNamedObjectPath+11E
	ntdll!RtlGetAppContainerNamedObjectPath+EE

泄漏 0x1500 个字节,注意这里是 16 进制!
分配了 0x15+    1500 (   1500 -      0)     15 allocs	BackTrace509C6F0           
+      15 (     15 -      0)	BackTrace509C6F0	allocations        

	ntdll!RtlWalkHeap+194
	ntdll!RtlAllocateHeap+10DC
	ntdll!RtlAllocateHeap+3E
	ucrtbase!malloc_base+26
	ConsoleApplication1!main+1C (C:\Users\xxx\Desktop\ConsoleApplication1\ConsoleApplication1\ConsoleApplication1.cpp, 12)
	KERNEL32!BaseThreadInitThunk+19
	InfraCore32!IfAddHook+197C9
	ntdll!RtlGetAppContainerNamedObjectPath+11E
	ntdll!RtlGetAppContainerNamedObjectPath+EE

Total increase ==   5596 requested +    444 overhead =   6040

注意

注意分析结果文件中的每个 BackTrace(如 BackTrace509C6F0) 代表还驻留在内存中,没被释放。
可用下面的代码测试

#include <iostream>
#include <windows.h>
#include <vector>

int main()
{
    std::cout << "snapshot1 \n";
    Sleep(10 * 1000);  // 这里抓取一次内存快照
    
    std::vector<void*> v;
    for (int i = 0; i < 100; ++i)
    {
        void* p = malloc(100);
        v.push_back(p);
    }

    // 下面代码释放内存,最终在 UMDH 里显示没有内存分配(泄漏)。
    // 如果去掉,就会报泄漏。
    {
        for (void* p : v)
        {
            free(p);
        }
        v.clear();
        v.~vector();
    }

    std::cout << "snapshot2 \n";
    Sleep(10 * 1000);  // 这里抓取一次内存快照
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值