最近,我们的软件在运行过程中,遇到了一个很诡异的问题。
软件在一个测试同事的win7 32位系统上运行1个多小时后会闪退崩溃。奇怪的是,在测试该产品的主要测试人员的win10电脑上,则从未出现过这个情况。
下面详细介绍一下这个问题的排查过程,以及给我们的一些启示!
1、初步分析
我们在软件中安装了异常信息捕获机制,但是软件发生闪退时,并没有捕获到有效dump文件(崩溃信息存放在dump文件中),生成的dump文件是空的!可能是在导出异常上下文信息时发生了二次崩溃,所以没有生成有效的dump文件。
这个问题在他电脑上出现过好几次了。于是,让同事将windows系统上常用的软件调试利器windbg挂载到目标进程上,看看复现闪退时能否抓到有效的信息。结果问题复现后,抓到的异常上下文对应的代码模块基本是不可能产生异常的,所以问题排查还是没有头绪!
突然无意中想到,软件是运行一两个小时后出现的,难道是软件中有内存泄漏?把进程的虚拟内存耗完了,导致再申请内存时都失败了,产生了空指针,导致空指针访问违例,导致软件闪退。这种假设可以解释windbg捕获到的不可能发生异常的代码块的现象了!
于是重新启动软件,观察了任务管理器中我们软件对应的进程的内存占用情况。看到我们软件的进程的内存一直在增长,在运行1个多小时后竟然涨到1GB多了,这下基本可以确定,肯定是内存泄漏导致的内存被耗完,从而导致软件再申请内存失败了,导致访问了空指针,导致软件闪退了!
下面就是使用一些内存泄漏的检测工具来来定位内存泄漏的模块了!
2、使用腾讯的tMemMonitor内存泄漏检测工具检测内存泄露
最开始尝试使用腾讯的tMemMonitor内存泄漏检测工具,检测一下内存泄漏发生在哪个模块中。
具体的做法是,使用tMemMonitor将我们的软件启动起来:
让软件运行一个多小时,让软件产生1GB以上的内存泄漏,然后关闭软件,如果检测到内存泄漏就会弹出生成检测报告的提示,打开报告就可以看到检测结果了。
注意,tMemMonitor内存泄漏检测工具只能检测release版本的程序,被检测程序必须由tMemMonitor启动,并且被监测程序在退出时必须是正常退出的。如果关闭软件时发生了崩溃,tMemMonitor是不会生成内存泄漏报告的。
检测报告中会显示发生内存泄漏的dll或exe的模块名,并且会将相关的函数调用堆栈打印出来,比如:
但实际跑下来,看到检测报告,并没有看到有用的信息,检测到泄漏内存的都比较小,和实际泄漏的内存大小相差甚远!难道不是用户态的内存泄漏?是内核态的内存泄漏?
3、使用windbg检测内存泄漏
使用tMemMonitor工具分析不出来,于是又尝试使用windbg分析了一把。使用此方法之前,要预先安装好windbg工具。最新版本的windbg是从内置在微软官方的SDK中的,可以自行到微软的官方网站上下载安装。
此内存泄漏检测方法,会用到windbg安装路径下的gflags.exe和umdh.exe程序,如下所示:
具体的操作步骤是:
1)打开cmd窗口,切换到windbg的安装目录中,比如我的安装路径是:C:\Program Files\Windows Kits\10\Debuggers\\x86。
2)先使用命令设置用户态函数栈回溯标记:gflags /i xxxxxxxxx.exe +ust,具体含义可以以“gflags /?”查看gflags相关命令行的参数说明:
3)使用umdh.exe将时刻1时的堆内存分配情况输出到日志文件中:umdh.exe -pn:xxxxxxxxx.exe -f:E:\log1.txt。其中,umdh.exe是windows debug tools 下的一款命令行工具,它的全程是User Mode Dump Heap 这个工具会分析当前进程在堆上分配的内存。可以使用“umdh /?”查看umdh.exe支持的命令行参数,以及如何使用的:
然后让程序运行一个多小时后,让程序有足够多的内存泄漏,然后再用命令:umdh.exe -pn:xxxxxxxxx.exe -f:E:\log2.txt,导出时刻2时的堆内存分配使用情况。
4)使用命令:umdh.exe E:\log1.txt E:\log2.txt -f:E:\result.txt,比较两个时刻中间的时间段的堆内存的增长及使用情况,找出可能出现内存泄漏的地方:
这似乎还是有问题,明明泄漏了1GB多的内存,怎么检测结果中最多泄漏的那项计算出来泄漏的内存才200MB,相差的比较多的,看来windbg似乎也不可信啊!
4、使用代码分块注释的办法,定位到发生内存泄漏的模块
问题还是没查出来,这个就比较头疼了,软件马上要对外发布正式商用版本了,这个问题必须要解决啊!
最后没办法,只能采用逐步注释代码的方法,看看能否定位内存泄漏发生在哪个模块中。经多次尝试发现,与dcs数据协作模块的库有关系。然后查看了一下dcs服务器的连接状态,服务器连不上,底层一直在不断的定时重连,难道是每次重连失败后没有释放socket套接字等资源导致的内存泄漏?
于是找到相关模块的负责同事,让他们排查,排查下来后发现,确实是服务器重连失败后没有将相关资源释放掉导致的内存泄漏(使用websocket和服务器通信的)!
后来在我自己的两台电脑上验证了一下,软件运行在我win7 32位电脑上是有内存泄漏的,但在我另一台win7 64位系统上则没有内存泄漏。在测试同事的win10系统上,也详细观察了,win10系统上居然没有内存泄漏,软件运行一切正常!这个内存泄漏难道是和操作系统是强相关的?
5、进一步研究确认
知道大概的原因之后,我又用windbg和tMemMonitor都重新检测了一下内存泄漏,看看哪个工具更好用,定位的更准确!
我们选择检测的时间段内,软件已经占用了1.15GB的内存,内存泄漏估计得有900MB左右了。
此时windbg分析出来的内存泄漏模块确实是对的,如下所示:
但是泄漏的内存只有200MB左右,这和实际的内存泄漏大小有很大的出入。当前windbg分析出的内存泄漏是用户态的,可能有部分泄漏发生在程序的内核态?
而腾讯的tMemMonitor和windbg比要逊色不少,tMemMonitor不仅检测出的泄漏内存大小比实际泄漏大小要小很多,而且根本没有定位到发生内存泄漏的模块。所以,windbg还是要强大不少的。
经后来验证,在另一台的win7 64位系统中,之前之所以没有内存泄露,是因为这台机器上登录平台的账号是没有dcs服务的权限,就不会登录dcs服务器,就不会触发dcs服务器的重连。而本例中的内存泄漏就是dcs重连失败后没有清理相关资源导致的。后来使用一个有dcs服务权限的账号在这台win7 64位系统中登录我们的软件,同样也出现了内存泄漏。
但同样的代码、同样的软件在win10系统中却没有内存泄漏,通过打印日志可以确定win10系统中也触发了dcs服务器的重连了,这可能是win10系统的内存管理机制和win7不同引起的吧!
6、总结
遇到问题后,要进行深入细致的研究,要搞清楚各种情况的来龙去脉!在详细的研究过程中要多思考多验证,会有新的发现和新的收获!