【内存泄漏】使用 UMDH 定位用户模式内存泄漏
概述:使用 UMDH 分析内存泄漏
[toc]
UMDH
UMDH(用户模式转储堆),是与操作系统一起用于分析特性进程Windows堆分配。UMDH查找特定进程中的哪个例程正在泄漏内存。特别要注意的事:使用UMDH显示堆栈跟踪数据之前,必须使用 GFlags 正确配置系统。Windows的调试工具中包含了 GFlags。
使用
启用 UMDH stack
跟踪:
-
在 GFlags 图形界面中,选择 “图像文件” 选项卡,键入进程名称 (包括文件扩展名) ,按 TAB 键,选择 " 创建用户模式堆栈跟踪数据库",然后选择 " 应用"。或者,在命令行界面使用
gflags
命令设置。1
gflags -i imageName +ust
当完成分析后,使用
-
清楚设置:1
gflags -i imageName -ust
-
默认情况下,在 x86 处理器上 Windows 收集的堆栈跟踪数据量限制为 32 MB,在 x64 处理器上限制为 64 mb。 如果必须增加此数据库的大小,请选择 “GFlags” 图形界面中的 " 映像文件 " 选项卡,键入进程名称,按 tab 键,选中 " Stack Backtrace (Megs) " 复选框,在 “关联” 文本框中键入值 (,以) MB 为单位),然后选择 " 应用"。 仅在必要时增加此数据库,因为它可能会耗尽有限的 Windows 资源。 如果不再需要更大的大小,则将此设置返回到其原始值。
-
如果更改了 “系统注册表” 选项卡上的任何标志,则必须重新启动 Windows 以使这些更改生效。 如果更改了 " 图像文件 " 选项卡上的任何标志,则必须重新启动该过程才能使更改生效。 对内核标志选项卡所做的更改会立即生效,但下次重新启动 Windows,它们会丢失。
在使用 UMDH 之前,必须有权访问应用程序的正确符号。UMDH 使用环境变量指定的符号路径 _NT_SYMBOL_PATH
。将此变量设置为包含应用程序的符号的路径。如果还包括 Windows 符号的路径,则分析可能更完整。此符号路径的语法与调试器使用的语法相同。
例如,如果你的应用程序的符号位于 C:\MySymbols
,并且你想要为你的 Windows 符号使用公共 Microsoft 符号存储区,则使用 C:\MyCache
作为下游存储,你将使用以下命令设置你的符号路径:
1 |
|
此外,若需要确保准确的结果,必须禁用 BSTR
缓存。为此,请将 OANOCACHE
环境变量的值设置为 1(需要在启动跟踪其分配的应用程序之前进行设置)。如果需要跟踪服务所做的分配,则必须将 OANOCACHE
设置为西戎环境变量,然后重新启动 Windows 以使此设置生效。
检测通过 UMDH 增加的堆分配
1. 确认进程ID (PID)
1 |
|
2. 保存日志文件
使用 UMDH 分析此进程的堆内存分配,并将其保存到日志文件。将 -p
开关与 PID 一起使用,并将 -f
开关与日志文件的名称一起使用。例如,如果 PID 为123,并且你想要将日志文件命名为 Log1.txt
,请使用以下命令:
1 |
|
3. 查看日志
使用记事本或其他程序打开日志文件。 此文件包含每个堆分配的调用堆栈、通过该调用堆栈进行的分配数,以及通过该调用堆栈使用的字节数。
4. 前后两次日志对比结果
由于您正在查找内存泄漏,因此,单个日志文件的内容是不够的。 你必须比较在不同时间记录的日志文件,以确定哪些分配正在增长。
UMDH 可以比较两个不同的日志文件,并在各自的分配大小中显示更改。 您可以使用大于符号 (>) 将结果重定向到第三个文本文件。 你可能还需要包含-d 选项,该选项将字节和分配计数从十六进制转换为十进制。 例如,若要比较 Log1.txt 和 Log2.txt,将比较结果保存到文件 LogCompare.txt,请使用以下命令:
1 |
|
5. 分析内存泄漏
1 |
|
对于每个调用堆栈 (在 UMDH 日志文件中标记为 “BackTrace” ) ,这两个日志文件之间有比较。 在此示例中,第一个日志文件 (Log1.txt) 为 BackTrace00B53 分配的0x9DF0 字节,而第二个日志文件记录了0xF110 字节,这意味着在捕获两个日志的时间之间分配有0x5320 的额外字节。 这些字节来自 BackTrace00B53 标识的调用堆栈。
6. 查看泄漏堆栈
若要确定该 backtrace 中的内容,请打开一个原始日志文件 (例如 Log2.txt) ,然后搜索 “BackTrace00B53”。结果类似于以下数据:
1 |
|
上述泄漏内存分析:
此 UMDH 输出显示有) (十进制21280从调用堆栈中分配的总字节数。 这些字节是从 0x14 (十进制 20) 单独分配的,每个 0x428 (十进制 1064) 字节。
将为调用堆栈提供 “BackTrace00B53” 的标识符,并显示此堆栈中的调用。 在检查调用堆栈时,可以看到 DisplayMyGraphics 例程正在通过 new 运算符分配内存,该运算符调用例程 malloc,后者使用 Visual C++ 运行时库从堆中获取内存。
确定要在源代码中显式显示的最后一个调用。 在这种情况下,它可能是 new 运算符,因为调用 malloc 是作为 新 的实现的一部分而不是作为单独的分配。 因此, DisplayMyGraphics例程中new运算符的此实例会重复分配未释放的内存。