为什么在 while 循环中有一个 nop

逆向工程 拆卸
2021-06-27 04:13:21

所以我写了以下C代码:

#include <stdio.h>


int main() {
    int i = 1;

    while(i) {
        printf("in loop\n");
        i++;

        if(i == 10) {
            break;
        }
    }

    return 0;
}

使用 gcc (Ubuntu/Linaro 4.7.2-2ubuntu1) 4.7.2 编译,它反汇编为:

   0x000000000040051c <+0>: push   %rbp
   0x000000000040051d <+1>: mov    %rsp,%rbp
   0x0000000000400520 <+4>: sub    $0x10,%rsp
   0x0000000000400524 <+8>: movl   $0x1,-0x4(%rbp)
   0x000000000040052b <+15>:    jmp    0x400541 <main+37>
   0x000000000040052d <+17>:    mov    $0x400604,%edi
   0x0000000000400532 <+22>:    callq  0x4003f0 <puts@plt>
   0x0000000000400537 <+27>:    addl   $0x1,-0x4(%rbp)
   0x000000000040053b <+31>:    cmpl   $0xa,-0x4(%rbp)
   0x000000000040053f <+35>:    je     0x400549 <main+45>
   0x0000000000400541 <+37>:    cmpl   $0x0,-0x4(%rbp)
   0x0000000000400545 <+41>:    jne    0x40052d <main+17>
   0x0000000000400547 <+43>:    jmp    0x40054a <main+46>
   0x0000000000400549 <+45>:    nop
   0x000000000040054a <+46>:    mov    $0x0,%eax
   0x000000000040054f <+51>:    leaveq 
   0x0000000000400550 <+52>:    retq  

为什么nop在+45上有一个为什么je在 +35上不直接跳到 +46?

3个回答

它可能是为了功能对齐。现在它返回 on 0x400550,它可以被 8 除。如果它返回 on 0x40054f,则它未对齐。不过也只是推测。

大多数微处理器以对齐的 16 字节或 32 字节块获取代码。如果一个重要的子程序入口或跳转标签恰好位于 16 字节块的末尾,那么微处理器在获取该代码块时只会得到几个有用的代码字节。在解码标签之后的第一条指令之前,它可能还必须获取接下来的 16 个字节。这可以通过将重要的子程序条目和循环条目按 16 对齐来避免。按 8 对齐将确保在第一次取指令时可以加载至少 8 字节的代码,如果指令很小,这可能就足够了。

通过 Optimizing subroutines in assembly language by Agner Fog。PDF

NOP 插入的另一个原因是管道调度。如果分支预测需要一个周期来确定它是否正确(如果不刷新管道),那么在将结果提交到寄存器之前您需要一个周期延迟。

关于跳转等于 NOP 的具体示例,在我看来,处理器需要一个周期来确定它是否得到正确答案并根据需要调整管道。

深入研究代码并了解正在发生的事情,做得很好。:)