この記事のポイント
ulimit -c unlimitedでコアダンプを有効化し、クラッシュ原因をGDBで特定できるgdbserverでVPS・組み込み機器をローカルのGDBから遠隔デバッグできるwatchコマンドで変数の値変化を自動検出し、バグの発生箇所を絞り込める- Ubuntu 24.04 のGDBは 15.1(22.04 は 12.1)。新機能を使うなら24.04 を選ぶ
- デバッグシンボルは
gcc -g -O0で埋め込む。本番ビルドとは別に保管する
GDBは入門記事でインストール方法を覚えたあと、なかなか次のステップに進めないデバッガの代表格です。run と bt だけで済む場面はほぼなく、実務では「クラッシュした本番プロセスのコアダンプを手元で解析する」「VPS上で動くサービスをローカルのIDEからステップ実行する」という状況が多い。この記事では、そういった実際にはまる場面をUbuntu 24.04のDockerコンテナで再現して、GDB 15.1の実測データとともに解説します。
動作確認済み環境
本記事は ubuntu:24.04 Docker公式イメージで全コマンドを実行した実測結果を載せています。
| 項目 | バージョン | 確認方法 |
|---|---|---|
| OS | Ubuntu 24.04 LTS (Noble Numbat) | lsb_release -ds |
| GDB | 15.1-1ubuntu1~24.04.1 | gdb --version |
| GDBserver | 15.1-1ubuntu1~24.04.1 | gdbserver --version |
| GCC | 13.3.0-6ubuntu2~24.04.1 | gcc --version |
| 実行環境 | Docker公式イメージ(x86_64) | docker run --rm ubuntu:24.04 |
GDB と gdbserver のインストール
Ubuntu 24.04 では gdb と gdbserver は別パッケージです。リモートデバッグも使うなら両方入れておきます。コンパイラも一緒に入れておくと後の手順がスムーズです。
…
Setting up gdb (15.1-1ubuntu1~24.04.1) …
Setting up gdbserver (15.1-1ubuntu1~24.04.1) …
$ gdb –version
GNU gdb (Ubuntu 15.1-1ubuntu1~24.04.1) 15.1
$ gdbserver –version
GNU gdbserver (Ubuntu 15.1-1ubuntu1~24.04.1) 15.1
$ gcc –version
gcc (Ubuntu 13.3.0-6ubuntu2~24.04.1) 13.3.0

