---
title: Valgrind 与 Sanitizers 实战
url: https://doc.liz6.com/systems-programming/08-performance-and-debugging/02-valgrind-and-sanitizers-in-practice
locale: zh
area: systems-programming
tags:
- systems-programming
- 性能与调试
date: 2026-06-30
modified: 2026-06-30
description: '覆盖: Valgrind (memcheck/helgrind/cachegrind/massif) → ASAN/UBSAN/TSAN → 对比与选择 → 实际调试案例 适用: Linux 用户态, C/C++'
---

# Valgrind 与 Sanitizers 实战

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

## Valgrind: 动态二进制翻译

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

```bash
# 内存泄漏 + 越界检测:
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 工具族

```bash
# 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 快很多，但需要重编译。

```bash
# 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

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

---

## 实战案例

```bash
# 案例 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*
