《valgrind之内存调试》

来源:互联网 发布:手绘视频软件bgm 编辑:程序博客网 时间:2024/04/30 20:16

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 –versionvalgrind-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# lltotal 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)


0 0