デバッガはプログラムを止めて中を見る
デバッガは、プログラムを途中で止めて、変数の中身を見る道具です。gdb は Linux でよく使われるデバッガで、クラッシュした行を教えてくれます。
セグメンテーション違反と出て止まったプログラムでも、gdb で動かせば、どの行で何が起きたかが分かります。
このレッスンのゴール
メモ
デバッガを使って、プログラムがクラッシュする行を見つけられる。
重要な言葉
| ことば | いみ | れい |
|---|---|---|
| デバッガ | プログラムを止めて、中を調べる道具 | gdb |
| ブレークポイント | プログラムを止める行の印 | |
| バックトレース | 今の関数と、呼び出し元の一覧 | |
| シグナル | OSがプロセスに送る知らせ | SIGSEGV |
| セグメンテーション違反 | 使えないメモリに触って、OSに止められること | |
| デバッグ情報 | 行番号を実行ファイルに残すもの。gcc -g で付く |
学生が入力するセルは、このプレビューでは空白です。
-gを付けると行番号が分かる
gcc -g で作ると、実行ファイルにソースの行番号が残ります。gdb で run すると、クラッシュした行で止まります。
OSは、使えないメモリに触ったプロセスに、シグナルを送って止めます。その名前が SIGSEGV で、セグメンテーション違反の知らせです。
4行目で落ちる小さなプログラム
int main(void)
{
int *p = 0;
*p = 1;
return 0;
}ソースを書いて -g で作る
printf 'int main(void)\n{\n int *p = 0;\n *p = 1;\n return 0;\n}\n' > /tmp/crash.c && gcc -g -o /tmp/crash /tmp/crash.cここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。
バックトレースで落ちた行を見る
gdb -batch -ex 'set debuginfod enabled off' -ex run -ex bt /tmp/crash 2>&1 | grep '^#0' | grep -o 'main () at .*'ここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。
落ちた時点の p を見る
gdb -batch -ex 'set debuginfod enabled off' -ex run -ex 'print p' /tmp/crash 2>&1 | tail -1ここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。
ブレークポイントで落ちる前に止める
break 4 は、4行目を実行する直前で止める印です。止まったら、print p で変数を見て、next で1行進めます。
落ちる前の変数を見られるので、原因に近づけます。quit で gdb を終わります。
gdb のよく使うコマンド
| コマンド | 働き |
|---|---|
| run | プログラムを始める |
| break 4 | 4行目で止まる印を付ける |
| bt | バックトレースを出す |
| print p | 変数 p の値を見る |
| next | 1行進める |
| quit | gdb を終わる |
学生が入力するセルは、このプレビューでは空白です。
4行目の手前で止めて p を見る
gdb -batch -ex 'set debuginfod enabled off' -ex 'break 4' -ex run -ex 'print p' /tmp/crash 2>&1 | grep -E 'Breakpoint 1,|^\$1'ここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。
クラッシュの形から原因の見当がつく
クラッシュの形を見ると、原因の見当がつきます。同じ SIGSEGV でも、場所とバックトレースの形が違います。
終わらない再帰は、スタックを使い切って落ちます。バックトレースが、同じ関数で何千行も続きます。下の Run は、2000行で打ち切って数えます。
症状と原因
| 症状 | 考えられる原因 |
|---|---|
| SIGSEGV で、番地が0の近く | NULLポインタを読み書きした |
| SIGSEGV で、同じ関数が何千行も続く | 終わらない再帰(スタックあふれ) |
| SIGSEGV で、free 済みの場所 | 解放後の使用 |
| SIGFPE | 整数を0で割った(機種による) |
| SIGABRT | プログラムが自分で止めた |
学生が入力するセルは、このプレビューでは空白です。
終わらない再帰のプログラムを作る
printf 'int f(int n)\n{\n return f(n + 1) + 1;\n}\nint main(void)\n{\n return f(0);\n}\n' > /tmp/deep.c && gcc -g -o /tmp/deep /tmp/deep.cここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。
2000フレームまで数える
gdb -batch -ex 'set debuginfod enabled off' -ex run -ex 'bt 2000' /tmp/deep 2>&1 | grep -c '^#'ここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。
よくある間違い
注意
クラッシュした行が、誤りのある行だ、と思いがちです。その行は、誤りの結果が現れた場所にすぎません。p が0になったのは、もっと前の行かもしれません。バックトレースと変数の値から、さかのぼって探します。
さらに挑戦: 自分のクラッシュを作る
課題が通ったら、自分で小さなプログラムを書き、わざと落としましょう。free したポインタを使う、または終わらない再帰、のどちらかです。
gdb で動かして、どのシグナルで止まり、バックトレースがどうなるかを、課題の報告の形で書き出しましょう。