《valgrind之內存調試》

valgrind之內存調試

 

摘要:由於CC++程序中常常需要程序員自主申請和釋放內存,在大型的、複雜的應用程序中就會常常出現內存錯誤。Valgrindlinux環境下的一款功能齊全的內存調試和性能分析工具集,它包括MemcheckCallgrindCachegrindHelgrindMassif等工具。本文旨在介紹Valgrind工具集中的內存檢測工具Memcheck的用法,以提高內存錯誤的查找效率。

關鍵字: ValgrindMemcheck,內存調試


1.       序言

Valgrindlinux環境下開發應用程序時用於內存調試和性能分析的工具集,其中Memcheck工具可以用來檢查C/C++程序中的內存操作錯誤。本文列舉了幾種常見的內存操作錯誤以及Memcheck工具的檢測結果,其中包括以下幾種類型:

  • 使用未初始化的內存
  • 內存讀寫越界
  • 內存覆蓋
  • 讀寫已經釋放的內存
  • 內存泄露

文章內容主要分爲四個部分,valgrind工具的下載與安裝、實例解析、常用選項說明和suppressing errors的設置。通過這四部分的學習,讀者可以基本掌握valgrind工具的內存調試方法。


2.       下載與安裝valgrind

valgrind最新版爲3.9.0版,發佈日期爲20131031號。目前大多是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

指定使用的具體工具,可以爲MemcheckCallgrindCachegrindHelgrindMassif等工具

-q, --quiet

只打印錯誤信息,儘可能保持安靜。

-v,--verbose

打印詳細信息

--trace-children=<yes|no> [default: no]

enable時,valgrind將會追蹤由exec類系統調用初始化的子進程,這對多進程應用非常有用。由於valgrind會自動追蹤fork產生的子進程,但仍然建議開啓該選項,因爲很多進程fork子進程後會立即調用exec

--trace-children-skip=patt1,patt2,……

patt1patt2指定了一系列的模式(進程名),用於指定某個進程及該進程的所有子進程不被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只顯示統計信息,yesfull顯示詳細信息,即上述四種泄露的詳細信息。

--leak-resolution=<low|med|high> [default: high]

設置內存泄露追蹤等級,默認爲highlowmed分別只向上追蹤24層調用。

--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 manual2.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)


發佈了34 篇原創文章 · 獲贊 17 · 訪問量 17萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章