金沢行きの終電を逃したので。
アセンブラにドキドキしてみた。
とりあえず
こんなコードを拾ってきた。これで飯三杯は食える
# sample000.s .text .globl _main _main: movl $0, %eax ret
動かしてみる
$ gcc -g sample000.s -o sample000 && gdb ./sample000 (gdb) run Starting program: /Users/amachang/projects/lang/assembler/sample000 Reading symbols for shared libraries ++. done Program exited normally. (gdb)
なんか動いたみたい
ブレークしてみる
(gdb) break main Breakpoint 1 at 0x1ffa: file sample000.s, line 5. (gdb) run Starting program: /Users/amachang/projects/lang/assembler/sample000 Breakpoint 1, _main () at sample000.s:5 5 movl $0, %eax (gdb)
おお。止まった。
register の値を見てみる。(ちなみに regist という英語はないらしい)
(gdb) info register eax 0x0 0 ecx 0xbffff914 -1073743596 edx 0x0 0 ebx 0xbffff978 -1073743496 esp 0xbffff8ec 0xbffff8ec ebp 0xbffff90c 0xbffff90c esi 0x0 0 edi 0x0 0 eip 0x1ffa 0x1ffa <_main> eflags 0x246 582 cs 0x17 23 ss 0x1f 31 ds 0x1f 31 es 0x1f 31 fs 0x0 0 gs 0x37 55 Current language: auto; currently asm (gdb)
やべwww 意味不www
step して register の値を見てみる
(gdb) step 6 ret (gdb) info register eax 0x0 0 ecx 0xbffff914 -1073743596 edx 0x0 0 ebx 0xbffff978 -1073743496 esp 0xbffff8ec 0xbffff8ec ebp 0xbffff90c 0xbffff90c esi 0x0 0 edi 0x0 0 eip 0x1fff 0x1fff <_main+5> eflags 0x246 582 cs 0x17 23 ss 0x1f 31 ds 0x1f 31 es 0x1f 31 fs 0x0 0 gs 0x37 55 (gdb)
eip の値が 0x1fa から 0x1ff になった。 これが今実行中の場所を表す何かということか。
_main が _main+5 になっている。へー。そういうことか。
movl $0 %eax は 5 バイトの処理ということがわかる。
step してみる
(gdb) step 0x00001fce in start ()
なんか、 start っていう関数に戻ってきた。
神の領域に入ってきたみたいな気分だ
とりあえず、 next させまくって終了した。なんかいろいろなことが行われているんだなあ。
ちょっとトイレいってくる
戻ってきた。
スタックポインタを見てみたい。
_main から _main を呼び出してどうなっているか見てみる
# sample001.s .text .globl _main _main: call _main
こんな感じ?
無限ループだから、そのまま実行しないように注意しながら実行
$ gcc -g sample001.s -o sample001 && gdb ./sample001 (gdb) break main Breakpoint 1 at 0x1ffa: file sample001.s, line 4. (gdb) run Starting program: /Users/amachang/projects/lang/assembler/sample001 Reading symbols for shared libraries ++. done Breakpoint 1, _main () at sample001.s:4 4 call _main (gdb) info register eax 0x0 0 ecx 0xbffff914 -1073743596 edx 0x0 0 ebx 0xbffff978 -1073743496 esp 0xbffff8ec 0xbffff8ec ebp 0xbffff90c 0xbffff90c esi 0x0 0 edi 0x0 0 eip 0x1ffa 0x1ffa <_main> eflags 0x246 582 cs 0x17 23 ss 0x1f 31 ds 0x1f 31 es 0x1f 31 fs 0x0 0 gs 0x37 55 Current language: auto; currently asm
ここで、もう一回 main を呼び出す
(gdb) step Breakpoint 1, _main () at sample001.s:4 4 call _main (gdb) info register eax 0x0 0 ecx 0xbffff914 -1073743596 edx 0x0 0 ebx 0xbffff978 -1073743496 esp 0xbffff8e8 0xbffff8e8 ebp 0xbffff90c 0xbffff90c esi 0x0 0 edi 0x0 0 eip 0x1ffa 0x1ffa <_main> eflags 0x246 582 cs 0x17 23 ss 0x1f 31 ds 0x1f 31 es 0x1f 31 fs 0x0 0 gs 0x37 55
ここで値が変わったのが esp だけ、スタックのポインタか。
スタックのポインタってスタックにデータを積むと減るんですね。
ret はただ単に jump して 4 バイトの何かをスタックに積むだけの命令なんだ。
もっといろいろやるのかと思ってた。(やってるかもしれないけど)
スタックに何が入っているかを見てみる。
あ、でもレジスタの値をどうやって print から使うかわかんねwww
直接アドレスを指定してみる
(gdb) print *(void**) 0xbffff8e8 $1 = (void *) 0x1fff
おお。 sample000.s のときに確認した eip の値、 _main+5 の位置が入ってるんだ。
ってことは call main も 5 バイト命令で、 call main の次の行を指してるんだ。
じゃあ、 ret はそこに戻るためのものか。
次は、別の関数を呼び出すプログラムを書いてみる。
.text .globl _sub _sub ret .globl _main _main: call _sub movl $0, %eap ret
こんな感じ
$ gcc -g sample002.s -o sample002 && gdb ./sample002 (gdb) break main Breakpoint 1 at 0x1ff3: file sample002.s, line 7. (gdb) run Starting program: /Users/amachang/projects/lang/assembler/sample002 Reading symbols for shared libraries ++. done Breakpoint 1, _main () at sample002.s:7 7 call _sub (gdb) info register eax 0x0 0 ecx 0xbffff914 -1073743596 edx 0x0 0 ebx 0xbffff978 -1073743496 esp 0xbffff8ec 0xbffff8ec ebp 0xbffff90c 0xbffff90c esi 0x0 0 edi 0x0 0 eip 0x1ff3 0x1ff3 <_main> eflags 0x246 582 cs 0x17 23 ss 0x1f 31 ds 0x1f 31 es 0x1f 31 fs 0x0 0 gs 0x37 55 Current language: auto; currently asm (gdb) step _sub () at sample002.s:4 4 ret (gdb) info register eax 0x0 0 ecx 0xbffff914 -1073743596 edx 0x0 0 ebx 0xbffff978 -1073743496 esp 0xbffff8e8 0xbffff8e8 ebp 0xbffff90c 0xbffff90c esi 0x0 0 edi 0x0 0 eip 0x1ff2 0x1ff2 <_sub> eflags 0x246 582 cs 0x17 23 ss 0x1f 31 ds 0x1f 31 es 0x1f 31 fs 0x0 0 gs 0x37 55 (gdb)
お。 _main にいるときと _sub にいるときの eip の差が 1 ということは ret は 1 バイトの命令なんだー。
で、 step して ret の挙動を見る
(gdb) step _main () at sample002.s:8 8 movl $0, %eax (gdb) info register eax 0x0 0 ecx 0xbffff914 -1073743596 edx 0x0 0 ebx 0xbffff978 -1073743496 esp 0xbffff8ec 0xbffff8ec ebp 0xbffff90c 0xbffff90c esi 0x0 0 edi 0x0 0 eip 0x1ff8 0x1ff8 <_main+5> eflags 0x246 582 cs 0x17 23 ss 0x1f 31 ds 0x1f 31 es 0x1f 31 fs 0x0 0 gs 0x37 55 (gdb)
あああああああwやべw 前のスタックの値を調べるの忘れた><
もっかい戻って調べる
(gdb) kill Kill the program being debugged? (y or n) y (gdb) run Starting program: /Users/amachang/projects/lang/assembler/sample002 Breakpoint 1, _main () at sample002.s:7 7 call _sub (gdb) step _sub () at sample002.s:4 4 ret (gdb) print *(void**) 0xbffff8e8 $1 = (void *) 0x1ff8
おおお。 0x1ff8 つまり、 ret は movl $0, %eax に等しい。
で、 %eip と %esp の値しか変わっていないということから、 ret はスタックの一番上の値をポップしてジャンプするだけだと分かる。
じゃあ、 ret の部分をポップとジャンプにしても同じ挙動をするかをためしてみる。
ジャンプとポップをどう書けばいいか調べてくる!
調べ中
http://developer.apple.com/documentation/DeveloperTools/Reference/Assembler/ASMIntroduction/chapter_1_section_1.html
はあはあ、やっと調べた
こんな感じ?
.text .globl _sub _sub: popl %ebx jmp (%ebx) .globl _main _main: call _sub movl $0, %eax ret
実行してみる
$ gcc -g sample003.s -o sample003 && gdb ./sample003 (gdb) run Starting program: /Users/amachang/projects/lang/assembler/sample003 Reading symbols for shared libraries ++. done Program received signal EXC_BAD_ACCESS, Could not access memory. Reason: KERN_PROTECTION_FAILURE at address: 0x000000b8 0x000000b8 in ?? () (gdb)
うはwwっw 落ちたよww
$ gcc -g sample003.s -o sample003 && gdb ./sample003 (gdb) break main Breakpoint 1 at 0x1ff5: file sample003.s, line 8. (gdb) run Starting program: /Users/amachang/projects/lang/assembler/sample003 Reading symbols for shared libraries ++. done Breakpoint 1, _main () at sample003.s:8 8 call _sub (gdb) step Current language: auto; currently asm _sub () at sample003.s:4 4 popl %ebx (gdb) info register eax 0x0 0 ecx 0xbffff914 -1073743596 edx 0x0 0 ebx 0xbffff978 -1073743496 esp 0xbffff8e8 0xbffff8e8 ebp 0xbffff90c 0xbffff90c esi 0x0 0 edi 0x0 0 eip 0x1ff2 0x1ff2 <_sub> eflags 0x246 582 cs 0x17 23 ss 0x1f 31 ds 0x1f 31 es 0x1f 31 fs 0x0 0 gs 0x37 55 (gdb) step _sub () at sample003.s:5 5 jmp (%ebx) (gdb) info register eax 0x0 0 ecx 0xbffff914 -1073743596 edx 0x0 0 ebx 0x1ffa 8186 esp 0xbffff8ec 0xbffff8ec ebp 0xbffff90c 0xbffff90c esi 0x0 0 edi 0x0 0 eip 0x1ff3 0x1ff3 <_sub+1> eflags 0x246 582 cs 0x17 23 ss 0x1f 31 ds 0x1f 31 es 0x1f 31 fs 0x0 0 gs 0x37 55 (gdb) step Program received signal EXC_BAD_ACCESS, Could not access memory. Reason: KERN_PROTECTION_FAILURE at address: 0x000000b8 0x000000b8 in ?? () (gdb)
0xb8 に飛ぼうとしてる。。 jmp のところの括弧がいらないんだな。
括弧はメモリを参照するためのものか
.text .globl _sub _sub: popl %ebx jmp %ebx .globl _main _main: call _sub movl $0, %eax ret
直して実行!
$ gcc -g sample003.s -o sample003 && gdb ./sample003 (gdb) break main Breakpoint 1 at 0x1ff5: file sample003.s, line 8. (gdb) run Starting program: /Users/amachang/projects/lang/assembler/sample003 Reading symbols for shared libraries ++. done Breakpoint 1, _main () at sample003.s:8 8 call _sub (gdb) step Current language: auto; currently asm _sub () at sample003.s:4 4 popl %ebx (gdb) step _sub () at sample003.s:5 5 jmp %ebx (gdb) step _main () at sample003.s:9 9 movl $0, %eax (gdb) step 10 ret (gdb) step 0x00001fc6 in start () (gdb)
おお!動いたよ! ret の動きは ok
これでスタックと call 、 ret を理解できたからプログラムっぽいものを作ってみる
そんなときは
みんな大好きフィボナッチ!
まず足し算が分からない。。。
ここを見ると普通に式が書けるみたいだ。すげー addl とかじゃないんだー。
http://developer.apple.com/documentation/DeveloperTools/Reference/Assembler/ASMSyntax/chapter_3_section_3.html#//apple_ref/doc/uid/TP30000821-TPXREF114
1 + 1 をして %eax に入れる _sub 関数を作ってみた。
1 + 1 は $ 1 + 1 でできた。
その後、変数に入れたい気分になったけどここはアセンブラの世界だから、変数などないのだった。
.text .globl _sub _sub: movl $1 + 1, %eax ret .globl _main _main: call _sub movl $0, %eax ret
実行
$ gcc -g sample004.s -o sample004 && gdb ./sample004 (gdb) break main Breakpoint 1 at 0x1ff4: file sample004.s, line 8. (gdb) run Starting program: /Users/amachang/projects/lang/assembler/sample004 Reading symbols for shared libraries ++. done Breakpoint 1, _main () at sample004.s:8 8 call _sub (gdb) step Current language: auto; currently asm _sub () at sample004.s:4 4 movl $1 + 1, %eax (gdb) step 5 ret (gdb) step _main () at sample004.s:9 9 movl $0, %eax (gdb) info register eax 0x2 2 ecx 0xbffff914 -1073743596 edx 0x0 0 ebx 0xbffff978 -1073743496 esp 0xbffff8ec 0xbffff8ec ebp 0xbffff90c 0xbffff90c esi 0x0 0 edi 0x0 0 eip 0x1ff9 0x1ff9 <_main+5> eflags 0x246 582 cs 0x17 23 ss 0x1f 31 ds 0x1f 31 es 0x1f 31 fs 0x0 0 gs 0x37 55 (gdb)
おお %eax に 2 が入ってる。 1 + 1 は 2 だ!
でも、この movl $ 1 + 1, %eax って、実際はすごいたくさんの命令が含まれてるんだろうなあ。
というわけで何バイトあるか見てみる
$ gcc -g sample004.s -o sample004 && gdb ./sample004 (gdb) break main Breakpoint 1 at 0x1ff4: file sample004.s, line 8. (gdb) run Starting program: /Users/amachang/projects/lang/assembler/sample004 Reading symbols for shared libraries ++. done Breakpoint 1, _main () at sample004.s:8 8 call _sub (gdb) step Current language: auto; currently asm _sub () at sample004.s:4 4 movl $1 + 1, %eax (gdb) info register eax 0x0 0 ecx 0xbffff914 -1073743596 edx 0x0 0 ebx 0xbffff978 -1073743496 esp 0xbffff8e8 0xbffff8e8 ebp 0xbffff90c 0xbffff90c esi 0x0 0 edi 0x0 0 eip 0x1fee 0x1fee <_sub> eflags 0x246 582 cs 0x17 23 ss 0x1f 31 ds 0x1f 31 es 0x1f 31 fs 0x0 0 gs 0x37 55 (gdb) step 5 ret (gdb) info register eax 0x2 2 ecx 0xbffff914 -1073743596 edx 0x0 0 ebx 0xbffff978 -1073743496 esp 0xbffff8e8 0xbffff8e8 ebp 0xbffff90c 0xbffff90c esi 0x0 0 edi 0x0 0 eip 0x1ff3 0x1ff3 <_sub+5> eflags 0x246 582 cs 0x17 23 ss 0x1f 31 ds 0x1f 31 es 0x1f 31 fs 0x0 0 gs 0x37 55 (gdb)
%eip の _sub が _sub+5 になってる。ってことは 5 バイトでいいの?
ってことは、これ実際は先に計算されて movl $2, %eax として実行されてんじゃん><
足し算したのは、コンパイラでした。。。というオチ。
うむむ。。
よく仕様を読んでみると定数しか式にできないみたいだなあ
というわけで、もっかい足し算をするプログラムを書いてみる
.text .globl _sub _sub: popl %eax popl %ebx add %ebx, %eax ret .globl _main _main: pushl $1 pushl $1 call _sub movl $0, %eax ret
これを実行してみる
$ gcc -g sample005.s -o sample005 && gdb ./sample005 (gdb) break main Breakpoint 1 at 0x1fef: file sample005.s, line 10. (gdb) run Starting program: /Users/amachang/projects/lang/assembler/sample005 Reading symbols for shared libraries ++. done Breakpoint 1, _main () at sample005.s:10 10 pushl $1 (gdb) step Current language: auto; currently asm _main () at sample005.s:11 11 pushl $1 (gdb) step _main () at sample005.s:12 12 call _sub (gdb) step _sub () at sample005.s:4 4 popl %eax (gdb) step _sub () at sample005.s:5 5 popl %ebx (gdb) step _sub () at sample005.s:6 6 movl %ebx, %eax (gdb) info register eax 0x1ff8 8184 ecx 0xbffff914 -1073743596 edx 0x0 0 ebx 0x1 1 esp 0xbffff8e8 0xbffff8e8 ebp 0xbffff90c 0xbffff90c esi 0x0 0 edi 0x0 0 eip 0x1fec 0x1fec <_sub+2> eflags 0x246 582 cs 0x17 23 ss 0x1f 31 ds 0x1f 31 es 0x1f 31 fs 0x0 0 gs 0x37 55 (gdb)
あれれ?
1 を push して 1 を push して call したんだから、このコードだと 0x1ff8 (本来の戻り番地) + 1 になってしまうのか><
しかも戻り番地が 0x1 という場所になってしまう
ということは、先に戻り番地を pop してから、引数を pop して計算して、また戻り番地を push して ret すればいいのかな?
めんどくさあああ
とりあえず、こんな感じになるのかな。
.text .globl _sub _sub: popl %edx # 戻り番地を退避 popl %eax popl %ebx add %ebx, %eax pushl %edx # 戻り番地を設定 ret .globl _main _main: pushl $1 pushl $1 call _sub movl $0, %eax ret
実行してみる
$ gcc -g sample005.s -o sample005 && gdb ./sample005 (gdb) break main Breakpoint 1 at 0x1ff1: file sample005.s, line 12. (gdb) run Starting program: /Users/amachang/projects/lang/assembler/sample005 Reading symbols for shared libraries ++. done Breakpoint 1, _main () at sample005.s:12 12 pushl $1 (gdb) step Current language: auto; currently asm _main () at sample005.s:13 13 pushl $1 (gdb) step _main () at sample005.s:14 14 call _sub (gdb) step _sub () at sample005.s:4 4 popl %edx # 戻り番地を退避 (gdb) step _sub () at sample005.s:5 5 popl %eax (gdb) step _sub () at sample005.s:6 6 popl %ebx (gdb) step _sub () at sample005.s:7 7 add %ebx, %eax (gdb) step 8 pushl %edx # 戻り番地を設定 (gdb) step _sub () at sample005.s:9 9 ret (gdb) step _main () at sample005.s:15 15 movl $0, %eax (gdb) info register eax 0x2 2 ecx 0xbffff914 -1073743596 edx 0x1ffa 8186 ebx 0x1 1 esp 0xbffff8ec 0xbffff8ec ebp 0xbffff90c 0xbffff90c esi 0x0 0 edi 0x0 0 eip 0x1ffa 0x1ffa <_main+9> eflags 0x202 514 cs 0x17 23 ss 0x1f 31 ds 0x1f 31 es 0x1f 31 fs 0x0 0 gs 0x37 55 (gdb)
おおおお。 %eax に 2 が入ってるよ!やった 1 + 1 ができたよ!クララが立ったよ!
ふと思った、この _sub って C 言語からも呼べるのかな?
C 言語では引数はどうやって渡すんだろう。
というわけで、 main を C 言語のほうで実装した
# sample006.s .text .globl _sub _sub: popl %edx # 戻り番地を退避 popl %eax popl %ebx add %ebx, %eax pushl %edx # 戻り番地を設定 ret
/* sample006.c */ int sub(int, int); int main() { sub(1, 2); return 0; }
実行してみる
$ gcc -g sample006.s sample006.c -o sample006 && gdb ./sample006 (gdb) break main Breakpoint 1 at 0x1fe3 (gdb) run Starting program: /Users/amachang/projects/lang/assembler/sample006 Reading symbols for shared libraries ++. done Breakpoint 1, 0x00001fe3 in main () (gdb) step Single stepping until exit from function main, which has no line number information. _sub () at sample006.s:4 4 popl %edx # 戻り番地を退避 (gdb) step Current language: auto; currently asm _sub () at sample006.s:5 5 popl %eax (gdb) step _sub () at sample006.s:6 6 popl %ebx (gdb) step _sub () at sample006.s:7 7 add %ebx, %eax (gdb) step 8 pushl %edx # 戻り番地を設定 (gdb) step _sub () at sample006.s:9 9 ret (gdb) info register eax 0x3 3 ecx 0xbffff914 -1073743596 edx 0x1ff7 8183 ebx 0x2 2 esp 0xbffff8d4 0xbffff8d4 ebp 0xbffff8e8 0xbffff8e8 esi 0x0 0 edi 0x0 0 eip 0x1fdc 0x1fdc <_sub+6> eflags 0x206 518 cs 0x17 23 ss 0x1f 31 ds 0x1f 31 es 0x1f 31 fs 0x0 0 gs 0x37 55 (gdb) print *(void**) 0xbffff8d4 $1 = (void *) 0x1ff7 (gdb) step 0x00001ff7 in main () (gdb)
おお、ちゃんと 1 + 2 の結果が %eax にの結果が入った!
で、ちゃんと ret も動いてるっぽい
引数は (1, 2) だと pushl $2 、 pushl $1 の順で push されるみたいだ忘れそうだから覚えておこう。
C 言語もただ単に引数を push して call してるだけだと分かった!
足し算ができたらあとは、条件分岐だなあ。
ちょっと、条件付きジャンプ命令について調べてくる!
とりあえず、フィボナッチ数を求める関数を書いてみる!
.text .globl _sub _sub: popl %edx popl %ecx cmp $2, %ecx ja sub_a movl $1, %eax pushl %edx jmp sub_return sub_a: pushl %edx dec %ecx pushl %ecx dec %ecx pushl %ecx call _sub popl %ecx pushl %eax pushl %ecx call _sub popl %ecx add %ecx, %eax sub_return: ret .globl _main _main: pushl $6 call _sub ret
はあはあ、
でも、実行してみたら違う値がかえってくる><
だめだああ。しかも全然デバッグできるコードじゃねえ!
スパゲッティだ><
ちょっと一旦フィボナッチ数はあきらめよう><
今の知識だとどう書いても訳の分からないコードになってしまう><!
アセンブラのスパゲッティにならない書き方を調べる
ちょっと、サンプルコードを探してくる!
Google Code Search ++
http://google.com/codesearch?hl=en&lr=&q=file%3A%5C.s%24+%22.globl+_main%22&btnG=Search
値に名前を付ける方法を知った。
これは使える
http://developer.apple.com/documentation/DeveloperTools/Reference/Assembler/ASMLayout/chapter_4_section_6.html#//apple_ref/doc/uid/TP30000822-TPXREF111
あと、よく leal っていう命令が使われてるなあ。どんな命令なんだろう。調べてみよう
leal について Load Effective Address と説明してあるがさっぱり分からん。
ちょっとググってみたら
http://alohakun.blog7.fc2.com/blog-entry-422.html
alohakun さんのページに詳しく書いてあった。
というか、まずこのページを一読するべき。今から読みます。
いろいろ分かったこと
# メモリの値の代入 movl (%ebp), %eax # メモリの値の代入(インデックス付き) movl 4(%ebp), %eax # たぶん、以下と %eax は同じなる? add $4, %ebp movl (%ebp), %eax # メモリのアドレスの代入 leal 4(%ebp), %eax # たぶん、以下と %eax は同じになる? add $4, %ebp movl %ebp, %eax
つまり、 leal は足し算の代入ってこと。
leal (%edx,%eax), %ecx
とすれば %edx + %eax の値が %ecx に入るらしい。
alohakun さん勉強になりました!ありがとうございました!
ところで (%abp, $4) と 4(%ebp) はだいたい同じ意味なんでしょうかね。
おおお。ここに仕様があった!
http://developer.apple.com/documentation/DeveloperTools/Reference/Assembler/i386Instructions/chapter_7_section_3.html#//apple_ref/doc/uid/TP30000825-TPXREF109
あああ。なるほど
よく読むと leal は足し算というよりは、アドレスを求める演算をした結果だけを使うのものか。
で、アドレス求めた結果からアドレスの中の値を使うのが movl ってことか。
ひょっとして、 load ってアドレスのコピーで、ムーブが値のコピーって意味なのかな。違うかな。まあ、わからん。
なるほどなるほど。
アドレスを求めるためのオペランドは以下のように定義されている。
displacement(base_register,index_register,scale)
以下のコードを書いてみて ecx が何になるかを見てみる。
.text .globl _main _main: movl $16, %eax movl $32, %ebx leal 8(%eax, %ebx, 4), %ecx ret
$ gcc -g sample008.s -o sample008 && gdb ./sample008 (gdb) break main Breakpoint 1 at 0x1fee: file sample008.s, line 4. (gdb) run Starting program: /Users/amachang/projects/lang/assembler/sample008 Reading symbols for shared libraries ++. done Breakpoint 1, _main () at sample008.s:4 4 movl $16, %eax (gdb) step Current language: auto; currently asm 5 movl $32, %ebx (gdb) step 6 leal 8(%eax, %ebx, 4), %ecx (gdb) step 7 ret (gdb) info register eax 0x10 16 ecx 0x98 152 edx 0x0 0 ebx 0x20 32 esp 0xbffff8ec 0xbffff8ec ebp 0xbffff90c 0xbffff90c esi 0x0 0 edi 0x0 0 eip 0x1ffc 0x1ffc <_main+14> eflags 0x246 582 cs 0x17 23 ss 0x1f 31 ds 0x1f 31 es 0x1f 31 fs 0x0 0 gs 0x37 55 (gdb)
152 か。
ということは、 8 + %eax + (%ebx * 4) でいいのかな 8 + 16 + 128 = 152 だもんね。
一応、別の値で確認しとこう。
何が 4 倍されるのか分かりやすいように、 1 と 10 と 100 でやってみる。
.text .globl _main _main: movl $1, %eax movl $10, %ebx leal 100(%eax, %ebx, 4), %ecx ret
$ gcc -g sample009.s -o sample009 && gdb ./sample009 (gdb) break main Breakpoint 1 at 0x1fee: file sample009.s, line 4. (gdb) run Starting program: /Users/amachang/projects/lang/assembler/sample009 Reading symbols for shared libraries ++. done Breakpoint 1, _main () at sample009.s:4 4 movl $1, %eax (gdb) step Current language: auto; currently asm 5 movl $10, %ebx (gdb) step 6 leal 100(%eax, %ebx, 4), %ecx (gdb) step 7 ret (gdb) info register eax 0x1 1 ecx 0x8d 141 edx 0x0 0 ebx 0xa 10 esp 0xbffff8ec 0xbffff8ec ebp 0xbffff90c 0xbffff90c esi 0x0 0 edi 0x0 0 eip 0x1ffc 0x1ffc <_main+14> eflags 0x246 582 cs 0x17 23 ss 0x1f 31 ds 0x1f 31 es 0x1f 31 fs 0x0 0 gs 0x37 55 (gdb)
うんやっぱりそうか
leal a(b, c, d), e は e = a + b + (c * d) ってことで FA。
あと、いろいろなコードを見てると ret の前に leave ってやってる。なんだろうこれ
これに関しても以下のエントリのコメント欄にありました
http://alohakun.blog7.fc2.com/blog-entry-422.html
あと、インテルのところにも何かいろいろありました。
Resource & Design Center for Development with Intel
leave は enter とセットなんですね。ちょっとインテルのマニュアルを読んでみます。
なんか、よく分からんけど関数の先頭で enter をして、関数の最後で leave をすれば、スタックフレームのための push とか pop とか書かなくていいってことかな?
でも、 enter はほとんど使われないらしい。そして、 Google Code Search でも使用例があまりない。
とりあえず、 leave を使ってる例を見つけたのでコピペして、 gdb で挙動をみる。
.text .globl _main _main: pushl %ebp movl %esp, %ebp subl $8, %esp movl $0, %eax leave ret
あーーー。なるほどなるほど!
%ebp にスタックポインタを保存しておいて、スタックフレーム分スタックポインタをずらして leave それを戻してるのか!
とりあえず、ここで仮眠をとる(フィボナッチの道は遠いな)
おきた。
ところで、さっきのコードって subl $8, %espしてるけど、なんで 8 なんだろう。
リターンアドレスしか保存しないんなら subl $4, %esp だけでいいよなあ。
0(%esp) と 4(%esp) には何が入っているんだろう。
ちょっと sub 関数を作ってもっかいためしてみる!
.text .globl _sub _sub: pushl %ebp movl %esp, %ebp subl $8, %esp movl $0, %eax leave ret .globl _main _main: pushl %ebp movl %esp, %ebp subl $8, %esp call _sub movl $0, %eax leave ret
$ gcc -g sample013.s -o sample013 && gdb ./sample013 (gdb) break main Breakpoint 1 at 0x1ff1: file sample013.s, line 12. (gdb) run Starting program: /Users/amachang/projects/lang/assembler/sample013 Reading symbols for shared libraries ++. done Breakpoint 1, _main () at sample013.s:15 15 call _sub (gdb) step Current language: auto; currently asm _sub () at sample013.s:4 4 pushl %ebp (gdb) step _sub () at sample013.s:5 5 movl %esp, %ebp (gdb) step 6 subl $8, %esp (gdb) step 7 movl $0, %eax (gdb) info register eax 0x0 0 ecx 0xbffff914 -1073743596 edx 0x0 0 ebx 0xbffff978 -1073743496 esp 0xbffff8d0 0xbffff8d0 ebp 0xbffff8d8 0xbffff8d8 esi 0x0 0 edi 0x0 0 eip 0x1fe4 0x1fe4 <_sub+6> eflags 0x282 642 cs 0x17 23 ss 0x1f 31 ds 0x1f 31 es 0x1f 31 fs 0x0 0 gs 0x37 55 (gdb) print *(void**) 0xbffff8d0 $1 = (void *) 0x0 (gdb) print *(void**) 0xbffff8d4 $2 = (void *) 0x0 (gdb) print *(void**) 0xbffff8d8 $3 = (void *) 0xbffff8e8 (gdb) step 8 leave (gdb) step _sub () at sample013.s:9 9 ret (gdb) info register eax 0x0 0 ecx 0xbffff914 -1073743596 edx 0x0 0 ebx 0xbffff978 -1073743496 esp 0xbffff8dc 0xbffff8dc ebp 0xbffff8e8 0xbffff8e8 esi 0x0 0 edi 0x0 0 eip 0x1fea 0x1fea <_sub+12> eflags 0x282 642 cs 0x17 23 ss 0x1f 31 ds 0x1f 31 es 0x1f 31 fs 0x0 0 gs 0x37 55 (gdb) print *(void**) 0xbffff8dc $4 = (void *) 0x1ff6 (gdb)
あれれれれ。なんか、 leave の挙動が想像と違うような。。
あ。そっか。リターンアドレスは call によって push されているんだから 自分で subl でスタックポインタをずらす必要はないのか。
もっかい調べてみると 16 byte で align するためにスタックポインタを subl してるって書いてあった。
Uli's Programming Blog
align ってなんのために必要なんだろう。
そういえば、昔ブックマークした気がする。やってて良かった「はてなブックマーク」(PR)!
データ型のアラインメントとは何か,なぜ必要なのか?
もう一回読んだ。なるほどなるほど。
「アセンブラだから速い」というわけではなくて、アラインメントのように自分で注意して意識的に書かないと速いコードを書けない場合もあるんですね。
次に、スタック上のメモリを変数として使う方法を考えてみた。
レジスタは、ダイナミックスコープの変数みたいなもので、呼び出した先の関数によって破壊されてしまう可能性がある。
なので、とにかく変数的なものはすべてスタックから辿らなければならないんだよね。(あ、でも、レジスタでやって pusha とかでもいいのか。)
ここらへんかなあ
http://developer.apple.com/documentation/DeveloperTools/Reference/Assembler/ASMLayout/chapter_4_section_6.html#//apple_ref/doc/uid/TP30000822-TPXREF111
うううう。どうもこの identifier 、ファイルスコープっぽい感じだ。
「ここからここまで、この名前」みたいなのできないかな。
名前の重複避けたかったらファイルを分けろってことかな><
変数に名前を付けた。 24 ってなってるのはスタックのアラインメントを揃えるため
変数を使って足し算関数を書き直してみる。
.text .set _sub_var_a, 8 + 0 .set _sub_var_b, 8 + 4 .globl _sub _sub: pushl %ebp movl %esp, %ebp subl $8, %esp movl _sub_var_a(%ebp), %eax movl _sub_var_b(%ebp), %ebx addl %ebx, %eax leave ret .globl _main _main: pushl %ebp movl %esp, %ebp subl $24, %esp movl $3, (%esp) movl $4, 4(%esp) call _sub movl $0, %eax leave ret
実行してみる
$ gcc -g sample013.s -o sample013 && gdb ./sample013 (gdb) break main Breakpoint 1 at 0x1fe4: file sample013.s, line 18. (gdb) run Starting program: /Users/amachang/projects/lang/assembler/sample013 Reading symbols for shared libraries ++. done Breakpoint 1, _main () at sample013.s:21 21 movl $3, (%esp) (gdb) step Current language: auto; currently asm 22 movl $4, 4(%esp) (gdb) step 23 call _sub (gdb) step _sub () at sample013.s:7 7 pushl %ebp (gdb) step _sub () at sample013.s:8 8 movl %esp, %ebp (gdb) step 9 subl $8, %esp (gdb) step 10 movl _sub_var_a(%ebp), %eax (gdb) step 11 movl _sub_var_b(%ebp), %ebx (gdb) step 12 addl %ebx, %eax (gdb) step 13 leave (gdb) step _sub () at sample013.s:14 14 ret (gdb) step _main () at sample013.s:24 24 movl $0, %eax (gdb) info register eax 0x7 7 ecx 0xbffff914 -1073743596 edx 0x0 0 ebx 0x4 4 esp 0xbffff8d0 0xbffff8d0 ebp 0xbffff8e8 0xbffff8e8 esi 0x0 0 edi 0x0 0 eip 0x1ff8 0x1ff8 <_main+26> eflags 0x202 514 cs 0x17 23 ss 0x1f 31 ds 0x1f 31 es 0x1f 31 fs 0x0 0 gs 0x37 55 (gdb)
よし、 %eax が 7 になった
もう一度、フィボナッチ数を求めるプログラムにチャレンジする
.text .set _sub_var_n, 8 + 0 .globl _sub _sub: pushl %ebp movl %esp, %ebp subl $24, %esp movl _sub_var_n(%ebp), %ebx cmp $2, %ebx ja L_sub_above movl $1, %eax jmp L_sub_return L_sub_above: dec %ebx movl %ebx, (%esp) movl %ebx, 4(%esp) call _sub movl %eax, 8(%esp) movl 4(%esp), %ebx dec %ebx movl %ebx, (%esp) call _sub addl 8(%esp), %eax L_sub_return: leave ret .globl _main _main: pushl %ebp movl %esp, %ebp subl $24, %esp movl $10, (%esp) call _sub movl $0, %eax leave ret
できた。実行してみる。
$ gcc -g sample014.s -o sample014 && gdb ./sample014 (gdb) break main Breakpoint 1 at 0x1fed: file sample014.s, line 36. (gdb) run Starting program: /Users/amachang/projects/lang/assembler/sample014 Reading symbols for shared libraries ++. done Breakpoint 1, _main () at sample014.s:39 39 movl $10, (%esp) (gdb) next Current language: auto; currently asm 40 call _sub (gdb) next 41 movl $0, %eax (gdb) info register eax 0x37 55 ecx 0xbffff914 -1073743596 edx 0x0 0 ebx 0x2 2 esp 0xbffff8d0 0xbffff8d0 ebp 0xbffff8e8 0xbffff8e8 esi 0x0 0 edi 0x0 0 eip 0x1ff9 0x1ff9 <_main+18> eflags 0x202 514 cs 0x17 23 ss 0x1f 31 ds 0x1f 31 es 0x1f 31 fs 0x0 0 gs 0x37 55 (gdb)
やったーーーーー!できた!!
10 を入れたら、ちゃんと %eax に 55 がかえってきたよ!