我正试图理解C程序在程序集级别上的样子,所以我运行gdb并在main和get_input上使用反汇编。该计划很短,所以我可以更好地遵循它。 有两行我不明白。在main()中首先是:
0x00000000004005a3 <+4>: mov $0x0,%eax
我们保存rbp的旧值并将rsp的当前值保存到rbp。该指示的目的是什么?
get_input()中的另一个是:
000000000400581 <+4>: sub $0x10,%rsp
在这里,我们首先将rbp的旧值保存到堆栈中。然后给rbp rsp的当前值。然后从rsp中减去16个字节。我知道这是空间分配但为什么它是16字节而不是8字节?我只将缓冲区设为8个字节,其他8个字节的目的是什么?
#include <stdio.h>
void get_input()
{
char buffer[8];
gets(buffer);
puts(buffer);
}
int main()
{
get_input();
return 0;
}
转储函数main的汇编代码:
0x000000000040059f <+0>: push %rbp
0x00000000004005a0 <+1>: mov %rsp,%rbp
0x00000000004005a3 <+4>: mov $0x0,%eax
0x00000000004005a8 <+9>: callq 0x40057d <get_input>
0x00000000004005ad <+14>: mov $0x0,%eax
0x00000000004005b2 <+19>: pop %rbp
0x00000000004005b3 <+20>: retq
End of assembler dump.
转储函数get_input的汇编代码:
0x000000000040057d <+0>: push %rbp
0x000000000040057e <+1>: mov %rsp,%rbp
0x0000000000400581 <+4>: sub $0x10,%rsp
0x0000000000400585 <+8>: lea -0x10(%rbp),%rax
0x0000000000400589 <+12>: mov %rax,%rdi
0x000000000040058c <+15>: callq 0x400480 <gets@plt>
0x0000000000400591 <+20>: lea -0x10(%rbp),%rax
0x0000000000400595 <+24>: mov %rax,%rdi
0x0000000000400598 <+27>: callq 0x400450 <puts@plt>
0x000000000040059d <+32>: leaveq
0x000000000040059e <+33>: retq
答案 0 :(得分:3)
main()
...
0x000000000040059f <+0>: push %rbp
将%RBP
的值推入堆栈。
0x00000000004005a0 <+1>: mov %rsp,%rbp
将%RSP
的值复制到%RBP
(创建一个新的堆栈帧)。
0x00000000004005a3 <+4>: mov $0x0,%eax
将即时值0x0
移至%EAX
。也就是说,它将%EAX
归零。当您处于64位模式时,这也会清除所有%RAX
。
0x00000000004005a8 <+9>: callq 0x40057d <get_input>
推送%RIP
的值(直接撤消),然后跳转到标签/功能get_input()
。
0x00000000004005ad <+14>: mov $0x0,%eax
根据AMD64 System V ABI,函数的返回值存储在%RAX
中(不考虑浮点和大结构)。它还说有两组寄存器:调用者保存和被调用者保存。当您调用函数时,您不能期望调用者保存的寄存器保持不变,必要时必须将它们自己保存在堆栈中。同样,被调用的函数必须保留被调用者保存的寄存器(如果它使用它们)。来电者保存的寄存器为%RAX
,%RDI
,%RSI
,%RDX
,%RCX
,%R8
,%R9
,{{ 1}}和%R10
。被调用者保存的寄存器为%R11
,%RBX
,%RSP
,%RBP
,%R12
,%R13
和%R14
。< / p>
现在,当%R15
显然执行main()
时,它必须在return 0
中返回0
,对吧?但是,应该考虑两件事。首先,在AMD64 System V ABI中,%RAX
。 sizeof(int) == 4
宽度为8个字节,但%RAX
宽度为4个字节,因此应使用%EAX
来处理%EAX
个广泛的内容,例如int
回报价值。其次,main()
是%EAX
的一部分,%RAX
被调用者保存,因此我们不能在调用后依赖其值。因此,我们执行%RAX
以将函数的返回值设置为零。
MOV $0x0, %EAX
恢复0x00000000004005b2 <+19>: pop %rbp
的来电者main()
,即销毁%RBP
的堆叠帧。
main()
从0x00000000004005b3 <+20>: retq
返回,返回值为main()
。
然后,我们有0
...
get_input()
将0x000000000040057d <+0>: push %rbp
的值推入堆栈。
%RBP
将0x000000000040057e <+1>: mov %rsp,%rbp
的值复制到%RSP
(创建一个新的堆栈帧)。
%RBP
从0x0000000000400581 <+4>: sub $0x10,%rsp
减去16(为当前帧保留16个字节的临时存储空间)。
%RSP
将有效地址0x0000000000400585 <+8>: lea -0x10(%rbp),%rax
加载到-0x10(%RBP)
。也就是说,它会将%RAX
的值从%RAX
中减去16 %RBP
。这意味着%RAX
现在指向本地临时存储的第一个字节。
0x0000000000400589 <+12>: mov %rax,%rdi
根据ABI,函数的第一个参数在%RDI
上给出,第二个参数在%RSI
上给出......在这种情况下,%RAX
的值给出为要被称为函数的第一个参数。
0x000000000040058c <+15>: callq 0x400480 <gets@plt>
调用函数gets()
。
0x0000000000400591 <+20>: lea -0x10(%rbp),%rax
与上述相同。
0x0000000000400595 <+24>: mov %rax,%rdi
传递%RAX
作为第一个参数。
0x0000000000400598 <+27>: callq 0x400450 <puts@plt>
调用函数puts()
。
0x000000000040059d <+32>: leaveq
等同于MOV %RBP, %RSP
然后POP %RBP
,即破坏堆栈帧。
0x000000000040059e <+33>: retq
从函数get_input()
返回,没有正确的返回值。
现在...
MOV $0x0, %EAX
该指示的目的是什么?
该指令的第二个实例非常重要,因为它设置了main()
的返回值。但是,第一个实际上是多余的。您可能已在编译器上禁用了优化。
然后从rsp中减去16个字节。我知道这是空间分配但为什么它是16字节而不是8字节?我只将缓冲区设为8个字节,其他8个字节的目的是什么?
ABI要求%RSP
在每次函数调用之前定位在16字节边界上。顺便说一句,你应该远离静态大小的缓冲区和gets()
。
答案 1 :(得分:1)
第一条指令mov $0x0, %eax
将零移动到EAX中以设置返回码。
第二条指令sub $0x10,%rsp
正在分配内存并对齐系统调用堆栈。调用标准需要16字节对齐,而不是8。