本物のC

デバッグ:GDB


より厄介なバグの原因を突き止める為には幾度となく試行錯誤を繰り返す必要がありますが、一々コードを書き替えてコンパイルするのは如何にも面倒です。

そこで、プログラムの挙動の制御や解析を実行時に行う為のツールこそがデバッガ(debugger)であると言えます。

GDB とは GNU Project debugger の事で、gcc と共に UNIX 系で広く使われているデバッガです。(MinGW 等で Windows でも使えます。)

gdb の起動

C プログラムは特に何もしなくてもgdbでデバッグ可能ですが、コンパイル時に-gオプションを付けておくとバイナリにデバッグ情報が付加されるので、行数などソースコードとの対応が分かりやすくなります。(ただし最適化を掛け過ぎると混沌とした事になるので注意しましょう。)

デバッグしたいプログラムをgdbへ渡して起動します。例えば Ubuntu 上で a.out を渡すと

$ gdb a.out GNU gdb (Ubuntu 7.7.1-0ubuntu5~14.04.2) 7.7.1 Copyright (C) 2014 Free Software Foundation, Inc. License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html> This is free software: you are free to change and redistribute it. There is NO WARRANTY, to the extent permitted by law. Type "show copying" and "show warranty" for details. This GDB was configured as "x86_64-linux-gnu". Type "show configuration" for configuration details. For bug reporting instructions, please see: <http://www.gnu.org/software/gdb/bugs/>. Find the GDB manual and other documentation resources online at: <http://www.gnu.org/software/gdb/documentation/>. For help, type "help". Type "apropos word" to search for commands related to "word"... Reading symbols from a.out...done. (gdb)

と出ますがほぼ定型文で、最後の Reading symbols from a.out ~ だけ確認すればよいでしょう。デバッグ情報が無い場合は (no debugging symbols found) が出ます。

行頭の (gdb) は入力待ちを示しています。

他にgdbをオプション無しで起動した後、fileでプログラムを指定したり、

$ gdb ... (gdb) file a.out Reading symbols from a.out...done.

attachにプロセス ID なりプロセス名なりを指定して実行中のプロセスを捕まえる事もできます。

終了はquitです。

尚、ここに紹介する様な基本のコマンドは大抵最初の一文字で代替できる様になっています。quitqrunrなど。

プログラムの実行

runでプログラムを実行します。

(gdb) run Starting program: /home/UserName/src/a.out

普通に終了すると

[Inferior 1 (process 1234) exited normally]

という具合ですが、例えばアドレス 0 へ書き込もうとするコードが実行されれば

Program received signal SIGSEGV, Segmentation fault. 0x00000000004004fd in main () at foo.c:5 5 *ptr = 0; (gdb)

と中断します。

foo.c:5 とその次の行はエラーを引き起こしたソースファイルと行番号、その行の内容で、これらはデバッグ情報によるものです。

ブレークポイント

プログラムを実行する前にブレークポイント(breakpoint)を仕掛けておく事で、エラー時以外にもプログラムを中断させる事ができます。

ブレークポイントはbreakで設定します。場所は関数名で指定したり、

(gdb) break encode Breakpoint 1 at 0x4013e6: file caesar.c, line 8.

ファイルと行番号で指定したり、

(gdb) break caesar.c:10 Breakpoint 2 at 0x4013ef: file caesar.c, line 10.

条件を付けたりする事ができます。(ただし条件付きブレークポイントは結構遅くなるので注意が要ります。)

(gdb) break caesar.c:10 if i == 0 Note: breakpoint 2 also set at pc 0x4013ef. Breakpoint 3 at 0x4013ef: file caesar.c, line 10.

この二行目は上のブレークポイントと位置が被っている事を知らせています。

またwatchは特定の変数が書き換わった時にプログラムを中断させます。

ブレークポイントを削除する場合はdeleteに番号を指定します。

(gdb) delete 2

現在有効なブレークポイントの一覧はinfo breakで参照できます。

(gdb) info break Num Type Disp Enb Address What 1 breakpoint keep y 0x00000000004013e6 in encode at caesar.c:8 3 breakpoint keep y 0x00000000004013ef in encode at caesar.c:10 stop only if i == 0

尚、実行中のプログラムは Ctrl+C で中断させる事も可能です。

実行の制御

runは「はじめから」なので、ブレークポイントなどで中断したプログラムの再開にはcontinueを使います。

stepnextは命令を一つ実行し、大抵は制御が次の行に進みます。

stepは関数の呼び出しがあればその関数の中に進みますが、

28 encode(n, text); (gdb) step encode (n=10, text=0x61ff0c "abc") at caesar.c:11 11 for (i = 0; text[i]; i++) (gdb)

nextはそうしません。

28 encode(n, text); (gdb) next 29 puts(text); (gdb)

またuntilは指定した行まで制御を進めます。

finishreturnによって、直ちに関数から抜ける事ができます。

実行中のプログラムを終了する場合はkillを使います。

(gdb) kill Kill the program being debugged? (y or n) y

メモリの参照

info argsで現在の関数の引数を、info localでローカル変数を表示します。

(gdb) info args n = 10 text = 0x151890 "abc" (gdb) info locals i = 0

printで特定の変数の内容を表示します。

(gdb) print n $1 = 10

/に続けてフォーマットの指定ができます。

(gdb) print/x n $2 = 0xa

printfと同じものの他に、tで二進表記が可能です。

(gdb) print/t n $3 = 1010

配列型は各要素が列挙されるので、バイナリでもchar配列などにキャストしてprintを使うと中身が分かります。

(gdb) print/x (char[4]) text $4 = {0x90, 0x18, 0xc0, 0x0}

backtraceまたはbtでバックトレース表示、frameでスタックフレームを選択します。

スレッド

info threadsによってプロセス内のスレッドの一覧が得られます。

(gdb) info threads Id Target Id Frame 7 Thread 4356.0x220c 0x77af8f00 in ?? () 6 Thread 4356.0x2604 0x004013e7 in worker (arg=0x0) at th2.c:13 * 5 Thread 4356.0x1208 worker (arg=0x0) at th2.c:13 4 Thread 4356.0x10b8 0x77af876c in ?? () 3 Thread 4356.0x1740 0x77af876c in ?? () 2 Thread 4356.0x2228 0x77af876c in ?? () 1 Thread 4356.0x2350 0x77ab5774 in ?? ()

コマンドの対象となるスレッドはthreadで変更する事ができます。

(gdb) thread 1 [Switching to thread 1 (Thread 4356.0x2350)] #0 0x77ab5774 in ?? ()