x86 NOP 和 FNOP 指令有啥区别?

Posted

技术标签:

【中文标题】x86 NOP 和 FNOP 指令有啥区别?【英文标题】:What's the difference between the x86 NOP and FNOP instructions?x86 NOP 和 FNOP 指令有什么区别? 【发布时间】:2014-07-29 05:30:12 【问题描述】:

我在阅读Intel instruction manual 并注意到有一条“NOP”指令在主 CPU 上什么都不做,而一个“FNOP”指令在 FPU 上什么都不做。为什么有两个单独的指令什么都不做?

我看到的唯一不同是它们抛出了不同的异常,因此您可以观察 FNOP 的异常以检测是否有可用的 FPU。但是没有像 CPUID 这样的其他机制来检测这个吗?有两个单独的 NOP 指令有什么实际原因?

【问题讨论】:

原来8086和8087是分开的芯片。 8086 进行整数运算,nop 告诉 8086 什么也不做。 8087 做了浮点​​运算,fnop 告诉 8087 什么也不做。 我确实认为这是#MF 例外。它仅在显式写入等待 FPU 指令(通常为 FWAIT)时才会引发。当您开始使用 0x90 开始剔除代码时,该异常会在代码中完全不同的位置引发。 【参考方案1】:

扩展 Raymond Chen 和 Hans Passant 的 cmets,有两个单独的指令以及为什么它们不完全具有相同效果的历史原因。

NOPFNOP 这两条指令最初都没有设计为显式无操作指令。 NOP 指令实际上只是指令XCHG AX,AX 的别名。 (或者在 32 位模式下XCHG EAX, EAX。)在早期的英特尔处理器上,它实际上并没有什么作用。虽然它没有外部可见的影响,但在内部它就像XCHG 指令一样执行,需要尽可能多的周期来执行。 '486 是第一个对其进行特殊处理的 Intel CPU,它可以在 1 个周期内执行 NOP,而执行任何其他寄存器到寄存器 XCHG 指令需要 3 个周期。

处理XCHG AX,AX 指令在现代英特尔处理器中变得非常重要。如果它实际上仍在与自己交换相同的寄存器,那么如果附近的指令也使用AX 寄存器,它可能会引入管道停顿。通过特殊处理,CPU 最终不会认为NOP 需要等待设置AX 的前一条指令,或者后面的指令需要等待NOP

这表明有许多不同的指令什么都不做,尽管XCHG AX,AX 是唯一一个单字节的指令(作为exchange-register-with-accumulator single byte XCHG encodings 的特例)。这些指令通常用作连续NOP 指令的单个指令替代,例如出于性能原因对齐循环开始时。例如,如果你想要一个 6 字节的 NOP,你可以使用LEA EAX,[EAX + 00000000]。英特尔最终添加了一个显式的多字节 NOP 指令。 (好吧,与其说是官方记录的那样添加了自 Pentium Pro 以来一直存在的指令。)但是只有单字节形式被特殊处理;如果附近的指令使用相同的寄存器,多字节 NOP 将产生停顿。

当 AMD 为其 CPU 添加 64 位支持时,他们走得更远。 NOP 不再等同于 64 位模式下的 XCHG EAX,EAX。英特尔指令集的问题之一是有很多指令只修改部分寄存器。例如MOV BX,AX 仅修改EBX 的低 16 位,而高 16 位保持不变。这些部分修改使 CPU 很难避免停顿,因此 AMD 决定在 64 位模式下使用 32 位指令时防止这种情况。每当 32 位操作的结果存储在(64 位)寄存器中时,the value is zero extended to 64-bits so that entire register is modified。这意味着XCHG EAX,EAX 不再是 NOP,因为它清除了EAX 的高 32 位(因此,如果您显式编写 XCHG EAX,EAX,它无法组装到 0x90,并且必须使用 87 C0 编码)。在 64 位模式下,NOP 现在是一个明确的 NOP,没有其他解释。


