汇编 - 为什么%rsp减少了太多,为什么参数存储在堆栈的顶部?

时间:2014-09-17 22:39:18

标签: c assembly

在这里组装新手......我写了以下简单的C程序:

void fun(int x, int* y)
{
  char arr[4];
  int* sp;
  sp = y;
}

int main()
{
  int i = 4;
  fun(i, &i);
  return 0;
}

我用gcc编译它并用-S运行objdump,但汇编代码输出让我感到困惑:

000000000040055d <fun>:
void fun(int x, int* y)
{
  40055d:       55                      push   %rbp
  40055e:       48 89 e5                mov    %rsp,%rbp
  400561:       48 83 ec 30             sub    $0x30,%rsp
  400565:       89 7d dc                mov    %edi,-0x24(%rbp)
  400568:       48 89 75 d0             mov    %rsi,-0x30(%rbp)
  40056c:       64 48 8b 04 25 28 00    mov    %fs:0x28,%rax
  400573:       00 00
  400575:       48 89 45 f8             mov    %rax,-0x8(%rbp)
  400579:       31 c0                   xor    %eax,%eax
  char arr[4];
  int* sp;
  sp = y;
  40057b:       48 8b 45 d0             mov    -0x30(%rbp),%rax
  40057f:       48 89 45 e8             mov    %rax,-0x18(%rbp)
}
  400583:       48 8b 45 f8             mov    -0x8(%rbp),%rax
  400587:       64 48 33 04 25 28 00    xor    %fs:0x28,%rax
  40058e:       00 00
  400590:       74 05                   je     400597 <fun+0x3a>
  400592:       e8 a9 fe ff ff          callq  400440 <__stack_chk_fail@plt>
  400597:       c9                      leaveq
  400598:       c3                      retq

0000000000400599 <main>:

int main()
{
  400599:       55                      push   %rbp
  40059a:       48 89 e5                mov    %rsp,%rbp
  40059d:       48 83 ec 10             sub    $0x10,%rsp
  int i = 4;
  4005a1:       c7 45 fc 04 00 00 00    movl   $0x4,-0x4(%rbp)
  fun(i, &i);
  4005a8:       8b 45 fc                mov    -0x4(%rbp),%eax
  4005ab:       48 8d 55 fc             lea    -0x4(%rbp),%rdx
  4005af:       48 89 d6                mov    %rdx,%rsi
  4005b2:       89 c7                   mov    %eax,%edi
  4005b4:       e8 a4 ff ff ff          callq  40055d <fun>
  return 0;
  4005b9:       b8 00 00 00 00          mov    $0x0,%eax
}
  4005be:       c9                      leaveq
  4005bf:       c3                      retq

首先,在行中:

  400561:       48 83 ec 30             sub    $0x30,%rsp

为什么在调用'fun'(48字节)时堆栈指针会减少这么多?我假设它与对齐问题有关,但我无法想象它为什么需要这么多空间(我只计算12个字节的局部变量(假设8个字节的指针))?

其次,我认为在x86_64中,函数的参数要么存储在特定的寄存器中,要么存在很多,只是在“上面”(向下增长的堆栈)基指针%rbp。就像http://en.wikipedia.org/wiki/Call_stack#Structure中的图片一样,除了'颠倒'之外。

但是界限:

  400565:       89 7d dc                mov    %edi,-0x24(%rbp)
  400568:       48 89 75 d0             mov    %rsi,-0x30(%rbp)

向我建议,它们是从堆栈的基础向下存储的(%rsi和%edi是main放置参数的地方,在调用'fun'之前,而0x30从%rbp向下是堆栈的正好位置指针指向...)。当我尝试使用它们时,比如将它们的值赋给局部变量,它会从堆栈顶部附近的位置抓取它们:

  sp = y;
  40057b:       48 8b 45 d0             mov    -0x30(%rbp),%rax
  40057f:       48 89 45 e8             mov    %rax,-0x18(%rbp)

......这里发生了什么?!根据我阅读的每个基本教程,我希望他们从存储的寄存器中获取参数,或者在基本指针上方,我认为它们应该是'应该是'。我在这里找到的与堆栈框架问题相关的每个答案和帖子都证实了我对堆栈框架“应该”的样子的理解,那么为什么我的汇编输出如此令人难以置信呢?

3 个答案:

答案 0 :(得分:4)

因为这些东西是真正发生的简单版本。这就像想知道为什么牛顿力学不能将行星的运动模拟到毫米。编译器需要堆栈空间来处理各种事情。例如,保存被调用者保存的寄存器。

此外,基本的事实是调试模式编译包含各种调试和检查机制。编译器输出检查代码是否正确的各种代码,例如调用__stack_chk_fail

只有两种方法可以理解给定编译器的输出。第一种是实现编译器,或者非常熟悉实现。第二是接受你所理解的是一种粗略的简化。选一个。

答案 1 :(得分:1)

因为你在没有优化的情况下进行编译,所以编译器会做很多额外的事情,可能会让事情更容易调试,这会占用大量的额外空间。

  • 它不会尝试压缩堆栈帧以重用任何内存,也不会删除任何未使用的东西。
  • 它将参数冗余地复制到堆栈框架中(这需要更多内存)
  • 它复制了一个金丝雀&#39;在堆栈上防止堆栈粉碎缓冲区溢出(即使它们不能在此代码中发生)。

尝试启用优化功能,您将看到更多真实代码。

答案 2 :(得分:0)

这是64位代码。堆栈空间的0x30对应堆栈上的6个插槽。你有似乎是:

  • 2个用于函数参数的插槽(也可以在寄存器中传递)
  • 2个局部变量的插槽
  • 1个用于保存AX寄存器的插槽
  • 1个插槽看起来像堆栈防护,可能与DEBUG模式有关。

最好的事情是尝试而不是提问。尝试以不同的模式(DEBUG,优化等)进行编译,并使用不同数量和类型的参数和变量。有时候询问其他人太容易了 - 通过自己的实验,你可以更好地学习。