検索
連載

「Hello World!」の主役printf()の内部動作をデバッガGDBで追うmain()関数の前には何があるのか(2)(2/3 ページ)

C言語の「Hello World!」プログラムで使われる、「printf()」「main()」関数の中身を、デバッガによる解析と逆アセンブル、ソースコード読解などのさまざまな側面から探る連載。今回は、「Hello World!」の主役printf()の内部動作をデバッガGDBで追う。

Share
Tweet
LINE
Hatena

gdbserverで画面崩れを防ぐ

 さてこれで実行はできたわけだが、「Hello World!」の文字列はGDBを起動しているスクリーン上に出力されている。

 これは実は、後述するソースコードやアセンブラの表示を行うと、画面が崩れてしまい確認しにくくなるという問題がある。場合によってはそもそも視認できない、ということもあるようだ。

 これを回避するためには、gdbserverを利用してサンプル・プログラムの起動コンソールとGDBの操作コンソールを分けるとよい。以下にgdbserverによるデバッグ手法を説明しておくので、「Hello World!」のメッセージ出力が確認できない、という場合には参考にしてほしい。

なお本書では説明を1画面で行いたいという都合上、gdbserver は利用せずにひとつのコンソール上で作業を行っている。このため「Hello World!」の文字列表示で画面が崩れているような場合もあるので注意していただきたいと思う。


 まずgdbserverがインストールされていない場合には、スーパーユーザで以下を実行することでgdbserverをインストールする。

[user@localhost ~]$ gdbserver
-bash: gdbserver: command not found
[user@localhost ~]$ su
Password:
[root@localhost user]# yum install gdb-gdbserver

 インストールできたら、gdbserver 経由でサンプル・プログラムを起動しよう。

[user@localhost ~]$ cd hello
[user@localhost hello]$ gdbserver localhost:12345 ./hello
Process ./hello created; pid = 2324
Listening on port 12345

 ここでは接続用ポート番号を「12345」という適当なものにしているが、すでに使われている場合には以下のようなエラーになるので、ポート番号を12345以外に変更して再度試してみてほしい。

[user@localhost hello]$ gdbserver localhost:12345 ./hello
Process ./hello created; pid = 2466
Can't bind address: Address already in use.
Killing process(es): 2466
[user@localhost hello]$

 gdbserverが起動できたら、次は別のターミナルでGDBを起動する。

[user@localhost ~]$ cd hello
[user@localhost hello]$ gdb -q hello
Reading symbols from /home/user/hello/hello...done.
(gdb)

 gdbの「-q」オプションは、起動時のメッセージ出力を抑制するためのものだ。本書ではページ数が限られていることもあり、以降は-qオプションを利用することにする。

 GDB側で「target extended-remote」というコマンドでgdbserverに接続する。これでgdbserver側で起動しているサンプル・プログラムを、GDB側から操作できる。

(gdb) target extended-remote localhost:12345
Remote debugging using localhost:12345
0x080481c0 in _start ()
Created trace state variable $trace_timestamp for target's variable 1.
(gdb)

なお接続には通常は「target remote 」コマンドを利用するのだが、それだとgdbserverを利用しない場合と比べて様々な操作の違いが出てくる。例えば起動時にrunでなくcontinueで動作開始する、runでリスタートができない、終了すると接続が切断されてしまいやはりリスタートできない、といったものだ。「targetextended-remote 」で接続することで、gdbserverを経由せずにgdb上でプログラムを動作させているときと同じ感覚で操作できる。


 あとは通常のGDBの操作と同じだ。runで実行してみよう。

(gdb) run
The program being debugged has been started already.
Start it from the beginning? (y or n) y
Starting program: /home/user/hello/hello
 
Program exited normally.
(gdb)

 GDB 側にはメッセージは出力されていない。プログラムの実行はgdbserver 側で行われ、メッセージもそちらに出力されることになる。

[user@localhost hello]$ gdbserver localhost:12345 ./hello
Process ./hello created; pid = 2324
Listening on port 12345
Remote debugging from host 127.0.0.1
Process ./hello created; pid = 2353
Hello World! 1 ./hello
 
