【win】使用 UMDH 定位用户模式内存泄漏

217 阅读6分钟

我正在参加「掘金·启航计划」

[toc]

UMDH

UMDH(用户模式转储堆),是与操作系统一起用于分析特性进程Windows堆分配。UMDH查找特定进程中的哪个例程正在泄漏内存。特别要注意的事:使用UMDH显示堆栈跟踪数据之前,必须使用 GFlags 正确配置系统。Windows的调试工具中包含了 GFlags

使用

启用 UMDH stack 跟踪:

  1. 在 GFlags 图形界面中,选择 "图像文件" 选项卡,键入进程名称 (包括文件扩展名) ,按 TAB 键,选择 " 创建用户模式堆栈跟踪数据库",然后选择 " 应用"。或者,在命令行界面使用 gflags 命令设置。

    glags /i imageName +ust
    

    当完成分析后,使用 - 清楚设置:

    glags /i imageName -ust
    
  2. 默认情况下,在 x86 处理器上 Windows 收集的堆栈跟踪数据量限制为 32 MB,在 x64 处理器上限制为 64 mb。 如果必须增加此数据库的大小,请选择 "GFlags" 图形界面中的 " 映像文件 " 选项卡,键入进程名称,按 tab 键,选中 " Stack Backtrace (Megs) " 复选框,在 "关联" 文本框中键入值 (,以) MB 为单位),然后选择 " 应用"。 仅在必要时增加此数据库,因为它可能会耗尽有限的 Windows 资源。 如果不再需要更大的大小,则将此设置返回到其原始值。

  3. 如果更改了 "系统注册表" 选项卡上的任何标志,则必须重新启动 Windows 以使这些更改生效。 如果更改了 " 图像文件 " 选项卡上的任何标志,则必须重新启动该过程才能使更改生效。 对内核标志选项卡所做的更改会立即生效,但下次重新启动 Windows,它们会丢失。

在使用 UMDH 之前,必须有权访问应用程序的正确符号。UMDH 使用环境变量指定的符号路径 _NT_SYMBOL_PATH。将此变量设置为包含应用程序的符号的路径。如果还包括 Windows 符号的路径,则分析可能更完整。此符号路径的语法与调试器使用的语法相同。

例如,如果你的应用程序的符号位于 C:\MySymbols,并且你想要为你的 Windows 符号使用公共 Microsoft 符号存储区,则使用 C:\MyCache 作为下游存储,你将使用以下命令设置你的符号路径:

set _NT_SYMBOL_PATH=c:\mysymbols;srv*c:\mycache*https://msdl.microsoft.com/download/symb

此外,若需要确保准确的结果,必须禁用 BSTR 缓存。为此,请将 OANOCACHE 环境变量的值设置为 1(需要在启动跟踪其分配的应用程序之前进行设置)。如果需要跟踪服务所做的分配,则必须将 OANOCACHE 设置为西戎环境变量,然后重新启动 Windows 以使此设置生效。

检测通过 UMDH 增加的堆分配

1. 确认进程ID (PID)

tlist | finstr "进程名"

2. 保存日志文件

使用 UMDH 分析此进程的堆内存分配,并将其保存到日志文件。将 -p 开关与 PID 一起使用,并将 -f 开关与日志文件的名称一起使用。例如,如果 PID 为123,并且你想要将日志文件命名为 Log1.txt ,请使用以下命令:

umdh -p:123 -f:log1.txt

3. 查看日志

使用记事本或其他程序打开日志文件。 此文件包含每个堆分配的调用堆栈、通过该调用堆栈进行的分配数,以及通过该调用堆栈使用的字节数。

4. 前后两次日志对比结果

由于您正在查找内存泄漏,因此,单个日志文件的内容是不够的。 你必须比较在不同时间记录的日志文件,以确定哪些分配正在增长。

UMDH 可以比较两个不同的日志文件,并在各自的分配大小中显示更改。 您可以使用大于符号 (>) 将结果重定向到第三个文本文件。 你可能还需要包含-d 选项,该选项将字节和分配计数从十六进制转换为十进制。 例如,若要比较 Log1.txt 和 Log2.txt,将比较结果保存到文件 LogCompare.txt,请使用以下命令:

umdh log1.txt log2.txt > logcompare.txt 

5. 分析内存泄漏

+ 5320 ( f110 - 9df0) 3a allocs BackTrace00B53 
Total increase == 5320 

对于每个调用堆栈 (在 UMDH 日志文件中标记为 "BackTrace" ) ,这两个日志文件之间有比较。 在此示例中,第一个日志文件 (Log1.txt) 为 BackTrace00B53 分配的0x9DF0 字节,而第二个日志文件记录了0xF110 字节,这意味着在捕获两个日志的时间之间分配有0x5320 的额外字节。 这些字节来自 BackTrace00B53 标识的调用堆栈。

6. 查看泄漏堆栈

若要确定该 backtrace 中的内容,请打开一个原始日志文件 (例如 Log2.txt) ,然后搜索 "BackTrace00B53"。结果类似于以下数据:

00005320 bytes in 0x14 allocations (@ 0x00000428) by: BackTrace00B53
ntdll!RtlDebugAllocateHeap+0x000000FD
ntdll!RtlAllocateHeapSlowly+0x0000005A
ntdll!RtlAllocateHeap+0x00000808
MyApp!_heap_alloc_base+0x00000069
MyApp!_heap_alloc_dbg+0x000001A2
MyApp!_nh_malloc_dbg+0x00000023
MyApp!_nh_malloc+0x00000016
MyApp!operator new+0x0000000E
MyApp!DisplayMyGraphics+0x0000001E
MyApp!main+0x0000002C
MyApp!mainCRTStartup+0x000000FC
KERNEL32!BaseProcessStart+0x0000003D 

上述泄漏内存分析:

此 UMDH 输出显示有) (十进制21280从调用堆栈中分配的总字节数。 这些字节是从 0x14 (十进制 20) 单独分配的,每个 0x428 (十进制 1064) 字节。

将为调用堆栈提供 "BackTrace00B53" 的标识符,并显示此堆栈中的调用。 在检查调用堆栈时,可以看到 DisplayMyGraphics 例程正在通过 new 运算符分配内存,该运算符调用例程 malloc,后者使用 Visual C++ 运行时库从堆中获取内存。

确定要在源代码中显式显示的最后一个调用。 在这种情况下,它可能是 new 运算符,因为调用 malloc 是作为 的实现的一部分而不是作为单独的分配。 因此, DisplayMyGraphics例程中new运算符的此实例会重复分配未释放的内存。

参考链接

使用 UMDH 查找用户模式内存泄漏 - Windows drivers | Microsoft Docs