使用Windbg定位Windows C++程序中的内存泄露

目录

1、概述

2、使用Windbg监测内存泄露的一般步骤

3、详解整个操作过程

3.1、gflags.exe和umdh.exe介绍

3.2、启动cmd命令行

3.3、设置pdb符号库路径

3.4、调用gflags设置启用udmh.exe的堆栈跟踪

3.5、第一次使用umdh抓取堆内存使用快照

3.6、第二次使用umdh抓取堆内存使用快照

3.7、比较两次堆内存使用快照,得出结论

4、Windbg分析内存泄露的不足


       最近有个客户在使用我们的Windows软件时又遇到了内存泄露问题,软件在客户的机器环境上运行半个多小时后就会出现闪退崩溃。去年我们也遇到过类似的问题,很大概率是第三方安全软件导致的,第三方安全库注入到我们的进程中,应该是注入的库有内存泄露,导致我们的进程出现问题。本文简单讲述一下如何使用Windbg来定位Windows程序中的内存泄露问题。

1、概述

       Windows平台上分析内存泄露的工具有很多,比如Rational Purify、BoundsChecker等。但这些工具已经好几年不再维护了,老的版本已不再兼容较新的Visual Studio了。即便能兼容,也是比较麻烦的,比如BoundsChecker就需要将头文件和库添加到工程中重新编译程序,大型软件中包含了多个模块,在不确定发生内存泄露的模块的情况下,在所有模块中都添加BoundsChecker的文件重新编译一下是不现实的。

       我们需要一个工具,在不需要重新编译程序的情况下,就能直接去分析程序是否存在内存泄露。之前一直在寻找这样的工具,一直没有找到合适的。使用过腾讯的tMemoryMonitor内存监测工具,但该工具并不好用,很多实际场景下的内存泄露都监测不到。

       最后只能使用Windbg去检测内存泄露。Windbg是微软提供的Windows下强大的调试工具,可以分析多种软件异常问题,在我们日常开发过程中会频繁地使用到该工具,但之前使用Windbg监测内存泄露问题还是第一次。目前我们已经使用Windbg定位了好几起内存泄露的问题,Windbg还是比较给力的。

2、使用Windbg监测内存泄露的一般步骤

       在监测之前,需要安装新版本的Windbg,因为我们实际上是使用Windbg安装目录下的gflags.exe和umdh.exe两个程序去完成内存监测的。

       新版本的Windbg已经合并到Windows SDK包中,要安装Windbg,则需要下载Windows SDK开发包,在安装时可以选择只安装Windbg,操作起来有点小麻烦。

        使用gflags.exe和umdh.exe监测内存泄露的一般步骤如下:

1)右键以管理员权限启动cmd命令行窗口,用cd命令,切换到windbg的安装目录中,比如我的安装路径是:C:\Program Files\Windows Kits\10\Debuggers\x86。
2)在cmd中输入:gflags /i XXX.exe +ust。
3)在cmd中输入:umdh.exe -pn:XXX.exe -f:E:\log1.txt,等待命令执行完成。
4)让软件运行一会,操作软件使之产生内存泄露。
5)在cmd中输入:umdh.exe -pn:XXX.exe -f:E:\log2.txt,等待命令执行完成。
6)在cmd中输入:umdh.exe E:\log1.txt E:\log2.txt -f:E:\result.txt,等待命令执行完成。
7)查看result.txt文件中的堆内存使用变化的统计,定位问题。

3、详解整个操作过程

3.1、gflags.exe和umdh.exe介绍

        gflags.exe和umdh.exe是新版本Windbg安装路径下的程序。

        gflags.exe (Global Flags) ,全局标志编辑器 ,用来启用和禁用高级调试、诊断和故障排除功能。 它通常用于打开其他工具跟踪、计数和日志的指示器。可以通过命令行执行其相关的操作,也可以在gflags的UI界面上设置:

图片[1]-使用Windbg定位Windows C++程序中的内存泄露-卡核

       umdh.exe(User-Mode Dump Heap),用户转储堆程序,用来追踪并分析进程的堆内存分配。 对于每个堆内存的分配,umdh都可以追踪其分配的大小、开销的大小、指向分配的指针和分配堆栈。 如果进程具有多个活动内存堆,umdh将追踪所有堆。 此类分析可以实时展示,也可以保存在日志文件中。该工具程序是没有UI界面,是通过cmd命令行执行相关操作的。

