有很多的
... code ...
call sub_...
nop
... code ...
我正在处理的可执行转储中的模式。它们出现在子程序的中间,我相信不会用于对齐目的。我很好奇这个结构的起源。
该程序已打包,所以我不确定 call-nop 对是最初存在还是在解包后出现。
有很多的
... code ...
call sub_...
nop
... code ...
我正在处理的可执行转储中的模式。它们出现在子程序的中间,我相信不会用于对齐目的。我很好奇这个结构的起源。
该程序已打包,所以我不确定 call-nop 对是最初存在还是在解包后出现。
加壳程序可能已经用对另一个函数的直接(相对)调用替换了对导入函数的间接调用。这将使指令缩短一个字节,需要用一个 NOP 填充:
FF 15 ?? ?? ?? ?? call cs:[__imp_foo] ; RIP-relative offs32 indirect
E8 ?? ?? ?? ?? call foo ; RIP-relative offs32
NOP之后的第一条指令很可能是其他地方不同分支/跳转的目标。为了更好的 i-cache 利用率和更好的 BTB 预测,跳转到对齐的目标通常是可取的:
11.5 代码对齐
大多数微处理器以对齐的 16 字节或 32 字节块获取代码。
如果一个重要的子程序入口或跳转标签恰好位于 16 字节块的末尾,那么微处理器在获取该代码块时只会得到几个有用的代码字节。在解码标签之后的第一条指令之前,它可能还必须获取接下来的 16 个字节。这可以通过将重要的子程序条目和循环条目对齐 16 来避免。
按 8 对齐将确保在第一次取指令时可以加载至少 8 个字节的代码,如果指令很小,这可能就足够了。
如果子程序是关键热点的一部分,并且前面的代码不太可能在同一上下文中执行,我们可以通过缓存行大小(通常为 64 字节)来对齐子程序条目。
http://agner.org/optimize/optimizing_assembly.pdf#page=86
这将使 NOP 只是对齐以下指令的填充。如在别处指出,添加填充这个必须小心,因为添加填充一味很可能导致更糟糕的高速缓存器的使用,并因此降低性能。总是测量。
注意:在其他架构(即不是 x86/x86-64)中,有时需要调用后的 NOP;因为问题是关于 x86-64 这不应该适用。