自从我上一次组装课以来已经有一年左右的时间了。在该课程中,我们将MASM与Irvine库一起使用,以使其易于编程。
在我们完成了大多数指令之后,他说NOP指令实际上什么也不做,不用担心使用它。无论如何,这大约是期中考试,他有一些示例代码无法正常运行,因此他告诉我们添加一条NOP指令,它可以正常工作。我问我下课后为什么做,实际上做了什么,他说他不知道。
有人知道吗
自从我上一次组装课以来已经有一年左右的时间了。在该课程中,我们将MASM与Irvine库一起使用,以使其易于编程。
在我们完成了大多数指令之后,他说NOP指令实际上什么也不做,不用担心使用它。无论如何,这大约是期中考试,他有一些示例代码无法正常运行,因此他告诉我们添加一条NOP指令,它可以正常工作。我问我下课后为什么做,实际上做了什么,他说他不知道。
有人知道吗
Answers:
通常将时间NOP
用于对齐指令地址。例如,在编写Shellcode来利用缓冲区溢出或格式字符串漏洞时通常会遇到这种情况。
假设您相对向前跳转了100个字节,并对代码进行了一些修改。可能是您所做的修改弄乱了跳转目标的地址,因此您还必须更改上述相对跳转。在这里,您可以添加NOP
s向前推送目标地址。如果NOP
目标地址和跳转指令之间有多个s,则可以删除NOP
s以向后拉目标地址。
如果您正在使用支持标签的汇编程序,这将不是问题。您可以简单地执行操作JXX someLabel
(JXX是有条件的跳转),汇编程序将用该someLabel
标签的地址替换。但是,如果只是简单地手工修改汇编的机器代码(实际的操作码)(有时在编写shellcode时会发生这种情况),则还必须手动更改跳转指令。您可以对其进行修改,或者通过使用来移动目标代码地址NOP
。
NOP
指令的另一个用例是称为NOP底座。本质上,这个想法是创建足够多的指令数组,这些指令不会产生副作用(例如NOP
或先递增然后递减寄存器),但增加指令指针。例如,当您想跳转到某个未知地址的代码段时,这很有用。诀窍是将所述NOP底座放在目标代码之前,然后跳到该底座的某个位置。所发生的是,希望从没有副作用的数组继续执行,并且遍历每条指令前进,直到命中所需的代码为止。此技术通常用于上述缓冲区溢出漏洞利用中,尤其是用于应对安全措施,例如ASLR。
该NOP
指令的另一种特殊用途是在修改某个程序的代码时。例如,您可以用NOP
s 替换条件跳转的一部分,从而规避条件。这是“ 破解 ”软件复制保护时经常使用的方法。最简单的方法是删除代码if(genuineCopy) ...
行的汇编代码构造,并用NOP
s和.. 替换指令。不进行任何检查,并且非正版复制作品!
请注意,从本质上讲,shellcode和cracking的例子都一样。修改现有代码,而不更新依赖于相对寻址的操作的相对地址。
当没有其他指令可以重新排序放置在延迟槽中时,可以在延迟槽中使用nop 。
lw v0,4(v1)
jr v0
在MIPS中,这将是一个错误,因为在jr读取寄存器v0时,寄存器v0尚未加载前一条指令中的值。
解决此问题的方法是:
lw v0,4(v1)
nop
jr v0
nop
这将在装载字和跳转寄存器指令之后填充一个nop的交易槽,以便在执行跳转寄存器命令之前完成装载字指令。
进一步阅读-一点关于SPARC 填充延迟槽的信息。从该文档中:
延迟槽可以放入什么?
- 无论是否分支,都应执行一些有用的指令。
- 某些指令仅在分支(或不分支)时才有用,但在另一种情况下执行则不会造成任何伤害。
- 当所有其他方法均失败时,执行NOP指令
不得将什么放入延迟槽?
- 设置分支决策所依赖的CC的任何内容。分支指令决定是否立即分支,但直到延迟指令之后才真正执行分支。(仅分支延迟,而不是决定。)
- 另一个分支指令。(如果您未执行此操作,将会发生什么情况!结果是不可预测的!)
- 一个“设置”指令。这实际上是两条指令,不是一条指令,只有一半在延迟槽中。(汇编程序会警告您。)
注意延迟插槽中放置的第三个选项。您看到的错误很可能是某人填补了一定不能放入延迟槽的东西之一。将nop放置在该位置即可修复该错误。
注意:重新阅读问题后,该问题适用于x86,它没有延迟插槽(分支只是使管道停滞了)。因此,这不是该错误的原因/解决方案。在RISC系统上,这可能就是答案。
NOP(通常在x86上不仅如此)的一个目的是引入时间延迟。例如,您要编程一个微控制器,该微控制器必须以1 s的延迟输出到某些LED。可以使用NOP(和分支)来实现此延迟。当然,您可以使用一些ADD或其他方式,但这会使代码更不可读。也许您需要所有寄存器。
loop
延迟循环指令,而不是NOP指令。
通常在80x86上,不需要NOP指令来保证程序正确性,尽管有时在某些机器上,策略性放置的NOP可能会使代码运行得更快。例如,在8086上,代码将以两个字节的块的形式获取,并且处理器具有内部“预取”缓冲区,该缓冲区可以容纳三个此类块。有些指令的执行速度比获取指令快,而其他指令则需要一段时间才能执行。在慢速指令期间,处理器将尝试填充预取缓冲区,因此,如果接下来的几条指令很快,则可以快速执行它们。如果慢指令之后的指令从偶数字边界开始,则将预取接下来的六个字节的指令;如果从奇数字节边界开始,则仅预取五个字节。
这样的内存对齐问题可能会影响程序速度,但通常不会影响正确性。另一方面,在那些较旧的处理器上存在一些与预取相关的问题,在这些问题上NOP可能会影响正确性。如果一条指令更改了已经预取的代码字节,则8086(我认为80286和80386)将执行该预取指令,即使该指令不再与内存中的内容匹配。在更改内存的指令与更改后的代码字节之间添加一两个NOP可能会阻止在写入代码字节之前提取该代码字节。顺便说一下,请注意,许多复制保护方案都利用了这种行为。但也请注意,不能保证此行为。不同的处理器版本可能会以不同方式处理预取,如果修改了从中读取预取字节的内存,则有些可能会使预取字节无效,并且中断通常会使预取缓冲区无效;中断返回时,将重新获取代码。