點評五款用於 Linux 編程的內存調試器
Credit: Moini
作為一個程序員,我知道我肯定會犯錯誤——怎麼可能不犯錯!程序員也是人啊。有的錯誤能在編碼過程中及時發現,而有些卻得等到軟體測試了才能顯露出來。然而,還有一類錯誤並不能在這兩個階段被解決,這就導致軟體不能正常運行,甚至是提前終止。
如果你還沒猜出是那種錯誤,我說的就是和內存相關的錯誤。手動調試這些錯誤不僅耗時,而且很難發現並糾正。值得一提的是,這種錯誤很常見,特別是在用 C/C++ 這類允許手動管理內存的語言編寫的軟體里。
幸運的是,現在有一些編程工具能夠幫你在軟體程序中找到這些和內存相關的錯誤。在這些工具集中,我評估了五款支持 Linux 的、流行的、自由開源的內存調試器: Dmalloc 、 Electric Fence 、 Memcheck 、 Memwatch 以及 Mtrace 。在日常編碼中,我已經用過這五個調試器了,所以這些評估是建立在我的實際體驗之上的。
Dmalloc
開發者:Gray Watson
評估版本:5.5.2
支持的 Linux 版本:所有種類
許可: CC 3.0
Dmalloc 是 Gray Watson 開發的一款內存調試工具。它是作為庫來實現的,封裝了標準內存管理函數如malloc()
, calloc()
, free()
等,使程序員得以檢測出有問題的代碼。
如同工具的網頁所示,這個調試器提供的特性包括內存泄漏跟蹤、 重複釋放內存 錯誤跟蹤、以及 越界寫入 檢測。其它特性包括報告錯誤的文件/行號、通用的數據統計記錄。
更新內容
5.5.2 版本是一個 bug 修正發行版,修復了幾個有關構建和安裝的問題。
有何優點
Dmalloc 最大的優點就是高度可配置性。比如說,你可以配置它以支持 C++ 程序和多線程應用。 Dmalloc 還提供一個有用的功能:運行時可配置,這表示在 Dmalloc 執行時,可以輕易地啟用或者禁用它提供的一些特性。
你還可以配合 GNU Project Debugger (GDB)來使用 Dmalloc ,只需要將dmalloc.gdb
文件(位於 Dmalloc 源碼包中的 contrib 子目錄里)的內容添加到你的主目錄中的.gdbinit
文件里即可。
另外一個讓我對 Dmalloc 愛不釋手的優點是它有大量的資料文獻。前往官網的 Documentation 欄目,可以獲取所有關於如何下載、安裝、運行、怎樣使用庫,和 Dmalloc 所提供特性的細節描述,及其生成的輸出文件的解釋。其中還有一個章節介紹了一般問題的解決方法。
注意事項
跟 Mtrace 一樣, Dmalloc 需要程序員改動他們的源代碼。比如說你可以(也是必須的)添加頭文件dmalloc.h
,工具就能彙報產生問題的調用的文件或行號。這個功能非常有用,因為它節省了調試的時間。
除此之外,還需要在編譯你的程序時,把 Dmalloc 庫(編譯 Dmalloc 源碼包時產生的)鏈接進去。
然而,還有點更麻煩的事,需要設置一個環境變數,命名為DMALLOC_OPTION
,以供工具在運行時配置內存調試特性,比如定義輸出文件的路徑。可以手動為該環境變數分配一個值,不過初學者可能會覺得這個過程有點困難,因為該值的一部分用來表示要啟用的 Dmalloc 特性——以十六進位值的累加值表示。這裡有詳細介紹。
一個比較簡單方法設置這個環境變數是使用 Dmalloc 實用指令,這是專為這個目的設計的方法。
總結
Dmalloc 真正的優勢在於它的可配置選項。而且高度可移植,曾經成功移植到多種操作系統如 AIX 、 BSD/OS 、 DG/UX 、 Free/Net/OpenBSD 、 GNU/Hurd 、 HPUX 、 Irix 、 Linux 、 MS-DOG 、 NeXT 、 OSF 、 SCO 、 Solaris 、 SunOS 、 Ultrix 、 Unixware 甚至 Unicos(運行在 Cray T3E 主機上)。雖然使用 Dmalloc 需要學習許多知識,但是它所提供的特性值得為之付出。
Electric Fence
開發者:Bruce Perens
評估版本:2.2.3
支持的 Linux 版本:所有種類
許可:GPL v2
Electric Fence 是 Bruce Perens 開發的一款內存調試工具,它以庫的形式實現,你的程序需要鏈接它。Electric Fence 能檢測出堆內存溢出和訪問已經釋放的內存。
Electric Fence
顧名思義, Electric Fence 在每個所申請的緩存邊界建立了虛擬圍欄,這樣一來任何非法的內存訪問都會導致段錯誤。這個調試工具同時支持 C 和 C++ 程序。
更新內容
2.2.3 版本修復了工具的構建系統,使得 -fno-builtin-malloc
選項能真正傳給 GNU Compiler Collection (GCC)。
有何優點
我喜歡 Electric Fence 的首要一點是它不同於 Memwatch 、 Dmalloc 和 Mtrace ,不需要對你的源碼做任何的改動,你只需要在編譯的時候把它的庫鏈接進你的程序即可。
其次, Electric Fence 的實現保證了產生越界訪問的第一個指令就會引起段錯誤。這比在後面再發現問題要好多了。
不管是否有檢測出錯誤, Electric Fence 都會在輸出產生版權信息。這一點非常有用,由此可以確定你所運行的程序已經啟用了 Electric Fence 。
注意事項
另一方面,我對 Electric Fence 真正念念不忘的是它檢測內存泄漏的能力。內存泄漏是 C/C++ 軟體最常見也是最不容易發現的問題之一。不過, Electric Fence 不能檢測出棧溢出,而且也不是線程安全的。
由於 Electric Fence 會在用戶分配內存區的前後分配禁止訪問的虛擬內存頁,如果你過多的進行動態內存分配,將會導致你的程序消耗大量的額外內存。
Electric Fence 還有一個局限是不能明確指出錯誤代碼所在的行號。它所能做只是在檢測到內存相關錯誤時產生段錯誤。想要定位錯誤的行號,需要藉助 GDB這樣的調試工具來調試啟用了 Electric Fence 的程序。
最後一點,儘管 Electric Fence 能檢測出大部分的緩衝區溢出,有一個例外是,如果所申請的緩衝區大小不是系統字長的倍數,這時候溢出(即使只有幾個位元組)就不能被檢測出來。
總結
儘管局限性較大, Electric Fence 的易用性仍然是加分項。只要鏈接一次程序, Electric Fence 就可以在監測出內存相關問題的時候報警。不過,如同前面所說, Electric Fence 需要配合像 GDB 這樣的源碼調試器使用。
Memcheck
開發者:Valgrind 開發團隊
評估版本:3.10.1
支持的 Linux 發行版:所有種類
許可:GPL
Valgrind 是一個提供好幾款調試和分析 Linux 程序性能的工具的套件。雖然 Valgrind 能和不同語言——Java 、 Perl 、 Python 、 Assembly code 、 ortran 、 Ada 等——編寫的程序一起工作,但是它主要還是針對使用 C/C++ 所編寫的程序。
Memcheck ,一款內存錯誤檢測器,是其中最受歡迎的工具。它能夠檢測出如內存泄漏、無效的內存訪問、未定義變數的使用以及堆內存分配和釋放相關的問題等諸多問題。
更新內容
工具套件( 3.10.1 )主要修復了 3.10.0 版本發現的 bug 。除此之外,「從主幹開發版本向後移植的一些補丁,修復了缺失的 AArch64 ARMv8 指令和系統調用」。
有何優點
同其它所有 Valgrind 工具一樣, Memcheck 也是命令行程序。它的操作非常簡單:通常我們會使用諸如 prog arg1 arg2
格式的命令來運行程序,而 Memcheck 只要求你多加幾個值即可,如 valgrind --leak-check=full prog arg1 arg2
。
Memcheck
(注意:因為 Memcheck 是 Valgrind 的默認工具,所以在命令行執行命令時無需提及 Memcheck。但是,需要在編譯程序之初帶上 -g
參數選項,這一步會添加調試信息,使得 Memcheck 的錯誤信息會包含正確的行號。)
我真正傾心於 Memcheck 的是它提供了很多命令行選項(如上所述的--leak-check
選項),如此不僅能控制工具運轉還可以控制它的輸出。
舉個例子,可以開啟--track-origins
選項,以查看程序源碼中未初始化的數據;可以開啟--show-mismatched-frees
選項讓 Memcheck 匹配內存的分配和釋放技術。對於 C 語言所寫的代碼, Memcheck 會確保只能使用free()
函數來釋放內存,malloc()
函數來申請內存。而對 C++ 所寫的源碼, Memcheck 會檢查是否使用了delete
或delete[]
操作符來釋放內存,以及new
或者new[]
來申請內存。
Memcheck 最好的特點,尤其是對於初學者來說,是它會給用戶建議使用哪個命令行選項能讓輸出更加有意義。比如說,如果你不使用基本的--leak-check
選項, Memcheck 會在輸出時給出建議:「使用 --leak-check=full 重新運行以查看更多泄漏內存細節」。如果程序有未初始化的變數, Memcheck 會產生信息:「使用 --track-origins=yes 以查看未初始化變數的定位」。
Memcheck 另外一個有用的特性是它可以創建 抑制文件 ,由此可以略過特定的不能修正的錯誤,這樣 Memcheck 運行時就不會每次都報警了。值得一提的是, Memcheck 會去讀取默認抑制文件來忽略系統庫(比如 C 庫)中的報錯,這些錯誤在系統創建之前就已經存在了。可以選擇創建一個新的抑制文件,或是編輯現有的文件(通常是/usr/lib/valgrind/default.supp
)。
Memcheck 還有高級功能,比如可以使用定製內存分配器來檢測內存錯誤。除此之外, Memcheck 提供監控命令,當用到 Valgrind 內置的 gdbserver ,以及客戶端請求機制(不僅能把程序的行為告知 Memcheck ,還可以進行查詢)時可以使用。
注意事項
毫無疑問, Memcheck 可以節省很多調試時間以及省去很多麻煩。但是它使用了很多內存,導致程序執行變慢(由文檔可知,大概會花費 20 至 30 倍時間)。
除此之外, Memcheck 還有其它局限。根據用戶評論, Memcheck 很明顯不是線程安全的;它不能檢測出 靜態緩衝區溢出;還有就是,一些 Linux 程序如 GNU Emacs 目前還不能配合 Memcheck 工作。
如果有興趣,可以在這裡查看 Valgrind 局限性的詳細說明。
總結
無論是對於初學者還是那些需要高級特性的人來說, Memcheck 都是一款便捷的內存調試工具。如果你僅需要基本調試和錯誤檢查, Memcheck 會非常容易上手。而當你想要使用像抑制文件或者監控指令這樣的特性,就需要花一些功夫學習了。
雖然羅列了大量的局限性,但是 Valgrind(包括 Memcheck )在它的網站上聲稱全球有成千上萬程序員使用了此工具。開發團隊稱收到來自超過 30 個國家的用戶反饋,而這些用戶的工程代碼有的高達兩千五百萬行。
Memwatch
開發者:Johan Lindh
評估版本:2.71
支持的 Linux 發行版:所有種類
許可:GNU GPL
Memwatch 是由 Johan Lindh 開發的內存調試工具,雖然它扮演的主要角色是內存泄漏檢測器,但是(根據網頁介紹)它也具有檢測其它如內存重複釋放和錯誤釋放、緩衝區溢出和下溢、野指針寫入等等內存相關問題的能力。
Memwatch 支持用 C 語言所編寫的程序。也可以在 C++ 程序中使用它,但是這種做法並不提倡(由 Memwatch 源碼包隨附的 Q&A 文件中可知)。
更新內容
這個版本添加了ULONG_LONG_MAX
以區分 32 位和 64 位程序。
有何優點
跟 Dmalloc 一樣, Memwatch 也有優秀的文檔資料。參考 USING 文件,可以學習如何使用 Memwatch ,可以了解 Memwatch 是如何初始化、如何清理以及如何進行 I/O 操作,等等。還有一個 FAQ 文件,旨在幫助用戶解決使用過程遇到的一般問題。最後還有一個test.c
文件提供工作案例參考。
Memwatch
不同於 Mtrace , Memwatch 產生的日誌文件(通常是memwatch.log
)是人類可閱讀的格式。而且, Memwatch 每次運行時總會把內存調試結果拼接到輸出該文件的末尾。如此便可在需要之時輕鬆查看之前的輸出信息。
同樣值得一提的是當你執行了啟用 Memwatch 的程序, Memwatch 會在標準輸出中產生一個單行輸出,告知發現了錯誤,然後你可以在日誌文件中查看輸出細節。如果沒有產生錯誤信息,就可以確保日誌文件不會寫入任何錯誤,多次運行的話確實能節省時間。
另一個我喜歡的優點是 Memwatch 還提供了在源碼中獲取其輸出信息的方式,你可以獲取信息,然後任由你進行處理(參考 Memwatch 源碼中的mwSetOutFunc()
函數獲取更多有關的信息)。
注意事項
跟 Mtrace 和 Dmalloc 一樣, Memwatch 也需要你往你的源文件里增加代碼:你需要把memwatch.h
這個頭文件包含進你的代碼。而且,編譯程序的時候,你需要連同memwatch.c
一塊編譯;或者你可以把已經編譯好的目標模塊包含起來,然後在命令行定義MEMWATCH
和MW_STDIO
變數。不用說,想要在輸出中定位行號, -g 編譯器選項也少不了。
此外, Memwatch 缺少一些特性。比如 Memwatch 不能檢測出對一塊已經被釋放的內存進行寫入操作,或是在分配的內存塊之外的進行讀取操作。而且, Memwatch 也不是線程安全的。還有一點,正如我在開始時指出,在 C++ 程序上運行 Memwatch 的結果是不能預料的。
總結
Memcheck 可以檢測很多內存相關的問題,在處理 C 程序時是非常便捷的調試工具。因為源碼小巧,所以可以從中了解 Memcheck 如何運轉,有需要的話可以調試它,甚至可以根據自身需求擴展升級它的功能。
Mtrace
開發者: Roland McGrath 和 Ulrich Drepper
評估版本: 2.21
支持的 Linux 發行版:所有種類
許可:GNU GPL
Mtrace 是 GNU C 庫中的一款內存調試工具,同時支持 Linux 上的 C 和 C++ 程序,可以檢測由函數malloc()
和free()
不匹配的調用所引起的內存泄漏問題。
Mtrace
Mtrace 實際上是實現了一個名為mtrace()
的函數,它可以跟蹤程序中所有 malloc/free 調用,並在用戶指定的文件中記錄相關信息。文件以一種機器可讀的格式記錄數據,所以有一個 Perl 腳本——同樣命名為 mtrace ——用來把文件轉換並為人類可讀格式。
更新內容
Mtrace 源碼和 Perl 文件同 GNU C 庫( 2.21 版本)一起釋出,除了更新版權日期,其它別無改動。
有何優點
Mtrace 最好的地方是它非常簡單易學。你只需要了解在你的源碼中如何以及何處添加 mtrace()
及對應的 muntrace()
函數,還有如何使用 Mtrace 的 Perl 腳本。後者非常簡單,只需要運行指令mtrace <program-executable> <log-file-generated-upon-program-execution>
(例子見開頭截圖最後一條指令)。
Mtrace 另外一個優點是它的可伸縮性,這體現在不僅可以使用它來調試完整的程序,還可以使用它來檢測程序中獨立模塊的內存泄漏。只需在每個模塊里調用mtrace()
和muntrace()
即可。
最後一點,因為 Mtrace 會在mtrace()
——在源碼中添加的函數——執行時被觸發,因此可以很靈活地使用信號動態地(在程序執行時)使能 Mtrace 。
注意事項
因為mtrace()
和mauntrace()
函數 —— 聲明在mcheck.h
文件中,所以必須在源碼中包含此頭文件 —— 的調用是 Mtrace 工作的基礎(mauntrace()
函數並非總是必要),因此 Mtrace 要求程序員至少改動源碼一次。
需要注意的是,在編譯程序的時候帶上 -g 選項( GCC 和 G++ 編譯器均有提供),才能使調試工具在輸出結果時展示正確的行號。除此之外,有些程序(取決於源碼體積有多大)可能會花很長時間進行編譯。最後,帶 -g 選項編譯會增加了可執行文件的大小(因為提供了額外的調試信息),因此記得程序需要在測試結束後,不帶 -g 選項重新進行編譯。
使用 Mtrace ,你需要掌握 Linux 環境變數的基本知識,因為在程序執行之前,需要把用戶把環境變數MALLOC_TRACE
的值設為指定的文件(mtrace()
函數將會記錄全部信息到其中)路徑。
Mtrace 在檢測內存泄漏和試圖釋放未經過分配的內存方面存在局限。它不能檢測其它內存相關問題如非法內存訪問、使用未初始化內存。而且,有人抱怨 Mtrace 不是線程安全的。
總結
不言自明,我在此討論的每款內存調試器都有其優點和局限。所以,哪一款適合你取決於你所需要的特性,雖然有時候容易安裝和使用也是一個決定因素。
要想捕獲軟體程序中的內存泄漏, Mtrace 最適合不過了。它還可以節省時間。由於 Linux 系統已經預裝了此工具,對於不能聯網或者不可以下載第三方調試調試工具的情況, Mtrace 也是極有助益的。
另一方面,相比 Mtrace , Dmalloc 不僅能檢測更多錯誤類型,還提供更多特性,比如運行時可配置、 GDB 集成。而且, Dmalloc 不像這裡所說的其它工具,它是線程安全的。更不用說它的詳細資料了,這讓 Dmalloc 成為初學者的理想選擇。
雖然 Memwatch 的資料比 Dmalloc 的更加豐富,而且還能檢測更多的錯誤種類,但是你只能在 C 語言寫就的程序中使用它。一個讓 Memwatch 脫穎而出的特性是它允許在你的程序源碼中處理它的輸出,這對於想要定製輸出格式來說是非常有用的。
如果改動程序源碼非你所願,那麼使用 Electric Fence 吧。不過,請記住, Electric Fence 只能檢測兩種錯誤類型,而此二者均非內存泄漏。還有就是,需要基本了解 GDB 以最大化發揮這款內存調試工具的作用。
Memcheck 可能是其中綜合性最好的了。相比這裡提及的其它工具,它能檢測更多的錯誤類型,提供更多的特性,而且不需要你的源碼做任何改動。但請注意,基本功能並不難上手,但是想要使用它的高級特性,就必須學習相關的專業知識了。
via: http://www.computerworld.com/article/3003957/linux/review-5-memory-debuggers-for-linux-coding.html
作者:Himanshu Arora 譯者:soooogreen 校對:PurlingNayuki,ezio
本文轉載來自 Linux 中國: https://github.com/Linux-CN/archive