連載
» 2017年06月01日 05時00分 公開

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

[坂井弘亮,著]

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

 図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.

RSSについて

アイティメディアIDについて

メールマガジン登録

@ITのメールマガジンは、 もちろん、すべて無料です。ぜひメールマガジンをご購読ください。