x86 および x86-64 の命令エンコーディング

最終更新: 2022-04-22 00:33:12 +0800 +0800
tags: Assembly | X86 | X86-64
原文は英語で書かれています。あなたはAI翻訳版を読んでいます。

x86 は可変長命令を使用する。命令には、レガシープレフィックス、オペコード、 ModR/M バイト、scale-index-base(SIB)バイト、ディスプレースメント、即値を 含められる。常に存在するのはオペコードだけである。

legacyprefixesopcodeModR/MSIBdisplacementimmediate

ロングモードでは、レガシープレフィックスとオペコードの間に REX プレフィックスを 追加できる。VEX、XOP、EVEX エンコーディングは、新しい命令ファミリー向けにこの配置の 一部を置き換える。形式にかかわらず、アーキテクチャ上の x86 命令は 15 バイトを超えられない。

各フィールドはアセンブリ言語のトークンをそのまま直列化したものではない。オペコードが オペランドの一部を符号化する場合も、ModR/M フィールドがオペコードを拡張する場合もある。 また、アドレス式は加算と乗算の列を要求するのではなく、プロセッサーのアドレッシング形式の 1 つを選択する。

演算とオペランドの境界は固定されていない #

アセンブリ構文では、命令ニーモニックの後に 0 個以上のオペランドを記述する。

nasm
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 ビットモードでは、これらの命令は次のようにエンコードされる。

text
ff 05 00 00 00 00    inc dword [0]
ff 0d 00 00 00 00    dec dword [0]

どちらもオペコードバイト FF を使う。ModR/M バイトは異なる。

text
05 = 00 000 101    mod=00, opcode extension=/0, r/m=101
0d = 00 001 101    mod=00, opcode extension=/1, r/m=101

32 ビットのアドレスサイズで mod=00、r/m=101 は、ディスプレースメントだけの アドレスを表す。4 個のゼロバイトが絶対 disp32 である。これらのエンコーディングでは reg フィールドが演算を選択するため、レジスターの指定には使えない。

オペコードに埋め込まれたレジスター #

一部のオペコードファミリーは、最後のオペコードバイトの下位 3 ビットにレジスター番号を 埋め込む。命令表では、レジスター幅に応じて +rb、+rw、+rd、+rq と表記する。

たとえば 1 バイトのオペコード 90 から 97 は、32 ビットモードで EAX と 8 個の 32 ビット汎用レジスターのいずれかを交換する。

アセンブリオペコード
xchg eax, eax90
xchg eax, ecx91
xchg eax, edx92
xchg eax, ebx93
xchg eax, esp94
xchg eax, ebp95
xchg eax, esi96
xchg eax, edi97

命令表では、このファミリーを 90+rd と記載する。一方、xchg ebx, ecx は オペコード 87 と ModR/M バイトを使う。

text
87 d9    xchg ebx, ecx

ほかの +r ファミリーには、PUSH、POP、即値を伴う MOV、BSWAP がある。 1 バイトの INC および DEC のレジスター形式が 40 から 4F を使うのは、 64 ビットモード以外だけである。ロングモードでは、それらのバイト値は REX プレフィックスに割り当てられている。

アセンブリのアドレス式はエンコーディングフィールドを選択する #

[eax + ebx*4 + 0x32] のような式は、算術命令をエンコードするものではない。 アドレス生成ハードウェアにその式を計算させる ModR/M、SIB、ディスプレースメントの 各フィールドを、アセンブラーに選択させる。

例を次に示す。

text
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 個である。エンコーダーは同じグループから 重複するプレフィックスを出力すべきではない。冗長または競合するプレフィックスの動作は、 プロセッサーによって異なってきたためである。

グループバイト用途
1F0, F2, F3LOCK、繰り返し、または必須オペコードの選択
22E, 36, 3E, 26, 64, 65セグメントオーバーライド。旧来の分岐ヒント
366オペランドサイズのオーバーライドまたは必須オペコードの選択
467アドレスサイズのオーバーライド

レガシーデコードでは全グループを番号順に並べる必要はないが、アセンブラーは慣例的な グループ順を使う。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 フィールドにエンコードする。

セグメントオーバーライドと分岐ヒント #

セグメントオーバーライドのバイトは次のとおりである。

バイトセグメント
2ECS
36SS
3EDS
26ES
64FS
65GS

16 ビットモードと 32 ビットモードでは、オーバーライドによってメモリーオペランドに 使うセグメントが変わる。64 ビットモードでは CS、DS、ES、SS のベースは ゼロとして扱われるため、通常は FS と GS のオーバーライドだけがアドレス形成に影響する。

