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