天天看點

arm linux下交叉編譯valgrind工具進行記憶體洩露檢測和性能分析

C/C++等底層語言在提供強大功能及性能的同時,其靈活的記憶體通路也帶來了各種糾結的問題。如果crash的地方正是記憶體使用錯誤的地方,說明你人品好。如果crash的地方記憶體明顯不是consistent的,或者記憶體管理資訊都已被破壞,編譯器不能發現這些問題,.運作時才能捕獲到這些錯誤并且還是随機出現的,那就比較麻煩了。當然,祼看code打log是一個辦法,但其效率不是太高,尤其是在運作成本高或重制機率低的情況下。另外,靜态檢查也是一類方法,有很多工具(lint, cppcheck, klockwork, splint, etc.)。但缺點是誤報很多,不适合針對性問題。另外好點的一般還要錢。最後,就是動态檢查工具。下面介紹幾個Linux平台下主要的運作時記憶體檢查工具。絕大多數都是開源免費且支援x86和ARM平台的。

首先,比較常見的記憶體問題有下面幾種:

• memory overrun:寫記憶體越界(越界通路堆、棧和全局變量)

• double free:同一塊記憶體釋放兩次

• use after free:記憶體釋放後使用

• wild free:釋放記憶體的參數為非法值

• access uninitialized memory:通路未初始化記憶體

• read invalid memory:讀取非法記憶體,本質上也屬于記憶體越界

• memory leak:記憶體洩露

• use after return:caller通路一個指針,該指針指向callee的棧内記憶體

• stack overflow:棧溢出

針對上面的問題,主要有以下幾種方法:

1. 為了檢測記憶體非法使用,需要hook記憶體配置設定和操作函數。hook的方法可以是用C-preprocessor,也可以是在連結庫中直接定義(因為Glibc中的malloc/free等函數都是weak symbol),或是用LD_PRELOAD。另外,通過hook strcpy(),memmove()等函數可以檢測它們是否引起buffer overflow。

2. 為了檢查記憶體的非法通路,需要對程式的記憶體進行bookkeeping,然後截獲每次訪存操作并檢測是否合法。bookkeeping的方法大同小異,主要思想是用shadow memory來驗證某塊記憶體的合法性。至于instrumentation的方法各種各樣。有run-time的,比如通過把程式運作在虛拟機中或是通過binary translator來運作;或是compile-time的,在編譯時就在訪存指令時就加入檢查操作。另外也可以通過在配置設定記憶體前後加設為不可通路的guard page,這樣可以利用硬體(MMU)來觸發SIGSEGV,進而提高速度。

3. 為了檢測棧的問題,一般在stack上設定canary,即在函數調用時在棧上寫magic number或是随機值,然後在函數傳回時檢查是否被改寫。另外可以通過mprotect()在stack的頂端設定guard page,這樣棧溢出會導緻SIGSEGV而不至于破壞資料。

以下是幾種常用工具在linux x86_64平台的實驗結果,注意其它平台可能結果有差異。

Tool\Problem memory overrun double free use after free wild free access uninited read invalid memory memory leak use after return stack overflow
Memory checking tools in Glibc Yes Yes Yes Yes(if use memcpy, strcpy, etc)
Valgrind Yes Yes Yes Yes Yes Yes Yes Yes Yes
Memwatch Yes Yes Yes
Dmalloc Yes Yes Yes Yes Yes

 下面簡單介紹一下這些工具以及基本用法。更詳細用法請參見各自manual。

valgrind通常用來成分析程式性能及程式中的記憶體洩露等錯誤

一 Valgrind工具集介紹

Valgrind包含下列工具:

    1、memcheck:檢查程式中的記憶體問題,如洩漏、越界、非法指針等。

    2、callgrind:檢測程式代碼的運作時間和調用過程,以及分析程式性能。

    3、cachegrind:分析CPU的cache命中率、丢失率,用于進行代碼優化。

    4、helgrind:用于檢查多線程程式的競态條件。

    5、massif:堆棧分析器,訓示程式中使用了多少堆記憶體等資訊。

