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

出典: フリー教科書『ウィキブックス(Wikibooks)』
ナビゲーションに移動 検索に移動

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

歴史[編集]

歴史的には、もちろん16ビットの方が古い(8ビットはさらに古く、4ビットが最も古い)。

本ページでは、バイナリー互換性のある16ビットから説明します。

新しいビット幅のアーキテクチャーが出てくると、過去に定義されていた用語は、新しいアーキテクチャーとの整合性をとるために徐々に変更されていくので、当時と同じものはありません。

8086のレジスター[編集]

8086のレジスターはすべて16ビット幅で、8086には次のようなレジスターがあります。AX、BX、CX、DX、BP、SP、DI、SI、CS、SS、ES、DS、IP、FLAGSです。

Windowsを使用している場合は、MS-DOSプロンプトに入り、「DEBUG.EXE」というプログラムを実行することができます。これは8086について学ぶのに非常に便利で、以前はすべてのバージョンのWindowsに付属していましたが、16ビットコードを実行できないため、64ビット版のWindowsには付属していません(そもそもMS-DOSプロンプトがありません)。

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

8086には20bitのアドレスバスがありますが、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)をセグメントアドレスと呼びます。

ここで注意したいのは、物理アドレスとセグメントアドレスは一対一に対応していないということです。言い換えれば、1つの物理アドレスに対して、2つ以上のセグメントアドレスが存在する可能性があります[1]。例えば、セグメントアドレスで表現すると、B000:8000とB200:6000の2つのアドレスがあるとします。計算してみると、どちらも物理アドレスは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となります。このようにして決定されたセグメントアドレスの表現は、「ノーマライズドアドレス」という特別な名称がつけられています。

CS:IP(Code Segment: Instruction Pointer)は、次に実行する命令を取り出すための物理メモリー上の20ビット幅のアドレスを表します。同様に、SS:SP(stack segment: stack pointer)は、スタックの先頭として扱われる20ビットの絶対アドレスを示します(x86では値のプッシュ/ポップやサブルーチンコールの戻番地の保持、それにBPを使ったローカル変数の実装にこれを使用)。

浮動小数点コプロセッサー[編集]

8086ファミリーには、8087という浮動小数点コプロセッサー(NDP)が用意されていました。 8087は、Am9511と同じようなスタックマシンでした。 ただし、Am9511とは異なり8087とホストプロセッサーの8086と協調して操作し、プログラマーから見ると(チョット風変わりな)浮動数点命令(NPX)を持った一体のプロセッサーのように見えます。 8087は8レベルのスタックを内蔵したレジスターファイルで実現しており、演算はスタックトップに対して行われます。 このためレジスターが8本あってもレジスターを示すインデックスは必要なく浮動小数点命令のビット長の短縮に貢献しています。

浮動小数点コプロセッサーは、80187,80287,80387を経て 80486 ではプロセッサーに内蔵されるようになり、以後単体の浮動所数点コプロセッサー製品は提供されなくなりました[2]

プロテクトモード(80286)[編集]

80186では、命令追加[3]と周辺チップをの統合にとどまる小変更でした。 これに対し、80286はアドレスバスを従来の20ビットから24ビットに拡張しアドレス空間を一気に16倍にする野心的なものでした。 増加したアドレス空間を利用するために新たなアドレシングモード「プロテクトモード」が追加され、従来のセグメントモデルのアドレッシングモードには「リアルモード」という名前が追号されました[4]。リセット直後の80286はリアルモードで起動します。

プロテクトモードでは、セグメントレジスターの値はアドレスの上位ではなく、セレクターを意味します。

セレクターによってディスクリプタテーブルの中のセグメントディスクリプタが指定されベースアドレスが決まりオフセットが足され物理メモリにアクセスすしますが、セレクタの下位3ビットは 特権レベルを示します。

特権レベルが合致しないアクセスには例外が上がります。これリアルモードならば例外ベクトルが参照されますが、プロテクトモードでは割込みディスクリプターテーブル(IDT: Interrupt Descriptor Table)が参照されます。

このように80286のリアルモードではディスクリプターをメモリ保護・メモリ管理・特権管理の要としています。 しかし、80286が発売された1982年にはプロテクトモードを活かして設計されたオペレーティングシステムは一般向けに普及しせず、プロテクトモードを前提とするWindows3.0(スタンダードモード)の発売は1990年まで待つ必要があり、本格的にリングプロテクション機構を利用したOS/2 1.x の登場はそれより早い1987年でしたが広く普及するには至りませんでした。

A20ゲートの物語
前述したように、8086のプログラムにはA0からA19までの20本のアドレスラインがあった。そのため、最大で1MBのメモリーをアドレス指定することができた(1MBは2の20乗)。しかし、8086には16ビットのレジスターしかないので、プログラムはセグメント:オフセット方式を使うか、16ビットのレジスターだけを使って64KB(16の2乗で64KB)までしかアクセスできませんでした。これらの方法であれば、プログラムは1MBのメモリーのすべてにアクセスすることができました。

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

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

