記憶體泄露檢測工具Valgrind

  • 2019 年 10 月 3 日
  • 筆記

記憶體泄露簡介

什麼是記憶體泄漏

  記憶體泄漏(Memory Leak)是指程式中已動態分配的堆記憶體由於某種原因,程式未釋放或無法釋放,造成系統記憶體的浪費,導致程式運行速度減慢甚至系統崩潰等嚴重後果。
  記憶體泄漏缺陷具有隱蔽性、積累性的特徵,比其他記憶體非法訪問錯誤更難檢測。因為記憶體泄漏的產生原因是記憶體塊未被釋放,屬於遺漏型缺陷而不是過錯型缺陷。此外,記憶體泄漏通常不會直接產生可觀察的錯誤癥狀,而是逐漸積累,降低系統整體性能,極端的情況下可能使系統崩潰。

記憶體泄露產生的方式

以產生的方式來分類,記憶體泄漏可以分為四類:

  • 常發性會記憶體泄漏:發生記憶體泄漏的程式碼會被多次執行到,每次被執行時都導致一塊記憶體泄漏。
  • 偶發性記憶體泄漏:發生記憶體泄漏的程式碼只有在某些特定環境或操作過程下才會發生。常發性和偶發性是相對的。對於特定的環境,偶發性的也許就變成了常發性的。所以測試環境和測試方法對檢測記憶體泄漏至關重要。
  • 一次性記憶體泄漏:發生記憶體泄漏的程式碼只會被執行一次,或者由於演算法上的缺陷,導致總會有一塊且僅有一塊記憶體發生泄漏。
  • 隱式記憶體泄漏:程式在運行過程中不停的分配記憶體,但是直到結束的時候才釋放記憶體。嚴格的說這裡並沒有發生記憶體泄漏,因為最終程式釋放了所有申請的記憶體。但是對於一個伺服器程式,需要運行幾天,幾周甚至幾個月,不及時釋放記憶體也可能導致最終耗盡系統的所有記憶體。所以,我們稱這類記憶體泄漏為隱式記憶體泄漏。從用戶使用程式的角度來看,記憶體泄漏本身不會產生什麼危害,作為一般的用戶,根本感覺不到記憶體泄漏的存在。真正有危害的是記憶體泄漏的堆積,這會最終耗盡系統所有的記憶體。從這個角度來說,一次性記憶體泄漏並沒有什麼危害,因為它不會堆積,而隱式記憶體泄漏危害性則非常大,因為較之於常發性和偶發性記憶體泄漏它更難被檢測到。

Valgrind

簡介

  Valgrind具是一個用於調試和分析Linux程式的GPL系統。使用Valgrind的工套件,您可以自動檢測許多記憶體管理和執行緒錯誤,使程式更穩定。還可以執行詳細的分析以幫助加速程式的執行。
  Valgrind是Julian Seward的作品。Valgrind是運行在Linux上一套基於模擬技術的程式調試和分析工具,它包含一個內核,一個軟體合成的CPU,和一系列的小工具。如下圖所示:

安裝 

  CentOS安裝:

# sudo yum install valgrind -y

  Ubuntu 安裝:

# sudo apt install valgrind -y

示常式序

  給出一個簡單示例malloc.c 。

#include<stdio.h>  #include<stdlib.h>  void fun()  {           int *x = malloc(10 * sizeof(int));           x[10] = 0;  }  int main()  {           int i = 99;           fun();           printf("i = %dn",i);           return 0;  }

  以上程式碼存在兩個問題:

  • 沒有free掉申請的資源;
  • fun函數裡面越界了,x[10]是非法的。

