title: 內存泄漏檢測神器valgrind
categories:[內存檢測]
tags:[valgrind]
date: 2022/01/08
1.概述
Valgrind是一款用於內存調試、內存泄漏檢測以及性能分析的軟件開發工具。
Valgrind 發行版目前包括七個生產質量工具:一個內存錯誤檢測器、兩個線程錯誤檢測器、一個緩存和分支預測分析器、一個調用圖生成緩存和分支預測分析器,以及兩個不同的堆分析器。它還包括一個實驗性的 SimPoint 基本塊矢量生成器。它可在以下平台上運行:X86/Linux、AMD64/Linux、ARM/Linux、ARM64/Linux、PPC32/Linux、PPC64/Linux、PPC64LE/Linux、S390X/Linux、MIPS32/Linux、MIPS64/Linux、X86/Solaris , AMD64/Solaris, ARM/Android (2.3.x 及更高版本), ARM64/Android, X86/Android (4.0 及更高版本), MIPS32/Android, X86/FreeBSD, AMD64/FreeBSD, X86/Darwin 和 AMD64/Darwin (Mac OS X 10.12)
1.1 工具
它一般包含下列工具:
1.Memcheck
最常用的工具,用來檢測程序中出現的內存問題,所有對內存的讀寫都會被檢測到,一切對malloc()/free()/new/delete的調用都會被捕獲。所以,它能檢測以下問題:
-
對未初始化內存的使用;
-
讀/寫釋放后的內存塊;
-
讀/寫超出malloc分配的內存塊;
-
讀/寫不適當的棧中內存塊;
-
內存泄漏,指向一塊內存的指針永遠丟失;
-
不正確的malloc/free或new/delete匹配;
-
memcpy()相關函數中的dst和src指針重疊。
2.Callgrind
和gprof類似的分析工具,但它對程序的運行觀察更是入微,能給我們提供更多的信息。和gprof不同,它不需要在編譯源代碼時附加特殊選項,但加上調試選項是推薦的。Callgrind收集程序運行時的一些數據,建立函數調用關系圖,還可以有選擇地進行cache模擬。在運行結束時,它會把分析數據寫入一個文件。callgrind_annotate可以把這個文件的內容轉化成可讀的形式。
3.Cachegrind
Cache分析器,它模擬CPU中的一級緩存I1,Dl和二級緩存,能夠精確地指出程序中cache的丟失和命中。如果需要,它還能夠為我們提供cache丟失次數,內存引用次數,以及每行代碼,每個函數,每個模塊,整個程序產生的指令數。這對優化程序有很大的幫助。
4.Helgrind
它主要用來檢查多線程程序中出現的競爭問題。Helgrind尋找內存中被多個線程訪問,而又沒有一貫加鎖的區域,這些區域往往是線程之間失去同步的地方,而且會導致難以發掘的錯誤。Helgrind實現了名為“Eraser”的競爭檢測算法,並做了進一步改進,減少了報告錯誤的次數。不過,Helgrind仍然處於實驗階段。
5.Massif
堆棧分析器,它能測量程序在堆棧中使用了多少內存,告訴我們堆塊,堆管理塊和棧的大小。Massif能幫助我們減少內存的使用,在帶有虛擬內存的現代系統中,它還能夠加速我們程序的運行,減少程序停留在交換區中的幾率。
此外,lackey和nulgrind也會提供。Lackey是小型工具,很少用到;Nulgrind只是為開發者展示如何創建一個工具。
1.2原理
Memcheck 能夠檢測出內存問題,關鍵在於其建立了兩個全局表。
Valid-Value 表
對於進程的整個地址空間中的每一個字節(byte),都有與之對應的 8 個 bits;對於CPU的每個寄存器,也有一個與之對應的bit向量。這些bits負責記錄該字節或者寄存器值是否具有有效的、已初始化的值。
Valid-Address 表
對於進程整個地址空間中的每一個字節(byte),還有與之對應的1個bit,負責記錄該地址是否能夠被讀寫。
檢測原理:
當要讀寫內存中某個字節時,首先檢查這個字節對應的 A bit。如果該A bit顯示該位置是無效位置,memcheck則報告讀寫錯誤。
內核(core)類似於一個虛擬的 CPU 環境,這樣當內存中的某個字節被加載到真實的 CPU 中時,該字節對應的 V bit 也被加載到虛擬的 CPU 環境中。一旦寄存器中的值,被用來產生內存地址,或者該值能夠影響程序輸出,則 memcheck 會檢查對應的V bits,如果該值尚未初始化,則會報告使用未初始化內存錯誤。
2.安裝使用
2.1下載
去官網https://valgrind.org/下載最新版本
centos下載:
yum install valgrind
Ubuntu下載
sudo apt-get install valgrind
2.2使用
用法:valgrind[options] prog-and-args [options]:
常用選項,適用於所有Valgrind工具
-tool=
h –help 顯示幫助信息。
-version 顯示valgrind內核的版本,每個工具都有各自的版本。
q –quiet 安靜地運行,只打印錯誤信息。
v –verbose 更詳細的信息, 增加錯誤數統計。
-trace-children=no|yes 跟蹤子線程? [no]
-track-fds=no|yes 跟蹤打開的文件描述?[no]
-time-stamp=no|yes 增加時間戳到LOG信息? [no]
-log-fd=
-log-file=
-log-file-exactly=
-log-file-qualifier= 取得環境變量的值來做為輸出信息的文件名。 [none]
-log-socket=ipaddr:port 輸出LOG到socket ,ipaddr:port
LOG信息輸出:
-xml=yes 將信息以xml格式輸出,只有memcheck可用
-num-callers=
-error-limit=no|yes 如果太多錯誤,則停止顯示新錯誤? [yes]
-error-exitcode=
-db-attach=no|yes 當出現錯誤,valgrind會自動啟動調試器gdb。[no]
-db-command=
適用於Memcheck工具的相關選項:
-leak-check=no|summary|full 要求對leak給出詳細信息? [summary]
-leak-resolution=low|med|high how much bt merging in leak check [low]
-show-reachable=no|yes show reachable blocks in leak check? [no]
3.應用例子
3.1數組越界
malloc1.c
#include <stdio.h>
int main(int argc, char **argv) {
int *x = malloc(8*sizeof(int));
x[9] = 0; // 數組下標越界
free(x);
return 0;
}
編譯:
gcc -Wall malloc1.c -g -o malloc1
使用Valgrind檢查程序BUG:
# --leak-check=full 所有泄露檢查
valgrind --tool=memcheck --leak-check=full ./malloc1
運行結果:
[root@hackett valgrind]# valgrind --tool=memcheck --leak-check=full ./malloc1
==550168== Memcheck, a memory error detector
==550168== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==550168== Using Valgrind-3.17.0 and LibVEX; rerun with -h for copyright info
==550168== Command: ./malloc1
==550168==
==550168== Invalid write of size 4
==550168== at 0x4005FB: main (malloc1.c:7)
==550168== Address 0x520b064 is 4 bytes after a block of size 32 alloc'd
==550168== at 0x4C360A5: malloc (vg_replace_malloc.c:380)
==550168== by 0x4005EE: main (malloc1.c:5)
==550168==
==550168==
==550168== HEAP SUMMARY:
==550168== in use at exit: 0 bytes in 0 blocks
==550168== total heap usage: 1 allocs, 1 frees, 32 bytes allocated
==550168==
==550168== All heap blocks were freed -- no leaks are possible
==550168==
==550168== For lists of detected and suppressed errors, rerun with: -s
==550168== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 0 from 0)
分析:
1、每一行開頭的數字代表是進程ID,這里的進程ID為550168
2、550168 Invalid write of size 4
550168 at 0x4005BB: main (malloc1.c:7)
這兩行顯示的是錯誤出現的位置,就是 x 分配了 10 byte 的空間,但是向第 11 個 byte 寫數據, 所以就會顯示 Invalid write 的錯誤,代碼在malloc1.c的第7行
3.2內存釋放后進行讀寫
malloc2.c
#include <stdio.h>
int main(int argc, char **argv) {
char *p = malloc(1);
*p = 'a';
char c = *p;
printf("[%c]\n",c);
free(p); // 釋放
c = *p; //取值 讀
return 0;
}
編譯:
gcc -Wall malloc2.c -g -o malloc2
使用Valgrind檢查程序BUG:
# --leak-check=full 所有泄露檢查
valgrind --tool=memcheck --leak-check=full ./malloc2
運行結果:
[root@hackett valgrind]# valgrind --tool=memcheck --leak-check=full ./malloc2
==550063== Memcheck, a memory error detector
==550063== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==550063== Using Valgrind-3.17.0 and LibVEX; rerun with -h for copyright info
==550063== Command: ./malloc2
==550063==
[a]
==550063== Invalid read of size 1
==550063== at 0x400679: main (malloc2.c:15)
==550063== Address 0x520b040 is 0 bytes inside a block of size 1 free'd
==550063== at 0x4C38A03: free (vg_replace_malloc.c:755)
==550063== by 0x400674: main (malloc2.c:13)
==550063== Block was alloc'd at
==550063== at 0x4C360A5: malloc (vg_replace_malloc.c:380)
==550063== by 0x40063E: main (malloc2.c:5)
==550063==
==550063==
==550063== HEAP SUMMARY:
==550063== in use at exit: 0 bytes in 0 blocks
==550063== total heap usage: 2 allocs, 2 frees, 1,025 bytes allocated
==550063==
==550063== All heap blocks were freed -- no leaks are possible
==550063==
==550063== For lists of detected and suppressed errors, rerun with: -s
==550063== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 0 from 0)
分析:
1、每一行開頭的數字代表是進程ID,這里的進程ID為550063
2、550063 Invalid read of size 1
550063 at 0x400679: main (malloc2.c:15)
550063 Address 0x520b040 is 0 bytes inside a block of size 1 free'd
550063 at 0x4C38A03: free (vg_replace_malloc.c:755)
550063 by 0x400674: main (malloc2.c:13)
這五行顯示的是錯誤出現的位置,就是 p指向的內存已經釋放了,還對其進行讀操作,所以就會顯示 Invalid read 的錯誤,代碼在malloc2.c的第15行
3.3無效讀寫
malloc3.c
#include <stdio.h>
int main(int argc, char **argv) {
char *p = malloc(1);
*p = 'a';
char c = *(p+1); // 地址加1 無效讀
printf("[%c]\n",c);
free(p); // 釋放
return 0;
}
編譯:
gcc -Wall malloc3.c -g -o malloc3
使用Valgrind檢查程序BUG:
# --leak-check=full 所有泄露檢查
valgrind --tool=memcheck --leak-check=full ./malloc3
運行結果:
[root@iZwz97bu0gr8vx0j8l6kkzZ valgrind]# valgrind --tool=memcheck --leak-check=full ./malloc3
==550135== Memcheck, a memory error detector
==550135== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==550135== Using Valgrind-3.17.0 and LibVEX; rerun with -h for copyright info
==550135== Command: ./malloc3
==550135==
==550135== Invalid read of size 1
==550135== at 0x40064E: main (malloc3.c:9)
==550135== Address 0x520b041 is 0 bytes after a block of size 1 alloc'd
==550135== at 0x4C360A5: malloc (vg_replace_malloc.c:380)
==550135== by 0x40063E: main (malloc3.c:5)
==550135==
[]
==550135==
==550135== HEAP SUMMARY:
==550135== in use at exit: 0 bytes in 0 blocks
==550135== total heap usage: 2 allocs, 2 frees, 1,025 bytes allocated
==550135==
==550135== All heap blocks were freed -- no leaks are possible
==550135==
==550135== For lists of detected and suppressed errors, rerun with: -s
==550135== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 0 from 0)
分析:
1、每一行開頭的數字代表是進程ID,這里的進程ID為550135
2、550135 Invalid read of size 1
550135 at 0x40064E: main (malloc3.c:9)
這五行顯示的是錯誤出現的位置,就是對未分配內存的空間進行讀取,所以就會顯示 Invalid read 的錯誤,代碼在malloc3.c的第9行
3.4內存泄漏
malloc4.c
#include <stdio.h>
int main(int argc, char **argv) {
char *p = malloc(1);
*p = 'a';
char c = *p;
printf("[%c]\n",c); // 申請后沒有釋放p 內存泄漏
return 0;
}
編譯:
gcc -Wall malloc4.c -g -o malloc4
使用Valgrind檢查程序BUG:
# --leak-check=full 所有泄露檢查
valgrind --tool=memcheck --leak-check=full ./malloc4
運行結果:
[root@hackett valgrind]# valgrind --tool=memcheck --leak-check=full ./malloc4
==550195== Memcheck, a memory error detector
==550195== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==550195== Using Valgrind-3.17.0 and LibVEX; rerun with -h for copyright info
==550195== Command: ./malloc4
==550195==
[a]
==550195==
==550195== HEAP SUMMARY:
==550195== in use at exit: 1 bytes in 1 blocks
==550195== total heap usage: 2 allocs, 1 frees, 1,025 bytes allocated
==550195==
==550195== 1 bytes in 1 blocks are definitely lost in loss record 1 of 1
==550195== at 0x4C360A5: malloc (vg_replace_malloc.c:380)
==550195== by 0x4005EE: main (malloc4.c:5)
==550195==
==550195== LEAK SUMMARY:
==550195== definitely lost: 1 bytes in 1 blocks
==550195== indirectly lost: 0 bytes in 0 blocks
==550195== possibly lost: 0 bytes in 0 blocks
==550195== still reachable: 0 bytes in 0 blocks
==550195== suppressed: 0 bytes in 0 blocks
==550195==
==550195== For lists of detected and suppressed errors, rerun with: -s
==550195== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 0 from 0)
分析:
1、每一行開頭的數字代表是進程ID,這里的進程ID為550195
2、550195 1 bytes in 1 blocks are definitely lost in loss record 1 of 1
550195 at 0x4C360A5: malloc (vg_replace_malloc.c:380)
550195 by 0x4005EE: main (malloc4.c:5)
這三行顯示的是錯誤出現的位置,是 stack trace內存泄漏,代碼在malloc1.c的第5行,申請了內存沒有進行對應的free
3.5內存多次釋放
malloc5.c
#include <stdio.h>
int main(int argc, char **argv) {
char *p = malloc(1);
*p = 'a';
char c = *p; // 地址加1
printf("[%c]\n",c);
free(p);
free(p);// 內存多次釋放
free(p);// 內存多次釋放
return 0;
}
編譯:
gcc -Wall malloc5.c -g -o malloc5
使用Valgrind檢查程序BUG:
# --leak-check=full 所有泄露檢查
valgrind --tool=memcheck --leak-check=full ./malloc5
運行結果:
[root@hackett valgrind]# valgrind --tool=memcheck --leak-check=full ./malloc5
==550227== Memcheck, a memory error detector
==550227== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==550227== Using Valgrind-3.17.0 and LibVEX; rerun with -h for copyright info
==550227== Command: ./malloc5
==550227==
[a]
==550227== Invalid free() / delete / delete[] / realloc()
==550227== at 0x4C38A03: free (vg_replace_malloc.c:755)
==550227== by 0x400680: main (malloc5.c:14)
==550227== Address 0x520b040 is 0 bytes inside a block of size 1 free'd
==550227== at 0x4C38A03: free (vg_replace_malloc.c:755)
==550227== by 0x400674: main (malloc5.c:13)
==550227== Block was alloc'd at
==550227== at 0x4C360A5: malloc (vg_replace_malloc.c:380)
==550227== by 0x40063E: main (malloc5.c:5)
==550227==
==550227== Invalid free() / delete / delete[] / realloc()
==550227== at 0x4C38A03: free (vg_replace_malloc.c:755)
==550227== by 0x40068C: main (malloc5.c:15)
==550227== Address 0x520b040 is 0 bytes inside a block of size 1 free'd
==550227== at 0x4C38A03: free (vg_replace_malloc.c:755)
==550227== by 0x400674: main (malloc5.c:13)
==550227== Block was alloc'd at
==550227== at 0x4C360A5: malloc (vg_replace_malloc.c:380)
==550227== by 0x40063E: main (malloc5.c:5)
==550227==
==550227==
==550227== HEAP SUMMARY:
==550227== in use at exit: 0 bytes in 0 blocks
==550227== total heap usage: 2 allocs, 4 frees, 1,025 bytes allocated
==550227==
==550227== All heap blocks were freed -- no leaks are possible
==550227==
==550227== For lists of detected and suppressed errors, rerun with: -s
==550227== ERROR SUMMARY: 2 errors from 2 contexts (suppressed: 0 from 0)
分析:
1、每一行開頭的數字代表是進程ID,這里的進程ID為550227
2、550227 Invalid free() / delete / delete[] / realloc()
550227 at 0x4C38A03: free (vg_replace_malloc.c:755)
550227 by 0x400680: main (malloc5.c:14)
這三行顯示的是第一個錯誤出現的位置,就是多次釋放內存問題, 所以就會顯示 Invalid free() / delete / delete[] / realloc() 的錯誤,代碼在malloc5.c的第14行
3、550227 Invalid free() / delete / delete[] / realloc()
550227 at 0x4C38A03: free (vg_replace_malloc.c:755)
550227 by 0x40068C: main (malloc5.c:15)
這三行顯示的是第二個錯誤出現的位置,就是多次釋放內存問題, 所以就會顯示 Invalid free() / delete / delete[] / realloc() 的錯誤,代碼在malloc5.c的第15行
3.6動態內存管理
常見的內存分配方式分三種:靜態存儲,棧上分配,堆上分配。全局變量屬於靜態存儲,它們是在編譯時就被分配了存儲空間,函數內的局部變量屬於棧上分配,而最靈活的內存使用方式當屬堆上分配,也叫做內存動態分配了。常用的內存動態分配函數包括:malloc, alloc, realloc, new等,動態釋放函數包括free, delete。
一旦成功申請了動態內存,我們就需要自己對其進行內存管理,而這又是最容易犯錯誤的。
malloc6.c
#include <stdio.h>
// 內存動態管理
int main(int argc, char **argv) {
int i;
char *p = (char *)malloc(10);
char *pt = p;
for(i = 0; i < 10; i++) {
p[i] = 'A'+i;
}
free(p);
pt[1] = 'x';
free(pt);
return 0;
}
編譯:
gcc -Wall malloc6.c -g -o malloc6
使用Valgrind檢查程序BUG:
# --leak-check=full 所有泄露檢查
valgrind --tool=memcheck --leak-check=full ./malloc6
運行結果:
[root@hackett valgrind]# valgrind --tool=memcheck --leak-check=full ./malloc6
==550239== Memcheck, a memory error detector
==550239== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==550239== Using Valgrind-3.17.0 and LibVEX; rerun with -h for copyright info
==550239== Command: ./malloc6
==550239==
==550239== Invalid write of size 1
==550239== at 0x400639: main (malloc6.c:16)
==550239== Address 0x520b041 is 1 bytes inside a block of size 10 free'd
==550239== at 0x4C38A03: free (vg_replace_malloc.c:755)
==550239== by 0x400630: main (malloc6.c:14)
==550239== Block was alloc'd at
==550239== at 0x4C360A5: malloc (vg_replace_malloc.c:380)
==550239== by 0x4005EE: main (malloc6.c:7)
==550239==
==550239== Invalid free() / delete / delete[] / realloc()
==550239== at 0x4C38A03: free (vg_replace_malloc.c:755)
==550239== by 0x400647: main (malloc6.c:18)
==550239== Address 0x520b040 is 0 bytes inside a block of size 10 free'd
==550239== at 0x4C38A03: free (vg_replace_malloc.c:755)
==550239== by 0x400630: main (malloc6.c:14)
==550239== Block was alloc'd at
==550239== at 0x4C360A5: malloc (vg_replace_malloc.c:380)
==550239== by 0x4005EE: main (malloc6.c:7)
==550239==
==550239==
==550239== HEAP SUMMARY:
==550239== in use at exit: 0 bytes in 0 blocks
==550239== total heap usage: 1 allocs, 2 frees, 10 bytes allocated
==550239==
==550239== All heap blocks were freed -- no leaks are possible
==550239==
==550239== For lists of detected and suppressed errors, rerun with: -s
==550239== ERROR SUMMARY: 2 errors from 2 contexts (suppressed: 0 from 0)
分析:
1、每一行開頭的數字代表是進程ID,這里的進程ID為550239
2、550239 Invalid write of size 1
550239 at 0x400639: main (malloc6.c:16)
這一行顯示這里有一個錯誤,就是發生非法寫操作, 所以就會顯示 Invalid write 的錯誤,代碼在malloc6.c的第16行
3、550239 Invalid free() / delete / delete[] / realloc()
550239 at 0x4C38A03: free (vg_replace_malloc.c:755)
550239 by 0x400647: main (malloc6.c:18)
550239 Address 0x520b040 is 0 bytes inside a block of size 10 free'd
550239 at 0x4C38A03: free (vg_replace_malloc.c:755)
550239 by 0x400630: main (malloc6.c:14)
這五行顯示的是第二個錯誤出現的位置,指針p和pt指向的是同一塊內存,卻被先后釋放兩次。系統會在堆上維護一個動態內存鏈表,如果被釋放,就意味着該塊內存可以繼續被分配給其他部分,如果內存被釋放后再訪問,就可能覆蓋其他部分的信息,這是一種嚴重的錯誤,上述程序第16行中就在釋放后仍然寫這塊內存。
總結:
1.申請內存在使用完成后就要釋放。如果沒有釋放,或少釋放了就是內存泄露;多釋放也會產生問題。
2.注意數組的大小,別越界讀寫訪問非法內存
3.malloc和new要對應free和delete使用。
如果你覺得文章還不錯,可以給個"三連",文章同步到個人微信公眾號[加班猿]
我是hackett,我們下期見