X86指令编码内幕 --- 指令格式

本文详细解析了x86/x64体系结构中的General-Purpose Instruction编码格式,包括指令编码的重要组成部分:Prefix、Opcode、ModRM/SIB以及Displacement/Immediate,并讨论了指令长度的限制。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

在序言里的例子里:

 

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. 编码序列

instruction_format

如上图所示:这是 x86/x64 体系的 General-Pupose Instruction(通用体系指令)的编码格式,记住这个编码序列很重要,这是解析指令编码的基石

这个编码序列组成部分分为:

  • Legacy Prefix
  • REX prefix
  • Opcode
  • ModRM
  • SIB
  • Displacement
  • Immediate

按功能组别,可以将这个指令序列分为 4 大部分:PrefixOpcodeModRM/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/m2-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 都有符号扩展的行为,扩展到目标操作数的位数。

实际上:
   operand size 具有 z 或 b 属性的指令,才可能进行 sign-extended(符号扩展)行为。

(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

本质上:
   所有 displacement 都是 signed(符号数),displacement 是基于 base 地址的一个 offset(偏移量)。

★ 当地址形式中无 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 存储的,这主要体现在 displacementimmediate 部分。

在 encode 序列里,legacy prefix 在低端,依次往 immediate 部分在高端。

mov dword ptr es:[eax + ecx * 8 + 0x11223344], 0x12345678

指令中的 displacement0x11223344,在内存的 little endian 序列是:44 33 22 11

immediate0x12345678 在内存的 lttle endian 序列是:78 56 34 12

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值