X86アセンブラ/16、32、64ビット

出典: フリー教科書『ウィキブックス(Wikibooks)』
移動: 案内検索

x86アセンブリ言語は、16ビット、32ビット、64ビットの各アーキテクチャ間で多くの違いがある。ここでは、異なるビット幅のアーキテクチャ間での基本的な違いについていくつか述べる。

8086のレジスタ[編集]

8086のレジスタは全て16ビットである。8086には次のようなレジスタがある。AX、BX、CX、DX、BP、SP、DI、SI、CS、SS、ES、DS、IPである。

Windowsを使っていれば、MS-DOSプロンプトに入り「debug.exe」というプログラムを実行できる。これは、8086について学ぶには非常に役に立つし、全てのバージョンのWindowsに附属している。

AX, BX, CX, DX
これらのレジスタは8ビットのレジスタとしても使うことができる。つまり、AXはAH(上位8ビット)とAL(下位8ビット)として使用できる。

ここに問題が生じる。16ビットのレジスタを使ってどのように20ビットのアドレス空間を参照すれば良いのだろうか? この問題を解決するには、CS(コードセグメント)、DS(データセグメント)、ES(エクストラセグメント)、SS(スタックセグメント)の各セグメントレジスタを使う。20ビットのアドレスに変換するために、セグメントレジスタで基点となるアドレスを決め、残りをオフセットレジスタで決めるのである。これは、CS:IPと表記される(CSがセグメントアドレスで、IPがオフセットアドレスであるという意味である)。同様に、アドレスがSS:SPと書かれた場合、SSはセグメントアドレスでSPはオフセットアドレスである。

例題[編集]

CS = 0x258CかつIP = 0x0012(以降、「0x」は16進数を示す)であれば、CS:IPは「CS * 16 + IP」つまり「0x258C * 0x10 + 0x0012(10進数で16は、16進数では0x10となるのを忘れずに)である。

つまり、CS:IP = CS * 16 + IP = 0x258C * 0x10 + 0x0012 = 0x258D2となる。20ビットのアドレスは絶対アドレスと呼ばれ、セグメント:オフセット表現 (CS:IP) はセグメント化アドレス (Segmented Address) と呼ばれる。

ここで重要なのは、物理アドレスとセグメント化アドレスが一対一に対応しているのではないということである。つまり、物理アドレスに対しては、2通り以上のセグメント化アドレスが存在する。例えば、セグメント化アドレスで表現して、B000:8000とB200:6000というアドレスがあるとする。計算してみると、どちらも物理アドレスはB8000となる(B000:8000 = B000 * 10 + 8000 = B0000 + 8000 = B8000であり、B200:6000 = B200 * 10 + 6000 = B2000 + 6000 = B8000である)。しかし、適切なマッピング方法を使えばこの問題は避けることができる。つまり、きちんと一次変換になるようなセグメント化アドレスから物理アドレスへのマッピング方法を作れば良いのである。これを使えば逆に、簡単に物理アドレスから一つのセグメント化アドレスを決めることができるようになる。

例えば、セグメントアドレス部分が物理アドレスを0x10で割った値と同じになるようにし、オフセットアドレスは残りの部分と同じ値となるようにする。物理アドレスがB8000であったならば、(B8000/10):(B8000%10) = B800:0をセグメント化アドレスとすると決めるのである。このようにして決めたセグメント化アドレスの表現には「標準化アドレス (Normalized Address) 」という特別な名称が与えられている。

CS:IP(コードセグメント:命令ポインタ)は、実行される次の命令を取り出す物理メモリ上の20ビット幅のアドレスを表現できる。同様に、SS:SP(スタックセグメント:スタックポインタ)は、スタックのトップとして扱われる20ビットの絶対アドレスを示すのに使われる(8086は、これを値をプッシュ/ポップするために使う)。

32ビットレジスタ[編集]

32ビットのデータバスをサポートするようになったチップでは、大きなレジスタもサポートされるようにアップデートされる必要があった。

EAX, EBX, ECX, EDX 
これらは上述のレジスタの32ビット版である。

A20ゲートの物語[編集]

先に述べたように、8086プログラムはA0からA19までの20本のアドレスラインを持っていた。そのため、最大1MBのメモリにアドレスを付けることができた(1MBとは2の20乗である)。しかし、8086は16ビットのレジスタしか持っていなかったため、プログラムは、セグメント:オフセット方式を使うか、16ビットレジスタを単独で使い64KB以上はアクセスできないかの(2の16乗は64KBである)、どちらかであった。これらにより、プログラムは1MBのメモリの全てにアクセスは可能になっていた。