Memcheck

  是最常用的小工具,用來檢測程式中出現的記憶體問題,所有對記憶體的讀寫都會被檢測到,一切對malloc和free的調用都會被捕獲,它能檢測下列問題:

  • 對未初始化記憶體的使用;
  • 讀/寫釋放後的記憶體塊;
  • 讀/寫超出malloc分配的記憶體塊;
  • 讀/寫不適當的棧中的記憶體塊;
  • 記憶體泄漏,指向一塊記憶體的指針永遠丟失;
  • 不正確的malloc/free或new/delete匹配;
  • memcpy相關函數中的dst和src指針重疊;

  我們使用Memcheck小工具來檢測存在的問題。
  編譯:gcc -Wall -o malloc malloc.c
  檢測:valgrind ./malloc
    –show-reachable=<yes|no> [default: no]
    –leak-check=full 查看更為詳細資訊

  其中,34496是程式運行時的進程號。
  Invalid write of size 4:表示非法寫入(越界),下面是告訴我們錯誤發生的位置,在main中調用的fun函數。
  HEAP SUMMARY:說明了堆的情況,可以看到申請了40個位元組,後面說有1個申請,0個被free。
  LEAK SUMMARY:也是說的堆的泄漏情況,明顯丟失的有40個位元組。
  如果main中的i未初始化,這裡還會有一些其他的錯誤。

# gcc -Wall -o malloc malloc.c  malloc.c: In function ‘main’:  malloc.c:22:16: warning: ‘i’ is used uninitialized in this function [-Wuninitialized]   # 未初始化變數            printf("i = %dn",i);                  ^  [root@localhost memcheck]# valgrind ./malloc  ==34555== Memcheck,a memory error detector  ==34555== Copyright (C) 2002-2017,and GNU GPL'd,by Julian Seward et al.                                                     ==34555== Using Valgrind-3.13.0 and LibVEX; rerun with -h for copyright info  ==34555== Command: ./malloc  ==34555==  ==34555== Invalid write of size 4  ==34555==    at 0x40057B: fun (in /root/memcheck/malloc)  ==34555==    by 0x400594: main (in /root/memcheck/malloc)  ==34555==  Address 0x5203068 is 0 bytes after a block of size 40 alloc'd                                                      ==34555==    at 0x4C29BC3: malloc (vg_replace_malloc.c:299)  ==34555==    by 0x40056E: fun (in /root/memcheck/malloc)  ==34555==    by 0x400594: main (in /root/memcheck/malloc)  ==34555==  ==34555== Conditional jump or move depends on uninitialised value(s)  ==34555==    at 0x4E80B9E: vfprintf (in /usr/lib64/libc-2.17.so)  ==34555==    by 0x4E893E8: printf (in /usr/lib64/libc-2.17.so)  ==34555==    by 0x4005A8: main (in /root/memcheck/malloc)  ==34555==  ==34555== Use of uninitialised value of size 8  ==34555==    at 0x4E7E26B: _itoa_word (in /usr/lib64/libc-2.17.so)  ==34555==    by 0x4E824F0: vfprintf (in /usr/lib64/libc-2.17.so)  ==34555==    by 0x4E893E8: printf (in /usr/lib64/libc-2.17.so)  ==34555==    by 0x4005A8: main (in /root/memcheck/malloc)  ==34555==  ==34555== Conditional jump or move depends on uninitialised value(s)  ==34555==    at 0x4E7E275: _itoa_word (in /usr/lib64/libc-2.17.so)  ==34555==    by 0x4E824F0: vfprintf (in /usr/lib64/libc-2.17.so)  ==34555==    by 0x4E893E8: printf (in /usr/lib64/libc-2.17.so)  ==34555==    by 0x4005A8: main (in /root/memcheck/malloc)  ==34555==  ==34555== Conditional jump or move depends on uninitialised value(s)  ==34555==    at 0x4E8253F: vfprintf (in /usr/lib64/libc-2.17.so)  ==34555==    by 0x4E893E8: printf (in /usr/lib64/libc-2.17.so)  ==34555==    by 0x4005A8: main (in /root/memcheck/malloc)  ==34555==  ==34555== Conditional jump or move depends on uninitialised value(s)  ==34555==    at 0x4E80C6B: vfprintf (in /usr/lib64/libc-2.17.so)  ==34555==    by 0x4E893E8: printf (in /usr/lib64/libc-2.17.so)  ==34555==    by 0x4005A8: main (in /root/memcheck/malloc)  ==34555==  ==34555== Conditional jump or move depends on uninitialised value(s)  ==34555==    at 0x4E80CEE: vfprintf (in /usr/lib64/libc-2.17.so)  ==34555==    by 0x4E893E8: printf (in /usr/lib64/libc-2.17.so)  ==34555==    by 0x4005A8: main (in /root/memcheck/malloc)  ==34555==  i = 0  ==34555==  ==34555== HEAP SUMMARY:  ==34555==     in use at exit: 40 bytes in 1 blocks  ==34555==   total heap usage: 1 allocs,0 frees,40 bytes allocated  ==34555==  ==34555== LEAK SUMMARY:  ==34555==    definitely lost: 40 bytes in 1 blocks  ==34555==    indirectly lost: 0 bytes in 0 blocks  ==34555==      possibly lost: 0 bytes in 0 blocks  ==34555==    still reachable: 0 bytes in 0 blocks  ==34555==         suppressed: 0 bytes in 0 blocks  ==34555== Rerun with --leak-check=full to see details of leaked memory  ==34555==  ==34555== For counts of detected and suppressed errors,rerun with: -v  ==34555== Use --track-origins=yes to see where uninitialised values come from  ==34555== ERROR SUMMARY: 7 errors from 7 contexts (suppressed: 0 from 0)

