【问题标题】:x86_64 ABI: disassembly issuex86_64 ABI:反汇编问题
【发布时间】: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 来堆叠并在返回之前读取它

【问题讨论】:

    标签: assembly x86-64 intel abi


    【解决方案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 都会很高兴地检测到该函数从未被有效使用并直接将其删除。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-08-07
      • 1970-01-01
      • 2010-12-16
      相关资源
      最近更新 更多