しかし、セグメント化方式には副作用があった。プログラムは1MBのメモリ全てを参照できるが、実際にはそれより少し先まで参照できるのである。どういうことか見てみよう。

覚えておいてほしいのは、セグメント:オフセット表現から20ビットの物理アドレスへの変換には標準化アドレスの方法を使うということである。

セグメント:オフセット表現から物理アドレスへの変換は以下のような式となる。

   セグメント:オフセット = セグメント * 16 + オフセット

ここで、表現できるメモリの最大量を見てみよう。セグメントとオフセットの両方に最大値を入れて、20ビットの絶対的な物理アドレスの値に変換してみよう。

つまり、セグメントの最大値 = FFFF、オフセットの最大値 = FFFFとするのである。

さあFFFF:FFFFを20ビットの物理アドレスに変換してみよう。10進数の16は、16進数では10であることを忘れないように。

FFFF:FFFF = FFFF * 10 + FFFF = FFFF0 + FFFF = FFFF0 + (FFF0 + F) = FFFFF + FFF0 = 1MB + FFF0となる。

  • 注記: 16進数でFFFFFは1MB(1メガバイト)に等しい。そして、FFF0は64KBから16バイト引いた値に等しい。


この物語から得られたことは、リアルモードのプログラムは、実際には (1M + 64K - 16) バイトのメモリを参照できるということである。

「参照する」という言葉を使うとき「アクセスする」という意味ではないことに注意してほしい。プログラムはこのはみ出したメモリを参照することはできるが、アクセスすることも実際に存在するアドレスラインの数を無視することもできない。つまり8086では、プログラムに1MB以上のメモリを参照させようとしても、絶対にできない。アドレスラインに載せようとしているアドレスは実際には20ビット以上であり、これはつまりアドレスがラップアラウンドしてしまうということである。

例えば、プログラムが1MB + 1を参照しようとすると、ラップラウンドしてメモリのゼロ番地にアクセスすることになる。 同様に1MB + 2はラップラウンドしてアドレス1(つまり0000:0001)にアクセスすることになる。

スーパーファンキーなプログラマが、プログラマでこの機能を駆使し、ほんの少し早くほんの少し小さなプログラムを書いていたこともあった。このテクニックを使うことにより、メモリの最初の32KBの部分へのアクセスすることが、セグメントレジスタを再読み込みすることなくメモリの最後のさらに後の32KBの部分を参照することによって可能であった。

簡単に言うと、セグメント:オフセット表現では、セグメントを固定し、16ビットのオフセットの値を変化させることによって64KBのメモリエリア内を操作できるのである(64KBは2の16乗である)。セグメントレジスタに1MBから32KB足りない値を設定すると、32KBを越えた部分は1MBの限界を越えることとなり、この最後の32KB分はラップアラウンドする。

スーパーファンキーなプログラマは、プロセッサがより多くのアドレスラインを持つようになることを見落としていた(注記: ビル・ゲイツでさえ、「640KB以上のメモリを必要とする者などいるのか?」と言ったとされている。彼らもおそらく同じように考えていたのであろう)。8086がリリースされてからちょうど2年後の1982年に、インテルは80286プロセッサをリリースした。これは24ビットのアドレスラインを持っていた。80286はリアルモードをサポートしたため、理論上は古い8086用のプログラムへの後方互換性を持っていたが、多くの8086用のプログラムは正常に動作しなかった。これは、1MBの上限を越えたアドレスを参照すると、ラップアラウンドしメモリの頭にアクセスできるということに依存していたためである。これに対する互換性のために、IBMのエンジニアはA20アドレスライン(8086はA0からA19までのアドレスラインしか持たない)にキーボードコントローラを通して信号を送り、A20互換モードを有効化/無効化できるようなメカニズムを提供した。なぜキーボードコントローラなのかと不思議に思うだろう。答えは未使用のピンがあったからである。80286は8086をの完全な互換性を持つものとして市場に出回ったため、アップグレードした顧客は、80286が8086用に書かれたプログラムが実行できる高速なプロセッサであって、バグまで互換性を持っていないことに非常に怒ったのである。

32ビットアドレッシング[編集]

32ビットのアドレッシングは最大4GBのメモリを扱うことができる。つまり、32ビットプロセッサではオフセットアドレッシングを使う必要がないということである。これは「フラットアドレッシング」方法と呼ばれ、レジスタ内のアドレスは直接物理メモリの場所を指し示す。セグメントレジスタは、異なるセグメントを定義するのに使用されている。プログラムはスタックセクションを実行しようとしてはいけないし、データセクションでスタック操作をしようとしてもいけない。これらを分けるために使用されているのである。