インストールできたらバージョンを確認しておきます。Ubuntu 22.04 では GDB 12.1 が入りますが、Ubuntu 24.04 では 15.1 になります(後述の比較表を参照)。
コアダンプ解析:クラッシュの原因を事後に特定する
「本番で突然落ちたが再現しない」という状況で真価を発揮するのがコアダンプです。プロセスがクラッシュした瞬間のメモリ状態を丸ごとファイルに保存し、後からGDBで解析できます。
手順1:コアダンプを有効化する
デフォルトではコアダンプのサイズ上限が0(無効)になっています。ulimit -c unlimited で解除します。
0 ← デフォルトは無効
$ ulimit -c unlimited
$ ulimit -c
unlimited
$ echo ‘/work/core.%p’ > /proc/sys/kernel/core_pattern
# %p はプロセスID。コアファイルが /work/core.<PID> として保存される
$ cat /proc/sys/kernel/core_pattern
/work/core.%p
注意
ulimit -c unlimited はカレントシェルのみ有効です。systemd サービスとして動くプロセスには /etc/systemd/system/myservice.service に LimitCORE=infinity を追加する必要があります。
手順2:デバッグシンボル付きでコンパイルする
コアダンプを解析するには、実行ファイルにデバッグシンボルが含まれていることが必要です。コンパイル時に -g フラグを付けます。
#include <stdio.h>
void do_invalid_access(int *ptr) { *ptr = 42; } // NULL参照
void intermediate(int *ptr) { do_invalid_access(ptr); }
int main(void) { int *p = NULL; intermediate(p); return 0; }
$ gcc -g -O0 -o crash crash.c
# -g: デバッグシンボル埋め込み -O0: 最適化なし(ステップ追跡を正確に)
$ ls -lh crash
-rwxr-xr-x 1 user user 18K Jun 22 crash
手順3:プログラムを実行してコアダンプを生成する
実際に動かしてみます。NULLポインタを参照するので SIGSEGV でクラッシュし、コアダンプが生成されます。
Program started
about to dereference null pointer…
Segmentation fault (core dumped)
$ ls -lh /work/core.*
-rw——- 1 root root 432K Jun 22 04:34 /work/core.3705
432KB のコアファイルが生成されました。このファイルとバイナリを使ってGDBで解析します。
手順4:GDB でコアダンプを解析する
GDBにバイナリとコアファイルの両方を渡します。起動直後にクラッシュ地点が表示され、スタックトレースで原因を追えます。
Core was generated by `./crash’.
Program terminated with signal SIGSEGV, Segmentation fault.
#0 0x…in do_invalid_access (ptr=0x0) at crash.c:7
7 *ptr = 42; // NULL dereference → SIGSEGV
(gdb) bt full
#0 do_invalid_access (ptr=0x0) at crash.c:7
#1 0x… intermediate (ptr=0x0) at crash.c:11
#2 0x… main () at crash.c:17
null_ptr = 0x0
(gdb) frame 2
#2 main () at crash.c:17
17 intermediate(null_ptr);
(gdb) print null_ptr
$1 = (int *) 0x0 ← main() で NULL を渡していたことが判明


実際に動かすと、起動直後に #0 do_invalid_access (ptr=0x0) at crash.c:7 と表示され、クラッシュした行が即座に特定されました。bt full でコールスタック全体を展開し、frame 2 で main() に移動して print null_ptr を実行すると 0x0(NULL)が確認できます。再現させなくてもクラッシュ原因を特定できるのがコアダンプ解析の強みです。
ウォッチポイント:変数の変化を自動検出する
「この変数がいつ書き換わるかわからない」という状況で役に立つのが watch コマンドです。変数の値が変化するたびにGDBが自動停止します。
ウォッチポイントの使い方
(gdb) break main
Breakpoint 1 at 0x1191: file watchtest.c, line 12.
(gdb) run
Breakpoint 1, main () at watchtest.c:12
(gdb) watch counter
Hardware watchpoint 2: counter
(gdb) continue
Hardware watchpoint 2: counter
Old value = 0
New value = 5
sensor_update (new_val=5) at watchtest.c:8
8 temperature += 0.1 * new_val; ← この行の直前で停止
(gdb) print temperature
$2 = 36.600000000000001 ← temperature 更新前のため初期値のまま
(gdb) continue
Hardware watchpoint 2: counter
Old value = 5
New value = 10
sensor_update (new_val=10) at watchtest.c:8
(gdb) print temperature
$4 = 37.100000000000001 ← sensor_update(5) 完了後の値
(gdb) info watchpoints
Num Type Disp Enb What
2 hw watchpoint keep y counter hit 2 times

実測では watch counter を設定後に continue を実行すると、sensor_update(5) の内部で counter が 0→5 に変化した瞬間に自動停止しました。停止位置は watchtest.c:8(temperature += 0.1 * new_val の直前)なので、この時点での print temperature は 36.6(温度更新前の初期値)になります。次の continue で sensor_update(5) が完了してから counter が 5→10 に変わる際に再度停止し、その時点の temperature は 37.1(sensor_update(5) 完了後)になります。ウォッチポイントはハードウェア支援(Hardware watchpoint)で動くため、ソフトウェア実装より高速で、ブレークポイントとは独立して機能します。
gdbserver によるリモートデバッグ
VPS上のサービスをローカルのGDBからデバッグしたいとき、または組み込みボードにGDBを入れる余裕がないときに使うのが gdbserver です。ターゲット側に軽量な gdbserver だけを置き、デバッグロジックはすべてローカルのGDBが担います。

手順1:ターゲット側で gdbserver を起動する
Process /work/counter created; pid = 3718
Listening on port 12345
# プログラムはここで停止し、GDB からの接続を待つ
手順2:ローカルの GDB から接続する
開発マシン側ではバイナリを起動せず、target remote コマンドでターゲットに接続します。
(gdb) target remote 192.168.1.10:12345
Reading /lib64/ld-linux-x86-64.so.2 from remote target…
0x000074a0… in _start () from target:/lib64/ld-linux-x86-64.so.2
(gdb) break increment
Breakpoint 1 at 0x…: file counter.c, line 7.
(gdb) continue
Breakpoint 1, increment (n=10) at counter.c:7
7 for (int i = 0; i < n; i++) {
(gdb) print global_count
$1 = 0 ← increment 実行前の値
(gdb) continue
$3 = 10 ← increment(10) 後の値を確認
(gdb) detach
[Inferior 1 (process 3718) detached]

実測では gdbserver 0.0.0.0:12345 ./counter を起動後、ローカルのGDBから target remote localhost:12345 で接続し、break increment → continue で increment(n=10) のブレークポイントに正常に停止しました。print global_count で値が 0 と確認でき、継続後は 10 に変わっていることも確認できます。detach するとプログラムはそのまま実行を続け、ターゲット側で最終出力 Final count: 15 が表示されました。
注意:ファイアウォール設定
VPS上で gdbserver を使う場合、ポート 12345(任意)を開放する必要があります。本番環境ではデバッグが終わったら必ずポートを閉じてください。SSHポートフォワーディング(ssh -L 12345:localhost:12345 user@vps)を使えば外部にポートを開かずにリモートデバッグできます。
Ubuntu 22.04 と 24.04 の GDB バージョン比較

| パッケージ | Ubuntu 22.04 (Jammy) | Ubuntu 24.04 (Noble) | 変更点 |
|---|---|---|---|
| GDB | 12.1 | 15.1 | メジャー3世代アップ / Python拡張強化 |
| GDBserver | 12.1 | 15.1 | GDBと同梱・同バージョン |
| GCC | 11.4.0 | 13.3.0 | デバッグ情報形式(DWARF)が強化 |
| Python GDB API | Python 3.10 | Python 3.12 | pretty-printer スクリプトの互換に注意 |
上記は docker run --rm ubuntu:22.04 と docker run --rm ubuntu:24.04 それぞれで apt install gdb gdbserver gcc && dpkg -l を実行した実測結果です。22.04 から 24.04 へのアップグレードでGDBはバージョン12→15と大きく上がります。既存の pretty-printer スクリプト(Python製)がある場合、Python 3.10→3.12 の変更で動かなくなる可能性があるので注意が必要です。
よくあるエラーと対処
①「No debugging symbols found」が出る
Reading symbols from ./myapp…(No debugging symbols found in ./myapp)
-g フラグなしでコンパイルしたバイナリです。-g -O0 を付けて再コンパイルしてください。本番バイナリの場合は objcopy --only-keep-debug でシンボルを別ファイルに保存しておく方法もあります。
②「warning: Error disabling address space randomization」が出る
Dockerコンテナ内で --privileged なしに実行すると出るメッセージです。デバッグの動作自体には支障ありませんが、docker run --privileged または --cap-add=SYS_PTRACE を付けると警告が消えます。
# SYS_PTRACE ケーパビリティで ptrace が使えるようになる
③コアダンプが生成されない
以下を順番に確認します:
unlimited ← 0 なら ulimit -c unlimited で有効化
$ cat /proc/sys/kernel/core_pattern
/work/core.%p ← コアファイルの保存先と形式を確認
$ ls -la /work/core.* # 保存先にコアファイルがあるか確認
# systemd使用環境では /var/lib/systemd/coredump/ に保存される場合あり
# coredumpctl list で確認できる
Ubuntu のデスクトップ環境や systemd を使うVPSでは、/proc/sys/kernel/core_pattern が |/usr/share/apport/apport %p... のようにパイプになっている場合があります。その場合は coredumpctl list でコアが保存されていないか確認してください。
まとめ
Ubuntu 24.04 の GDB 15.1 で実際にコアダンプ解析・リモートデバッグ・ウォッチポイントを動かしました。確認できたポイントをまとめます。
- コアダンプ解析は
ulimit -c unlimitedの有効化と-gフラグでのコンパイルがセット。gdb ./binary core.xxxxで起動後すぐにクラッシュ地点が表示される bt fullでコールスタック全体と各フレームのローカル変数が確認でき、frame N+printでクラッシュの根本原因を特定できたwatch 変数名でハードウェアウォッチポイントを設定すると値の変化を自動検出できる(実測で counter の 0→5→10 の変化を捕捉)- gdbserver はターゲット側に軽量なサーバーを置き、
(gdb) target remote ホスト:ポートで接続するだけ。VPS・組み込み機器でも同じ手順でリモートデバッグできた - Ubuntu 22.04 の GDB 12.1 から 24.04 の GDB 15.1 へのアップグレードで機能が大幅強化されているが、Python製 pretty-printer の互換性に注意が必要
GDBは覚えるコマンドが多く見えますが、bt・frame・print・watch・target remote の5つを実際に手を動かして使うと、他のコマンドも自然に覚えられます。コアダンプとリモートデバッグが使えるようになると、再現しないバグへの対応が格段に変わります。
VPSでGDBのリモートデバッグを試してみたい方は、 も参考にしてください。


コメント