至于FNOP 指令,在最初的 8087 上,FPU 是如何处理这条指令的并不完全清楚,但我很确定它也没有作为明确的无操作处理。至少一本旧的英特尔手册,ASM86 Language Rerefence Manual 确实记录了一些没有效果的事情(“将堆栈顶部存储到堆栈顶部”)。从它在操作码映射中的位置来看,它可能是FST STFLD ST 的别名,两者都会将栈顶复制到栈顶。然而,它确实得到了一些特殊处理,它平均执行 13 个周期,而不是平均 18 或 20 个周期,堆栈分别堆栈 FSTFLD 指令。如果它被视为无操作指令,我希望它会更快,因为有许多 8087 指令可以在一半的时间内执行。

更重要的是,FNOP 指令的行为与 NOP 不同,因为 FPU 指令过去是在英特尔处理器上实现的。 CPU 本身不支持浮点运算,而是将这些任务卸载到可选的浮点协处理器上,最初是 8087。协处理器的优点之一是它与 CPU 并行执行指令。然而,这意味着 CPU 有时需要等待 FPU 完成操作。 CPU 会自动等待它完成前一条指令的执行,然后再给它下一条指令,但程序需要显式等待(使用WAIT 指令)才能读取协处理器写入内存的结果。

因为协处理器并行工作,这也意味着如果 FPU 指令产生浮点异常,当它检测到这一点时,CPU 已经开始执行下一条指令。通常,当一条指令在 CPU 上产生异常时,会在该指令仍在执行时对其进行处理,但当 FPU 指令产生异常时,CPU 已经通过将其移交给 FPU 完成了该指令的执行。不是中断 CPU 并异步传递浮点异常,而是仅在显式或隐式等待协处理器时通知 CPU。

在现代处理器中,FPU 不再是协处理器,而是 CPU 的一个组成部分。这意味着程序不再需要等待 FPU 将值写入内存。但是,处理 FPU 异常的方式没有改变。 (事实证明,在现代 CPU 上很难实现立即传递异常,因此他们利用了他们不必这样做的一种情况。)因此,如果之前的 FPU 指令生成了未传递的浮点异常,NOP不传递异常,而FNOP,因为它是一条 FPU 指令,将执行隐式“等待”,导致浮点异常被传递。

这个例子演示了区别:

FLD1       ; push 1.0 onto the FPU stack
FLDZ       ; push 0.0
FDIV       ; divide 1.0 by 0.0
NOP        ; does nothing
NOP        ; does nothing
FNOP       ; signals a FP zero-divide exception and then does nothing

【讨论】:

我以为我对 NOP 了如指掌 :) 写得很好。 是的,我只是在搜索网络后偶然发现了 64 位 NOP 部分,以验证其他一些细节。 感谢您的回复 - 这是对我问题的一个很好的回答。 所以FNOP 和FWAIT 都在等待x87 异常?该手册没有提到 FNOP 的效果,并暗示这是 all FWAIT 所做的。这真的正确吗,FNOP和FWAIT之间没有区别?如果不是,那么我们又回到了 FNOP 为何退出的问题。 @PeterCordes 在查看旧手册后,我发现我错了,FNOP 实际上只是源操作数为 ST 的 FST 指令,因此它将 ST 设置为 ST。因此它不会浪费任何编码空间,尽管它似乎在执行时经过特殊处理,因为它的执行周期更少(13 对 18)。

以上是关于x86 NOP 和 FNOP 指令有啥区别?的主要内容,如果未能解决你的问题,请参考以下文章

"rep; nop;" 是啥意思?在 x86 程序集中是啥意思?它与“暂停”指令相同吗?

32 位 X86 中的局部变量和全局变量有啥区别?

Android 逆向x86 汇编 ( align | db | dw | dd | nop | 伪指令 )

Android 逆向x86 汇编 ( align | db | dw | dd | nop | 伪指令 )

KVM、QEMU和KQemu有啥区别

ELF 文件和 bin 文件有啥区别?