このようにcall命令を探してはnextiでメッセージが出力されるかどうか確認し、新たにブレークポイントを設定して実行し直してということを繰り返していけば、どの関数の先でメッセージ出力が行われているのかを突き詰めていくことができる。
そしていずれはメッセージ出力の核の部分、実際の出力が行われる最後の1命令まで迫ることができるはずだ。
今までわかったことをまとめてみよう。hello.cのメッセージ出力処理は、以下のような関数呼び出しによって行われているということになる。
main()→printf()→vfprintf()→_IO_new_file_xsputn()→(不明)→メッセージ出力処理
次は_IO_new_file_xsputn()にブレークポイントを張り、再びrunで実行する。
(gdb) break _IO_new_file_xsputn Breakpoint 4 at 0x80673a8 (gdb) run The program being debugged has been started already. Start it from the beginning? (y or n) Starting program: /home/user/hello/hello Breakpoint 1, main (argc=1, argv=0xbffffc14) at hello.c:5 (gdb)
ブレークポイントが増えてきたので、ここらで一度整理しておこう。
まずinfo breakpointsでブレークポイント一覧を出力する。アセンブラ表示をしているため画面に入りきらない場合には、「Ctrl+X」の後に続けて「A」を押すことで、リスト表示のON / OFFを一時的に切替えられる。
(gdb) info breakpoints Num Type Disp Enb Address What 1 breakpoint keep y 0x080482c5 in main at hello.c:5 breakpoint already hit 1 time 2 breakpoint keep y 0x080591d3 <vfprintf+19> 3 breakpoint keep y 0x08059735 <vfprintf+1397> 4 breakpoint keep y 0x080673a8 <_IO_new_file_xsputn+8> (gdb)
_IO_new_file_xsputn()以外のブレークポイントは一時的に無効にしてしまおう。これには以下のようにdisableコマンドでブレークポイントの番号(上記info breakpointsの出力の左端の値)を指定すればいい。
(gdb) disable 1 2 3
もう一度、ブレークポイントの一覧を見てみよう。
(gdb) info breakpoints Num Type Disp Enb Address What 1 breakpoint keep n 0x080482c5 in main at hello.c:5 breakpoint already hit 1 time 2 breakpoint keep n 0x080591d3 <vfprintf+19> 3 breakpoint keep n 0x08059735 <vfprintf+1397> 4 breakpoint keep y 0x080673a8 <_IO_new_file_xsputn+8> (gdb)
1〜3番までは「Enb」の部分が「n」になっており、無効化されていることがわかる。
あとはさらに調査を進めていくだけだ。実際の作業はブレークポイントを張ってはステップ実行でメッセージ出力が行われるかどうかを確認するだけの単調なものになるので読み飛ばしていただいても構わないが、ここでは筆者が実施した手順について、いちおう説明しておこう。
「Ctrl+X」→「A」でリスト表示を元に戻し、continueしてみよう。今度は図2.23のようにして、_IO_new_file_xsputn()の先頭まで一気に進むことだろう。
そしてcontinueしてみるが、メッセージは出力されず、再度_IO_new_file_xsputn()でブレークする。さらにcontinueしても同じだ。3回目と4回目のcontinueも同様だ。
そして5回目のcontinueでようやく図2.24のようになり、メッセージが出力された。
ということは_IO_new_file_xsputn()での5回目のブレークの際に、nextiで処理を進めればいいことになる。
runで再実行し、_IO_new_file_xsputn()でブレークするのでcontinueを4回実行する。そしてnextiで処理を進めていく。
すると、図2.25の箇所でメッセージ出力がされた。
ポインタ経由で関数呼び出しされているので、先ほどと同様にcall命令にブレークポイントを設定する。call命令のアドレスは0x80673f1だ。
(gdb) break *0x80673f1 Breakpoint 5 at 0x80673f1 (gdb)
先ほどのブレークポイントは無効化しておく。
(gdb) disable 4
runで再実行しよう。
(gdb) run The program being debugged has been started already. Start it from the beginning? (y or n) Starting program: /home/user/hello/hello Breakpoint 5, 0x080673f1 in _IO_new_file_xsputn () (gdb)
continueを実行すると同じ場所でまたブレークするのだが、繰り返すと2回目のcontinueでメッセージが出力される。
runで再実行し、1回continueを行い、2回目のブレークはstepiで関数呼び出しの中に入る。すると_IO_new_file_overflow()という関数の内部に入った。
nextiで処理を進めよう。すると図2.26の位置でメッセージが出力された。
_IO_new_do_write()という関数の先でメッセージ出力が行われているようだ。
_IO_new_do_write()にブレークポイントを張り、他のブレークポイントは無効にする。
(gdb) break _IO_new_do_write Breakpoint 6 at 0x8068116 (gdb) disable 5 (gdb) run The program being debugged has been started already. Start it from the beginning? (y or n) Starting program: /home/user/hello/hello Breakpoint 6, 0x08068116 in _IO_new_do_write () (gdb)
ブレーク後にcontinueしてみると、2回目の_IO_new_do_write()の呼び出しでメッセージが出力されているようだ。
ということでrunで再実行し、1回はcontinueでスキップし、2回目のブレークの際にnextiで処理を進めていく。
すると図2.27の位置でメッセージが出力された。
ポインタ経由で関数呼び出しが行われている。そこでcall命令(0x8068198)にブレークポイントを設定し、先ほどのブレークポイントを無効化する。
(gdb) break *0x8068198 Breakpoint 7 at 0x8068198 (gdb) disable 6 (gdb)
runで再実行してみると、1回目のブレークでメッセージ出力されるようだ。ということでもう一度runで再実行し、stepiで関数の中に入る。
すると図2.28のようになった。
_IO_new_file_write()が呼ばれているので、_IO_new_file_write()にブレークポイントを張り、先ほどのブレークポイントは無効化する。
(gdb) break _IO_new_file_write Breakpoint 8 at 0x8067638 (gdb) disable 7 (gdb)
runで再実行する。ブレークするのでcontinueすると、どうやら1回目のブレークでメッセージが表示されるようだ。ということでrunで再実行し、ブレークしたらnextiで処理を進める。
すると、図2.29のようなwrite()の呼び出しがあった。
ハロー“Hello, World” OSと標準ライブラリのシゴトとしくみ
坂井弘亮著
秀和システム 3,200円
C言語の入門書では、"Hello, World"と出力するプログラムを最初に作るのが定番です。"Hello, World"は、たった7行の単純なプログラムですが、printf()の先では何が行われているのか、main()の前にはいったい何があるのか、考えてみると謎だらけです。本書は、基礎中の基礎である"Hello, World"プログラムを元に、OSと標準ライブラリの仕組みをあらゆる角度からとことん解析します。資料に頼らず、自分の手で調べる方法がわかります。
Copyright © ITmedia, Inc. All Rights Reserved.