エンコーディング

エンコーディング
アセンブラを書く際に意識する必要はありませんが、CPU 命令がどのような形でバイナリにエンコードされるかについて理解しておくと、役立つ場合があります。

ここでは、命令のエンコーディングについて、簡単に説明していきます。
命令エンコーディング
一つの命令は、1〜15 バイトの可変長として、エンコードされます。
一つの命令を表すバイト全体は、「命令エンコーディング」と呼ばれます。

命令エンコーディングは、基本的に以下の順で、各情報が並びます。

  • プレフィックス (複数バイト)
  • オペコード (1 byte)
  • ModRM (1 byte)
  • SIB (1 byte)
  • ディスプレイスメント (アドレス指定のオフセット値)
  • オペランドなどの即値
オペコード
オペコードは、命令の基本的な動作を指定する1バイトです。
すべての命令には、オペコードが必要です。

1バイトで表現できない命令は、エスケープ・プレフィックスを前に付けます。
プレフィックス
一つの命令のエンコードに対して、最大4つのプレフィックスバイトが使用できます。
レガシープレフィックス
以下は、x86 から存在しているプレフィックスです。

命令説明
66h-オペランドサイズの上書き。
レジスタ/メモリのオペランドサイズを変更します。
64bit モードの場合、このプレフィックスがあると、16bit のサイズを指定することになります。

※SSE 命令などでは、オペコードを拡張するために利用されます。
67h-アドレスサイズの上書き。
オペランドでメモリのアドレス指定を行う際の、アドレスサイズを変更します。
(アドレス指定で使われる、base と index のレジスタのサイズも決まります)

LOOPx 命令などで、暗黙のカウントレジスタとして使用される場合は、rCX レジスタのサイズも変更します。

64bit モードの場合、プレフィックスなしで 64bit、プレフィックスありで 32bit のサイズになります。
2EhCSセグメント指定の上書き。
オペランドでメモリのアドレス指定を行う際、CS セグメントを指定します。
※64bit モードでは、CS、DS、ES、SS セグメントの上書き指定は無視されます。
3EhDSDS セグメント
26hESES セグメント
64hFSFS セグメント
65hGSGS セグメント
36hSSSS セグメント
F0hLOCKメモリの読み込み/変更/書き込み命令を、アトミックに発生させる。
※サポートしているのは、一部の命令のみ。
F3hREP
REPE
REPZ
MOVSx などのメモリ操作命令を、rCX の回数分繰り返す。
REP は比較のない命令時に使います。
REPE/REPZ は、比較があり、条件が Z = 1 の場合に繰り返します。
※SSE 命令などでは、オペコードを拡張するために利用されます。
F2hREPNE
REPNZ
メモリ操作命令の繰り返し (ZF = 0)。
※SSE 命令などでは、オペコードを拡張するために利用されます。
REX プレフィックス
REX プレフィックスは、64bit モードでのみ使用できます。
REX プレフィックスは、オペコードまたは最初のエスケープバイトの直前に、1つだけ付けることができます。

REX は、1バイト内に5つのフィールドが含まれ、値は 40h〜4Fh の範囲になります。

|7 - 4| 3 | 2 | 1 | 0 | bit
|  4  | W | R | X | B |

REX.W1 の場合、オペランドサイズが 64bit に変更されます。
REX.RModRM.reg フィールドを拡張するためのビット。
1 の場合、x64 で拡張されたレジスタを指定できます。
REX.XSIB.index フィールドを拡張するためのビット。
メモリのアドレス指定時、index レジスタに、x64 で拡張されたレジスタを指定できるようになります。
REX.BModRM.r/m、または SIB.base フィールドのいずれかを拡張するためのビット。
オペランドのレジスタや、アドレス指定の base レジスタで、x64 で拡張されたレジスタを指定できるようになります。
エスケープシーケンス
エスケープシーケンスは、プレフィックスの一種です。
オペコードの前に付けられ、1バイトで表現できない命令に対して、追加の情報を指定します。

エスケープシーケンスの長さは、1〜3 バイトです。

エスケープシーケンスには、「レガシー・エスケープシーケンス」と「拡張エスケープシーケンス」があります。
レガシー・エスケープシーケンス
x86 から存在するエスケープシーケンスです。
1バイトまたは2バイトのシーケンスになります。

0F2byte オペコードマップ
0F 380F_38h オペコードマップ
0F 3A0F_3Ah オペコードマップ
拡張エスケープシーケンス
拡張エスケープシーケンスは、AVX 命令などで使われます。

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 |

prefixVEX (C4h)
XOP (8Fh)
RModRM.reg フィールドを拡張するためのビット (REX と同じ)
XSIB.index フィールドを拡張するためのビット (REX と同じ)
BModRM.r/m または SIB.base フィールドを拡張するためのビット (REX と同じ)
map_select (5bit)オペコードマップの選択。

00000b : 予約
00001b : VEX オペコードマップ 1 (2byte オペコードマップ)
00010b : VEX オペコードマップ 2 (0F_38h オペコードマップ)
00011b : VEX オペコードマップ 3 (0F_3Ah オペコードマップ)
W1 の場合、オペランドサイズが 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
LL = 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 の動作モードでアドレス指定を使う場合の説明となっています。
ModRM
ModRM は1バイトで、3つのフィールドに分かれます。

| 7 6 | 5 4 3 | 2 1 0 | bit
| mod |  reg  |  r/m  |

mod11b は、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/mmod と組み合わせることで、オペランドのレジスタまたはアドレス指定の形式を決定します。
SIB
SIB は1バイトで、3つのフィールドに分かれます。
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 でレジスタを指定する場合、基本的には以下のように適用されます。

-01234567
GPRrAXrCXrDXrBXrSP/AHrBP/CHrSI/DHrDI/BH
GPR (拡張)r8r9r10r11r12r13r14r15
mmxMMX0MMX1MMX2MMX3MMX4MMX5MMX6MMX7
xmmXMM0XMM1XMM2XMM3XMM4XMM5XMM6XMM7
xmm (拡張)XMM8XMM9XMM10XMM11XMM12XMM13XMM14XMM15
ymmYMM0YMM1YMM2YMM3YMM4YMM5YMM6YMM7
ymm (拡張)YMM8YMM9YMM10YMM11YMM12YMM13YMM14YMM15

※拡張は、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 に符号拡張されます。
即値
即値は、オペランドの値として使われ、命令エンコーディングの最後に置かれます。
※一部の命令では、即値バイトを他の用途で使用する場合があります。

各命令と動作モードに応じて、8bit, 16bit, 32bit, 64bit の値になります。

64bit の即値は、64bit モード時の MOV 命令でのみ許可されます。