セグメント:オフセット表現から物理アドレスへの変換は以下の通りです。

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

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

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

では、10進数の16が16進数の10であることを忘れずに、FFFF:FFFを20ビットの物理アドレスに変換してみましょう。

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のメモリーにアクセスすることができました[5]

簡単に言えば、セグメント:オフセット表現では、セグメントは固定されており、16ビットのオフセットの値を変更することで64KBのメモリー領域を操作することができる(64KBは2の16乗)。セグメントレジスターに1MBに満たない32KBの値を設定すると、32KBを超える部分は1MBの制限を超えてしまい、この最後の32KBの部分は折り返しになってしまいます。

スーパーファンキーなプログラマーたちは、プロセッサーのアドレスラインが増えることを見落としていたのだ(注:ビル・ゲイツ氏でさえ、「640KB以上のメモリーを必要とする人はいないだろう」と言ったと言われています)。8086の発売からわずか2年後の1982年、インテルは80286というプロセッサーを発表した。これは24ビットのアドレスラインを持っていた。80286はリアルモードをサポートしていたので、理論的には古い8086用のプログラムとの下位互換性があったが、8086用のプログラムの多くは正常に動作しなかった。これは、1MBの制限を超えたアドレスを参照する際に、メモリーの先頭に回り込んでアクセスできるかどうかに依存していたからである。これに対する互換性のために、IBMのエンジニアは、キーボードコントローラーを介してA20互換モードを制御し、プロセッサーのA20のアドレスライン(8086にはA0からA19までのアドレスラインしかない)がメモリーバスを叩くか否かをを切り替えた。なぜ、キーボードコントローラーなのか?答えは未使用のピンがあったからである。80286は8086と完全に互換性があるものとして販売されていたため、アップグレードした顧客は、80286が8086用に書かれたプログラムを実行できる高速なプロセッサーであるにもかかわらず、バグまでは互換性がないことに非常に腹を立てていたのです。

このA20ピンの増設で新たに使えるようになったメモリー領域をHMA(High Memory Area)と呼びます。


80286にはプロテクトモードにいったん入った後、リアルモードに戻る直接的方法が提供されていませんでした。 IBMはその対処として、キーボードコントローラーからCPUをリセットする技法を考案しました。

IA-32[編集]

IA-32という呼称自体は、インテルが新しい64ビットアーキテクチャであるIA-64を発表した際にはじめて使われた用語で、80386が発表された当時は単に32ビット命令セットや32ビット拡張と呼ばれていました。

80386では、データバスが従来の16ビットから32ビットとなり、より大きな幅のレジスターをサポートすることになりました。

EAX、EBX、ECX、EDX、EBP、ESP、EDI、ESI、EIP、EFLAG:従来のレジスターの32ビット版です。
FS、GS
セグメントレジスターは16ビットのままですが、2つのセグメントレジスターが増えました。


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

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

仮想86モード[編集]

MMXレジスター[編集]

SSEレジスター[編集]

拡張MMXレジスター[編集]

AMD64[編集]

ZMMレジスター[編集]

脚註[編集]

  1. ^ このように1つの物理アドレスを異なったセグメントアドレスで表せることをエーリアス(alias; 別名)と呼ぼます。x86のセグメントとオフセットは12ビット重ねっているので、1つの物理アドレスは 212 = 4096 通りのセグメントアドレスで表せます。
  2. ^ 80486SXには80487SXが浮動小数点コプロセッサーとして提供されていますが、80487SXは80486DXそのものでソケットに刺されるとバスを80486SXから奪い取って動作しコプロセッサーではなく後のオーバー・ドライブ・プロセッサーと同じように動作します。
  3. ^ NEC のV30は8086互換プロセッサーとされていますが、命令セット的には80186互換です。
  4. ^ System/360などのメインフレームをご存知の方は、プロテクトモードとリアルモードの名前が逆に感じるかもしれません。
  5. ^ なぜ、このようなトリックが必要になるかというセグメント FFFF はリセット時にCSにセットされる値で、FFFF:0000からリセット時にプログラムが始まります。リセット時にはBIOS(厳密に言うとPOST;パワーオンセルフテスト)から処理が始まる関係でBIOSのコードもこの付近にあります。他方セグメント 0000にもBIOSや割り込みハンドラーが参照するデーターがあります。セグメントレジスターを書き換えるのは速度的なペナルティがある操作なので、セグメントを FFFF にしたままにすれば最後の32KBと最初の32KBの両方にアクセスできるのは魅力のあるハックでした。

参考文献[編集]