1. 程式人生 > >C 記憶體洩漏檢測工具

C 記憶體洩漏檢測工具

所有使用動態記憶體分配(dynamic memory allocation)的程式都有機會遇上記憶體洩露(memory leakage)問題,在Linux裡有三種常用工具來檢測記憶體洩露的情況,包括:

  1. mtrace
  2. dmalloc
  3. memwatch

1. mtrace

from:

mtrace是三款工具之中是最簡單易用的,mtrace是一個C函式,在<mcheck.h>裡宣告及定義,函式原型為:

    void mtrace(void);

其實mtrace是類似malloc_hook的 malloc handler,只不過mtrace的handler function已由系統為你寫好,但既然如此,系統又怎麼知道你想將malloc/free的記錄寫在哪裡呢?為此,呼叫mtrace()前要先設定 MALLOC_TRACE環境變數:

    #include <stdlib.h>     ....     setenv("MALLOC_TRACE", "output_file_name", 1);     ...

「output_file_name」就是儲存檢測結果的檔案的名稱。

但是檢測結果的格式是一般人無法理解的,而只要有安裝mtrace的話,就會有一名為mtrace的Perl script,在shell輸入以下指令:

    mtrace [binary] output_file_name

就會將output_file_name的內容轉化成能被理解的語句,例如「No memory leaks」,「0x12345678 Free 10 was never alloc」諸如此類。

例如以下有一函式:(暫且放下single entry single exit的原則)

    #include <stdio.h>     #include <stdlib.h>     #include <errno.h>     #include <mcheck.h>     int main() {         char *hello;             setenv("MALLOC_TRACE", "output", 1);         mtrace();         if ((hello = (char *) malloc(sizeof(char))) == NULL) {             perror("Cannot allocate memory.");             return -1;         }         return 0;     }

執行後,再用mtrace 將結果輸出:

    - 0x08049670 Free 3 was never alloc'd 0x42029acc     - 0x080496f0 Free 4 was never alloc'd 0x420dc9e9     - 0x08049708 Free 5 was never alloc'd 0x420dc9f1     - 0x08049628 Free 6 was never alloc'd 0x42113a22     - 0x08049640 Free 7 was never alloc'd 0x42113a52     - 0x08049658 Free 8 was never alloc'd 0x42113a96     Memory not freed:     -----------------        Address     Size     Caller     0x08049a90      0x1  at 0x80483fe

最後一行標明有一個大小為1 byte的記憶體尚未釋放,大概是指「hello」吧。

    若我們把該段記憶體釋放:

    #include <stdio.h>     #include <stdlib.h>     #include <errno.h>     #include <mcheck.h>     int main() {         char *hello;             setenv("MALLOC_TRACE", "output", 1);         mtrace();         if ((hello = (char *) malloc(sizeof(char))) == NULL) {             perror("Cannot allocate memory.");             return -1;         }         free(hello);         return 0;     }

結果如下:

    - 0x080496b0 Free 4 was never alloc'd 0x42029acc     - 0x08049730 Free 5 was never alloc'd 0x420dc9e9     - 0x08049748 Free 6 was never alloc'd 0x420dc9f1     - 0x08049668 Free 7 was never alloc'd 0x42113a22     - 0x08049680 Free 8 was never alloc'd 0x42113a52     - 0x08049698 Free 9 was never alloc'd 0x42113a96     No memory leaks.

mtrace的原理是記錄每一對malloc-free的執行,若每一個malloc都有相應的free,則代表沒有記憶體洩露,對於任何非malloc/free情況下所發生的記憶體洩露問題,mtrace並不能找出來。

但是mtrace在Red Hat 9的安裝包並不提供,必須自己編譯才能用哦。

wget --passive-ftp ftp://rpmfind.net/linux/redhat/9... -2.3.2-11.9.src.rpm   rpm -ivh glibc*.src.rpm   cd /usr/src/redhat/SPECS/   rpmbuild -ba glibc-9.spec   cd /var/tmp/glibc-2.3.2-root/usr/bin/   cp mtrace /usr/bin/

2. 使用dmalloc&valgrind檢測記憶體洩露

dmalloc是一個簡單易用的C/C++記憶體leak檢查工具,以一個執行庫的方式釋出。dmalloc能夠檢查出直到程式執行結束還沒有釋放的記憶體,

並且能夠精確指出在哪個原始檔的第幾行。

支援的平臺:AIX, BSD/OS, DG/UX, Free/Net/OpenBSD, GNU/Hurd, HPUX, Irix, Linux, MS-DOG, NeXT, OSF, SCO, Solaris, SunOS, 

Ultrix, Unixware, Windoze, and even Unicos on a Cray T3E. 最新版本:5.5.2.

1. $tar zxvf dmalloc-5.5.2.Tgz  2.$cd dmalloc-5.5.2   3. $./configure  4.$make;sudo make install

設定環境變數:

