X86アセンブラ/x86アーキテクチャ

x86アーキテクチャは8個の汎用レジスタ (GPR) と6個のセグメントレジスタ、1個のフラグレジスタ、1個の命令ポインタを持っている。
64ビットCPUでは、さらに上述以外の補助的なレジスタもあるが、とりあえず上述のレジスタが基本的なので、上述のレジスタについて説明する。
汎用レジスタのうち、特に重要な8個には、下記のような名前がついている。また、その8個のレジスタの使用目的は下記の様に規定されている。
インテルの用意している命令コマンドの中には、特定のレジスタを対象にしたものがあるので、上述のレジスタの規約を守らなければならない。
たとえばintelが用意するアセンブラコマンドの enter 命令は、対象レジスタがebpレジスタとespレジスタだと規定されている事が、2004年のデベロッパーマニュアルで読み取れる(『中巻A:命令セット・リファレンスA-M』3-217)。
反対意見もあり、一説では、上述のレジスタは他の用途にも使えるともいう言説を紹介している文献もあるが、しかし当のインテル社自体、いくつかの命令コマンドの対象を特定のレジスタに適用しているので、他の用途に使うのはあまり推奨されないだろう。
実際、一般的な入門書では、上述のような用途(たとえばEAXはアキュムレータに使うなど)で使うのが、推奨されている。
さて、これら目的の規定されたレジスタとは別に、特に目的の規定されていないレジスタがあり、R8 から R15 までの番号のレジスタも汎用レジスタである、たとえば一時的な値の格納に使われたりするレジスタである。主に64ビットCPUで、R8 〜 R15 が追加されている。 また、これら R8 〜 R15 には特に目的は規定されておらず、任意の目的に使用することができる。
ただし、ABIの規定により、R10がCPUのソフトウェア割込みであるシステムコールに使われるなど、いくつかの取決めがある。
命令ポインタ(RIP/EIP/IP)とフラグレジスタ(dflags, eflags)は、(8個の)汎用レジスタではない。命令ポインタなどについては、別の節で説明する。


汎用レジスタの最初の4個は、4通りのアクセスの仕方ができる。

