跳转至

valgrind

valgrind 一款内存检测工具,包括如下模块 1. Memcheck:是一个内存错误检测器。对未初始化内存的使用;读/写释放后的内存块;读/写超出 malloc 分配的内存块;读/写不适当的栈中内存块;内存泄漏,指向一块内存的指针永远丢失;不正确的 malloc/free 或 new/delete 匹配;memcpy() 相关函数中的 dst 和 src 指针重叠 2. Cachegrind: 是一个缓存和分支预测分析器。它可以帮助您使程序运行得更快。它模拟 CPU 中的一级缓存 I1,Dl 和二级缓存,能够精确地指出程序中 cache 的丢失和命中。如果需要,它还能够为我们提供 cache 丢失次数,内存引用次数,以及每行代码,每个函数,每个模块,整个程序产生的指令数。 3. Callgrind: 是一个调用图生成缓存分析器。它与 Cachegrind 有一些重叠,但也收集了一些 Cachegrind 没有的信息。 4. Helgrind:是一个线程错误检测器。它可以帮助您使您的多线程程序更正确。锁检测。Helgrind 寻找内存中被多个线程访问,而又没有一贯加锁的区域,这些区域往往是线程之间失去同步的地方,而且会导致难以发掘的错误 5. DRD:也是一个线程错误检测器。它与 Helgrind 类似,但使用不同的分析技术,因此可能会发现不同的问题。 6. Massif:是一个堆分析器。它能测量程序在堆栈中使用了多少内存,告诉我们堆块,堆管理块和栈的大小,它可以帮助您使程序使用更少的内存。 7. DHAT:是一种不同类型的堆分析器。它可以帮助您了解块生命周期、块利用率和布局效率低下的问题。 8. BBV:是一个实验性的 SimPoint 基本块向量生成器。它对从事计算机体系结构研究和开发的人很有用。

