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:
a) Supp文件的书写格式
Supp文件的详细格式可参考valgrind manual的2.5小节,附件中列出了相关参考网址。
关于花括号内的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)