汇编中的跟踪程序。
Tracing program in assembly.
我试图了解 C 程序在汇编级别的样子,所以我 运行 gdb 并在 main 和 get_input 上使用反汇编。该程序很短,因此我可以更好地遵循它。
有两行我不明白。 main() 中的第一个是:
0x00000000004005a3 <+4>: mov [=14=]x0,%eax
我们保存 rbp 的旧值,将 rsp 的当前值保存到 rbp。该指令的目的是什么?
get_input()中的另一个是:
000000000400581 <+4>: sub [=10=]x10,%rsp
这里我们也从保存 rbp 的旧值开始,将其压入堆栈。然后将 rsp 的当前值赋予 rbp。然后从 rsp 中减去 16 个字节。我知道这是 space 分配的,但为什么它是 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 [=12=]x0,%eax
0x00000000004005a8 <+9>: callq 0x40057d <get_input>
0x00000000004005ad <+14>: mov [=12=]x0,%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 [=13=]x10,%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
对于main()
...
0x000000000040059f <+0>: push %rbp
将 %RBP
的值压入堆栈。
0x00000000004005a0 <+1>: mov %rsp,%rbp
将 %RSP
的值复制到 %RBP
(创建一个新的堆栈框架)。
0x00000000004005a3 <+4>: mov [=12=]x0,%eax
将立即数0x0
移动到%EAX
。也就是说,它将 %EAX
归零。当您处于 64 位模式时,这也会清除所有 %RAX
.
0x00000000004005a8 <+9>: callq 0x40057d <get_input>
压入%RIP
的值(可直接撤消),然后跳转到label/function get_input()
.
0x00000000004005ad <+14>: mov [=14=]x0,%eax
根据AMD64 System V ABI,函数的return值存储在%RAX
中(不考虑浮点数和大型结构)。它还说有两组寄存器:调用者保存和被调用者保存。当你调用一个函数时,你不能期望调用者保存的寄存器保持不变,如果有必要你必须自己将它们保存在堆栈中。同样,被调用的函数必须保留被调用者保存的寄存器(如果它使用它们)。调用者保存的寄存器是 %RAX
、%RDI
、%RSI
、%RDX
、%RCX
、%R8
、%R9
、%R10
,和 %R11
。被调用者保存的寄存器是 %RBX
、%RSP
、%RBP
、%R12
、%R13
、%R14
和 %R15
。 =100=]
现在,由于 main()
显然执行 return 0
,它必须 return %RAX
中的 0
,对吗?但是,应该考虑两件事。首先,在 AMD64 System V ABI 中,sizeof(int) == 4
。 %RAX
是 8 个字节宽,但是 %EAX
是 4 个字节宽,所以 %EAX
应该用于操作 int
-wide 的东西,比如 main()
's return 值。其次,%EAX
是 %RAX
的一部分,而 %RAX
是调用者保存的,因此我们不能在调用后依赖它的值。因此,我们执行 MOV [=68=]x0, %EAX
以将函数的 return 值设置为零。
0x00000000004005b2 <+19>: pop %rbp
恢复main()
的调用者%RBP
,即销毁main()
的栈帧
0x00000000004005b3 <+20>: retq
Return 来自 main()
,return 值为 0
。
然后,我们有 get_input()
...
0x000000000040057d <+0>: push %rbp
将 %RBP
的值压入堆栈。
0x000000000040057e <+1>: mov %rsp,%rbp
将 %RSP
的值复制到 %RBP
(创建一个新的堆栈框架)。
0x0000000000400581 <+4>: sub [=19=]x10,%rsp
%RSP
减16(为当前帧预留16字节的暂存空间)
0x0000000000400585 <+8>: lea -0x10(%rbp),%rax
将有效地址-0x10(%RBP)
载入%RAX
。也就是说,它将 %RBP
的值减去 16 的结果加载到 %RAX
中。这意味着 %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
Return 函数 get_input()
没有正确的 return 值。
现在...
MOV [=68=]x0, %EAX
What is the purpose of that instruction?
该指令的第二个实例非常重要,因为它设置了 main()
的 return 值。然而,第一个实际上是多余的。您可能在编译器上禁用了优化。
Then 16 bytes are subtracted from rsp. I understand this is space allocated but why is it 16 bytes and not 8 bytes? I made the buffer 8 bytes only, what are the purpose of the other 8 bytes?
ABI 要求 %RSP
应在每个函数调用之前位于 16 字节边界上。顺便说一句,你应该远离静态大小的缓冲区和 gets()
.
第一条指令 mov [=10=]x0, %eax
将零移入 EAX 以设置 return 代码。
第二条指令,sub [=11=]x10,%rsp
正在为系统调用分配内存和对齐堆栈。调用标准要求 16 字节对齐,而不是 8。
我试图了解 C 程序在汇编级别的样子,所以我 运行 gdb 并在 main 和 get_input 上使用反汇编。该程序很短,因此我可以更好地遵循它。 有两行我不明白。 main() 中的第一个是:
0x00000000004005a3 <+4>: mov [=14=]x0,%eax
我们保存 rbp 的旧值,将 rsp 的当前值保存到 rbp。该指令的目的是什么?
get_input()中的另一个是:
000000000400581 <+4>: sub [=10=]x10,%rsp
这里我们也从保存 rbp 的旧值开始,将其压入堆栈。然后将 rsp 的当前值赋予 rbp。然后从 rsp 中减去 16 个字节。我知道这是 space 分配的,但为什么它是 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 [=12=]x0,%eax
0x00000000004005a8 <+9>: callq 0x40057d <get_input>
0x00000000004005ad <+14>: mov [=12=]x0,%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 [=13=]x10,%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
对于main()
...
0x000000000040059f <+0>: push %rbp
将 %RBP
的值压入堆栈。
0x00000000004005a0 <+1>: mov %rsp,%rbp
将 %RSP
的值复制到 %RBP
(创建一个新的堆栈框架)。
0x00000000004005a3 <+4>: mov [=12=]x0,%eax
将立即数0x0
移动到%EAX
。也就是说,它将 %EAX
归零。当您处于 64 位模式时,这也会清除所有 %RAX
.
0x00000000004005a8 <+9>: callq 0x40057d <get_input>
压入%RIP
的值(可直接撤消),然后跳转到label/function get_input()
.
0x00000000004005ad <+14>: mov [=14=]x0,%eax
根据AMD64 System V ABI,函数的return值存储在%RAX
中(不考虑浮点数和大型结构)。它还说有两组寄存器:调用者保存和被调用者保存。当你调用一个函数时,你不能期望调用者保存的寄存器保持不变,如果有必要你必须自己将它们保存在堆栈中。同样,被调用的函数必须保留被调用者保存的寄存器(如果它使用它们)。调用者保存的寄存器是 %RAX
、%RDI
、%RSI
、%RDX
、%RCX
、%R8
、%R9
、%R10
,和 %R11
。被调用者保存的寄存器是 %RBX
、%RSP
、%RBP
、%R12
、%R13
、%R14
和 %R15
。 =100=]
现在,由于 main()
显然执行 return 0
,它必须 return %RAX
中的 0
,对吗?但是,应该考虑两件事。首先,在 AMD64 System V ABI 中,sizeof(int) == 4
。 %RAX
是 8 个字节宽,但是 %EAX
是 4 个字节宽,所以 %EAX
应该用于操作 int
-wide 的东西,比如 main()
's return 值。其次,%EAX
是 %RAX
的一部分,而 %RAX
是调用者保存的,因此我们不能在调用后依赖它的值。因此,我们执行 MOV [=68=]x0, %EAX
以将函数的 return 值设置为零。
0x00000000004005b2 <+19>: pop %rbp
恢复main()
的调用者%RBP
,即销毁main()
的栈帧
0x00000000004005b3 <+20>: retq
Return 来自 main()
,return 值为 0
。
然后,我们有 get_input()
...
0x000000000040057d <+0>: push %rbp
将 %RBP
的值压入堆栈。
0x000000000040057e <+1>: mov %rsp,%rbp
将 %RSP
的值复制到 %RBP
(创建一个新的堆栈框架)。
0x0000000000400581 <+4>: sub [=19=]x10,%rsp
%RSP
减16(为当前帧预留16字节的暂存空间)
0x0000000000400585 <+8>: lea -0x10(%rbp),%rax
将有效地址-0x10(%RBP)
载入%RAX
。也就是说,它将 %RBP
的值减去 16 的结果加载到 %RAX
中。这意味着 %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
Return 函数 get_input()
没有正确的 return 值。
现在...
MOV [=68=]x0, %EAX
What is the purpose of that instruction?
该指令的第二个实例非常重要,因为它设置了 main()
的 return 值。然而,第一个实际上是多余的。您可能在编译器上禁用了优化。
Then 16 bytes are subtracted from rsp. I understand this is space allocated but why is it 16 bytes and not 8 bytes? I made the buffer 8 bytes only, what are the purpose of the other 8 bytes?
ABI 要求 %RSP
应在每个函数调用之前位于 16 字节边界上。顺便说一句,你应该远离静态大小的缓冲区和 gets()
.
第一条指令 mov [=10=]x0, %eax
将零移入 EAX 以设置 return 代码。
第二条指令,sub [=11=]x10,%rsp
正在为系统调用分配内存和对齐堆栈。调用标准要求 16 字节对齐,而不是 8。