X86アセンブラ/x86アーキテクチャ
目次 |
x86アーキテクチャ [編集]
x86アーキテクチャは8個の汎用レジスタ (GPR) と6個のセグメントレジスタ、1個のフラグレジスタ、1個の命令ポインタを持っている。
汎用レジスタ (GPR) [編集]
8個の汎用レジスタは、下記のとおりである。ベースである8086では下記の様にレジスタの使用目的が規定されていた。
- EAX/AX/AL/AH : アキュムレータレジスタ。算術演算操作の結果が格納される。
- ECX/CX/CL/CH : カウンタレジスタ。シフトローテート命令とループ命令に使用される。
- EDX/DX/DL/DH : データレジスタ。算術演算操作とI/O操作に使用される。
- EBX/BX/BL/BH : ベースレジスタ。セグメントモードでのDS(後述)に格納されたデータを指し示すために使用される。
- ESP/SP : スタックポインターレジスタ。スタックのトップを指し示すポインタ。
- EBP/BP : スタックベースポインタレジスタ。スタックのベースを指し示すのに使用される。
- ESI/SI : ソースレジスタ。ストリーム操作でのソースへのポインタとして使用される。
- EDI/DI : デスティネーションレジスタ。ストリーム操作でのデスティネーションへのポインタとして使用される。
汎用レジスタの最初の4個は、4通りのアクセスの仕方ができる。
- 32ビットアクセス: EAX、ECX、EDX、EBX(80386以降で拡張され使用可能)
- 16ビットアクセス: AX、CX、DX、BX
- 8ビットアクセス: AL、CL、DL、BL(AX、CX、DX、BXのLSB側8bit)
- 8ビットアクセス: AH、CH、DH、BH(AX、CX、DX、BXのMSB側8bit)
80386以降ではレジスタの使用目的は特に規定はされていない。
最後の4個は、2通りのアクセス方法がある。
- 32ビットアクセス: ESP、EBP、ESI、EDI(80386以降で拡張され使用可能)
- 16ビットアクセス: SP、BP、SI、DI
セグメントレジスタ [編集]
セグメントとは、メモリ空間について「先頭アドレスとサイズ」を定義し一纏めにした領域(いわば配列)のようなものであり、用途(スタック領域を指し示す・コード領域を指し示す・データ領域を指し示す)毎に、そのメモリ領域の先頭を示すポインタであるのがセグメントレジスタである。
6個のセグメントレジスタは以下のとおりである。
- SS : スタックセグメント。スタックへのポインタ。
- CS : コードセグメント。コードへのポインタ。
- DS : データセグメント。データへのポインタ。
- ES : エクストラセグメント。追加のデータへのポインタ。(EはExtraのEである)
- FS : Fセグメント。さらに追加のデータへのポインタ。(FはEの次である)
- GS : Gセグメント。さらにさらに追加のデータへのポインタ。(GはFの次である)
ほとんどの現代的なオペレーティングシステム(LinuxやMicrosoft Windowsなど)で動くほとんどのアプリケーションは、ほとんど全てのセグメントレジスタが同じ場所を指し示すメモリモデルを採用しており(代わりにページングを使用している)、事実上これらの使用は無効になっている。 FSやGSはこの決まりの例外となっており、スレッド独自のデータを指し示すのに使われる。
EFLAGSレジスタ [編集]
EFLAGSは1個の32ビットレジスタで、操作の結果やプロセッサの状態の格納と制御のための判断材料として使用される。
これらのビットの名前は、以下のとおりである。
| 31 | 30 | 29 | 28 | 27 | 26 | 25 | 24 | 23 | 22 | 21 | 20 | 19 | 18 | 17 | 16 |
| 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | ID | VIP | VIF | AC | VM | RF |
| 15 | 14 | 13 | 12 | 11 | 10 | 9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
| 0 | NT | IOPL | OF | DF | IF | TF | SF | ZF | 0 | AF | 0 | PF | 1 | CF | |
ビットの名前が0または1になっているものは予約されているので、変更されるべきではない。
| これらのフラグの使い方はそれぞれ以下のようである。 | |
|---|---|
| 0. | CF : キャリーフラグ。最後の算術演算操作で加算においてレジスタの大きさを越えてビットのキャリー(桁上がり)かボロー(桁借り)をした場合にセットされる。これは、キャリーの生じた加算やボローの生じた減算の次の操作がされた場合に、1個のレジスタだけ扱うことのできる値であるか確認するのに使われる。 |
| 2. | PF : パリティフラグ。結果の最下位バイトに値1 のビットが偶数個含まれている場合にセットされ、奇数個の場合にはクリアされる。 |
| 4. | AF : 調整フラグ。2進化10進 (BCD) 演算の算術演算でキャリーまたはボローが生じたらセットされる。 |
| 6. | ZF : ゼロフラグ。操作の結果がゼロ (0) になった場合にセットされる。 |
| 7. | SF : 符号フラグ。操作の結果が負となった場合にセットされる。 |
| 8. | TF : トラップフラグ。ステップバイステップのデバッギングをする場合にセットする。 |
| 9. | IF : 割り込み可能フラグ。割り込みを有効化したい場合にセットする。 |
| 10. | DF : 方向フラグ。ストリームの方向を制御する。セットするとストリング命令においてポインタがデクリメントされる(通常はインクリメントされる)。 |
| 11. | OF : オーバーフローフラグ。符号付き算術演算の結果がレジスタが格納できないほど大きい値になった場合にセットされる。 |
| 12-13. | IOPL : I/O特権レベルフィールド(2ビット)。現在のプロセスのI/O特権レベルを示す。 |
| 14. | NT : ネストタスクフラグ。割り込みチェーンを制御する。現在のプロセスが次のプロセスにリンクされている場合にセットされる。 |
| 16. | RF : 再開フラグ。デバッグ例外への応答を制御する。 |
| 17. | VM : 仮想8086モード。8086互換モードにある場合にセットされる。 |
| 18. | AC : アラインメントチェックフラグ。メモリチェックでアラインメントチェックが有効な場合にセットされる。 |
| 19. | VIF : 仮想割り込みフラグ。IFの仮想イメージ。 |
| 20. | VIP : 仮想割り込み保留フラグ。割り込みが保留されている場合にセットされる。 |
| 21. | ID : 識別フラグ。セットできればCPUID命令がサポートされる。 |
命令ポインタ [編集]
命令ポインタ (EIP) は、分岐が置きない前提で、次に実行する命令のアドレスを保持している。
EIPはcall命令の直後にのみ読みことができる。
メモリ [編集]
x86アーキテクチャはリトルエンディアンであり、16bitからなるワードや32bitからなるロングワードデータなどのマルチバイトデータの値は最下位バイトから順に書き込まれる。 これはバイト単位の順序についてであり、ビット単位の順序についてではない。
つまり、32ビットの値である0xB3B2B1B0は(以下0xは16進数を示す)、x86システムのメモリ上では以下のように表現される。
| B0 | B1 | B2 | B3 |
同様に、32ビットの倍精度ワード0x1BA583D4 は、メモリ上では以下となる。
| D4 | 83 | A5 | 1B |
したがって、メモリをバイト単位で表示すると0xD4, 0x83, 0xA5, 0x1Bのように見える。
2の補数表現 [編集]
2の補数というのは、2進数で負の整数を表現する標準的な方法である。 数値の符号を変えるには、全てのビットを逆にし、1を足せば良い。
00012 |
|
| これの各ビットを反転させると以下のようになる |
11102 |
| これに1を足す。 |
11112 |
00012は10進数での1である。
11112は10進数での-1である。
アドレッシングモード [編集]
x86の各機械語は、動作の対象として(オペランドと呼ぶ)、上記レジスタ、メモリ空間あるいは数値(即値と呼ばれる)にアクセスするが、その方法は命令により限定されている。
アドレッシングモードとは、オペランドにアクセスする方法を示す、名称である。
- レジスタアドレッシング
mov ax, bx ; レジスタbxの内容をaxにコピーする
- イミディエイト(即値)
- (値がその場で与えられる)
mov ax, 1 ; 値1をレジスタaxに設定する
あるいは、
mov ax, 0x010C ; 値0x010Cをレジスタaxに設定する
- 直接メモリアドレッシング
mov ax, [102h] ; 実際の値は、DS:0として 0 + 102= 0x102であるメモリアドレスの値が設定される。
- 直接オフセットアドレッシング
- (アドレスを修正するために算術演算をする)
byte_tbl db 12,15,16,22,..... ; バイトテーブル mov al,[byte_tbl+2] mov al,byte_tbl[2] ; 上と同じ
- レジスタ間接
- (オペランドのアドレスを格納したレジスタへのポインタを書く)
mov ax,[di]
- インダイレクトアドレスに使われるレジスタはBX、BP、SI、DIである。
- ベース-インデックス
mov ax,[bx + di]
- 例えば、配列について言えば、bxはベースのアドレスであり、diが配列のインデックスとなる。
- ディスプレイスメント付きベース-インデックス
mov ax,[bx + di + 10]
スタック [編集]
スタックとは後入れ先出し方式のデータ構造で、データはスタックに上からプッシュされ、入れた順序を逆にポップしてくる。
mov ax, 006Ah mov bx, F79Ah mov cx, 1124h push ax
AXの値をスタックの最上部にプッシュするとする。スタックは$006Aという値を保持している。
push bx
BXの値を同様にスタックにプッシュする。スタックは$006Aと$F78Aの値を保持している。
push cx
同様にCXの値もプッシュすると、スタックは$006A、$F79A、$1124を保持することとなる。
call do_stuff
何かをする。関数は使うレジスタを使わなかったので、レジスタはそのままだった。
pop cx
スタックに最後にプッシュされた要素をポップさせてCXに読み込む。CXの値は$1124となり、スタックには$006A、$F79Aが保持されている。
pop bx
スタックの最上部の要素をポップさせてBXに読み込む。BXの値は$F729となり、スタックには$006Aしかなくなる。
pop ax
スタックの最上部の要素をAXに読み込む。AXの値は$006Aとなり、スタックは空となる。
スタックは通常、引数を関数て手続きに渡すために使われる。また、call命令が使われた時に制御フローを監視するためにも使われる。 その他のスタックのよくある使い方は、一時的にレジスタの値を保存しておくことである。
CPUの動作モード [編集]
リアルモード [編集]
リアルモードは元々のインテル8086プロセッサからの遺産である。一般的にはこれについて何も知る必要はない(MS-DOS用のプログラムを書くか、もっとありそうなことでは、BIOSから直接実行されるブートローダを書くかしない限りは)。
インテル8086はメモリにアクセスするのに20ビットのアドレスを使っていた。しかし、プロセッサ自体は16ビットであったため、インテルは、20ビットのアドレッシングスペースを16ビットワードに割り当てる方法を用意するという、アドレッシングスキームを発明した。現在のx86プロセッサは、後方互換性のため、若干の違いはあるがこの8086の動作を真似た動作モードである、リアルモードで起動する。
リアルモードでは、セグメントレジスタとオフセットレジスタの両方が最終的なメモリアドレスを得るために使われる。セグメントレジスタの値は、16倍(つまり左に4ビットシフト)し、オフセットが加算されてメモリアドレスを得る。これによって1MBのスペースが使えるようになった。しかし、このアドレッシング方法によって、セグメントレジスタの値をその最大値である0xFFFFにすることによって、1MBまでアクセスできるようになった。 8086と8088では、このエリアへの全てのアクセスによってメモリを使い切ることとなるが、80286以降では、この1MB以降に最大65520バイトまで、A20アドレスラインを有効化することにより、同様な方法でアクセスすることができる。参照: A20ゲートの物語
リアルモードのセグメントとプロテクトモードでのマルチセグメントメモリモードに共通するの利点は、全てのアドレスがあるアドレスから相対的な位置として与えられるということである(これをセグメントベースアドレスと呼ぶ)。プログラムはそれ自身のアドレススペースを持つことができ、完全にセグメントレジスタを無視することができる。そして、プログラムを実行するためにはポインタを移動させる必要がない。プログラムは近くにあるコールを実行し、同じセグメント内でジャンプする。そしてデータは常にセグメントベースアドレスから相対的にある(リアルモードアドレッシング方法では、セグメントレジスタに格納された値から計算される)。
これはDOSの*.COMフォーマットがしていることである。ファイルの内容はメモリに読み込まれ、そのまま実行される。しかし、リアルモードセグメントは常に64KBであるため、COMファイルは64KBより大きくすることができない(実際には65280バイトまで可能であるが、DOSはセグメント最初の256を管理用に使う)。長い間、これは問題にはならなかった。
プロテクトモード [編集]
フラットメモリモデル [編集]
LinuxやWindowsといった現代的なオペレーティングシステムでプログラミングする場合には、基本的にはフラット32ビットモードでプログラミングをする。 全てのレジスタはアドレッシングに使うことができ、完全に32ビットのレジスタを16ビットレジスタの代わりに使うことで、より効率的になる。 加えて、セグメントレジスタは一般的にはフラットモードでは使われず、これに触れるのは悪い考えであるといえる。
マルチセグメントメモリモデル [編集]
(未執筆)