【问题标题】:Long nop instructions in nasmnasm 中的长 nop 指令
【发布时间】:2018-06-23 01:02:34
【问题描述】:

nasm 是否有任何内置方式来发出给定长度的 long-nop(又名multi-byte nops)指令?

【问题讨论】:

  • @Jester - 是的,我知道这一点并使用它 - 但它不能直接访问 nop 指令:它们只能通过 align 指令间接插入。我想直接使用它们,例如。 “在此处插入一个 2 字节的 nop”。
  • 您可以直接使用宏,例如db __ALIGN_32BIT_2B__ 应该在 32 位代码中插入一个 2 字节的 NOP。
  • @Jester - 谢谢 - 我在哪里看到这些?我在手册中没有找到那个字符串。
  • 它在宏包本身中,因此它没有记录并且可能会改变。

标签: assembly x86 nasm


【解决方案1】:

仅引用 2017 年 12 月的 https://www.intel.com/content/dam/www/public/us/en/documents/manuals/64-ia-32-architectures-optimization-manual.pdf 第 124 (3-28) 页:

3.5.1.10 使用 NOP

代码生成器生成一个空操作 (NOP) 来对齐指令。 32位模式下不同长度的NOP示例如下:

1-byte: XCHG EAX, EAX
2-byte: 66 NOP
3-byte: LEA REG, 0 (REG) (8-bit displacement)
4-byte: NOP DWORD PTR [EAX + 0] (8-bit displacement)
5-byte: NOP DWORD PTR [EAX + EAX*1 + 0] (8-bit displacement)
6-byte: LEA REG, 0 (REG) (32-bit displacement)
7-byte: NOP DWORD PTR [EAX + 0] (32-bit displacement)
8-byte: NOP DWORD PTR [EAX + EAX*1 + 0] (32-bit displacement)
9-byte: NOP WORD PTR [EAX + EAX*1 + 0] (32-bit displacement)

这些都是真正的 NOP,除了推进 EIP 之外,对机器的状态没有影响。

因为 NOP 需要硬件资源来解码和执行,所以使用最少的数字来实现所需的填充。

单字节 NOP:[XCHG EAX,EAX] 具有特殊的硬件支持。虽然它仍然消耗一个 µop 及其附带的资源,但消除了对 EAX 旧值的依赖。

这个微操作可以尽早执行,减少未完成指令的数量,是成本最低的 NOP。

其他 NOP 没有特殊的硬件支持。它们的输入和输出寄存器由硬件解释。因此,代码生成器应安排使用包含最旧值的寄存器作为输入,以便 NOP 尽早调度和释放 RS 资源。

尝试观察以下 NOP 生成优先级:

• Select the smallest number of NOPs and pseudo-NOPs to provide the desired padding.
• Select NOPs that are least likely to execute on slower execution unit clusters.
• Select the register arguments of NOPs to reduce dependencies.

【讨论】:

  • 看起来这些序列避免了 P6 引入的 long-nop 0F 1F modrm ...,而不是使用 lea。以这种方式使用的 LEA 在架构意义上是“真正的 nop”,但不是微架构意义上的,而 0F 1F 与 90 短 NOP 运行相同,不使用执行端口并且不延长涉及任何寄存器的 dep 链。在 x86-64 代码中,您应该始终使用0F 1F NOP 而不是 LEA,或者在同时使用 CMOV 或其他 P6 功能的 32 位代码中。
【解决方案2】:

答案似乎是不,开箱即用,没有官方方法可以开箱即用地在 nasm1 中发出这些 long-nop。

所以我只是根据英特尔手册中推荐的序列编写了自己的 1 到 9 个字节的宏2:

;; long-nop instructions: nopX inserts a nop of X bytes
;; see "Table 4-12. Recommended Multi-Byte Sequence of NOP Instruction" in
;; "Intel® 64 and IA-32 Architectures Software Developer’s Manual" (325383-061US)
%define nop1 nop                                                     ; just a nop, included for completeness
%define nop2 db 0x66, 0x90                                           ; 66 NOP
%define nop3 db 0x0F, 0x1F, 0x00                                     ;    NOP DWORD ptr [EAX]
%define nop4 db 0x0F, 0x1F, 0x40, 0x00                               ;    NOP DWORD ptr [EAX + 00H]
%define nop5 db 0x0F, 0x1F, 0x44, 0x00, 0x00                         ;    NOP DWORD ptr [EAX + EAX*1 + 00H]
%define nop6 db 0x66, 0x0F, 0x1F, 0x44, 0x00, 0x00                   ; 66 NOP DWORD ptr [EAX + EAX*1 + 00H]
%define nop7 db 0x0F, 0x1F, 0x80, 0x00, 0x00, 0x00, 0x00             ;    NOP DWORD ptr [EAX + 00000000H]
%define nop8 db 0x0F, 0x1F, 0x84, 0x00, 0x00, 0x00, 0x00, 0x00       ;    NOP DWORD ptr [EAX + EAX*1 + 00000000H]
%define nop9 db 0x66, 0x0F, 0x1F, 0x84, 0x00, 0x00, 0x00, 0x00, 0x00 ; 66 NOP DWORD ptr [EAX + EAX*1 + 00000000H]

