アセンブリ言語の勉強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 .
ジャンプとループ
いわゆる分岐と繰り返しを学ぶ。
- 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レジスタの制御だけをするみたいだけど、関数呼び出しという事なら
『汎用レジスタの値全てをスタックに積んで戻る時に全て復元してくれる』
とかの方がしっくりくるかも。
この辺くらいまででも、だいぶいろんな事ができそう。
とりあえず目指すは自分言語を実装できるとこかなぁ。
そこまでいければ理論上、高級言語でできる事が全部できるわけだし。