這幾個工具的使用是通過指令:valgrand --tool=name 程式名來分别調用的,當不指定tool參數時預設是 --tool=memcheck

二 Valgrind工具詳解

1.Memcheck

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

       1、對未初始化記憶體的使用;

       2、讀/寫釋放後的記憶體塊;

       3、讀/寫超出malloc配置設定的記憶體塊;

       4、讀/寫不适當的棧中記憶體塊;

       5、記憶體洩漏,指向一塊記憶體的指針永遠丢失;

       6、不正确的malloc/free或new/delete比對;

       7、memcpy()相關函數中的dst和src指針重疊。

這些問題往往是C/C++程式員最頭疼的問題,Memcheck能在這裡幫上大忙。

例如:

[plain]  view plain copy

  1. #include <stdlib.h>  
  2. #include <malloc.h>  
  3. #include <string.h>  
  4. void test()  
  5. {  
  6.     int *ptr = malloc(sizeof(int)*10);  
  7.     ptr[10] = 7; // 記憶體越界  
  8.     memcpy(ptr +1, ptr, 5); // 踩記憶體  
  9.     free(ptr);   
  10.     free(ptr);// 重複釋放  
  11.     int *p1;  
  12.     *p1 = 1; // 非法指針  
  13. }  
  14. int main(void)  
  15. {  
  16.     test();  
  17.     return 0;  
  18. }  

将程式編譯生成可執行檔案後執行: valgrind --leak-check=full ./程式名

輸出結果如下:

==4832== Memcheck, a memory error detector

==4832== Copyright (C) 2002-2010, and GNU GPL'd, by Julian Seward et al.

==4832== Using Valgrind-3.6.1 and LibVEX; rerun with -h for copyright info

==4832== Command: ./tmp

==4832== 

==4832== Invalid write of size 4      // 記憶體越界

==4832==    at 0x804843F: test (in /home/yanghao/Desktop/testC/testmem/tmp)

==4832==    by 0x804848D: main (in /home/yanghao/Desktop/testC/testmem/tmp)

==4832==  Address 0x41a6050 is 0 bytes after a block of size 40 alloc'd

==4832==    at 0x4026864: malloc (vg_replace_malloc.c:236)

==4832==    by 0x8048435: test (in /home/yanghao/Desktop/testC/testmem/tmp)

==4832==    by 0x804848D: main (in /home/yanghao/Desktop/testC/testmem/tmp)

==4832== 

==4832== Source and destination overlap in memcpy(0x41a602c, 0x41a6028, 5) // 踩記憶體

==4832==    at 0x4027BD6: memcpy (mc_replace_strmem.c:635)

==4832==    by 0x8048461: test (in /home/yanghao/Desktop/testC/testmem/tmp)

==4832==    by 0x804848D: main (in /home/yanghao/Desktop/testC/testmem/tmp)

==4832== 

==4832== Invalid free() / delete / delete[] // 重複釋放

==4832==    at 0x4025BF0: free (vg_replace_malloc.c:366)

==4832==    by 0x8048477: test (in /home/yanghao/Desktop/testC/testmem/tmp)

==4832==    by 0x804848D: main (in /home/yanghao/Desktop/testC/testmem/tmp)

==4832==  Address 0x41a6028 is 0 bytes inside a block of size 40 free'd

==4832==    at 0x4025BF0: free (vg_replace_malloc.c:366)

==4832==    by 0x804846C: test (in /home/yanghao/Desktop/testC/testmem/tmp)

==4832==    by 0x804848D: main (in /home/yanghao/Desktop/testC/testmem/tmp)

==4832== 

==4832== Use of uninitialised value of size 4 // 非法指針

==4832==    at 0x804847B: test (in /home/yanghao/Desktop/testC/testmem/tmp)

==4832==    by 0x804848D: main (in /home/yanghao/Desktop/testC/testmem/tmp)

==4832== 

==4832== 

==4832== Process terminating with default action of signal 11 (SIGSEGV) //由于非法指針指派導緻的程式崩潰

==4832==  Bad permissions for mapped region at address 0x419FFF4

==4832==    at 0x804847B: test (in /home/yanghao/Desktop/testC/testmem/tmp)

