在序言里的例子里:
mov dword ptr es:[eax + ecx * 8 + 0x11223344], 0x12345678 |
这里稍作修改:将内存操作数 operand size 的指示符 word ptr 改回 dword ptr,使得指令的 operand size 是 32 位。
这是个具有典型指令编码意义的指令,它的 encodes(机器编码)是:26 c7 84 c8 44 33 22 11 78 56 34 12 (共12个字节)。
go ahead~
1. 编码序列
如上图所示:这是 x86/x64 体系的 General-Pupose Instruction(通用体系指令)的编码格式,记住这个编码序列很重要,这是解析指令编码的基石。
这个编码序列组成部分分为:
- Legacy Prefix
- REX prefix
- Opcode
- ModRM
- SIB
- Displacement
- Immediate
按功能组别,可以将这个指令序列分为 4 大部分:Prefix、Opcode、ModRM/SIB 以及 Displace/Immediate
1.1 Prefix(前缀)
AMD推出 x86 扩展 64 位技术时,增加了一个用于访问扩展的 64 位数据的 REX prefix,而 x86 的 prefix 则变为了指令格式中的 Legacy prefix。
1.1.1 Legacy prefix
在 legacy 的 x86 模式下 REX prefix 是无效的,但是在 x64 的 64 位下 Legacy prefix 是有效的。
1.1.2 REX prefix
在 64 位模式下,由于绝大多数指令的 default operand size(缺省操作数大小)是 32 位,为了可以访问扩展的 64 位数据和 64 位地址,x64 指令体系引入了 REX prefix 来实现这一目的。
1.2 Opcode(操作码)
大多数通用指令的 Opcode 是单字节,最多是 2 字节,但是对有些 Float 指令和 SSEx 等 midea 指令来说是 3 个字节的。opcode 是指令的核心部分,代表指令是用来干什么的?怎样干?
1.3 ModRM/SIB
ModRM 和 SIB 是用来提供 operands 的寻址模式。
1.3.1 ModRM
ModRM 字节,意为:mod-reg-r/m 按 2-3-3 比例划分字节。
ModRM.mod 是提供寻址模式, ModRM.reg 用来提供寄存器 ID,ModRM.r/m 提供 register 或 memory 的 ID
1.3.2 SIB
SIB 意即:Sacle-Index-Base 也是按 2-3-3 比例划分字节。
这两个字节用来为 memory 操作数提供 base, index 以及 scale
1.4 Disp/Imme
displacement 与 immediate 直接嵌在指令编码中。
1.4.1 displacement
displacemnt 需要 ModRM 甚至 SIB 提供寻址, displacement 最大为 4 个字节 32 位。
1.4.2 immediate
immediate 部分大多数情况下不需要 ModRM 字节提供寻址,在一些指令还是需要 ModRM 进行寻址。
immediate 最大可为 8 个字节,在 x64 的 64 位下的某些情况才会有的。
注意:
displacement 和 immediate 的两种情况:
★ 符号数(signed)
在这种情形下:进行运算时,是符号数。
当小于目标 operands 位数时,displacement 和 immediate 都有符号扩展的行为,扩展到目标操作数的位数。
实际上: (1)当指令的 operand size 是 z 属性时,如果 immediate 是小于 effective operand size 的,immediate 会进行 sign-extended 行为。 (2)同样 operand size 是 b 属性的,如果 immediate 是小于 effective operand size 的,immediate 也会进行 sign-extended 行为。 (3)当指令的 displacement size 小于 address size 的,displacement 会进行 sign-extended 行为。 |
★ 无符号数(unsigned)
在这种情况下,displacement 是一个绝对地址值,immediate 是一个无符号立即数值。
如:mov eax, dword ptr [0x11223344] 或 mov eax, 0x12345678
本质上: ★ 当地址形式中无 base 寄存器时,虽然它是绝对值形式,实际上它仍是基于 segment base 地址。 |
因此,只有 immediate 才具有的 unsigned 的时候。
对照上面的 encode 来看:
26 c7 84 c8 44 33 22 11 78 56 34 12
(1) 26 是 legacy prefix,这是 segment-override prefix,指明是 ES 段选择子
(2) c7 是 Opcode,表明这个指令是 mov reg/mem, imme
(3) 84 是 ModRm,即:10-000-100。
(4) c8 是 SIB,即:11-001-000
(5) 44332211 是 32 位 displacement 值
(6) 78563412 是 32 位 immediate 值
2、指令长度
上图中显示,指令长度最长是 15 个字节,在什么时候达到饱和的 15 个字节呢?
2.1 什么情况下达到饱和的 15 bytes
答案是如下类似指令:
lock add dword ptr es:[eax+ecx*8+0x11223344], 0x12345678 |
当在 16 位代码下,这条指令将达到饱和的 15 个字节长度。
注意:仅在 16 位下,这条指令的编码是:
26 66 67 F0 81 84 C8 44 33 22 11 78 56 34 12 (正好 15 个字节)
这个编码的具体含义:
26 66 67 F0: 这 4 个字节是 prefix,这 4 个字节达到了饱和的 prefix 状态。
- 26 是 ES segment register
- 66 是 operand-size override
- 67 是 address-size override
- F0 是 Lock prefix
C7:Opcode
84:ModRM
C8:SIB
44 33 22 11:displacement
78 56 34 12:immediate
有没有超过 15 个字节的指令编码,答案是:没有! 那么在 64 位下呢? 答案同样是没有!
2.2 为什么指令长度最长是 15 字节?
★ 4 个字节的 prefix 已经达到饱和度了:
- 1 个字节用来调整 segment selector registers
- 1 个字节用来调整 Operand effective size
- 1 个字节用来调整 Address effective size
- 还有 1 个字节用来 lock 总线
已经无法再增加 prefix 了。
★ 若是采用 2 个字节的 Opcode 码,则寻址模式上不会有 mem32, imm32 这种操作法。所以还是采用 1 个字节的 Opcode,而得到 4 个字节的立即数。
★ ModRM + SIB:2 个字节。
★ 4 个字节的 displacement 值。
★ 4 个字节的 immediate 值。
这样每个组成部分都呈饱和状态,加起来总共 15 个字节,而只有采用 mem32, imme32 这种寻址模式可能会达到饱和状态。
在 64 位下,若采用 mem, imme 的寻址模式,这和 32 位是一致的,所以不会超越 15 个字节,
2.3 prefix 的限制
prefix 的限制来自 legacy prefix (包括:operand size override、address size override、segment override、repeat prefix 以及 lock prefix)本身之间,
以及与 REX prefix 之间存在冲突:
- 在 64 位下,operand size override
3、 encodes 的字节序
x86/x64 指令的 encodes 在内存中是以 little endian 存储的,这主要体现在 displacement 和 immediate 部分。
在 encode 序列里,legacy prefix 在低端,依次往 immediate 部分在高端。
mov dword ptr es:[eax + ecx * 8 + 0x11223344], 0x12345678 |
指令中的 displacement 是 0x11223344,在内存的 little endian 序列是:44 33 22 11
immediate 是 0x12345678 在内存的 lttle endian 序列是:78 56 34 12