
void?mtrace(void);
mtrace()?函數(shù)會為那些和動態(tài)內存分配有關的函數(shù)(譬如 malloc()、realloc()、memalign() 以及 free())安裝 “鉤子(hook)” 函數(shù),這些 hook 函數(shù)會為我們記錄所有有關內存分配和釋放的跟蹤信息,而 muntrace() 則會卸載相應的 hook 函數(shù)。
基于這些 hook 函數(shù)生成的調試跟蹤信息,我們就可以分析是否存在 “內存泄露” 這類問題了。
mtrace 機制需要我們實際運行一下程序,然后才能生成跟蹤的日志,但在實際運行程序之前還有一件要做的事情是需要告訴 mtrace (即前文提到的 hook 函數(shù))生成日志文件的路徑。
設置日志生成路徑有兩種,一種是設置環(huán)境變量:export MALLOC_TRACE=./test.log // 當前目錄下?另一種是在代碼層面設置:setenv("MALLOC_TRACE", "output_file_name", 1);``output_file_name就是儲存檢測結果的文件的名稱。
#include?
#include?
#include?
int?main(int?argc,?char?**argv)
{
????mtrace();??//?開始跟蹤
????char?*p?=?(char?*)malloc(100);
????free(p);
????p?=?NULL;
????p?=?(char?*)malloc(100);
????muntrace();???//?結束跟蹤,并生成日志信息
????return?0;
}
gcc -g test.c -o test生成可執(zhí)行文件。=?Start
@?./test:[0x400624]?+?0x21ed450?0x64
@?./test:[0x400634]?-?0x21ed450
@?./test:[0x400646]?+?0x21ed450?0x64
=?End
#?addr2line?-e?test?0x400624
/home/test.c:9
mtrace test ./test.log執(zhí)行,輸出如下信息:Memory?not?freed:
-----------------
???????????Address?????Size?????Caller
0x00000000021ed450?????0x64??at?/home/test.c:14

最常用的工具,用來檢測程序中出現(xiàn)的內存問題,所有對內存的讀寫都會被檢測到,一切對malloc() / free() / new / delete 的調用都會被捕獲。
所以,它能檢測以下問題:對未初始化內存的使用;讀/寫釋放后的內存塊;讀/寫超出malloc分配的內存塊;讀/寫不適當?shù)臈V袃却鎵K;內存泄漏,指向一塊內存的指針永遠丟失;不正確的malloc/free或new/delete匹配;memcpy()相關函數(shù)中的dst和src指針重疊。
和 gprof 類似的分析工具,但它對程序的運行觀察更是入微,能給我們提供更多的信息。和 gprof 不同,它不需要在編譯源代碼時附加特殊選項,但加上調試選項是推薦的。
Callgrind 收集程序運行時的一些數(shù)據(jù),建立函數(shù)調用關系圖,還可以有選擇地進行 cache 模擬。在運行結束時,它會把分析數(shù)據(jù)寫入一個文件。callgrind_annotate 可以把這個文件的內容轉化成可讀的形式。
Cache 分析器,它模擬 CPU 中的一級緩存 I1,Dl 和二級緩存,能夠精確地指出程序中 cache 的丟失和命中。如果需要,它還能夠為我們提供 cache 丟失次數(shù),內存引用次數(shù),以及每行代碼,每個函數(shù),每個模塊,整個程序產生的指令數(shù)。這對優(yōu)化程序有很大的幫助。
它主要用來檢查多線程程序中出現(xiàn)的競爭問題。Helgrind 尋找內存中被多個線程訪問,而又沒有一貫加鎖的區(qū)域,這些區(qū)域往往是線程之間失去同步的地方,而且會導致難以發(fā)掘的錯誤。
Helgrind 實現(xiàn)了名為“Eraser”的競爭檢測算法,并做了進一步改進,減少了報告錯誤的次數(shù)。不過,Helgrind 仍然處于實驗階段。
堆棧分析器,它能測量程序在堆棧中使用了多少內存,告訴我們堆塊,堆管理塊和棧的大小。
Massif 能幫助我們減少內存的使用,在帶有虛擬內存的現(xiàn)代系統(tǒng)中,它還能夠加速我們程序的運行,減少程序停留在交換區(qū)中的幾率。
此外,lackey 和 nulgrind 也會提供。Lackey 是小型工具,很少用到;Nulgrind 只是為開發(fā)者展示如何創(chuàng)建一個工具。

