CIL nop操作码的目的是什么?


82

我正在经历MSIL,并注意到MSIL中有很多nop指令。

MSDN文章说,如果对操作码进行了修补,它们将不采取任何措施,并被用于填充空间。在调试版本中使用的版本比发行版本更多。

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

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


19
在运行程序集时,语言编译器向程序集中发出的MSIL nop指令与JIT编译器发出的x86 nop指令(在该平台上)之间,这些答案存在很大的混淆。[实际上,可接受的答案是关于x86点,并且与MSIL没有关系。]理想情况下,该问题应分为两个不同的问题:MSIL :: nop的目的?平台nop的目的是什么?
史蒂夫·史坦纳

Answers:


107

NOP有几个用途:

  • 它们允许调试器在一行上放置一个断点,即使该断点在生成的代码中与其他断点结合在一起也是如此。
  • 它允许装载机修补具有不同大小目标偏移量的跳跃。
  • 它允许将代码块在特定边界处对齐,这对于缓存很有用。
  • 它允许增量链接通过调用新节来覆盖代码块,而不必担心整个函数的大小会改变。

来自维基百科:“ NOP最常用于计时目的,用于强制内存对齐,防止危险,占用分支延迟时隙,使现有指令(例如跳转)无效或用作要替换的占位符在以后的程序开发中使用主动指令(或在重构时替换已删除的指令会出现问题或耗时)。在某些情况下,NOP会产生较小的副作用;例如,在Motorola 68000系列处理器上,NOP操作码将导致管道同步。”
mbomb007 '17

10

这是调试如何使用MSIL / CIL nops(不是x86机器代码nop)的方法:

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

Rick Byer在DebuggingModes.IgnoreSymbolStoreSequencePoints上的博客条目解释了一些细节。

C#还将Nops置于呼叫指示之后,以便源中的返回站点位置是呼叫,而不是呼叫之后的行。


C#还将Nops置于呼叫指示之后,以便源中的返回站点位置是呼叫,而不是呼叫之后的行。 不知道我是否明白这一点。您有参考资料吗?
user492238

8

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


断点需要暂停吗?为什么不只是在常规操作码上设置断点?
丹·戈德斯坦

4
许多常规操作码在发行版本中得到了优化。除非您没有占位符,否则那会弄乱您的断点,那里的断点仍然指向
Jimmy

1
在调试版本中,它们还用于提供指令,以在代码没有指令的地方中断。例如,大括号。
Greg D

1
您可能需要添加对blogs.msdn.com/oldnewthing/archive/2007/08/17/4422794.aspx的引用作为源。
Greg D

1
好吧,谢谢格雷格(Greg)和吉米(Jimmy)将其变为实际答案。
哈波

6

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

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

在这种情况下,专用的nop可能会强制下一条指令进入特定的流水线(第一个或不是第一个),并改善后续指令的配对,以使nop的成本 被摊销。


5

杜德!无操作真棒!这是一条只消耗时间的指令。在昏暗的黑暗时代,您可以使用它来对关键循环中的时序进行微调整,或者更重要的是,它可以用作自我修改代码中的填充物。


我了解它在直接在硬件上运行时的用法,但是MSIL是JITed。
丹·戈德斯坦

MSIL仅在具有JIT的系统上JIT-MSIL不需要JIT。
底座

5

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

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

确保在进行加法运算之前寄存器具有正确的值。

另一个用途是填充执行单元,例如中断向量必须是一定大小(32字节),因为向量0的地址对于向量1 0x20来说是0,以此类推,因此,编译器将NOP放在其中需要。


4

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


4
我在VS for Edit中实现了调试器支持,然后继续(我没有实现CLR或编译器部分)。要确保从方法的旧版本到新版本的正确映射(特别是在跳出观念处理代码的情况下),小插曲是故事的一部分。但是,替换MSIL是在整个功能的基础上完成的。对于CLR托管代码,没有必要在msil中“留出空间”来完成该部分。对于本地“编辑并继续”,您在这里说的是正确的。
史蒂夫·斯坦纳


4

50年为时已晚,但嘿。

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

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

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

如今,对于编译器而言,这些技术已毫无意义。


3

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


由于msil是在运行时JIT编译的,因此可能会丢失该映射(例如,本机指令没有唯一的MSIL指令)。NOP用作从语言编译器到JIT编译器的通信机制,以保留这种映射。
史蒂夫·斯坦纳

3

在软件破解场景中,解锁应用程序的经典方法是使用NOP修补检查密钥,注册或时间段或其他时间的行,因此它无能为力,只是继续启动应用程序就好像注册了一样。


5
我敢肯定,没有发明无操作指令来帮助人们盗版软件:-)
西蒙·霍华德

3

我还看到代码中的NOP会对其进行自我修改,以模糊处理其作为占位符的功能(老式的老式复制保护)。


3

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


1

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

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



1

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


1

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

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


1

我使用NOP来自动调整进入ISR后累积的延迟。非常方便地把握时机。


-1

nop在内存破坏利用有效载荷方面将很有用。当然nopxchg eax, eax


另一个答案已经提到用于代码注入漏洞的NOP幻灯片。所以做了另一种答案在这个同样的问题。这个答案并没有提及如何他们是有用的,如果你不知道的不是很明显。
Peter Cordes
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.