Ubuntu 22.04 Valgrind 安装与命令参数详解
Ubuntu 22.04 上从在线与离线两种方式安装 Valgrind 3.18.1,并详解 Memcheck、Cachegrind、Callgrind、Massif、Helgrind、DRD 各工具及泄漏、越界、悬垂指针等真实检测回显。
Ubuntu 22.04 Valgrind 安装与命令参数详解
目标:在 Ubuntu 22.04 x86_64 服务器上从零安装 Valgrind 3.18.1,并逐命令、逐参数给出真实输入与真实回显,覆盖内存检测、缓存剖析、堆剖析、线程检测等全部场景。本文全部命令与输出均已在 Ubuntu 22.04.5 LTS(x86_64)实测通过。
一、环境信息
| 项目 | 内容 |
|---|---|
| 操作系统 | Ubuntu 22.04.5 LTS(jammy) |
| 系统架构 | x86_64 |
| 内核版本 | 6.8.0-138-generic |
| 包管理器 | apt / dpkg(Debian 系) |
| Valgrind 版本 | valgrind-3.18.1 |
| 配套编译器 | gcc 11.4.0 |
💡 说明:Valgrind 是二进制分发软件,Ubuntu 官方软件源(jammy)内置
valgrind软件包,版本为1:3.18.1-1ubuntu2,直接通过 apt 安装即可,无需编译源码。
二、工具概述
Valgrind 是一个用于内存调试、内存泄漏检测以及性能剖析的程序开发框架。它通过在目标程序外增加一层「模拟执行」环境,对程序发出的每一条指令进行插桩分析,从而在不修改被测程序源码的前提下发现内存错误。
Valgrind 自带六套生产级工具:
| 工具 | 作用 |
|---|---|
| Memcheck | 内存错误检测器:检测非法读写、use-after-free、double free、内存泄漏、未初始化变量等 |
| Helgrind | 线程错误检测器:检测多线程程序中的数据竞争(data race)与锁误用 |
| DRD | 线程错误检测器:检测数据竞争,与 Helgrind 互补 |
| Cachegrind | 缓存与分支预测剖析器:统计指令、缓存命中/缺失 |
| Callgrind | 调用图生成剖析器:记录函数调用关系与指令开销,是 Cachegrind 的扩展 |
| Massif | 堆剖析器:统计程序运行过程中的堆内存使用峰值与时间线 |
💡 说明:Valgrind 默认工具是 Memcheck,即不带
--tool参数时直接运行内存检测;其他工具需用--tool=<名称>显式指定。
典型使用场景:
- 程序偶发段错误(Segmentation fault)却定位不到位置时,用 Memcheck 找出非法读写指令所在行。
- 服务长期运行内存持续上涨时,用 Memcheck 的
--leak-check=full找出泄漏点。 - 性能优化前用 Cachegrind/Callgrind 分析热点函数,用 Massif 分析堆内存峰值。
三、在线安装方案
3.1 更新软件源
首次安装前建议先刷新软件源索引,保证能拉到最新版本的软件包:
sudo apt update
实测回显(末尾片段):
Hit:4 http://security.ubuntu.com/ubuntu jammy-security InRelease
Reading package lists...
Building dependency tree...
Reading state information...
8 packages can be upgraded. Run 'apt list --upgradable' to see them.
💡 说明:
Hit表示软件源索引未发生变化(仍是当前最新);若显示Get则说明拉取了新的索引文件。最后一行提示有 8 个软件包可升级,与 Valgrind 安装无冲突。
3.2 安装 valgrind 软件包
sudo apt install -y valgrind
实测回显(本机已安装过,apt 会给出「已是最新版本」提示):
WARNING: apt does not have a stable CLI interface. Use with caution in scripts.
Reading package lists...
Building dependency tree...
Reading state information...
valgrind is already the newest version (1:3.18.1-1ubuntu2).
0 upgraded, 0 newly installed, 0 to remove and 8 not upgraded.
⚠️ 注意:
WARNING: apt does not have a stable CLI interface是 apt 对脚本化调用方式的通用提示,不影响安装结果,可忽略。首次安装(未装过 valgrind)时,该行会变为下载与解包进度信息,最终出现Setting up valgrind (1:3.18.1-1ubuntu2) ...即表示安装成功。
3.3 验证安装
valgrind --version
实测回显:
valgrind-3.18.1
which valgrind
实测回显:
/usr/bin/valgrind
💡 说明:
valgrind --version输出版本号即可确认安装成功,Valgrind 的版本号格式为valgrind-<主版本>.<次版本>.<修订号>。
四、离线安装方案
当目标服务器无法访问外网时,需要先在联网机器上下载 .deb 安装包,再拷贝到离线服务器用 dpkg -i 安装。
4.1 下载安装包(在联网机器上执行)
Ubuntu 的 apt 支持「只下载不安装」模式。使用 --reinstall --download-only 组合,即使软件已安装也会强制重新下载软件包:
sudo apt install --reinstall --download-only -y valgrind
实测回显:
WARNING: apt does not have a stable CLI interface. Use with caution in scripts.
Reading package lists...
Building dependency tree...
Reading state information...
0 upgraded, 0 newly installed, 1 reinstalled, 0 to remove and 8 not upgraded.
Need to get 14.1 MB of archives.
After this operation, 0 B of additional disk space will be used.
Get:1 http://archive.ubuntu.com/ubuntu jammy/main amd64 valgrind amd64 1:3.18.1-1ubuntu2 [14.1 MB]
Fetched 14.1 MB in 4s (3917 kB/s)
Download complete and in download only mode
💡 说明:
--download-only表示只把.deb下载到/var/cache/apt/archives/,不执行安装;--reinstall用于保证「已安装」状态下也能重新拉取软件包文件。回显中的Get:1 ... valgrind ... [14.1 MB]与Download complete and in download only mode即表示下载成功。
4.2 确认下载的 .deb 文件
ls -lh /var/cache/apt/archives/valgrind*.deb
实测回显:
-rw-r--r-- 1 root root 14M Feb 8 2022 /var/cache/apt/archives/valgrind_1%3a3.18.1-1ubuntu2_amd64.deb
⚠️ 注意:文件名中的
1%3a3.18.1-1ubuntu2是 Debian 版本号转义形式(%3a即冒号:),这是 apt 的文件命名规则,属正常现象。
4.3 拷贝到离线安装目录
创建离线软件包目录,并把 .deb 文件拷贝进去:
mkdir -p /opt/offline/valgrind
cp /var/cache/apt/archives/valgrind_1%3a3.18.1-1ubuntu2_amd64.deb /opt/offline/valgrind/
查看拷贝结果:
ls -lh /opt/offline/valgrind/
实测回显:
total 14M
-rw-r--r-- 1 root root 14M Feb 8 2022 valgrind_1%3a3.18.1-1ubuntu2_amd64.deb
💡 说明:本文示例使用
/opt/offline/valgrind/作为离线包存放目录,实际使用时可改成任意路径;随后将该目录整体拷贝到离线服务器即可。
4.4 查看软件包元信息(离线服务器上可选做)
dpkg-deb -I 可以不解包直接查看 .deb 包的控制信息(包名、版本、依赖、描述等),用于离线导入前的完整性确认:
dpkg-deb -I /opt/offline/valgrind/valgrind_1%3a3.18.1-1ubuntu2_amd64.deb
实测回显:
new Debian package, version 2.0.
size 14094056 bytes: control archive=9998 bytes.
1458 bytes, 31 lines control
27938 bytes, 362 lines md5sums
180 bytes, 5 lines * postinst #!/bin/sh
180 bytes, 5 lines * postrm #!/bin/sh
180 bytes, 5 lines * preinst #!/bin/sh
180 bytes, 5 lines * prerm #!/bin/sh
Package: valgrind
Version: 1:3.18.1-1ubuntu2
Architecture: amd64
Maintainer: Ubuntu Developers <ubuntu-devel-discuss@lists.ubuntu.com>
Installed-Size: 77484
Depends: libc6 (>= 2.34), libc6-i386 (>= 2.34), libc6-dbg
Recommends: gdb
Suggests: valgrind-dbg, valgrind-mpi, kcachegrind, alleyoop, valkyrie (>> 1.3.0)
Breaks: valgrind-dev
Replaces: valgrind-dev
Section: devel
Priority: optional
Homepage: http://www.valgrind.org/
Description: instrumentation framework for building dynamic analysis tools
Valgrind is a system for debugging and profiling Linux programs. With its tool
suite you can automatically detect many memory management and threading bugs,
avoiding hours of frustrating bug-hunting and making your programs more stable.
You can also perform detailed profiling to help speed up your programs and use
Valgrind to build new tools.
.
The Valgrind distribution currently includes six production-quality tools:
* a memory error detector (Memcheck)
* two thread error detectors (Helgrind and DRD)
* a cache and branch-prediction profiler (Cachegrind)
* a call-graph generating cache and branch-prediction profiler (Callgrind)
* a heap profiler (Massif)
It also includes three experimental tools:
* a stack/global array overrun detector (SGCheck)
* a second heap profiler that examines how heap blocks are used (DHAT)
* a SimPoint basic block vector generator (BBV)
Original-Maintainer: Alessandro Ghedini <ghedo@debian.org>
💡 说明:
Depends字段显示 valgrind 依赖libc6、libc6-i386、libc6-dbg等基础库,离线服务器若缺少这些依赖,需一并下载后按顺序安装。
4.5 离线安装(在离线服务器上执行)
将离线包目录拷贝到目标服务器后,用 dpkg -i 直接安装:
sudo dpkg -i /opt/offline/valgrind/valgrind_1%3a3.18.1-1ubuntu2_amd64.deb
实测回显(在已安装同版本的机器上重新导入时):
(Reading database ... 24685 files and directories currently installed.)
Preparing to unpack .../valgrind_1%3a3.18.1-1ubuntu2_amd64.deb ...
Unpacking valgrind (1:3.18.1-1ubuntu2) over (1:3.18.1-1ubuntu2) ...
Setting up valgrind (1:3.18.1-1ubuntu2) ...
安装后验证:
valgrind --version
实测回显:
valgrind-3.18.1
⚠️ 注意:若目标服务器从未安装过 valgrind,
dpkg -i遇到依赖缺失(libc6-dbg等)会报dependency problems,此时需先用sudo apt install -y ./xxx.deb(自动解析依赖)或sudo apt-get -f install补装依赖。
五、命令与参数详解
5.1 准备测试程序
本节所有命令均基于以下测试程序。在服务器上创建测试目录:
mkdir -p /opt/devchain-demo/valgrind-ubuntu-test
编写测试源码。第一个是内存泄漏程序 leak.c(malloc 后忘记 free):
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
int *p = (int *)malloc(10 * sizeof(int));
if (p == NULL) {
fprintf(stderr, "malloc failed\n");
return 1;
}
for (int i = 0; i < 10; i++) {
p[i] = i * i;
}
printf("sum = %d\n", p[9]);
/* 忘记 free(p),产生内存泄漏 */
return 0;
}
第二个是数组越界程序 oob.c(对堆数组越界读写):
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
int *arr = (int *)malloc(5 * sizeof(int));
int i;
if (arr == NULL) {
return 1;
}
for (i = 0; i < 5; i++) {
arr[i] = i + 1;
}
/* 越界写:arr[5] 超出堆块(5 * 4 = 20 字节) */
arr[5] = 100;
printf("arr[5] = %d\n", arr[5]);
/* 越界读:arr[6] 超出堆块 */
i = arr[6];
printf("arr[6] = %d\n", i);
free(arr);
return 0;
}
第三个是悬垂指针程序 usefree.c(释放后再访问,use-after-free):
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
int *p = (int *)malloc(sizeof(int));
if (p == NULL) {
return 1;
}
*p = 42;
printf("value = %d\n", *p);
free(p);
/* 释放之后再访问 p,产生 use-after-free */
printf("after free = %d\n", *p);
return 0;
}
第四个是二次释放程序 dblfree.c(同一块内存 free 两次):
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
int *p = (int *)malloc(sizeof(int));
if (p == NULL) {
return 1;
}
*p = 7;
printf("value = %d\n", *p);
free(p);
/* 同一块内存释放两次,产生 double free */
free(p);
return 0;
}
第五个是未初始化变量程序 uninit.c(未初始化的局部变量参与运算):
#include <stdio.h>
int main(void)
{
int a;
int b = 10;
/* a 未初始化就被用于算术运算 */
int c = a + b;
printf("c = %d\n", c);
return 0;
}
第六个是正确程序 good.c(申请与释放配对,无错误):
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
int *p = (int *)malloc(5 * sizeof(int));
if (p == NULL) {
return 1;
}
for (int i = 0; i < 5; i++) {
p[i] = i + 1;
}
printf("last = %d\n", p[4]);
free(p);
return 0;
}
第七个是求和程序 sum.c(Cachegrind/Callgrind 剖析用):
#include <stdio.h>
int add(int a, int b)
{
return a + b;
}
int sum(int n)
{
int total = 0;
for (int i = 1; i <= n; i++) {
total = add(total, i);
}
return total;
}
int main(void)
{
int total = sum(1000);
printf("sum(1000) = %d\n", total);
return 0;
}
⚠️ 注意:
good.c与sum.c是「正确程序」,用于对照 Memcheck 对干净程序的输出,以及供剖析类工具统计性能数据。
逐个编译(带调试信息 -g、关闭优化 -O0,便于 Valgrind 精确定位到源码行):
cd /opt/devchain-demo/valgrind-ubuntu-test
gcc -g -O0 -o leak leak.c
gcc -g -O0 -o oob oob.c
gcc -g -O0 -o usefree usefree.c
gcc -g -O0 -o dblfree dblfree.c
gcc -g -O0 -o uninit uninit.c
gcc -g -O0 -o good good.c
gcc -g -O0 -o sum sum.c
💡 说明:
-g生成调试符号,让 Valgrind 的错误报告能显示leak.c:6这样的源码位置;-O0关闭优化,避免编译器优化掉或重排可疑代码,保证检测结果稳定。
5.2 查看版本:--version
valgrind --version
实测回显:
valgrind-3.18.1
💡 说明:
--version输出 Valgrind 框架版本号;该版本与具体工具(Memcheck 等)共用同一个版本号。
5.3 查看帮助:--help
valgrind --help
实测回显(关键片段,上半部分为工具选择与基础选项):
usage: valgrind [options] prog-and-args
tool-selection option, with default in [ ]:
--tool=<name> use the Valgrind tool named <name> [memcheck]
basic user options for all Valgrind tools, with defaults in [ ]:
-h --help show this message
--help-debug show this message, plus debugging options
--help-dyn-options show the dynamically changeable options
--version show version
-q --quiet run silently; only print error msgs
-v --verbose be more verbose -- show misc extra info
--trace-children=no|yes Valgrind-ise child processes (follow execve)? [no]
--trace-children-skip=patt1,patt2,... specifies a list of executables
that --trace-children=yes should not trace into
--trace-children-skip-by-arg=patt1,patt2,... same as --trace-children-skip=
but check the argv[] entries for children, rather
than the exe name, to make a follow/no-follow decision
--child-silent-after-fork=no|yes omit child output between fork & exec? [no]
--vgdb=no|yes|full activate gdbserver? [yes]
full is slower but provides precise watchpoint/step
--vgdb-error=<number> invoke gdbserver after <number> errors [999999999]
to get started quickly, use --vgdb-error=0
and follow the on-screen directions
--vgdb-stop-at=event1,event2,... invoke gdbserver for given events [none]
where event is one of:
startup exit valgrindabexit all none
--track-fds=no|yes|all track open file descriptors? [no]
all includes reporting stdin, stdout and stderr
--time-stamp=no|yes add timestamps to log messages? [no]
--log-fd=<number> log messages to file descriptor [2=stderr]
--log-file=<file> log messages to <file>
--log-socket=ipaddr:port log messages to socket ipaddr:port
Memcheck 专属选项片段:
user options for Memcheck:
--leak-check=no|summary|full search for memory leaks at exit? [summary]
--leak-resolution=low|med|high differentiation of leak stack traces [high]
--show-leak-kinds=kind1,kind2,.. which leak kinds to show?
[definite,possible]
--errors-for-leak-kinds=kind1,kind2,.. which leak kinds are errors?
[definite,possible]
where kind is one of:
definite indirect possible reachable all none
--leak-check-heuristics=heur1,heur2,... which heuristics to use for
improving leak search false positive [all]
where heur is one of:
stdstring length64 newarray multipleinheritance all none
--show-reachable=yes same as --show-leak-kinds=all
--show-reachable=no --show-possibly-lost=yes
same as --show-leak-kinds=definite,possible
--show-reachable=no --show-possibly-lost=no
same as --show-leak-kinds=definite
--xtree-leak=no|yes output leak result in xtree format? [no]
--xtree-leak-file=<file> xtree leak report file [xtleak.kcg.%p]
--undef-value-errors=no|yes check for undefined value errors [yes]
--track-origins=no|yes show origins of undefined values? [no]
--partial-loads-ok=no|yes too hard to explain here; see manual [yes]
💡 说明:
[ ]内是该选项的默认值。例如--tool=<name> ... [memcheck]表示默认使用 Memcheck;--leak-check=no|summary|full ... [summary]表示默认只做泄漏「摘要」统计,需要详细信息时必须显式加--leak-check=full。
5.4 工具选择:--tool=<名称>
Valgrind 通过 --tool 指定分析工具。实测的六个工具逐一如下。
5.4.1 --tool=memcheck(内存错误检测,默认工具)
不写 --tool 与显式写 --tool=memcheck 等价。用越界程序验证:
valgrind --tool=memcheck ./oob
实测回显:
==8199== Memcheck, a memory error detector
==8199== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==8199== Using Valgrind-3.18.1 and LibVEX; rerun with -h for copyright info
==8199== Command: ./oob
==8199==
==8199== Invalid write of size 4
==8199== at 0x1091EB: main (oob.c:17)
==8199== Address 0x4a8b054 is 0 bytes after a block of size 20 alloc'd
==8199== at 0x4848899: malloc (in /usr/libexec/valgrind/vgpreload_memcheck-amd64-linux.so)
==8199== by 0x10919E: main (oob.c:6)
==8199==
==8199== Invalid read of size 4
==8199== at 0x1091F9: main (oob.c:18)
==8199== Address 0x4a8b054 is 0 bytes after a block of size 20 alloc'd
==8199== at 0x4848899: malloc (in /usr/libexec/valgrind/vgpreload_memcheck-amd64-linux.so)
==8199== by 0x10919E: main (oob.c:6)
==8199==
==8199== Invalid read of size 4
==8199== at 0x109215: main (oob.c:21)
==8199== Address 0x4a8b058 is 4 bytes after a block of size 20 alloc'd
==8199== at 0x4848899: malloc (in /usr/libexec/valgrind/vgpreload_memcheck-amd64-linux.so)
==8199== by 0x10919E: main (oob.c:6)
==8199==
arr[5] = 100
arr[6] = 0
==8199==
==8199== HEAP SUMMARY:
==8199== in use at exit: 0 bytes in 0 blocks
==8199== total heap usage: 2 allocs, 2 frees, 4,116 bytes allocated
==8199==
==8199== All heap blocks were freed -- no leaks are possible
==8199==
==8199== For lists of detected and suppressed errors, rerun with: -s
==8199== ERROR SUMMARY: 3 errors from 3 contexts (suppressed: 0 from 0)
解读:
Invalid write of size 4:对 4 字节(一个 int)的非法写操作,发生在oob.c:17的arr[5] = 100;,地址0x4a8b054位于大小为 20 字节堆块(5 * sizeof(int))的紧邻边界之后(0 bytes after a block of size 20),即堆越界写。Invalid read of size 4:随后对arr[6]的两处读取同样是越界读,其中第二处为4 bytes after a block,越界更远。ERROR SUMMARY: 3 errors from 3 contexts:共发现 3 个独立错误。- 程序自身输出
arr[5] = 100、arr[6] = 0是未定义行为的实际结果,值不可依赖。
💡 说明:
==8199==前缀中的数字是 Valgrind 为被测进程分配的 PID,每次运行会变化,属正常现象。
5.4.2 --tool=cachegrind(缓存与分支预测剖析)
valgrind --tool=cachegrind ./sum
实测回显:
==8381== Cachegrind, a cache and branch-prediction profiler
==8381== Copyright (C) 2002-2017, and GNU GPL'd, by Nicholas Nethercote et al.
==8381== Using Valgrind-3.18.1 and LibVEX; rerun with -h for copyright info
==8381== Command: ./sum
==8381==
--8381-- warning: L3 cache found, using its data for the LL simulation.
--8381-- warning: specified LL cache: line_size 64 assoc 11 total_size 34,603,008
--8381-- warning: simulated LL cache: line_size 64 assoc 17 total_size 35,651,584
sum(1000) = 500500
==8381==
==8381== I refs: 144,627
==8381== I1 misses: 1,338
==8381== LLi misses: 1,321
==8381== I1 miss rate: 0.93%
==8381== LLi miss rate: 0.91%
==8381==
==8381== D refs: 57,102 (39,093 rd + 18,009 wr)
==8381== D1 misses: 2,118 ( 1,491 rd + 627 wr)
==8381== LLd misses: 1,868 ( 1,277 rd + 591 wr)
==8381== D1 miss rate: 3.7% ( 3.8% + 3.5% )
==8381== LLd miss rate: 3.3% ( 3.3% + 3.3% )
==8381==
==8381== LL refs: 3,456 ( 2,829 rd + 627 wr)
==8381== LL misses: 3,189 ( 2,598 rd + 591 wr)
==8381== LL miss rate: 1.6% ( 1.4% + 3.3% )
Cachegrind 运行结束后会在当前目录生成剖析数据文件 cachegrind.out.<pid>:
ls -l cachegrind.out.*
实测回显:
-rw------- 1 root root 108333 Oct 6 06:36 cachegrind.out.8381
使用配套的 callgrind_annotate 命令把剖析数据转成可读的函数级报告:
callgrind_annotate cachegrind.out.8381 | head -30
实测回显:
--------------------------------------------------------------------------------
Profile data file 'cachegrind.out.8381'
--------------------------------------------------------------------------------
I1 cache: 32768 B, 64 B, 8-way associative
D1 cache: 49152 B, 64 B, 12-way associative
LL cache: 35651584 B, 64 B, 17-way associative
Profiled target: ./sum
Events recorded: Ir I1mr ILmr Dr D1mr DLmr Dw D1mw DLmw
Events shown: Ir I1mr ILmr Dr D1mr DLmr Dw D1mw DLmw
Event sort order: Ir I1mr ILmr Dr D1mr DLmr Dw D1mw DLmw
Thresholds: 99 0 0 0 0 0 0 0 0
Include dirs:
User annotated:
Auto-annotation: on
--------------------------------------------------------------------------------
Ir I1mr ILmr Dr D1mr DLmr Dw D1mw DLmw
--------------------------------------------------------------------------------
144,627 (100.0%) 1,338 (100.0%) 1,321 (100.0%) 39,093 (100.0%) 1,491 (100.0%) 1,277 (100.0%) 18,009 (100.0%) 627 (100.0%) 591 (100.0%) PROGRAM TOTALS
--------------------------------------------------------------------------------
Ir I1mr ILmr Dr D1mr DLmr Dw D1mw DLmw file:function
--------------------------------------------------------------------------------
22,677 (15.68%) 18 ( 1.35%) 18 ( 1.36%) 7,619 (19.49%) 169 (11.33%) 152 (11.90%) 3,187 (17.70%) 8 ( 1.28%) 3 ( 0.51%) ./elf/./elf/dl-lookup.c:do_lookup_x
19,877 (13.74%) 11 ( 0.82%) 11 ( 0.83%) 4,183 (10.70%) 95 ( 6.37%) 88 ( 6.89%) 2,185 (12.13%) 7 ( 1.12%) 1 ( 0.17%) ./elf/./elf/dl-lookup.c:_dl_lookup_symbol_x
17,442 (12.06%) 21 ( 1.57%) 21 ( 1.59%) 4,377 (11.20%) 410 (27.50%) 390 (30.54%) 1,998 (11.09%) 243 (38.76%) 236 (39.93%) ./elf/../sysdeps/x86_64/dl-machine.h:_dl_relocate_object
10,014 ( 6.92%) 0 0 5,005 (12.80%) 0 0 2,004 (11.13%) 0 0 sum.c:sum
10,000 ( 6.91%) 0 0 4,000 (10.23%) 0 0 3,000 (16.66%) 0 0 sum.c:add
9,802 ( 6.78%) 17 ( 1.27%) 17 ( 1.29%) 2,326 ( 5.95%) 252 (16.90%) 240 (18.79%) 354 ( 1.97%) 1 ( 0.16%) 0 ./elf/./elf/do-rel.h:_dl_relocate_object
5,372 ( 3.71%) 5 ( 0.37%) 5 ( 0.38%) 990 ( 2.53%) 40 ( 2.68%) 40 ( 3.13%) 6 ( 0.03%) 1 ( 0.16%) 1 ( 0.17%) ./elf/./elf/dl-tunables.c:__GI___tunables_init
💡 说明:
Ir是指令引用数,I1mr/D1mr是一级缓存缺失数,DLmr/DLmw是最后一级缓存(LL)缺失数。报告中可见我们自己的sum.c:sum与sum.c:add函数,add被循环调用 1000 次、指令开销 10,000 条,正是热点所在;其余高开销项来自动态链接器ld-linux。
5.4.3 --tool=callgrind(调用图剖析)
valgrind --tool=callgrind ./sum
实测回显:
==8482== Callgrind, a call-graph generating cache profiler
==8482== Copyright (C) 2002-2017, and GNU GPL'd, by Josef Weidendorfer et al.
==8482== Using Valgrind-3.18.1 and LibVEX; rerun with -h for copyright info
==8482== Command: ./sum
==8482==
==8482== For interactive control, run 'callgrind_control -h'.
sum(1000) = 500500
==8482==
==8482== Events : Ir
==8482== Collected : 144090
==8482==
==8482== I refs: 144,090
生成的数据文件:
ls -l callgrind.out.*
实测回显:
-rw------- 1 root root 69077 Oct 6 06:36 callgrind.out.8482
用 callgrind_annotate 查看函数级调用开销(含函数所属二进制路径):
callgrind_annotate callgrind.out.8482 | head -30
实测回显:
--------------------------------------------------------------------------------
Profile data file 'callgrind.out.8482' (creator: callgrind-3.18.1)
--------------------------------------------------------------------------------
I1 cache:
D1 cache:
LL cache:
Timerange: Basic block 0 - 27622
Trigger: Program termination
Profiled target: ./sum (PID 8482, part 1)
Events recorded: Ir
Events shown: Ir
Event sort order: Ir
Thresholds: 99
Include dirs:
User annotated:
Auto-annotation: on
--------------------------------------------------------------------------------
Ir
--------------------------------------------------------------------------------
144,090 (100.0%) PROGRAM TOTALS
--------------------------------------------------------------------------------
Ir file:function
--------------------------------------------------------------------------------
22,677 (15.74%) ./elf/./elf/dl-lookup.c:do_lookup_x [/usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2]
19,877 (13.79%) ./elf/./elf/dl-lookup.c:_dl_lookup_symbol_x [/usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2]
17,436 (12.10%) ./elf/../sysdeps/x86_64/dl-machine.h:_dl_relocate_object
10,014 ( 6.95%) sum.c:sum [/opt/devchain-demo/valgrind-ubuntu-test/sum]
10,000 ( 6.94%) sum.c:add [/opt/devchain-demo/valgrind-ubuntu-test/sum]
💡 说明:Callgrind 与 Cachegrind 的区别在于它额外记录函数调用关系(call graph),支持
callgrind_control交互式控制与callgrind_annotate的--tree调用树输出,是分析「谁调用了谁、各自开销多少」的首选工具。
5.4.4 --tool=massif(堆剖析)
valgrind --tool=massif ./leak
实测回显:
==8515== Massif, a heap profiler
==8515== Copyright (C) 2003-2017, and GNU GPL'd, by Nicholas Nethercote
==8515== Using Valgrind-3.18.1 and LibVEX; rerun with -h for copyright info
==8515== Command: ./leak
==8515==
sum = 81
==8515==
生成的数据文件:
ls -l massif.out.*
实测回显:
-rw-r--r-- 1 root root 1280 Oct 6 06:36 massif.out.8515
用 ms_print 输出可视化报告(ASCII 图 + 快照表):
ms_print massif.out.8515 | head -40
实测回显:
--------------------------------------------------------------------------------
Command: ./leak
Massif arguments: (none)
ms_print arguments: massif.out.8515
--------------------------------------------------------------------------------
KB
4.062^ :#
| :#
| :#
| :#
| :#
| :#
| :#
| :#
| :#
| :#
| :#
| :#
| :#
| :#
| :#
| :#
| :#
| :#
| :#
0 +----------------------------------------------------------------------->ki
0 135.1
Number of snapshots: 5
Detailed snapshots: [3 (peak)]
--------------------------------------------------------------------------------
n time(i) total(B) useful-heap(B) extra-heap(B) stacks(B)
--------------------------------------------------------------------------------
0 0 0 0 0 0
1 134,390 56 40 16 0
2 134,808 4,160 4,136 24 0
💡 说明:Massif 追踪堆内存随时间的变化。
4.062 KB处的尖峰对应leak.c中 40 字节堆块的分配;useful-heap(B)是程序实际申请使用的字节数,extra-heap(B)是分配器对齐等额外开销。若程序申请了大块内存后长期占用,Massif 报告会直观显示峰值出现的位置。
5.4.5 --tool=helgrind(线程错误检测)
用正确程序验证(无数据竞争时输出干净):
valgrind --tool=helgrind ./good
实测回显:
==8535== Helgrind, a thread error detector
==8535== Copyright (C) 2007-2017, and GNU GPL'd, by OpenWorks LLP et al.
==8535== Using Valgrind-3.18.1 and LibVEX; rerun with -h for copyright info
==8535== Command: ./good
==8535==
last = 5
==8535==
==8535== Use --history-level=approx or =none to gain increased speed, at
==8535== the cost of reduced accuracy of conflicting-access information
==8535== For lists of detected and suppressed errors, rerun with: -s
==8535== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
💡 说明:Helgrind 面向 pthread 多线程程序。当多个线程无锁访问同一共享变量时,它输出
Possible data race during read/write of size 4之类报告;ERROR SUMMARY: 0 errors表示未发现数据竞争。--history-level提示可在多线程场景下加速分析(默认full最精确也最慢)。
5.4.6 --tool=drd(另一款线程错误检测)
valgrind --tool=drd ./good
实测回显:
==8927== drd, a thread error detector
==8927== Copyright (C) 2006-2020, and GNU GPL'd, by Bart Van Assche.
==8927== Using Valgrind-3.18.1 and LibVEX; rerun with -h for copyright info
==8927== Command: ./good
==8927==
last = 5
==8927==
==8927== For lists of detected and suppressed errors, rerun with: -s
==8927== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
💡 说明:DRD 与 Helgrind 同为数据竞争检测器。DRD 对「同一地址的并发读写」报告更克制、误报率相对较低;两者可互相印证,对复杂多线程程序建议两种都跑一遍。
5.5 完整泄漏检测:--leak-check=full
默认 --leak-check=summary 只输出泄漏汇总,--leak-check=full 会为每一处泄漏打印完整调用栈:
valgrind --leak-check=full ./leak
实测回显:
==7960== Memcheck, a memory error detector
==7960== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==7960== Using Valgrind-3.18.1 and LibVEX; rerun with -h for copyright info
==7960== Command: ./leak
==7960==
sum = 81
==7960==
==7960== HEAP SUMMARY:
==7960== in use at exit: 40 bytes in 1 blocks
==7960== total heap usage: 2 allocs, 1 frees, 4,136 bytes allocated
==7960==
==7960== 40 bytes in 1 blocks are definitely lost in loss record 1 of 1
==7960== at 0x4848899: malloc (in /usr/libexec/valgrind/vgpreload_memcheck-amd64-linux.so)
==7960== by 0x10919E: main (leak.c:6)
==7960==
==7960== LEAK SUMMARY:
==7960== definitely lost: 40 bytes in 1 blocks
==7960== indirectly lost: 0 bytes in 0 blocks
==7960== possibly lost: 0 bytes in 0 blocks
==7960== still reachable: 0 bytes in 0 blocks
==7960== suppressed: 0 bytes in 0 blocks
==7960==
==7960== For lists of detected and suppressed errors, rerun with: -s
==7960== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 0 from 0)
解读:
HEAP SUMMARY:进程退出时堆上仍有40 bytes in 1 blocks(即10 * sizeof(int)),2 allocs, 1 frees表明有一次分配未配对释放。40 bytes in 1 blocks are definitely lost:确定泄漏(definitely lost),并给出调用栈main (leak.c:6)定位到malloc那一行。LEAK SUMMARY中definitely lost: 40 bytes in 1 blocks是必须修复的泄漏;still reachable表示指针仍存活但未释放(常出现在全局缓存中)。
💡 说明:泄漏分四类——
definitely lost(确定丢失,指针已丢失)、indirectly lost(间接丢失,随确定丢失块一起丢失)、possibly lost(可能丢失,指针可能指向块内部)、still reachable(仍可达但未释放)。
5.6 展示全部泄漏类型:--show-leak-kinds=all
--show-leak-kinds=all 强制展示所有泄漏类型(默认只展示 definite,possible):
valgrind --leak-check=full --show-leak-kinds=all ./leak
实测回显:
==8029== Memcheck, a memory error detector
==8029== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==8029== Using Valgrind-3.18.1 and LibVEX; rerun with -h for copyright info
==8029== Command: ./leak
==8029==
sum = 81
==8029==
==8029== HEAP SUMMARY:
==8029== in use at exit: 40 bytes in 1 blocks
==8029== total heap usage: 2 allocs, 1 frees, 4,136 bytes allocated
==8029==
==8029== 40 bytes in 1 blocks are definitely lost in loss record 1 of 1
==8029== at 0x4848899: malloc (in /usr/libexec/valgrind/vgpreload_memcheck-amd64-linux.so)
==8029== by 0x10919E: main (leak.c:6)
==8029==
==8029== LEAK SUMMARY:
==8029== definitely lost: 40 bytes in 1 blocks
==8029== indirectly lost: 0 bytes in 0 blocks
==8029== possibly lost: 0 bytes in 0 blocks
==8029== still reachable: 0 bytes in 0 blocks
==8029== suppressed: 0 bytes in 0 blocks
==8029==
==8029== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 0 from 0)
💡 说明:
--show-leak-kinds=all相当于老版本参数--show-reachable=yes,它让LEAK SUMMARY中五类泄漏全部列出(本测试程序只有definitely lost一类,故其余均为 0)。对含全局缓存或单例的程序,用它可看到still reachable的明细。
5.7 未初始化变量溯源:--track-origins=yes
默认 Memcheck 能发现未初始化值的使用,但只报告「值未初始化」;加上 --track-origins=yes 后,报告末尾会多出 Uninitialised value was created by ... 溯源信息:
valgrind --track-origins=yes ./uninit
实测回显:
==8228== Memcheck, a memory error detector
==8228== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==8228== Using Valgrind-3.18.1 and LibVEX; rerun with -h for copyright info
==8228== Command: ./uninit
==8228==
==8228== Conditional jump or move depends on uninitialised value(s)
==8228== at 0x48D5A96: __vfprintf_internal (vfprintf-internal.c:1516)
==8228== by 0x48BF79E: printf (printf.c:33)
==8228== by 0x10917F: main (uninit.c:10)
==8228== Uninitialised value was created by a stack allocation
==8228== at 0x109149: main (uninit.c:4)
==8228==
==8228== Use of uninitialised value of size 8
==8228== at 0x48B92EB: _itoa_word (_itoa.c:177)
==8228== by 0x48D4A7D: __vfprintf_internal (vfprintf-internal.c:1516)
==8228== by 0x48BF79E: printf (printf.c:33)
==8228== by 0x10917F: main (uninit.c:10)
==8228== Uninitialised value was created by a stack allocation
==8228== at 0x109149: main (uninit.c:4)
==8228==
==8228== Conditional jump or move depends on uninitialised value(s)
==8228== at 0x48B92FC: _itoa_word (_itoa.c:177)
==8228== by 0x48D4A7D: __vfprintf_internal (vfprintf-internal.c:1516)
==8228== by 0x48BF79E: printf (printf.c:33)
==8228== by 0x10917F: main (uninit.c:10)
==8228== Uninitialised value was created by a stack allocation
==8228== at 0x109149: main (uninit.c:4)
==8228==
==8228== Conditional jump or move depends on uninitialised value(s)
==8228== at 0x48D5583: __vfprintf_internal (vfprintf-internal.c:1516)
==8228== by 0x48BF79E: printf (printf.c:33)
==8228== by 0x10917F: main (uninit.c:10)
==8228== Uninitialised value was created by a stack allocation
==8228== at 0x109149: main (uninit.c:4)
==8228==
==8228== Conditional jump or move depends on uninitialised value(s)
==8228== at 0x48D4BC5: __vfprintf_internal (vfprintf-internal.c:1516)
==8228== by 0x48BF79E: printf (printf.c:33)
==8228== by 0x10917F: main (uninit.c:10)
==8228== Uninitialised value was created by a stack allocation
==8228== at 0x109149: main (uninit.c:4)
==8228==
c = 10
==8228==
==8228== HEAP SUMMARY:
==8228== in use at exit: 0 bytes in 0 blocks
==8228== total heap usage: 1 allocs, 1 frees, 4,096 bytes allocated
==8228==
==8228== All heap blocks were freed -- no leaks are possible
==8228==
==8228== For lists of detected and suppressed errors, rerun with: -s
==8228== ERROR SUMMARY: 7 errors from 5 contexts (suppressed: 0 from 0)
解读:
Conditional jump or move depends on uninitialised value(s):分支判断依赖未初始化值,栈帧回溯到main (uninit.c:10)即printf("c = %d\n", c);这一行——因为c由未初始化的a参与运算得出。Use of uninitialised value of size 8:8 字节(long 宽度)未初始化值被用于数值转字符串(_itoa_word)。- 每条错误末尾的
Uninitialised value was created by a stack allocation at 0x109149: main (uninit.c:4)就是--track-origins=yes带来的溯源信息,精确定位到变量a的声明行。
⚠️ 注意:
--track-origins=yes会显著拖慢程序(约增加 2 倍以上运行时间),通常只在对未初始化错误做最终定位时开启;日常快速扫描可以不加。
5.8 有错即返错误码:--error-exitcode=<数字>
默认 Valgrind 检测到错误时进程退出码仍为 0,不利于脚本判断;--error-exitcode 让检测到错误时返回指定退出码:
valgrind --error-exitcode=99 ./oob
echo $?
实测回显(oob 有 3 个错误,退出码为 99):
99
valgrind --error-exitcode=99 ./good
echo $?
实测回显(good 无错误,退出码为 0):
0
💡 说明:此参数在 CI 流水线中极常用:
valgrind --error-exitcode=1 ./app && echo "PASS"即可让任何内存错误导致构建失败。注意--error-exitcode只对 Valgrind 报告的错误生效,程序自身return 0仍会返回 0。
5.9 静默模式:-q / --quiet
-q 只输出错误信息,屏蔽版权信息、HEAP SUMMARY 等常规输出,适合与 --error-exitcode 配合做批量检查:
valgrind -q --leak-check=full ./leak
实测回显:
sum = 81
==8290== 40 bytes in 1 blocks are definitely lost in loss record 1 of 1
==8290== at 0x4848899: malloc (in /usr/libexec/valgrind/vgpreload_memcheck-amd64-linux.so)
==8290== by 0x10919E: main (leak.c:6)
==8290==
💡 说明:对比 5.5 节的完整输出可见,
-q模式下程序自身的sum = 81仍正常输出,Valgrind 只保留错误与泄漏明细,去掉了大段横幅与汇总信息。
5.10 输出到日志文件:--log-file=<文件>
默认 Valgrind 报告写到 stderr;用 --log-file 重定向到文件,程序自身的 stdout 不受影响:
valgrind --log-file=/opt/devchain-demo/valgrind-ubuntu-test/vg.log ./oob
查看日志内容:
cat /opt/devchain-demo/valgrind-ubuntu-test/vg.log
实测回显(--log-file 下会额外多出 Parent PID: 行):
==8318== Memcheck, a memory error detector
==8318== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==8318== Using Valgrind-3.18.1 and LibVEX; rerun with -h for copyright info
==8318== Command: ./oob
==8318== Parent PID: 8306
==8318==
==8318== Invalid write of size 4
==8318== at 0x1091EB: main (oob.c:17)
==8318== Address 0x4a8b054 is 0 bytes after a block of size 20 alloc'd
==8318== at 0x4848899: malloc (in /usr/libexec/valgrind/vgpreload_memcheck-amd64-linux.so)
==8318== by 0x10919E: main (oob.c:6)
==8318==
==8318== Invalid read of size 4
==8318== at 0x1091F9: main (oob.c:18)
==8318== Address 0x4a8b054 is 0 bytes after a block of size 20 alloc'd
==8318== at 0x4848899: malloc (in /usr/libexec/valgrind/vgpreload_memcheck-amd64-linux.so)
==8318== by 0x10919E: main (oob.c:6)
==8318==
==8318== Invalid read of size 4
==8318== at 0x109215: main (oob.c:21)
==8318== Address 0x4a8b058 is 4 bytes after a block of size 20 alloc'd
==8318== at 0x4848899: malloc (in /usr/libexec/valgrind/vgpreload_memcheck-amd64-linux.so)
==8318== by 0x10919E: main (oob.c:6)
==8318==
==8318==
==8318== HEAP SUMMARY:
==8318== in use at exit: 0 bytes in 0 blocks
==8318== total heap usage: 2 allocs, 2 frees, 4,116 bytes allocated
==8318==
==8318== All heap blocks were freed -- no leaks are possible
==8318==
==8318== For lists of detected and suppressed errors, rerun with: -s
==8318== ERROR SUMMARY: 3 errors from 3 contexts (suppressed: 0 from 0)
💡 说明:
--log-file支持%p(PID)占位符,如--log-file=vg.%p.log,多进程场景下每个进程各写一份日志,互不覆盖。
5.11 生成抑制文件:--gen-suppressions=all
Valgrind 允许用「抑制文件」(suppression file)屏蔽已知的第三方库误报。--gen-suppressions=all 会在每条错误下方输出一段可直接复制使用的抑制描述:
valgrind --gen-suppressions=all --log-file=/opt/devchain-demo/valgrind-ubuntu-test/supp.log ./oob
查看生成内容:
cat /opt/devchain-demo/valgrind-ubuntu-test/supp.log
实测回显(每个错误后追加一段 { ... } 抑制描述):
==8347== Memcheck, a memory error detector
==8347== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==8347== Using Valgrind-3.18.1 and LibVEX; rerun with -h for copyright info
==8347== Command: ./oob
==8347== Parent PID: 8335
==8347==
==8347== Invalid write of size 4
==8347== at 0x1091EB: main (oob.c:17)
==8347== Address 0x4a8b054 is 0 bytes after a block of size 20 alloc'd
==8347== at 0x4848899: malloc (in /usr/libexec/valgrind/vgpreload_memcheck-amd64-linux.so)
==8347== by 0x10919E: main (oob.c:6)
==8347==
{
<insert_a_suppression_name_here>
Memcheck:Addr4
fun:main
}
==8347== Invalid read of size 4
==8347== at 0x1091F9: main (oob.c:18)
==8347== Address 0x4a8b054 is 0 bytes after a block of size 20 alloc'd
==8347== at 0x4848899: malloc (in /usr/libexec/valgrind/vgpreload_memcheck-amd64-linux.so)
==8347== by 0x10919E: main (oob.c:6)
==8347==
{
<insert_a_suppression_name_here>
Memcheck:Addr4
fun:main
}
==8347== Invalid read of size 4
==8347== at 0x109215: main (oob.c:21)
==8347== Address 0x4a8b058 is 4 bytes after a block of size 20 alloc'd
==8347== at 0x4848899: malloc (in /usr/libexec/valgrind/vgpreload_memcheck-amd64-linux.so)
==8347== by 0x10919E: main (oob.c:6)
==8347==
{
<insert_a_suppression_name_here>
Memcheck:Addr4
fun:main
}
==8347==
==8347== HEAP SUMMARY:
==8347== in use at exit: 0 bytes in 0 blocks
==8347== total heap usage: 2 allocs, 2 frees, 4,116 bytes allocated
==8347==
==8347== All heap blocks were freed -- no leaks are possible
==8347==
==8347== For lists of detected and suppressed errors, rerun with: -s
==8347== ERROR SUMMARY: 3 errors from 3 contexts (suppressed: 0 from 0)
💡 说明:将日志中每段
{ ... }收集到单独文件(如valgrind.supp),把第一行占位符<insert_a_suppression_name_here>改成有意义的名字后,用--suppressions=valgrind.supp即可在后续运行中自动屏蔽同类错误。Memcheck:Addr4表示该错误类别为「4 字节地址非法访问」。
5.12 Memcheck 五类经典错误完整演示
5.12.1 内存泄漏(malloc 不 free)——leak.c
完整回显见 5.5 节。核心结论:
==7960== 40 bytes in 1 blocks are definitely lost in loss record 1 of 1
==7960== at 0x4848899: malloc (in /usr/libexec/valgrind/vgpreload_memcheck-amd64-linux.so)
==7960== by 0x10919E: main (leak.c:6)
==7960== LEAK SUMMARY:
==7960== definitely lost: 40 bytes in 1 blocks
5.12.2 数组越界读写——oob.c
完整回显见 5.4.1 节。核心结论:
==8199== Invalid write of size 4
==8199== at 0x1091EB: main (oob.c:17)
==8199== Address 0x4a8b054 is 0 bytes after a block of size 20 alloc'd
==8199== Invalid read of size 4
==8199== at 0x1091F9: main (oob.c:18)
5.12.3 use-after-free(释放后使用)——usefree.c
valgrind ./usefree
实测回显:
==8121== Memcheck, a memory error detector
==8121== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==8121== Using Valgrind-3.18.1 and LibVEX; rerun with -h for copyright info
==8121== Command: ./usefree
==8121==
==8121== Invalid read of size 4
==8121== at 0x1091E7: main (usefree.c:15)
==8121== Address 0x4a8b040 is 0 bytes inside a block of size 4 free'd
==8121== at 0x484B27F: free (in /usr/libexec/valgrind/vgpreload_memcheck-amd64-linux.so)
==8121== by 0x1091E2: main (usefree.c:13)
==8121== Block was alloc'd at
==8121== at 0x4848899: malloc (in /usr/libexec/valgrind/vgpreload_memcheck-amd64-linux.so)
==8121== by 0x10919E: main (usefree.c:6)
==8121==
value = 42
after free = 42
==8121==
==8121== HEAP SUMMARY:
==8121== in use at exit: 0 bytes in 0 blocks
==8121== total heap usage: 2 allocs, 2 frees, 4,100 bytes allocated
==8121==
==8121== All heap blocks were freed -- no leaks are possible
==8121==
==8121== For lists of detected and suppressed errors, rerun with: -s
==8121== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 0 from 0)
解读:
Invalid read of size 4出现在usefree.c:15(printf("after free = %d\n", *p);)。- 关键标识
Address 0x4a8b040 is 0 bytes inside a block of size 4 free'd:地址位于一块已被free的 4 字节堆块内——即典型的悬垂指针。 - 报告同时给出「释放发生地」(
usefree.c:13的free(p))与「分配发生地」(usefree.c:6的malloc),一条错误即可还原对象完整生命周期。 - 程序仍打印出
after free = 42只是侥幸,堆内存内容尚未被覆盖,但这属于未定义行为。
⚠️ 注意:use-after-free 是安全漏洞高发区(攻击者可利用悬垂指针改写堆元数据)。修复方式是释放后置空指针,或改用 RAII/智能指针管理生命周期。
5.12.4 二次释放(double free)——dblfree.c
valgrind ./dblfree
实测回显:
==8216== Memcheck, a memory error detector
==8216== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==8216== Using Valgrind-3.18.1 and LibVEX; rerun with -h for copyright info
==8216== Command: ./dblfree
==8216==
==8216== Invalid free() / delete / delete[] / realloc()
==8216== at 0x484B27F: free (in /usr/libexec/valgrind/vgpreload_memcheck-amd64-linux.so)
==8216== by 0x1091EE: main (dblfree.c:15)
==8216== Address 0x4a8b040 is 0 bytes inside a block of size 4 free'd
==8216== at 0x484B27F: free (in /usr/libexec/valgrind/vgpreload_memcheck-amd64-linux.so)
==8216== by 0x1091E2: main (dblfree.c:13)
==8216== Block was alloc'd at
==8216== at 0x4848899: malloc (in /usr/libexec/valgrind/vgpreload_memcheck-amd64-linux.so)
==8216== by 0x10919E: main (dblfree.c:6)
==8216==
value = 7
==8216==
==8216== HEAP SUMMARY:
==8216== in use at exit: 0 bytes in 0 blocks
==8216== total heap usage: 2 allocs, 3 frees, 4,100 bytes allocated
==8216==
==8216== All heap blocks were freed -- no leaks are possible
==8216==
==8216== For lists of detected and suppressed errors, rerun with: -s
==8216== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 0 from 0)
解读:
Invalid free() / delete / delete[] / realloc():对非法指针调用free,定位到dblfree.c:15(第二次free(p))。- 报告指明该地址
0x4a8b040 is 0 bytes inside a block of size 4 free'd,即该块此前已被dblfree.c:13的第一次free释放过。 HEAP SUMMARY中2 allocs, 3 frees直观暴露「释放次数多于分配次数」这一不平衡。- 裸跑该程序通常直接
Segmentation fault或触发 glibcdouble free or corruption报错;Valgrind 能在崩溃前给出精确到行的诊断。
5.12.5 未初始化变量——uninit.c
完整回显见 5.7 节。核心结论:
==8228== Conditional jump or move depends on uninitialised value(s)
==8228== by 0x10917F: main (uninit.c:10)
==8228== Uninitialised value was created by a stack allocation
==8228== at 0x109149: main (uninit.c:4)
==8228== ERROR SUMMARY: 7 errors from 5 contexts
5.12.6 正确程序对照——good.c
valgrind ./good
实测回显:
==8240== Memcheck, a memory error detector
==8240== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==8240== Using Valgrind-3.18.1 and LibVEX; rerun with -h for copyright info
==8240== Command: ./good
==8240==
last = 5
==8240==
==8240== HEAP SUMMARY:
==8240== in use at exit: 0 bytes in 0 blocks
==8240== total heap usage: 2 allocs, 2 frees, 4,116 bytes allocated
==8240==
==8240== All heap blocks were freed -- no leaks are possible
==8240==
==8240== For lists of detected and suppressed errors, rerun with: -s
==8240== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
解读:
in use at exit: 0 bytes in 0 blocks与2 allocs, 2 frees:分配与释放完全配对。All heap blocks were freed -- no leaks are possible:无任何泄漏。ERROR SUMMARY: 0 errors from 0 contexts:零错误,是 Valgrind 检查通过的「绿色」标准输出形态。
六、配置说明
6.1 安装路径与主要命令
Valgrind 安装后各二进制文件位于 /usr/bin/,可用 dpkg -L 查看完整文件清单:
dpkg -L valgrind
实测回显(前 15 行):
/.
/usr
/usr/bin
/usr/bin/callgrind_annotate
/usr/bin/callgrind_control
/usr/bin/cg_annotate
/usr/bin/cg_diff
/usr/bin/cg_merge
/usr/bin/ms_print
/usr/bin/valgrind
/usr/bin/valgrind-di-server
/usr/bin/valgrind-listener
/usr/bin/valgrind.bin
/usr/bin/vgdb
/usr/include
主要命令对应关系:
| 命令 | 用途 |
|---|---|
valgrind | 主程序(前端) |
valgrind.bin | Valgrind 真正执行引擎(由前端调用,无需直接使用) |
vgdb | Valgrind 与 GDB 的调试桥接工具 |
callgrind_annotate | 解析 Callgrind/Cachegrind 数据文件生成报告 |
callgrind_control | 对正在运行的 Callgrind 进程做交互控制 |
cg_annotate | Cachegrind 数据文件解析(旧版) |
ms_print | 解析 Massif 数据文件生成堆剖析报告 |
6.2 默认抑制文件
Valgrind 自带默认抑制文件,位于 /usr/libexec/valgrind/default.supp(安装包提供),用于屏蔽 glibc、X11 等第三方库的已知无害误报。可通过 --default-suppressions=no 关闭,也可用 --suppressions=<文件> 追加自定义抑制。
6.3 与 GDB 的配合
vgdb 允许在 Valgrind 检测到错误时挂起程序并交给 GDB 交互调试:
valgrind --vgdb=yes --vgdb-error=0 ./oob
💡 说明:
--vgdb-error=0表示「一旦出现错误立即进入 vgdb 等待调试器」,在另一终端运行gdb ./oob后输入target remote | vgdb即可接管;本机若安装了 gdb(valgrind 的 Recommends 依赖)可直接体验。
七、常见问题(FAQ)
7.1 为什么 valgrind 运行程序比裸跑慢得多?
Valgrind 采用动态二进制插桩,每条指令都要经过模拟层,通常比原生执行慢 5~20 倍(Memcheck)或 20~100 倍(--track-origins=yes)。这是正常现象,性能剖析类工具(Cachegrind/Callgrind)的统计值反映的是「模拟执行」下的相对开销,用于横向比较热点即可,不代表真实机器时间。
7.2 为什么我的越界访问没被 Valgrind 报出来?
Memcheck 的越界检测依赖「红区」(redzone)机制:堆块与栈帧之间预留的标记区。若越界落在合法地址范围内(例如数组之间恰好相邻且未跨越红区),可能检测不到。栈上局部数组的越界,用 -g 编译且越界足够远时通常能报出 Invalid write;堆数组越界则稳定可报(本文 oob.c 即堆越界演示)。想覆盖更多场景,可用 AddressSanitizer(gcc -fsanitize=address)与 Valgrind 交叉验证。
7.3 still reachable 的泄漏需要修复吗?
still reachable 表示指针仍存活、只是退出时未释放,常见于进程级缓存或单例。多数情况下可接受,但长期运行的服务中它会累积成真实增长,建议在退出路径显式清理。definitely lost 与 indirectly lost 则必须修复。
7.4 多线程程序该用 Helgrind 还是 DRD?
两者都检测数据竞争。Helgrind 更全面但对锁协议(如自定义自旋锁)可能误报;DRD 报告更克制。建议先 Helgrind 后 DRD 交叉验证,怀疑误报时用 --history-level=none 提速复测。
7.5 --gen-suppressions=all 生成的抑制文件为什么没生效?
三个常见原因:未把第一行 <insert_a_suppression_name_here> 改成合法名字;复制时丢掉了行首空格(抑制描述的缩进有语法意义);调用时未加 --suppressions=抑制文件 参数。另外 --gen-suppressions=all 与 --log-file 配合时,抑制块在日志中输出,需手动整理成独立文件。
7.6 为什么 valgrind 提示 WARNING: apt does not have a stable CLI interface?
这是 apt 在非交互/脚本调用时的通用警告,与 Valgrind 无关,可忽略。
7.7 检测结果中 ==NNNN== 前面的数字是什么?
是 Valgrind 为被测进程分配的 PID(每次运行不同),用于多进程场景区分输出流。可通过 --log-file=log.%p 让每个进程写入独立日志。
八、卸载与清理
8.1 查看安装状态
dpkg -l valgrind
实测回显:
Desired=Unknown/Install/Remove/Purge/Hold
| Status=Not/Inst/Conf-files/Unpacked/halF-conf/Half-inst/trig-aWait/Trig-pend
|/ Err?=(none)/Reinst-required (Status,Err: uppercase=bad)
||/ Name Version Architecture Description
+++-==============-=================-============-=============================================================
ii valgrind 1:3.18.1-1ubuntu2 amd64 instrumentation framework for building dynamic analysis tools
💡 说明:状态列
ii表示「已安装(install)且已配置(config)」,iU表示解包未配置,rc表示已删除但残留配置文件,据此可判断包的健康状态。
8.2 卸载软件包
仅移除程序文件、保留配置文件:
sudo apt remove -y valgrind
连同配置文件一起彻底清除:
sudo apt purge -y valgrind
清除 apt 缓存中已下载的安装包(释放磁盘空间):
sudo apt clean
8.3 清理剖析数据文件
Cachegrind/Callgrind/Massif 运行后会在当前目录生成 cachegrind.out.*、callgrind.out.*、massif.out.* 数据文件,可按需删除:
rm -f /opt/devchain-demo/valgrind-ubuntu-test/cachegrind.out.*
rm -f /opt/devchain-demo/valgrind-ubuntu-test/callgrind.out.*
rm -f /opt/devchain-demo/valgrind-ubuntu-test/massif.out.*
九、参考链接
- Valgrind 官方文档:https://www.valgrind.org/docs/manual/manual.html
- Memcheck 用户手册:https://www.valgrind.org/docs/manual/mc-manual.html
- Cachegrind/Callgrind 手册:https://www.valgrind.org/docs/manual/cg-manual.html
- Massif 手册:https://www.valgrind.org/docs/manual/ms-manual.html
- Ubuntu 软件包查询(valgrind):https://packages.ubuntu.com/jammy/valgrind