为什么x86中的nopl
指令采用操作数?nops不就是什么都不做吗?
nopl 0x0(%rax)
许多处理器的二进制指令集有多种方式来表示功能相同的指令。例如,原始的ARM指令集包括以b << n
形式的任意值加载R0的指令,其中b
是0到255之间的值,n
是0到24之间的偶数。如果想用值256来加载R0,可以用1<<8
来加载R0的指令,也可以用4<<6
、16<<4
或64<<2
来加载R0的指令。加载这些不同值的指令都有不同的二进制编码,尽管这四条指令具有相同的效果。
一些编译器的汇编程序特意提供一种方法来请求一段代码应该使用看似相同的指令中的哪一条。虽然这通常并不重要,但有时可能需要避免在代码段中使用某些字节值,或者有时修改代码段中的某些字节应该产生特定的效果。例如,前面提到的ARM指令中的8位用来指定b
的值。如果代码用值12覆盖上面一条指令的b
部分,则加载到R0中的值将取决于原始四条指令中的哪一条被使用;可以是0x0C00、0x0300、0x00C0或0x0030。
尽管8x86的汇编程序通常不能显式地区分所有可能的指令编码,但是在某些情况下,能够指定指令中应该包含哪些字节值可能是有帮助的。例如,处理异常的一种方法是在发生异常时进行例行检查,检查返回地址处的指令是否是某种特定形式的NOP,如果是,则将其操作数解释为保存异常相关信息的数据结构的地址。在实践中,大多数8 x86语言支持异常使用其他方式处理,但上述方法将减缓正常函数返回获取所需的时间和执行一个长NOP,但能够有效地处理特殊出口相对(大多数语言使用较慢的方法来处理中断的利益避免执行NOP的成本在不例外的情况下,但其他语言可以选择用不同的方式来做事情)。
有时我在调试时使用nops。如果我知道某些东西是如何出错的,但它需要数千个断点中断才能发现我编写的代码来测试它。它可能看起来像这样(c风格的代码):
if (condition_occurred)
{
asm("nop");
}
当我在"asm"行上设置断点时,调试器将使用nop的线性(物理)地址(对应于虚拟地址)设置DRx寄存器。当到达此位置时,将发生断点中断,并进入调试器。如果在没有调试器的情况下执行,nop将被处理(什么都不会发生)。所以这里我想要一个什么都不做的指令,它做(不做)是有意义的。
这里是一个"什么都不做"的nop指令实际上做了一些事情的例子…尽管间接。
请参阅本文第8页,并注意示例3中循环的第一条(顶部)指令(这是示例2的发展)。还有页面右下方的脚注。
作者暗示,额外的nops可能会进一步加快这一进程。