检测Windows程序的内存和资源泄漏之原生语言环境

最近接连收到大客户的反馈,我们开发的一个软件,姑且称之为App-E吧,在项目规模特别大的情况下,长时间使用会逐渐耗尽内存,运行越来越缓慢,软件最终崩溃。由于App-E是使用混合语言开发的,主界面使用C#, 核心模块库大多是采用C/C++实现的DLL,很多配置和用户流程又是用Tcl/Tk脚本语言写的, 普通的内存检测工具比如Purify,Purify Plus, Valgrind 要么不支持Windows,要么不支持混合语言,对于这种复杂架构下的内存问题都无能为力。无奈之下,只好采取分而治之的方式,对不同的代码模块采用不同的检测分析方法。我打算在这篇博客里把分析原生语言内存和资源泄漏过程中尝试过的工具和使用经验做一下初步的总结,下篇文章打算翻译一篇MSDN上关于检测和避免.Net环境下的内存和资源泄漏的佳作。

原生语言的内存泄漏检测

使用微软debug版本的C Run-Time库自带的内存泄漏检测APIs

Microsoft的这篇开发文档:Memory Detection Enabling详细介绍了如何开启内存检测。理论上这种方法很简单,只需要在程序头文件中包含以下三行代码,并在Debug模式下重新编译代码,链接debug版本的C Run-Time 库即可。

1 #define _CRTDBG_MAP_ALLOC
2 #include<stdlib.h>
3 #include<crtdbg.h>

但我们的产品恰恰很难满足这两个基本条件,原因是:把产品App-E切换到debug版本的C Run-Time库很费劲,频繁做全编译的代价了也太大了。 很多产品即使在debug模式下也习惯使用release版本的运行库以提高运行速度,我们的产品长久以来就是如此。我试着在Makefile中把Run-Time库从msvcrt.dll切换为msvcprtd.dll,手工编译了几个小模块发现没有问题,include上述头文件也没有问题;但是启动全编译后第二天早晨检查编译结果,发现产生了好几百个编译错误,要知道产品App-E全编译一遍需要8个小时左右,反反复复调试编译问题我根本耗不起。

使用免费的第三方内存泄漏检测库Visual Leak Detector
  • 优点
    类似于微软的方案,VLD也要求在待分析程序的源代码中包含VLD的头文件并重新编译程序;相对于微软的方案,VLD的最大优点是不需要依赖debug版本的Run-Time库,并且支持C和C++语言,还可以更方便地定制内存检测报告。
  • 缺点
    即使只想检测某个模块库中的问题,也不能仅仅只在特定库对应的源代码中包括VLD头文件,VLD要求必须在主程序入口处编译链接VLD头文件。这意味着我还是不能避免全编译。此外,VLD也不支持混合代码模式,如果主程序是用C#等托管语言开发的,即使算法库或功能模块库使用native语言开发,VLD也无能为力,我们的产品App-E正是如此。
开发一段简单的内存泄漏检测代码,按需编译

经过几天的分析对比,我已经很确定内存泄漏问题只存在于特定的几个算法库和他们对应的用户界面,那么怎么才能短平快地针对这几个特定的库做内存泄漏分析呢? 内存泄漏检测的机制很简单,自己写段代码实现检测方法,在特定库中编译链接使用岂不是更方便?事实上早就有人这么做了,我不需要重新发明轮子。codeproject有人分享了一段C语言的内存检测代码,基本思想如下:

  1. 使用宏定义把malloc/free方法映射到自己定义的my_malloc/my_free方法上
  2. 在my_malloc方法中调用malloc方法分配内存,在一个哈希表中记录内存指针,分配的内存大小和代码调用文件和行数等信息;
    C语言的库函数对集合的支持很差,没有直接可用的哈希表,可以自己实现一个链表代替。
    主流的C语言编译器都支持__FILE__/__LINE__宏定义用以获取代码调用文件和行数,我们只需要定义一个合适的struct来保存内存分配信息就行;
  3. 在my_free方法中调用free方法释放内存,清除对应的内存分配记录。
  4. 实现一个report方法,遍历记录内存分配的链表,输出没被释放的内存块对应的代码调用行数等信息。
  5. 在待分析的模块库代码的合适位置调用report方法,重新编译该模块库。

资源泄漏检测

使用上述codeproject的内存检测代码,对几个特定算法库进行了检测,我发现它们的内存泄漏并不严重,那么为什么程序消耗的内存一直在快速增长,运行速度也越来越慢呢? 考虑到算法模块的用户交互界面是用tcl/tk实现的,我怀疑也许tcl/tk脚本存在诸如GDI对象之类的系统资源泄漏,那有哪些工具可以分析检测资源泄漏呢?

Process Explorer

作为SysInternals工具集的一员,Process Explorer的进程检测和管理能力堪称强大,我平时一直用它替代Windows 任务管理器,最近才发现Process Explorer的Performance Tab页堪称检测进程内存和资源使用情况的利器。

Bear

Bear是一款专注于系统资源检测的Windows小工具,它可以检测:

  • 所有GDI对象的使用情况 (hDC, hRegion, hBitmap, hPalette, hFont, hBrush)
  • 所以用户对象的使用情况 (hWnd, hMenu, hCursor, SetWindowsHookEx, SetTimer and some other stuff)
  • 句柄(Handle)数量

相对于Process Explorer的大而全,Bear专注而细腻,更便于我们监测定位进程具体的资源泄漏类型,从而缩小排查的范围。

结合使用两种工具,对比在进行特定软件操作前后的系统资源变化情况,并对照相应的源代码,我在tk脚本中成功发现了一处资源泄漏,MDI Window上显示的canvas在销毁窗口后没有被destroy,导致在打开关闭含有大量器件的原理图时,每次都会产生2M左右的内存增长,GDI对象更是数以百计地增长。

原文地址:https://www.cnblogs.com/egoechog/p/native_memory_resource_leaks.html