Dr Memory 簡介
Dr. Memory 是一個開源免費的內存檢測工具,它能夠及時發現內存相關的編程錯誤,比如未初始化訪問、內存非法訪問以及內存泄露等。它不僅能夠在 Linux 下面工作,也能在微軟的 Windows 操作系統上工作。不過,本文撰寫時,DrMemory 僅能支持 32 位程序,這是它的一個巨大缺陷,但相信隨着開發的進行,DrMemory 會推出支持 64 位程序的版本。
Dr Memory 與 Valgrind 類似,可以直接檢查已經編譯好的可執行文件。用戶不用改寫被檢查程序的源代碼,也無須重新鏈接第三方庫文件,使用起來非常方便。
Dr. Memory 建立在 DynamoRIO 這個動態二進制插樁平台上。動態監測程序的運行,並對內存訪問相關的執行代碼進行動態修改,記錄其行為,並采用先進的算法進行錯誤檢查。
根據 DrMemory 開發人員發表在 CGO 2011上的論文 Practical Memory Checking with Dr. Memory,DrMemory 對程序的正常執行影響較小,這在同類工具中是比較領先的。其 performance 和 Valgrind 的比較如圖 1 所示(圖片源自 DrMemory 主頁):
圖 1. 和 Valgrind 的性能比較
Valgrind 對程序的正常運行影響較大,一般來說如果進行全面內存檢測,會使程序的運行速度有 50 到 300 倍的減慢。而 DrMemory 在這個方面則有一定的優勢。
易用性和性能是 DrMemory 的主要優點,此外 DrMemory 可以用於調試 Windows 程序,因此它被廣泛認為是 Windows 上的 Valgrind 替代工具。在 Linux 平台中,DrMemory 也往往可以作為 Valgrind 之外的另一個選擇。
DrMemory 對內存泄露的監測采用了比較獨特的算法,大量減少了”false positive”,即虛假錯誤。如果您使用 Valgrind 等工具后仍無法找到程序中的內存錯誤,不妨試試 DrMemory 吧。
在 Linux 上,DrMemory 的目前版本尚不能調試 64 位程序,這是它的一個比較大的缺點。
DrMemory 的安裝
在 Linux 上,安裝 Dr Memory 非常簡單,簡單地將下載包解壓即可,如
tar –xzvf DrMemory-Linux-1.4.6-2.tar.gz
要想使用 DrMemory,要保證下面這些軟件已經正確安裝:
perl、objdump、addr2line。
在任何一個當前的 Linux 發行版中,這幾個軟件應該都已經安裝了,因此基本上您只需要下載 DrMemory 的 tar 包,然后解壓即可使用了。
Windows 上 DrMemory 提供了可執行安裝包,只需點擊下一步,即可安裝完畢。
Hello 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 錯誤報告類型
DrMemory總共可以檢測出4種主要錯誤他們分別是內存非法訪問(Unaddressable Access)、未初始化讀(Uninitialized Access)、Heap 操作參數錯誤(Invalid Heap Argument) 、內存泄漏(Memory Leaks),下面對這幾種主要錯誤來進行詳細講解:
1)內存非法訪問(Unaddressable Access)
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 的分布如下圖所示:
圖 2. 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。這里給出的是造成錯誤的具體指令。
2)未初始化讀(Uninitialized Access)
讀取未初始化的內存其結果是未知的,使用這樣的數據是很危險的。讓我們查看下面這個測試程序(並不危險的程序):
示例程序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 表明這是一個未初始化讀錯誤。這是常見的類成員變量沒有進行初始化錯誤
3)Heap 操作參數錯誤(Invalid Heap Argument)
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 操作參數錯誤。
4)內存泄漏(Memory Leaks)
內存泄露是常見的內存錯誤,我們可能都曾經遇到過。不過 Dr.Memory 對內存泄露的定義比較獨特,在程序退出之前,Dr.Memory 把所有依然被分配的內存分為三類:
Still-reachable allocation
很多程序分配了內存之后,在其整個生命周期內都不釋放。雖然這是一種泄露,但實際上多數情況下這是無害的,甚至是特意這樣設計的。因此 Dr.Memory 並不認為這是一種內存泄露,而稱之為”Still-reachable allocation”。
Leak
有一些內存無法再被釋放,因為指向該內存的指針丟失了。比如下面這個代碼:
內存 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”。
參考下圖,理解這種情況。
圖 4.mid-chunk new
從堆分配器的角度來看,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 試一下。