CIL nop 操作码的目的是啥?
Posted
技术标签:
【中文标题】CIL nop 操作码的目的是啥?【英文标题】:What's the purpose of the CIL nop opcode?CIL nop 操作码的目的是什么? 【发布时间】:2010-09-19 02:13:48 【问题描述】:我正在浏览 MSIL,并注意到 MSIL 中有很多 nop 指令。
MSDN 文章说,如果操作码被修补,它们不会执行任何操作并用于填充空间。与发布版本相比,它们在调试版本中的使用要多得多。
我知道汇编语言中会使用这类语句来对齐后面的指令,但是为什么 MSIL 中需要 MSIL nop?
(编者注:接受的答案是关于机器代码 NOP,而不是问题最初询问的 MSIL/CIL NOP。)
【问题讨论】:
在语言编译器向程序集发出的 MSIL nop 指令与运行程序集时 JIT 编译器发出的 x86 nop 指令(在该平台上)之间存在大量混淆。 [事实上,公认的答案是关于 x86 nop,与 MSIL 无关。] 理想情况下,这个问题应该分成 2 个不同的问题: MSIL::nop 的目的?原生平台 nop 的用途和用途? 【参考方案1】:NOP 有多种用途:
它们允许调试器在一行上放置一个断点,即使它在生成的代码中与其他断点组合。 它允许加载程序使用不同大小的目标偏移来修补跳转。 它允许代码块在特定边界对齐,这对缓存很有好处。 它允许增量链接通过调用新部分来覆盖代码块,而不必担心整个函数会改变大小。【讨论】:
来自Wikipedia:“NOP 最常用于计时目的,强制内存对齐,防止危险,占用分支延迟槽,使现有指令无效,例如跳转, 或作为占位符,稍后在程序开发中被活动指令替换(或在重构有问题或耗时时替换已删除的指令)。在某些情况下,NOP 可能有轻微的副作用;例如,在 Motorola 68000 系列处理器上,NOP 操作码会导致管道同步。”【参考方案2】:以下是调试使用MSIL / CIL nops(不是x86机器代码nop
)的方式:
语言编译器(C#、VB 等)使用 Nops 来定义隐式序列点。这些告诉 JIT 编译器在哪里确保机器指令可以映射回 IL 指令。
Rick Byer 在DebuggingModes.IgnoreSymbolStoreSequencePoints 上的博客文章解释了一些细节。
C# 还在调用指令之后放置 Nops,以便源中的返回站点位置是调用而不是调用之后的行。
【讨论】:
C# 还在调用指令之后放置 Nops,以便源中的返回站点位置是调用而不是调用之后的行。 不确定我是否明白这一点。你有什么参考资料吗?【参考方案3】:它为代码中的基于行的标记(例如断点)提供了机会,其中发布版本不会发出任何信号。
【讨论】:
断点需要在 nop 上吗?为什么不在常规操作码上放一个断点? 许多常规操作码在发布版本中得到优化。这会弄乱你的断点,除非有一个占位符 nop 断点仍然指向 在调试版本中,它们还用于提供一条指令以在代码没有指令的地方中断。例如,打开大括号。 您可能需要添加对blogs.msdn.com/oldnewthing/archive/2007/08/17/4422794.aspx 的引用作为来源。 好吧,感谢 Greg 和 Jimmy 将其转化为实际答案。【参考方案4】:老兄!无操作太棒了!这是一个除了消耗时间什么都不做的指令。在昏暗的黑暗时代,您可以使用它来对关键循环中的时序进行微调,或者更重要的是作为自我修改代码的填充物。
【讨论】:
我了解它在直接在硬件上运行时的用途,但 MSIL 是 JITed。 MSIL 仅在具有 JIT 的系统上进行 JIT - MSIL 不需要 JIT。【参考方案5】:在针对特定处理器或架构进行优化时,它还可以使代码运行得更快:
处理器长期以来采用多条大致并行工作的流水线,因此可以同时执行两条独立的指令。在具有两个管道的简单处理器上,第一个可能支持所有指令,而第二个仅支持一个子集。此外,当必须等待尚未完成的前一条指令的结果时,流水线之间会出现一些停顿。
在这种情况下,一个专用的nop可能会强制下一条指令进入一个特定的流水线(第一条,或者不是第一条),并改善后续指令的配对,使得nop 不仅仅是摊销。
【讨论】:
【参考方案6】:在我最近(四年)工作的一个处理器中,NOP 用于确保前一个操作在下一个操作开始之前完成。例如:
加载值到寄存器(需要 8 个周期) 8号 加1注册
这确保寄存器在添加操作之前具有正确的值。
另一个用途是填充执行单元,例如必须有一定大小(32 字节)的中断向量,因为向量 0 的地址是 0,向量 1 的地址是 0x20 等等,所以编译器会放入 NOP如果需要,在那儿。
【讨论】:
【参考方案7】:他们可以在调试时使用它们来支持编辑并继续。它为调试器提供了工作空间,可以在不更改偏移量等的情况下用新代码替换旧代码。
【讨论】:
我在 VS 中实现了对编辑和继续的调试器支持(我没有实现 CLR 或编译器部分)。 Nops 是确保从旧版本到新版本方法的正确映射的一部分(特别是在跳出异常处理代码的情况下)。但是,更换 MSIL 是在整个功能的基础上完成的。对于 CLR 托管代码,没有必要在 msil 中“留出空间”来完成该部分。关于原生编辑并继续,您在此处所说的内容是正确的。【参考方案8】:50 年太晚了,但是嘿。
如果您手动输入汇编代码,则 Nop 很有用。 如果你必须删除代码,你可以 nop 旧的操作码。
类似地,您可以通过覆盖一些操作码并跳转到其他地方来插入新代码。在那里放置被覆盖的操作码,并插入新代码。准备好后跳回去。
有时您必须使用可用的工具。在某些情况下,这只是一个非常基本的机器代码编辑器。
如今,对于编译器,这些技术已经毫无意义了。
【讨论】:
【参考方案9】:它们的一个典型用途是让您的调试器始终可以将源代码行与 IL 指令相关联。
【讨论】:
由于 msil 在运行时是 JIT 编译的,因此可能会丢失该映射(例如,本机指令没有唯一的 MSIL 指令)。 NOP 用作从语言编译器到 JIT 编译器的通信机制,以保留这样的映射。【参考方案10】:在软件破解场景中,解锁应用程序的经典方法是使用 NOP 修补检查密钥或注册或时间段或诸如此类的行,这样它就什么也不做,只是继续启动应用程序,就好像已注册。
【讨论】:
我很确定无操作指令并不是为了帮助人们盗版软件而发明的 :-)【参考方案11】:我还看到代码中的 NOP 会修改自身以混淆它作为占位符的作用(非常古老的复制保护)。
【讨论】:
【参考方案12】:正如 ddaa 所说,nops 让您考虑堆栈中的差异,因此当您覆盖返回地址时,它会跳转到 nop sled(连续的很多 nop),然后正确命中可执行代码,而不是跳转到指令中不是开始的某个字节。
【讨论】:
【参考方案13】:NOP-Slides 是一个有点非正统的用法,用于缓冲区溢出攻击。
【讨论】:
正在寻找这个:) 更正您的链接,它不起作用,请始终确保,您提交了网络存档链接,因为他们不会给出 404 未找到。 目前正在研究 shellcode,想要更多关于 NOP 的上下文。不得不回去一点,但这里有一个档案:web.archive.org/web/20110124015428/http://www.phreedom.org:80/…【参考方案14】:它们允许链接器将较长的指令(通常是长跳转)替换为较短的指令(短跳转)。 NOP 占用了额外的空间 - 代码无法移动,因为它会阻止其他跳转工作。这发生在链接时,因此编译器无法知道长跳转还是短跳转合适。
至少,这是它们的传统用途之一。
【讨论】:
【参考方案15】:这不是对您特定问题的回答,但在过去,如果您无法用其他有用的指令填写它,您可以使用 NOP 填写 branch delay slot。
【讨论】:
【参考方案16】:.NET 编译器是否对齐 MSIL 输出?我想它可能对加快对 IL 的访问很有用......另外,我的理解是它被设计为可移植的,并且在其他一些硬件平台上需要对齐访问。
【讨论】:
【参考方案17】:我学习的第一个程序集是 SPARC,所以我熟悉分支延迟槽,如果你不能用另一条指令填充它,通常是你要放在分支指令之上的指令或在循环中增加一个计数器, 你使用 NOP。
我不熟悉破解,但我认为使用 NOP 覆盖堆栈是很常见的,因此您不必准确计算恶意函数的开始位置。
【讨论】:
【参考方案18】:我使用 NOP 自动调整进入 ISR 后累积的延迟。非常方便地确定时间。
【讨论】:
以上是关于CIL nop 操作码的目的是啥?的主要内容,如果未能解决你的问题,请参考以下文章