在terminal輸入export DMALLOC_OPTIONS=log=logfile, debug=0x3(in Bash)/export DMALLOC_OPTIONS=debug 0x3,log=logfile(in Csh).

或者在terminal輸入dmalloc -l logfile -i 100 low,然後重新登陸使用者,或者執行: source ~/.bashrc 或 source ~/.profile

在原始檔中新增下面的C程式碼:

#ifdef DMALLOC

#include "dmalloc.h"

#endif

值得注意的是:要在每一個.C檔案裡面新增,而且必須新增在所包含的標頭檔案最後一行!

編譯使用的CFLAGS: -DDMALLOC -DDMALLOC_FUNC_CHECK

如: gcc -DDMALLOC -DDMALLOC_FUNC_CHECK (-ldmalloc) dm_test.c

執行:   ./a.out

執行上面的命令會在當前目錄下生成 logfile檔案,開啟logfile,那麼哪個地方的記憶體leak就一目瞭然了。

例子:

      #include <stdio.h>

      #include <stdlib.h>

      #include <string.h>

      #ifdef DMALLOC

      #include <dmalloc.h>

      #endif

      int main(int argc, char **argv)

      {

          char *string;

          string = malloc(sizeof(char));

          string = malloc(sizeof(int*));

          return 0;

      }

    Dmalloc的侷限性:

1. Dmalloc只能檢測堆上記憶體,對棧記憶體和靜態記憶體無能為力。

2. dmalloc只用於利用malloc申請的記憶體,對使用sbrk()或mmap()分配的記憶體無能為力。

3. dmalloc不能用於檢測讀寫沒有申請的或者沒有初始化的記憶體,也不能檢測寫只讀記憶體。

Valgrind是一個GPL的軟體,用於Linux(For x86, amd64 and ppc32)程式的記憶體除錯和程式碼剖析。你可以在它的環境中執行你的
程式來監視記憶體的使用情況,比如C 語言中的malloc和free或者 C++中的new和 delete。使用Valgrind的工具包,你可以自動的檢
測許多記憶體管理和執行緒的bug,避免花費太多的時間在bug尋找上,使得你的程式更加穩固。
Valgrind的主要功能
Valgrind工具包包含多個工具,如Memcheck,Cachegrind,Helgrind, Callgrind,Massif。下面分別介紹個工具的作用:
Memcheck 工具主要檢查下面的程式錯誤:
使用未初始化的記憶體 (Use of uninitialised memory)
使用已經釋放了的記憶體 (Reading/writing memory after it has been free’d)
使用超過 malloc分配的記憶體空間(Reading/writing off the end of malloc’d blocks)
對堆疊的非法訪問 (Reading/writing inappropriate areas on the stack)
申請的空間是否有釋放 (Memory leaks – where pointers to malloc’d blocks are lost forever)
malloc/free/new/delete申請和釋放記憶體的匹配(Mismatched use of malloc/new/new [] vs free/delete/delete [])
src和dst的重疊(Overlapping src and dst pointers in memcpy() and related functions)
Callgrind
Callgrind收集程式執行時的一些資料,函式呼叫關係等資訊,還可以有選擇地進行cache 模擬。在執行結束時,它會把分析資料寫入
一個檔案。callgrind_annotate可以把這個檔案的內容轉化成可讀的形式。
Cachegrind
它模擬 CPU中的一級快取I1,D1和L2二級快取,能夠精確地指出程式中 cache的丟失和命中。如果需要,它還能夠為我們提供cache丟失次數,
記憶體引用次數,以及每行程式碼,每個函式,每個模組,整個程式產生的指令數。這對優化程式有很大的幫助。
Helgrind
它主要用來檢查多執行緒程式中出現的競爭問題。Helgrind 尋找記憶體中被多個執行緒訪問,而又沒有一貫加鎖的區域,這些區域往往是執行緒之間
失去同步的地方,而且會導致難以發掘的錯誤。Helgrind實現了名為”Eraser” 的競爭檢測演算法,並做了進一步改進,減少了報告錯誤的次數。
Massif
堆疊分析器,它能測量程式在堆疊中使用了多少記憶體,告訴我們堆塊,堆管理塊和棧的大小。Massif能幫助我們減少記憶體的使用,在帶有虛擬
記憶體的現代系統中,它還能夠加速我們程式的執行,減少程式停留在交換區中的機率。
Valgrind 安裝
1、 到www.valgrind.org下載最新版valgrind-3.2.3.tar.bz2
2、 解壓安裝包:tar –jxvf valgrind-3.2.3.tar.bz2
3、 解壓後生成目錄valgrind-3.2.3
4、 cd valgrind-3.2.3
5、 ./configure
6、 make;sudo make install
注意:不要移動Valgrind到一個與--prefix指定的不一樣的目錄,這將導致一些莫名其妙的錯誤,大多數在Valgrind處理/fork/exec呼叫時。
1.檢查記憶體錯誤:
例如我們原來有一個程式test,這是一個用gcc –g引數編譯的程式,執行它需要:
#./a.out
如果我們想用valgrind的記憶體檢測工具,我們就要用如下方法呼叫:
#valgrind --leak-check=full --show-reachable=yes --trace-children= yes   ./a.out
logfile加上會好些,程式在執行期間stderr會有一些輸出。加上logfile的話可以像dmalloc那樣開啟logfile來檢視錯誤資訊。
其中--leak-check=full 指的是完全檢查記憶體洩漏,--show-reachable=yes是顯示記憶體洩漏的地點,--trace-children=yes是跟入子程序。
如果您的程式是會正常退出的程式,那麼當程式退出的時候valgrind自然會輸出記憶體洩漏的資訊。如果您的程式是個守護程序,那麼也不要緊,
我們 只要在別的終端下殺死memcheck程序(因為valgrind預設使用memcheck工具,就是預設引數—tools=memcheck):
#killall memcheck
這樣我們的程式(./a.out)就被kill了
2.檢查程式碼覆蓋和效能瓶頸:
我們呼叫valgrind的工具執行程式:
#valgrind --tool=callgrind ./sec_infod
會在當前路徑下生成callgrind.out.pid(當前生產的是callgrind.out.19689),如果我們想結束程式,可以:
#killall callgrind
然後我們看一下結果:
#callgrind_annotate --auto=yes callgrind.out.19689   >log
#vim log
3.Valgrind使用引數
          --log-fd=N 預設情況下,輸出資訊是到標準錯誤stderr,也可以通過—log-fd=8,輸出到描述符為8的檔案
          --log-file=filename將輸出的資訊寫入到filename.PID的檔案裡,PID是執行程式的進行ID。可以通過--log- file
                     exactly=filename指定就輸出到filename檔案。
          --log-file-qualifier=<VAR>,取得環境變數的值來做為輸出資訊的檔名。如—log-file- qualifier=$FILENAME。
          --log-socket=IP:PORT 也可以把輸出資訊傳送到網路中指定的IP:PORT去
          --error-limit=no 對錯誤報告的個數據進行限制,預設情況不做限制
          --tool=<toolname> [default: memcheck]
          --tool=memcheck:要求用memcheck這個工具對程式進行分析
          --leak-ckeck=yes 要求對leak給出詳細資訊
          --trace-children=<yes|no> [default: no]跟蹤到子程序裡去,預設請況不跟蹤
          --xml=<yes|no> [default: no]將資訊以xml格式輸出,只有memcheck可用
          --gen-suppressions=<yes|no|all> [default: no]如果為yes,valgrind會在每發現一個錯誤便停下讓使用者做選擇是繼續還是退出
