在程序集中跟踪程序。

时间:2016-04-02 14:11:20

标签: c assembly x86 calling-convention

我正试图理解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 

2 个答案:

答案 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中,%RAXsizeof(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。