==4832==    by 0x804848D: main (in /home/yanghao/Desktop/testC/testmem/tmp)

==4832== 

==4832== HEAP SUMMARY:

==4832==     in use at exit: 0 bytes in 0 blocks

==4832==   total heap usage: 1 allocs, 2 frees, 40 bytes allocated

==4832== 

==4832== All heap blocks were freed -- no leaks are possible

==4832== 

==4832== For counts of detected and suppressed errors, rerun with: -v

==4832== Use --track-origins=yes to see where uninitialised values come from

==4832== ERROR SUMMARY: 4 errors from 4 contexts (suppressed: 11 from 6)

Segmentation fault

從valgrind的檢測輸出結果看,這幾個錯誤都找了出來。

2.Callgrind

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

生成可視化的圖形需要下載下傳gprof2dot:http://jrfonseca.googlecode.com/svn/trunk/gprof2dot/gprof2dot.py

這是個Python腳本,把它下載下傳之後修改其權限chmod +7 gprof2dot.py ,并把這個腳本添加到$PATH路徑中的任一檔案夾下,我是将它放到了/usr/bin目錄下,這樣就可以直接在終端下執行gprof2dot.py了。

   Callgrind可以生成程式性能分析的圖形,首先來說說程式性能分析的工具吧,通常可以使用gnu自帶的gprof,它的使用方法是:在編譯程式時添加-pg參數,例如:

[plain]  view plain copy

  1. #include <stdio.h>  
  2. #include <malloc.h>  
  3. void test()  
  4. {  
  5.     sleep(1);  
  6. }  
  7. void f()  
  8. {  
  9.     int i;  
  10.     for( i = 0; i < 5; i ++)  
  11.         test();  
  12. }  
  13. int main()  
  14. {  
  15.     f();  
  16.     printf("process is over!\n");  
  17.     return 0;  
  18. }  

首先執行 gcc -pg -o tmp tmp.c,然後運作該程式./tmp,程式運作完成後會在目前目錄下生成gmon.out檔案(這個檔案gprof在分析程式時需要),

再執行gprof ./tmp | gprof2dot.py |dot -Tpng -o report.png,打開 report.png結果:

arm linux下交叉編譯valgrind工具進行記憶體洩露檢測和性能分析

顯示test被調用了5次,程式中耗時所占百分比最多的是test函數。

再來看 Callgrind的生成調用圖過程吧,執行:valgrind --tool=callgrind ./tmp,執行完成後在目錄下生成"callgrind.out.XXX"的檔案這是分析檔案,可以直接利用:callgrind_annotate callgrind.out.XXX 列印結果,也可以使用:gprof2dot.py -f callgrind callgrind.out.XXX |dot -Tpng -o report.png 來生成圖形化結果:

arm linux下交叉編譯valgrind工具進行記憶體洩露檢測和性能分析

它生成的結果非常詳細,甚至連函數入口,及庫函數調用都辨別出來了。

3.Cachegrind

       Cache分析器,它模拟CPU中的一級緩存I1,Dl和二級緩存,能夠精确地指出程式中cache的丢失和命中。如果需要,它還能夠為我們提供cache丢失次數,記憶體引用次數,以及每行代碼,每個函數,每個子產品,整個程式産生的指令數。這對優化程式有很大的幫助。

    作一下廣告:valgrind自身利用該工具在過去幾個月内使性能提高了25%-30%。據早先報道,kde的開發team也對valgrind在提高kde性能方面的幫助表示感謝。

它的使用方法也是:valgrind --tool=cachegrind 程式名,

4.Helgrind

    它主要用來檢查多線程程式中出現的競争問題。Helgrind尋找記憶體中被多個線程通路,而又沒有一貫加鎖的區域,這些區域往往是線程之間失去同步的地方,而且會導緻難以發掘的錯誤。Helgrind實作了名為“Eraser”的競争檢測算法,并做了進一步改進,減少了報告錯誤的次數。不過,Helgrind仍然處于實驗階段。

首先舉一個競态的例子吧:

