内存泄露检测工具 - Valgrind
用C/C++开发其中最令人头疼的一个问题就是内存管理,有时候为了查找一个内存泄漏或者一个内存访问越界,需要要花上好几天时间,如果有一款工具能够帮助我们做这件事情就好了,valgrind正好就是这样的一款工具。
Valgrind是一款基于模拟linux下的程序调试器和剖析器的软件套件,可以运行于x86, amd64和ppc32架构上。valgrind包含一个核心,它提供一个虚拟的CPU运行程序,还有一系列的工具,它们完成调试,剖析和一些类似的任务。valgrind是高度模块化的,所以开发人员或者用户可以给它添加新的工具而不会损坏己有的结构。
valgrind的官方网址是:http://valgrind.org
你可以在它的网站上下载到最新的valgrind,它是开放源码和免费的。
一、介绍
valgrind包含几个标准的工具,它们是:
1、memcheck
memcheck探测程序中内存管理存在的问题。它检查所有对内存的读/写操作,并截取所有的malloc/new/free/delete调用。因此memcheck工具能够探测到以下问题:
1)使用未初始化的内存
2)读/写已经被释放的内存
3)读/写内存越界
4)读/写不恰当的内存栈空间
5)内存泄漏
6)使用malloc/new/new[]和free/delete/delete[]不匹配。
2、cachegrind
cachegrind是一个cache剖析器。它模拟执行CPU中的L1, D1和L2 cache,因此它能很精确的指出代码中的cache未命中。如果你需要,它可以打印出cache未命中的次数,内存引用和发生cache未命中的每一行代码,每一个函数,每一个模块和整个程序的摘要。如果你要求更细致的信息,它可以打印出每一行机器码的未命中次数。在x86和amd64上,cachegrind通过CPUID自动探测机器的cache配置,所以在多数情况下它不再需要更多的配置信息了。
3、helgrind
helgrind查找多线程程序中的竞争数据。helgrind查找内存地址,那些被多于一条线程访问的内存地址,但是没有使用一致的锁就会被查出。这表示这些地址在多线程间访问的时候没有进行同步,很可能会引起很难查找的时序问题。
二、valgrind对你的程序都做了些什么
valgrind被设计成非侵入式的,它直接工作于可执行文件上,因此在检查前不需要重新编译、连接和修改你的程序。要检查一个程序很简单,只需要执行下面的命令就可以了
valgrind --tool=tool_name program_name
比如我们要对ls -l命令做内存检查,只需要执行下面的命令就可以了
valgrind --tool=memcheck ls -l
不管是使用哪个工具,valgrind在开始之前总会先取得对你的程序的控制权,从可执行关联库里读取调试信息。然后在valgrind核心提供的虚拟CPU上运行程序,valgrind会根据选择的工具来处理代码,该工具会向代码中加入检测代码,并把这些代码作为最终代码返回给valgrind核心,最后valgrind核心运行这些代码。
如果要检查内存泄漏,只需要增加--leak-check=yes就可以了,命令如下
valgrind --tool=memcheck --leak-check=yes ls -l
不同工具间加入的代码变化非常的大。在每个作用域的末尾,memcheck加入代码检查每一片内存的访问和进行值计算,代码大小至少增加12倍,运行速度要比平时慢25到50倍。
valgrind模拟程序中的每一条指令执行,因此,检查工具和剖析工具不仅仅是对你的应用程序,还有对共享库,GNU C库,X的客户端库都起作用。
三、安装valgrind
1. 下载源码,http://valgrind.org/downloads/valgrind-3.7.0.tar.bz2
2. 上传linux计算机并解压:tar-jxvf valgrind-3.7.0.tar.bz2
3../configure
4. 编译make;
5. 安装make install
四、现在开始
首先,在编译程序的时候打开调试模式(gcc编译器的-g选项)。如果没有调试信息,即使最好的valgrind工具也将中能够猜测特定的代码是属于哪一个函数。打开调试选项进行编译后再用valgrind检查,valgrind将会给你的个详细的报告,比如哪一行代码出现了内存泄漏。
当检查的是C++程序的时候,还应该考虑另一个选项 -fno-inline。它使得函数调用链很清晰,这样可以减少你在浏览大型C++程序时的混乱。比如在使用这个选项的时候,用memcheck检查openoffice就很容易。当然,你可能不会做这项工作,但是使用这一选项使得valgrind生成更精确的错误报告和减少混乱。
一些编译优化选项(比如-O2或者更高的优化选项),可能会使得memcheck提交错误的未初始化报告,因此,为了使得valgrind的报告更精确,在编译的时候最好不要使用优化选项。
如果程序是通过脚本启动的,可以修改脚本里启动程序的代码,或者使用--trace-children=yes选项来运行脚本。
下面是用memcheck检查ls -l命令的输出报告,在终端下执行下面的命令
valgrind --tool=memcheck ls -l
程序会打印出ls -l命令的结果,最后是valgrind的检查报告如下:
[root@RedHat test]# valgrind --tool=memcheck ls -l
==10740== Memcheck, a memory error detector
==10740== Copyright (C) 2002-2011, and GNU GPL'd, by Julian Seward et al.
==10740== Using Valgrind-3.7.0 and LibVEX; rerun with -h for copyrightinfo
==10740== Command: ls -l
==10740==
total 0
==10740==
==10740== HEAP SUMMARY:
==10740== in use at exit: 12,896bytes in 3 blocks
==10740== total heap usage: 137allocs, 134 frees, 55,575 bytes allocated
==10740==
==10740== LEAK SUMMARY:
==10740== definitely lost: 0bytes in 0 blocks
==10740== indirectly lost: 0bytes in 0 blocks
==10740== possibly lost: 0bytes in 0 blocks
==10740== still reachable: 12,896bytes in 3 blocks
==10740== suppressed: 0bytes in 0 blocks
==10740== Rerun with --leak-check=full to see details of leaked memory
==10740==
==10740== For counts of detected and suppressed errors, rerun with: -v
==10740== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 19 from 8)
这里的“10740”指的是执行ls -l的进程ID,这有利于区别不同进程的报告。memcheck会给出报告,分配置和释放了多少内存,有多少内存泄漏了,还有多少内存的访问是可达的,检查了多少字节的内存。
下面举两个用valgrind做内存检查的例子
例子一 (test.c):
#include <string.h> int main(int argc, char *argv[]) { char *ptr; ptr = (char*) malloc(10); strcpy(ptr, "01234567890"); return 0; }
编译程序
gcc -g -o test test.c
用valgrind执行命令
valgrind --tool=memcheck --leak-check=yes ./test
报告如下
[root@RedHat test]# valgrind --tool=memcheck --leak-check=yes ./test
==10753== Memcheck, a memory error detector
==10753== Copyright (C) 2002-2011, and GNU GPL'd, by Julian Seward et al.
==10753== Using Valgrind-3.7.0 and LibVEX; rerun with -h for copyrightinfo
==10753== Command: ./test
==10753==
==10753== Invalid write of size 4
==10753== at 0x4008E98: memcpy(mc_replace_strmem.c:838)
==10753== by 0x8048429: main(test.c:8)
==10753== Address 0x4020030 is 8bytes inside a block of size 10 alloc'd
==10753== at 0x40070B4: malloc(vg_replace_malloc.c:263)
==10753== by 0x8048408: main(test.c:7)
==10753==
==10753==
==10753== HEAP SUMMARY:
==10753== in use at exit: 10bytes in 1 blocks
==10753== total heap usage: 1allocs, 0 frees, 10 bytes allocated
==10753==
==10753== 10 bytes in 1 blocks are definitely lost in loss record 1 of 1
==10753== at 0x40070B4: malloc(vg_replace_malloc.c:263)
==10753== by 0x8048408: main(test.c:7)
==10753==
==10753== LEAK SUMMARY:
==10753== definitely lost: 10bytes in 1 blocks
==10753== indirectly lost: 0bytes in 0 blocks
==10753== possibly lost: 0bytes in 0 blocks
==10753== still reachable: 0bytes in 0 blocks
==10753== suppressed: 0bytes in 0 blocks
==10753==
==10753== For counts of detected and suppressed errors, rerun with: -v
==10753== ERROR SUMMARY: 2 errors from 2 contexts (suppressed: 12 from 8)
从这份报告可以看出,进程号是10753,test.c的第8行写内存越界了,引起写内存越界的是strcpy函数,
第7行泄漏了10个字节的内存,引起内存泄漏的是malloc函数。
例子二(test2.c)
#include <stdio.h> int foo(int x) { if (x < 0) { printf("%d ", x); } return 0; } int main(int argc, char *argv[]) { int x; foo(x); return 0; }
编译程序
gcc -g -o test2 test2.c
用valgrind做内存检查
valgrind --tool=memcheck ./test2
输出报告如下
[root@RedHat test]# valgrind --tool=memcheck ./test2
==10767== Memcheck, a memory error detector
==10767== Copyright (C) 2002-2011, and GNU GPL'd, by Julian Seward et al.
==10767== Using Valgrind-3.7.0 and LibVEX; rerun with -h for copyrightinfo
==10767== Command: ./test2
==10767==
==10767== Conditional jump or move depends on uninitialised value(s)
==10767== at 0x80483CE: foo(test2.c:5)
==10767== by 0x80483FF: main(test2.c:16)
==10767==
==10767==
==10767== HEAP SUMMARY:
==10767== in use at exit: 0bytes in 0 blocks
==10767== total heap usage: 0allocs, 0 frees, 0 bytes allocated
==10767==
==10767== All heap blocks were freed -- no leaks are possible
==10767==
==10767== For counts of detected and suppressed errors, rerun with: -v
==10767== Use --track-origins=yes to see where uninitialised values comefrom
==10767== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 12 from 8)从这份报告可以看出进程PID是4285,test2.c文件的第16行调用了foo函数,在test2.c文件的第5行foo函数使用了一个未初始化的变量。
valgrind还有很多使用选项,具体可以查看valgrind的man手册页和valgrind官方网站的在线文档。
五、内核模块内存泄漏检测kmemcheck
1. 解压内核源码。
2. 配置编译选项CONFIG_KMEMCHECK=y。
3. 重新编译内核,启动编译后的内核。
4. 然后修改接口:
echo 1 > /proc/sys/kernel/kmemcheck #0:disable1:enable 2: one-slot
5. 执行需要检测的内核模块或内核代码;
6. 查看mesg日志
六、致谢: