【问题标题】: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 的用途和用途?

标签: assembly bytecode cil


【解决方案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 后累积的延迟。非常方便地确定时间。

                    【讨论】:

                      猜你喜欢
                      • 2012-04-01
                      • 1970-01-01
                      • 2023-03-11
                      • 1970-01-01
                      • 1970-01-01
                      • 1970-01-01
                      • 1970-01-01
                      • 1970-01-01
                      • 2019-03-30
                      相关资源
                      最近更新 更多