アセンブリ言語の勉強2

ゆっくりとアセンブラの勉強再開。

乗算、除算

乗算の場合は8ビット演算の場合、AXに結果が格納され
16ビット演算の場合、下位AX、上位DXなので、
演算前にAX,DXを共に初期化してやれば結果的に同じになりそう。


しかし除算の場合はそうは行かない。
除算は
8ビット演算の場合、商がALレジスタに、余りがAHレジスタに格納される。
16ビット演算の場合、商がAXレジスタに、余りがDXレジスタに格納される。
これはややこしい。


この辺って32ビットCPU、64ビットCPUになった時、
8,16,32ビットそれぞれの演算結果はどう扱うんだろう?
とりあえず今は触れないでおこう。。。

ポインタ

レジスタを[]で囲む事でポインタとしてレジスタの値を扱える。
つまりBXレジスタに200という値が入っていたとすると、メモリの200番地の”値”を参照する

-a 100
2D6D:0100 mov bx,200
2D6D:0103 mov byte ptr [bx], 20
2D6D:0106 mov ax, 10
2D6D:0109 add ax, [bx]
2D6D:010B
-g =100 10B

AX=0030  BX=0200  CX=0000  DX=0010  SP=FFEE  BP=0000  SI=0000  DI=0000
DS=2D6D  ES=2D6D  SS=2D6D  CS=2D6D  IP=010B   NV UP EI PL NZ NA PE NC
2D6D:010B 0317          ADD     DX,[BX]                            DS:0200=0020

20 と 10を足した結果がAXレジスタに格納されている。
20が格納されているはずのメモリの200番地をダンプする

-d 200 201
2D6D:0200  20 00                                              .

ポインタに使えるレジスタはBX、BPとインデックスレジスタ

ジャンプとループ

いわゆる分岐と繰り返しを学ぶ。

  • JMP <無条件ジャンプ>

指定アドレスに無条件に飛ぶ。これは余り必要なさそう。

  • J〜 <条件ジャンプ>

高級言語のif文を実現する為に使う。 前回の演算(AddやSub等)結果をフラグレジスタが記憶していてそのフラグの値に応じてジャンプするかどうかが決まる。
フラグレジスタは複数あるけどSF(前回の演算結果が負数),ZF(前回の演算結果が0)あたりが一番使いそうな感じかな。
前回の演算結果の演算には一般的にCMP命令を使う。CMP命令はSubと同じ事をするがデスティネーションレジスタに値を書き込まない。

-a 100
2D6D:0100 mov ax,10
2D6D:0103 cmp ax,20
2D6D:0106 jae 120 ;前回の演算が正か0の場合にジャンプ
2D6D:0108 jb 130 ;前回の演算が負の場合にジャンプ
2D6D:010A
-a 120
2D6D:0120 mov ax,41
2D6D:0123 jmp 109
-a 130
2D6D:0130 mov ax,42
2D6D:0133 jmp 109

;実行
-g =100 10A
AX=0042  BX=0200  CX=0000  DX=0010  SP=FFEE  BP=0000  SI=0000  DI=0000
DS=2D6D  ES=2D6D  SS=2D6D  CS=2D6D  IP=010A   NV UP EI NG NZ NA PE CY
2D6D:010A 07            POP     ES

これで高級言語

if((20-10) >= 0) {
  ax = 0x41;
} else {
  bx = 0x42;
}

としたのと同じ。

  • loop <繰り返し>

ジャンプだけで繰り返しは実現可能だが86系のCPUではloopコマンドをサポートしている。
『loopコマンドに指定したアドレスにCXレジスタが0で無ければジャンプしCXレジスタをデクリメントする』 という命令。ジャンプ先をloopの手前にする事で何度もそこを通り繰り返し処理を行う。
高級言語でいうところのdo 〜 while文?


こうやって見るとコンピュータの制御は逐次、分岐、繰り返しの3つだが
それは論理的な制御で、内部的には逐次と分岐の2つしかない事がわかる。

サブルーチン、スタック

  • サブルーチン

callで指定アドレスにジャンプしretで呼び出し元に戻る

-a 100
2D6D:0100 mov ax,10
2D6D:0103 call 200
2D6D:0106 call 200
-a 200
2D6D:0200 add ax,10
2D6D:0203 ret

-g =100 109
AX=0030  BX=0200  CX=0000  DX=0010  SP=FFEE  BP=0000  SI=0000  DI=0000
DS=2D6D  ES=2D6D  SS=2D6D  CS=2D6D  IP=0109   NV UP EI PL NZ NA PE NC
2D6D:0109 26            ES:
2D6D:010A 07            POP     ES
;AXレジスタが最初にセットした10にサブルーチンで10を足す処理を2回呼び出す事で30になっている。
  • スタック

pushコマンドでSPレジスタが指すアドレスへ値をセット。
popコマンドでSPレジスタが指すアドレスの一個前の値を取り出す。
サブルーチンはcall時にIPレジスタの値をスタックに積み、ret時にpopで取り出した値をIPレジスタにセットするという処理。
頑張ればジャンプ命令のみで自力実装できるわけだ。まぁ普通しないだろうけど。


サブルーチンコールではIPレジスタの制御だけをするみたいだけど、関数呼び出しという事なら
『汎用レジスタの値全てをスタックに積んで戻る時に全て復元してくれる』
とかの方がしっくりくるかも。



この辺くらいまででも、だいぶいろんな事ができそう。
とりあえず目指すは自分言語を実装できるとこかなぁ。
そこまでいければ理論上、高級言語でできる事が全部できるわけだし。