試行錯誤のデバッグで探る、printf()内のポインタ経由での関数呼び出しが行き着く先とは:main()関数の前には何があるのか(3)(3/4 ページ)
C言語の「Hello World!」プログラムで使われる、「printf()」「main()」関数の中身を、デバッガによる解析と逆アセンブル、ソースコード読解などのさまざまな側面から探る連載。今回は、printf()内のポインタ経由での関数呼び出しが行き着く先を試行錯誤のデバッグで探る。
ポインタ経由での関数呼び出しを探る
図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命令の位置でブレークしている。
メッセージ出力が行われるのはここだろうか。nextiで処理を進めてみよう。
メッセージは出力されない。1回目の関数呼び出しではメッセージは出力されないようだ。
再びcontinueで処理を進める。すると再度、call命令の位置でブレークした。
そしてnextiを実行すると、今度は図2.21のようになった。
図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のようになった。
どうやら_IO_new_file_xsputn()という関数が呼び出されていて、その先でメッセージの出力が行われているようだ。
あとはまた_IO_new_file_xsputn()にブレークポイントを張り、再度実行することで_IO_new_file_xsputn()の中に入っていくことができる。
Copyright © ITmedia, Inc. All Rights Reserved.
関連記事
- プログラミング言語Cについて知ろう
プログラミング言語の基本となる「C」。正しい文法や作法を身に付けよう。Cには確かに学ぶだけの価値がある(編集部) - シェルコード解析に必携の「5つ道具」
コンピュータウイルスの解析などに欠かせないリバースエンジニアリング技術ですが、何だか難しそうだな、という印象を抱いている人も多いのではないでしょうか。この連載では、「シェルコード」を例に、実践形式でその基礎を紹介していきます。(編集部) - 【 od 】コマンド――ファイルを8進数や16進数でダンプする
本連載は、Linuxのコマンドについて、基本書式からオプション、具体的な実行例までを紹介していきます。今回は、「od」コマンドです。