基本的なアセンブリ命令(Intel記法)
基本的なアセンブリ命令について勉強したのでアウトプットします。
前提
今回はIntel記法で書いていきます。
他にもAT&T記法があるのですが、より直感的に理解しやすい方を優先的に覚えることにしました。
一応差分を
Intel記法 mov eax, 1 AT&T記法 mov $1, %eax
基本命令
代入命令
mov dst, src
srcの値をdstにコピーします。
アセンブリではなくプログラミング言語で書くとdst = src
といったところでしょうか。
アドレス計算
lea dst, src 例: lea eax, [esp+0x10]
srcのアドレス計算を行った後にdstに値を格納するという命令です。
例で言えばespに0x10を加算した値をeaxへ格納します。
交換
xchg op1, op2
op1とop2の値を交換します。
加減算
add dest, src sub dest, src
addが加算。subが減算です。
dest = dest + src
dest = dest - src
になります。
例:add eax, 1 -> eax = eax + 1
乗算
mul src
これは加減算と違い引数が一つしかありません。
かける数はsrcに依存したAレジスタ(al, ax, eax)となります。というのも以下の表をみてください。
サイズ | 上位bit | 下位bit |
---|---|---|
8bit | AH | AL |
16bit | DX | AX |
32bit | EDX | EAX |
64bit | RDX | RAX |
例えばmul ebx
の場合、ebxは32bitレジスタです。
なのでebxにeaxをかけます。
計算の結果上位32bitをEDXへ、下位32bitをEAXへ格納します。
これがmulの計算になります。
srcのレジスタのアドレス幅によってかける値が変わるので覚えるのが大変そうです。
除算
div src
これもsrcのアドレス幅に依存するという意味では同じです。
例をあげます。
div rbx
この場合rbxは64bitなのでrbx / rax
の計算が行われます。
その後、商をrax, 剰余をrdxへ格納されます。
分岐命令
フラグレジスタ
分岐命令はフラグレジスタと呼ばれるもので判断します。
そのため先に書いておくことで分岐の仕組みが少し理解しやすくなるかもしれません。
代表的なもの(?) を取り上げます。
フラグレジスタ | bit | 名前 | 内容 |
---|---|---|---|
ZF | 6 | ゼロフラグ | 演算結果がゼロなら1が立つ |
SF | 7 | サインフラグ | 演算結果の正負(正なら0, 負なら1) |
OF | 11 | オーバーフローフラグ | 桁溢れがあれば1 |
フラグレジスタは他のレジスタと違い各bitに意味があります。
それが上記の表のbitの部分です。
後で紹介するcmpなどの演算結果を用いて各bitに値を設定していくことになります。
分岐処理はそれらの各bitを見て判断していきます。
では実際に各命令をみていきます。
比較命令
cmp op1, op2
cmpは比較を行う命令です。
少し詳しく言うとこの命令ではop1 - op2
という減算が行われます。
しかし、その結果は保持されません。結果は保持されませんが、フラグレジスタの変更は行われます。
これがsub命令とは違う点です。あくまで目的はフラグレジスタの変更となっています。
演算の結果が0であればZFに1が設定されます。
つまりop1とop2の値が同じであればゼロであることを判断するZFにbitが立つということです。
また、op1の方が大きい場合場合(op1 - op2 > 0)、負数を表すSFには0が入ります。
その逆でop2の方が大きい場合(op1 - op2 < 0)、SFには1が入ります。
c言語風に書くと以下のような形です。
if ((op1 - op2) > 0) { SF = 0 } else if ((op1 - op2) < 0) { SF = 1 } else { ZF = 1 }
あくまでイメージなので書き方が汚いのは気にしないでください...
cmpは減算処理を行ってフラグを設定しますが、実は同じようにsubでも設定することができます。
その場合演算結果は保持されますが、フラグレジスタも設定されます。
無条件分岐命令
jmp addr
これは一番シンプルな分岐命令で引数に指定したアドレスへ処理を移します。
これは条件なく分岐するため、無条件分岐命令とも呼ばれます。
条件分岐命令
条件分岐命令ではフラグレジスタを元に分岐を行っていきます。
表形式で基本的な命令を書きます。
命令 | 意味 | 内容 |
---|---|---|
JE | Jump if Equal | ZF=1のとき分岐 |
JZ | Jump if Zero | ZF=1のとき分岐 |
JNE | Jump if Not Equal | ZF=0の時分岐 |
JNZ | Jump if Not Zero | ZF=0の時分岐 |
JG | Jump if Grater | ZF=0 && SF=OF |
JL | Jump if Less | SF != OF |
JE, JZ
ZF = 1のときとはどういうときか。
それはcmp op1, op2
という命令があったときop1とop2の値が同じ時です。
op1 - op2 = 0
のときZFにはビットが立つという話は比較命令のときにもしましたので、振り返ってもらえれば良いと思います。
JNE, JNZはその逆でZF=0のときに分岐するのでop1, op2の値が違った場合に分岐します。
JG
ZF=0 かつ SF=OFとはどういう場合か。
簡単にいえば演算結果が負の数ではない、かつオーバーフローを起こしていないということになります。
op1 > op2 のときと言ってよいと思います。
JL
これはJGの逆で演算結果が負数で、かつオーバーフローを起こしていないときということになります。
条件分岐を簡易言語で書く
if (op1 == op2) (JE|JZ)処理 if (op1 > op2) (JG|JNE|JNZ)処理 if (op1 < op2) (JL|JNE|JZE)処理
他言語風に書くとこんな感じでしょうか。
まとめ
今回の記事ではpush, pop, call, retなどの重要な命令が書いてありません。
それには理由があって、次に各記事で関数呼び出し処理と照らし合わせながらまとめていこうと考えているからです。
アドバイス・補足等ありましたらTwitterまでお願いします。