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

    技术2022-05-19  51

    在序言里的例子里:

     

    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 PrefixREX prefixOpcodeModRMSIBDisplacementImmediate

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

    实际上:   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 register66 是 operand-size override67 是 address-size overrideF0 是 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 registers1 个字节用来调整 Operand effective size1 个字节用来调整 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


    最新回复(0)