panda's tech note

命令エンコード

(この文章は,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オペランドがメモリ参照の場合は,

  1. Mod=00b: [REG]
  2. Mod=01b: [REG]+disp8
  3. 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の値により,

  1. Scale=00b: [Index+Baes]
  2. Scale=01b: [Index*2+Base]
  3. Scale=10b: [Index*4+Base]
  4. 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に足します.