なぜx86
大学の機械語序論という授業でx86(x86_64ではない)でアセンブラを書いています。
x86のアドレッシングモードが難しかったので復習を兼ねてまとめます。
やりたかったこと
授業でfib(10)
を求めてebx
に格納する課題がありました。
方針としては、aというシンボルのデータ領域を、アドレッシングモードを使って配列のように扱い、 フィボナッチ数列を計算することにしました。
わかりやすくRubyで書くとこんな方針です。
# fib.rb
a = [1, 1, 0, 0, 0, 0, 0, 0, 0, 0]
(0..8).each {|i| a[i+2] = a[i+1] + a[i]}
puts a[10]
アセンブラでa[i+2]
やa[i+1]
を参照する必要があったので、
アドレッシングモードを利用したいと思ったわけです。
x86のアドレッシングモード
授業の資料によると、x86 では、以下のアドレッシングができるそうです。
BASE+(INDEX*SCALE)+DISPLACEMENT
- DISPLACEMENT: アドレス、もしくはベースレジスタからのオフセットを示す。定数。
- BASE: ベースレジスタ
- INDEX: インデックスレジスタ、配列の添え字のようなもの
- SCALE: 2,4,8 のいずれか。配列要素のサイズを指定する。
そして、これを次のように記述するらしいです。
DISPLACEMENT(BASE, INDEX, SCALE)
注意点
ここがポイントなのですが、
アドレッシングモードを利用するときには必ず型を守らなくてはいけません。
特に、 BASEをレジスタにするのを忘れて エラーになるケースが多いような気がします。
コンパイラはjunk error
とか適当なエラーメッセージしか出してくれないので、
何がいけないのか分からずに苦労しました。
- DISPLACEMENTには定数を指定する。
- BASEとINDEXにはレジスタを指定する。
- DISPLACEMENTはバイト単位(long型の配列なら、添字*4を指定する)。
これらを踏まえると、%eax
にインデックス、%ecx
に配列arrayの先頭アドレスを格納したとき、
array[%eax+2]
を参照するためには、次のようにする必要がありました。
8(%ecx, %eax, 4) # array[%eax+2]
慣れている人にとっては常識かもしれませんが、 普段RubyとかC#しか書いていない人にとっては苦行でした。
また、%ecx
に配列aの先頭アドレスを格納するときには、
mov $a, %ecx
というようにaにダラーが必要なのですが、これも知らないと間違えそうです。
成果物
最終的にfib(10)
を求めてebx
に格納するx86のアセンブラのコードはこんなのになりました。
アセンブラは難しいです。
# fib-x84asm.s
.data
.align 4
a:
.long 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0
.text
.global main
main:
mov $0, %eax # ループカウンタ
mov $a, %ecx # aの先頭ポインタ
L1: # for(eax=0; eax<9; eax++)
cmp $9, %eax
je L2
mov (%ecx,%eax, 4), %ebx # ebx = a[eax]
add 4(%ecx, %eax, 4), %ebx # ebx += a[eax+1]
mov %ebx, 8(%ecx, %eax, 4) # a[eax+2] = ebx
add $1, %eax
jmp L1
L2:
call stop