3.2、启动cmd命令行

       一般我们需要右键以管理员权限运行cmd命令行窗口,特别是在Win10系统中。然后用cd命令,切换到windbg的安装目录中,比如我的安装路径是:C:\Program Files\Windows Kits\10\Debuggers\x86,以方便下面在命令行中直接启动该路径下的gflags.exe和umdh.exe两个程序。

3.3、设置pdb符号库路径

       umdh最终会分析出使用堆内存的函数调用堆栈,为了方便查看到函数调用堆栈中的具体函数,可以设置pdb符号库文件路径。我们可以在命令行中设置如下的环境变量:

set _NT_SYMBOL_PATH="D:\MySymbols;srv*C:\WINDOWS\Symbols*http://msdl.microsoft.com/download/symbols"

环境变量的名称为_NT_SYMBOL_PATH,其值是包含pdb的路径。路径包含两部分,前半部分的D:\MySymbols是我们软件模块的符号库文件,后半部分则是微软系统库在线下载的符号库路径。

       如果不想设置微软系统库在线pdb路径,可以只设置软件自己模块的pdb文件。除了在命令行中添加环境变量,也可以在系统中添加环境变量。以win10系统为例,在系统搜索栏中搜索环境变量,找到编辑环境变量的入口,依次操作即可:

图片[2]-使用Windbg定位Windows C++程序中的内存泄露-卡核

图片[3]-使用Windbg定位Windows C++程序中的内存泄露-卡核

图片[4]-使用Windbg定位Windows C++程序中的内存泄露-卡核

3.4、调用gflags设置启用udmh.exe的堆栈跟踪

        继续在cmd命令行中输入:

gflags /i XXX.exe +ust

其中/i用来指定目标进程的名称,参数ust的含义是:Create user mode stack trace database。此命令用来给目标进程创建用户模式堆栈跟踪数据库,用于追踪堆内存的使用情况。

3.5、第一次使用umdh抓取堆内存使用快照

        继续在cmd命令行中输入:

umdh.exe -pn:XXX.exe -f:E:\log1.txt

其中-pn参数用来指定目标进程的进程名,-f参数用来指定存放抓取来的堆内存使用快照数据信息。此命令运行后,将第一次抓取的堆内存使用快照数据保存到E:\log1.txt中。

       输入该命令后,让程序运行一会,尽可能运行的时间长一会,保证在这段时间内产生足够多的内存泄露,这样windbg的监测结果更准确一些。

3.6、第二次使用umdh抓取堆内存使用快照

        继续在cmd中输入如下的命令:

umdh.exe -pn:XXX.exe -f:E:\log2.txt

命令说明同上。此命令运行后,将第二次抓取的堆内存使用快照数据保存到E:\log2.txt中。

3.7、比较两次堆内存使用快照,得出结论

        最后在cmd中输入:

umdh.exe E:\log1.txt E:\log2.txt -f:E:\result.txt

即比较log1.txt 和log2.txt两文件中的堆内存使用变化量,得出统计数据,将统计结果保存到E:\result.txt文件中。打开E:\result.txt,即可查看到堆内存的变化情况,按堆内存申请数量从高到底排列,每一项统计结果都有详细的函数调用堆栈,一般我们只需要分析使用量较高的几项即可。一般第一项就是发生内存泄露的堆栈,比如:

图片[5]-使用Windbg定位Windows C++程序中的内存泄露-卡核

 4、Windbg分析内存泄露的不足

       Windbg只能监测两个时间点的申请堆内存的变化量,并没有去统计释放的堆内存,这一点和Linux上排查内存泄露的神器Valgrind要差一些。所以Windbg统计出来的结果中,排在第一位的项可能并不是内存泄露的项,需要我们去结合代码将其他几项过滤掉,最终确定发生内存泄露的那一项。 

© 版权声明
THE END
喜欢就支持一下吧
点赞3929赞赏 分享
评论 抢沙发
头像
欢迎您留下宝贵的见解!
提交
头像

昵称

取消
昵称表情代码图片

    暂无评论内容