Visual Leak Detector原理剖析

 

认识VLD

VLD(Visual Leak Detector)是一款用于Visual C++的开源内存泄漏检测工具,咱们只须要在被检测内存泄漏的工程代码里#include “vld.h”就能够开启内存泄漏检测功能。当咱们使用Visual Studio debugger来调试咱们的进程时,VLD能够在程序退出时很直观地将泄漏的内存地址、堆栈、大小、内容输出到Visual StudioOutput窗口,此时咱们只须要直接双击调用堆栈就能够跳转到对应的代码行,从而直接明了的知道哪里分配了的内存没有被释放,以下图:api

本文要剖析的VLD版本是V2.3,其源码能够在http://vld.codeplex.com/下载到。数组

VLD原理概述

从本质上来讲,VLD是经过监控全部内存操做(分配和释放)来检测内存泄漏的,而监控内存操做的方式也有不少种,好比Hook全部内存操做函数到自定义的函数中,或者经过修改IATImport Address Table导入地址表)来将本来要调用的内存操做函数修改成VLD自已的函数。这里VLD 的方法是后者。函数

VLD原理剖析

#include “vld.h”开始看去

1.         首先咱们在vld.h文件里能够看到#if defined _DEBUG || defined VLD_FORCE_ENABLE,这里表示的意思是Debug版本是默认开启监控的,但若是使用的是Release版本的话就须要在工程设置里加上VLD_FORCE_ENABLE预约义宏内存监控才会生效,这点也是须要提醒各位看官的。工具

2.         再者咱们看到能够看到#pragma comment(lib, "vld.lib")这行代码。其实vld工程自己是一个dll工程,但dll工程编译时同时也会生成一个lib,经过这行代码咱们的程序就不须要手动调用LoadLibrary来加载dll了。测试

3.         再者咱们能够看到这样一段代码:优化

大概的意思就是导出一个对象(这个对象的名字叫g_vld,它是vld.cpp里的一个全局变量)为咱们的程序所看到,从而保证vld.dll会被加载,否则编译器可能会认为vld.dll里咱们的代码并无依赖vld从而给优化掉了。这样,当vld.dll被加载时,这个对象就会被初始化,从而修改各dll的导入地址表来作监控全部内存操做。spa

g_vld对象看VLD如何监控全部内存操做

1.         咱们在vld.cpp里能够看到这个对象的定义,它是类VisualLeakDetector的实例,当此对象被初始化时会调用类的构造函数,接下来咱们看看构造函数里作了什么事。debug

2.         首先,咱们看看下面这段代码:指针

这里枚举了全部已加载的模块,而后再对枚举到的模块调用attachToLoadedModules,接下来咱们看看attachToLoadedModules作了什么事情。调试

3.         attachToLoadedModules函数里,它会枚举全部的模块,并调用下面这行代码:

这个函数的主要工做就是修改moduleBase所指示的模块的导入地址表,下面我看看传入参数m_patchTable是什么。

4.         能够看到m_patchTable是一个moduleentry_t数组,moduleentry_t的定义(在utility.h文件里)以下:

它的第一个成员exportModuleName表示的是一个dll的名字,第二个成员moduleBase表示的是该dll的基地址,第三个参数patchTable是一个结构体的指针,咱们再看看patchentry_t的定义(在utility.h文件里):

其中importNameapi的名字,这些API都内存操做相关的APIoriginal表示的是该API的函数地址,replacementVLD内部一个函数的地址,它是用来替换原来的函数地址的。

5.         接下来看看m_patchTable的具体内容(部分)吧:

再取其中的m_kernel32Patch看看其具体内容:

6.         经过代码能够发现PatchModule函数拿到上面的内存后,会对m_patchTable数组里的每个元素调用PatchImport函数,而PatchImport就是真正修改导入地址表的地方。

VLD如何管理监控到的内存

1.         全部内存分配最终都会调用kernel32HeapAlloc以及HeapReAlloc函数,而前文的介绍中已经提到这两个函数会被VLD内部函数经过修改导入地址表而替换。各位看官看到这里可能会问:“既然全部内存分配最终都会走到这里,那为何不直接监控这几个函数呢?为何还要监控那么多其余函数(malloc,new,realloc,calloc等等)?”个人理解是:为了获取调用malloc,new,realloc,calloc这些函数时的堆栈。虽然从HeapAlloc开始回溯堆栈也没有什么问题,但VLD这不是为了美观么,不想有多余的干扰信息。

2.         VLD监控到分配的内存后会保存到一个map结构中:

这里以堆句柄为first keysecond是一个结构体heapinfo_t,定义以下:

这里的成员blockMap又是一个map

BlockMap以分配的内存地址为first key,而后second又是一个结构体,定义以下:

这里保存了所监控到的内存分配堆栈、分配序号、大小等信息。

3.         有了这个map结构后,当有内存被分配时,就会向这个map插入元素;而当有内存被释放时,就会从这个map中删除对应的元素。

4.         最后,当程序退出时,g_vld对象会被析构,此时map中的全部元素就是没有被释放的内存,也就是泄漏的内存。

总结

         以上就是VLD的基本原理了,其实在VLD早前的版本中它并非用的这种修改导入地址表的机制,这里就再也不赘述了。总的来讲,VLD仍是一款简单易用的内存泄漏检测工具,可是它也有自已的缺陷:须要修改源程序的代码。这就使得须要有代码的状况下才可以检查内存泄漏,但对于测试人员来讲,有时咱们拿到的只是编译好的二进制,状况好点的话会有PDB,此时如何去检查内存泄漏也是一个值得研究的方向。

相关文章
相关标签/搜索