命令エンコード
(この文章は,Intel Software Developer's Manual December 2017版に基づき書いています.)
x86 ISAの命令セットはIntel Software Developer's Manual (SDM) Volume 2に記載されています.ここでは,SDM Volume 2を読むにあたって,x86 ISAの命令エンコーディングについて説明します.
x86 (x86-64)の命令はプレフィックス,オペコード,オペランドから構成されます.より具体的には,Intel SDM Vol. 2AのFigure 2-1(以下に転載)の構成のように,オペランドはModR/M,SIB,Displacement,Immediate(即値)の4つの組み合わせとなります.
+-------------+------------+----------+---------+--------------+-------------+
| Instruction | Opcode | ModR/M | SIB | Displacement | Immediate |
| Prefixes | | | | | |
+-------------+------------+----------+---------+--------------+-------------+
Prefixes of 1-, 2- or 1 byte 1 byte Address Immediate
1 byte each 3-byte (if (if displacement data of 1,
(optional) opcode required) required) of 1, 2, or 4 2, or 4
bytes or none bytes or none
このうち,ModR/M,SIBはそれぞれ,Mod,Reg/Opcode,R/MおよびScale,Index,Baseの3つの値の組み合わせです.それぞれ,以下のようなビットマップで表します.
ModR/M
7 6 5 4 3 2 1 0
+--+--+--+--+--+--+--+--+
| Mod | Reg/ | R/M |
| | Opcode | |
+--+--+--+--+--+--+--+--+
SIB
7 6 5 4 3 2 1 0
+--+--+--+--+--+--+--+--+
|Scale| Index | Base |
| | | |
+--+--+--+--+--+--+--+--+
ModおよびR/Mフィールドの組み合わせで,レジスタまたはメモリ参照の1つのオペランドを指定します.Reg/Opcodeフィールドはレジスタを表すオペランドですが,オペランドが1つの場合,このフィールドには拡張オペコードが入ります.オペランドのエンコーディングはそれぞれの命令で異なるので,SDMのOperand Encodingのテーブルを参照します.
レジスタコード
オペランドでレジスタを指定する場合には各レジスタに対応したレジスタコードが使われます.32ビットCPUからあるレジスタについては,以下の表の通り,0から7までのコードを使います.なお,64ビットCPU(x86-64, Intel(R) 64)で追加されたR8-R15レジスタについては,REXプレフィックスとレジスタコードの組み合わせで表現します.
r8(/r) | r16(/r) | r32(/r) | r64(/r) | mm(/r) | xmm(/r) | レジスタコード |
---|---|---|---|---|---|---|
AL | AX | EAX | RAX | MM0 | XMM0 | 0 |
CL | CX | ECX | RCX | MM1 | XMM1 | 1 |
DL | DX | EDX | RDX | MM2 | XMM2 | 2 |
BL | BX | EBX | RBX | MM3 | XMM3 | 3 |
AH | SP | ESP | RSP | MM4 | XMM4 | 4 |
CH | BP | EBP | RBP | MM5 | XMM5 | 5 |
DH | SI | ESI | RSI | MM6 | XMM6 | 6 |
BH | DI | EDI | RDI | MM7 | XMM7 | 7 |
ModR/MのModおよびR/Mフィールドで表すオペランドは,レジスタまたはメモリ参照を指定します.レジスタの場合は,Mod=11b
でR/Mに上表のレジスタコードを入れます.R8-15レジスタについてはREX.Bをセットします.第1オペランドがメモリ参照の場合は,
- Mod=
00b
: [REG] - Mod=
01b
: [REG]+disp8 - Mod=
10b
: [REG]+disp32
として,REGに対応する番号をR/Mに入れます.ただし,Mod=00b
かつR/M=101b
のときは,disp32になります.つまり,ベースポインタ(EBP/RBP)からの相対アドレスは,[RBP]であってもMod=01b
または10b
によりdisp8またはdisp32を0にして指定します.また,R/M=100b
のときは,SIBを参照します.つまり,スタックポインタ(ESP/RSP)からの相対アドレスはR/M=100b
としてSIBのBaseレジスタにESP/RSPを指定します.SIBのエンコーディングは,Scaleの値により,
- Scale=
00b
: [Index+Baes] - Scale=
01b
: [Index*2+Base] - Scale=
10b
: [Index*4+Base] - Scale=
11b
: [Index*8+Base]
のようにBaseレジスタとIndexレジスタの{1,2,4,8}倍数の和のアドレスを表します.IndexがR8-15レジスタの場合はREX.Xをセットします.BaseにはR8-15レジスタは使えません.また,Indexにレジスタコード4が指定された場合は,[ESP/RSP*{1,2,4,8}+Base]ではなく[Base]となります.
ModR/MのReg/Opcodeフィールドについては,レジスタの場合は上表の対応するレジスタ番号を入れます.R8-15レジスタの場合はREX.Rをセットします.
MOV命令での例
例えば,MOV命令の一部を抜粋すると以下のように書かれています.Op/EnはOperand Encodingの略です.
MOV—Move
Opcode | Instruction | Op/En | 64/32-bit Mode | Compat/Leg Mode | Description |
---|---|---|---|---|---|
88 /r | MOV r/m8,r8 | MR | Valid | Valid | Move r8 to r/m8 |
89 /r | MOV r/m16,r16 | MR | Valid | Valid | Move r16 to r/m16 |
89 /r | MOV r/m32,r32 | MR | Valid | Valid | Move r32 to r/m32 |
REX.W 89 /r | MOV r/m64,r64 | MR | Valid | Valid | Move r64 to r/m64 |
8A /r | MOV r/m8,r8 | RM | Valid | Valid | Move r/m8 to r8 |
8B /r | MOV r/m16,r16 | RM | Valid | Valid | Move r/m16 to r16 |
8B /r | MOV r/m32,r32 | RM | Valid | Valid | Move r/m32 to r32 |
REX.W 8B /r | MOV r/m64,r64 | RM | Valid | Valid | Move r/m64 to r64 |
Operand Encoding
Op/En | Operand 1 | Operand 2 | Operand 3 | Operand 4 |
---|---|---|---|---|
MR | ModRM:r/m(w) | ModRM:reg(r) | NA | NA |
RM | ModRM:reg(w) | ModRM:r/m(r) | NA | NA |
Op/Enの項目を見るとMRとRMがあります.そしてOperand Encoding表を参照すると,MRは第1オペランドが ModRM:r/m(w),第2オペランドが ModRM:reg(r) となっています.一方,RMは第1オペランドと第2オペランドが逆になっています.
MOV命令では,MRは,Reg/Opcodeフィールドで指定されたレジスタ(第2オペランド)から,ModおよびR/Mフィールドで指定されたレジスタまたはメモリ参照(第1オペランド)にデータをコピーします.RMは,ModおよびR/Mフィールドで指定されたレジスタまたはメモリ参照(第2オペランド)から,Reg/Opcodeフィールドで指定されたレジスタ(第1オペランド)にデータをコピーします.
つまり,MRはアセンブリで表すと
mov [RDI-4],RAX # Intel記法
mov %rax,-4(%rdi) ; AT&T記法
のように,第1オペランドがメモリ参照,第2オペランドがレジスタになります.一方,RMは
mov RAX,[RDI] # Intel記法
mov -4(%rdi),%rax ; AT&T記法
のように第1オペランドがレジスタ,第2オペランドがメモリ参照となります.ここで,
mov RAX,RDX # Intel記法
mov %rdx,%rax ; AT&T記法
のように第1オペランド,第2オペランドが共にレジスタの場合は,MR,RMのどちらでもエンコードできますので,どちらのエンコーディングを用いるかは実装依存です.
Opcodeの修飾子の読み方
SDMではOpcodeに修飾子がついています.上記のMOV命令の場合は,/rやREX.Wがついていました.SDMで使用されている修飾子は以下の通りです.
- NP: 66/F2/F3プレフィックスが使用できない命令.
- REX.W: REXプレフィックスがオペランドサイズや命令のセマンティクスに影響する.
- /digit: digitは0から7の数字で,ModR/MのうちR/Mフィールドのみがオペランドとして使用される命令です.Regフィールドにはdigitの値を入れます.
- /r: ModR/MのうちR/MフィールドもRegフィールド(レジスタ)もオペランドとして使われます.
- cb, cw, cd, cp, co, ct: 1バイト(cb),2バイト(cw),4バイト(cd),6バイト(cp),8バイト(co),10バイト(ct)の値がOpcodeに続きます.この値は,コードオフセットやコードセグメントレジスタの新しい値を指定するのに使われます.
- ib, iw, id, io: 1バイト(ib),2バイト(iw),4バイト(id),8バイト(io)の即値がOpcode,ModR/MまたはSIBの後に続きます.値はリトルエンディアン.
- +rb, +rw, +rd, +ro: Opcodeの下位3ビットがModR/Mを使わずにレジスタオペランドとして使われます.非64ビットモードでは,0から7のレジスタコードがOpcodeの下位3ビットに入ります.64ビットモードでは,REX.BとOpcodeの下位3ビットがオペランドを表します.+ro(64ビットレジスタ)は64ビットモードでのみ使用されます.
- +i: 浮動小数点演算においてFPUレジスタスタックの1つ,ST(i)がオペランドであるときに使います.iの値をOpcodeに足します.