一、valgrind概述
Valgrind是一款用于内存调试、内存泄漏检测以及性能分析、检测线程错误的软件开发工具。
Valgrind 是运行在Linux 上的多用途代码剖析和内存调试软件。主要包括Memcheck、Callgrind、Cachegrind 等工具,每个工具都能完成一项任务调试、检测或分析。可以检测内存泄露、线程违例和Cache 的使用等。Valgrind 基于仿真方式对程序进行调试,它先于应用程序获取实际处理器的控制权,并在实际处理器的基础上仿真一个虚拟处理器,并使应用程序运行于这个虚拟处理器之上,从而对应用程序的运行进行监视。应用程序并不知道该处理器是虚拟的还是实际的,已经编译成二进制代码的应用程序并不用重新进行编译,Valgrind 直接解释二进制代码使得应用程序基于它运行,从而能够检查内存操作时可能出现的错误。
说明:在Valgrind下运行的程序运行速度要慢得多,而且使用的内存较多。Memcheck工具下的程序是正常情况的两倍多。因此,建议在性能好的机器上使用Valgrind。同时,valgrind不是一个在线跟踪调试工具。
1.1 原理
Memcheck 能够检测出内存问题,关键在于其建立了两个全局表。Valid-Value 表
对于进程的整个地址空间中的每一个字节(byte),都有与之对应的 8 个 bits;对于CPU的每个寄存器,也有一个与之对应的bit向量。这些bits负责记录该字节或者寄存器值是否具有有效的、已初始化的值。
Valid-Address 表
对于进程整个地址空间中的每一个字节(byte),还有与之对应的1个bit,负责记录该地址是否能够被读写。
检测原理:
当要读写内存中某个字节时,首先检查这个字节对应的 A bit。如果该A bit显示该位置是无效位置,memcheck则报告读写错误。
内核(core)类似于一个虚拟的 CPU 环境,这样当内存中的某个字节被加载到真实的 CPU 中时,该字节对应的 V bit 也被加载到虚拟的 CPU 环境中。一旦寄存器中的值,被用来产生内存地址,或者该值能够影响程序输出,则 memcheck 会检查对应的V bits,如果该值尚未初始化,则会报告使用未初始化内存错误。
二、valgrind能做什么
memcheck :检查程序中的内存问题,如泄漏、越界、非法指针等。能够侦测到如下问题。 如使用未初始化的内存 、读/写已经被释放的内存 、读/写内存越界 、读/写不恰当的内存栈空间 、内存泄漏 、使用 malloc 和 free 不匹配 等。
callgrind:检测程序代码的运行时间和调用过程,以及分析程序性能。和gprof不同,它不需要在编译源代码时附加特殊选项,但加上调试选项是推荐的。Callgrind收集程序运行时的一些数据,建立函数调用关系图,还可以有选择地进行cache模拟。在运行结束时,它会把分析数据写入一个文件。callgrind_annotate可以把这个文件的内容转化成可读的形式。
cachegrind:cache分析器;分析CPU的cache命中率、丢失率,用于进行代码优化。它模拟CPU中的一级缓存和二级缓存,能够精确地指出程序中cache的丢失和命中。如果需要,它还能够为我们提供cache丢失次数、内存引用次数、以及每行代码、每个函数、每个模块、整个程序产生的指令数。这对优化程序有很大的帮助。
helgrind:用于检查多线程程序的竞态条件。Helgrind寻找内存中被多个线程访问,而又没有加锁的区域,这些区域往往是线程之间失去同步的地方,而且会导致难以发掘的错误。Helgrind实现了名为“Eraser”的竞争检测算法,并做了进一步改进,减少了报告错误的次数。不过,Helgrind仍然处于实验阶段。
DRD:另一个线程错误检测器。和helgrind类似但是使用了不同的技术,所以或许能找到helgrind找不到的问题。
massif:堆栈分析器,指示程序中使用了多少堆内存等信息。
DHAT:另一个堆分析器。它有助于理解块(block)的生命周期、块的使用和布局的低效等问题
三、valgrind版本下载和官网介绍
比如3.x.0版本下载:
http://www.valgrind.org/downloads/valgrind-3.x.0.tar.bz2z
四、使用说明和参数说明
使用命令:
valgrind --tool=memcheck --leak-check=yes ./test
valgrind ./test./valgrind --leak-check=full /usr/bin/test
参数说明:
以下是 Valgrind 常用的一些参数说明:
--tool=<toolname>
:指定要使用的 Valgrind 工具。常用的工具包括:
memcheck
:用于检测内存错误和泄漏。cachegrind
:用于分析缓存使用情况。callgrind
:用于分析函数调用关系。helgrind
:用于检测多线程程序中的竞争条件。massif
:用于分析程序的堆内存使用情况。
--leak-check=<option>
:指定内存泄漏检测的级别。常用选项包括:
no
:不进行内存泄漏检测。summary
:在程序结束时输出内存泄漏的摘要信息。full
:在程序结束时输出详细的内存泄漏信息,包括泄漏的堆栈跟踪。
--show-leak-kinds=<kinds>
:指定要显示的内存泄漏类型。常用的类型包括:
definite
:显示确定的内存泄漏。indirect
:显示间接的内存泄漏。possible
:显示可能的内存泄漏。
--suppressions=<file>
:指定 suppressions 文件的路径,用于忽略特定的错误或警告。
--num-callers=<n>
:指定在错误或警告信息中显示的调用堆栈的层数。
--track-origins=<yes|no>
:指定是否跟踪未初始化变量的来源。默认情况下,Valgrind 不会跟踪未初始化变量的来源。
--vgdb=<yes|no>
:指定是否启用与 GDB 的集成调试功能
五、使用过程常见错误修复
错误1:执行./valgrind缺少配置或者相关库
shell# ./valgrind
valgrind: failed to start tool 'memcheck' for platform 'arm64-linux': No such file or directory
ERROR: ld.so: object '/home/vgpreload_core-arm64-linux.so' from LD_PRELOAD cannot be preloaded (cannot open shared object file): ignored.
解决:
1、配置环境变量:
export VALGRIND_LIB="/home/own_path/valgrind"
2、添加memcheck相关的信息,将如下相关的库和配置统一放到valgrind同级目录
位置:valgrind/build/memcheck/
memcheck-arm64-linux //此处可以将memcheck-arm64-linux改为memcheck
vgpreload_memcheck-arm64-linux.so
vgpreload_core-arm64-linux.so
default.supp
其他库文件参考:
vgpreload_core-arm64-linux.so
vgpreload_drd-arm64-linux.so
vgpreload_exp-dhat-arm64-linux.so
vgpreload_exp-sgcheck-arm64-linux.so
vgpreload_helgrind-arm64-linux.so
vgpreload_massif-arm64-linux.so
vgpreload_memcheck-arm64-linux.so
错误2:执行valgrind发现如下警告信息
shell# ./valgrind
valgrind: no program specified
valgrind: Use --help for more information.
解决:
需要对valgrind添加参数
shell# ./valgrind --leak-check=full /usr/bin/test
shell#
==18958== Memcheck, a memory error detector
==18958== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==18958== Using Valgrind-3.x.0 and LibVEX; rerun with -h for copyright info
==18958== Command: /usr/bin/test
==18958==
==18958== Use of uninitialised value of size 8
==18958== at 0x4014EEC: (in /lib64/ld-2.2.so)
==18958== by 0x4006AB3: (in /lib64/ld-2.2.so)
==18958== by 0x40070E3: (in /lib64/ld-2.2.so)
==18958== by 0x4003567: (in /lib64/ld-2.2.so)
==18958== by 0x4011D17: (in /lib64/ld-2.2.so)
==18958== by 0x40017EB: (in /lib64/ld-2.2.so)
==18958== by 0x4001B7B: (in /lib64/ld-2.2.so)
==18958== by 0x4001047: (in /lib64/ld-2.2.so)
==158== HEAP SUMMARY:
==158== in use at exit: 792 bytes in 6 blocks
==158== total heap usage: 11 allocs, 5 frees, 79,224 bytes allocated
==158==
==158== 304 bytes in 1 blocks are possibly lost in loss record 5 of 6
==158== at 0x4846220: calloc (vg_replace_malloc.c:752)
==158== by 0x400DBB3: (in /lib64/ld-2.2.so)
==158== by 0x400E3AF: _dl_allocate_tls (in /lib64/ld-2.2.so)
==158== by 0x486716B: pthread_create (in /lib64/libpthread-2.2.so)
==158== by 0x4BED593: (in /lib64/librt-2.2.so)
==158== by 0x486D5B3: (in /lib64/libpthread-2.2.so)
==158== by 0x4BEC7B7: timer_create (in /lib64/librt-2.2.so)
==158== by 0x4096F3: (in /usr/bin/test)
==158== by 0x409817: (in /usr/bin/test)
==158== by 0x40DDBB: (in /usr/bin/test)
==158== by 0x43AE3B: (in /usr/bin/test)
==158== by 0x522AD1B: (below main) (in /lib64/libc-2.2.so)
==158==
==158== 304 bytes in 1 blocks are possibly lost in loss record 6 of 6
==158== at 0x4846220: calloc (vg_replace_malloc.c:752)
==158== by 0x400DBB3: (in /lib64/ld-2.2.so)
==158== by 0x400E3AF: _dl_allocate_tls (in /lib64/ld-2.2.so)
==158== by 0x486716B: pthread_create (in /lib64/libpthread-2.2.so)
==158== by 0x46D1DB: (in /usr/bin/test)
==158== by 0x43B537: (in /usr/bin/test)
==158== by 0x522AD1B: (below main) (in /lib64/libc-2.2.so)
==158==
==158== LEAK SUMMARY:
==158== definitely lost: 0 bytes in 0 blocks
==158== indirectly lost: 0 bytes in 0 blocks //描述可能发生的内存泄漏
==158== possibly lost: 608 bytes in 2 blocks
==158== still reachable: 184 bytes in 4 blocks
==158== suppressed: 0 bytes in 0 blocks对leak summary说明:
definitely lost:指确定泄露的内存,应尽快修复。当程序结束时如果一块动态分配的内存没有被释放且通过程序内的指针变量均无法访问这块内存则会报这个错误。
indirectly lost:指间接泄露的内存,其总是与 definitely lost 一起出现,只要修复 definitely lost 即可恢复。当使用了含有指针成员的类或结构时可能会报这个错误
possibly lost:指可能泄露的内存,大多数情况下应视为与 definitely lost 一样需要尽快修复。当程序结束时如果一块动态分配的内存没有被释放且通过程序内的指针变量均无法访问这块内存的起始地址,但可以访问其中的某一部分数据,则会报这个错误。
still reachable:如果程序是正常结束的,那么它可能不会造成程序崩溃,但长时间运行有可能耗尽系统资源,因此笔者建议修复它。如果程序是崩溃(如访问非法的地址而崩溃)而非正常结束的,则应当暂时忽略它,先修复导致程序崩溃的错误,然后重新检测。
suppressed:已被解决。出现了内存泄露但系统自动处理了。可以无视这类错误。
六、valgrind报告生成说明
报告格式:
{问题描述}
at {地址、函数名、模块或代码行}
by {地址、函数名、代码行}
by ...{逐层依次显示调用堆栈,格式同上}
Address 0xXXXXXXXX {描述地址的相对关系}
七、Callgrind使用
1. 编译程序:在使用 Callgrind 之前,您需要编译您的程序,并确保在编译时启用调试符号(通常是通过添加 `-g` 标志来实现)。这将使 Valgrind 能够提供更详细的调试信息。
2. 运行 Callgrind:在终端中,使用以下命令运行您的程序:
valgrind --tool=callgrind 您的程序
这将启动 Callgrind 工具,并在程序运行结束后生成一个名为 `callgrind.out.xxxx` 的输出文件,其中 `xxxx` 是进程 ID。
3. 分析 Callgrind 输出:使用 `kcachegrind` 工具来可视化和分析 Callgrind 的输出。在终端中运行以下命令:
kcachegrind callgrind.out.xxxx
这将打开 kcachegrind 界面,并加载 Callgrind 的输出文件。在 kcachegrind 中,您可以查看函数调用关系图、函数执行次数、函数执行时间等信息,以帮助您分析程序的性能瓶颈和优化点。
需要准备条件:
callgrind-arm64-linux
callgrind_control
命令:
./valgrind --tool=callgrind /usr/bin/test
生成产物:
callgrind.out.21796
注意:出现./callgrind_control
sh: ./callgrind_control: /usr/bin/perl: bad interpreter: No such file or directory
需要安装perl,另外可以使用KCachegrind+Callgrind进行图形化界面分析函数耗时问题。