Dr. Memory 是一個開源免費的內存檢測工具,它可以及時發現內存相關的編程錯誤,好比未初始化訪問、內存非法訪問以及內存泄露等。它不只可以在 Linux 下面工做,也能在微軟的 Windows 操做系統上工做。不過,本文撰寫時,DrMemory 僅能支持 32 位程序,這是它的一個巨大缺陷,但相信隨着開發的進行,DrMemory 會推出支持 64 位程序的版本。html
Dr Memory 與 Valgrind 相似,能夠直接檢查已經編譯好的可執行文件。用戶不用改寫被檢查程序的源代碼,也無須從新連接第三方庫文件,使用起來很是方便。ios
Dr. Memory 創建在 DynamoRIO 這個動態二進制插樁平臺上。動態監測程序的運行,並對內存訪問相關的執行代碼進行動態修改,記錄其行爲,並採用先進的算法進行錯誤檢查。算法
根據 DrMemory 開發人員發表在 CGO 2011上的論文 Practical Memory Checking with Dr. Memory,DrMemory 對程序的正常執行影響較小,這在同類工具中是比較領先的。其 performance 和 Valgrind 的比較如圖 1 所示(圖片源自 DrMemory 主頁):編程
Valgrind 對程序的正常運行影響較大,通常來講若是進行全面內存檢測,會使程序的運行速度有 50 到 300 倍的減慢。而 DrMemory 在這個方面則有必定的優點。數組
易用性和性能是 DrMemory 的主要優勢,此外 DrMemory 能夠用於調試 Windows 程序,所以它被普遍認爲是 Windows 上的 Valgrind 替代工具。在 Linux 平臺中,DrMemory 也每每能夠做爲 Valgrind 以外的另外一個選擇。數據結構
DrMemory 對內存泄露的監測採用了比較獨特的算法,大量減小了」false positive」,即虛假錯誤。若是您使用 Valgrind 等工具後仍沒法找到程序中的內存錯誤,不妨試試 DrMemory 吧。函數
在 Linux 上,DrMemory 的目前版本尚不能調試 64 位程序,這是它的一個比較大的缺點。工具
在 Linux 上,安裝 Dr Memory 很是簡單,簡單地將下載包解壓便可,如性能
tar –xzvf DrMemory-Linux-1.4.6-2.tar.gz
要想使用 DrMemory,要保證下面這些軟件已經正確安裝:學習
perl、objdump、addr2line。
在任何一個當前的 Linux 發行版中,這幾個軟件應該都已經安裝了,所以基本上您只須要下載 DrMemory 的 tar 包,而後解壓便可使用了。
Windows 上 DrMemory 提供了可執行安裝包,只需點擊下一步,便可安裝完畢。
DrMemory 的使用很簡單,能夠說它是傻瓜式。首先個人DrMemory安裝路徑是C:\Program Files (x86)\Dr. Memory\bin64\drmemory.exe;示例程序可執行文件路徑:C:\Users\31937\Desktop\test\bin\Debug\test.exe,而後在執行drmemory.exe C:\Users\31937\Desktop\test\bin\Debug\test.exe命令時,先須要將控制檯路徑切換到你的DrMemory安裝路徑下,而後執行drmemory.exe C:\Users\31937\Desktop\test\bin\Debug\test.exe命令
C:\Program Files (x86)\Dr. Memory\bin64>drmemory.exe C:\Users\31937\Desktop\test\bin\Debug\test.exe
示例程序1:
1 #include <stdlib.h> 2 using namespace std; 3 4 int main() 5 { 6 int *pPtr = (int *)malloc(sizeof(int)); 7 return 0; 8 }
執行完命令後控制檯顯示的結果爲:
屏幕上會有如上所示的錯誤彙總,4 byte(s) of leak(s) 而且將定位在main.cpp的第6行。不錯吧。根據提示,更多的細節被寫入一個 result 文本文件。打開並查看該文件,就能夠知道程序在哪裏出現了內存錯誤了。真是太方便了。不過 result 文件是否容易閱讀呢?下面咱們來詳細解釋如何閱讀 DrMemory 產生的 result 文件。
DrMemory總共能夠檢測出4種主要錯誤他們分別是內存非法訪問(Unaddressable Access)、未初始化讀(Uninitialized Access)、Heap 操做參數錯誤(Invalid Heap Argument) 、內存泄漏(Memory Leaks),下面對這幾種主要錯誤來進行詳細講解:
DrMemory 認爲任何對未分配內存區域的讀寫都是非法的。
非法訪問就是對以上三種方法分配的內存區域以外進行的訪問。常見的問題包括 buffer overflow、數組越界、讀寫已經 free 的內存、堆棧溢出等等。讓咱們測試下面這個問題程序。
例子程序2:
1 #include <iostream> 2 #include <stdlib.h> 3 using namespace std; 4 5 int main() 6 { 7 char *x = (char *)malloc(sizeof(char)); 8 char c = *(x+8); //buffer overlow 11 return 0; 12 }
Buffer overflow
例子程序的第8 行存在 buffer overflow。在內存中,buffer 的分佈以下圖所示:
訪問 x+8 將產生一個非法內存訪問。對此,Dr Memory 將給出以下的錯誤信息:
首先用大寫的單詞 UNADDRESSABLE ACCESS 代表這是一個非法訪問錯誤。接着,「reading 0x01397620-0x01397621 1 byte(s)」表示這是一個非法讀,讀取的範圍爲 0x01397620到 0x01397621,一共讀了 1 個 byte。接下來的三行是調用堆棧信息,能夠方便地看到錯誤發生在哪一個源文件的哪一行(程序 t 須要在用 gcc 編譯的時候給定-g 選項)。此外 DrMemory 還給出了一些輔助的錯誤信息。好比:
1.錯誤發生的位置:# 0 main [C:/Users/31937/Desktop/test/main.cpp:8]
2.錯誤發生的時間:Note: @0:00:00.516 in thread 9716。這代表錯誤是程序開始的第 0.516 秒後發生的,有些狀況下,人們能夠根據這個時間進行輔助判斷。
3.錯誤細節:Note: refers to 7 byte(s) beyond last valid byte in prior malloc。這裏給出了錯誤的詳細信息,如前所述,形成非法訪問的可能不少,在本例中是 buffer overflow,所以這裏的詳細信息能夠幫助咱們瞭解非法訪問的具體緣由。
Note: prev lower malloc: 0x01397618-0x01397619。這裏給出了 overflow 以前的合法內存地址,有些狀況下對於查錯 有必定的幫助。
Note: instruction: mov 0x08(%eax) -> %al。這裏給出的是形成錯誤的具體指令。
讀取未初始化的內存其結果是未知的,使用這樣的數據是很危險的。讓咱們查看下面這個測試程序(並不危險的程序):
示例程序3:
1 #include <iostream> 2 #include <stdlib.h> 3 using namespace std; 4 5 class Test 6 { 7 public:int m_iNum; 8 }; 9 int main() 10 { 11 Test pTest; 12 cout<<pTest.m_iNum; 13 return 0; 14 }
運行結果:
首先用大寫的單詞 UNINITIALIZED READ 代表這是一個未初始化讀錯誤。這是常見的類成員變量沒有進行初始化錯誤
C 語言用 malloc()、free()等函數處理內存 heap 的使用。若是使用不當,會形成未知後果,好比傳入 free()的參數不正確,可能形成 crash,或者用 new 分配,卻用 free 來釋放內存。這類錯誤 DrMemory 稱之爲 Invalid Heap Argument 錯誤。
示例程序4:
1 #include <iostream> 2 #include <stdlib.h> 3 using namespace std; 4 5 int main() 6 { 7 int *pPtr = (int *)malloc(sizeof(int)); 8 free(pPtr); 9 free(pPtr); 10 return 0; 11 }
運行結果
首先用大寫的單詞 INVALID HEAP ARGUMENT 代表這是一個Heap 操做參數錯誤。
內存泄露是常見的內存錯誤,咱們可能都曾經遇到過。不過 Dr.Memory 對內存泄露的定義比較獨特,在程序退出以前,Dr.Memory 把全部依然被分配的內存分爲三類:
Still-reachable allocation
不少程序分配了內存以後,在其整個生命週期內都不釋放。雖然這是一種泄露,但實際上多數狀況下這是無害的,甚至是特地這樣設計的。所以 Dr.Memory 並不認爲這是一種內存泄露,而稱之爲」Still-reachable allocation」。
Leak
有一些內存沒法再被釋放,由於指向該內存的指針丟失了。好比下面這個代碼:
1 char *ptr = (char *)malloc(sizeof(char)*10); 2 char *ptr1 = (char *)malloc(sizeof(char)*100);
3 ptr=ptr1; //leak
DrMemory 稱這類錯誤爲內存泄露。由於這些內存已經沒有辦法被釋放了。
Possible Leak
如前所述指向內存的指針被修改會被認爲是一個 Leak,但並不是全部的指針修改都是一個 Leak。DrMemory 利用一些經驗規則(Heuristic)將如下幾種指針修改列爲 Possible Leak。
第一種狀況:C++程序利用 new[]分配了一個數組,該數組的每一個元素都是 擁有本身的析構函數的複雜數據結構。這種狀況下,New 操做符爲每一個元素加上一個 header 用來保存數組的個數,以便 delete[]操做符知道須要調用多少個析構函數。但 new[]返回 caller 的是 header 以後的地址,這樣就變成了一個 mid-allocation 指針。這可能被 Dr memory 認爲是一個內存泄露。但可使用-no_midchunk_new_ok 選項讓 DrMemory 將這類錯誤報告爲」possible leak」而非」leak」。
參考下圖,理解這種狀況。
從堆分配器的角度來看,buffer 的起點在 A 處,但 new 返回 B,給 Object 變量賦值。從某種角度上看,指針 A 丟失了,是一個 leak,但實際上,當調用 delete []操做符時,C++運行時庫會自動將 Object 指針減 4,從而指向 A 點,再進行釋放。某些編譯器不使用這種作法,則沒有這個問題。
第二種狀況,某些 C++編譯器在處理多繼承時,會出現 mid-chunk 指針。很抱歉,具體細節本人也不甚瞭解。Dr Memory 的原文以下:it includes instances of a pointer to a class with multiple inheritance that is cast to one of the parents: it can end up pointing to the subobject representation in the middle of the allocation. 您能夠用-no_midchunk_inheritance_ok 選項將這類「錯誤」報告爲」possible leak」 。
還有一種可能:std::string 類把一個 char[]數組放置在分配空間中,並返回一個指針直接指向它,形成了一個 mid-allocation 指針。您能夠用-no_midchunk_string_ok 選項讓這類錯誤顯示爲」possible leak」。
示例程序5:
1 #include <stdlib.h>
2 using namespace std; 3 4 int main() 5 { 6 int *pPtr = (int *)malloc(sizeof(int)); 7 return 0; 8 }
顯示的結果:
屏幕上會有如上所示的錯誤彙總,4 byte(s) of leak(s) 而且將定位在main.cpp的第6行。不錯吧。根據提示,更多的細節被寫入一個 result 文本文件。打開並查看該文件,就能夠知道程序在哪裏出現了內存錯誤了。真是太方便了。不過 result 文件是否容易閱讀呢?下面咱們來詳細解釋如何閱讀 DrMemory 產生的 result 文件。
很高興也很遺憾我能爲你們介紹一款新的內存調試工具。咱們恐怕已經面臨太多的選擇,假如您用 Google 搜索,會找到不少相似的工具,他們中的多數都不易使用,也許您花了不少的精力去學習某款工具的使用,卻發現它根本就不適合您的環境。
惋惜,不一樣的工具備不一樣的優勢和缺點,直到今天,尚沒有一款工具可以替代全部其它的同類。寫程序有時很無奈,尤爲是面對內存錯誤的時候,多一個選擇也許會讓你擺脫困境。下一次,假如人們告訴您程序有內存泄露,那麼不妨用 DrMemory 試一下。