x86_64 ABI:反汇编问题

Posted

技术标签:

【中文标题】x86_64 ABI:反汇编问题【英文标题】:x86_64 ABI: disassembly issue 【发布时间】:2015-09-11 17:30:28 【问题描述】:

我有以下 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 下面的所有内容都是空闲空间。

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

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

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

【问题讨论】:

【参考方案1】:

首先,请注意您正在查看未优化的编译器输出。在关闭优化的情况下,编译器输出通常看起来有点愚蠢,因为编译器将 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 + 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 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 都会很高兴地检测到该函数从未被有效使用并直接将其删除。

【讨论】:

以上是关于x86_64 ABI:反汇编问题的主要内容,如果未能解决你的问题,请参考以下文章

如何反汇编 VC++ 应用程序?

hsdis反汇编java源码工具的使用方法

x86 反汇编,涉及模块化散列

反汇编工具使用

如何反汇编原始 16 位 x86 机器代码?

《Linux内核 核心知识全解析(完)》