Valid-Value 表 對于進程整個地址空間中的每一個字節(jié)(byte),都有與之對應的 8個bits;對于 CPU 的每個寄存器,也有一個與之對應的 bit 向量。這些 bits 負責記錄該字節(jié)或者寄存器值是否具有有效的、已初始化的值。
Valid-Address 表 對于進程整個地址空間中的每一個字節(jié)(byte),還有與之對應的1個 bit,負責記錄該地址是否能夠被讀寫。
檢測原理:當要讀寫內存中某個字節(jié)時,首先檢查這個字節(jié)對應的Valid-Address 表中的 A bit。如果該 A bit顯示該位置是無效位置,memcheck 則報告讀寫錯誤。內核(core)類似于一個虛擬的 CPU 環(huán)境,這樣當內存中的某個字節(jié)被加載到真實的 CPU 中時,該字節(jié)對應的Valid-Value 表中的 V bit 也被加載到虛擬的 CPU 環(huán)境中。一旦寄存器中的值,被用來產生內存地址,或者該值能夠影響程序輸出,則 memcheck 會檢查對應的V bits,如果該值尚未初始化,則會報告使用未初始化內存錯誤。
valgrind 將內存泄漏分成 4 類:
間接性泄露(indirectly lost):泄露的運行內存表針儲存在確立泄露的運行內存中,伴隨著確立泄露的運行內存不能瀏覽,造成間接性泄露的運行內存也不能瀏覽。例如:
struct?list?{
?struct?list?*next;
};
int?main(int?argc,?char?**argv)
{
?struct?list?*root;
?root?=?(struct?list?*)malloc(sizeof(struct?list));
?root->next?=?(struct?list?*)malloc(sizeof(struct?list));
?printf("root?%p?roop->next?%p\n",?root,?root->next);
?root?=?NULL;
?return?0;
}
為了更好地在出難題時要詳盡打印出出去棧信息內容,實際上大家最好是在編譯程序時加上 -g 選擇項。如果有動態(tài)性載入的庫,必須再加上?--keep-debuginfo=yes?,不然假如發(fā)覺是動態(tài)性載入的庫發(fā)生泄露,因為動態(tài)庫被卸載掉了,造成找不到符號表。編碼編譯程序提升,不建議應用 -O2既之上。-O0很有可能會造成運作變慢,建議使用-O1。
#include?
#include?
void?func()
{
??//只申請內存而不釋放
????void?*p=malloc(sizeof(int));
}
int?main()
{
????func();
????return?0;
}
使用valgrind命令來執(zhí)行程序同時輸出日志到文件
valgrind?--log-file=valReport?--leak-check=full?--show-reachable=yes?--leak-resolution=low?./a.out
–log-file=valReport 是指定生成分析日志文件到當前執(zhí)行目錄中,文件名為valReport
–leak-check=full 顯示每個泄露的詳細信息
–show-reachable=yes 是否檢測控制范圍之外的泄漏,比如全局指針、static指針等,顯示所有的內存泄露類型
–leak-resolution=low 內存泄漏報告合并等級
–track-origins=yes表示開啟“使用未初始化的內存”的檢測功能,并打開詳細結果。如果沒有這句話,默認也會做這方面的檢測,但不會打印詳細結果。執(zhí)行輸出后,報告解讀,其中54017是指進程號,如果程序使用了多進程的方式來執(zhí)行,那么就會顯示多個進程的內容。
==54017==?Memcheck,?a?memory?error?detector
==54017==?Copyright?(C)?2002-2017,?and?GNU?GPL'd,?by?Julian?Seward?et?al.
==54017==?Using?Valgrind-3.15.0?and?LibVEX;?rerun?with?-h?for?copyright?info
==54017==?Command:?./a.out
==54017==?Parent?PID:?52130
第二段是對堆內存分配的總結信息,其中提到程序一共申請了1次內存,其中0次釋放了,4 bytes被分配(1 allocs, 0 frees, 4 bytes allocated)。
在head summary中,有該程序使用的總heap內存量,分配內存次數(shù)和釋放內存次數(shù),如果分配內存次數(shù)和釋放內存次數(shù)不一致則說明有內存泄漏。
==54017==?HEAP?SUMMARY:
==54017==???in?use?at?exit:?4?bytes?in?1?blocks
==54017==???total?heap?usage:?1?allocs,?0?frees,?4?bytes?allocated
第三段的內容描述了內存泄露的具體信息,其中有一塊內存占用4字節(jié)(4 bytes in 1 blocks),在調用malloc分配,調用棧中可以看到是func函數(shù)最后調用了malloc,所以這一個信息是比較準確的定位了我們泄露的內存是在哪里申請的。
==54017==?4?bytes?in?1?blocks?are?definitely?lost?in?loss?record?1?of?1
==54017==????at?0x4C29F73:?malloc?(vg_replace_malloc.c:309)
==54017==????by?0x40057E:?func()?(in?/home/oceanstar/CLionProjects/Share/src/a.out)
==54017==????by?0x40058D:?main?(in?/home/oceanstar/CLionProjects/Share/src/a.out)
最后這一段是總結,4字節(jié)為一塊的內存泄露。
==54017==?LEAK?SUMMARY:
==54017==????definitely?lost:?4?bytes?in?1?blocks??//?確立泄露
==54017==????indirectly?lost:?0?bytes?in?0?blocks??//?間接性泄露
==54017==????possibly?lost:?0?bytes?in?0?blocks???//?很有可能泄露
==54017==????still?reachable:?0?bytes?in?0?blocks?//?仍可訪達
==54017==????suppressed:?0?bytes?in?0?blocks
#include?
#include?
int?main()
{
????int?len?=?5;
????int?*pt?=?(int*)malloc(len*sizeof(int));?//problem1:?not?freed
????int?*p?=?pt;
????for?(int?i?=?0;?i?????????p++;
????}
????*p?=?5;?//problem2:?heap?block?overrun
????printf("%d\n",?*p);?//problem3:?heap?block?overrun
????//?free(pt);
????return?0;
}
problem1: 指針pt申請了空間,但是沒有釋放; problem2: pt申請了5個int的空間,p經過5次循環(huán)已達到p[5]的位置,?*p = 5時,訪問越界(寫越界)。(下面valgrind報告中 Invalid write of size 4)
==58261==?Invalid?write?of?size?4
==58261==????at?0x400707:?main?(main.cpp:12)
==58261==??Address?0x5a23054?is?0?bytes?after?a?block?of?size?20?alloc'd
==58261==????at?0x4C29F73:?malloc?(vg_replace_malloc.c:309)
==58261==????by?0x4006DC:?main?(main.cpp:7)
problem1: 讀越界 (下面valgrind報告中 Invalid read of size 4 )
==58261==?Invalid?read?of?size?4
==58261==????at?0x400711:?main?(main.cpp:13)
==58261==??Address?0x5a23054?is?0?bytes?after?a?block?of?size?20?alloc'd
==58261==????at?0x4C29F73:?malloc?(vg_replace_malloc.c:309)
==58261==????by?0x4006DC:?main?(main.cpp:7)
#include?
#include?
int?main()
{
????int?*x;
????x?=?static_cast(malloc(8?*?sizeof(int)));
????x?=?static_cast(malloc(8?*?sizeof(int)));
????free(x);
????free(x);
????return?0;
}
報告如下,Invalid free() / delete / delete[] / realloc()
==59602==?Invalid?free()?/?delete?/?delete[]?/?realloc()
==59602==????at?0x4C2B06D:?free?(vg_replace_malloc.c:540)
==59602==????by?0x4006FE:?main?(main.cpp:10)
==59602==??Address?0x5a230a0?is?0?bytes?inside?a?block?of?size?32?free'd
==59602==????at?0x4C2B06D:?free?(vg_replace_malloc.c:540)
==59602==????by?0x4006F2:?main?(main.cpp:9)
==59602==??Block?was?alloc'd?at
==59602==????at?0x4C29F73:?malloc?(vg_replace_malloc.c:309)
==59602==????by?0x4006E2:?main?(main.cpp:8)
申請釋放接口不匹配的報告如下,用malloc申請空間的指針用free釋放;用new申請的空間用delete釋放(Mismatched free() / delete / delete []):
==61950==?Mismatched?free()?/?delete?/?delete?[]
==61950==????at?0x4C2BB8F:?operator?delete[](void*)?(vg_replace_malloc.c:651)
==61950==????by?0x4006E8:?main?(main.cpp:8)
==61950==??Address?0x5a23040?is?0?bytes?inside?a?block?of?size?5?alloc'd
==61950==????at?0x4C29F73:?malloc?(vg_replace_malloc.c:309)
==61950==????by?0x4006D1:?main?(main.cpp:7)
int?main()
{
????char?str[11];
????for?(int?i?=?0;?i?11;?i++){
????????str[i]?=?i;
????}
????memcpy(str?+?1,?str,?5);
????char?x[5]?=?"abcd";
????strncpy(x?+?2,?x,?3);
}
問題出在memcpy上, 將str指針位置開始copy 5個char到str+1所指空間,會造成內存覆蓋。strncpy也是同理。報告如下,Source and destination overlap:
==61609==?Source?and?destination?overlap?in?memcpy(0x1ffefffe31,?0x1ffefffe30,?5)
==61609==????at?0x4C2E81D:?memcpy@@GLIBC_2.14?(vg_replace_strmem.c:1035)
==61609==????by?0x400721:?main?(main.cpp:11)
==61609==?
==61609==?Source?and?destination?overlap?in?strncpy(0x1ffefffe25,?0x1ffefffe23,?3)
==61609==????at?0x4C2D453:?strncpy?(vg_replace_strmem.c:552)
==61609==????by?0x400748:?main?(main.cpp:14)
內存檢測方式無非分為兩種:
文章來源于網絡,版權歸原作者所有,如有侵權,請聯(lián)系刪除。


關注我【一起學嵌入式】,一起學習,一起成長。
覺得文章不錯,點擊“分享”、“贊”、“在看” 唄!