x86 は可変長命令を使用する。命令には、レガシープレフィックス、オペコード、 ModR/M バイト、scale-index-base(SIB)バイト、ディスプレースメント、即値を 含められる。常に存在するのはオペコードだけである。
ロングモードでは、レガシープレフィックスとオペコードの間に REX プレフィックスを 追加できる。VEX、XOP、EVEX エンコーディングは、新しい命令ファミリー向けにこの配置の 一部を置き換える。形式にかかわらず、アーキテクチャ上の x86 命令は 15 バイトを超えられない。
各フィールドはアセンブリ言語のトークンをそのまま直列化したものではない。オペコードが オペランドの一部を符号化する場合も、ModR/M フィールドがオペコードを拡張する場合もある。 また、アドレス式は加算と乗算の列を要求するのではなく、プロセッサーのアドレッシング形式の 1 つを選択する。
演算とオペランドの境界は固定されていない #
アセンブリ構文では、命令ニーモニックの後に 0 個以上のオペランドを記述する。
xchg eax, ecx
inc dword [eax]
mov edx, [ecx + 0x32251212]バイナリエンコーディングでは、ニーモニック専用のフィールドと各オペランド専用の 個別フィールドを確保していない。x86 は複数のコンパクトなエンコーディング戦略を使う。
ModR/M によるオペコード拡張 #
多くの命令は、3 ビットの ModR/M reg フィールドを追加のオペコードビットとして使う。
Intel と AMD の構文では、これらの形式を /0 から /7 と表す。たとえば、
FF /0 は INC r/m32、FF /1 は DEC r/m32 である。
32 ビットモードでは、これらの命令は次のようにエンコードされる。
ff 05 00 00 00 00 inc dword [0]
ff 0d 00 00 00 00 dec dword [0]どちらもオペコードバイト FF を使う。ModR/M バイトは異なる。
05 = 00 000 101 mod=00, opcode extension=/0, r/m=101
0d = 00 001 101 mod=00, opcode extension=/1, r/m=10132 ビットのアドレスサイズで mod=00、r/m=101 は、ディスプレースメントだけの
アドレスを表す。4 個のゼロバイトが絶対 disp32 である。これらのエンコーディングでは
reg フィールドが演算を選択するため、レジスターの指定には使えない。
オペコードに埋め込まれたレジスター #
一部のオペコードファミリーは、最後のオペコードバイトの下位 3 ビットにレジスター番号を
埋め込む。命令表では、レジスター幅に応じて +rb、+rw、+rd、+rq と表記する。
たとえば 1 バイトのオペコード 90 から 97 は、32 ビットモードで EAX と
8 個の 32 ビット汎用レジスターのいずれかを交換する。
| アセンブリ | オペコード |
|---|---|
xchg eax, eax | 90 |
xchg eax, ecx | 91 |
xchg eax, edx | 92 |
xchg eax, ebx | 93 |
xchg eax, esp | 94 |
xchg eax, ebp | 95 |
xchg eax, esi | 96 |
xchg eax, edi | 97 |
命令表では、このファミリーを 90+rd と記載する。一方、xchg ebx, ecx は
オペコード 87 と ModR/M バイトを使う。
87 d9 xchg ebx, ecxほかの +r ファミリーには、PUSH、POP、即値を伴う MOV、BSWAP がある。
1 バイトの INC および DEC のレジスター形式が 40 から 4F を使うのは、
64 ビットモード以外だけである。ロングモードでは、それらのバイト値は REX
プレフィックスに割り当てられている。
アセンブリのアドレス式はエンコーディングフィールドを選択する #
[eax + ebx*4 + 0x32] のような式は、算術命令をエンコードするものではない。
アドレス生成ハードウェアにその式を計算させる ModR/M、SIB、ディスプレースメントの
各フィールドを、アセンブラーに選択させる。
例を次に示す。
ff 44 98 32 inc dword [eax + ebx*4 + 0x32]各バイトは次のように分解できる。
FF /0はINC r/m32を選択する。- ModR/M
44は01 000 100であり、メモリーアドレッシング、/0、後続する SIB を表す。 - SIB
98は10 011 000であり、スケール 4、インデックスEBX、ベースEAXを表す。 32は 8 ビットのディスプレースメントである。
したがって、同じニーモニックでも、オペランドに応じて異なる長さとフィールド配置を使える。
レガシープレフィックス #
レガシープレフィックスは、意味に基づく 4 つのグループに分かれる。命令で有効にできる プレフィックスは、各グループにつき最大 1 個である。エンコーダーは同じグループから 重複するプレフィックスを出力すべきではない。冗長または競合するプレフィックスの動作は、 プロセッサーによって異なってきたためである。
| グループ | バイト | 用途 |
|---|---|---|
| 1 | F0, F2, F3 | LOCK、繰り返し、または必須オペコードの選択 |
| 2 | 2E, 36, 3E, 26, 64, 65 | セグメントオーバーライド。旧来の分岐ヒント |
| 3 | 66 | オペランドサイズのオーバーライドまたは必須オペコードの選択 |
| 4 | 67 | アドレスサイズのオーバーライド |
レガシーデコードでは全グループを番号順に並べる必要はないが、アセンブラーは慣例的な グループ順を使う。REX プレフィックスは異なる。REX が存在する場合は、レガシー プレフィックスの後、かつオペコードまたはオペコードマップのエスケープの直前に 配置しなければならない。
Lock プレフィックスと Repeat プレフィックス #
F0 は LOCK プレフィックスである。デスティネーションがメモリーであると規定された
読み出し・変更・書き込み命令だけで有効となる。有効なロック付き演算は、ほかの論理
プロセッサーに対してアトミックである。現代のプロセッサーは通常、対象キャッシュラインの
排他的所有権を保持しながら、キャッシュコヒーレンシによってこの性質を実現する。
すべての LOCK 命令で外部バスロックをアサートするわけではない。分割アクセスと
キャッシュ不可能なメモリーでは、より強い処理が必要になる場合がある。未対応の命令や
レジスターデスティネーションで LOCK を使うと #UD が発生する。
文字列演算では、F3 は REP または REPE/REPZ、F2 は REPNE/REPNZ を表す。
カウント値は、アドレスサイズによって選択される CX、ECX、RCX から取得する。
停止条件の詳細は命令によって異なる。
複数の SSE および後発の命令ファミリーでは、F2、F3、66 は繰り返し操作や
サイズ操作ではなく、必須のオペコード選択プレフィックスである。同じオペコードマップの
バイトが、そのプレフィックスに応じて異なる命令を識別できる。VEX と EVEX は、
同等の選択情報を pp フィールドにエンコードする。
セグメントオーバーライドと分岐ヒント #
セグメントオーバーライドのバイトは次のとおりである。
| バイト | セグメント |
|---|---|
2E | CS |
36 | SS |
3E | DS |
26 | ES |
64 | FS |
65 | GS |
16 ビットモードと 32 ビットモードでは、オーバーライドによってメモリーオペランドに
使うセグメントが変わる。64 ビットモードでは CS、DS、ES、SS のベースは
ゼロとして扱われるため、通常は FS と GS のオーバーライドだけがアドレス形成に影響する。
一部の古いプロセッサーは、条件分岐に付いた 2E と 3E を、静的な不成立予測と
成立予測のヒントとして解釈していた。現在のソフトウェアはこれらのヒントに依存すべきではない。
オペランドサイズのオーバーライド #
66 は、対応する命令でデフォルト以外のオペランドサイズを選択する。32 ビットの
コードセグメントでは、一般に 32 ビットと 16 ビットのオペランドを切り替える。
ff 05 00 00 00 00 inc dword [0]
66 ff 05 00 00 00 00 inc word [0]このプレフィックスは、INC がロードおよびストアする整数の幅を変える。この例の
32 ビットアドレスのディスプレースメントは変えない。オペランドサイズとアドレスサイズは
独立している。
64 ビットモードでは、整数オペランドのデフォルトサイズは一般に 32 ビットである。
対応する命令では、66 が 16 ビット、REX.W が 64 ビットを選択する。
個々のオペコード定義で異なる規則を定めることもできる。
アドレスサイズのオーバーライド #
67 はデフォルト以外のアドレスサイズを選択する。32 ビットのコードセグメントでは
16 ビットアドレッシングを選択できる。64 ビットモードでは 64 ビットではなく
32 ビットのアドレッシングを選択する。ロングモードには 16 ビットのアドレスサイズ
エンコーディングがない。
アドレスサイズは、ModR/M と SIB が使うレジスターとアドレッシング表を決定する。 また、文字列命令のカウントレジスターも選択する。それ自体で、ロード、ストア、 計算される値の幅が変わることはない。
オペコードマップ #
レガシーエンコーディングは複数のオペコードマップを使う。
- 1 バイトマップには、
0Fエスケープ以外のオペコードが含まれる。 0F xxは 2 バイトマップを選択する。0F 38 xxと0F 3A xxは、追加の 3 バイトマップを選択する。
オペコードには、方向、幅、条件コード、レジスターのフィールドを含められる。 後ろに ModR/M、ディスプレースメント、即値の各フィールドが続くかどうかは、 オペコードバイト数だけではなく命令マニュアルによって決まる。
VEX と EVEX プレフィックスは、レガシーの 0F、0F 38、0F 3A バイトを
そのまま記述する代わりに、オペコードマップセレクターをエンコードする。また、
必須プレフィックスの状態、ベクトル長、追加のレジスタービット、ソースオペランドも
エンコードする。したがって、プレフィックスに見えるこれらのバイトは、独立した
レガシー修飾子の列ではなく、別個のエンコーディング形式の一部である。
ModR/M バイト #
ModR/M は mod、reg、r/m に分かれる。
reg の意味はオペコードが定義する。オペランドレジスターを指定する場合と、
/0 から /7 のオペコード拡張を指定する場合がある。mod と r/m
フィールドは、レジスターまたはメモリーのアドレッシング形式を選択する。
mod | 32 ビットおよび 64 ビットのアドレスサイズにおける一般的な意味 |
|---|---|
00 | メモリー。通常はディスプレースメントなし |
01 | 符号拡張された disp8 を伴うメモリー |
10 | disp32 を伴うメモリー |
11 | レジスターオペランド。メモリーアドレッシングなし |
特殊な r/m 値と SIB 値によって、mod=00 の規則が変わる。
レジスター形式 #
mod=11 では、r/m がレジスターを指定する。reg がオペランドフィールドなら、
別のレジスターを指定する。拡張ビットがなければ、3 ビットのコードは次のように選択する。
| コード | 32 ビット | 16 ビット | 下位 8 ビット | レガシー上位 8 ビット |
|---|---|---|---|---|
000 | EAX | AX | AL | - |
001 | ECX | CX | CL | - |
010 | EDX | DX | DL | - |
011 | EBX | BX | BL | - |
100 | ESP | SP | REX ありの SPL | REX なしの AH |
101 | EBP | BP | REX ありの BPL | REX なしの CH |
110 | ESI | SI | REX ありの SIL | REX なしの DH |
111 | EDI | DI | REX ありの DIL | REX なしの BH |
コードが汎用、MMX、ベクトル、制御、デバッグ、その他のどのレジスタークラスを 識別するかは、オペランドサイズと命令クラスによって決まる。REX、VEX、EVEX の 拡張ビットで、より大きな番号のレジスターを追加できる。
メモリー形式 #
mod が 11 以外なら、r/m はメモリーアドレッシング形式を選択する。
32 ビットのアドレスサイズでは次の規則が適用される。
- ほとんどの
r/m値はベースレジスターを選択する。 r/m=100は後続の SIB バイトを選択する。mod=00, r/m=101はベースを持たずdisp32から成るアドレスを選択する。mod=01は符号拡張されたdisp8を加える。mod=10はdisp32を加える。
64 ビットのアドレスサイズでは、mod=00, r/m=101 は代わりに
RIP + sign-extended disp32 を選択する。RIP 相対アドレッシングは、近接するコードや
データへの位置独立参照に対応する。絶対アドレスには、SIB のベースなし形式や、
アドレスをレジスターにロードする方法など、別のエンコーディング戦略が必要になる場合がある。
デコーダーは、有効アドレス形式を段階的に選択する。
16 ビットのアドレスサイズでは、BX、BP、SI、DI から構成される別の表を使い、
SIB バイトは使わない。そのアドレスサイズで mod=10 が選択するディスプレースメントは
16 ビットである。
SIB バイト #
32 ビットおよび 64 ビットのアドレスサイズ形式では、メモリー用 ModR/M の
r/m=100 が SIB バイトを選択する。
有効アドレスは次のとおりである。
base + index * scale + displacementscale フィールドは次のようにエンコードする。
| ビット | スケール |
|---|---|
00 | 1 |
01 | 2 |
10 | 4 |
11 | 8 |
拡張ビットがない場合、index=100 はインデックスなしを意味する。ロングモードでは、
拡張ビットによって追加のレジスターを選択できる。mod=00 で base=101 は
ベースなしを意味し、disp32 が必須となる。mod=01 または 10 では、
同じ base ビットが EBP または RBP を選択し、mod が示す
ディスプレースメントを使う。
したがって、スケール付きインデックスが不要な場合でも、ESP または RSP を
ベースにすると SIB バイトが必要になる。アセンブラーはこの表現上の詳細を隠蔽する。
ディスプレースメントと即値 #
ディスプレースメントは有効アドレスの計算に使われる。その有無とエンコード幅は、 アドレッシング形式によって決まる。
disp8は有効なアドレスサイズまで符号拡張される。disp16は、選択された 16 ビットアドレッシング形式で使われる。disp32は、32 ビット形式と、ロングモードのベース相対または RIP 相対形式で使われる。
即値は、命令に埋め込まれたオペランド値である。その幅はデスティネーション幅から 直接決まるのではなく、オペコード固有である。例を次に示す。
- 符号拡張された
imm8を使う、16 ビットまたは 32 ビットオペランドへの算術演算 - 符号拡張された
imm32を使う、64 ビットオペランドへの算術演算 - 完全な 64 ビット即値を格納できる
MOV r64, imm64 - カウントに暗黙の値 1、レジスター
CL、またはエンコードされたimm8を使うシフト命令
ディスプレースメントと即値はリトルエンディアンで格納される。逆アセンブラーが フィールドの境界を判定するには、事前にオペコードとモードを知る必要がある。
ロングモードの REX エンコーディング #
1 バイトの REX プレフィックスは 0100WRXB 形式で、40 から 4F の値を占める。
| ビット | 機能 |
|---|---|
W | 対応する命令で 64 ビットオペランドを選択する |
R | ModR/M の reg フィールドを拡張する |
X | SIB の index フィールドを拡張する |
B | ModR/M の r/m、SIB の base、またはオペコード埋め込みレジスターを拡張する |
対応する 64 ビットオペランドを使う、R8 から R15 を参照する、または
バイトレジスター SPL、BPL、SIL、DIL を必要とする命令には、
REX プレフィックスが必要である。REX はオペコードの直前になければならない。
REX バイトの後にレガシープレフィックスを置くと、そのバイトは命令の有効な
REX プレフィックスとして機能しない。
REX プレフィックスが 1 つでもあると、その命令では AH、BH、CH、DH を
使えなくなる。これらのレガシーエンコーディングは、REX で導入された下位バイト
レジスターのコードと重複する。
ロングモードのデフォルトは、一律に 64 ビットではない。
- アドレスサイズのデフォルトは 64 ビットであり、
67は 32 ビットを選択する。 - ほとんどの整数命令のオペランドはデフォルトで 32 ビットである。
66は一般に 16 ビットオペランドを選択する。REX.Wは、定義されている場合に 64 ビットオペランドを選択する。- スタック、分岐、固定幅の命令には、オペコード固有の規則がある。
例を次に示す。
48 8b 44 8b 20 mov rax, [rbx + rcx*4 + 0x20]48はREX.Wである。8BはMOV r64, r/m64である。- ModR/M
44は、メモリーオペランド、デスティネーションレジスターRAX、 SIB バイト、disp8を選択する。 - SIB
8Bは、スケール 4、インデックスRCX、ベースRBXを選択する。 20はディスプレースメントである。
VEX と EVEX #
REX は従来の汎用エンコーディングを拡張するが、ベクトル命令ファミリーにはさらに多くの 状態が必要である。VEX は、オペコードマップ、必須プレフィックスの選択、ベクトル長、 レジスター拡張、追加のソースレジスターフィールドをエンコードする。一般的な VEX 形式は 2 バイトまたは 3 バイトを使う。
EVEX はさらに、マスキング、ゼロ化、ブロードキャスト、丸め、より大きなベクトル長のための
レジスタービットと制御を追加する。一部の EVEX メモリーオペランドは圧縮された
ディスプレースメントスケーリングを使うため、エンコードされた disp8 は、
アドレス計算前にタプル依存の係数で乗算される。
VEX と EVEX は、古いモードで別の意味を持っていたバイト値を再利用する。モードと プレフィックスの有効性に関する規則によって、プロセッサーは形式を区別できる。 バイト単位でレガシープレフィックス規則を適用せず、完全なプレフィックス構造として デコードする。
実践的なデコード手順 #
依存関係の順番で命令をデコードする。
- 実行モード、コードセグメントのデフォルト、有効なアーキテクチャ拡張を確定する。
- 有効なレガシープレフィックスを読み取る。
- 該当する場合、REX、VEX、XOP、EVEX の形式を識別する。
- オペコードマップとオペコードを選択する。
- オペコード定義を参照し、ModR/M が必要かどうかを判定する。
- ModR/M がメモリーを選択し、SIB に対応するアドレスサイズで
r/m=100なら、SIB をデコードする。 - アドレスサイズ、
mod、特殊なフィールド値からディスプレースメント幅を判定する。 - オペコードと有効なオペランドサイズから即値の幅を判定する。
- レジスター拡張フィールドと命令固有の意味規則を適用する。
単独のバイトだけで意味を判定することはできない。たとえば 66 は、オペランドサイズの
オーバーライド、必須オペコードのセレクター、または VEX/EVEX の状態に間接的に
エンコードされた値になり得る。したがって、信頼できるデコードにはプロセッサー
マニュアルの命令表が必要である。
コメント
GitHub で閲覧・コメントする(投稿にはログインが必要です)