[plain]  view plain copy

  1. #include <stdio.h>  
  2. #include <pthread.h>  
  3. #define NLOOP 50  
  4. int counter = 0;   
  5. void *threadfn(void *);  
  6. int main(int argc, char **argv)  
  7. {  
  8.     pthread_t tid1, tid2,tid3;  
  9.     pthread_create(&tid1, NULL, &threadfn, NULL);  
  10.     pthread_create(&tid2, NULL, &threadfn, NULL);  
  11.     pthread_create(&tid3, NULL, &threadfn, NULL);  
  12.     pthread_join(tid1, NULL);  
  13.     pthread_join(tid2, NULL);  
  14.     pthread_join(tid3, NULL);  
  15.     return 0;  
  16. }  
  17. void *threadfn(void *vptr)  
  18. {  
  19.       int i, val;  
  20.       for (i = 0; i < NLOOP; i++) {  
  21.     val = counter;  
  22.     printf("%x: %d \n", (unsigned int)pthread_self(),  val+1);  
  23.     counter = val+1;  
  24.       }  
  25.       return NULL;  
  26. }  

這段程式的 競态在30~32行,我們想要的效果是3個線程分别對全局變量累加50次,最後全局變量的值為150,由于這裡沒有加鎖,很明顯競态使得程式不能達到我們的目标。我們來看Helgrind是如何幫我們檢測到競态的。 先編譯程式:gcc -o test thread.c -lpthread ,然後執行:valgrind --tool=helgrind ./ test 輸出結果如下:

49c0b70: 1 

49c0b70: 2 

==4666== Thread #3 was created

==4666==    at 0x412E9D8: clone (clone.S:111)

==4666==    by 0x40494B5: [email protected]@GLIBC_2.1 (createthread.c:256)

==4666==    by 0x4026E2D: pthread_create_WRK (hg_intercepts.c:257)

==4666==    by 0x4026F8B: [email protected]* (hg_intercepts.c:288)

==4666==    by 0x8048524: main (in /home/yanghao/Desktop/testC/testmem/a.out)

==4666== 

==4666== Thread #2 was created

==4666==    at 0x412E9D8: clone (clone.S:111)

==4666==    by 0x40494B5: [email protected]@GLIBC_2.1 (createthread.c:256)

==4666==    by 0x4026E2D: pthread_create_WRK (hg_intercepts.c:257)

==4666==    by 0x4026F8B: [email protected]* (hg_intercepts.c:288)

==4666==    by 0x8048500: main (in /home/yanghao/Desktop/testC/testmem/a.out)

==4666== 

==4666== Possible data race during read of size 4 at 0x804a028 by thread #3

==4666==    at 0x804859C: threadfn (in /home/yanghao/Desktop/testC/testmem/a.out)

==4666==    by 0x4026F60: mythread_wrapper (hg_intercepts.c:221)

==4666==    by 0x4048E98: start_thread (pthread_create.c:304)

==4666==    by 0x412E9ED: clone (clone.S:130)

==4666==  This conflicts with a previous write of size 4 by thread #2

==4666==    at 0x80485CA: threadfn (in /home/yanghao/Desktop/testC/testmem/a.out)

==4666==    by 0x4026F60: mythread_wrapper (hg_intercepts.c:221)

==4666==    by 0x4048E98: start_thread (pthread_create.c:304)

==4666==    by 0x412E9ED: clone (clone.S:130)

==4666== 

==4666== Possible data race during write of size 4 at 0x804a028 by thread #2

==4666==    at 0x80485CA: threadfn (in /home/yanghao/Desktop/testC/testmem/a.out)

==4666==    by 0x4026F60: mythread_wrapper (hg_intercepts.c:221)

==4666==    by 0x4048E98: start_thread (pthread_create.c:304)

==4666==    by 0x412E9ED: clone (clone.S:130)

==4666==  This conflicts with a previous read of size 4 by thread #3

==4666==    at 0x804859C: threadfn (in /home/yanghao/Desktop/testC/testmem/a.out)

==4666==    by 0x4026F60: mythread_wrapper (hg_intercepts.c:221)

==4666==    by 0x4048E98: start_thread (pthread_create.c:304)

