assembly x86多字节NOP和指令前缀

lvjbypge  于 2022-11-13  发布在  其他
关注(0)|答案(1)|浏览(317)

作为一个小回忆,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
  • 一个双字节

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

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

我希望0x78 0x9A被“截断”并被视为一条新指令。然而,当编译此指令并在生成的可执行文件上运行objdump时,它仍然使用所有4个字节(一个DWORD)作为位移。
我是不是误解了“displacement”在这里的意思?或者前缀0x66对多字节NOP指令没有任何影响?

hfsqlsce

hfsqlsce1#

66H前缀将操作数的大小覆盖为16位。
如果你想使用67H,它不会覆盖地址的大小。(但是不要在32位代码中使用;这往往会导致LCP暂停,尤其是在使用带有disp 16的nopw [bx+0x1111]而不是从不带67H前缀的相同ModRM编码中获得的disp 32时。)
以下是所有传统x86前缀的列表(不包括雷克斯或VEX/EVEX)。

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 (in 32-bit mode)

然而,最好不要创建自己的NOP指令,而是坚持使用推荐的(多字节)NOP。
AMD建议如下:

  • 表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]

(The此表中的拆卸错误:它在使用66H前缀的指令中显示[AX]。该前缀将 * 操作数 * 大小设置为16,但地址大小未修改。如果在32位模式中使用67H前缀,则AX在16位寻址模式中不可编码。16位地址大小意味着2字节位移而不是4字节位移,这就是为什么67H在Intel CPU上的32位模式下解码速度很慢,从而导致错误的LCP暂停。)
英特尔不介意最多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

rep通常被忽略,但是新的CPU扩展经常使用rep来使旧的操作码具有不同的含义。一个更安全的选择是前缀 * 可以 * 对该操作码有效,但对寄存器操作数没有影响,如段覆盖。或者如DS前缀(当它已经是默认值时),或者在64位模式下,CS/DS/ES/SS的基数都是0。
或者选择需要雷克斯前缀的寄存器,因此汇编器必须使用相同指令的更长版本。

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

带立即数操作数得指令有短型与长型两种(test除外).

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

大多数汇编程序会帮助你缩短所有指令,所以你必须自己用db或NASM/YASM and edx, strict dword 7来编写较长的指令。
将它们散布在战略位置可以帮助您对齐跳转目标,而不必由于NOP的解码或执行而导致延迟。
请记住,在大多数CPU上,执行NOP仍然会耗尽资源。前端解码/微操作缓存/发布槽,并在ROB中跟踪它,直到退出。填充其他指令会占用相同的额外I缓存空间,但没有其他成本。
当您为Skylake中得JCC错误启用英特尔微码解决方法时,这些扩展指令得技术有时会自动使用:请参阅How can I mitigate the impact of the Intel jcc erratum on gcc?这可能需要在内部循环 * 内 * 扩展指令,而您确实不希望在此处使用NOP,因此英特尔建议扩展早期的指令。

相关问题