x86汇编中NOP指令和align语句的目的


15

自从我上一次组装课以来已经有一年左右的时间了。在该课程中,我们将MASM与Irvine库一起使用,以使其易于编程。

在我们完成了大多数指令之后,他说NOP指令实际上什么也不做,不用担心使用它。无论如何,这大约是期中考试,他有一些示例代码无法正常运行,因此他告诉我们添加一条NOP指令,它可以正常工作。我问我下课后为什么做,实际上做了什么,他说他不知道。

有人知道吗


NOP不会执行任何操作,但是会消耗周期。如果没有我们只能猜测的代码,我认为您的问题无法回答。好吧,我的猜测将是NOP幻灯片 ...
yannis 2012年

11
NOP实际上在做某事。它增加指令指针。
EricSchaefer 2012年

Answers:


37

通常将时间NOP用于对齐指令地址。例如,在编写Shellcode来利用缓冲区溢出格式字符串漏洞时通常会遇到这种情况。

假设您相对向前跳转了100个字节,并对代码进行了一些修改。可能是您所做的修改弄乱了跳转目标的地址,因此您还必须更改上述相对跳转。在这里,您可以添加NOPs向前推送目标地址。如果NOP目标地址和跳转指令之间有多个s,则可以删除NOPs以向后拉目标地址。

如果您正在使用支持标签的汇编程序,这将不是问题。您可以简单地执行操作JXX someLabel(JXX是有条件的跳转),汇编程序将用该someLabel标签的地址替换。但是,如果只是简单地手工修改汇编的机器代码(实际的操作码)(有时在编写shellcode时会发生这种情况),则还必须手动更改跳转指令。您可以对其进行修改,或者通过使用来移动目标代码地址NOP

NOP指令的另一个用例是称为NOP底座。本质上,这个想法是创建足够多的指令数组,这些指令不会产生副作用(例如NOP或先递增然后递减寄存器),但增加指令指针。例如,当您想跳转到某个未知地址的代码段时,这很有用。诀窍是将所述NOP底座放在目标代码之前,然后跳到该底座的某个位置。所发生的是,希望从没有副作用的数组继续执行,并且遍历每条指令前进,直到命中所需的代码为止。此技术通常用于上述缓冲区溢出漏洞利用中,尤其是用于应对安全措施,例如ASLR

NOP指令的另一种特殊用途是在修改某个程序的代码时。例如,您可以用NOPs 替换条件跳转的一部分,从而规避条件。这是“ 破解 ”软件复制保护时经常使用的方法。最简单的方法是删除代码if(genuineCopy) ...行的汇编代码构造,并用NOPs和.. 替换指令。不进行任何检查,并且非正版复制作品!

请注意,从本质上讲,shellcode和cracking的例子都一样。修改现有代码,而不更新依赖于相对寻址的操作的相对地址。


2
这是一个很棒的答案,感谢您抽出宝贵的时间来解释这个问题!我终于明白了!
alvonellos 2012年

某些实时系统(会想到PLC)允许您在运行新程序时将新逻辑“修补”到现有程序中。这些系统在每条小逻辑之前都会留下NOP,因此您可以跳到要插入的新逻辑来覆盖NOP。在新逻辑的末尾,它将跳到您要替换的原始逻辑的末尾。新逻辑还将在前面带有NOP,因此您也可以替换新逻辑。
斯科特·惠特洛克'18

10

当没有其他指令可以重新排序放置在延迟槽中时,可以在延迟槽中使用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系统上,这可能就是答案。


4
请注意,该问题被标记为x86,并且x86没有延迟槽。也永远不会,因为这是一个巨大的变化。
MSalters 2012年

6

使用NOP的至少一个原因是对齐。x86处理器从很大的块中读取主存储器中的数据,并且要读取的块的开头始终对齐,因此,如果有一个代码块,它将被大量读取,则应对齐该块。这将导致很少的加速。


