4. 指令 Opcode 码


上一页 返回目录 下一页

x86/x64 通用指令编码的核心是:OpcodeModRM 以及 SIB

因此:指令编码设计模式是 Opcode 的设计要考虑兼顾 ModRM,ModRM 要服务于 Opcode,SIB 是对 ModRM 的补充辅助。

关于 opcode 的组成结构,请详见《x86/x64 指令编码构造》一文:http://www.mouseos.com/x64/encode.html

 

1. 初窥 Opcode

在 1 个字节的空间里:00 - FF,Prefix 与 Opcode 共同占用这个空间。

由于 x86/x64 是 CISC 架构,指令不定长。解码器解码的唯一途径就是按指令编码的序列进行解码,关键是第 1 字节是什么? 如:遇到 66h,它就是 prefix,遇到 89h,它就是 Opcode。

Prefix 与 Opcode 共享空间的原因是:Prefix 是可选的。在编码序列里,只有 Opcode 是不可缺少的,其它都是可选。这就决定了指令编码中的第 1 个字节对解码工作的重要性。


除了 1 个字节的 Opcode 外,还有 2 个字节的 Opcode 以及 3 个字节的 Opcode,第 2 个 Opcode 码是由 0F 字节进行引导,这个 0F 被称为 escape prefix(escape opcode)即:2 个字节的 Opcode 码,其第 1 个 Opcode 必定是 0F 字节。


1.1 escape opcode

x86/x64 平台上的 3 个字节的 Opcode 码是通过 escape opcode + opcode 形式。

这些 escape opcode 可以理解为:opcode 的 prefix,这些 prefix 可以说是 opcode 的一部分。

这些 escape opcode 是:

3 个字节的 Opcode 用于 SIMD 指令上(SSE 系列指令),然而随着 AMD/Intel 在未来有可能加入更多的指令子集,意味着可能会产生更多的 escape opcode(已经被集成在 VEX prefix 或 XOP preix 中)


1.2 SIMD prefix

在多数 SIMD 指令上,SIMD prefix escape opcode 联合起来最终决定指令的 opcode

这些 SIMD prefix 包括:

在这种情况下,Opcode 码将达到 4 个 bytes,下面是一个 4 bytes Opcode 码的例子:

它是 SSE4.1 的一条 BLENDPS 指令,SIMD prefix 与 escape opcode 结合为 4 个 bytes Opcode 码


1.3 escape opcode 与 SIMD prefix 总结

---
说明
escape opcode
0f
引导 opcode
0f 38
0f 3A
SIMD prefix
66
SIMD 指令修饰性 prefix
F3
F2

movntdq xmmword ptr [rax], xmm0

这是一条 SSE2 指令,它 encodes 是:

它的 Opcode 是 66 0f e7 (3 bytes opcode),这里 66 是 SIMD prefix,0f 是 escape prefix


2. 学会看 Opcode 表

怎么去看指令的 opcode,获得指令 opcode 编码,还是很有学问的

2.1 从指令参考页里看 opcode 码

下面的图是 AMD 文档中对于指令参考页的描述:

指令参考页

从指令参考页里可以得出以下信息:

这确实可以得到想要的 Opcode 码,还有 Operand 数以其属性。下面摘录了 mov 指令一部分的参考页:

 mov 指令参考页

从这里看出,这个 Opcode 8B 有几种操作数形式:

operand size 可以为:

不过这并不是了解 Opcode 码的好地方,指令参考页主要是对指令的操作进行相应的描述。对掌握 Opcode 码不是那么直观和透彻。下面要看全局的 Opcode 表格。


2.2 怎么看 Opcode 表

学会看 Opcode 表才能清晰地进行分析,Opcode 表是一个全面的透彻的总结表,又可以说十分细致。

Intel 和 AMD 的文档中均提供了 Opcode 表,Opcode 表有 One-byte Opcode 表、Two-byte Opcode 表和 X87 Opcode 表等。


2.2.1 Opcode 表上的基本元素

下图是一部分 Opcode 表

Opcode 表


Opcode 表上描述的范围是 00 ~ FF,即 1 个字节共 256 个值,每 1 个值描述不同的属性,包括:

每个 Opcode 码还附有相应的 Operands 属性,Operands 属性是用来描述 Operands 的,包括 Operands 个数、寻址类型及 Size