==4666==    by 0x412E9ED: clone (clone.S:130)

==4666== 

49c0b70: 3 

......

55c1b70: 51 

==4666== 

==4666== For counts of detected and suppressed errors, rerun with: -v

==4666== Use --history-level=approx or =none to gain increased speed, at

==4666== the cost of reduced accuracy of conflicting-access information

==4666== ERROR SUMMARY: 8 errors from 2 contexts (suppressed: 99 from 31)

helgrind成功的找到了 競态的所在位置,标紅所示。

5. Massif

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

       Massif對記憶體的配置設定和釋放做profile。程式開發者通過它可以深入了解程式的記憶體使用行為,進而對記憶體使用進行優化。這個功能對C++尤其有用,因為C++有很多隐藏的記憶體配置設定和釋放。

此外,lackey和nulgrind也會提供。Lackey是小型工具,很少用到;Nulgrind隻是為開發者展示如何建立一個工具。我們就不做介紹了。

三 使用Valgrind

       Valgrind使用起來非常簡單,你甚至不需要重新編譯你的程式就可以用它。當然如果要達到最好的效果,獲得最準确的資訊,還是需要按要求重新編譯一下的。比如在使用memcheck的時候,最好關閉優化選項。

       valgrind指令的格式如下:

       valgrind [valgrind-options] your-prog [your-prog options]

valgrind --tool=massif --stacks=yes ./test

(這個工具有個bug, 隻有程式中出現new或者malloc之類的堆操作,才會統計棧的使用,否則隻統計堆的使用)

一些常用的選項如下:

選項 作用
-h --help 顯示幫助資訊。
--version 顯示valgrind核心的版本,每個工具都有各自的版本。
-q --quiet 安靜地運作,隻列印錯誤資訊。
-v --verbose 列印更詳細的資訊。
--tool=<toolname> [default: memcheck] 最常用的選項。運作valgrind中名為toolname的工具。如果省略工具名,預設運作memcheck。
--db-attach=<yes|no> [default: no] 綁定到調試器上,便于調試錯誤。

注意:

1、對于項目工程測試适合使用Valgrind和mtrace,而單元測試選擇Dmalloc和memwatch。

2、inux中從列印出的位址找到行号,執行

addr2line -e test 0x40053E

如果可執行檔案錯誤,您将獲得??:?作為響應。

如果可執行檔案中沒有包括調試符号,您将獲得??:0 作為響應。

3、交叉編譯valgrind步驟

1)配置./configure --host=arm-linux CC=arm-histbv310-linux-gcc --prefix=/opt/valgrind

2)安裝make&&make install

3)将valgrind拷貝到開發闆的opt目錄下 : cp /opt/valgrind /opt/ -r

4)将opt設定為開發闆全局變量或在valgrind/bin目錄下使用記憶體洩露檢測memcheck 和性能分析callgrind 工具,操作方法和linux的使用相同

4、對于arm交叉編譯版本不要使用arm-linux-gcc 的strip工具來處理根檔案系統的庫檔案,保留二進制檔案中的包含的符号表和調試資訊。

否則執行memcheck的工具時,出現以下錯誤:

arm linux下交叉編譯valgrind工具進行記憶體洩露檢測和性能分析

4、把callgrind.out.3131傳到 window 7 ,

如果要時行源代碼顯示,XX.C也傳到windows 7 

用kcachegrind 打開callgrind.out.3131 ,即顯示下面圖

LINUX: http://kcachegrind.sourceforge.net/html/Home.html      
windows: https://sourceforge.net/projects/precompiledbin/files/?source=navbar      
arm linux下交叉編譯valgrind工具進行記憶體洩露檢測和性能分析

參考部落格

Valgrind使用說明

http://www.cnblogs.com/wangkangluo1/archive/2011/07/20/2111248.html

linux下利用valgrind工具進行記憶體洩露檢測和性能分析

http://blog.csdn.net/yanghao23/article/details/7514587

Linux中的常用記憶體問題檢測工具

http://blog.csdn.net/jinzhuojun/article/details/46659155

繼續閱讀