このページの目次
Valgrind と Sanitizers の実践
カバー範囲: Valgrind (memcheck/helgrind/cachegrind/massif) → ASAN/UBSAN/TSAN → 比較と選択 → 実際のデバッグ事例 対象: Linux ユーザー空間, C/C++
Valgrind: ダイナミックバイナリトランスレーション
Valgrind は本質的に JIT コンパイラです。プログラムの機械語を中間表現(VEX IR)にトランスレーションし、IR 上でインスツルメンテーションによる解析を行った後、再び機械語に戻して実行します。対象プログラムを再コンパイルする必要はありませんが、パフォーマンスのオーバーヘッドが大きくなります。
# メモリリークと境界外アクセスの検出:
# リークの発生元を追跡:
# 出力の解釈:
# ==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, ダブルフリー, 境界外アクセス, 未初期化値, メモリリーク
# helgrind: データ競合
# cachegrind: CPU キャッシュのシミュレーション
# massif: ヒープ解析
Valgrind の制限
オーバーヘッド:
memcheck: 約 20〜30 倍の低速化
helgrind: 約 10〜20 倍の低速化
cachegrind: 約 20〜50 倍の低速化
massif: 約 10 倍の低速化
サポートされていないもの:
- 静的リンクされたプログラム (ld.so と libc は置換可能である必要がある)
- 一部の珍しいシステムコール
- 一部の命令セット拡張 (AVX-512 は一部サポート)
Sanitizers: コンパイル時のインスツルメンテーション
Sanitizers はコンパイル時にチェックコードを挿入し、実行時にエラーを検出します。Valgrind よりも高速ですが、再コンパイルが必要です。
# ASAN: ヒープ境界外アクセス, UAF, スタックオーバーフロー, グローバル境界外アクセス
# → エラー発生時: 正確な位置 + 割り当て位置 + 解放位置を出力 (カラー表示!)
# UBSAN: 未定義動作
# 検出対象: 整数オーバーフロー, シフトの境界外, null 間参照, 型のアライメントエラー
# TSAN: データ競合 (64-bit のみ)
# → 報告: "data race on variable x between thread T1 and T2"
# LSAN: メモリリーク (ASAN に含まれている)
ASAN_OPTIONS=detect_leaks=1
# MSAN: 未初期化メモリへの読み取り (Clang のみ)
ASAN の原理
ASAN はシャドウメモリ (shadow memory) を使用します:
8 バイトのメモリごとに 1 バイトのシャドウメモリ
シャドウ値: 0=完全にアクセス可能, 1-7=部分的に有効, 負数=アクセス不可
メモリアクセスのたびに、コンパイラがシャドウチェックを挿入
→ シャドウ値がアクセス不可を示す場合 → ASAN がレポート
→ 割り当て領域の周囲に「レッドゾーン」(redzone) を配置 → 境界外アクセス検出
→ 解放後に「クォランティン」(quarantine) に配置 → UAF 検出
メモリオーバーヘッド: 約 2 倍 (シャドウメモリ + レッドゾーン + クォランティン)
Valgrind vs Sanitizers
| Valgrind | Sanitizers | |
|---|---|---|
| 再コンパイルが必要か | いいえ (任意のバイナリ) | はい (-fsanitize=) |
| 速度 | 20〜50 倍の低速化 | 1.5〜5 倍の低速化 |
| メモリオーバーヘッド | 追加メモリなし | 2 倍 (ASAN) |
| 検出精度 | 高い (バイナリトランスレーションにより全てを可視化) | 高い (コンパイル時のインスツルメンテーション) |
| スレッド検出 | helgrind | TSAN |
| 使用シーン | ソースコードなしのブラックボックス | ソースコードあり、CI に統合 |
実践事例
# 事例 1: ヒープ境界外アクセス → ASAN
# test.c:4: buf[100] = 0; // buf は char[100]
# ==ERROR: AddressSanitizer: heap-buffer-overflow
# WRITE of size 1 at 0x602000000064 thread T0
# #0 0x4007b3 in main test.c:4
# 事例 2: データ競合 → TSAN
# 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
# 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 (ヒーププロファイラ、massif の代替)
キーワード: Valgrind, memcheck, helgrind, massif, ASAN, UBSAN, TSAN, MSAN, shadow memory, data race