82

我正在浏览 MSIL 并注意到 MSIL 中有很多nop指令。

MSDN 文章说,如果操作码被修补,它们不会执行任何操作并用于填充空间。它们在调试版本中的使用比在发布版本中更多。

我知道这些语句在汇编语言中用于对齐后面的指令,但是为什么 MSIL 中需要 MSIL nop 呢?

(编者注:接受的答案是关于机器代码 NOP,而不是问题最初询问的 MSIL/CIL NOP。)

4

18 回答 18

110

NOP 有多种用途:

  • 它们允许调试器在一行上放置一个断点,即使它与生成的代码中的其他断点结合使用。
  • 它允许加载器使用不同大小的目标偏移量来修补跳转。
  • 它允许代码块在特定边界对齐,这对缓存很有好处。
  • 它允许增量链接通过调用新部分来覆盖代码块,而不必担心整个函数会改变大小。
于 2008-10-24T19:41:12.837 回答
11

以下是调试使用MSIL / CIL nops(不是 x86 机器代码nop)的方式:

语言编译器(C#、VB 等)使用 Nop 来定义隐式序列点。这些告诉 JIT 编译器在哪里确保机器指令可以映射回 IL 指令。

Rick Byer 关于DebuggingModes.IgnoreSymbolStoreSequencePoints的博客文章解释了一些细节。

C# 还在调用指令之后放置 Nops,以便源中的返回站点位置是调用而不是调用之后的行。

于 2008-10-25T03:15:34.933 回答
9

它为代码中的基于行的标记(例如断点)提供了机会,其中发布版本不会发出任何内容。

于 2008-10-24T19:16:39.643 回答
6

老兄!无操作太棒了!这是一个除了消耗时间什么都不做的指令。在昏暗的黑暗时代,您可以使用它来对关键循环中的时序进行微调,或者更重要的是作为自修改代码的填充物。

于 2008-10-24T19:19:02.977 回答
6

在针对特定处理器或架构进行优化时,它还可以使代码运行得更快:

长期以来,处理器采用多条大致并行工作的流水线,因此可以同时执行两条独立的指令。在具有两个管道的简单处理器上,第一个可能支持所有指令,而第二个仅支持一个子集。此外,当必须等待尚未完成的前一条指令的结果时,流水线之间会出现一些停顿。

在这种情况下,一个专用的nop可能会强制下一条指令进入特定的管道(第一条,或者不是第一条),并改进后续指令的配对,从而使nop的成本 超过摊销。

于 2008-10-24T22:43:13.267 回答
5

在我最近(四年)工作的一个处理器中,NOP 用于确保在下一个操作开始之前完成上一个操作。例如:

将值加载到寄存器(需要 8 个周期) nop 8 add 1 to register

这确保寄存器在添加操作之前具有正确的值。

另一个用途是填充执行单元,例如必须具有一定大小(32 字节)的中断向量,因为向量 0 的地址是 0,向量 1 的地址是 0x20 等等,所以编译器会在其中放入 NOP需要。

于 2009-05-28T13:03:36.383 回答
4

他们可以在调试时使用它们来支持编辑并继续。它为调试器提供了工作空间,可以在不更改偏移量等的情况下用新代码替换旧代码。

于 2008-10-24T19:24:02.250 回答
4

50 年太晚了,但是嘿。

如果您手动输入汇编代码,则 Nop 很有用。如果你必须删除代码,你可以 nop 旧的操作码。

类似地,您可以通过覆盖一些操作码并跳转到其他地方来插入新代码。在那里放置被覆盖的操作码,并插入新代码。准备好后你跳回去。

有时您必须使用可用的工具。在某些情况下,这只是一个非常基本的机器代码编辑器。

如今,对于编译器,这些技术不再有意义。

于 2009-05-28T12:46:59.147 回答
3

它们的一个典型用途是使您的调试器始终可以将源代码行与 IL 指令相关联。

于 2008-10-24T19:17:58.797 回答
3

在软件破解场景中,解锁应用程序的经典方法是使用 NOP 修补检查密钥或注册或时间段或诸如此类的行,这样它就什么也不做,只是继续启动应用程序,就好像它已注册一样.

于 2008-10-24T19:33:08.330 回答
3

我还看到代码中的 NOP 会修改自身以混淆它作为占位符的作用(非常古老的复制保护)。

于 2008-10-24T20:03:31.873 回答
3

正如 ddaa 所说,nops 让您考虑堆栈中的差异,因此当您覆盖返回地址时,它会跳转到 nop 雪橇(连续很多 nop),然后正确命中可执行代码,而不是跳转到一些不是开始的指令中的字节。

于 2008-10-25T02:24:50.277 回答
3

NOP-Slides是一个有点非正统的用法,用于缓冲区溢出漏洞利用。

于 2009-02-02T09:28:29.030 回答
1

它们允许链接器将较长的指令(通常是长跳转)替换为较短的指令(短跳转)。NOP 占用了额外的空间 - 代码无法移动,因为它会阻止其他跳转工作。这发生在链接时,因此编译器无法知道长跳转或短跳转是否合适。

至少,这是他们的传统用途之一。

于 2008-10-24T19:22:23.920 回答
1

这不是您特定问题的答案,但在过去,如果您无法用其他有用的指令填充它,您可以使用 NOP 来填充分支延迟槽。

于 2008-10-24T19:26:04.490 回答
1

.NET 编译器是否对齐 MSIL 输出?我想它可能对加快对 IL 的访问很有用......另外,我的理解是它被设计为可移植的,并且在其他一些硬件平台上需要对齐访问。

于 2008-10-24T19:27:27.797 回答
1

我学到的第一个程序集是 SPARC,所以我熟悉分支延迟槽,如果你不能用另一条指令填充它,通常是你要放在分支指令之上的指令或在循环中增加一个计数器,你使用一个NOP。

我不熟悉破解,但我认为使用 NOP 覆盖堆栈是很常见的,因此您不必准确计算恶意函数的开始位置。

于 2008-10-24T20:55:02.597 回答
1

我使用 NOP 自动调整进入 ISR 后累积的延迟。非常方便地确定时间。

于 2008-10-25T04:21:08.857 回答