その時々

その時々で違うんです。特に決まっていないんです。

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なのですが、なんででしょうかね。
アセンブラコードが違いますし、スタックフレームの作られ方も違ってきます。
悩ましいことです。