汇编中的跟踪程序。

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。