View Code

Callgrind

  和gprof 類似的分析工具,但它對程式的運行觀察更細緻入微,能給我們提供更多的資訊。和gprof不同,它不需要在編譯源程式碼時添加附加特殊選項,但加上調試選項是推薦的。
  Callgrind收集程式運行時的一些數據,建立函數調用關係圖,還可以有選擇的進行cache模擬。在運行結束時,它會把分析數據寫入一個文件,callgrind_annotate可以把這個文件的內容轉化成可讀的形式。

  Callgrind可以幫助我們對程式的運行進行觀察。

# valgrind --tool=callgrind ./malloc

  

  可以看到生成了一個文件callgrind.out.34755,同樣34755為程式運行時的進程號。當callgrind運行你的程式時,還可以使用callgrind_control來觀察程式的執行,而且不會干擾它的運行。
  顯示程式的詳細資訊:

# callgrind_annotate callgrind.out.34755

  

Cachegrind

  Cache分析器,它模擬CPU中的一級快取I1,DI和二級快取,能夠精確的指出程式中cache的丟失和命中。如果需要,它還能為我們提供cache丟失次數,記憶體引用次數,以及每行程式碼,每個函數,每個模組整個程式產生的指令數,這對優化程式有很大的幫助。  

# valgrind --tool=cachegrind ./mallo

Helgrind

  用來檢測多執行緒程式中出現的競爭問題。Helgrind尋找記憶體中內對個執行緒訪問,而又沒有一貫加鎖的區域。這些區域往往是執行緒之間失去同步的情況,而且會導致難以發掘的錯誤。
  Helgrind實現了名為“Eraser”的競爭檢測演算法,並做了進一步改進,減少了報告錯誤的次數。不過Helgrinf仍然處於實驗階段。

Massif

  堆棧分析器,它能測量程式在堆棧中使用了多少記憶體,告訴我們堆塊,堆管理塊和棧的大小。Massif能幫助我們減少記憶體的使用,在代用虛擬記憶體的現代系統中,它還能加速我們程式的運行,減少程式停留在交換區中的幾率。

  此外,lackey和nulgrind也會提供。Lackey是小型工具,很少用到;Nulgrind只是為開發者展示如何創建一個工具。

參考

  Linux下幾款C++程式中的記憶體泄露檢查工具
    https://blog.csdn.net/gatieme/article/details/51959654
  Linux下檢測記憶體泄露的工具 valgrind
    https://cloud.tencent.com/developer/article/1075945
  Valgrind調試
    http://www.voidcn.com/article/p-xyrqgaum-yq.html