エンコーディング
アセンブラを書く際に意識する必要はありませんが、CPU 命令がどのような形でバイナリにエンコードされるかについて理解しておくと、役立つ場合があります。
ここでは、命令のエンコーディングについて、簡単に説明していきます。
ここでは、命令のエンコーディングについて、簡単に説明していきます。
命令エンコーディング
一つの命令は、1〜15 バイトの可変長として、エンコードされます。
一つの命令を表すバイト全体は、「命令エンコーディング」と呼ばれます。
命令エンコーディングは、基本的に以下の順で、各情報が並びます。
一つの命令を表すバイト全体は、「命令エンコーディング」と呼ばれます。
命令エンコーディングは、基本的に以下の順で、各情報が並びます。
- プレフィックス (複数バイト)
- オペコード (1 byte)
- ModRM (1 byte)
- SIB (1 byte)
- ディスプレイスメント (アドレス指定のオフセット値)
- オペランドなどの即値
オペコード
オペコードは、命令の基本的な動作を指定する1バイトです。
すべての命令には、オペコードが必要です。
1バイトで表現できない命令は、エスケープ・プレフィックスを前に付けます。
すべての命令には、オペコードが必要です。
1バイトで表現できない命令は、エスケープ・プレフィックスを前に付けます。
プレフィックス
一つの命令のエンコードに対して、最大4つのプレフィックスバイトが使用できます。
レガシープレフィックス
以下は、x86 から存在しているプレフィックスです。
値 | 命令 | 説明 |
66h | - | オペランドサイズの上書き。 レジスタ/メモリのオペランドサイズを変更します。 64bit モードの場合、このプレフィックスがあると、16bit のサイズを指定することになります。 ※SSE 命令などでは、オペコードを拡張するために利用されます。 |
---|---|---|
67h | - | アドレスサイズの上書き。 オペランドでメモリのアドレス指定を行う際の、アドレスサイズを変更します。 (アドレス指定で使われる、base と index のレジスタのサイズも決まります) LOOPx 命令などで、暗黙のカウントレジスタとして使用される場合は、rCX レジスタのサイズも変更します。 64bit モードの場合、プレフィックスなしで 64bit、プレフィックスありで 32bit のサイズになります。 |
2Eh | CS | セグメント指定の上書き。 オペランドでメモリのアドレス指定を行う際、CS セグメントを指定します。 ※64bit モードでは、CS、DS、ES、SS セグメントの上書き指定は無視されます。 |
3Eh | DS | DS セグメント |
26h | ES | ES セグメント |
64h | FS | FS セグメント |
65h | GS | GS セグメント |
36h | SS | SS セグメント |
F0h | LOCK | メモリの読み込み/変更/書き込み命令を、アトミックに発生させる。 ※サポートしているのは、一部の命令のみ。 |
F3h | REP REPE REPZ | MOVSx などのメモリ操作命令を、rCX の回数分繰り返す。 REP は比較のない命令時に使います。 REPE/REPZ は、比較があり、条件が Z = 1 の場合に繰り返します。 ※SSE 命令などでは、オペコードを拡張するために利用されます。 |
F2h | REPNE REPNZ | メモリ操作命令の繰り返し (ZF = 0)。 ※SSE 命令などでは、オペコードを拡張するために利用されます。 |
REX プレフィックス
REX プレフィックスは、64bit モードでのみ使用できます。
REX プレフィックスは、オペコードまたは最初のエスケープバイトの直前に、1つだけ付けることができます。
REX は、1バイト内に5つのフィールドが含まれ、値は 40h〜4Fh の範囲になります。
REX プレフィックスは、オペコードまたは最初のエスケープバイトの直前に、1つだけ付けることができます。
REX は、1バイト内に5つのフィールドが含まれ、値は 40h〜4Fh の範囲になります。
|7 - 4| 3 | 2 | 1 | 0 | bit | 4 | W | R | X | B |
REX.W | 1 の場合、オペランドサイズが 64bit に変更されます。 |
---|---|
REX.R | ModRM.reg フィールドを拡張するためのビット。 1 の場合、x64 で拡張されたレジスタを指定できます。 |
REX.X | SIB.index フィールドを拡張するためのビット。 メモリのアドレス指定時、index レジスタに、x64 で拡張されたレジスタを指定できるようになります。 |
REX.B | ModRM.r/m、または SIB.base フィールドのいずれかを拡張するためのビット。 オペランドのレジスタや、アドレス指定の base レジスタで、x64 で拡張されたレジスタを指定できるようになります。 |
エスケープシーケンス
エスケープシーケンスは、プレフィックスの一種です。
オペコードの前に付けられ、1バイトで表現できない命令に対して、追加の情報を指定します。
エスケープシーケンスの長さは、1〜3 バイトです。
エスケープシーケンスには、「レガシー・エスケープシーケンス」と「拡張エスケープシーケンス」があります。
オペコードの前に付けられ、1バイトで表現できない命令に対して、追加の情報を指定します。
エスケープシーケンスの長さは、1〜3 バイトです。
エスケープシーケンスには、「レガシー・エスケープシーケンス」と「拡張エスケープシーケンス」があります。
レガシー・エスケープシーケンス
x86 から存在するエスケープシーケンスです。
1バイトまたは2バイトのシーケンスになります。
1バイトまたは2バイトのシーケンスになります。
0F | 2byte オペコードマップ |
---|---|
0F 38 | 0F_38h オペコードマップ |
0F 3A | 0F_3Ah オペコードマップ |
拡張エスケープシーケンス
拡張エスケープシーケンスは、AVX 命令などで使われます。
VEX と XOP の2つがあり、XOP は AMD プロセッサのみで使われます。
VEX には、C4h と C5h の2つのプレフィックスがあり、それぞれ、3バイトと2バイトのシーケンスになります。
VEX と XOP の2つがあり、XOP は AMD プロセッサのみで使われます。
VEX には、C4h と C5h の2つのプレフィックスがあり、それぞれ、3バイトと2バイトのシーケンスになります。
3バイトシーケンス
|- byte0 -|--- byte1 --------------|--- byte2 ----------| | | 7 | 6 | 5 | 4 0 | 7 | 6 3 | 2 | 1 0 | bit | prefix | R | X | B | map_select | W | vvvv | L | pp |
prefix | VEX (C4h) XOP (8Fh) |
---|---|
R | ModRM.reg フィールドを拡張するためのビット (REX と同じ) |
X | SIB.index フィールドを拡張するためのビット (REX と同じ) |
B | ModRM.r/m または SIB.base フィールドを拡張するためのビット (REX と同じ) |
map_select (5bit) | オペコードマップの選択。 00000b : 予約 00001b : VEX オペコードマップ 1 (2byte オペコードマップ) 00010b : VEX オペコードマップ 2 (0F_38h オペコードマップ) 00011b : VEX オペコードマップ 3 (0F_3Ah オペコードマップ) |
W | 1 の場合、オペランドサイズが 64bit に変更されます (REX と同じ) |
vvvv (4bit) | 3つ目または4つ目のレジスタオペランドを指定する。0000: XMM15/YMM15 | 1000: XMM7/YMM7 0001: XMM14/YMM14 | 1001: XMM6/YMM6 0010: XMM13/YMM13 | 1010: XMM5/YMM5 0011: XMM12/YMM12 | 1011: XMM4/YMM4 0100: XMM11/YMM11 | 1100: XMM3/YMM3 0101: XMM10/YMM10 | 1101: XMM2/YMM2 0110: XMM9/YMM9 | 1110: XMM1/YMM1 0111: XMM8/YMM8 | 1111: XMM0/YMM0 |
L | L = 0 は、128bit (XMM レジスタ/128bit メモリ)。 L = 1 は、256bit (YMM レジスタ/256bit メモリ)。 |
pp | 暗黙的な 66, F2, F3 プレフィックスを指定し、オペコード拡張として使用します。 00 : なし 01 : 66h 10 : F3h 11 : F2h |
2バイトシーケンス
3バイトシーケンスの一部の値が、特定の組み合わせの場合、コンパクトな2バイトシーケンスに置き換えることができます。|- byte0 -|-- byte1 -----------| | | 7 | 6 3 | 2 | 1 0 | bit | C5 | R | vvvv | L | pp | X = 1, B = 1, W = 0, map_select = 00001b
ModRM と SIB
「ModRM (mode-register-memory)」の1バイトは、オペコードの直後に続き、命令のオペランドの指定を行います。
2つのレジスタ、または、1つのレジスタとメモリアドレス指定を行うことができます。
「SIB (scale-index-base)」の1バイトは、オペランドでメモリアドレス指定がある場合、ModRM の直後に続きます。
「base + offset + scale * index」の形式において、base と index レジスタや scale の値の指定を行います。
[base] または [base + offset] の形式の場合、ModRM のバイトだけで、メモリアドレス指定を行うことができる場合があります。
scale * index の指定がある場合や、base に特定のレジスタを使う場合は、SIB バイトが必要になります。
※以下は、32/64bit の動作モードでアドレス指定を使う場合の説明となっています。
2つのレジスタ、または、1つのレジスタとメモリアドレス指定を行うことができます。
「SIB (scale-index-base)」の1バイトは、オペランドでメモリアドレス指定がある場合、ModRM の直後に続きます。
「base + offset + scale * index」の形式において、base と index レジスタや scale の値の指定を行います。
[base] または [base + offset] の形式の場合、ModRM のバイトだけで、メモリアドレス指定を行うことができる場合があります。
scale * index の指定がある場合や、base に特定のレジスタを使う場合は、SIB バイトが必要になります。
※以下は、32/64bit の動作モードでアドレス指定を使う場合の説明となっています。
ModRM
ModRM は1バイトで、3つのフィールドに分かれます。
| 7 6 | 5 4 3 | 2 1 0 | bit | mod | reg | r/m |
mod | 11b は、r/m でオペランドのレジスタを指定。 11b 以外は、メモリアドレス指定の形式を決定します (オフセット値のありなしと、そのサイズの指定)。 00b: offset なし、または disp32 r/m = 4 (rSP/r12) 時は、SIB を使って [base + (scale * index)] または [(scale * index) + disp32]。 r/m = 5 (rBP/r13) 時は、64bit モード時 [rIP + disp32]。それ以外は [disp32]。 r/m がそれ以外の場合は、[base(r/m)] 01b: offset は 8bit r/m = 4 時は、SIB を使って [base + (scale * index) + disp8] (base は 0 にできない)。 r/m がそれ以外の場合は、[base(r/m) + disp8] 10b: offset は 32bit r/m = 4 時は、SIB を使って [base + (scale * index) + disp32] (base は 0 にできない)。 r/m がそれ以外の場合は、[base(r/m) + disp32] |
---|---|
reg | レジスタのオペランドを指定します。 ※一部の命令では、演算エンコーディングを拡張するために使用されます。 |
r/m | mod と組み合わせることで、オペランドのレジスタまたはアドレス指定の形式を決定します。 |
SIB
SIB は1バイトで、3つのフィールドに分かれます。
ModRM のバイトでアドレス指定が表現できない場合に使用されます。
ModRM のバイトでアドレス指定が表現できない場合に使用されます。
| 7 6 | 5 4 3 | 2 1 0 | bit | scale | index | base |
scale | インデックスを乗算する時の、スケール係数。 00b = 1 01b = 2 10b = 4 11b = 8 |
---|---|
index | インデックス値として使うレジスタの指定。 index = 4 で、REX(VEX) プレフィックスなし、または REX(VEX).X = 0 の場合は、index * scale が 0 として扱われます (index = 4 は rSP ですが、rSP * scale を使うようなことはないため)。 |
base | アドレスのベース値として使うレジスタの指定。 base = 5 (rBP/r13) で ModRM.mod = 0 の場合は、ベースの値を 0 とし、disp32 が使われます。 それ以外は、base のレジスタを、ベース位置として扱います。 |
レジスタの指定
ModRM.reg, ModRM.r/m, SIB.base, SIB.index でレジスタを指定する場合、基本的には以下のように適用されます。
※拡張は、REX や VEX プレフィックスの指定ビットが 1 の場合。
※オペランドサイズが 8bit 時の rSP〜rDI の場合、REX がない時は AH, CH, DH, BH。REX があって、対応する拡張ビットが 0 の時は SPL, BPL, SIL, DIL。
- | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
GPR | rAX | rCX | rDX | rBX | rSP/AH | rBP/CH | rSI/DH | rDI/BH |
---|---|---|---|---|---|---|---|---|
GPR (拡張) | r8 | r9 | r10 | r11 | r12 | r13 | r14 | r15 |
mmx | MMX0 | MMX1 | MMX2 | MMX3 | MMX4 | MMX5 | MMX6 | MMX7 |
xmm | XMM0 | XMM1 | XMM2 | XMM3 | XMM4 | XMM5 | XMM6 | XMM7 |
xmm (拡張) | XMM8 | XMM9 | XMM10 | XMM11 | XMM12 | XMM13 | XMM14 | XMM15 |
ymm | YMM0 | YMM1 | YMM2 | YMM3 | YMM4 | YMM5 | YMM6 | YMM7 |
ymm (拡張) | YMM8 | YMM9 | YMM10 | YMM11 | YMM12 | YMM13 | YMM14 | YMM15 |
※拡張は、REX や VEX プレフィックスの指定ビットが 1 の場合。
※オペランドサイズが 8bit 時の rSP〜rDI の場合、REX がない時は AH, CH, DH, BH。REX があって、対応する拡張ビットが 0 の時は SPL, BPL, SIL, DIL。
ディスプレイスメントと即値
ディスプレイスメントと即値は、1,2,4,8 バイトの値を直接出力します (リトルエンディアン)。
ディスプレイスメント
ディスプレイスメントは、オフセットとも呼ばれます。
オペランドでメモリのアドレス指定が行われており、その際にオフセットの数値が必要な場合は、ModRM または SIB バイトの後に続きます。
符号付きで、8bit, 16bit, 32bit の値のいずれかです。
サイズは、ModRM.mod の値と動作モードによって決まります。
64bit モードの場合は、アドレスの計算中に 64bit に符号拡張されます。
オペランドでメモリのアドレス指定が行われており、その際にオフセットの数値が必要な場合は、ModRM または SIB バイトの後に続きます。
符号付きで、8bit, 16bit, 32bit の値のいずれかです。
サイズは、ModRM.mod の値と動作モードによって決まります。
64bit モードの場合は、アドレスの計算中に 64bit に符号拡張されます。
即値
即値は、オペランドの値として使われ、命令エンコーディングの最後に置かれます。
※一部の命令では、即値バイトを他の用途で使用する場合があります。
各命令と動作モードに応じて、8bit, 16bit, 32bit, 64bit の値になります。
64bit の即値は、64bit モード時の MOV 命令でのみ許可されます。
※一部の命令では、即値バイトを他の用途で使用する場合があります。
各命令と動作モードに応じて、8bit, 16bit, 32bit, 64bit の値になります。
64bit の即値は、64bit モード時の MOV 命令でのみ許可されます。