最終更新:2026-10-12
C++ メモリリーク調査実戦¶
最近、データベースカーネル(Greenplum/PostgreSQL 系)のメモリ問題を調査していたところ、非常に定番な WARNING の列を手にしました:problem in alloc set ExecutorState: bad single-chunk。このログの意味はこうです:メモリプール(alloc set)が検証時に、ある chunk のヘッダ情報が壊れているのを見つけた——十中八九、どこかのコードが範囲外書き込みをして、隣の chunk のメタデータを踏み潰したのです。
この種の問題の難しさは:**エラーを報告する場所が、バグのある場所ではない**こと。WARNING はメモリが解放され検証されるときに初めて出力され、範囲外書き込み自体は、ずっと前の任意のコード片で起きている可能性があります。以下に当時の完全な調査過程を貼ります。gdb で「事件現場」を取り戻す方法を一緒に見ていきましょう。
まず現場の情報に目を通します:
regression=# SELECT t1.a, t1.c, t2.b, t2.c FROM prt1_e t1, prt2_e t2 WHERE (t1.a + t1.b)/2 = (t2.b + t2.a)/2 AND t1.c = 0 ORDER BY t1.a, t2.b;
WARNING: problem in alloc set ExecutorState: bad single-chunk 0x7cfdd48 in block 0x7cfc580 (seg1 slice1 127.0.1.1:7003 pid=4295)
WARNING: problem in alloc set ExecutorState: found inconsistent memory block 0x7cfc580 (seg1 slice1 127.0.1.1:7003 pid=4295)
WARNING: problem in alloc set ExecutorState: bad single-chunk 0x7cfdd48 in block 0x7cfc580 (seg1 slice1 127.0.1.1:7003 pid=4295)
WARNING: problem in alloc set ExecutorState: found inconsistent memory block 0x7cfc580 (seg1 slice1 127.0.1.1:7003 pid=4295)
WARNING: problem in alloc set ExecutorState: bad single-chunk 0x87f85c8 in block 0x87f6e00 (seg0 slice1 127.0.1.1:7002 pid=4294)
WARNING: problem in alloc set ExecutorState: found inconsistent memory block 0x87f6e00 (seg0 slice1 127.0.1.1:7002 pid=4294)
WARNING: problem in alloc set ExecutorState: bad single-chunk 0x7d3c5c8 in block 0x7d3ae00 (seg2 slice1 127.0.1.1:7004 pid=4296)
WARNING: problem in alloc set ExecutorState: found inconsistent memory block 0x7d3ae00 (seg2 slice1 127.0.1.1:7004 pid=4296)
WARNING: problem in alloc set ExecutorState: bad single-chunk 0x87f85c8 in block 0x87f6e00 (seg0 slice1 127.0.1.1:7002 pid=4294)
WARNING: problem in alloc set ExecutorState: bad single-chunk 0x7d3c5c8 in block 0x7d3ae00 (seg2 slice1 127.0.1.1:7004 pid=4296)
WARNING: problem in alloc set ExecutorState: found inconsistent memory block 0x87f6e00 (seg0 slice1 127.0.1.1:7002 pid=4294)
WARNING: problem in alloc set ExecutorState: found inconsistent memory block 0x7d3ae00 (seg2 slice1 127.0.1.1:7004 pid=4296)
WARNING からはいくつかの重要な情報が取れます:
(1)壊れた chunk のアドレスは確定:0x7cfdd48(bad single-chunk がそれを指す);
(2)それが属する block は 0x7cfc580。同じ block に対する「found inconsistent memory block」は、block 全体のチェーン検証も壊れていることを示します;
(3)複数の segment(seg0/seg1/seg2)が同時に報告しているので、環境の揺らぎではなく、決定論的なロジックバグです。
確かなアドレスがあれば、方針は極めて直接的です:メモリアロケータ(aset.c の AllocSetAlloc)に**条件付きブレークポイント**を打ち、「割り当て時にこのアドレスを返す」その一回の呼び出しをインターセプトし、そのアドレスに**ハードウェア watchpoint** を設定します。書き込んだ者が犯人です。
gdb セッションは次のとおり:
デバッグ:
(gdb) b aset.c:1109 if chunk=0x7cfdd48
Breakpoint 1 at 0x105d420: file aset.c, line 1109.
(gdb) c
Continuing.
Thread 1 "postgres" hit Breakpoint 1, AllocSetAlloc (context=0x787ac50, size=4096) at aset.c:1109
1109 block->freeptr += (chunk_size + ALLOC_CHUNKHDRSZ);
(gdb) p chunk
$1 = (AllocChunk) 0x7cfdd48
(gdb) p chunk->size
$2 = 9187201950435737471
(gdb) p &chunk->size
$3 = (Size *) 0x7cfdd48
(gdb) watch (*((Size *) 0x7cfdd48))
Hardware watchpoint 2: (*((Size *) 0x7cfdd48))
(gdb) i b
Num Type Disp Enb Address What
1 breakpoint keep y 0x000000000105d420 in AllocSetAlloc at aset.c:1109
stop only if chunk=0x7cfdd48
breakpoint already hit 1 time
2 hw watchpoint keep y (*((Size *) 0x7cfdd48))
(gdb) d 1
(gdb) c
振り返り¶
このケースの流れは、どんな C/C++ プロジェクトのメモリ破壊調査にもそのまま転用できます:
(1)まずエラーログから**正確なアドレス**を抽出する。これが唯一の信頼できるアンカーです;
(2)条件付きブレークポイント b aset.c:1109 if chunk=0x7cfdd48 で割り当ての瞬間を押さえ、このアドレスが誰に渡ったかを確認する;
(3)watch (*(Size*)0x7cfdd48) のハードウェア watchpoint で犯人が現れるのを待つ——ハードウェアブレークポイントはメモリを書き換えず、プログラムの動作に影響しないため、メモリ破壊系の問題に最も信頼できる;
(4)実行して watchpoint がヒットした時のコールスタックこそが、範囲外書き込みの完全な現場です。
注目すべきは、最初のステップで p chunk->size が 9187201950435737471(0x7F7F7F7F7F7F7F7F)を出したこと——0x7F の山で、書き潰されたメモリの典型的な特徴です。メモリを踏んだコードがフラグや埋め草の値を書き込んでいることを示しており、この値をコード全体で検索すると、そのまま元凶にたどり着けることも多いのです。
最後に問題を一つ:範囲外書き込みが fork した子プロセスで起きていて、あなたは親プロセスをデバッグしている場合、上の流れはどう調整すべきでしょうか? コメント欄での議論を歓迎します。
関連トピック¶
- Bazel で C++ プロジェクトをビルド | C++那些事
- Bazel と LLDB の連携デバッグ | C++那些事
- CMake 入門と実践 | C++那些事
- Conan C++ パッケージ管理入門 | C++那些事
- C++ Insights によるコンパイル期観察 | C++那些事
- C++ Jupyter Notebook インタラクティブ | C++那些事
- C++ string から int への変換 | C++那些事
- C++ dbg-macro デバッグマクロ | C++那些事
- GCC コンパイルオプション詳解 | C++那些事
- GDB で C++ プログラムをデバッグ | C++那些事