既に起動しているプロセスをgdbで制御する

2007/1/17

デバッグやテストを行っていると、プログラムが無限ループに入り込んで返ってこないような状況が発生することがあります。 そのバグが再現の難しいものであれば「gdbを使って起動しとけば良かった。。。」というような気分になる時があります。 しかし、gdbには既に起動しているプログラムの制御を横から奪い取ってデバッグするというありがたい機能があります。

ここでは、既に起動しているプログラムをgdbでいじる方法を説明したいと思います。 実際にデバッグをする方法を説明するというよりは、gdbで動いているプロセスにアタッチする例を説明します。 その後のデバッグに関しては、いつものgdbの使い方をしていただければ大丈夫です。

とりあえずアタッチしてみる

既に起動しているプロセスをgdbを解析するのは非常に簡単です。 単にgdbでプロセスにアタッチするだけです。 gdbでプロセスにアタッチする方法は2つあります。 一つ目はgdbに-pオプションをつけて呼ぶ出す方法です。 二つ目はgdbを起動後にattachコマンドでプロセスにアタッチする方法です。 どちらの方法を使っても得られる結果は同じです。 以下の例では、アタッチする先のプロセスidを1111としています。



 % gdb -p 1111




 % gdb
(gdb) attach 1111


/bin/bashをアタッチしてみる

では、実際に動いているプログラムをgdbでいじってみましょう。 とりあえず、例として/bin/bashをいじってみます。 まず、/bin/bashを実行してプロセスIDを取得しましょう。



 % /bin/bash
 $ ps x | grep /bin/bash
2222


この例では、/bin/bashのpidは2222であるとします。

/bin/bashのプロセスIDを取得したら、別ターミナルからgdbを実行してみましょう。 -p オプションと/bin/bashのプロセスIDをつけてgdbを実行すると、gdbが/bin/bashの処理を途中から奪います。



 % gdb -p 2222
Attaching to process 2222
(gdb)


まずは、試しにgdbでスタックトレースを実行してみましょう。 gdbプロンプトのところで「bt」「backtrace」「where」のいずれかのコマンドを打ち込むとスタックフレームの状態がみられます。



(gdb) bt
#0  0x400ff8c8 in read () from /lib/libc.so.6
#1  0xbffff41f in ?? ()
#2  0x080b7c8b in rl_read_key ()
#3  0x080a8bd8 in readline_internal_char ()
#4  0x080a8d97 in readline_internal_char ()
#5  0x080a8dc0 in readline_internal_char ()
#6  0x080a894a in readline ()
#7  0x0805e3c5 in yy_input_name ()
#8  0x0805e33c in yy_input_name ()
#9  0x0805ebf5 in read_secondary_line ()
#10 0x0805f67c in reset_parser ()
#11 0x0805f0c0 in execute_prompt_command ()
#12 0x0805dff3 in yyparse ()
#13 0x0805ce94 in parse_command ()
#14 0x0805cebf in read_command ()
#15 0x0805cbe9 in reader_loop ()
#16 0x0805afdb in main ()
#17 0x4003f937 in __libc_start_main () from /lib/libc.so.6


もうちょっと遊んでみる

さて、もうちょっと遊んでみましょう。 次は、/bin/bashのプロセスでlsコマンドを実行してしまいます。

gdbのprintコマンドは本来は値を表示するための物ですが、そこで関数を実行出来てしまいます。 例えば、system関数を使うとlsなどが実行出来てしまいます。

以下のようなコマンドをgdbプロンプトで打ち込んだ後に、/bin/bashのターミナルを見るとlsが実行されているのが見られます。



(gdb) print system("ls")
$1 = 0


例えば、以下のようにwriteシステムコールを使うことも可能です。



(gdb) print write(fileno(stdout), "hello\n", 6)
$2 = 6


最後に制御をgdbから/bin/bash自身に戻すには、gdbのdetachコマンドを使います。 gdbを終了するにはquitコマンドを使います。



(gdb) detach
Detaching from program: /bin/bash, process 2222
(gdb) quit


この技を使うと、デバッグ用途以外にも、どうしても落とせないデーモンがハングった時に無理矢理軌道に乗せなおすなどが出来る場合もありますが、かなりの条件が整わないといけないですし、かなり危険なので、もうどうしようもない最終手段以外では使わない方が良いと思われます。

皆様のデバッグの一助になれば幸いです。

おまけ

GDBハンドブック 実践 デバッグ技法 ―GDB、DDD、Eclipseによるデバッギング Debug Hacks デバッグを極めるテクニック&ツール

GDBハンドブック

実践 デバッグ技法 ―GDB、DDD、Eclipseによるデバッギング

Debug Hacks デバッグを極めるテクニック&ツール

最近のエントリ

過去記事

過去記事一覧

IPv6基礎検定

YouTubeチャンネルやってます!