Linuxのセグフォ・異常終了を瞬時に特定!addr2lineコマンドでクラッシュ行を割り出す実践デバッグ術

概要

プログラムが突然セグメンテーション違反(Segmentation Fault)で落ちたりコアダンプを吐き出したりした際、ログに残された生メモリ番地だけを眺めていても原因究明には膨大な時間がかかります。本ガイドでは、バイナリ上のメモリ番地からソースコードの該当ファイル名・行番号をダイレクトに逆引きできる強力なCLIユーティリティ「addr2line」の実践的な活用手順を解説します。デバッグシンボル(-g)の埋め込みビルドをはじめ、C++特有のマングル名解決(-C)やインライン関数のコールスタック展開(-i)に至るまで、現場ですぐに役立つトラブルシューティング手法を網羅しました。

1. addr2line 実行環境のセットアップ(binutils・gccの導入と動作検証)

addr2line コマンド本体および検証用コードのコンパイル環境を整えるため、パッケージマネージャから binutils と gcc をシステムへ一括導入します。端末で sudo apt-get update -qq && sudo apt-get install -y binutils gcc を実行してください。

binutilsとgccのインストール実行

パッケージのセットアップが完了したら、addr2line -v を叩いてツールが正常に認識されているかバージョン情報を確認しましょう。
addr2line は、クラッシュダンプやスタックトレースに現れる16進数の命令アドレスを受け取り、コンパイル時に埋め込まれたデバッグ情報を照合してソースコード上の正確な位置情報へとマッピングしてくれるGNUツールです。
⚙️ [実務で役立つ代表的なフラグ一覧]

-e : 調査対象となるELFバイナリ(実行ファイル)のパスを指定
-f : 行番号の出力に合わせて対象の関数・シンボル名も併せて表示
-C : コンパイラによってエンコードされたシンボル(マングル名)を人間が判読可能な名前に復元(デマングル)
-p : ファイルパス、行番号、関数名を1行のすっきりしたフォーマットで整形表示
-a : 解析結果の先頭に入力値となったメモリ番地そのものを付加
-i : コンパイラ最適化でインライン展開された関数の多段呼び出し元まで遡って追跡

addr2lineのバージョン確認

2. デバッグシンボル付き(-g)検証用バイナリのビルドとアドレス特定

検証作業を隔離された環境で行うため、mkdir -p ~/addr2line_lab && cd ~/addr2line_lab を実行して専用の作業ディレクトリを立ち上げ、カレントディレクトリを切り替えます。

作業用ディレクトリの作成と移動

障害シミュレーションの題材として、faulty_function という関数を持つ最小限のCソースファイル test_crash.c を作成します。

検証用Cソースコードの生成

addr2line による逆引きを成功させる鍵は、DWARFデバッグ情報の保持にあります。最適化によるコードの変形を防ぐ -O0 と、シンボルを埋め込む -g フラグを明示し、gcc -g -O0 test_crash.c -o test_crash でバイナリを生成します。

デバッグ情報付きバイナリのコンパイル

生成された実行ファイルのシンボルテーブルを検査し、ターゲット関数が配置されている仮想アドレスを把握するため nm test_crash | grep faulty_function を実行します。
コマンドの出力結果から、当該関数が 0000000000001149 のアドレスにマップされていることが読み取れます。

nmコマンドによる関数の仮想メモリアドレス確認

3. アドレス値からソースファイル名と該当行を特定する(-e / -f オプション)

後続の調査コマンドを簡潔に組み立てるため、FUNC_ADDR=$(nm test_crash | grep faulty_function | awk '{print $1}') を用いて抽出したアドレス文字列を変数へ格納しておきます。

関数アドレスを変数にエクスポート

-e オプションで対象の実行バイナリを指定し、引数に取得した番地を渡すことで、該当箇所のファイル名およびコード行(test_crash.c:3)が一発で特定されます。

addr2lineによるファイル名と行番号の逆引き

さらに -f フラグを組み合わせると、行番号だけでなく問題のロジックを内包する関数名(faulty_function)もセットで出力させることが可能です。

関数名を含めた詳細情報の出力

4. C++シンボルのデマングル(-C)とインライン関数チェーンの追跡(-i / -p)

C++特有のシンボル装飾やインライン展開への追従を確かめるため、sudo apt-get install -y g++ を実行してC++ビルドチェインを導入します。

g++コンパイラの追加インストール

多重インライン展開の構造を模倣したサンプルコードとして、以下の C++ ソース test_cpp.cpp を用意します。

インライン構造を含むC++サンプルの作成

本番運用に近い環境を想定し、積極的なインライン展開を促す最適化オプション -O2 とデバッグ情報 -g を併用して、g++ -g -O2 test_cpp.cpp -o test_cpp でビルドします。

インライン最適化を有効にしたビルド実行

解析の基点とするため、MAIN_ADDR=$(nm test_cpp | grep 'main' | head -n 1 | awk '{print $1}') を実行して main 関数のエントリアドレスを変数 MAIN_ADDR にセットします。

main関数のアドレスを取得して変数に格納

シンボル名を可読化する -C に加え、展開元を再帰的に辿る -i、出力を1行にまとめる -p を指定してアドレス解析を実行します。
なお、実行時に ??:0 と返されるケースは、コンパイラの高次最適化によるコードの並べ替えや、位置独立実行ファイル(PIE)のベースアドレス配置により、単純な静的アドレスとソースマッピング情報が直結しなかった際によく見られる挙動です。

デマングルとインライン追跡を併用したaddr2lineの実行結果

以上、Linux環境下で原因不明のクラッシュやセグフォに直面した際、addr2lineを使って短時間で問題のソース行を突き止めるアプローチを解説しました。
生のアドレス値が並ぶ無機質なエラーログを見ても焦る必要はありません。addr2lineを自在に使いこなすことで、デバッグ効率を大幅に引き上げましょう。