検索
連載

試行錯誤のデバッグで探る、printf()内のポインタ経由での関数呼び出しが行き着く先とはmain()関数の前には何があるのか(3)(3/4 ページ)

C言語の「Hello World!」プログラムで使われる、「printf()」「main()」関数の中身を、デバッガによる解析と逆アセンブル、ソースコード読解などのさまざまな側面から探る連載。今回は、printf()内のポインタ経由での関数呼び出しが行き着く先を試行錯誤のデバッグで探る。

Share
Tweet
LINE
Hatena

ポインタ経由での関数呼び出しを探る

 図2.18では「call *0x1c(%eax)」という関数呼び出しが行われている。

 「0x1c(%eax)」という表記は連載第1回で説明したように、EAXレジスタに0x1cを加算したアドレスの位置の値、という意味だ。よってこれは、関数へのポインタを経由しての関数呼び出しになっている。call命令手前の3つのmov命令によってスタック上に準備している引数も含めてC言語風に書くと、以下のような感じだ。

int (*f)(int a, int b, int c);
f = *(EAX + 0x1c)
f(EBX, EDX, ESI);

 ポインタ経由で関数呼び出しされているので、その先にどのような関数があるのかが一見してわからない。アセンブラを見る限り、関数のアドレスはEAX+0x1Cという位置にあるのだが、関数呼び出しによってEAXの値は変化してしまうので、調べるのも面倒そうだ。

 ということでこの関数呼び出しの位置にブレークポイントを張ってみよう。call命令は0x8059735というアドレスに配置されている。そして以下のようにすれば、アドレス指定でブレークポイントを設定することができる。アドレス値の前に「*」が必要となる点に注意してほしい。

(gdb) break *0x8059735
Breakpoint 3 at 0x8059735
(gdb)

 Ctrl+Lで画面をきれいにして、再度、実行してみよう。

(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)

 最初はmain()の先頭でブレークしているようだ。continueで処理を進める。

(gdb) continue
Continuing.
 
Breakpoint 2, 0x080591d3 in vfprintf ()
(gdb)

 vfprintf()でブレークしたようだ。さらにcontinueで処理を進めよう。

(gdb) continue
Continuing.
 
Breakpoint 3, 0x08059735 in vfprintf ()
(gdb)

 画面は図2.19のようになった。先ほどブレークポイントを設定したcall命令の位置でブレークしている。

図2.19: call命令でブレークする
図2.19: call命令でブレークする

 メッセージ出力が行われるのはここだろうか。nextiで処理を進めてみよう。

図2.20: nextiでcall命令を実行する
図2.20: nextiでcall命令を実行する

 メッセージは出力されない。1回目の関数呼び出しではメッセージは出力されないようだ。

 再びcontinueで処理を進める。すると再度、call命令の位置でブレークした。

 そしてnextiを実行すると、今度は図2.21のようになった。

図2.21: 2回目のcall 命令でメッセージが出力される
図2.21: 2回目のcall 命令でメッセージが出力される

 図2.21はやはり画面が崩れてしまっているのだが、メッセージが出力されている。

 ということは、2回目のcall命令でメッセージが出力されるということだ。

 runで再実行しよう。1回目のmain()でのブレークと、2回目のvfprintf()でのブレークは、continueで継続する。さらにcall命令でブレークするはずだが、これもcontinueで継続する。そして2回目のcall命令のブレークのときに、stepiで関数内部に入っていこう。

(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) continue
Continuing.
 
Breakpoint 2, 0x080591d3 in vfprintf ()
(gdb) continue
Continuing.
 
Breakpoint 3, 0x08059735 in vfprintf ()
(gdb) continue
Continuing.
 
Breakpoint 3, 0x08059735 in vfprintf ()
(gdb) stepi
0x080673a0 in _IO_new_file_xsputn ()
(gdb)

 すると画面は図2.22のようになった。

図2.22: _IO_new_file_xsputn()の呼び出し
図2.22: _IO_new_file_xsputn()の呼び出し

 どうやら_IO_new_file_xsputn()という関数が呼び出されていて、その先でメッセージの出力が行われているようだ。

 あとはまた_IO_new_file_xsputn()にブレークポイントを張り、再度実行することで_IO_new_file_xsputn()の中に入っていくことができる。

Copyright © ITmedia, Inc. All Rights Reserved.

ページトップに戻る