アセンブリ言語に関する世界観です。ハードウェア(1.半導体)も参照のこと。
はじめて読む8086―16ビット・コンピュータをやさしく語る (アスキーブックス)を参考に執筆しました。
16bitの8086のごく基本的な命令です。詳しい説明が欲しい方は書籍をご覧ください。
基本的に、レジスタと呼ばれる高速な記憶装置に対して、演算命令を与えることで処理を行う。
レジスタの中でも、AX, BX, CX, DXの4つのレジスタはよく使う。AXレジスタの上位の8ビットにはAH、下位の8ビットにはALという略称がつけられている。
後日注記:AXはこの中でもよく使われるレジスタで、ほかのレジスタよりも高速。BX、CX、DXは予備として使用する。特に、BXはベースアドレスのポインタに、CXは回数などのカウントに、DXは一時的な記憶領域のために使うことが多い。
16ビットCPUでは、アドレスの指定を「セグメント方式」で行う。アドレス部は「32CE:0100」といったように、セグメントアドレスとオフセットアドレスに分割される。通常はセグメントベースが固定され、オフセットアドレスだけでアドレスを指定することができる。セグメントベースにオフセットアドレスを加えた値が物理アドレスとなる。
セグメントの例:
-A 0100(オフセットアドレス0100Hからアセンブル) 32CE:0100 MOV AL,10 32CE:0102 ADD AL,20 32CE:0104 SUB AL,10 32CE:0106 MOV [0100],AL 32CE:0108 MOV AH,02 32CE:010A MOV DL,[0100] 32CE:010C INT 21 32CE:010E
ここではセグメントアドレスは32CEであり、オフセットアドレスは0100から始まる。
後日注記:セグメントレジスタには、CS(コードセグメント)、DS(データセグメント)、ES(エクストラセグメント)、SS(スタックセグメント)がある。CSはプログラムの格納されているセグメントのセグメントアドレス、DSはデータを格納するセグメントのセグメントアドレスを指定する。
(以上ははじめて読む8086―16ビット・コンピュータをやさしく語る (アスキーブックス)を参考に執筆しました。)
以下はデータを記憶に退避したり、記憶からデータを取得するMOV命令。16進数の値やレジスタがオフセットアドレスであることを示すには[]を使う。
命令 | 説明 |
---|---|
MOV 受ける側, 送る側 | データの転送(UNIXやDOSのコマンドとは方向が逆になる) |
MOV AL, 010H | 10HをALにロードする |
MOV [0100H], AL | ALをメモリ上の番地(オフセットアドレス)「100H」にストアする |
MOV AL, [0100H] | メモリ上の番地「100H」をALにロードする |
MOV [0102H], AL | ALをメモリ上の番地「102H」にストアする |
特定の場所にデータを記憶しておきたい場合などは、MOV XX [XX]などが大活躍します。メモリのことをなぜメモリ(記憶)というのかが良く分かります。
後日注記:MOV命令では、メモリからメモリへの転写はできない。必ずメモリ→レジスタ(これをロードと呼ぶ)か、レジスタ→メモリ(これをストアと呼ぶ)か、あるいは値のレジスタあるいはメモリに対する転送になる。
後日注記:MOV命令は、ある特定の場所にさまざまな場所から参照・変更する共通のデータを格納するために使うことができる。たとえば、xとyの内容を「100H」と「102H」に格納することにしておいて、ジャンプ命令を使って記述したサブルーチンの中からこの二つの領域にアクセスし、x + yの値を「104H」に格納するなどすれば、簡単なサブルーチンを機械語で書くことができる。
以下は演算・比較命令。
命令 | 説明 |
---|---|
ADD AL, 20H | ALに「20H」を足し算 |
SUB AL, 10H | ALから「10H」を引き算 |
INC AL | ALをインクリメントする(+1) |
DEC AL | ALをデクリメントする(-1) |
CMP AX, XX | AXと「XX」を比較し、結果をフラグレジスタに格納 |
MUL BL | ALとBLを掛け算し、結果をAXに格納 (ALとBLは8bit、AXは16bit) |
MUL BX | AXとBXを掛け算し、結果をDX:AXに格納 (AXとBXは16bit、DX:AXは32bit) |
DIV BL | AXをBLで割り算し、商をALに、余りをAHに格納 (AXは16bit、BLとALとAHは8bit) |
DIV BX | DX:AXをBXで割り算し、商をAXに、余りをDXに格納 (DX:AXは32bit、BXとAXとDXは16bit) |
以下はジャンプ命令。
命令 | 説明 |
---|---|
JMP XX | アドレス行(オフセットアドレス)「XX」にジャンプしてプログラムを実行する(無条件ジャンプ) |
JZ XX | ZF(ゼロフラグ)がセットされていれば、アドレス行「XX」にジャンプしてプログラムを実行する |
ジャンプ命令を使う場合は、ジャンプ先のアドレスは普通分からないので、ジャンプ先を示すにはマクロアセンブラ(MSAM)を使う。
これ以外にも、「CMP AL,08H」という命令の後ならば、次のような条件ジャンプ命令を使うことができる。
命令 | 説明 |
---|---|
JZ XX | (AL=08H)ならばジャンプ |
JNZ XX | (AL≠08H)ならばジャンプ |
JB XX | (AL<08H)ならばジャンプ |
JA XX | (AL>08H)ならばジャンプ |
JBE XX | (AL≦08H)ならばジャンプ |
JAE XX | (AL≧08H)ならばジャンプ |
ここに示した条件ジャンプ命令は、比較に使う値を符号なしと想定した場合の命令だ。これに対して、符号ありと想定した場合の条件ジャンプ命令も用意されている。(つまり、負の数を扱う場合の条件ジャンプ命令。)
命令 | 説明 |
---|---|
JL XX | (AL<08H)ならばジャンプ |
JG XX | (AL>08H)ならばジャンプ |
JLE XX | (AL≦08H)ならばジャンプ |
JGE XX | (AL≧08H)ならばジャンプ |
以下はループ命令。
命令 | 説明 |
---|---|
LOOP XX | アドレスXXにジャンプしてループ。 CXをデクリメントし、0になればループ終了。 0にならなければループ継続。 |
以下はサブルーチン命令。
命令 | 説明 |
---|---|
CALL XX | アドレスXXからのサブルーチンを実行。 |
RET | サブルーチン呼び出し以後の命令に戻る。 |
以下はスタックポインタ命令。
命令 | 説明 |
---|---|
PUSH AX | スタックポインタ(SP)を-2デクリメントし、スタックにAXを積む(プッシュ) |
POP AX | スタックからAXに取り出し(ポップ)、スタックポインタを+2インクリメントする |
PUSHとPOPはいわばデータの避難場所。データを一時的に退避し、必要になった時点で取り出すためによく使われる。アドレスを気にしなくても手軽にデータを保持できるのが利点。最後に積んだデータが最初に取り出される。
スタックポインタ(SP)はスタックの一番上にあるデータを指し示すポインタのことで、スタックの実現のためにあり、PUSHやPOPを行うごとに2ずつ減ったり増えたりする。
以下は入出力命令。
命令 | 説明 |
---|---|
IN AL, XX | ポートXXからの入力をALに格納 |
OUT XX, AL | ポートXXにALを出力 |
このほか、ファンクションコールといって、ソフトウェア割り込みを使うことで、MS-DOSの機能を使うことができる。
命令 | 説明 |
---|---|
INT XX | XX番のソフトウェア割り込み命令を実行 |
ソフトウェア割り込みのタイプ20Hは、「プログラム終了」を表すシステムコール。
ソフトウェア割り込みのタイプ21Hを使うことで、MS-DOSの機能を使うことができる。ファンクションコールでは、AHレジスタにファンクション番号を入れて目的の機能を指定する。
命令 | 説明 |
---|---|
MOV AH, XXH INT 21H | MS-DOSのファンクションXXの機能を ソフトウェア割り込みにより呼び出す |
たとえば、文字の入力や出力は、すべてこのファンクションコールによって実現できる。ファンクション番号01、06、0Aはコンソールからの入力に使う。02、09はコンソールへの出力に使う。
このほか、論理演算命令(AND, OR)、ストリング操作命令、ローテート・シフト命令などがある。
(以上ははじめて読む8086―16ビット・コンピュータをやさしく語る (アスキーブックス)を参考に執筆しました。)
2022.12.05編集
DEBUGコマンドを使ったアセンブラでは、数値を指定すると16進数として解釈されますが、その他のアセンブラで16進数を示すには数値のあとにHを付けます。
つまり、
MOV [0101H], AL
は、DEBUGコマンドでは、
MOV [0101], AL
と書いてください。
マクロアセンブラ(MASM)によって、DEBUGコマンドなどで利用可能な16進数の数値によるアドレス指定に加えて、ラベル(LOOP_HOGE:など)を使ったり、数値の豊かな表現('A'など)を使ったりすることができる。
LOOP_HOGE:
のように「:」が付いているのがラベルだ。ラベルはその次の命令が格納されているアドレスを意味する。ラベルを使うことで、ラベルに対応するアドレスを自動的に決定してくれる。
16bitと32/64bitの違いとして、16bitではレジスタの名称がax, bx, cx, dxなどとなっていたのが、32bitではeを前につけてeax, ebx, ecx, edxとなり、64bitではeではなくrをつけてrax, rbx, rcx, rdxとなります。xやeはextendの略で、rはregister(レジスタ)の意味。よってレジスタax、レジスタbxのように考えれば分かりやすいかもしれない。このほかにもさまざまな用途に使うレジスタがたくさんあります。
また、Intel構文とは別にAT&T構文があり、%や$をつけて、
mov $30, %eax
のようになります。これは、Intel構文だと
mov eax, 30
となります。ディスティネーション(格納先)とソース(値あるいは参照先)の順番も逆です。
ほかに、DWORD PTRなどがでてきますが、DWORDは4バイト、WORDは2バイト、BYTEは1バイトを意味する修飾子となります。BYTE PTR 3のようにします。
また、[esi]のようなアドレス指定では、esiが保持しているアドレスのデータを表します。
命令の後にqがついているものは64bit、lがついているものは32bitの命令となります。たとえばmovqやmovlなど。
詳しくは以下が参考になります。
以下のページが参考になります。
(以下ははじめて読む486―32ビットコンピュータをやさしく語るを参考に執筆しました。)
Intel 486プロセッサには、「リアルモード」(MS-DOSのプログラムの実行)、「プロテクトモード」(Windows 3.1などのアプリケーションの実行)、「仮想8086モード」(WindowsにおけるMS-DOSのプログラムの実行)という3つの動作モードがある。
リアルモードは、従来との互換性を保っているモードのことで、8086とまったく同じ動作をする。これに対してWindows 3.1は拡張機能(Windowsのメモリ管理やタスク管理のために必要)を有効にしたプロテクトモードで実行される。プロテクトモードになった時点で従来のMS-DOSプログラムは実行できないため、仮想8086モードで実行される。
プロテクトモードのプログラムを実行している状態で、特権レベル0~3までの特権レベルがある。特権レベル0では、すべての命令が実行でき、すべてのメモリにアクセス可能となる。また、特権レベル0では「特権命令」という一部の命令が実行可能となる。オペレーティングシステムは特権レベル0で動作させる。
また、アプリケーションソフトウェアは特権レベル3で動作させる。特権レベル3では、一部の命令は実行できず、一部のメモリにはアクセスできない。特権レベル1や2は、OSの補助プログラムやハードウェアを操作させるプログラムなどを実行させる。
以前のMac OS Xの命令セットで、IBM製品のほかLinuxなどでも対応していることのあるPowerPCのニーモニック(命令)の一覧は以下が参考になる。
ある意味、IBMはIBM-PC/AT互換機とPowerOS CPUで、本当はメインフレームだけではなくWindowsとMacも裏で牛耳っている恐ろしい会社である。プレステ3向けにCellというCPUも作っていたが、これは期待はずれだったらしい(Cellの理想であるソーシャルゲーム機という発想は革新的だったが、ゲームとの相性が悪かったとのこと)。
PowerPCも参照のこと。
ARMの命令セットは以下を参照のこと。組み込み向けで、Fedoraなどでも対応しているし、Androidという成功例もある。
ARMも参照もこと。
Intelを参照のこと。
CPUアーキテクチャを参照のこと。
C言語(6.プリプロセッサとインラインアセンブラ)を参照のこと。
書籍