valgrind之內存調試
摘要:由於C和C++程序中常常需要程序員自主申請和釋放內存,在大型的、複雜的應用程序中就會常常出現內存錯誤。Valgrind是linux環境下的一款功能齊全的內存調試和性能分析工具集,它包括Memcheck、Callgrind、Cachegrind、Helgrind、Massif等工具。本文旨在介紹Valgrind工具集中的內存檢測工具Memcheck的用法,以提高內存錯誤的查找效率。
關鍵字: Valgrind,Memcheck,內存調試
1. 序言
Valgrind是linux環境下開發應用程序時用於內存調試和性能分析的工具集,其中Memcheck工具可以用來檢查C/C++程序中的內存操作錯誤。本文列舉了幾種常見的內存操作錯誤以及Memcheck工具的檢測結果,其中包括以下幾種類型:
- 使用未初始化的內存
- 內存讀寫越界
- 內存覆蓋
- 讀寫已經釋放的內存
- 內存泄露
文章內容主要分爲四個部分,valgrind工具的下載與安裝、實例解析、常用選項說明和suppressing errors的設置。通過這四部分的學習,讀者可以基本掌握valgrind工具的內存調試方法。
2. 下載與安裝valgrind
valgrind最新版爲3.9.0版,發佈日期爲2013年10月31號。目前大多是linux distribution中都包含有valgrind工具,用戶可在命令行用valgrind --version命令查看其版本,本次測試所在機器上valgrind工具版本爲3.5.0:
# valgrind –version
valgrind-3.5.0
用戶也可在valgrind home網站上下載安裝包(http://www.valgrind.org/downloads/),解壓後參考其中的README文檔進行valgrind工具的安裝。在解壓後的valgrind工具目錄下,可按如下步驟安裝:
1) 配置系統環境:# ./autogen.sh
2) 檢查安裝環境並配置安裝參數:# ./configure --prefix=/usr (--prefix選項後製定安裝目錄,可自行指定其他合理路徑)
3) 編譯源文件:# make
4) 安裝valgrind:# make install
5) 檢測是否安裝成功:# valgrind --version
3. Valgrind工具的使用實例
a) 生成可執行文件
以下爲一個包含序言中提到的五種內存操作錯誤的測試用例:
爲了使Valgrind發現的錯誤更精確,能夠定位到源代碼行,在編譯源程序時需要加上-g參數:
# gcc -g mem_leak.c -o mem_leak
# ll
total 16
-rwxr-xr-x 1 root root 8863 Feb 16 18:17 mem_leak
-rw-r--r-- 1 root root 603 Feb 16 18:17 mem_leak.c
b) 啓動被測試的可執行文件
valgrind 命令的基本格式爲:valgrind [base option] --tool=<tool name> [tool option] your-program [program options]
valgrind的基本選項可參考本文第4小節。在可執行文件mem_leak所在目錄下,通過valgrind啓動該可執行文件的方式如下:
# valgrind --tool=memcheck --leak-check=full ./mem_leak
c) 錯誤信息分析
Valgrind工具會在可執行文件執行過程中順序輸出檢測到的錯誤信息。下面分段介紹可執行文件mem_leak的測試結果,其中雙斜線開頭的爲註釋信息。
工具相關信息:
//24593 爲進程ID,本段信息是valgrind的版本信息。 ==24593== Memcheck, a memory error detector ==24593== Copyright (C) 2002-2009, and GNU GPL'd, by Julian Seward et al. ==24593== Using Valgrind-3.5.0 and LibVEX; rerun with -h for copyright info ==24593== Command: ./mem_leak ==24593==
第一個內存錯誤:使用了未初始化的變量
//以下每一段信息都是一個錯誤提示。 //其中每段第一行描述錯誤類型爲uninitialised value相關。 //後面則列出該錯誤在源文件中的位置以及函數調用層次關係,這裏由於測試用例寫的很簡單,調用層次就出現了系統調用。 //下面四段信息均與源文件第11行代碼使用了未初始化的變量相關。 ==24593==Conditional jump or move depends on uninitialisedvalue(s) ==24593== at 0x3318C6C188:_IO_file_overflow@@GLIBC_2.2.5 (in /lib64/libc-2.5.so) ==24593== by 0x3318C474DA: vfprintf (in/lib64/libc-2.5.so) ==24593== by 0x3318C4D549: printf (in/lib64/libc-2.5.so) ==24593== by 0x4005B4: main (mem_leak.c:11) ==24593== ==24593==Conditional jump or move depends on uninitialisedvalue(s) ==24593== at 0x3318C6C1BB:_IO_file_overflow@@GLIBC_2.2.5 (in /lib64/libc-2.5.so) ==24593== by 0x3318C474DA: vfprintf (in/lib64/libc-2.5.so) ==24593== by 0x3318C4D549: printf (in/lib64/libc-2.5.so) ==24593== by 0x4005B4: main (mem_leak.c:11) ==24593== ==24593==Conditional jump or move depends on uninitialisedvalue(s) ==24593== at 0x3318C474E0: vfprintf (in/lib64/libc-2.5.so) ==24593== by 0x3318C4D549: printf (in/lib64/libc-2.5.so) ==24593== by 0x4005B4: main (mem_leak.c:11) ==24593== ==24593==Syscall param write(buf) points to uninitialisedbyte(s) ==24593== at 0x3318CC6420: __write_nocancel (in/lib64/libc-2.5.so) ==24593== by 0x3318C6BC02:_IO_file_write@@GLIBC_2.2.5 (in /lib64/libc-2.5.so) ==24593== by 0x3318C6BB15: _IO_do_write@@GLIBC_2.2.5(in /lib64/libc-2.5.so) ==24593== by 0x3318C6CF71:_IO_file_xsputn@@GLIBC_2.2.5 (in /lib64/libc-2.5.so) ==24593== by 0x3318C4368E: vfprintf (in/lib64/libc-2.5.so) ==24593== by 0x3318C4D549: printf (in/lib64/libc-2.5.so) ==24593== by 0x4005B4: main (mem_leak.c:11) ==24593== Address 0x4c0a009 is not stack'd, malloc'd or(recently) free'd ==24593== //此行爲可執行文件的輸出信息,由於str[0]未初始化,此處沒有打印出可見字符。 str[0]=
第二個內存錯誤:內存讀寫越界
//下面三短信息均爲Invalid read/write錯誤,同時valgrind還指出了讀寫的size。
//錯誤信息指出讀寫的指針在源文件第7行代碼中分配空間。
==24593== Invalid read of size 1
==24593== at 0x4005BD: main (mem_leak.c:14)
==24593== Address 0x4c47054 is 10 bytes after a block of size 10 alloc'd
==24593== at 0x4A05E1C: malloc (vg_replace_malloc.c:195)
==24593== by 0x400589: main (mem_leak.c:7)
==24593==
str[20] =
==24593== Invalid write of size 4
==24593== at 0x4005DA: main (mem_leak.c:15)
==24593== Address 0x4c4704a is 0 bytes after a block of size 10 alloc'd
==24593== at 0x4A05E1C: malloc (vg_replace_malloc.c:195)
==24593== by 0x400589: main (mem_leak.c:7)
==24593==
==24593== Invalid write of size 1
==24593== at 0x4005E0: main (mem_leak.c:15)
==24593== Address 0x4c4704e is 4 bytes after a block of size 10 alloc'd
==24593== at 0x4A05E1C: malloc (vg_replace_malloc.c:195)
==24593== by 0x400589: main (mem_leak.c:7)
==24593==
第三個內存錯誤:內存覆蓋
// valgrind提示Source anddestination overlap,即檢測到內存覆蓋錯誤。
//該錯誤由源文件19行的strncpy導致。
==24593== Source and destination overlap in strncpy(0x4c47045, 0x4c47047, 5)
==24593== at 0x4A08267: strncpy (mc_replace_strmem.c:329)
==24593== by 0x400610: main (mem_leak.c:19)
==24593==
第四個內存錯誤:讀寫已經釋放的內存
//這裏同樣提示Invalid read/write,及其讀寫的size
//不過此處提示錯誤在源文件22行,此行將str2指向的空間釋放了,但沒有將str2賦爲NULL,23行和24行的讀寫操作導致了此錯誤。
==24593==Invalid read of size 1
==24593== at 0x40061E: main (mem_leak.c:23)
==24593== Address 0x4c47090 is 0 bytes inside a blockof size 10 free'd
==24593== at 0x4A05A31: free(vg_replace_malloc.c:325)
==24593== by 0x400619: main (mem_leak.c:22)
==24593==
str2[0]=
==24593==Invalid write of size 4
==24593== at 0x400637: main (mem_leak.c:24)
==24593== Address 0x4c47090 is 0 bytes inside a blockof size 10 free'd
==24593== at 0x4A05A31: free(vg_replace_malloc.c:325)
==24593== by 0x400619: main (mem_leak.c:22)
==24593==
==24593==Invalid write of size 1
==24593== at 0x40063D: main (mem_leak.c:24)
==24593== Address 0x4c47094 is 4 bytes inside a blockof size 10 free'd
==24593== at 0x4A05A31: free(vg_replace_malloc.c:325)
==24593== by 0x400619: main (mem_leak.c:22)
==24593==
第五個內存錯誤:內存泄露。valgrind在可執行文件執行結束後,會輸出堆上內存變化的統計信息(HEAP SUMMARY)與內存泄漏統計信息(LEAK SUMMARY)。這是內存調試過程中非常重要的信息,通過它們,我們會清楚地知道內存有沒有泄漏以及該泄漏屬於哪種類型。
//valgrind提示程序退出前,還有10bytes空間未釋放,即源代碼中str所指向空間
//valgrind還統計了空間開闢和釋放的次數以及開闢的總大小,分別爲2 allocs, 1 frees, 20 bytes allocated
//最後valgrind還指出了未釋放空間的開闢位置在源文件第7行
==24593==
==24593==HEAP SUMMARY:
==24593== in use at exit: 10 bytes in 1 blocks
==24593== total heap usage: 2 allocs, 1 frees, 20bytes allocated
==24593==
==24593==10 bytes in 1 blocks are definitely lost in loss record 1 of 1
==24593== at 0x4A05E1C: malloc(vg_replace_malloc.c:195)
==24593== by 0x400589: main (mem_leak.c:7)
==24593==
//下面是內存泄漏的統計信息:
//其中definitelylost爲最嚴重的泄漏類型,例如本次的開闢空間未釋放
//其他幾種類型的內存泄露可參考valgrind manual的4.2.7小節內容④:
==24593==LEAK SUMMARY:
==24593== definitely lost: 10 bytes in 1 blocks
==24593== indirectly lost: 0 bytes in 0 blocks
==24593== possibly lost: 0 bytes in 0 blocks
==24593== still reachable: 0 bytes in 0 blocks
==24593== suppressed: 0 bytes in 0 blocks
Valgrind的錯誤統計
//valgrind一共檢測到12個錯誤,上面所列5類錯誤總共包括12段。
==24593==
==24593== For counts of detected andsuppressed errors, rerun with: -v
==24593== Use --track-origins=yes to seewhere uninitialised values come from
==24593==ERROR SUMMARY: 12 errors from 12 contexts (suppressed: 4 from 4)
4. Valgrind工具選項說明
valgrind 命令的基本格式爲:valgrind [base option] --tool=<tool name> [tool option] your-program [program options]
本文僅列出了一些常用的選項,更詳細的選項說明可參考valgrind home網站上的相關章節(http://www.valgrind.org/)。
a) Valgrind基本選項及其說明
--tool
指定使用的具體工具,可以爲Memcheck、Callgrind、Cachegrind、Helgrind、Massif等工具
-q, --quiet
只打印錯誤信息,儘可能保持安靜。
-v,--verbose
打印詳細信息
--trace-children=<yes|no> [default: no]
enable時,valgrind將會追蹤由exec類系統調用初始化的子進程,這對多進程應用非常有用。由於valgrind會自動追蹤fork產生的子進程,但仍然建議開啓該選項,因爲很多進程fork子進程後會立即調用exec。
--trace-children-skip=patt1,patt2,……
patt1,patt2指定了一系列的模式(進程名),用於指定某個進程及該進程的所有子進程不被valgrind追蹤。
--trace-children-skip-by-arg=patt1,patt2,……
和--trace-children-skip類似,只是通過參數來匹配進程。
--log-file=<filename>
將valgrind的所有輸出信息都打印到指定文件。如果不指定filename此選項將會被忽略。filename有三種特殊的指定方式:
%p:進程ID
%q{FOO}:用環境變量FOO來代替
%%:表示特殊字符%
b) Memcheck相關選項及其說明
--leak-check=<no|summary|yes|full> [default: summary]
no表示不檢測,summary只顯示統計信息,yes和full顯示詳細信息,即上述四種泄露的詳細信息。
--leak-resolution=<low|med|high> [default: high]
設置內存泄露追蹤等級,默認爲high,low和med分別只向上追蹤2和4層調用。
--show-leak-kind=<definite|indirect|possible|reachable|all|none>
指定檢測哪幾種leak
--track-origins=<yes|no> [default: no]
指定valgrind是否追蹤使用的未初始化變量的源頭。
5. Suppressing errors的設置
Suppressing errors是工具valgrind在測試過程中會自動過濾並忽略的錯誤,可由用戶自行設定。默認情況下valgrind打印出來的錯誤信息非常多,不利於檢查。用戶可以通過以下兩種常用方式設置suppressing errors:
- 設置安裝目錄下的默認supp文件(/install_path/lib/valgrind/default.supp)。
- --suppressions命令指定用戶自己的supp文件:--suppressions =/path/to/file.supp
a) Supp文件的書寫格式
Supp文件的詳細格式可參考valgrind manual的2.5小節,附件中列出了相關參考網址。
- 一個文件可包含多個suppressingerrors。
- 每個suppressingerrors由一對花括號括起。
- 花括號必須獨佔兩行並寫在每行第一個字符。
- 花括號內爲suppressingerrors的說明。
關於花括號內的suppressingerrors說明,舉例說明如下:
{
a-contrived-example //第一行:suppressingerrors的名字
Memcheck:Leak //第二行:工具名:具體的suppression名
fun:malloc //以下所有行:函數調用關係,“…”表示1層或多層調用
...
fun:ddd
...
fun:main
}
//該suppressing errors表示:未釋放空間的malloc函數在函數ddd中直接或間接被調用,函數main又直接或間接調用函數ddd,設置這種memory leak爲suppressing errors。
b) Suppressing errors 的設置
書寫自己的supp文件的一個很好的辦法是。下面是一個測試的supp文件,他可以屏蔽掉本文測試用例中除了內存泄露以外的所有錯誤,內容如下所示:
{
My_own_supp_uninitialise1
Memcheck:Cond //Cond表示使用未初始化內存的錯誤
fun:_IO_file_overflow@@GLIBC_2.2.5
fun:vfprintf
fun:printf
fun:main
}
{
My_own_supp_uninitialise2
Memcheck:Cond
fun:vfprintf
fun:printf
fun:main
}
{
My_own_supp_uninitialise3
Memcheck:Param //系統調用的參數錯誤,具體調用參考下一行
write(buf) //系統調用write,其buf參數無效
fun:__write_nocancel
fun:_IO_file_write@@GLIBC_2.2.5
fun:_IO_do_write@@GLIBC_2.2.5
fun:_IO_file_xsputn@@GLIBC_2.2.5
fun:vfprintf
fun:printf
fun:main
}
{
My_own_supp_invalid_rw1
Memcheck:Addr1 //Addr1表示使用無效地址1bytes
fun:main
}
{
My_own_supp_invalid_rw4
Memcheck:Addr4
fun:main
}
{
My_own_supp_overlap
Memcheck:Overlap //Overlap表示內存覆蓋錯誤
fun:strncpy
fun:main
}
通過valgrind啓動可執行文件時指定選項--suppressions=/file_path/test.supp,輸出的錯誤提示只剩下內存泄露這一項,其他的錯誤都被忽略了,具體如下:
# valgrind --tool=memcheck --leak-check=full--suppressions=./test.supp ./mem_leak
==3703==Memcheck, a memory error detector
==3703==Copyright (C) 2002-2009, and GNU GPL'd, by Julian Seward et al.
==3703==Using Valgrind-3.5.0 and LibVEX; rerun with -h for copyright info
==3703==Command: ./mem_leak
==3703==
str[0] =
str[20]=
str2[0]=
==3703==
==3703==HEAP SUMMARY:
==3703== in use at exit: 10 bytes in 1 blocks
==3703== total heap usage: 2 allocs, 1 frees, 20bytes allocated
==3703==
==3703==10 bytes in 1 blocks are definitely lost in loss record 1 of 1
==3703== at 0x4A05E1C: malloc(vg_replace_malloc.c:195)
==3703== by 0x400589: main (mem_leak.c:7)
==3703==
==3703==LEAK SUMMARY:
==3703== definitely lost: 10 bytes in 1 blocks
==3703== indirectly lost: 0 bytes in 0 blocks
==3703== possibly lost: 0 bytes in 0 blocks
==3703== still reachable: 0 bytes in 0 blocks
==3703== suppressed: 0 bytes in 0 blocks
==3703==
==3703==For counts of detected and suppressed errors, rerun with: -v
==3703==ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 15 from 15)