注意:
  所谓 Group(组)属性 Opcode 是指:Intel 将一些 operands 寻址相同 Opcode 码抽出来组成一组。具体的功能是由 ModRM 的 reg 来决定。ModRM.reg  就起决定性作用,它反过影响 Opcode 码。ModRM.reg 此时相当是一个 index 值。用来选择 Opcode 码。

  这主要原因是原因:这种 Opcode 的操作数无法与 ModRM 得到良好的配合。从而决定了 Opcode 受制于 ModRM.reg。
很典型的 FFh,这就是一个 Group 属性的 Opcode 码。 FFh 是一组指令的代表,FFh 要由 ModRM.reg 才能决定它的指令功能。当 ModRM.reg = 010 时,FFh 是 CALL 指令的 Opcode 码。 当 ModRM.reg = 000 时,它是 INC 指令的 Opcode 码。

实际上:
   Opcode 的组是按照 Operands 的属性进行分组的。

看看 mov 指令 8B Opcode 表是怎样的:

上面图中的 opcode 表中圆圈所示是 Opcode 8B,它对应的是指令 mov,表格中的 Gv, Ev 是描述这个 Opcode 码所对应的指令的 Operand 属性

Gv, Ev 表示:

(1)两个 Operands 分别是:目标操作数 Gv,源操作数 Ev 或说:frist operand 是 Gv, second operand 是 Ev

(2)Gv 表示:G 是寄存器操作数,v 是表示操作数大小依赖于指令的 Effective Operand-Size,可以是 16 位,32 位以及 64 位。

(3)Ev 表示:E 是寄存器或者内存操作数,具体要依赖于 ModRM.r/m,操作数大小和 G 一致。

4 个字符便可以很直观的表示出:操作数的个数以及寻址方式,更重要的信息是这个 Opcode 的操作数需要 ModRM 进行寻址。


2.2.2 operand 描述表

要看懂 Opcode 表必须学会分析和理解 Operand 属性字符,Intel 和 AMD 的 Opcode 表前面都有对 Operands 属性字符很仔细清晰的定义和说明。

表格1:operand type 表

类型
描述
寻址方式
A
 operand 是一个 far pointer(selector:offset 形式)  以 immediate 形式直接在 encode 里给出。(offset 在低,seletor 在高)
C
 operand 是一个 control register(CR0 ~ CR15)  由 modrm.reg 提供寻址。
D
 operand 是一个 debug register(DR0 ~ DR15)  由 modrm.reg 提供寻址。
E
 operand 是一个 register 或者 memory  由 modrm.r/m 提供寻址。
F
 operand 是 rflags 寄存器  直接嵌在 opcode 里。
G
 operand 是一个通用寄存器 (rax ~ r15)  由 modrm.reg 提供寻址。
I
 operand 是立即数 immediate  encode 中的 immediate 形式。
J
 operand 是基于 rip 的 offset(偏移量),是 signed(符号数)  encode 中的 immediate 形式。
M
 operand 是 memory 操作数  由 modrm.r/m 提供寻址,其中 modrm.mod ≠ 11(它是 memory)
O
 operand 是 memory offset,直接提供绝对地址  encode 中的 immediate 形式,无需 modrm 和 SIB 寻址。
P
 operand 是 MMX 寄存器  由 modrm.reg 提供寻址。
PR
 operand 是 MMX 寄存器  由 modrm.r/m 提供寻址,其中 modrm.mod = 11
Q
 operand 是 MMX 寄存器或者 memory 操作数  由 modrm.r/m 提供寻址。
R
 opernad 是一个通用寄存器(rax ~ r15)  由 modrm.r/m 提供寻址,其中 modrm.mod = 11
S
 opernad 是 segment 寄存器  由 modrm.reg 提供寻址。
V
 operand 是 XMM 寄存器  由 modrm.reg 提供寻址。
VR
 operand 是 XMM 寄存器  由 modrm.r/m 提供寻址,其中 modrm.mod = 11
W
 opernad 是 XMM 寄存器或者 memory 操作数  由 modrm.r/m 提供寻址。
X
 operand 是串指令的源串 default operand 寻址  由 ds:rsi 提供寻址,在 encode 中无需给出。
Y
 operand 是串指令目的串 default operand 寻址  由 es:rdi 提供寻址,在 encode 中无需给出

 

表格2:operand size 表

类型
描述
operand size
a
 仅用于 bound 指令,operand 是一个 memory,它提供 array 的 limit(下限地址和上限地址)  word 或者 doubleword,依赖于 effective operand size(可以进行 operand size override)