1 安装

  1. 下载源码包 (https://valgrind.org/downloads/current.html),解压缩 (tar –jxvf valgrind.tar.bz2)
  2. 根据源码目录下的 README 文件,源码安装,可能需要安装 automakeautoconf
    1. yum install -y autoconf automake
To install from the GIT repository:

  0. Clone the code from GIT:
     git clone git://sourceware.org/git/valgrind.git
     There are further instructions at
     http://www.valgrind.org/downloads/repository.html.

  1. cd into the source directory.

  2. Run ./autogen.sh to setup the environment (you need the standard
     autoconf tools to do so).

  3. Continue with the following instructions...

To install from a tar.bz2 distribution:

  4. Run ./configure, with some options if you wish.  The only interesting
     one is the usual --prefix=/where/you/want/it/installed.

  5. Run "make".

  6. Run "make install", possibly as root if the destination permissions
     require that.

  7. See if it works.  Try "valgrind ls -l".  Either this works, or it
     bombs out with some complaint.  In that case, please let us know
     (see http://valgrind.org/support/bug_reports.html).

2 valgrind 通用命令行选项

  • 语法:valgrind [valgrind-options] your-prog [your-prog-options] valgrind 输出的第一列是进程号,第 2 列是检测信息,如输出结果
==12345== some-message-from-Valgrind

2.1 --tool

  • 功能:指定使用的 valigrind 提供的哪一种工具,memcheck(默认),cachegrind, callgrind, helgrind, drd, massif, dhat, lackey
  • 格式:--tool=<toolname> [default: memcheck]

2.2 --trace-children

title: 其实是跟踪exec调用的程序,而不是子进程
Note that Valgrind does trace into the child of a `fork` (it would be difficult not to, since `fork` makes an identical copy of a process), so this option is arguably badly named. However, most children of `fork` calls immediately call `exec` anyway.
  • 功能:跟踪子进程(exec system call),在多进程中开启
  • 格式:--trace-children=<yes|no> [default: no]

2.3 --time-stamp

  • 功能:每条消息前面都会显示自启动以来经过的挂钟时间,每字段分别是 days, hours, minutes, seconds and milliseconds
  • 格式:--time-stamp=<yes|no> [default: no]
  • 示例:下面 257 是毫秒,前面的 00,分别是 days, hours, minutes, seconds
[root@centos7 Valgrind]# valgrind --time-stamp=yes ./a.out 
...
==00:00:00:00.000 1597== Command: ./a.out
==00:00:00:00.000 1597== 
==00:00:00:00.257 1597== Invalid write of size 4
==00:00:00:00.257 1597==    at 0x40054E: f() (test.cc:6)
==00:00:00:00.257 1597==    by 0x40055E: main (test.cc:11)
...

2.4 --track-fds

  • 功能:
  • 格式:

2.5 --log-fd

  • 功能:指定 valgrind 将所有消息发送到指定的文件描述符
  • 格式:--log-fd=<number> [default: 2, stderr]

2.6 --log-file

  • 功能:指定 valgrind 将所有消息写入到指定的文件中
  • 格式:--log-file=<filename>,其中支持以下几个格式
    • %p:表示进程 id,通常在多进程中很有用,多个进程的消息分别写入自己的文件中
    • %n:被替换为此进程唯一的文件序列号。这对于从同一文件名模板生成多个文件的进程很有用。
[root@centos7 Valgrind]# valgrind --log-file=file-%p ls
[root@centos7 Valgrind]# ll file*
-rw-r--r--. 1 root root 915 Aug  7 20:12 file-2291

2.7 --log-socket

  • 功能:将输出写入指定的 socket 上,如果不指定端口,默认是 1500。可以通过使用 valgrind-listener 来监听客户端 valgrind 发送过来的信息
  • 格式:--log-socket=<ip-address:port-number>

2.8 --vgdb

具体操作见(https://valgrind.org/docs/manual/manual-core-adv.html#manual-core-adv.gdbserver)
  • 功能:允许外部 GNU GDB 调试器在 Valgrind 上运行时控制和调试程序
  • 格式:--vgdb=<no|yes|full> [default: yes]

2.9 --main-stacksize

  • 功能:指定主线程堆栈的大小。默认使用当前 ulimit 设置的值
  • 格式:--main-stacksize=<number> [default: use current 'ulimit' value]

2.10 --max-threads

  • 功能:指定 valgrind 支持的最大线程数。默认是 500 个线程
  • 格式:--max-threads=<number> [default: 500]

3 memcheck 内存泄漏检测

  • 官方文档 通过指定 --tool=memcheck(默认)来启用 memcheck
  • 选项
  • --leak-check=<no|summary|yes|full> [default: summary]: 当设置成 summary 时,仅统计内存泄漏数量,yes|full 会显示详细信息
  • --show-leak-kinds=<set> [default: definite,possible]: 指定需要显示的内存泄漏类型
    • 可选类型有:definite indirect possible reachable none all;all 相当于 definite indirect possible reachable

4 Massif 堆分析器

  • 官方文档
  • 基本语法:valgrind --tool=massif prog 运行 massif 后,等待进程结束或者 Ctrl+c 结束后才会将所有数据输出,默认情况下,此文件名称为 massif.out.<pid> ,其中 <pid> 是进程 ID,可以使用选项 --massif-out-file 更改此文件名。 得到数据文件后,可以使用 ms_print 工具显示,如 ms_print massif.out.12345。 ms_print 输出结果主要分为 2 个部分,第一个部分显示的是内存消耗图表,第二部分显示的是内存快照细节。

4.1 内存消耗图表

19.63^                                               ###                      
     |                                               #                        
     |                                               #  ::                    
     |                                               #  : :::                 
     |                                      :::::::::#  : :  ::               
     |                                      :        #  : :  : ::             
     |                                      :        #  : :  : : :::          
     |                                      :        #  : :  : : :  ::        
     |                            :::::::::::        #  : :  : : :  : :::     
     |                            :         :        #  : :  : : :  : :  ::   
     |                        :::::         :        #  : :  : : :  : :  : :: 
     |                     @@@:   :         :        #  : :  : : :  : :  : : @
     |                   ::@  :   :         :        #  : :  : : :  : :  : : @
     |                :::: @  :   :         :        #  : :  : : :  : :  : : @
     |              :::  : @  :   :         :        #  : :  : : :  : :  : : @
     |            ::: :  : @  :   :         :        #  : :  : : :  : :  : : @
     |         :::: : :  : @  :   :         :        #  : :  : : :  : :  : : @
     |       :::  : : :  : @  :   :         :        #  : :  : : :  : :  : : @
     |    :::: :  : : :  : @  :   :         :        #  : :  : : :  : :  : : @
     |  :::  : :  : : :  : @  :   :         :        #  : :  : : :  : :  : : @
   0 +----------------------------------------------------------------------->KB     0                                                                   29.48

Number of snapshots: 25
 Detailed snapshots: [9, 14 (peak), 24]

上面图表符号有 3 中,: 表示普遍快照,在第 2 部分中只以一行内容简单的显示,@ 表示的是详细快照,在第 2 部分中会以调用链格式显示详细分配链,而 # 表示峰值快照,在第 2 部分中也会详细展示。(注意:峰值快照可能并不是很准确,因为内存分配与释放变化很快,很难完全捕获到峰值。)

底部的文本显示,该程序拍摄了 25 个快照,即每个堆分配/解除分配一个快照,外加一些额外内容。Massif 首先为每个堆分配/解除分配拍摄快照,但随着程序运行时间的延长,它拍摄快照的频率会降低。随着程序的进行,它还会丢弃较旧的快照; 当它达到最大快照数(默认为 100 个,但可通过 --max-snapshots 选项更改)时,将删除其中的一半。这意味着始终会维护合理数量的快照。

底部的文字显示,为该程序拍摄了 3 个详细快照(快照 9、14 和 24)。默认情况下,每 10 个快照都会详细显示一次,但可以通过该 --detailed-freq 选项进行更改。

图形的大小可以通过 ms_print --x 和 --y 选项进行更改。每个竖条代表一个快照,即在某个时间点对内存使用情况的测量。如果下一个快照相距不止一列,则会从快照顶部到下一个快照列之前绘制一条水平字符线。

4.2 内存快照细节

4.2.1 普通快照

普通的快照显示的内容只有一行

--------------------------------------------------------------------------------
  n        time(B)         total(B)   useful-heap(B) extra-heap(B)    stacks(B)
--------------------------------------------------------------------------------
  0              0                0                0             0            0
  1          1,008            1,008            1,000             8            0
  2          2,016            2,016            2,000            16            0
  3          3,024            3,024            3,000            24            0
  4          4,032            4,032            4,000            32            0
  5          5,040            5,040            5,000            40            0
  6          6,048            6,048            6,000            48            0
  7          7,056            7,056            7,000            56            0
  8          8,064            8,064            8,000            64            0
  • n : 快照编号。
  • time:所花费的时间。在本例中,由于使用了 --time-unit=B ,所以时间单位是字节,默认单位 i,即指令执行数。
  • total:此时的总内存消耗量。
  • useful-heap:此时分配的有用堆字节数。这反映了程序请求的字节数。
  • extra-heap:此时分配的额外堆字节数。这反映了分配的字节数超出了程序请求的字节数。额外堆字节有两个来源。
  • 首先,每个堆块都有与之关联的管理字节。管理字节的确切数目取决于分配器的详细信息。默认情况下,Massif 假设每个块有 8 个字节,从示例中可以看出,但这个数字可以通过选项 --heap-admin 进行更改。
  • 其次,分配器通常将请求的字节数四舍五入为更大的数字,通常为 8 或 16。这是确保块中的元素适当对齐所必需的。如果要求 N 个字节,Massif 会将 N 四舍五入到选项 --alignment 指定值的最接近的倍数。
  • stacks:堆栈的大小。默认情况下,堆栈分析处于关闭状态,因为它会大大减慢 Massif 的速度。因此,堆栈列在示例中为零。可以使用该 --stacks=yes 选项打开堆栈分析。

4.2.2 详细快照

--------------------------------------------------------------------------------
  n        time(B)         total(B)   useful-heap(B) extra-heap(B)    stacks(B)
--------------------------------------------------------------------------------
 14         20,104           20,104           20,000           104            0
99.48% (20,000B) (heap allocation functions) malloc/new/new[], --alloc-fns, etc.
->49.74% (10,000B) 0x804841A: main (example.c:20)
| 
->39.79% (8,000B) 0x80483C2: g (example.c:5)
| ->19.90% (4,000B) 0x80483E2: f (example.c:11)
| | ->19.90% (4,000B) 0x8048431: main (example.c:23)
| |   
| ->19.90% (4,000B) 0x8048436: main (example.c:25)
|   
->09.95% (2,000B) 0x80483DA: f (example.c:10)
  ->09.95% (2,000B) 0x8048431: main (example.c:23)

上面第 5 行表示 99.48% 的内存通过 malloc/new/new[] 来分配的,我们主要关注的是第 2 层内容,这一层是直接调用 malloc/new/new[] 的地方。这 20000B 内存分配如下,第 6 行 10000B,第 8 行 8000B 的,第 14 行 2000B。 第 n+1 层加起来正常情况是等于第 n 层的。 第 3 层以上的层主要用来分析内存分配的上下文信息即有用的调用链信息。

4.3 测量所有内存

默认情况下 Massif 只测量堆内存,即被 malloccallocreallocmemalignnewnew[] 函数分配的内存。这意味着它不会直接测量使用较低级别的系统调用(如 mmap 、 mremap 和 brk )分配的内存。

4.3.1 测量栈内存

开启 --stacks=yes 选项可以测量栈内存

4.3.2 测量 mmap 、 mremap 和 brk

开启 --pages-as-heap=yes 将会测量所有内存,包括栈内存,因此同时开启 --stacks=yes。 开启后,ms_print 输出的每一个详细快照头部显示

(page allocation syscalls) mmap/mremap/brk, --alloc-fns, etc.

而不是

(heap allocation functions) malloc/new/new[], --alloc-fns, etc.

4.4 Massif 命令行选项

  • --heap=<yes|no> [default: yes]:指定是否应执行堆分析。
  • --heap-admin=<size> [default: 8]:如果启用了堆分析,则提供每个块要使用的管理字节数。这应该是平均值的估计值,因为它可能会有所不同。例如,glibc 在 Linux 上使用的分配器需要每个块 4 到 15 个字节,具体取决于各种因素。该分配器还需要释放块的管理空间,但 Massif 无法解释这一点。
  • --stacks=<yes|no> [default: no]:指定是否应执行栈分析。此选项会大大减慢 Massif 的速度,因此默认情况下处于关闭状态。请注意,Massif 假设主栈在启动时的大小为零。这不是真的,但以其他方式准确地做到这一点是很困难的。此外,从零开始可以更好地表示用户程序实际可以控制的主堆栈部分的大小。
  • --pages-as-heap=<yes|no> [default: no]:告诉 Massif 在页面级别而不是在 malloc'd 块级别分析内存。这意味着如 mmap 、 mremap 和 brk 分配的内存也会被记录。
  • --depth=<number> [default: 30]:为详细快照记录的分配树的最大深度。增加它将使 Massif 运行速度更慢,使用更多内存并生成更大的输出文件。
  • --alloc-fn=<name>:使用此选项指定的函数将被视为堆分配函数,例如 malloc 。这对于作为 或 包装 new 器的 malloc 函数很有用,这些函数可以用无趣的信息填充分配树。可以在命令行上多次指定此选项,以命名多个函数。
  • 请注意,仅当命名函数是堆栈跟踪中的顶部条目,或者正好位于以这种方式处理的另一个函数的下方时,才会以这种方式处理命名函数。例如,如果您有一个包装 malloc 的函数,并且 malloc2 该函数 malloc1 包装 malloc1 ,则仅指定 --alloc-fn=malloc2 将不起作用。您还需要指定 --alloc-fn=malloc1 。这有点不方便,但原因是检查分配函数的速度很慢,如果 Massif 可以在发现不匹配的堆栈跟踪条目后立即停止查看,而不必继续浏览所有条目,则可以节省大量时间。
  • 请注意,C++ 名称是分离的。另请注意,重载的 C++ 名称必须完整写入。可能需要单引号来防止外壳将它们分解。例如:--alloc-fn='operator new(unsigned, std::nothrow_t const&)'
  • 在 64 位平台和 unsigned 32 位平台上,需要将类型的 size_t 参数替换为 unsigned long 。
  • --alloc-fn 将与内联函数一起使用。内联函数名称不会被篡改,这意味着您只需要提供函数名称,而不需要提供参数列表。
  • --alloc-fn 不支持通配符。
  • --ignore-fn=<name>:此选项指定的函数中发生的任何直接堆分配(即对 malloc、 new 等的调用,或对 --alloc-fn  由选项命名的函数的调用)都将被忽略。这主要用于测试目的。可以在命令行上多次指定此选项,以命名多个函数。
  • 任何 realloc 被忽略的块也将被忽略,即使 realloc 调用未在被忽略的函数中发生。这样可以避免在使用中 realloc 缩小被忽略的块时堆大小为负的可能性。
  • 编写 C++ 函数名称的规则与 --alloc-fn 上述规则相同。
  • --threshold=<m.n> [default: 1.0]:堆分配的显著性阈值,以占总内存大小的百分比表示。将聚合占比小于此值的分配树条目。(即内存占比小于百分比的合并显示,不再细分。)请注意,这应该与同名的 ms_print 选项一起指定
  • --peak-inaccuracy=<m.n> [default: 1.0]:Massif 不一定记录实际的全局内存分配峰值; 默认情况下,仅当全局内存分配大小超过前一个峰值至少 1.0% 时,它才会记录峰值。这是因为在此过程中可能会有许多本地分配峰值,并且为每个峰值执行详细的快照将成本高昂且浪费,因为除了其中一个之外,其他所有分配峰值稍后都会被丢弃。这种不准确性可以通过此选项进行更改(甚至更改为 0.0%),但随着数字接近零,Massif 的运行速度会大大变慢。
  • --time-unit=<i|ms|B> [default: i]:用于分析的时间单位。有三种可能性:执行指令(i),这在大多数情况下是好的; 实际(Wallclock)时间(ms,即毫秒),有时很有用; 以及在堆和/或堆栈 (B) 上分配/释放的字节数,这对于非常短期的程序和测试目的很有用,因为它在不同机器上最容易重现。
  • --detailed-freq=<n> [default: 10]:详细快照的频率。每个 --detailed-freq=1 快照都很详细。
  • --max-snapshots=<n> [default: 100]:记录的最大快照数。如果设置为 N,则对于除运行时间非常短的程序之外的所有程序,最终快照数将介于 N/2 和 N 之间。
  • --massif-out-file=<file> [default: massif.out.%p]:将配置文件数据 file 写入默认输出文件。 massif.out.<pid> 和 %p 表示进程 ID , %q 表示环境变量的内容,这点参考 --log-file 。

4.5 ms_print 命令行选项

  • -h --help:显示帮助消息。
  • --version:显示版本号。
  • --threshold=<m.n> [default: 1.0]:与 Massif --threshold 的选项相同,但在分析之后而不是在分析期间应用。
  • --x=<4..1000> [default: 72]:图形的宽度,以列为单位。
  • --y=<4..1000> [default: 20]:图形的高度(以行为单位)。

4.6 使用 massif-visualizer

massif-visualizer  是 Massif 数据的图形查看器,通常比 ms_print 更易于使用。massif-visualizer 不在 Valgrind 中提供,但可在多个地方在线获得。

5 使用 Valgrind gdbserver 和 GDB 调试程序

官方文档

6 valgrind-listener

  • 功能:监听来自 valgrind 通过 socket 发送过来的信息
  • 语法:valgrind-listener [options]
    • -e --exit-at-zero:当连接的进程数回落到零时,退出。没有这个,它将永远运行,也就是说,直到你发送它 ctrl+c。
    • --max-connect=整数:默认情况下,监听器最多可以连接 50 个进程。有时,这个数字太小了。使用此选项可提供不同的限制。例如。 -- 最大连接=100。
    • portnumber:将其侦听的端口从默认值 (1500) 更改。指定的端口必须在 1024 到 65535 的范围内。同样的限制适用于由 --log-socket 指定到 Valgrind 本身的端口号。
  • 示例步骤
    1. 先启动一个终端,启动监听 valgrind-listener
    2. 在启动一个终端,启动 valgrind 检查,比如 valgrind --log-socket=192.168.211.7 ls
    3. 查看终端 1 是否接收到来自终端 2 发送的信息
### 终端2,启动valgrind检查ls
[root@centos7 Valgrind]# valgrind --log-socket=192.168.211.7 ls 
### 终端1,启动监听,当终端2执行命令后,终端1收到消息
[root@centos7 ~]# valgrind-listener 
valgrind-listener started at Sun Aug  7 22:40:55 2022

(1) -------------------- CONNECT --------------------
(1)
(1) ==2683== Memcheck, a memory error detector
(1) ==2683== Copyright (C) 2002-2022, and GNU GPL'd, by Julian Seward et al.
(1) ==2683== Using Valgrind-3.19.0 and LibVEX; rerun with -h for copyright info
(1) ==2683== Command: ls
(1) ==2683== 
(1) ==2683== 
(1) ==2683== HEAP SUMMARY:
(1) ==2683==     in use at exit: 19,916 bytes in 10 blocks
(1) ==2683==   total heap usage: 44 allocs, 34 frees, 56,463 bytes allocated
(1) ==2683== 
(1) ==2683== LEAK SUMMARY:
(1) ==2683==    definitely lost: 0 bytes in 0 blocks
(1) ==2683==    indirectly lost: 0 bytes in 0 blocks
(1) ==2683==      possibly lost: 0 bytes in 0 blocks
(1) ==2683==    still reachable: 19,916 bytes in 10 blocks
(1) ==2683==         suppressed: 0 bytes in 0 blocks
(1) ==2683== Rerun with --leak-check=full to see details of leaked memory
(1) ==2683== 
(1) ==2683== For lists of detected and suppressed errors, rerun with: -s
(1) ==2683== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
(1) 
(0) ------------------- DISCONNECT -------------------
(0)

7 valgrind 使用注意事项

  1. 您应该在此处运行真正的(机器代码)可执行文件。例如,如果您的应用程序是由 shell 或 Perl 脚本启动的,则需要对其进行修改以在真正的可执行文件上调用 Valgrind。直接在 Valgrind 下运行此类脚本将导致您收到与 /bin/sh、/usr/bin/perl 或您正在使用的任何解释器有关的错误报告。这可能不是您想要的并且可能会造成混淆。您可以通过提供选项 --trace-children=yes 来强制解决此问题,但仍有可能造成混淆