使用 UMDH 查找用户模式内存泄漏
用户模式转储堆 (UMDH) 实用工具与操作系统配合使用,以分析特定进程的 Windows 堆分配。 UMDH 查找特定进程中哪个例程正在泄漏内存。
UMDH 包含在 Windows 调试工具中。 有关完整详细信息,请参阅 UMDH。
准备使用 UMDH
如果尚未确定哪个进程正在泄漏内存,请先执行此操作。 有关详细信息,请参阅使用性能监视器查找User-Mode内存泄漏。
UMDH 日志中最重要的数据是堆分配的堆栈跟踪。 若要确定某个进程是否正在泄漏堆内存,请分析这些堆栈跟踪。
在使用 UMDH 显示堆栈跟踪数据之前,必须使用 GFlags 正确配置系统。 GFlags 包含在 Windows 调试工具中。
以下 GFlags 设置启用 UMDH 堆栈跟踪:
在 GFlags 图形界面中,选择“图像文件”选项卡,键入进程名称 (包括文件扩展名) ,按 TAB 键,选择“ 创建用户模式堆栈跟踪数据库”,然后选择“ 应用”。
或者,同样,使用以下 GFlags 命令行,其中 ImageName 是进程名称 (包括文件扩展名) :
gflags /i ImageName +ust
完成后,使用此命令清除 GFlag 设置。 有关详细信息,请参阅 GFlags 命令。
gflags /i ImageName -ust
默认情况下,Windows 收集的堆栈跟踪数据量在 x86 处理器上限制为 32 MB,在 x64 处理器上限制为 64 MB。 如果必须增加此数据库的大小,请在 GFlags 图形界面中选择“图像文件”选项卡,键入进程名称,按 TAB 键,检查“堆栈回溯 (Megs) 检查”框,在关联的文本框中键入以 MB) (值,然后选择“应用”。 仅在必要时增加此数据库,因为它可能会耗尽有限的 Windows 资源。 如果不再需要更大的大小,请将此设置返回到其原始值。
如果在“ 系统注册表 ”选项卡上更改了任何标志,则必须重启 Windows 才能使这些更改生效。 如果更改了“ 图像文件 ”选项卡上的任何标志,则必须重启该过程才能使更改生效。 对“ 内核标志 ”选项卡的更改会立即生效,但在下次 Windows 重启时会丢失。
在使用 UMDH 之前,必须有权访问应用程序的正确符号。 UMDH 使用环境变量_NT_SYMBOL_PATH指定的符号路径。 将此变量设置为包含应用程序的符号的路径。 如果还包括 Windows 符号的路径,则分析可能更完整。 此符号路径的语法与调试器使用的语法相同;有关详细信息,请参阅 符号路径。
例如,如果应用程序的符号位于 C:\MySymbols,并且你希望使用 C:\MyCache 作为下游存储区来使用 Windows 符号的公共 Microsoft 符号存储,则可以使用以下命令设置符号路径:
set _NT_SYMBOL_PATH=c:\mysymbols;srv*c:\mycache*https://msdl.microsoft.com/download/symbols
此外,为了确保结果准确,必须禁用 BSTR 缓存。 为此,请将 OANOCACHE 环境变量设置为等于 1 (1) 。 在启动要跟踪其分配的应用程序之前进行此设置。
如果需要跟踪服务进行的分配,必须将 OANOCACHE 设置为系统环境变量,然后重启 Windows 才能使此设置生效。
使用 UMDH 检测堆分配的增加
完成这些准备工作后,可以使用 UMDH 捕获有关进程的堆分配的信息。 为此,请遵循以下过程:
确定要调查 的进程 (PID) 的进程 ID 。
使用 UMDH 分析此过程的堆内存分配,并将其保存到日志文件。 将 -p 开关与 PID 配合使用,将 -f 开关用于日志文件的名称。 例如,如果 PID 为 124,并且想要将日志文件命名为Log1.txt,请使用以下命令:
umdh -p:124 -f:log1.txt
使用记事本或其他程序打开日志文件。 此文件包含每个堆分配的调用堆栈、通过该调用堆栈进行的分配数以及通过该调用堆栈使用的字节数。
由于要查找内存泄漏,因此单个日志文件的内容不足。 必须比较在不同时间记录的日志文件,以确定哪些分配正在增加。
UMDH 可以比较两个不同的日志文件,并按各自的分配大小显示更改。 可以使用大于符号 (>) 将结果重定向到第三个文本文件。 可能还需要包含 -d 选项,该选项将字节和分配计数从十六进制转换为十进制。 例如,若要比较Log1.txt和Log2.txt,将比较结果保存到文件LogCompare.txt,请使用以下命令:
umdh log1.txt log2.txt > logcompare.txt
打开LogCompare.txt文件。 其内容如下所示:
+ 5320 ( f110 - 9df0) 3a allocs BackTrace00B53 Total increase == 5320
对于 UMDH 日志文件中标记为“BackTrace”) 的每个调用堆栈 (,在两个日志文件之间进行了比较。 在此示例中,第一个日志文件 (Log1.txt) 记录了为 BackTrace00B53 分配的0x9DF0字节,而第二个日志文件记录0xF110个字节,这意味着在捕获两个日志的时间之间分配了0x5320个额外的字节。 这些字节来自 BackTrace00B53 标识的调用堆栈。
若要确定该回溯中的内容,请打开其中一个原始日志文件 (例如,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 输出显示0x5320 (十进制 21280) 从调用堆栈分配的总字节数。 这些字节是从0x14 (十进制 20) 0x428 (1064) 字节的单独分配分配的。
调用堆栈的标识符为“BackTrace00B53”,并显示此堆栈中的调用。 在查看调用堆栈时,可以看到 DisplayMyGraphics 例程正在通过 new 运算符分配内存,该运算符调用例程 malloc,后者使用 Visual C++ 运行时库从堆中获取内存。
确定其中哪一个调用是最后一个在源代码中显式显示的调用。 在这种情况下,可能是 新 运算符,因为对 malloc 的调用是 作为 new 实现的一部分发生的,而不是单独的分配。 因此,DisplayMyGraphics 例程中 new 运算符的此实例会重复分配未释放的内存。