在这里组装新手......我写了以下简单的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)
......这里发生了什么?!根据我阅读的每个基本教程,我希望他们从存储的寄存器中获取参数,或者在基本指针上方,我认为它们应该是'应该是'。我在这里找到的与堆栈框架问题相关的每个答案和帖子都证实了我对堆栈框架“应该”的样子的理解,那么为什么我的汇编输出如此令人难以置信呢?
答案 0 :(得分:4)
因为这些东西是真正发生的简单版本。这就像想知道为什么牛顿力学不能将行星的运动模拟到毫米。编译器需要堆栈空间来处理各种事情。例如,保存被调用者保存的寄存器。
此外,基本的事实是调试模式编译包含各种调试和检查机制。编译器输出检查代码是否正确的各种代码,例如调用__stack_chk_fail
。
只有两种方法可以理解给定编译器的输出。第一种是实现编译器,或者非常熟悉实现。第二是接受你所理解的是一种粗略的简化。选一个。
答案 1 :(得分:1)
因为你在没有优化的情况下进行编译,所以编译器会做很多额外的事情,可能会让事情更容易调试,这会占用大量的额外空间。
尝试启用优化功能,您将看到更多真实代码。
答案 2 :(得分:0)
这是64位代码。堆栈空间的0x30对应堆栈上的6个插槽。你有似乎是:
最好的事情是尝试而不是提问。尝试以不同的模式(DEBUG,优化等)进行编译,并使用不同数量和类型的参数和变量。有时候询问其他人太容易了 - 通过自己的实验,你可以更好地学习。