Claude Codeにコアダンプのgdbバックトレースを読ませる手順
C/C++のクラッシュ解析で、コアダンプ本体ではなくgdb -batchやlldb -bで書き出したbt fullとレジスタのテキストをClaude Codeに渡す手順と、秘密を伏せる方法をまとめます。
コアダンプではなく、バックトレースのテキストを渡す
C/C++のクラッシュ箇所をClaude Codeに絞らせるなら、コアダンプ本体は渡さず、gdbやlldbで書き出したテキストを渡します。使うのは bt full、全スレッドのバックトレース、レジスタ値の3つです。
理由は2つあります。1つ目は、コアダンプがバイナリで、そのままでは読めないことです。2つ目は、中身に秘密が含まれることがあるためです。コアファイルは、実行中プロセスのメモリイメージとレジスタ値などの状態を記録したものです。プロセスのメモリに載っていたAPIキーや環境変数は、そのままファイルに残ります。
テキスト化すれば、渡す前に目で確認して伏せられます。この記事の流れは次のとおりです。
- デバッグ情報つきでビルドし直す
- gdbまたはlldbをバッチ実行して、テキストを1ファイルに書き出す
- 秘密になる値を伏せる
claude -pかセッション内でテキストを渡し、仮説を出させる- 仮説をgdb側の追加コマンドで検証する
Claude Code自身がLinux ARM64でクラッシュする場合の切り分けは、Claude CodeのLinux ARM64クラッシュの記事が別に扱っています。ここで扱うのは、手元のC/C++プログラムが落ちたときの解析です。
デバッグ情報つきでビルドする
バックトレースの価値は、関数名と行番号が載っているかで決まります。デバッグ情報は、変数の型や、ソースの行番号と機械語アドレスの対応を記録したものです。コンパイラーに -g を渡すと生成されます。
GCCは -g と最適化の -O を併用できるため、最適化したままデバッグ情報を持てます。プリプロセッサーのマクロ展開まで調べたいときは、DWARF形式で -g3 を指定します。-g だけではマクロ情報が入らないコンパイラーが多いためです。
# 最適化はそのまま、デバッグ情報だけ足す例
g++ -O2 -g -o app main.cpp最適化を残すと、変数が最適化で消えて <optimized out> と表示されることがあります。原因の絞り込みに行き詰まったら、-O0 -g で再現するか試す手があります。
バイナリは、クラッシュしたときと同じビルドを渡します。コアファイルは一般にアドレス空間の一部しか持たず、残りは実行ファイルから補われます。ビルドし直した別のバイナリを渡すと、関数名や行番号がずれた出力になります。
gdbをバッチ実行してテキストに書き出す
gdbは -batch で、コマンドを流して終了します。バッチモードではページ送りが無効になり、端末の幅と高さが無制限になります。出力をそのままファイルへ流せるのが利点です。
gdb -batch -nx \
-ex 'bt full' \
-ex 'thread apply all bt' \
-ex 'info registers' \
./app core.1234 > crash/gdb.txt 2>&1各オプションの役割は次のとおりです。
| 指定 | 役割 |
|---|---|
-batch | 役割コマンド処理後に終了。エラーがあれば終了コードが非ゼロ |
-nx | 役割初期化ファイルを読まない。手元の設定差で出力が変わるのを防ぐ |
-ex 'bt full' | 役割クラッシュしたスレッドのスタックを、ローカル変数つきで出力 |
-ex 'thread apply all bt' | 役割全スレッドのバックトレース |
-ex 'info registers' | 役割浮動小数点とベクトルを除くレジスタ値 |
./app core.1234 | 役割実行ファイル、続いてコアファイル。2番目の引数が -c と同じ扱い |
bt full は、バックトレースにローカル変数の値を加える指定です。bt -full と同じ意味です。スタックが深いと出力が長くなるため、bt full 20 のようにフレーム数で絞る使い方もできます。
info registers は、選択中のスタックフレームのレジスタ値を出します。クラッシュ地点のレジスタが欲しいときは、frame コマンドで別のフレームに移る前の状態で実行します。
実行ファイルとコアを別々に指定したいときは、-se ./app -c core.1234 と書いても同じです。
lldbをバッチ実行してテキストに書き出す
lldbでは --batch(-b)が同じ役割を持ちます。-s、-S、-o、-O で渡したコマンドを実行して終了します。コアファイルは --core(-c)で指定します。
lldb -b -c core.1234 \
-o 'bt all' \
-o 'register read' \
./app > crash/lldb.txt 2>&1gdbとlldbのコマンドの対応は、lldbの公式マップにまとまっています。この記事で使うものを抜き出します。
| やりたいこと | gdb | lldb |
|---|---|---|
| 現在のスレッドのバックトレース | gdbbt | lldbbt または thread backtrace |
| 全スレッドのバックトレース | gdbthread apply all bt | lldbbt all |
| 先頭5フレームだけ | gdbbt 5 | lldbbt 5 |
| フレームを選ぶ | gdbframe 12 | lldbframe select 12 |
| 汎用レジスタを表示 | gdbinfo registers | lldbregister read |
| 実行中プロセスのコアを保存 | gdbgcore filename | lldbprocess save-core filename |
info registers に当たるlldbのコマンドは register read です。全レジスタセットを見たいときは register read --all を使います。
秘密を伏せてから渡す
テキストにしても、秘密は残ることがあります。bt full はローカル変数の値を出すため、認証トークンを持つ文字列や接続文字列がスタック上にあれば、そのまま書き出されます。ポインタが指す先の文字列がバックトレースに展開されることもあります。
そのため、渡す前に必ず目を通します。機械的に伏せる出発点として、次のような置換を挟む手があります。パターンは例であり、プロジェクトのキーの形式に合わせて調整してください。
perl -pe \
's/(Bearer )[A-Za-z0-9._-]+/$1<REDACTED>/g;
s/((api|secret|token|password)[A-Za-z_]*[=:"\s]+)[^"\s]+/$1<REDACTED>/gi;
s#(://[^:/@]+:)[^@]+@#$1<REDACTED>@#g' \
crash/gdb.txt > crash/gdb.redacted.txt正規表現は取りこぼします。置換後のファイルも、grep -n -i -E 'key|token|pass|secret' で見直してから渡すのが確実です。
Claude Code側でも、コアダンプ本体を読ませない設定を入れられます。権限設定の Read 拒否ルールは、ファイル読み取りを止めるパス指定です。
{
"permissions": {
"deny": ["Read(./crash/core.*)"]
}
}ドキュメントによると、Read ルールはGrepやGlobなど、ファイルを読む組み込みツールに対して「ベストエフォート」で適用されます。Bashで cat された場合の保証は、このルールには書かれていません。Bashコマンドの読み取りまで止めたいときは、サンドボックスの資格情報保護が使えます。sandbox.credentials の files に "mode": "deny" で書いたパスは、サンドボックス内のBashから読めなくなります。envVars に書いた変数は、サンドボックス内のコマンドの実行前に環境から外されます。
ただし、サンドボックスのドキュメントには、gdbやlldbのようなデバッガーをサンドボックス内で動かせるかどうかの記載がありません。実行は、Claude Codeを使っていない自分のターミナルで行う前提にしておくと、サンドボックスの挙動に左右されません。書き出したテキストだけを、Claude Codeに渡します。
テキストをClaude Codeに渡して仮説を出させる
渡し方は2通りあります。
非対話モードでは、標準入力にパイプで流せます。標準入力の上限は10MBで、超えると非ゼロの終了コードでエラー終了します。
cat crash/gdb.redacted.txt | claude -p \
'このgdb出力は ./app のクラッシュです。
1. クラッシュしたフレームを特定する
2. 該当ソースを読み、原因の仮説を可能性の高い順に3つまで挙げる
3. 各仮説を確かめるgdbコマンドを示す
ソースは変更しない'対話セッションでは、ファイルのパスを指定して読ませます。出力が10MBを超える場合は、パイプではなくファイルパスで渡します。
プロンプトの肝は、ソースを変更させず、仮説と確認コマンドの形で返させることです。バックトレースは「どこで落ちたか」を示すだけで、「なぜ壊れたか」は示しません。ヒープ破壊のように壊した箇所と落ちた箇所が離れるクラッシュでは、最上段のフレームを直しても再発します。次の3点を依頼に入れておくと、見当違いの修正を避けられます。
- クラッシュしたフレームが自分たちのコードか、ライブラリ内部かを区別させる
- 呼び出し元のうち、自分たちのコードの最も深いフレームのソースを読ませる
- ポインタや長さの引数が、呼び出し側でどう作られたかを追わせる
仮説をバッチ実行で検証する
Claude Codeが出した仮説は、gdbの追加コマンドで確かめます。1回目の出力に足りない情報があれば、コマンドを足してもう一度バッチ実行します。
gdb -batch -nx \
-ex 'frame 3' \
-ex 'info registers' \
./app core.1234 > crash/gdb-frame3.txt 2>&1出力を同じ手順で伏せ、同じセッションに渡して仮説を絞ります。この往復が1〜2回で収まらないときは、コアダンプよりも再現手順が足りていないことが多いです。再現できるなら、-g つきのビルドをデバッガー配下で走らせる方が早いことがあります。
実行中のプロセスの状態を残したいときは、gdbの gcore、lldbの process save-core でコアを生成できます。gcore はファイル名を省くと core.<プロセスID> で書き出します。gdbの gcore が使えるのは一部のOSに限られ、GNU/Linux、FreeBSD、Solaris、S390が挙がっています。クラッシュしたプロセスは、無効化されていなければコアファイルが自動で作られるので、gcore が要るのは落ちる前の状態を残したいときです。生成したコアも、本体は渡さず同じ手順でテキストにします。
つまずきやすい点
関数名が ?? になるときは、デバッグ情報がない、または渡した実行ファイルがクラッシュ時と違う、のどちらかです。同じビルドの、-g つきのバイナリを渡し直します。
<optimized out> が多いのは、最適化で変数が消えているためです。-O0 -g で再現できるか試します。
出力が長すぎるときは、bt full に件数を付け、thread apply all bt は必要なときだけ使います。ここで絞るほど、Claude Codeのコンテキストを無駄に使わずに済みます。
ログに秘密が混ざったときは、渡す前のファイルをシェルの履歴や共有ディレクトリに残さず、伏せた版だけを作業ディレクトリに置きます。環境変数の秘密を、Claude Codeが起動する子プロセスから外す設定はCLAUDE_CODE_SUBPROCESS_ENV_SCRUBの記事にあります。サンドボックスの全体像はサンドボックスの選び方で比較できます。
まとめ
コアダンプの解析をClaude Codeに任せるときの分かれ目は、何を渡すかです。バイナリのコアではなく、-g つきのバイナリから書き出した、伏せ済みのテキストを渡します。gdb -batch と lldb -b は、出力をファイルに流せるのでこの用途に合います。
Claude Codeに期待するのは、「落ちた場所」から「壊した場所」へさかのぼる仮説です。確かめるのはgdb側のコマンドで、結論の前に再現できるかを見ます。C++の検証コマンドを整える方法はC++のCMake presetsとctestに、デバッグ全般の渡し方はデバッグの精度を決める情報の質にあります。
参照した一次ソース
この記事の記述は、次の一次ソースと照合しています。更新時に参照先が変わった場合は一覧も更新します。
- sourceware.org/gdb/current/onlinedocs/gdb/Mode-Options.html
- sourceware.org/gdb/current/onlinedocs/gdb/File-Options.html
- sourceware.org/gdb/current/onlinedocs/gdb/Backtrace.html
- sourceware.org/gdb/current/onlinedocs/gdb/Registers.html
- sourceware.org/gdb/current/onlinedocs/gdb/Compilation.html
- sourceware.org/gdb/current/onlinedocs/gdb/Core-File-Generation.html
- lldb.llvm.org/man/lldb.html
- lldb.llvm.org/use/map.html
- code.claude.com/docs/en/sandboxing
- code.claude.com/docs/en/permissions
- code.claude.com/docs/en/headless