64ビットCPUにおける「EAX」とは、RAXに使っているレジスタの下位32ビットのことである。けっして、RAXレジスタとは別にEAXレジスタが用意されているわけではない。
なので、上述の一覧では32ビット「アクセス」のように言っているわけである。
同様に、64ビットCPUではRAXレジスタの下位16ビットを「AX」と読んでいる。
RAX以外のRCXやRDXも同様である。RCXの下位32ビットがECXであり、下位16ビットがCXである。
なお、現代的なCPUでは、さらに128ビット用のレジスタ(XMM0 〜 XMM15)も用意されているが、しかし入門的でないので128ビット用レジスタについては本ページでは説明を省略する。
8ビットアクセスの命令は、昔のintel 8008 の命令幅が8ビットだった時代の名残り(なごり)である。
8ビットアクセスのALなどの末尾のLは、下位(Low)のこと 。AHなどの末尾HはHighのこと。
8ビットを基準に、拡張 extended したので、16ビットには「X」(extend の2文字目)をつけてAX,CX,DX,などのように言うようになった(もともと8ビットでは A,C,D などと呼んでいた.。ALやAHなどのL,Hは、その拡張のころにつけられた末尾)。
さらに32ビットに拡張する際、Extend の1文字目eを付けて区別して、eax,ecx,edx のように呼ぶようになった。
rax などの頭文字 r は、64ビット用のこと。
最後の4個は、2通りのアクセス方法がある。
セグメントとは、メモリ空間について「先頭アドレスとサイズ」を定義し一纏めにした領域(いわば配列)のようなものであり、用途(スタック領域を指し示す・コード領域を指し示す・データ領域を指し示す)毎に、そのメモリ領域の先頭を示すポインタであるのがセグメントレジスタである(これはリアルモードの場合、プロテクトモードではセグメントディスクリプタが入る)。
6個のセグメントレジスタは以下のとおりである。
ほとんどの現代的なオペレーティングシステム(LinuxやMicrosoft Windowsなど)で動くほとんどのアプリケーションは、ほとんど全てのセグメントレジスタが同じ場所を指し示すメモリモデルを採用しており(代わりにページングを使用している)、事実上これらの使用は無効になっている。 FSやGSはこの決まりの例外となっており、スレッド独自のデータを指し示すのに使われる。
EFLAGSは1個の32ビットレジスタで、操作の結果やプロセッサの状態の格納と制御のための判断材料として使用される。
これらのビットの名前は、以下のとおりである。
ビットの名前が0または1になっているものは予約されているので、変更されるべきではない。
命令ポインタ (EIP) は、分岐が起きない前提で、次に実行する命令のアドレスを保持している。
EIPはcall命令の直後にのみ読むことができる。
x86アーキテクチャはリトルエンディアンであり、16bitからなるワードや32bitからなるロングワードデータなどのマルチバイトデータの値は最下位バイトから順に書き込まれる。 これはバイト単位の順序についてであり、ビット単位の順序についてではない。
つまり、32ビットの値である0xB3B2B1B0は(以下0xは16進数を示す)、x86システムのメモリ上では以下のように表現される。
同様に、32ビットの倍精度ワード0x1BA583D4 は、メモリ上では以下となる。
したがって、メモリをバイト単位で表示すると0xD4, 0x83, 0xA5, 0x1Bのように見える。
2の補数というのは、2進数で負の整数を表現する標準的な方法である。 数値の符号を変えるには、全てのビットを逆にし、1を足せば良い。
00012は10進数での1である。
11112は10進数での-1である。
x86の各機械語は、動作の対象として(オペランドと呼ぶ)、上記レジスタ、メモリ空間あるいは数値(即値と呼ばれる)にアクセスするが、その方法は命令により限定されている。
アドレッシングモードとは、オペランドにアクセスする方法を示す、名称である。
あるいは、
スタックとは後入れ先出し方式のデータ構造で、データはスタックに上からプッシュされ、入れた順序を逆にポップしてくる。
AXの値をスタックの最上部にプッシュするとする。スタックは$006Aという値を保持している。
BXの値を同様にスタックにプッシュする。スタックは$006Aと$F78Aの値を保持している。
同様にCXの値もプッシュすると、スタックは$006A、$F79A、$1124を保持することとなる。
何かをする。関数はレジスタを使わないものとする。
スタックに最後にプッシュされた要素をポップさせてCXに読み込む。CXの値は$1124となり、スタックには$006A、$F79Aが保持されている。
スタックの最上部の要素をポップさせてBXに読み込む。BXの値は$F729となり、スタックには$006Aしかなくなる。
スタックの最上部の要素をAXに読み込む。AXの値は$006Aとなり、スタックは空となる。
スタックは通常、引数を関数や手続きに渡すために使われる。また、call命令が使われた時に制御フローを監視するためにも使われる。 その他のスタックのよくある使い方は、一時的にレジスタの値を保存しておくことである。
2010年代の現代のパソコンのアーキテクチャも、深層部は ほぼ16ビット時代の名残り(なごり)のままである。
市場のパソコンのアーキテクチャは事実上、昔からの技術のツギハギになっており、16ビットCPUの名残のモード(「リアルモード」)の上に32ビットCPUのモード(「プロテクトモード」という)を付け加え、さらにその上に64ビットCPUのモードを付け加えるという、ツギハギのような状態になっているのである。
コンピュータの電源投入の起動直後、CPUはまず(16ビット時代の名残りである)リアルモードを実行し、それを土台に(32ビット時代の名残りの)プロテクトモードに切り替え、必要ならさらに64ビット時代のモードに切り替える、という仕組みになっている。

リアルモードとは、16ビットCPUの時代のモードである。
この16ビットCPU時代のモードの構造が比較的に単純だからか、2010年代の今でもOSなどの深層部に残っており、ブート起動直後に動作するのは実は16ビット時代のモードである。この、ブート直後などの16ビット時代のモードのことを「リアルモード」とIT業界では呼んでいる。
リアルモードは、仮想メモリをサポートしていないし、マルチタスク処理をサポートしていない。
リアルモードは元々の(1978年の)インテル8086プロセッサからの遺産である(※ 1980年代の「80386」でなく「8086」。80386とは別物)。一般的にはこれについて何も知る必要はない ←(いや、アセンブラの技術者なら知る必要あるだろ。)(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ビットレジスタの代わりに使うことで、より効率的になる。 加えて、セグメントレジスタは一般的にはフラットモードでは使われず、これに触れるのは悪い考えであるといえる。
(未執筆)