【问题标题】:x86 multi-byte NOP and instruction prefixx86 多字节 NOP 和指令前缀
【发布时间】:2014-12-31 00:21:46
【问题描述】:

回想一下,x86 架构将 0x0F 0x1F [mod R/M] 定义为多字节 NOP。

现在我正在查看 8 字节 NOP 的具体情况:我有

0x0F 0x1F 0x84 0x__ 0x__ 0x__ 0x__ 0x__

最后 5 个字节有任意值。

第三个字节[mod R/M],拆分后给出:

  • mod = 10b: 参数是 reg1 + 一个 DWORD 大小的位移
  • reg2 = 000b:(我们不在乎)
  • reg1 = 100b:表示参数是 SIB 字节 + DWORD 大小的位移。

现在,作为一个具体的例子,如果我采取

0x0F 0x1F 0x84 0x12 0x34 0x56 0x78 0x9A

我有

  • SIB = 0x12
  • displacement = 0x9A785634:一个双字

现在我添加 0x66 指令前缀,表示位移应该是 WORD 而不是 DWORD:

0x66 0x0F 0x1F 0x84 0x12 0x34 0x56 0x78 0x9A

我希望0x78 0x9A 被“切断”并被视为新指令。但是,当编译它并在生成的可执行文件上运行 objdump 时,它仍然使用所有 4 个字节(一个 DWORD)作为位移。

我在这种情况下是否误解了“位移”的含义?还是0x66前缀对多字节NOP指令没有影响?

【问题讨论】:

  • 0x66 前缀不影响位移的大小——只影响操作数大小。
  • 那么,如果一条指令的组成部分是:Prefix、Opcode、Mod R/M、SIB、Displacement、Immediate,那么 0x66 会影响 Immediate 的值吗?好吧,这有点道理:多字节 NOP 不采用立即值,因此 0x66 没有任何效果。谢谢!

标签: x86 prefix nop


【解决方案1】:

66H 前缀将操作数的大小覆盖为 16 位。
它不会覆盖地址的大小,如果你想使用67H

这是所有操作数的列表。

        F0h = LOCK  -- locks memory reads/writes
        String prefixes
        F3h = REP, REPE  
        F2h = REPNE      
        Segment overrides
        2Eh = CS
        36h = SS
        3Eh = DS
        26h = ES
        64h = FS
        65h = GS
        Operand override 
        66h. Changes size of data expected to 16-bit
        Address override 
        67h. Changes size of address expected to 16-bit

但是最好不要创建自己的 nop 指令,而是坚持推荐的(多字节)nop。

根据 AMD 推荐的多字节 nop 如下:

表 4-9。推荐的 NOP 指令多字节序列

bytes  sequence                encoding

 1      90H                            NOP
 2      66 90H                         66 NOP
 3      0F 1F 00H                      NOP DWORD ptr [EAX]
 4      0F 1F 40 00H                   NOP DWORD ptr [EAX + 00H]
 5      0F 1F 44 00 00H                NOP DWORD ptr [EAX + EAX*1 + 00H]
 6      66 0F 1F 44 00 00H             NOP DWORD ptr [AX + AX*1 + 00H]
 7      0F 1F 80 00 00 00 00H          NOP DWORD ptr [EAX + 00000000H]
 8      0F 1F 84 00 00 00 00 00H       NOP DWORD ptr [AX + AX*1 + 00000000H]
 9      66 0F 1F 84 00 00 00 00 00H    NOP DWORD ptr [AX + AX*1 + 00000000H]

Intel 不介意最多 3 个冗余前缀,因此 nop 最多 11 个字节可以这样构造。

 10     66 66 0F 1F 84 00 00 00 00 00H     NOP DWORD ptr [AX + AX*1 + 00000000H] 
 11     66 66 66 0F 1F 84 00 00 00 00 00H  NOP DWORD ptr [AX + AX*1 + 00000000H]

当然,您也可以通过在正常指令前加上冗余前缀来消除 nop。

例如

rep mov reg,reg //one extra byte

或强制 cpu 使用同一指令的更长版本。

test r8d,r8d is one byte longer than: test edx,edx

带有立即操作数的指令有短版和长版。

and edx,7 //short
and edx,0000007  //long

大多数汇编程序会帮助您缩短所有指令,因此您必须使用 db 自己编写较长的指令

将这些散布在战略位置可以帮助您对齐跳跃目标,而不会因 nop 的解码或执行而导致延迟。

请记住,大多数 CPU 执行 nop 仍然会占用资源。

【讨论】:

  • yasm docs 说你可以写add rax, dword 1 来强制使用 32 位立即数而不是 imm8,但它在实践中不起作用。 (仍然使用 Ubuntu 软件包中的 yasm 1.3.0 缩短为 imm8。)此外,即使指令不需要 REX 前缀(无需更改寄存器),您也可以使用 REX 前缀进行填充。 db 0x40 / test edx,edx 仍解码为 test edx,edx。没有设置位的 REX 只会改变使用 AH/BH/CH/DH 的 insn 的含义。 (对于 SIL 等,我必须按某种顺序查找)
  • 更新:YASM:add rax, strict dword 1 强制使用 32 位立即数。 NASM:add rax, strict qword 1 强制使用 32 位立即数。 NASM 和 YASM 都被对方呛到,抱怨尺寸。但无论如何,strict 是 NASM 语法中用于立即数和寻址模式置换的基本关键字。
  • 我建议避免在尚未定义的地方使用rep 前缀,因为英特尔稍后会赋予它们新的含义。例如,rep mov 现在在英特尔 HLE 中具有意义。
  • @fuz:另一方面,rep imul 很有趣。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-05-24
  • 2018-11-22
  • 1970-01-01
  • 2011-12-31
  • 1970-01-01
相关资源
最近更新 更多