当前位置: 首页 > 知识库问答 >
问题:

x86_64ABI:拆卸问题

羊舌庆
2023-03-14

我得到了下面的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都是4字节长,那为什么不是-0x8(%rbp)和-0xc(%rbp)呢?和内存对齐有联系吗?

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

共有1个答案

卓宏达
2023-03-14

首先,请注意,您正在查看未优化的编译器输出。关闭优化后,编译器输出通常看起来有点愚蠢,因为编译器实际上将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

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

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

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

将两个参数存储到堆栈变量< code>a和< code>b中:

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

加载a和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=(a b的结果)

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

结果复制到返回值(返回结果;

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

实际返回:

  400545:       5d                      pop    %rbp
  400546:       c3                      retq   

因此,您可以看到eax的冗余保存和加载只是因为保存和加载对应于原始C文件的不同语句:保存来自结果=,加载来自返回结果;

相比之下,这里是Clang的优化输出(-O):

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

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

 类似资料:
  • AWS S3 Web控制台列出了已删除的存储桶。选择桶并尝试清空或删除它会导致“清空桶”或“删除桶”模式确认按钮静默失败。 如果您单击桶并尝试上传文件,您会收到一条错误消息,“找不到错误数据”。如果您尝试创建一个文件夹,您会得到,“错误无法创建带名称的文件夹”。"如果尝试更改和属性、权限或管理,也会收到错误消息。 如果您尝试使用相同的名称创建一个bucket(可能是为了覆盖旧的bucket),则会

  • 创建套接字并建立连接后,我使用BufferedReader和PrintWriter从套接字进行写/读操作。 当我用完插座后,我就把它关上。 这将导致发送一个TCP fin,并留下一个半开的传输控制协议。有什么方法可以检测客户端/服务器是否关闭了连接?

  • 我是Spring Boot的新手,我有一个Spring Boot应用程序似乎忽略了@PreDestroy注释-当我从命令行或通过Eclipse运行时,我从来没有看到@PreDestroy代码在应用程序关闭时运行(例如通过ctrl-c) 代码如下。。。 应用java: 消息处理器配置: 消息处理器:

  • 问题内容: 我有一个扩展LinkedList类的类。这是一段代码摘录: 预期会返回自动拆箱的int值。但是由于某种原因,编译器会抛出一个错误,指出类型不兼容,所需的类型为int,找到的类型为Integer。这可以在不同的类中很好地工作!是什么赋予了?:( 问题答案: 这是因为你有追求。 通常,您使用类型参数:,但是使用了。也就是说,您创建了一个类型参数(阴影)。 就目前而言,您的课程相当于 删除t

  • 问题内容: 如何在GDB中永久更改反汇编样式。我尝试过:在GDB中,但是稍后启动GDB时,它仍然具有att风格。 问题答案: gdb启动时会执行〜/ .gdbinit文件(如果存在);您应该能够添加行 对它。

  • 但是我只想执行一次,@在场景大纲之前,@在场景大纲之后!在cucumber-JVM中有什么方法可以实现这一点吗?