目录
1、Valgrind简介
1.1、Memcheck工具
1.2、Callgrind工具
1.3、Cachegrind工具
1.4、Helgrind工具
1.5、Massif工具
2、如何使用Memcheck
2.1、启动Memcheck
2.2、输出消息解释
3、使用Memcheck检测内存问题实例
4、Valgrind和Memcheck其他命令选项
5、最后
VC++常用功能开发汇总(专栏文章列表,欢迎订阅,持续更新...)https://blog.csdn.net/chenlycly/article/details/124272585C++软件异常排查从入门到精通系列教程(专栏文章列表,欢迎订阅,持续更新...)https://blog.csdn.net/chenlycly/article/details/125529931C++软件分析工具从入门到精通案例集锦(专栏文章,持续更新中...)https://blog.csdn.net/chenlycly/article/details/131405795C/C++基础与进阶(专栏文章,持续更新中...)https://blog.csdn.net/chenlycly/category_11931267.html开源组件及数据库技术(专栏文章,持续更新中...)https://blog.csdn.net/chenlycly/category_12458859.html 对于C/C++程序开发人员来说,程序中大部分问题都是与内存有关的,内存相关的bug会贯穿着整个软件开发与维护过程,有些内存问题排查起来会相当的困难,甚至会严重影响项目推进的进度。本文介绍Linux下的一款重量级调式与分析工具包Valgrind,并针对其中最常用的工具Memcheck的使用进行较详细的介绍。
1、Valgrind简介
Valgrind是一套Linux下开放源代码(GPL V2)的仿真调试工具的集合,是运行在Linux 上的多用途代码分析和内存调试常用工具。Valgrind由内核(core)以及基于内核的其他调试工具组成。内核类似于一个框架(framework),它模拟了一个CPU环境,并提供服务给其他工具;而其 他工具则类似于插件 (plug-in),利用内核提供的服务完成各种特定的内存调试任务。
Valgrind支持的平台有:X86/Linux, AMD64/Linux, ARM/Linux, ARM64/Linux, PPC32/Linux, PPC64/Linux, PPC64LE/Linux, S390X/Linux, MIPS32/Linux, MIPS64/Linux, X86/Solaris, AMD64/Solaris, ARM/Android (2.3.x and later), ARM64/Android, X86/Android (4.0 and later), MIPS32/Android, X86/FreeBSD, AMD64/FreeBSD, X86/Darwin and AMD64/Darwin (Mac OS X 10.12).
Valgrind被设计成非入侵式的工具,无须修改源程序或重新编译链接,直接对运行时的可执行程序进行检测。它包含Memcheck、Callgrind、Cachegrind、Helgrind和Massif等几大工具,其中Memcheck是最常用的工具,也是默认选项,即不指定使用哪个工具时就是使用Memcheck进行检查。
Valgrind的体系结构如下图所示:
如上图,Valgrind提供了Memcheck、Callgrind、Cachegrind、Helgrind和Massif等调试分析工具,每个工具执行某些类型的调试和分析任务,以排查和分析软件中存在的问题。
1.1、Memcheck工具
这是Valgrind应用最广泛的一个工具,用来检测程序中多种内存问题,所有对内存的读写都会被监测到,一切对malloc、free、new、delete的调用都会被跟踪。该工具可以检测到如下的内存异常:
1)对未初始化内存的使用;
2)读/写已经释放了的内存块(引发内存访问违例);
3)读/写超出分配的内存块(内存越界);
4)读/写不适当的栈中内存块;
5)内存泄露;
6)malloc/free或者new/delete不匹配;
7)memcpy相关函数中的dst和src指针中内存地址重叠;
1.2、Callgrind工具
它主要用来检查程序中函数调用过程中出现的问题。它对程序的运行观察细致入微,能给我们提供更多的信息。它不需要在编译源代码时附加特殊选项,但还是推荐加上调试选项。Callgrind收集程序运行时的一些数据,建立函数调用关系图,还可以有选择地进行cache模拟。在运行结束时,它会把分析数据写入一个文件。callgrind_annotate可以把这个文件的内容转化成可读的形式。
1.3、Cachegrind工具
它主要用来检查程序中使用缓存时出现的问题。它模拟CPU中的一级缓存和二级缓存,能够精确地指出程序中cache的丢失和命中。如果需要,它还能够为我们提供cache丢失次数,内存引用次数,以及每行代码,每个函数,每个模块,整个程序产生的指令数,这对优化程序有很大的帮助。
1.4、Helgrind工具
它主要用来检查多线程程序中出现的竞争问题。Helgrind寻找内存中被多个线程访问,而又没有一贯加锁的区域,这些区域往往是线程之间失去同步的地方,而且会导致难以发觉的错误。Helgrind实现了名为Eraser的竞争检测算法,并做了进一步改进,减少了报告错误的次数。
1.5、Massif工具
它主要用来检查程序中使用堆栈时出现的问题。它能测量程序在堆栈中使用了多少内存,告诉我们堆块,堆管理块和栈的大小。Massif能帮助我们减少内存的使用,在带有虚拟内存的现代系统中,它还能够加速我们程序的运行,减少程序停留在交换区中的几率。
Massif对内存的分配和释放做profile。程序开发者通过它可以深入了解程序的内存使用行为,从而对内存使用进行优化。这个功能对C++尤其有用,因为C++有很多隐藏的内存分配和释放。
Valgrind是基于仿真的方式对程序进行调试,它先于应用程序获取实际处理器的控制权,并在实际处理器的基础上仿真一个虚拟处理器,并使应用程序运行于这个虚拟处理器之上,从而对应用程序的运行进行监视。应用程序并不知道该处理器是虚拟的还是实际的,已经编译成二进制代码的应用程序并不用重新进行编译,Valgrind 直接解释二进制代码使得应用程序基于它运行,从而能够检查内存操作时可能出现的错误。所以,在Valgrind下运行的程序运行速度要慢的多,而且使用的内存比目标程序要多的多,这也是Valgrind的一大劣势,这也导致部分场合下没法使用Valgrind去分析。
2、如何使用Memcheck
Memcheck是Valgrind中最常用的工具,下文将重点介绍它。
2.1、启动Memcheck
要启动Memcheck工具,直接敲入以下命令:
[root@localhost ~]# valgrind --tool=memcheck ls -a
其中--tool选项指定需所使用的工具,后面跟所要检查的程序及其启动参数(如ls -a)。因为Memcheck是Valgrind工具包默认使用工具,因此上面命令可以直接写成如下形式:
[root@localhost ~]# valgrind ls -a
这种形式,Valgrind会将检测结果直接输出到标准输出,如果所检查的程序也有标准输出的话,会夹杂在一起,阅读时容易造成混乱。使用如下方式启动将Valgrind检测结果输出到日志文件阅读时会比较清楚:
[root@localhost ~]# valgrind --tool=memcheck --log-file=filename ls –a
选--log-file用来指定输出的日志文件名,文件命中如果含有占位符%p,则实际生成的文件名该占位符将会被对应的进程号代替,这在检测多进程程序时比较有用,否则所有输出会写入一个单独的文件。
2.2、输出消息解释
以上面最后一条命令为例,我们得到对应的输出文件,查看文件,内容如下:
输出消息的各部份说明见上图示,可以看到它把内存泄露分为以下几个等级:肯定的(definitely)、可能的(possibly)。
另外,有个针对Memcheck的选项--leak-check=<no|summary|yes|full>,缺省值为summary即只给出泄露的摘要,如果设为yes或full,则会详细给出检查到的每个泄露的细节。
3、使用Memcheck检测内存问题实例
下面结合具体的程序例子来检验Memcheck工具的效果,并对一些具体的输出消息作一下说明。先看一个特意编写的错误程序:
敲入命令valgrind --tool=memcheck --leak-check=full --log-file=%pmemcheck ./valmemcheck后得到错误消息日志文件,为易于理解输出的错误消息,特意将源代码截图(右边)和错误消息截图(左边)放在一起,并用矩形框和连线把错误消息和对应的源代码对应起来,请放大参看下图:
基本上,大部分错误消息块分为两部分:上部分第一行指出错误类型,下面几行是函数调用栈回溯,这一部分也是比较重要的部分,直接提供给我们错误现场信息;对于有下部分的消息块,则其第一行指出具体错误涉及的内存地址,下面几行同样也是函数调用栈回溯。函数调用栈显示了函数所在源码行号,对于标准函数或库函数对应所在文件为vg_replace_xxx.c或mc_replace_xxx.c文件,我们可以不必理会,可以理解为Valgrind或Memcheck对这些库函数进行了hook。参见下面示意图:
对于第一个错误引用未初始化的内存,看到消息块的上面部分函数调用栈并没有直接指出错误所在的源代码行号:
==12299== Syscall param write(buf) points to uninitialised byte(s)
==12299== at 0x7990F3: __write_nocancel (in /lib/tls/libc-2.3.4.so)
==12299== by 0x6F4DE2: (below main) (in /lib/tls/libc-2.3.4.so)
==12299== Address 0x402b028 is 0 bytes inside a block of size 10 alloc'd
==12299== at 0x4004A41: malloc (vg_replace_malloc.c:207)
==12299== by 0x8048565: main (valmemcheck.cpp:8)
Memcheck有个选项--track-origins=<yes|no>用来控制是否跟踪未初始化内存的原始创建地,默认为no。将该选项设为yes重新检查后,重新输出该错误的消息块如下:
==15781== Syscall param write(buf) points to uninitialised byte(s)
==15781== at 0x7990F3: __write_nocancel (in /lib/tls/libc-2.3.4.so)
==15781== by 0x6F4DE2: (below main) (in /lib/tls/libc-2.3.4.so)
==15781== Address 0x402b028 is 0 bytes inside a block of size 10 alloc'd
==15781== at 0x4004A41: malloc (vg_replace_malloc.c:207)
==15781== by 0x8048565: main (valmemcheck.cpp:8)
==15781== Uninitialised value was created by a heap allocation
==15781== at 0x4004A41: malloc (vg_replace_malloc.c:207)
==15781== by 0x8048565: main (valmemcheck.cpp:8)
可以看到增加了最后3行消息。
结合错误日志和源码分析,可以判断所报的源和目的地址重叠问题基本上都准确,但内存泄露、malloc/new/new[]和free/delete/delete[]不匹配等错误,当内存地址指针通过几层函数调用传递后的情况,报告的错误不一定准确。
需要注意的是,在用Memcheck工具检查程序时,内存泄露错误需要在程序正常退出时才会报告,函数中调用exit()、或非正常退出都不会报告该类错误;其他类型的错误如访问越界、地址重叠等在运行过程中如果发生会马上报告。
此外,被检查的程序应该用debug版本,并且优化选项不能高于-O0,即编译选项为-g –O0。否则,Valgrind报告的错误所在源码行数会不准确。
4、Valgrind和Memcheck其他命令选项
本篇对工具的介绍参考了Valgrind官方的User Manual,有些地方按照原文翻译,对存在的翻译不贴切之处欢迎大家提出建议,如果想了解更多更细的信息可直接上官方网站(http://valgrind.org)下载手册,或在Linux上敲valgrind --help或man valgrind命令查看相关帮助信息。如果所使用的Linux系统上没有该工具,可直接上其官方网站下载源码进行编译安装,本文就不一一展开了。
除了上文已提到的主要常用的命令参数选项,Valgrind和Memcheck还有很多选项,此处列出仅为方便索引,如需使用,可查看相关文档。Valgrind的命令选项:
Memcheck的命令选项:
5、最后
通过对实际程序的检查,可以看到Memcheck提供了全面的内存错误检查,报告的错误也比较准确,为Linux下程序调试提供了一种有效的手段。我们说Valgrind是一款“重量级”工具,不仅仅指它功能比较强,事实上它消耗内存和CPU也是“重量级”的,需要配置较高的机器才能跑的比较顺畅。对于服务器程序,挂上Valgrind后会吃大量内存,会严重拖慢程序的运行速度,可能会导致服务器业务没法正常进行,这时可能就要换AddressSanitizer等其他工具进行监测了。关于AddressSanitizer相关内容,可以参见我之前的文章:
为什么选择C/C++内存检测工具AddressSanitizer?如何使用AddressSanitizer?https://blog.csdn.net/chenlycly/article/details/132863447