x86_64 ABI:反汇编问题

x86_64 ABI: disassembly issue

我有以下 C 代码:

#include <stdio.h>

int function(int a, int b)
{
    int res = a + b;
    return res;
}

int main(){
    function(1,2);
    exit(0);
}

我用 gcc 4.8.2(在 Ubuntu 14 下)为 x86-64 编译它并产生这个代码:

000000000040052d <function>:
  40052d:       55                      push   %rbp
  40052e:       48 89 e5                mov    %rsp,%rbp
  400531:       89 7d ec                mov    %edi,-0x14(%rbp)
  400534:       89 75 e8                mov    %esi,-0x18(%rbp)
  400537:       8b 45 e8                mov    -0x18(%rbp),%eax
  40053a:       8b 55 ec                mov    -0x14(%rbp),%edx
  40053d:       01 d0                   add    %edx,%eax
  40053f:       89 45 fc                mov    %eax,-0x4(%rbp)
  400542:       8b 45 fc                mov    -0x4(%rbp),%eax
  400545:       5d                      pop    %rbp
  400546:       c3                      retq   

有些东西我看不懂。

一开始我们pushrbp,将rsp保存在rbp中。然后在上面 然后堆栈(在 %rbp)我们已经保存了 rbp。那么 rbp 以下的所有内容都是免费的 space.

但随后我们将来自 ediesi 的传递参数放置在 -0x14(%rbp) 及以下。

但为什么我们不能将它们直接放在 rbp/rsp 指向的下方? edi esi 都是 4 个字节长,为什么不是 -0x8(%rbp) 和 -0xc(%rbp) 呢?它与内存对齐有关吗?

为什么在return之前有一个奇怪的保存eax到堆栈并读回?

首先,请注意您正在查看未优化的编译器输出。在关闭优化的情况下,编译器输出通常最终看起来有点愚蠢,因为编译器将 C 的每一行都直接翻译成等效的 运行 汇编,甚至没有费心去做最简单、最明显的优化。

对于你的第一个问题,答案是"because that's where your compiler decided the variables should go"。没有更好的答案——编译器在堆栈分配方案上差异很大。例如,我机器上的 Clang 输出的是:

pushq   %rbp
movq    %rsp, %rbp
movl    %edi, -4(%rbp)
movl    %esi, -8(%rbp)
movl    -4(%rbp), %esi
addl    -8(%rbp), %esi
movl    %esi, -12(%rbp)
movl    -12(%rbp), %eax
popq    %rbp
retq

你可以清楚地看到a存储在-4,b存储在-8,result存储在-12。这比你的 GCC 给你的包装更紧,但这只是 GCC 的一个怪癖,仅此而已。

关于你的第二个问题,让我们看看指令如何映射到 C:


标准函数序言(设置栈帧):

  40052d:       55                      push   %rbp
  40052e:       48 89 e5                mov    %rsp,%rbp

将两个参数存储到堆栈变量 ab:

  400531:       89 7d ec                mov    %edi,-0x14(%rbp)
  400534:       89 75 e8                mov    %esi,-0x18(%rbp)

a + b

加载 ab
  400537:       8b 45 e8                mov    -0x18(%rbp),%eax
  40053a:       8b 55 ec                mov    -0x14(%rbp),%edx

实际上a + b

  40053d:       01 d0                   add    %edx,%eax

设置result = (result of a+b)

  40053f:       89 45 fc                mov    %eax,-0x4(%rbp)

result复制到return值(return result;)

  400542:       8b 45 fc                mov    -0x4(%rbp),%eax

其实return:

  400545:       5d                      pop    %rbp
  400546:       c3                      retq   

所以你可以看到eax的冗余保存和加载只是因为保存和加载对应于你原来的C文件的不同语句:保存来自result =和加载来自 return result;.

为了比较,这里是 Clang 的优化输出 (-O):

pushq   %rbp
movq    %rsp, %rbp
addl    %esi, %edi
movl    %edi, %eax
popq    %rbp
retq

更聪明:没有栈操作,整个函数体只有addlmovl两条指令。 (当然,如果您声明函数 static,那么 GCC 和 Clang 都会很高兴地检测到该函数从未被有效使用并直接将其删除。)