main関数の引数や変数等のレジスタ周り
インラインアセンブラのローカル変数の部分を勉強していたら、こちらが気になったので脱線中です。
main関数で変数を宣言したときのEBPからのオフセットと呼びだした関数の中で変数を宣言したときのEBPからのオフセットが変わるのはなぜだろうと疑問が出てきました。
そこで、EBPとESP、いわゆるスタック関連について調査です。
まずは簡単なCのソースを書いてみます。
int main(int argc, char *argv[]) { return 0; }
これをgccでコンパイルしアセンブラコードを出力してみます。
$ gcc -S hoge.c
main関数はこんな感じになります。
main: pushl %ebp movl %esp, %ebp movl $0, %eax popl %ebp ret
最初にEBPレジスタをスタックに積んでいますが、ここは呼び出し元のアドレスを退避するためです。
終了するときにはpopl %ebpでEBPレジスタから読み出して終了することからも読み出し元へEBPレジスタの位置を教えていることが分かります。
次に、ESPレジスタをEBPレジスタにセットしています。
この時点でmain関数で使用するスタックベースがEBPレジスタにセットされます。
最終的には戻り値はEAXレジスタにセットして終了します。
スタックフレーム*1
未確認 | 呼び出し元のアドレス |
実際に引数を渡してデバッグしてみます。
$ gcc -g -o hoge hoge.c $ gdb hoge
1 2 3と引数を渡してみます。
(gdb) break main (gdb) run 1 2 3
レジスタの状態です。
(gdb) i r eax 0xbffff974 -1073743500 ecx 0x5dea12cf 1575621327 edx 0x4 4 ebx 0x283ff4 2637812 esp 0xbffff8c8 0xbffff8c8 ebp 0xbffff8c8 0xbffff8c8 esi 0x0 0 edi 0x0 0 eip 0x80483b7 0x80483b7 <main+3> eflags 0x200246 [ PF ZF IF ID ] cs 0x73 115 ss 0x7b 123 ds 0x7b 123 es 0x7b 123 fs 0x0 0 gs 0x33 51
これを見るかぎり呼び出し元は0xbffff8c8に格納されていることがわかります。
(gdb) x $ebp+4 0xbffff8cc: 0x00144bd6
スタックフレーム
0xbffff8c8 | 呼び出し元のアドレス:0x00144bd6 |
0x00144bd6から呼ばれていますね。
次に引数はどうなっているのかを見てみましょう。
(gdb) x $ebp+8 0xbffff8d0: 0x00000004
まずは引数の数が入っています。
ここでは4つです。
1 2 3と引数を入れたのですが、なぜ4つかといいますと、最初の引数には実行ファイルのパスが入っているからです。
スタックフレーム
0xbffff8c8 | 呼び出し元のアドレス:0x00144bd6 |
0xbffff8d0 | 引数の数:4 |
一番目の引数はどうなっているのでしょうか。
(gdb) x/xw $ebp+12 0xbffff8d4: 0xbffff974 (gdb) x 0xbffff974 0xbffff974: 0xbffffae7 (gdb) x/s 0xbffffae7 0xbffffae7: "/home/hogehoge/hoge"
まずベースポインタから+12オフセットしたアドレスを参照します。
ここでは0xbffff974に一番目の引数のポインタが格納されていることがわかります。
次にアドレス0xbffff974に格納されているポインタの値を参照します。
0xbffffae7に実際の文字列が格納されていることがわかります。
そこで0xbffffae7のアドレスを参照してみると、/home/hogehoge/hogeという実行ファイルのパスが表示されました。
スタックフレーム
0xbffff8c8 | 呼び出し元のアドレス:0x00144bd6 |
0xbffff8d0 | 引数の数:4 |
0xbffff974 | 一番目の引数のポインタ:0xbffffae7 |
2〜4番目の引数も同じように確認してみます。
(gdb) x/xw $ebp+16 0xbffff8d8: 0xbffff988 (gdb) x/xw 0xbffff988 0xbffff988: 0xbffffb0e (gdb) x/s 0xbffffb0e 0xbffffb0e: "ORBIT_SOCKETDIR=/tmp/orbit-hoge"
ところが、EBP+16には引数のポインタは格納されていません。
環境変数が入っていました。
スタックフレーム
0xbffff8c8 | 呼び出し元のアドレス:0x00144bd6 |
0xbffff8d0 | 引数の数:4 |
0xbffff974 | 一番目の引数のポインタ:0xbffffae7 |
0xbffff988 | 環境変数のポインタ:0xbffff988 |
引数の2〜4番目は引数の数を参考に一番目の引数のポインタから引数分POPしてやると取り出せるようです。
(gdb)x/xw $ebp+12 x/xw $ebp+12 0xbffff8d4: 0xbffff974 (gdb) x/x 0xbffff974 0xbffff974: 0xbffffae7 (gdb) x/s 0xbffffae7 0xbffffae7: "/home/hogehoge/hoge" (gdb) 0xbffffb08: "1" (gdb) 0xbffffb0a: "2" (gdb) 0xbffffb0c: "3"
次回は、変数宣言を調査したいと思います。
参考URL
http://questionbox.jp.msn.com/qa5902352.html
http://scientia.jpn.org/index.php?%A5%B3%A5%F3%A5%D4%A5%E5%A1%BC%A5%BF%B9%D6%BA%C2%2F%A5%B9%A5%BF%A5%C3%A5%AF
http://www.c-tipsref.com/words/stackframe.html
http://hp.vector.co.jp/authors/VA014520/asmhsp/chap4.html
http://d.hatena.ne.jp/eagletmt/20080712/1215841076
http://hack.ninja-web.net/academy003-060.htm
http://hack.ninja-web.net/academy002-051.htm
http://hack.ninja-web.net/academy002-052.htm
http://hack.ninja-web.net/academy002-053.htm
*1:関数呼び出し時に呼び出し元のアドレスや引数等をまとめたもの