一部の古いプロセッサーは、条件分岐に付いた 2E と 3E を、静的な不成立予測と 成立予測のヒントとして解釈していた。現在のソフトウェアはこれらのヒントに依存すべきではない。

オペランドサイズのオーバーライド #

66 は、対応する命令でデフォルト以外のオペランドサイズを選択する。32 ビットの コードセグメントでは、一般に 32 ビットと 16 ビットのオペランドを切り替える。

text
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 に分かれる。

7mod65r4eg32r1/m0

reg の意味はオペコードが定義する。オペランドレジスターを指定する場合と、 /0 から /7 のオペコード拡張を指定する場合がある。mod と r/m フィールドは、レジスターまたはメモリーのアドレッシング形式を選択する。

mod32 ビットおよび 64 ビットのアドレスサイズにおける一般的な意味
00メモリー。通常はディスプレースメントなし
01符号拡張された disp8 を伴うメモリー
10disp32 を伴うメモリー
11レジスターオペランド。メモリーアドレッシングなし

特殊な r/m 値と SIB 値によって、mod=00 の規則が変わる。

レジスター形式 #

mod=11 では、r/m がレジスターを指定する。reg がオペランドフィールドなら、 別のレジスターを指定する。拡張ビットがなければ、3 ビットのコードは次のように選択する。

コード32 ビット16 ビット下位 8 ビットレガシー上位 8 ビット
000EAXAXAL-
001ECXCXCL-
010EDXDXDL-
011EBXBXBL-
100ESPSPREX ありの SPLREX なしの AH
101EBPBPREX ありの BPLREX なしの CH
110ESISIREX ありの SILREX なしの DH
111EDIDIREX ありの DILREX なしの 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 のベースなし形式や、 アドレスをレジスターにロードする方法など、別のエンコーディング戦略が必要になる場合がある。

デコーダーは、有効アドレス形式を段階的に選択する。

Mmbnooaodds-R,eb/raM/oosmtrehesfrpoerrcmr/i/maml=100sS,IiB,bmodselectsdi0s,pl8a,ceomren3t2

16 ビットのアドレスサイズでは、BX、BP、SI、DI から構成される別の表を使い、 SIB バイトは使わない。そのアドレスサイズで mod=10 が選択するディスプレースメントは 16 ビットである。

SIB バイト #

32 ビットおよび 64 ビットのアドレスサイズ形式では、メモリー用 ModR/M の r/m=100 が SIB バイトを選択する。

7scal6e5in4dex32ba1se0

有効アドレスは次のとおりである。

text
base + index * scale + displacement

scale フィールドは次のようにエンコードする。

ビットスケール
001
012
104
118

拡張ビットがない場合、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 の値を占める。

706150403W2R1X0B
ビット機能
W対応する命令で 64 ビットオペランドを選択する
RModR/M の reg フィールドを拡張する
XSIB の index フィールドを拡張する
BModR/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 ビットオペランドを選択する。
  • スタック、分岐、固定幅の命令には、オペコード固有の規則がある。

例を次に示す。

text
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 は、古いモードで別の意味を持っていたバイト値を再利用する。モードと プレフィックスの有効性に関する規則によって、プロセッサーは形式を区別できる。 バイト単位でレガシープレフィックス規則を適用せず、完全なプレフィックス構造として デコードする。

実践的なデコード手順 #

依存関係の順番で命令をデコードする。

  1. 実行モード、コードセグメントのデフォルト、有効なアーキテクチャ拡張を確定する。
  2. 有効なレガシープレフィックスを読み取る。
  3. 該当する場合、REX、VEX、XOP、EVEX の形式を識別する。
  4. オペコードマップとオペコードを選択する。
  5. オペコード定義を参照し、ModR/M が必要かどうかを判定する。
  6. ModR/M がメモリーを選択し、SIB に対応するアドレスサイズで r/m=100 なら、SIB をデコードする。
  7. アドレスサイズ、mod、特殊なフィールド値からディスプレースメント幅を判定する。
  8. オペコードと有効なオペランドサイズから即値の幅を判定する。
  9. レジスター拡張フィールドと命令固有の意味規則を適用する。

単独のバイトだけで意味を判定することはできない。たとえば 66 は、オペランドサイズの オーバーライド、必須オペコードのセレクター、または VEX/EVEX の状態に間接的に エンコードされた値になり得る。したがって、信頼できるデコードにはプロセッサー マニュアルの命令表が必要である。

参考資料 #

コメント

GitHub で閲覧・コメントする(投稿にはログインが必要です)