我也已将这些添加到 nasm-utils project,因此如果您有相同的需求,这是获取它们的一种方法。


1虽然作为 Jester points out,您可以深入了解内部结构,找到一些用于实现“智能对齐”功能的宏。

2作为记录,我相信这些首先出现在 AMD 手册中,最终英特尔采用了相同的推荐顺序。

【讨论】:

  • 鉴于 nop6 和 nop9 宏上的操作数大小前缀 66h,我认为这些行上的 cmets 应该是 WORD PTR 而不是 @ 987654325@.
  • @SepRoland - 在这种情况下,我根本不应该使用指示 66,因为 WORD 暗示了它。基本上66H, NOP DWORD ptr [EAX + EAX*1 + 00H] 和NOP WORD ptr [EAX + EAX*1 + 00H] 是编写相同内容的两种方式,如果您明确编码db 0x66 后跟DWORD 版本,您将获得相同的字节。我上面显示的表格是从英特尔手册中逐字复制的。请注意,对于 64 字节模式,cmets 甚至都不正确:如果您实际组装了注释版本,则带有地址的 nop 都会长 1 个字节,因为它们会有 ...
  • ... 和额外的 0x67 前缀,因为 32 位地址 9(如 [eax ...])不是默认值。不过,字节编码在任何一种模式下都很好。
  • x86-64 是否允许使用66 REX nopl ... 在所有 CPU 上高效解码的 10 字节 NOP?即使在 0F 转义字节算作前缀的 CPU 上(Silvermont),它仍然只有 3 个前缀。
【解决方案3】:

请注意,在代码方面,英特尔处理器中只有一条 NOP 指令。代码为 0x90,只有一个字节。

较长的 "nop" 是不执行任何操作的指令,例如自身的寄存器 XCHG。例如,对于“2 bytes NOP”,您可以这样写:

XCHG AL, AL

编码为:

86 C0

因此您可以编写宏来获得您想要的任何大小。找到所有这些“什么都不做”的指令需要做一些工作。另外,有时(大多数情况下)编译器会尝试优化您的表达式。这就是可能需要输入代码的地方。

我所知道的最长编码将使用LEA 指令。这是地址偏移量的大小可以优化的地方,因为它们将是零,很多零,并且它们应该被优化。

正如 Jester 所说,您可以使用现有的宏。 Internet 上有该文件的副本。

https://github.com/letolabs/nasm/blob/master/macros/smartalign.mac

解码所有这些指令并查看它们是什么会很有趣。

例如,他们使用 MOV %si, %si 创建一个 2 字节的 NOP。

【讨论】:

  • Multi-byte NOP opcode made official 来自 2006 年 ...另请参阅 felixcloutier - 它们都被称为 nop。
  • @usr2564301 啊!我想我错过了这几行......不过,大多数仍然被解码为相应的指令。我想反汇编程序员应该意识到这一壮举,而且编译器可能应该引入nop +size 或类似的东西。
  • 我想这与mov al,[eax+1] 与人为延长mov al,[eax+0x0000001] 的问题相同。反汇编程序过去常常显示底层字节码的逐字节表示,但现在这不再起作用了。
  • 仅供参考,0x90 是xchg ax, ax。这不是“官方”NOP,它只是英特尔标记为 NOP 的无操作指令。
  • @DavidHoelzer:在 x86-64 中,0x90 是真正的 NOP。如果它以xchg eax,eax 运行,它会将 EAX 零扩展为 RAX。如果写xchg eax,eax,则不能编码为90;它必须使用通用的xchg r/m32, r32 编码,因为 NOP 指令接管了0x90 xchg-with-eax 操作码。 (其他寄存器仍然可以使用短编码,如0x91 到 xchg eax 和 ecx)。我不确定汇编程序如何选择以 32 位或 64 位模式对 xchg ax,ax 进行编码。 66 90 是合法的,尽管它是真正的 NOP,而不是 3-uop xchg。 (不过,用作 NOP 的 LEA 编码并不是真正的 NOP。)
猜你喜欢
  • 1970-01-01
  • 2018-11-22
  • 2021-12-28
  • 1970-01-01
  • 2020-02-21
  • 1970-01-01
  • 1970-01-01
  • 2011-12-29
  • 1970-01-01
相关资源
最近更新 更多