main関数の引数や変数等のレジスタ周り2
前回の続きで、main関数実行時のレジスタやスタックを見てみたいと思います。
今回は次のようなプログラムを作成します。
int main(int argc, char *argv[]) { int a,b,c; a=3; b=4; c=5; return 0; }
それではデバッグしてみます。
(gdb) b main (gdb) r (gdb) i r eax 0xbffff974 -1073743500 ecx 0x9aa2d435 -1700604875 edx 0x1 1 ebx 0x283ff4 2637812 esp 0xbffff8b8 0xbffff8b8 ebp 0xbffff8c8 0xbffff8c8 esi 0x0 0 edi 0x0 0 eip 0x80483ba 0x80483ba <main+6> eflags 0x200286 [ PF SF IF ID ] cs 0x73 115 ss 0x7b 123 ds 0x7b 123 es 0x7b 123 fs 0x0 0 gs 0x33 51
あれ、今回はespとebpの値が異なっています。
0xbffff8b8 | ESP スタックポインタ |
0xbffff8c8 | EBP ベースポインタ |
何やらデータが入っているようです。
(gdb) x $esp 0xbffff8b8: 0x080483fb (gdb) 0xbffff8bc: 0x00283ff4 (gdb) 0xbffff8c0: 0x080483f0 (gdb) 0xbffff8c4: 0x00000000 (gdb) 0xbffff8c8: 0xbffff948
なんでしょう。
(gdb) p &a $1 = (int *) 0xbffff8c4 (gdb) p &b $2 = (int *) 0xbffff8c0 (gdb) p &c $3 = (int *) 0xbffff8bc
実は変数宣言したa b cのアドレスはebpとespの間に確保されたのです。
ebpには呼び出し元のアドレスが格納されていますから、今現在分かっているスタックフレームは次のようになります。
0xbffff8bc | 変数c |
0xbffff8c0 | 変数b |
0xbffff8c4 | 変数a |
0xbffff8c8 | 呼び出し元のアドレス:0xbffff948 |
それでは実際に値が入るところまで確認します。
(gdb) n (gdb) (gdb) (gdb) x $ebp-4 0xbffff8c4: 0x00000003 (gdb) x $ebp-8 0xbffff8c0: 0x00000004 (gdb) x $ebp-12 0xbffff8bc: 0x00000005
関数を呼びだした時のスタックフレームの状態
だいぶ理解できてきました。
さらにこんなプログラムを作成してみます。
int foo(int d) { int e,f; e=6; f=d; return 0; } int main(int argc, char *argv[]) { int a,b,c; a=3; b=4; c=5; foo(b); return 0; }
main部分を見てみましょう。
(gdb) b main (gdb) r (gdb) i r eax 0xbffff974 -1073743500 ecx 0xd194ba49 -778782135 edx 0x1 1 ebx 0x283ff4 2637812 esp 0xbffff8b4 0xbffff8b4 ebp 0xbffff8c8 0xbffff8c8 esi 0x0 0 edi 0x0 0 eip 0x80483d4 0x80483d4 <main+6> eflags 0x200286 [ PF SF IF ID ] cs 0x73 115 ss 0x7b 123 ds 0x7b 123 es 0x7b 123 fs 0x0 0 gs 0x33 51
espがmain関数だけだったときと違っています。
main関数だけのときはespは0xbffff8b8でした。
今回は0xbffff8b4です。
1つ余分にスタックに積まれているようです。
(gdb) p &a $1 = (int *) 0xbffff8c4 (gdb) p &b $2 = (int *) 0xbffff8c0 (gdb) p &c $3 = (int *) 0xbffff8bc
変数の位置は同じです。
0xbffff8b8 | ??? |
0xbffff8bc | 変数c |
0xbffff8c0 | 変数b |
0xbffff8c4 | 変数a |
0xbffff8c8 | 呼び出し元のアドレス:0xbffff948 |
ということは0xbffff8b8に何か入っているということですね。
(gdb) x 0xbffff8b8 0xbffff8b8: 0x0804841b
これはなんの値なのか不明でした。
(gdb) s (gdb) (gdb) (gdb) disassemble Dump of assembler code for function main: 0x080483ce <+0>: push %ebp 0x080483cf <+1>: mov %esp,%ebp 0x080483d1 <+3>: sub $0x14,%esp 0x080483d4 <+6>: movl $0x3,-0x4(%ebp) 0x080483db <+13>: movl $0x4,-0x8(%ebp) 0x080483e2 <+20>: movl $0x5,-0xc(%ebp) => 0x080483e9 <+27>: mov -0x8(%ebp),%eax 0x080483ec <+30>: mov %eax,(%esp) 0x080483ef <+33>: call 0x80483b4 <foo> 0x080483f4 <+38>: mov $0x0,%eax 0x080483f9 <+43>: leave 0x080483fa <+44>: ret End of assembler dump.
変数bをeaxにセットし、eaxからespのアドレスへセットしているようです。
(gdb) x $ebp 0xbffff8ac: 0xbffff8c8 (gdb) x $ebp+4 0xbffff8b0: 0x080483f4 (gdb) x $ebp+8 0xbffff8b4: 0x00000004 (gdb) p &e $1 = (int *) 0xbffff8a8 (gdb) p &f $2 = (int *) 0xbffff8a4
一気に見ましたが、こんな感じのスタックフレームとなりました。
0xbffff8a4 | 変数f |
0xbffff8a8 | 変数e |
0xbffff8ac | 呼び出し元 |
0xbffff8b0 | foo終了後の次のアドレス |
0xbffff8b4 | 変数bの値 |
0xbffff8b8 | ??? |
0xbffff8bc | 変数c |
0xbffff8c0 | 変数b |
0xbffff8c4 | 変数a |
0xbffff8c8 | 呼び出し元のアドレス:0xbffff948 |
これで、スタックフレームについてだいぶ分かってきましたが、
新たな問題が発生しました。
前回掲載したこのコードですが
int main(int argc, char *argv[]) { return 0; }
アセンブラを出力するとこうなります。
main: pushl %ebp movl %esp, %ebp movl $0, %eax popl %ebp ret
ところが、べつの環境でアセンブラを出力するとこうなるのです。
main: leal 4(%esp), %ecx andl $-16, %esp pushl -4(%ecx) xorl %eax, %eax pushl %ebp movl %esp, %ebp pushl %ecx popl %ecx popl %ebp leal -4(%ecx), %esp ret
ちなみに前者は
gcc version 4.4.3 (Ubuntu 4.4.3-4ubuntu5)
でコンパイルしていますが、
後者は
gcc version 4.3.3 (Ubuntu 4.3.3-5ubuntu4)
とバージョンが違います。
CPUも前者は
Intel(R) Atom(TM) CPU N270 @ 1.60GHz
であるのにたいし、
後者は
Intel(R) Celeron(R) CPU 2.40GHz
です。
アーキテクチャはどちらもi686なのですが、なんででしょうかね。
アセンブラコードが違いますし、スタックフレームの作られ方も違ってきます。
悩ましいことです。