b
 operand size 固定为 byte,不可进行 operand size override  byte (8 位)
d
 operand size 固定为 doubleword,不可进行 operand size override  doubleword (32 位)
dq
 operand size 固定为 double-quadword,不可进行 operand size override  double-quadword (128 位)
p
 operand 是 far pointer:32 位或 48 位(16:16 或 16:32)  16:16 或 16:32 依赖于 effective operand size(可进行 operand size override)
pd
 packed double(128 位双精度浮点压缩数),即:64:64(128 位 packed double)  128 位 packed-double。
pi
 MMX - packed integer(64 位压缩整数)  64 位 packed-integer。
ps
 packed signed(128 位单精度压缩数),即:32:32:32:32(128 位 packed signed)  128 位 packed-signed。
q
 operand size 固定为 quadword,不可进行 operand size override  quadword(64 位)
s
 6 bytes 或 10 bytes 的描述符表类型(limit + base  16:32(16/32 位 opernad siz)或 16:64(64 位 operand size)
sd
 scalar double  scalar double
si
 scalar integer  scalar integer
ss
 scalar signed  scalar signed
v
 word,doubleword 或 quadword  word,doubleword 或 quadword 取决于 effective operand size,可进行 operand size override,REX prefix
w
 opernad size 固定为 word,不可进行 operand size override  word(16 位)
z
 
word =
effective operand size 是 16 位时  word 或 doubleword 依赖于 effective operand size
dword =
effective operand size 是 32 位或 64 位时
/n
 n 代表一个具体数值(0 ~ 7),在 ModRM.reg 中提供  由 ModRM.reg 提供 (000 ~ 111)

每个指令的 operand 属性都由上面的表1和表2两者描述, Operands 属性都有两组字符来定义,前面的一组大写字母是 Operand 类型,后面一组小定字母是 Operand Size。

如:Gv 表示:

★ G 是 Operand 类型,表示 General-Purpose Register(GPR)通用寄存器,也就是 rax ~ r15 共 16 个。这是有别与 Segment Register、XMM 寄存器等。

★ v 是 Operand 大小,依赖于当前的 Effective Operand-Size,这个 operand size 可以使用 66H prefix 和 REX prefix 进行 operand size override

 

举 2 个例子:

(1)以典型的 Jmp Jz 为例。

它的 Opcode 是 E9,Operand 属性是 Jz

J 是代表基于 RIP 的相对寻址,也就是说,操作数寻址是偏移量(Offset)加上 RIP 得出。

z 则表示 Operand-Size 是 16 或 32(effective operand size = 16 时是 16,effective operand size = 32/64 时是 32)

这与 v 不同,z 属性下不存在 64 位 operand size



(2)另一个典型的例子是 call Ev

它的 Opcode 是 FF,这个 Opcode 是个典型的 Group Opcode,为什么会定义为 Group,下面的将会有阐述。

操作数的寻址是典型的 ModRM 寻址,由 ModRM.r/m 寻址。

E 既可是 GPRs 也可以是 Mem。 它同样是 v 属性的 Operand-Size。

关于 default 与 effective 阐述, 详见: default(缺省) 与 effective(有效)

 

3.透析 Opcode 的编码规则

如上所述:prefix 与 Opcode 共享 00~FF 的空间,由于 Prefix 部分是 可选的,当 CPU 取指单元从 ITLB 加载指令 L1-Icache 和 prefetch buffer,预解码单元通过 prefix 自已的 ID 值来解析 prefix。如读入 66h 时是解析为 prefix 而不是 Opcode。同样,读入 0Fh 时被解析为是 2 个 字节的 Opcode 中的第 1 个字节。

Opcode 的 operands 寻址一部分是在 Opcode 码直接中指定,一部分是依赖 ModRM 给定,还有一部分不依赖 ModRM 给定。直接中 Opcode 指定的 operand 寻址的是 GPRs 寻址,如:inc eax 指令(Opcode 是 40h),还有串指令,如 loads 等。


3.1 1 个 Operand 的 Opcode 码编码规则

(1)直接嵌入 Opcode 中

  一部分 Opcode 的 operand 是直接嵌入 Opcode 中的,如:inc eax、push eax、pop eax 等。 这些指令编码是 1 个字节。不依赖于 ModRM 寻址。

(2)依赖于 ModRM 寻址,这部分 Opcode 码需要 ModRM.reg 进行补充修饰,是 Group 属性 Opcode

  对于单 Operand 的指令而又依赖于 ModRM 寻址。这种指令必定是 Group 属性的指令。

  它的 operand 属性是 Ev 字符。ModRM.reg 决定最终的 Opcode 操作码,ModRM.mod 和 ModRM.r/m 决定寻址模式。

  如前面提到的典型 Call Ev 这种指令,operand 可以是 register,也可以是 memory,由 ModRM.mod 来决定到底是 registers 还是 memory,ModRM.r/m 决定具体的 operand。

(3)不依赖于 ModRM 寻址,不是 Group 属性 Opcode

  这种情况下的 Operand 既不嵌入 Opcode 中,也不使用 ModRM 进行寻址,那么它必定是 immediate 值。它的 Operand 属性字符是 Iz、Ib 或者 Jz。

  这种指令很常见,如:push Iz、push Ib、Jmp Jz、Jmp Jb 等。

  push 0x12345678 这就是常见的这种指令,还非常常见的短跳转 jmp $+0x0c。




3.2、 2 个 Operands 的 Opcode 码编码规则。


(1)1 个 Operand 嵌入 Opcode,另一个 Operand 不依赖于 ModRM(非 Group 属性)

  这种情况下,一个 Operand 必定是 GPRs,另一个不使用 ModRM 寻址的 Operand 必定是 Immediate。所以它不是 Group 属性的。

看看以下两个 Opcode:

  指令 mov rax, Iv 它的 Opcode 是 B8,目标操作数是由 Opcode 中指定的 GPRs(rax),源操作数是不使用 ModRM 寻址的 Immediate。是一个寄存器与立即数的寻址指令。

  由于 mov rax, Iv 它的 immediate operand size 是 v 属性,因此:这个 immediate 可以使用 REX prefix 进行 override 到 64 位

如:指令 mov rax, 0x1122334455667788

  它的 encode 是:b8 88 77 66 55 44 33 22 11 (使用了 64 位的 immediate 值)

思考另一个问题:

  在 64 位下:mov qword ptr [rax],0x1122334455667788,这指令是的错误的。

  原因:这条指令是 MOV Ev, Iz,它是 z 属性的 operand size, 因此,它不可能有 64 位的 immediate 值。

本质上:

  是它的寻址是使用了 ModRM 的。此时,它要受限于 Immediate 最大为 4 个字节的限制。所以不会有 Iv 的属性

  mov rax, qword ptr [0x1122334455667788],这条指令是完全正确的,它的指令是 MOV rAX, Ov, 具有 v 属性的 size

  它不依赖于 ModRM 寻址。此时,它的 memory 是绝对地址,它将以 immediate 值形式直接嵌入指令编码中。



(2)依赖于 ModRM 寻址,非 Group 属性

  这种依赖于 ModRM 寻址而又非 Group 属性的 2 个 Operands,绝大部分是:寄存器与内存操作数之间或 2 个寄存器之间。

  它的 Operands 属性字符是 Gv, Ev 或 Ev, Gv。 典型的如: mov eax, ebx


(3)依赖于 ModRM 寻址,是 Group 属性

  在这种 Opcode 编码下,另一个操作数必定是 Immediate,典型的如:mov ecx, 0x10,它的 Operands 属性字符是 Ev, Iv 等。


3.3、 3 个 Operands 的 Opcode 编码

  在 AMD 的 SSE5 指令集推出之前,是没有第 3 个 Operand 是非寄存器或内存操作数的情形。所以,第 3 个操作必定是 Immediate 值。

  这种指令很少。imul eax, ebx, 3 这是其中的一种形式。


4、 Opcode 混乱的本质

  造成 x86/x64 平台的 Opcode 混乱,起因于众多寻址模式,而 operand 分为:0 operand、1 operand、2 operands、3 operands 以及 4 operands

 

Group 属性的 Opcode

  由于这部分 Opcode 需要 ModRM.reg 进行补充,那么 ModRM.r/m 就可以提供 1 个 operand 的寻址了。

  当 ModRM.mod = 11,它就提供 registers 寻址,当 ModRM.mod <> 11,它就提供 memory 寻址,

  因此,它的 operand 要么是 Ev 要么是 Iv、Iz 或 Ib

  当 operand 是 Iv、Iz 或 Ib 时,表示 Opcode 将忽略 ModRM.r/m 寻址。

上一页 返回目录 下一页


版权所有 mik 2008 - 2014