为什么x86的nopl指令需要带操作数?

15

为什么x86中的nopl指令需要一个操作数? 难道nop不就是什么都不做吗?

nopl   0x0(%rax)

你可以对一个参数“什么也不做”,对吧?90 nop 也有参数,实际上是 xchg eax, eax - harold
1
我问这个问题是因为我正在使用的反编译器将其归类为分支指令。有任何可能导致这种情况发生的原因吗? - RouteMapper
这有点像在一小段数据上建立分支(编码参数的字节),这有时被用来高效地跳过某些必须存在但原本是多余的数据(在我的看法中是一种丑陋的黑科技,但嘿……),因此你可以把它看做是一个分支。 - harold
需要哪些数据?操作数吗? - RouteMapper
1
supercat 给出了一个例子,说明这些数据有时候被用来做什么。 - harold
显示剩余4条评论
2个回答

9
许多处理器的二进制指令集有多种表示功能相同的指令的方式。例如,原始ARM指令集包括将R0加载为任何形式的值b << n 的指令,其中b是0到255的值,n是从0到24的偶数。如果想要将R0加载为值256,则可以加载加载它的指令与1<<8一样,或者可以使用4<<616<<464<<2的指令。尽管所有四个指令具有相同的效果,但加载这些不同值的指令都具有不同的二进制编码。
一些编译器的汇编程序费尽心思地提供了请求代码应该使用哪个看似相同的指令的方法。虽然通常这并不重要,但有时可能希望避免在代码片段中使用某些字节值,或者有时修改代码片段中的某些字节应该产生特定的效果。例如,前面提到的ARM指令中有8位用于指定b的值。如果代码覆盖上述指令中的b部分,并将其值设置为12,则加载到R0中的值将取决于使用了哪个原始的四个指令;它可能是0x0C00、0x0300、0x00C0或0x0030。
虽然8x86的汇编程序通常不能明确区分所有可能的指令编码,但在某些情况下能够指定应该包括哪些字节值在指令中可能会有所帮助。例如,处理异常的一种方法是具有一个例程,在发生异常时检查返回地址处的指令是否为一种特定形式的NOP,并且如果是,则将其操作数解释为保存异常相关信息的数据结构的地址。实际上,大多数支持异常的8x86语言都使用其他方法来处理它们,但前面提到的方法会通过需要获取和执行长NOP的时间来减慢正常函数返回的速度,但能够相对高效地处理异常退出(大多数语言为了避免在没有异常的情况下执行NOP的成本而使用较慢的方法来处理中断,但其他语言可以选择采取不同的方法)。

4
虽然这个条件为真,在这种情况下它更有用,因为操作数会改变指令的长度,就像评论中 nrz 的回答 所示。 - ughoavgfhw
如果只需要一个适当长度的任意NOP,可以简单地定义nopnop2nop3等,或者有一个助记符用于“生成最快的n字节nop序列”,其参数是所需大小。如果只需要例如7字节的NOP,则无需指定四个字节中的值。 - supercat
这正是大多数人所做的,如果汇编器没有预定义类似的助记符,他们就会使用宏来实现特定的NOP长度。 (大多数人只使用“align”指令,而不是手动多字节NOP;如果您需要手动填充指令,通常会希望将现有指令变长,而不是添加单独的NOP。在现代x86上可以使用哪些方法有效地扩展指令长度?)。因此,通常在汇编器或JIT的C源代码中看到NOP1 / NOP2 /等定义,而不是在汇编语言本身中。 - Peter Cordes

2

有时候我在调试时使用nops。如果我知道某些东西出了问题,但需要数千个断点才能发现,那么我会编写测试代码来测试它。它可能看起来像这样(C风格的代码):

if (condition_occurred)
{
  asm("nop");
}

当我在“asm”行上设置断点时,调试器将使用DRx寄存器设置一个线性(物理)地址(对应于虚拟地址)的nop。当到达此位置时,会发生断点中断并进入调试器。如果在没有调试器的情况下执行,nop将被处理(什么也不会发生)。因此,在这里,我想要一条确切地什么都不做的指令,这样做是有意义的(不会发生任何事情)。
这是一个例子,其中“无操作”的nop指令实际上会间接地做一些事情。
请参见本文第8页中示例3循环的第一个(顶部)指令(它是示例2的扩展)。还请注意页面右下角的脚注。
作者暗示额外的nop可能会进一步加快进程。
因此,nop绝对有其用处。

4
你似乎回答了问题“为什么存在nop指令?”而这不是在这里被问到的问题。 - Joseph Sible-Reinstate Monica

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