首页 文章

为什么GCC使用NOP填充功能?

提问于
浏览
74

我已经和C一起工作了一段时间,最近才开始进入ASM . 当我编译一个程序时:

int main(void)
  {
  int a = 0;
  a += 1;
  return 0;
  }

objdump反汇编有代码,但在ret之后nops:

...
08048394 <main>:
 8048394:       55                      push   %ebp
 8048395:       89 e5                   mov    %esp,%ebp
 8048397:       83 ec 10                sub    $0x10,%esp
 804839a:       c7 45 fc 00 00 00 00    movl   $0x0,-0x4(%ebp)
 80483a1:       83 45 fc 01             addl   $0x1,-0x4(%ebp)
 80483a5:       b8 00 00 00 00          mov    $0x0,%eax
 80483aa:       c9                      leave  
 80483ab:       c3                      ret    
 80483ac:       90                      nop
 80483ad:       90                      nop
 80483ae:       90                      nop
 80483af:       90                      nop
...

从我学到的东西,什么都不做,因为ret之后甚至都不会被执行 .

我的问题是:为什么要这么麻烦? ELF(linux-x86)无法使用任何大小的.text段(主)吗?

我很感激任何帮助,只是想学习 .

3 回答

  • 13

    这样做是为了将下一个函数与8,16或32字节边界对齐 .

    来自A.Fog的“用汇编语言优化子程序”:

    11.5代码对齐大多数微处理器在对齐的16字节或32字节块中获取代码 . 如果一个重要的子例程入口或跳转标签碰巧接近一个16字节块的末尾,则在获取该代码块时,微处理器将只获得一些有用的代码字节 . 在它可以解码标签之后的第一条指令之前,它也可能需要获取接下来的16个字节 . 通过将重要的子例程条目和循环条目对齐16可以避免这种情况 . [...]对齐子例程条目就像在子例程条目之前根据需要放置尽可能多的NOP一样简单,使地址可被8,16,32整除或64,根据需要 .

  • 84

    首先, gcc 并不总是如此 . 填充由-falign-functions控制,由 -O2-O3 自动打开:

    -falign-functions -falign-functions = n将函数的开头与下一个大于n的2次幂对齐,最多跳过n个字节 . 例如,-falign-functions = 32将函数与下一个32字节边界对齐,但-falign-functions = 24只有在跳过23个字节或更少时才能与下一个32字节边界对齐 . -fno-align-functions和-falign-functions = 1是等效的,意味着函数不会对齐 . 当n是2的幂时,一些汇编器仅支持该标志;在这种情况下,它被四舍五入 . 如果未指定n或为零,则使用与机器相关的默认值 . 在-O2,-O3级别启用 .

    执行此操作可能有多种原因,但x86上的主要原因可能是:

    大多数处理器在对齐的16字节或32字节块中获取指令 . 将关键循环条目和子例程条目对齐16可能是有利的,以便最小化代码中16字节边界的数量 . 或者,确保在关键循环条目或子例程条目之后的前几条指令中没有16字节边界 .

    (引自Agner Fog的“用汇编语言优化子程序” . )

    edit: 这是一个演示填充的示例:

    // align.c
    int f(void) { return 0; }
    int g(void) { return 0; }
    

    使用gcc 4.4.5使用默认设置编译时,我得到:

    align.o:     file format elf64-x86-64
    
    Disassembly of section .text:
    
    0000000000000000 <f>:
       0:   55                      push   %rbp
       1:   48 89 e5                mov    %rsp,%rbp
       4:   b8 00 00 00 00          mov    $0x0,%eax
       9:   c9                      leaveq 
       a:   c3                      retq   
    
    000000000000000b <g>:
       b:   55                      push   %rbp
       c:   48 89 e5                mov    %rsp,%rbp
       f:   b8 00 00 00 00          mov    $0x0,%eax
      14:   c9                      leaveq 
      15:   c3                      retq
    

    指定 -falign-functions 给出:

    align.o:     file format elf64-x86-64
    
    Disassembly of section .text:
    
    0000000000000000 <f>:
       0:   55                      push   %rbp
       1:   48 89 e5                mov    %rsp,%rbp
       4:   b8 00 00 00 00          mov    $0x0,%eax
       9:   c9                      leaveq 
       a:   c3                      retq   
       b:   eb 03                   jmp    10 <g>
       d:   90                      nop
       e:   90                      nop
       f:   90                      nop
    
    0000000000000010 <g>:
      10:   55                      push   %rbp
      11:   48 89 e5                mov    %rsp,%rbp
      14:   b8 00 00 00 00          mov    $0x0,%eax
      19:   c9                      leaveq 
      1a:   c3                      retq
    
  • 6

    据我所知,指令在cpu中流水线化,不同的cpu块(加载器,解码器等)处理后续指令 . 当正在执行 RET 指令时,很少有下一条指令已加载到cpu管道中 . 这是一个猜测,但你可以开始挖掘这里,如果你发现(也许 NOP 的具体数量是安全的,请分享你的发现 .

相关问题