Child exited with status 0

 図2.1はGUI 上でターミナルを2つ開き、片方でgdbserver、もう片方でGDBを動作させているところだ。左下のウィンドウがgdbserver、右上のウィンドウがGDBになる。左下のgdbserver 側で「Hello World!」のメッセージ出力がされていることを確認してほしい。

図2.1: gdbserverによる実行
図2.1: gdbserverによる実行

 プログラムの終了は、「quit」ではgdbserver側が終了せず、GDB側のみ終了する。以下のように「monitor exit」というコマンドで、gdbserver側を終了できる。GDB側を終了させてしまったときには、GDBを再起動しtarget extended-remoteで再接続できるので、再接続した状態でmonitor exitを実行する。

(gdb) monitor exit

 gdbserverは本来は組込み機器のようなプアなプログラム実行環境で、ターゲット機器上ではGDBを動作させずにプログラムのデバッグを行うためのものなのだが、プログラムの実行とGDBの操作を分離できるため、たとえばテキストエディタのようなプログラムのデバッグにも重宝する。

ブレークポイントを張ってみる

 さて、ただ実行するだけではデバッガを使う意味が無い。

 GDBの操作方法の説明に戻ろう。次はmain()関数の先頭に「ブレークポイント」を張って、そこでブレークしてみる。ブレークポイントを張るコマンドは「break」だ。

(gdb) break main
Breakpoint 1 at 0x80482c5: file hello.c, line 5.
(gdb)

 もう一度実行してみよう。

(gdb) run
Starting program: /home/user/hello/hello
 
Breakpoint 1, main (argc=1, argv=0xbffffc14) at hello.c:5
5	printf("Hello World! %d %s\n", argc, argv[0]);
(gdb)

 今度はmain()関数の内部、hello.cの5 行目でブレークしたようだ。

 これで、どうやらprintf()の呼び出し位置でブレークしているらしいということはわかる。ソースコードを表示させてみよう。

(gdb) layout src

 すると、図2.2のような画面になるだろう。

図2.2: layout srcの実行画面
図2.2: layout srcの実行画面

 printf()の行が反転表示されているので、そこで停止しているようだ。

 反転表示されている位置は「実行完了した行」ではなく「これから実行しようとしている行」を指す。つまりprintf()の呼び出し前で停止していることになる。

 このようにデバッガではブレークポイントを張ることで、任意の場所でプログラムの実行を一時停止することができる。

 この「ブレークポイント」が、デバッガの代表的な2つの機能のうちのひとつだ。

ステップ実行してみる

 デバッガの代表的な機能のもうひとつは、ステップ実行だ。

 この状態でステップ実行してみよう。ステップ実行は「next」というコマンドで行える。

図2.3: nextの実行画面
図2.3: nextの実行画面

 図2.3ではreturnの行が反転表示されている。表示位置が崩れてしまってはいるが、「Hello World!」のメッセージも出力されていることがわかる。

 なおここで表示崩れのために「Hello World!」のメッセージが確認できない、という場合があるかもしれない。そのような場合には、先述したgdbserver 経由での実行を試してみてほしい。

 さて現在はreturnの実行前でブレークしている状態だが、「continue」というコマンドを実行すると、ブレーク状態を解除して実行を継続する。

 やってみよう。

(gdb) continue
Continuing.
 
Program exited normally.
(gdb)

 実行が継続され、プログラムが終了した。画面は図2.4のようになっている。

図2.4: continueの実行画面
図2.4: continueの実行画面

 GDBではこのように、プログラムを適宜ブレークさせてはそのときの状態を見て、デバッグをしていく。

 さらにプログラムの実行とコマンド実行は、排他になっている。プログラムを実行している最中はコマンド実行できないし、コマンドを実行させるときにはプログラムの実行を一時的に停止する。プログラム実行とコマンド実行を交互に行う感じだ。

 まずはこのようなデバッガの操作に慣れてほしい。

Copyright © ITmedia, Inc. All Rights Reserved.

ページトップに戻る