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,此時如何去檢查內存泄漏也是一個值得研究的方向。

相關文章
相關標籤/搜索