Valgrind
概述
Valgrind 是一个用于构建动态分析工具的工具框架。它附带了一组工具,每个工具都执行某种调试、分析或类似的任务,帮助改进程序。Valgrind 的架构是模块化的,因此可以轻松地创建新的工具,而不会干扰现有的结构。许多有用的工具作为标准提供:
- Memcheck 是一个内存错误检测器。它帮助你使你的程序,尤其是用C和C++编写的程序更正确。
- Cachegrind 是一个缓存和分支预测分析器。它可以帮助你使你的程序运行得更快。
- Callgrind 是一个调用图生成缓存分析器。它与 Cachegrind 有一些重叠,但也收集了一些 Cachegrind 没有的信息。
- Helgrind 是一种线程错误检测器。它可以帮助你使多线程程序更加正确。
- DRD 也是一个线程错误检测器。它类似于 Helgrind,但使用不同的分析技术,因此可能会发现不同的问题。
- Massif 是一个堆分析器。它有助于减少程序使用的内存。
- DHAT 是另一种堆分析器。它有助于您了解块生命周期、块利用率和布局效率低下的问题。
- BBV 是一种实验性的 SimPoint 基本块矢量生成器。它对从事计算机体系结构研究和开发的人们是有用的。
实践
toolchain:gcc-9.1.0-2019.11-x86_64_arm-linux-gnueabihf
下 code
git clone https://sourceware.org/git/valgrind.git
编译
./autogen.sh
- 修改 configure,将 armv7 改成 arm 如下:
./configure --host=arm-linux-gnueabihf --prefix=$PWD/output make -j5 && make install
最后生成布局为:
运行
环境准备
- 板子挂载共享目录
ifconfig eth0 up
ifconfig eth0 xxx.xxx.xxx.xxx
mount -t nfs xxx.xxx.xxx.xxx:/xxx/nfs /tmp -o nolock
- 将生成的 output/bin 和 output/lib 拷贝到共享目录的相应位置,板子上布局如下:
- Valgrind 需要链接 not stripped 过的 ld*.so/libc*.so/libdl*.so,这些可以从相应toolchain release package里面找,比如9.1.0是从 gcc-9.1.0-2019.11-x86_64_arm-linux-gnueabihfarm-linux-gnueabihflibclib 找如下几个:
拷贝到共享目录的相应位置,板子上布局如下:
- 可以通过设置 LD_LIBRARY_PATH 去指定 libc*.so/libdl*.so 的位置,但是对于动态装载库 ld*.so 则无效。动态装载库的位置既不是由系统配置指定,也不是由环境参数决定,而是由 ELF 可执行文件决定。在动态链接的 ELF 可执行文件中,有一个专门的 section 叫 .interp,该 section 可通过 readelf 查看:
arm-linux-gnueabihf-readelf -p .interp $your_prog
输出为:
那怎么指定动态装载库路径?这里介绍一种比较 geek 的方式,通过 patchelf 直接改写 ELF 可执行文件中的 .interp section。
- 下code
git clone https://github.com/NixOS/patchelf
- 编译
./bootstrap.sh
./configure --prefix=$PWD/output/
make -j5 && make install
生成布局为:
执行 patchelf 修改 ELF 可执行文件的装载库路径:
~/patchelf/output/bin/patchelf --set-interpreter /tmp/gcc_9.1.0_lib/ld-2.30.so test
查看 .interp section 确认:
可以看到已经修改成功。
- 设置一下 Valgrind lib path 和 c lib path:
export VALGRIND_LIB=/tmp/valgrind/lib/valgrind
export LD_LIBRARY_PATH=/tmp/gcc_9.1.0_lib:$LD_LIBRARY_PATH
Memcheck
Memcheck 是一个内存错误检测器。它可以检测C和C++程序中常见的下列问题:
- 访问不应该访问的内存,例如溢出堆外、溢出堆栈顶部以及在释放内存后访问内存。
- 使用未定义值,即未初始化的值,或从其他未定义值派生的值。
- 堆内存释放不正确,例如堆块的双重释放,或者 malloc/new/new[]与free/delete/delete[] 的使用不匹配。
- memcpy 和相关函数中的 src 和 dst 指针区域存在重叠。
- 向内存分配函数的 size 参数传递一个可疑值(可能是负值)。
- 内存泄漏。
使用形式为:
./valgrind --tool=memcheck --leak-check=full [memcheck options] your-program [program options]
Cachegrind
Cachegrind 模拟程序如何与计算机的缓存层次结构和(可选)分支预测器交互。它模拟具有独立的一级指令和数据缓存(I1和D1)的机器,后面接着是统一的二级缓存(L2)。这完全符合许多现代机器的配置。然而,一些现代机器有三到四级缓存。对于这些机器(在 Cachegrind 可以自动检测缓存配置的情况下),Cachegrind 模拟第一级和最后一级缓存。这种选择的原因是,最后一级缓存对运行时的影响最大,因为它屏蔽了对主内存的访问。此外,一级缓存通常具有低关联性,因此模拟它们可以检测到代码与该缓存交互不良的情况(例如,按行长为2的幂次遍历矩阵列)。
使用形式为:
./valgrind --tool=cachegrind [cachegrind options] your-program [program options]
Callgrind
Callgrind 是一个分析工具,它将程序运行中函数之间的调用历史记录记录为调用图。默认情况下,收集的数据由执行的指令数量、指令与源行的关系、函数之间的调用方/被调用方关系以及调用次数组成。或者,缓存模拟或分支预测(类似于 Cachegrind)可以生成有关应用程序运行时行为的进一步信息。
使用形式为:
./valgrind --tool=callgrind [callgrind options] your-program [program options]
会生成一个文件:callgrind.out.
该文件可以拿到服务器上用 callgrind_annotate(其位置就是在 valgrind/output/bin 下) 去解析:
./callgrind_annotate [options] callgrind.out.
Helgrind
Helgrind 是一个 Valgrind 工具,用于检测使用 POSIX pthreads 线程原语的 C、C++ 和 FORTRAN 程序中的同步错误。
POSIX pthreads 中的主要抽象是:一组共享公共地址空间的线程、线程创建、线程 join、线程退出、互斥(锁)、条件变量(线程间事件通知)、读写锁、自旋锁、信号量和屏障。
Helgrind 可以如下检测三类错误:
- POSIX pthreads API 的错误使用。
- 由锁顺序问题引起的潜在死锁。
- 存在数据竞争,即在没有足够的锁定或同步的情况下访问内存。
使用形式为:
./valgrind --tool=helgrind [helgrind options] your-program [program options]
DRD
DRD 是一种用于检测多线程 C 和 C++ 程序中错误的 Valgrind 工具。该工具适用于任何使用 POSIX pthreads 线程原语或使用基于 POSIX pthreads 线程原语构建的线程概念的程序。
使用形式为:
./valgrind --tool=drd [drd options] your-program [program options]
Massif
Massif 是一个堆分析器。它测量程序使用的堆内存量。这既包括有用的空间,也包括对齐目的分配的额外字节。它还可以测量程序堆栈的大小,尽管默认情况下它不会这样做。
堆分析可以帮助您减少程序使用的内存量。在具有虚拟内存的现代计算机上,这提供了以下好处:
- 它可以加快程序的速度——更小的程序可以更好地与机器的缓存交互,避免分页。
- 如果程序使用大量内存,这将减少耗尽机器交换空间的机会。
此外,有些空间泄漏是传统的泄漏检查程序(如 Memcheck)无法检测到的,这是因为内存从来没有真正丢失过,因为仍有指针指向它,但它没有使用。存在该种泄漏的程序可能会不必要地增加它们在一段时间内使用的内存量。Massif 可以帮助识别这些泄漏。重要的是,Massif 不仅告诉你程序使用了多少堆内存,还提供了非常详细的信息,指示程序的哪些部分负责分配堆内存。
使用形式为:
./valgrind --tool=massif [massif options] your-program [program options]
会生成一个文件:massif.out.
该文件可以拿到服务器上用 ms_print(其位置就是在 valgrind/output/bin 下) 去解析:
./ms_print [options] massif.out.
DHAT
DHAT 是一个检查程序如何使用堆分配的工具。它跟踪分配的块,并检查每个内存访问,以找到它要访问的块(如果有的话)。它以分配点为基础,显示关于这些块的信息,例如大小、生存期、读写次数以及读写模式。使用此信息可以识别具有以下特征的分配点:
- 潜在的进程生存期泄漏:当前分配的块只会累积,并且只在运行结束时释放。
- 过度瞬变:分配寿命极短的块。
- 无用的或未充分使用的分配:已分配但未完全填充的块,或已填充但未随后读取的块。
- 布局低效的区块——从未进入的区域,或整个区块中分散着热场。
使用形式为:
./valgrind --tool=dhat [dhat options] your-program [program options]
执行后会生成 dhat.out.
如果出现如下 error:
说明生成的 json 文件格式有问题,可以该文件内容粘贴到该 json错误检查网站 检查,检查到如下问题:
相应修改即可,之后重新 Load 即可看到对应内容:
参考
[1] git 网址
[2] 官网 quick start
[3] 官网手册
[4] 官网 FAQ
[5] 研究论文
[6] 程序员的自我修养