CIL nop 操作码的用途是什么?
我正在浏览 MSIL 并注意到有很多 nop 指令。
MSDN 文章称,如果操作码被修补,它们不会采取任何行动,而是用于填充空间。 它们在调试版本中的使用比发布版本中的使用要多得多。
我知道汇编语言中使用此类语句来对齐后面的指令,但是为什么 MSIL 中需要 MSIL nops?
(编者注:接受的答案是关于机器代码 NOP,而不是问题最初询问的 MSIL/CIL NOP。)
I'm going through MSIL and noticing there are a lot of nop instructions in the MSIL.
The MSDN article says they take no action and are used to fill space if the opcode is patched. They're used a lot more in debug builds than release builds.
I know that these kinds of statements are used in assembly languages to align later instructions, but why are MSIL nops needed in MSIL?
(Editor's note: the accepted answer is about machine-code NOPs, not MSIL/CIL NOPs which the question originally asked about.)
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(18)
NOP 有多种用途:
NOPs serve several purposes:
以下是MSIL / CIL nops(不是x86机器代码
nop
) 用于调试:Nops 用于语言编译器(C# 、VB 等)来定义隐式序列点。 这些告诉 JIT 编译器在哪里确保机器指令可以映射回 IL 指令。
Rick Byer 的博客文章 DebuggingModes.IgnoreSymbolStoreSequencePoints 解释道一些细节。
C# 还在调用指令之后放置 Nops,以便源中的返回站点位置是调用而不是调用后的行。
Here's how MSIL / CIL nops (not x86 machine code
nop
) are used by debugging:Nops are used by language compilers (C#, VB, etc.) to define implicit sequence points. These tell the JIT compiler where to ensure machine instructions can be mapped back to IL instructions.
Rick Byer's blog entry on DebuggingModes.IgnoreSymbolStoreSequencePoints, explains a few of the details.
C# also places Nops after call instructions so that the return site location in source is the call out rather than the line after the call.
它为代码中基于行的标记(例如断点)提供了机会,而发布版本不会发出任何标记。
It provides an opportunity for line-based markers (e.g. breakpoints) in the code where a release build would emit none.
老兄! 无操作太棒了! 这是一条除了消耗时间之外什么也不做的指令。 在昏暗的黑暗时代,您可以使用它对关键循环中的时序进行微调,或更重要的是作为自修改代码的填充物。
Dude! No-op is awesome! It is an instruction that does nothing but consume time. In the dim dark ages you would use it to do microadjustments in timing in critical loops or more importantly as a filler in self-modifying code.
在针对特定处理器或架构进行优化时,它还可以使代码运行得更快:
处理器长期以来采用多个大致并行工作的管道,因此可以同时执行两条独立的指令。 在具有两个管道的简单处理器上,第一个管道可能支持所有指令,而第二个管道仅支持一个子集。 此外,当必须等待上一条尚未完成的指令的结果时,管道之间会出现一些停顿。
在这些情况下,专用的 nop 可能会强制下一条指令进入特定的管道(第一条或不是第一条),并改进后续指令的配对,从而降低 nop 的成本 超过摊销。
It may also make code run faster, when optimizing for specific processors or architectures:
Processors for a long time employ multiple pipelines that work roughly in parallel, so two independent instruction can be exceuted at the same time. On a simple processor with two pipelines, the first may support all instructions, whereas the second supports only a subset. Also, there are some stalls between the pipelines when one has to wait for the result of a previous instruction that isn't finished yet.
Under these circumstances, a dedicated nop may force the next instruction into a specific pipeline (the first, or not the first), and improve the pairing of following instructions so that the cost of the nop is more than amortized.
在我最近(四年)工作的一个处理器中,NOP 用于确保上一个操作在下一个操作开始之前完成。 例如:
加载值到寄存器(需要8个周期)
8号
添加 1 到寄存器
这确保了在添加操作之前寄存器具有正确的值。
另一个用途是填充执行单元,例如必须具有一定大小(32 字节)的中断向量,因为向量 0 的地址是 0,向量 1 0x20 等等,因此编译器将 NOP 放入其中,如果需要。
In one processor I worked for recently (for four years) NOP was used to make sure the previous operation finished before the next operation was started. For instance:
load value to register (takes 8 cycles)
nop 8
add 1 to register
This made sure register had the correct value before the add operation.
Another use was to fill in execution units, such as the interrupt vectors which had to be a certain size (32 bytes) because address for vector0 was, say 0, for vector 1 0x20 and so on, so the compiler put NOPs in there if needed.
他们可以在调试时使用它们来支持编辑并继续。 它为调试器提供了用新代码替换旧代码而无需更改偏移量等的空间。
They could be using them to support edit-and-continue while debugging. It provides the debugger with room to work to replace the old code with new without changing offsets, etc.
虽然晚了 50 年,但是嘿。
如果您手动输入汇编代码,则 Nop 非常有用。
如果您必须删除代码,您可以 nop 旧的操作码。
类似地,您可以通过覆盖某些操作码来插入新代码并跳转到其他地方。 您可以在此处放置覆盖的操作码,然后插入新代码。 准备好后,你跳回来。
有时您必须使用可用的工具。 在某些情况下,这只是一个非常基本的机器代码编辑器。
如今,有了编译器,这些技术就不再有意义了。
50 years too late but hey.
Nop's are useful if you are typing assembly code by hand.
If you had to remove code, you could nop the old opcodes.
similary, you could insert new code by overwriting some opcode and jump somewhere else. There you put the overwritten opcodes, and insert your new code. When ready you jump back.
Sometimes you had to use the tools which were available. In some cases this was just a very basic machinecode editor.
Nowadays with compilers the techniques make no sense whatsoever anymore.
它们的一个经典用途是让调试器始终能够将源代码行与 IL 指令关联起来。
One classic use for them is so that your debugger can always associate a source-code line with an IL instruction.
在软件破解场景中,解锁应用程序的经典方法是使用 NOP 修补检查密钥、注册或时间段等的行,这样它就不会执行任何操作,只是继续启动应用程序,就好像它已注册一样。
In the software cracking scene, a classic method to unlock an application would be to patch with a NOP the line that checks for the key or registration or time period or whatnot so it would do nothing and simply continue starting the application as if it is registered.
我还看到代码中的 NOP 会修改自身以混淆它作为占位符的作用(非常旧的复制保护)。
I've also seen NOPs in code that modifies itself to obfuscate what it does as a placeholder (veeery old copy protection).
正如 ddaa 所说,nops 让您考虑堆栈中的差异,以便当您覆盖返回地址时,它会跳转到 nop sled(连续很多 nop),然后正确命中可执行代码,而不是跳转到某些指令中不是开头的字节。
As ddaa said, nops let you account for variance in the stack, so that when you overwrite the return address it jumps to the nop sled (a lot of nops in a row) and then hits the executable code correctly, rather than jumping to some byte in the instruction that isn't the beginning.
有点非正统的用法是 NOP-Slides,用于缓冲区溢出漏洞利用。
A somewhat unorthodox use are NOP-Slides, used in buffer overflow exploits.
它们允许链接器用较短的指令(通常是长跳转)替换较长的指令(通常是长跳转)。 NOP 占用了额外的空间 - 代码无法移动,因为它会阻止其他跳转工作。 这发生在链接时,因此编译器无法知道长跳转还是短跳转是否合适。
至少,这是它们的传统用途之一。
They allow the linker to replace a longer instruction (typically long jump) with a shorter one (short jump). The NOP takes the extra space - the code could not be moved around as it would stop other jumps from working. This happens at link-time, so the compiler can't know whether a long or short jump would be appropriate.
At least, that's one of their traditional uses.
这不是您具体问题的答案,但在过去,您可以使用 NOP 来填充 分支延迟槽,如果你无法用其他有用的指令来填充它。
This is not an answer to your specific question, but back in the old days you could use a NOP to fill a branch delay slot, if you couldn't manage to fill it with an otherwise-useful instruction.
.NET 编译器是否对齐 MSIL 输出? 我想它可能对加速对 IL 的访问很有用...另外,我的理解是它被设计为可移植的,并且在其他一些硬件平台上需要对齐访问。
Do the .NET compilers align the MSIL output? I'd imagine it might be useful for speeding up access to the IL... Also, my understanding is that it's designed to be portable and aligned accesses are required on some other hardware platforms.
我学到的第一个汇编是SPARC,所以我熟悉分支延迟槽,如果你不能用其他指令填充它,通常是你要放在分支指令上方的指令或在循环中增加计数器,你可以使用一个NOP。
我对破解不熟悉,但我认为使用 NOP 覆盖堆栈是很常见的,因此您不必准确计算恶意函数的开始位置。
The first assembly I learned was SPARC so I'm familiar with the branch delay slot, if you can't fill it with another instruction, usually the instruction you were going to put above the branch instruction or increment a counter in loops, you use a NOP.
I'm not familiar with cracking, but I think is common to overwrite the stack using NOP so you have not to exactly calculate where your malicious function begins.
我使用 NOP 自动调整进入 ISR 后累积的延迟。 非常方便地准确确定时间。
I used NOPs to automagically adjust the latency accumulated after entering an ISR. Very handy to nail timing dead on.