更多選項請參看: http://www.valgrind.org/docs/manual/manual-core.html可以把一些預設選項編輯在~/.valgrindrc檔案裡。
這裡使用valgrind的memcheck和callgrind兩個工具的用法,其實valgrind還有幾個工具:“cachegrind”,用於檢查快取使用的;“helgrind”
用於檢測多執行緒競爭資源的,等等。
 與dmalloc相比,Valgrind使用範圍更廣,它不但能夠檢測出堆記憶體的洩露,對執行緒間使用共同資源所帶來的記憶體洩露也能夠察覺出來,
 但是valgrind所產生的log資訊往往比dmalloc多得多,有時候就是一千多行的log資訊,讓人看到就頭痛。
3.memwatch檢視linux記憶體洩露
1. 介紹
       
2. 使用
       
memwatch不需要安裝,只要下載包解壓即可,有用的檔案只有memwatch.c&memwatch.h,把這兩個檔案放入要檢測的程式的資料夾中即可。
編譯的命令為:gcc -DMEMWATCH -DMW_STDIO test.c memwatch.c -o test 
 3. 例項
    
編寫如下程式碼                 
  1. #include <stdio.h>

  2. #include <stdlib.h>

  3. #include <errno.h>

  4. #include <memwatch.h>

  5. int main()

  6. {

  7. char *hello;

  8. setenv("MALLOC_TRACE", "output", 1);

  9. mtrace();

  10. if ((hello = (char *) malloc(sizeof(char))) == NULL)

  11. {

  12. perror("Cannot allocate memory.");

  13. return -1;

  14. }

  15. return 0;

  16. }

編譯: gcc -DMEMWATCH  -DMW_STDIO test.c memwatch.c -o test

4. 執行結果,memwatch.log如下:

============= MEMWATCH 2.71 Copyright (C) 1992-1999 Johan Lindh ============= Started at Sat Jun 26 22:48:47 2004 Modes: __STDC__ 32-bit mwDWORD==(unsigned long) mwROUNDALLOC==4 sizeof(mwData)==32 mwDataSize==32 Stopped at Sat Jun 26 22:48:47 2004 unfreed: <1> test.c(9), 1 bytes at 0x805108c     {FE .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .} Memory usage statistics (global): N)umber of allocations made: 1 L)argest memory usage       : 1 T)otal of all alloc() calls: 1 U)nfreed bytes totals       : 1