まず、Cのソースコード(test.c)。
- #include <stdio.h>
- main()
- {
- int i, j, k;
- i = 1;
- j = 2;
- k = i + j;
- printf("%d %d %d\n", i, j, k);
- }
そして、アセンブラ(test.s)。
$ gcc -S test.c
でアセンブラのコードを出力できます。
- .file "test.c"
- .section .rodata
- .LC0:
- .string "%d %d %d\n"
- .text
- .globl main
- .type main, @function
- main:
- pushl %ebp
- movl %esp, %ebp
- andl $-16, %esp
- subl $32, %esp
- movl $1, 28(%esp)
- movl $2, 24(%esp)
- movl 24(%esp), %eax
- movl 28(%esp), %edx
- leal (%edx,%eax), %eax
- movl %eax, 20(%esp)
- movl $.LC0, %eax
- movl 20(%esp), %edx
- movl %edx, 12(%esp)
- movl 24(%esp), %edx
- movl %edx, 8(%esp)
- movl 28(%esp), %edx
- movl %edx, 4(%esp)
- movl %eax, (%esp)
- call printf
- leave
- ret
- .size main, .-main
- .ident "GCC: (Ubuntu 4.4.3-4ubuntu5) 4.4.3"
- .section .note.GNU-stack,"",@progbits
いろいろ分かることがあります。
まず、スタックのアドレスが番地の大きい方から小さいほうへと広がっているところです。これはx86マシンでは共通だと昔聞いたことがあります。
それから、andl $-16, %esp があるので、16byteのaddress alignmentを使っているようです。FFFFFFF0でマスキングして16の倍数になる次の数をスタックポインタに代入しています。これだとベースポインタとスタックポインタの整合性があわないようにみえるんですけど、結局ベースポインタ使ってないし、リターンする時ベースポインタをスタックポインタに戻す処理も省略されてるので別にいいような気もします。
意外だったのは、スタック内の変数にスタックポインタからの差分でアクセスしているところです。スタックポインタは実行時に移動するから、ベースポインタからの差分で変数にアクセスするのが普通かと思いましたが、そうでもないみたいです。
あと、最後にprintfの可変長引数のところの仕組みがおもしろいです。第一引数の%dが3つあるんで、第一引数をpopしたあと、3回popすればいいんですねー。当然といえば当然ですが、実際コード上でみるとちょっとした感動がありました。printfがcallされるときのstackの状態は下のようになっています。
0 件のコメント:
コメントを投稿