IT戦記

プログラミング、起業などについて書いているプログラマーのブログです😚

Mac OS X のアセンブラをちょっと触ってみた

金沢行きの終電を逃したので。
アセンブラにドキドキしてみた。

とりあえず

こんなコードを拾ってきた。これで飯三杯は食える

# 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 がかえってきたよ!