一. 简述valgrind是什么,为何能进行内存泄露
valgrind是一个程序调试及性能分析的工具集,涵盖memcheck, cachegrind,helgrind,callgrind,启动valgrind时通过--tool来指定具体要调用的工具。不论使用哪个工具,通过valgrind来启动程序时都会取得对程序的控制权,从关联库中读取调试信息。然后在valgrind核心提供的虚拟CPU上运行程序,根据选择的工具处理代码,该工具会向代码中植入检测代码,并把这些代码作为最终代码返回给valgrind核心,最后valgrind核心运行这些代码。
我们最常用valgrind进行内存泄露检查,通过--tool = memcheck --leak-check=yes指定。需要注意的是,memcheck会加入代码检查每一片内存的访问和进行值运算,导致整体代码大小至少增加12倍,运行速度比平时慢25-50倍,所以使用valgrind时,保证机器环境有足够多的内存,如果进程本身启动内存有十几G,那用valgrind启动程序时,一般启动特别慢,可能1h才能启动程序。或者,valgrind根本就拉不起来程序,此时,需要修改程序的相关配置参数,想办法将程序启动的进程内存减小。
二.编译程序时的注意
1. 编译时,打开调试模式(gcc编译器的-g选项)。
2. 编译时,关闭编译优化选项。一些编译优化选项(比如-O2或者更高的优化选项),可能会使得memcheck提交错误的未初始化报告,因此,为了使得valgrind的报告更精确,在编译的时候最好不要使用优化选项。
3. 如果程序有对tcmalloc编译依赖的话,需要将tcmalloc从编译依赖中去掉,否则valgrind扫描可能会遗漏一些报警信息。
4. 当检查的是C++程序的时候,还应该考虑另一个选项 -fno-inline。它使得函数调用链很清晰,这样可以减少你在浏览大型C++程序时的混乱。比如在使用这个选项的时候,用memcheck检查openoffice就很容易。当然,你可能不会做这项工作,但是使用这一选项使得valgrind生成更精确的错误报告和减少混乱。
三. valgrind下载 & 安装
---> 下载:wget http://www.valgrind.org/downloads/valgrind-3.8.1.tar.bz2
目前最新的版本是:Release 3.12.0 20 October 2016
---> 解压:bzip2 -d valgrind-3.8.1.tar.bz2; tar -xvf valgrind-3.8.1.tar
---> 安装: cd valgrind-3.8.1; ./configure --prefix=/home/work/tools/valgrind-3.8.1.install;make;make install
安装好后在valgrind-3.8.1.install目录里有: bin, lib, include, share 4个子目录
---> 确认是否安装正确:valgrind --version
---> 编辑/etc/profile,将valgrind工具加入PATH,再source /etc/profile,这样每次使用时都是新安装的valgrind
四、valgrind memcheck选项
参见手册http://www.valgrind.org/docs/manual/mc-manual.html
valgrind的选项非常的多,可以参考翻译手册中的说明进行选用,下面列出几个比较常用的:
Option :--leak-check=no|summary|full|yes [default summary]
Purpose : 当这个选项打开时,会当客户程序结束时查找内存泄露。如果设置为summary,Valgrind会报告有多少内存泄露发生了。如果设置为full或yes,Valgrind给出每一个独立的泄露的详细信息。
Option :--leak-resolution=high|med|low [default low]
Purpose :在做内存泄漏检查时,确定memcheck将怎么样考虑不同的栈是相同的情况。当设置为low时,只需要前两层栈匹配就认为是相同的情况;当设置为med,必须要四层栈匹配,当设置为high时,所有层次的栈都必须匹配。
注意--leak-resolution= 设置并不影响memcheck查找内存泄漏的能力。它只是改变了结果如何输出。
Option: --show-reachable=yes|no [default no]
Purpose : 当这个选项关闭时,内存泄露检测器只显示没有指针指向的内存块,或者只能找到指向块中间的指针。当这个选项打开时,内存泄露检测器还报告有指针指向的内存块。这些块是最有可能出现内存泄露的地方。你的程序可能,至少在原则上,应该在退出前释放这些内存块。这些有指针指向的内存块和没有指针指向的内存块,或者只有内部指针指向的块,都可能产生内存泄露,因为实际上没有一个指向块起始的指针可以拿来释放,即使你想去释放它。
Option: -v
Purpose : 显示详细信息。在各个方面显示你的程序的额外信息,例如:共享对象加载,使用的重置,执行引擎和工具的进程,异常行为的警告信息。重复这个标记可以增加详细的级别。
Option : -fno-inline
Purpose : 当检查的是C++程序的时候,可考虑这个选项-fno-inline。它使得函数调用链很清晰
Option : --max-stackframe=[default: 2000000]
Purpose : 栈的最大值,默认2000000。如果栈指针的偏移超过这个数量,valgrind则会认为程序是切换到了另外一个栈执行。
五. 内存泄露扫描过程
---> 程序启动(如果程序本身内存十几个G,启动非常慢,可能1h多才启动):
nohup valgrind--tool=memcheck --leak-check=full --show-reachable=yes
--max-stackframe=8000000 --log-file=./valgrind.log [程序启动命令] &
---> 判断程序是否启动成功:
搜索程序端口是否被监听来判断程序是否被valgrind拉起
netstat -anlp|grep 9600|grep LISTEN ##如果进程名是valgrind,则表示程序已经在valgrind环境中启动成功。
---> 给程序放一些请求,请求尽可能覆盖到程序的各个分支
---> 程序优雅退出,不要让服务在valgrind环境里core掉了。尽量服务有捕获kill信号能力,一般kill -9 pid会导致服务core掉。
---> 退出等待个几分钟,让valgrind收集退出信息输出到valgrind.log。最后分析valgrind.log
六. valgrind.log 报告分析
见手册http://www.valgrind.org/docs/manual/mc-manual.html ,关注关注definitely lost和possible lost处可能的内存泄露。
1)首先搜关键字『LEAK SUMMARY』,看『definitely lost』对应是否有值,如果不为0,肯定有内存泄露。
2)如果有内存泄露,往上搜报告中『definitely lost in loss』的报警处,对照程序代码一一排查。
3)常见内存泄露警告
---> a. Illegal read / Illegal write errors
已释放后的内存进行读/写或数组的越界访问等,valgrind报告程序在尝试读写非法内存,造成的后果是程序易发生segmentation fault(吐core)风险。
Invalid write of size X,访问超出了范围的内存,试图从该内存读取数据
---> b. Use of uninitialised values
检测使用未初始化变量,可以检测在条件判断语句中使用未初始化变量,因此应该养成在声明变量时就进行初始化的习惯
Conditional jump or move depends on uninitialised value(s)
---> c. When a heap block is freed with an inappropriate deallocation function
使用不正确的方法释放内存,比如new/delete , malloc/free使用混淆了,new分配的地方用free释放了
Mismatched free() / delete / delete []
---> d. Illegal frees
重复内存释放,比如2次使用free释放同一块内存
Invalid free() / delete / delete[]
---> e. Overlapping source and destination blocks
针对C语言常见的memcpy, strcpy, strncpy, strcat, strncat拷贝类函数,出现源串和目标串地址重叠
Source and destination overlap in memcpy(0xbffff294, 0xbffff280, 21)
---> f. Fishy argument values
所有内存分配类函数在分配内存时,都会传入一个内存分配大小的数,如果你传入的数大于机器所能分配的最大数时,如64位机器上,你传入的size大于了2**63
Argument 'size' of function malloc has a fishy (possibly negative) value: -3
---> g. Leak detecion
HEAP SUMMARY:
如果指定--show-reachable=yes,在程序退出时memcheck会收集reachable and indirectly lost blocks
LEAK SUMMARY:
memcheck会记录由malloc/new等函数创建的所有的heap blocks,因此在程序退出时,memcheck能够知道哪些block没有free。如下:
definitely lost: 4 bytes in 1 blocks
indirectly lost: 0 bytes in 0 blocks
possibly lost: 0 bytes in 0 blocks
still reachable: 95 bytes in 6 blocks
definitely lost:如果一个block在程序在退出后,memcheck找不到指向它的pointer,一般是由于在代码中对该block没有free造成的,要重点关注。
possibly lost:在程序退出时,memcheck发现仍有 interior pointer(如果一个pointer指向一个block的中间某个位置)指向一个block,那么该block被认为是possibly lost。
still reachable:如果memcheck发现仍有start pointer指向一个block,那么该block就是still reachable。
4)valgrind不能查出哪些错误?
valgrind不对静态数组(分配在栈上)进行边界检查。如果在程序中声明了一个数组:
int main()
{
char x[10];
x[11] = ´a´;
}
valgrind则不会警告你,出于测试目的,你可以把数组改为动态在堆上分配的数组,这样就可能进行边界检查了。这个方法好像有点得不偿失的感觉。
七. 安装或使用中踩得各种坑
1)valgrind: failed to start tool 'memcheck' for platform 'amd64-linux'
解决办法:
该错误是valgrind启动时,无法找到相关工具文件,有可能是安装好后相关lib的path未设置,也有可能是一开始安装就有问题。解决办法如下:
如果已经安装成功,试一下导出VALGRIND_LIB路径,用法如下(假设valgrind已经被安装到/home/work/tools/valgrind目录):##亲测有效##
export VALGRIND_LIB=/home/work/tools/valgrind3.8.1/lib/valgrind
2)valgrind --version报错
valgrind: mmap((nil), 134512640) failed during startup.
valgrind: is there a hard virtual memory limit set?
解决办法:
见下,版本太老,里边有bug,重新安装一下较新版本应该能解决。https://twiki.cern.ch/twiki/bin/view/LHCb/CodeAnalysisTools
3)vg_alloc_ThreadState: no free slots available
Increase VG_N_THREADS, rebuild and try again.
valgrind: the 'impossible' happened:
VG_N_THREADS is too low
解决办法:
a) 修改VG_N_THREADS和NTHREADS的值,安装用的是3.8.1版本,默认VG_N_THREADS是500,现在改为5000,同时NTHREADS由498改成4998。
include/pub_tool_threadstate.h: #define VG_N_THREADS 5000
./memcheck/tests/err_disable4.c: #define NTHREADS 4998 // VG_N_THREADS - 2
b) 按照1进行重新安装。
4) Valgrind: FATAL: VG_N_SEGMENTS is too low.
解决办法:
VG_N_SEGMENTS默认是5000。修改coregrind/m_aspacemgr/aspacemgr-linux.c: VG_N_SEGMENTS从5000改到50000,VG_N_NAME从1000改到5000。
按照1进行重新安装。
5)valgrind.log报告中,在definitely lost的代码处全是?????全写花了,看不出具体泄露的是在哪一行代码
解决办法:
加上-g,打开调试信息重新编译程序;
valgrind扫描时加上--show-reachable=yes。
参考:
1. http://www.xlgps.com/article/216977.html
2. 如何使用Valgrind memcheck工具进行C/C++的内存泄漏检测
4. valgrind介绍