85

我使用 C 语言已经有一段时间了,最​​近才开始接触 ASM。当我编译一个程序时:

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

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

...
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
...

从我学到的 nops 什么都不做,因为在 ret 之后甚至不会被执行。

我的问题是:为什么要打扰?ELF(linux-x86) 不能与任何大小的 .text 部分(+main) 一起使用吗?

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

4

3 回答 3

94

首先,gcc并不总是这样做。填充由 控制,由和-falign-functions自动打开:-O2-O3

-falign-functions
-falign-functions=n

将函数的开头对齐到下一个大于 的 2 次幂n,最多跳到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 的“优化汇编语言中的子程序”。)

编辑:这是一个演示填充的示例:

// 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   
于 2011-10-27T07:00:54.643 回答
15

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

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

11.5 代码对齐

大多数微处理器以对齐的 16 字节或 32 字节块获取代码。如果一个重要的子程序入口或跳转标签碰巧在一个 16 字节块的末尾附近,那么微处理器在获取该代码块时只会得到几个有用的代码字节。在解码标签之后的第一条指令之前,它可能也必须获取接下来的 16 个字节。这可以通过将重要的子程序条目和循环条目对齐 16 来避免。

[...]

对齐子程序入口很简单,只需在子程序入口前放置所需数量的 NOP 以使地址可根据需要被 8、16、32 或 64 整除。

于 2011-10-27T06:53:51.787 回答
5

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

于 2011-10-27T06:44:52.920 回答