这并非完全需要对齐该块,而是您不需要获取前一个块的最后几个字节。因此跳转是可以的0x1002,因为在16B的对齐块中仍有14个字节的指令包含目标地址,但跳转到则不行0x099D
彼得·科德斯

3

NOP(通常在x86上不仅如此)的一个目的是引入时间延迟。例如,您要编程一个微控制器,该微控制器必须以1 s的延迟输出到某些LED。可以使用NOP(和分支)来实现此延迟。当然,您可以使用一些ADD或其他方式,但这会使代码更不可读。也许您需要所有寄存器。


1
通常对于较长的时间帧(例如1秒)使用计时器。NOPS用于时钟量级(纳秒和微秒)内的时期。
mattnz

在微控制器而不是现代的x86上有意义。大多数x86代码不会饱和现代超标量无序CPU的流水线宽度,因此在大多数代码的每条指令之间添加NOP只会产生很小的影响(我想“平均”代码的数量可能是5〜20%的加倍指令数目,与没有显示出放缓但也有少数紧密的循环出近2倍的增长放缓一些代码。)无论如何,令人生畏的老x86代码传统上使用loop延迟循环指令,而不是NOP指令。
彼得·科德斯

3

通常在80x86上,不需要NOP指令来保证程序正确性,尽管有时在某些机器上,策略性放置的NOP可能会使代码运行得更快。例如,在8086上,代码将以两个字节的块的形式获取,并且处理器具有内部“预取”缓冲区,该缓冲区可以容纳三个此类块。有些指令的执行速度比获取指令快,而其他指令则需要一段时间才能执行。在慢速指令期间,处理器将尝试填充预取缓冲区,因此,如果接下来的几条指令很快,则可以快速执行它们。如果慢指令之后的指令从偶数字边界开始,则将预取接下来的六个字节的指令;如果从奇数字节边界开始,则仅预取五个字节。

这样的内存对齐问题可能会影响程序速度,但通常不会影响正确性。另一方面,在那些较旧的处理器上存在一些与预取相关的问题,在这些问题上NOP可能会影响正确性。如果一条指令更改了已经预取的代码字节,则8086(我认为80286和80386)将执行该预取指令,即使该指令不再与内存中的内容匹配。在更改内存的指令与更改后的代码字节之间添加一两个NOP可能会阻止在写入代码字节之前提取该代码字节。顺便说一下,请注意,许多复制保护方案都利用了这种行为。但也请注意,不能保证此行为。不同的处理器版本可能会以不同方式处理预取,如果修改了从中读取预取字节的内存,则有些可能会使预取字节无效,并且中断通常会使预取缓冲区无效;中断返回时,将重新获取代码。


3

在其他答案中仍然没有描述x86的特定情况:中断处理。对于某些样式,禁用中断时可能会有代码段,因为主代码可以处理与中断处理程序共享的某些数据,但是允许在这些段之间进行中断是合理的。如果一个天真的写


    STI
    CLI

这不会处理未决的中断,因为引用Intel:

设置IF标志后,处理器将在执行下一条指令后开始响应外部可屏蔽中断。

因此,至少应将其重写为:


    STI
    NOP
    CLI

在第二个变体中,将仅在NOP和CLI之间处理所有待处理的中断。(当然,可以将STI指令加倍,可以有很多替代形式。但是显式的NOP更为明显,至少对我来说是如此。)


-2

NOP表示无操作

它通常用于插入或删除机器代码或延迟执行特定代码。

破解者和调试器还使用它们来设置断点。

因此,可能会执行类似:XCHG BX,BX的结果。

在我看来,似乎还有少量操作仍在进行中,因此导致错误。

如果您熟悉VB,我可以举个例子:

如果您在vb中建立登录系统,并一起加载3个页面-在3个不同的选项卡中的facebook,youtube和twitter。

并全部使用1个登录按钮。如果您的互联网连接速度慢,可能会出现错误。这意味着其中一个页面尚未加载。因此,我们放入Application.DoEvents来克服这一点。可以使用相同的方式组装NOP。

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.