本页目录

Valgrind 与 Sanitizers 实战

覆盖: Valgrind (memcheck/helgrind/cachegrind/massif) → ASAN/UBSAN/TSAN → 对比与选择 → 实际调试案例 适用: Linux 用户态, C/C++

Valgrind: 动态二进制翻译

Valgrind 本质上是一个 JIT 编译器——它将程序的机器码翻译为中间表示 (VEX IR),在 IR 上插桩分析,再翻译回机器码执行。不需要重编译目标程序,但性能开销大。

# 内存泄漏 + 越界检测:
valgrind --leak-check=full ./prog arg1 arg2

# 追踪泄漏来源:
valgrind --leak-check=full --track-origins=yes ./prog

# 输出解读:
#   ==12345== Invalid read of size 4          ← 越界/use-after-free
#   ==12345==    at 0x4005A4: main (test.c:5)  ← 发生位置
#   ==12345==  Address 0x5204040 is 0 bytes after a block of size 40 alloc'd
#   ==12345==    at 0x4C2E80F: malloc (in /usr/lib/valgrind/vgpreload_memcheck.so)
#   ==12345==    by 0x40058F: main (test.c:3)  ← 分配位置

Valgrind 工具族

# memcheck (默认): 内存错误
#   检测: UAF, double free, 越界, 未初始化值, 内存泄漏

# helgrind: 数据竞争
valgrind --tool=helgrind ./prog

# cachegrind: CPU 缓存模拟
valgrind --tool=cachegrind ./prog
cg_annotate cachegrind.out.<pid>  # 源码级 miss 率

# massif: 堆分析
valgrind --tool=massif ./prog
ms_print massif.out.<pid>  # 时间线: 谁分配了多少内存

Valgrind 的限制

开销:
  memcheck: ~20-30x 慢
  helgrind: ~10-20x 慢
  cachegrind: ~20-50x 慢
  massif: ~10x 慢

不支持:
  - 静态链接的程序 (ld.so 和 libc 必须可替换)
  - 某些不常见的系统调用
  - 某些指令集扩展 (AVX-512 部分支持)

Sanitizers: 编译时插桩

Sanitizers 在编译时插入检查代码 → 运行时检测错误。比 Valgrind 快很多,但需要重编译。

# ASAN: 堆越界, UAF, 栈溢出, 全局越界
gcc -fsanitize=address -g -O1 -o prog prog.c
./prog
# → 触发错误时: 打印精确位置 + 分配位置 + 释放位置 (彩色!)

# UBSAN: 未定义行为
gcc -fsanitize=undefined -o prog prog.c
# 检测: 整数溢出, 移位越界, null 解引用, 类型对齐错误

# TSAN: 数据竞争 (仅 64-bit)
gcc -fsanitize=thread -g -O1 -o prog prog.c
# → 报告: "data race on variable x between thread T1 and T2"

# LSAN: 内存泄漏 (已包含在 ASAN 中)
ASAN_OPTIONS=detect_leaks=1 ./prog

# MSAN: 未初始化内存读 (Clang only)
clang -fsanitize=memory -g -o prog prog.c

ASAN 原理

ASAN 使用影子内存 (shadow memory):
  每 8 bytes 应用内存 → 1 byte 影子内存
  影子值: 0=完全可寻址, 1-7=部分有效, 负数=不可寻址

每次内存访问: 编译器插入影子检查
  → 如果影子值说不可寻址 → ASAN report
  → 分配周围放 "红区" (redzone) → 越界检测
  → 释放后放 "隔离区" (quarantine) → UAF 检测

内存开销: ~2x (影子内存 + 红区 + 隔离区)

Valgrind vs Sanitizers

ValgrindSanitizers
需要重编译否 (任意 binary)是 (-fsanitize=)
速度20-50x 慢1.5-5x 慢
内存开销无额外内存2x (ASAN)
检测精度高 (二进制翻译看所有)高 (编译时插桩)
多线程检测helgrindTSAN
使用场景不带源码的 black-box带源码, 集成到 CI

实战案例

# 案例 1: 堆越界 → ASAN
#   test.c:4: buf[100] = 0;  // buf is char[100]
gcc -fsanitize=address -g -o test test.c
./test
# ==ERROR: AddressSanitizer: heap-buffer-overflow
#     WRITE of size 1 at 0x602000000064 thread T0
#     #0 0x4007b3 in main test.c:4

# 案例 2: 数据竞争 → TSAN
gcc -fsanitize=thread -g -o test test.c
./test
# WARNING: ThreadSanitizer: data race
#   Write of size 4 at 0x7b0400000000 by thread T2:
#     #0 counter_thread test.c:8
#   Previous write of size 4 at 0x7b0400000000 by thread T1:
#     #0 counter_thread test.c:8

# 案例 3: 未初始化读 → Valgrind memcheck
valgrind ./prog
# Conditional jump or move depends on uninitialised value(s)

参考

  • 文档⁠: https://github.com/google/sanitizers, https://valgrind.org/docs/manual
  • LWN: "Address Sanitizer", "Thread Sanitizer"
  • 工具⁠: heaptrack (堆 profiler, alternative to massif)

关键词: Valgrind, memcheck, helgrind, massif, ASAN, UBSAN, TSAN, MSAN, shadow memory, data race