x86_64 ABI:反汇编问题

时间:2015-06-25 08:13:47

标签: assembly x86-64 intel abi

我有以下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   

我无法理解某些事情。

一开始我们推送 rbp 并将 rsp 保存在 rbp 中。然后在顶部 然后堆叠(并且在%rbp )我们已经保存了rbp。然后 rbp 下面的所有内容都是可用空间。

但是我们将 edi esi 的传递参数放在 -0x14(%rbp)及以下。

但是为什么我们不能将它们放在rbp / rsp指向的位置之下? edi esi < / strong>是4个字节长,为什么不-0x8(%rbp)和-0xc(%rbp),那么?它与内存对齐有关吗?

为什么在返回 之前有一个奇怪的保存eax来堆叠并回读它?

1 个答案:

答案 0 :(得分:3)

首先,请注意您正在查看未经优化的编译器输出。编译器输出通常最终看起来有点愚蠢,优化已关闭,因为编译器会将C的每一行字面转换为等效的程序集,而不必费心去做最简单,最明显的优化。

对于您的第一个问题,答案是“因为这是您的编译器决定变量应该去的地方”。没有更好的答案 - 编译器的堆栈分配方案差异很大。例如,我机器上的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

加载ba + b
  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 result;

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

实际上回归:

  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都会很高兴地发现该函数从未被有效地使用过,只需将其彻底删除即可。)。