valgrind 介紹
Valgrind是一套Linux下,開放源代碼(GPL V2)的仿真調試工具的集合。Valgrind由核心(core)以及基于核心的其他調試工具組成。核心類似于一個架構(framework),它模拟了一個CPU環境,并提供服務給其他工具;而其他工具則類似于插件 (plug-in),利用核心提供的服務完成各種特定的記憶體調試任務。
<a href="https://s2.51cto.com/wyfs02/M01/94/FC/wKioL1kQYlCTc0igAAA-PeLw-yo373.jpg-wh_500x0-wm_3-wmp_4-s_3301152614.jpg" target="_blank"></a>
Valgrind包括如下一些工具:
Memcheck。這是valgrind應用最廣泛的工具,一個重量級的記憶體檢查器,能夠發現開發中絕大多數記憶體錯誤使用情況,比如:使用未初始化的記憶體,使用已經釋放了的記憶體,記憶體通路越界等。這也是本文将重點介紹的部分。
Callgrind。它主要用來檢查程式中函數調用過程中出現的問題。
Cachegrind。它主要用來檢查程式中緩存使用出現的問題。
Helgrind。它主要用來檢查多線程程式中出現的競争問題。
Massif。它主要用來檢查程式中堆棧使用中出現的問題。
Extension。可以利用core提供的功能,自己編寫特定的記憶體調試工具
<a href="https://s5.51cto.com/wyfs02/M00/94/FC/wKiom1kQZKKyFRpjAAAuMa-IrKE742.jpg-wh_500x0-wm_3-wmp_4-s_1323953685.jpg" target="_blank"></a>
一個典型的Linux C程式記憶體空間由如下幾部分組成:
代碼段(.text)。這裡存放的是CPU要執行的指令。代碼段是可共享的,相同的代碼在記憶體中隻會有一個拷貝,同時這個段是隻讀的,防止程式由于錯誤而修改自身的指令。
初始化資料段(.data)。這裡存放的是程式中需要明确賦初始值的變量,例如位于所有函數之外的全局變量:int val="100"。需要強調的是,以上兩段都是位于程式的可執行檔案中,核心在調用exec函數啟動該程式時從源程式檔案中讀入。
未初始化資料段(.bss)。位于這一段中的資料,核心在執行該程式前,将其初始化為0或者null。例如出現在任何函數之外的全局變量:int sum;
堆(Heap)。這個段用于在程式中進行動态記憶體申請,例如經常用到的malloc,new系列函數就是從這個段中申請記憶體。
棧(Stack)。函數中的局部變量以及在函數調用過程中産生的臨時變量都儲存在此段中。
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,如果該值尚未初始化,則會報告使用未初始化記憶體錯誤。
Valgrind 使用
用法: valgrind [options] prog-and-args [options]: 常用選項,适用于所有Valgrind工具
-tool=<name> 最常用的選項。運作 valgrind中名為toolname的工具。預設memcheck。
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=<number> 輸出LOG到描述符檔案 [2=stderr]
-log-file=<file> 将輸出的資訊寫入到filename.PID的檔案裡,PID是運作程式的進行ID
-log-file-exactly=<file> 輸出LOG資訊到 file
-log-file-qualifier=<VAR> 取得環境變量的值來做為輸出資訊的檔案名。 [none]
-log-socket=ipaddr:port 輸出LOG到socket ,ipaddr:port
LOG資訊輸出
-xml=yes 将資訊以xml格式輸出,隻有memcheck可用
-num-callers=<number> show <number> callers in stack traces [12]
-error-limit=no|yes 如果太多錯誤,則停止顯示新錯誤? [yes]
-error-exitcode=<number> 如果發現錯誤則傳回錯誤代碼 [0=disable]
-db-attach=no|yes 當出現錯誤,valgrind會自動啟動調試器gdb。[no]
-db-command=<command> 啟動調試器的指令行選項[gdb -nw %f %p]
适用于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]
Valgrind 使用舉例(一)
#include <stdlib.h>
void f(void)
{
int* x = malloc(10 * sizeof(int));
x[10] = 0; //問題1: 數組下标越界
} //問題2: 記憶體沒有釋放
int main(void)
f();
return 0;
}
1、 編譯程式test.c
gcc -Wall test.c -g -o test
2、 使用Valgrind檢查程式BUG
valgrind --tool=memcheck --leak-check=full ./test
如果提示未安裝請先安裝
sudo apt install valgrind
...
==2691== ERROR SUMMARY: 2 errors from 2 contexts (suppressed: 0 from 0)
Valgrind 使用舉例(二)
使用未初始化記憶體問題
問題分析:
對于位于程式中不同段的變量,其初始值是不同的,全局變量和靜态變量初始值為0,而局部變量和動态申請的變量,其初始值為随機值。如果程式使用了為随機值的變量,那麼程式的行為就變得不可預期。
下面的程式就是一種常見的,使用了未初始化的變量的情況。數組a是局部變量,其初始值為随機值,而在初始化時并沒有給其所有數組成員初始化,如此在接下來使用這個數組時就潛在有記憶體問題。
#include <stdio.h>
int a[5];
int i,s;
a[0] = a[1] = a[3] = a[4] = 0;
s = 0;
for (i = 0;i<5;i++)
s += a[i];
if(s==377)
printf("sum is %d\n",s);
return 0;
==2838== Conditional jump or move depends on uninitialised value(s)
==2838== at 0x4005F3: main (badloop.c:11)
輸出結果顯示,程式的跳轉依賴于一個未初始化的變量。準确的發現了上述程式中存在的問題。
本文轉自 skinglzw 51CTO部落格,原文連結:http://blog.51cto.com/skinglzw/1923421,如需轉載請自行聯系原作者