nasm中的长nop指令

6

nasm有内置的方法来发出给定长度的long-nop(又称多字节nop)指令吗?


1
请参阅NASM手册中的smartalign章节(http://www.nasm.us/xdoc/2.11.08/html/nasmdoc5.html#section-5.2)。 - Jester
@Jester - 是的,我知道这个并使用它 - 但是它不直接访问nop指令:它们只能通过“align”指令间接插入。我想直接使用它们,例如,“在这里插入一个2字节的nop”。 - BeeOnRope
1
您可以直接使用宏,例如 db __ALIGN_32BIT_2B__ 可以在 32 位代码中插入一个 2 字节的 NOP。 - Jester
@Jester - 谢谢 - 我在哪里可以看到它们?我在手册中找不到该字符串。 - BeeOnRope
1
它在宏包本身中,因此未记录并且可能会更改。 - Jester
快速浏览felixcloutier建议官方表示法为nop [rm16/rm32]。如果nasm接受这种表示法,你可以自己创建一组宏,这样就不需要依赖于“未经记录”的外部宏。(或者-哎呀,迟来的想法-复制它们的定义...) - Jongware
3个回答

4

结论似乎是:没有官方的方法在nasm1中直接发出这些长nop指令。

因此,我根据Intel手册2中推荐的序列编写了自己的宏来生成1到9个字节的nop指令:

;; 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项目中,因此,如果您有相同的需求,这是一种获取它们的方法。


1尽管正如Jester 所指出的,您可以深入了解内部实现,找到用于实现“智能对齐”功能的一些宏。

2值得一提的是,我认为这些首先出现在AMD手册中,并且最终英特尔采用了相同的推荐序列。


考虑到在 nop6nop9 宏中给出的操作数大小前缀 66h,我认为这些行的注释应该读作 WORD PTR 而不是 DWORD PTR - Sep Roland
2
@SepRoland - 那么在这种情况下,我不应该使用66来表示,因为它已经被WORD隐含了。基本上,66H, NOP DWORD ptr [EAX + EAX*1 + 00H]NOP WORD ptr [EAX + EAX*1 + 00H]是写同一件事的两种方式,如果你明确地编码一个db 0x66,然后跟着DWORD版本,你会得到相同的字节。我上面展示的形式是从英特尔手册中逐字复制的。请注意,注释甚至对于64字节模式也不正确:如果您实际上组装了带有地址的nop,则所有地址都比注释版本长1个字节,因为它们将具有... - BeeOnRope
由于32位地址9(如“[eax ...]”)不是默认值,因此在使用Intel语法时需要添加额外的0x67前缀。但无论哪种模式,逐字节编码都是可以的。 - BeeOnRope
x86-64是否允许使用“66 REX nopl ...”进行10字节的NOP指令,以便在所有CPU上都能高效解码?即使在0F转义字节被计算为前缀的CPU(Silvermont)上,它仍然只有3个总前缀。 - Peter Cordes

2
只是引用了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]具有特殊的硬件支持。虽然它仍然消耗一个微操作和相关资源,但不再依赖于旧值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.

1
看起来这些序列避免了在P6中引入的长nop 0F 1F modrm ...,而是使用lea。以这种方式使用LEA在体系结构上是“真正的nop”,但在微体系结构上不是,而0F 1F90短NOP相同,不使用任何执行端口并且不会延长涉及任何寄存器的dep链。在x86-64代码中,您应该始终使用0F 1F NOPs而不是LEA,或者在还使用CMOV或其他P6功能的32位代码中也可以使用。 - Peter Cordes

-2
请注意,英特尔处理器中只有一条NOP指令。它的代码是0x90,只有一个字节。
更长的"nop"指令是不执行任何操作的指令,例如将寄存器与自身进行XCHG。例如,对于"2字节NOP",您可以编写:
XCHG AL, AL

这被编码为:

86 C0

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

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

正如Jester提到的那样,您可以使用现有的宏。互联网上有该文件的副本。

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

解码所有这些指令并查看它们是令人兴奋的。

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


2
“Multi-byte NOP opcode made official”(多字节NOP操作码正式发布)是来自2006年的消息...另请参阅felixcloutier——它们都被称为nop - Jongware
1
@usr2564301 啊!我猜我错过了这几行代码...虽然大多数仍然被解码为相应的指令。我想反汇编程序员应该意识到这一点,编译器可能也应该引入一个 nop +size 或类似的东西。 - Alexis Wilke
我想这个问题和 mov al,[eax+1] 与人为延长的 mov al,[eax+0x0000001] 是一样的。反汇编器过去会显示底层字节码的逐字节文字表示,但现在这种方法已经不再有效了。 - Jongware
1
FYI,0x90 是xchg ax,ax。它不是“官方”的NOP,只是Intel将其标记为无操作指令。 - David Hoelzer
4
在x86-64中,0x90是真正的NOP指令。如果它被解释为xchg eax, eax,那么它将把EAX零扩展为RAX。如果您写入xchg eax, eax,编码就不能使用90,因为NOP指令已经占用了0x90 xchg-with-eax操作码。但其他寄存器仍然可以使用短编码,例如0x91表示xchg eax和ecx。我不确定汇编器在32位或64位模式下如何选择编码xchg ax, ax66 90也是合法的,尽管它是真正的NOP,而不是3-uop xchg。(虽然LEA编码用作NOP不是真正的NOP) - Peter Cordes

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接