尝试了解汇编代码中的调用过程

时间:2015-10-06 01:10:50

标签: c assembly x86-64

我在C中编写了一个非常简单的程序,并尝试理解函数调用过程。

#include "stdio.h"

void Oh(unsigned x) {
    printf("%u\n", x);
}

int main(int argc, char const *argv[])
{
    Oh(0x67611c8c);
    return 0;
}

它的汇编代码似乎是

0000000100000f20 <_Oh>:
   100000f20:   55                      push   %rbp
   100000f21:   48 89 e5                mov    %rsp,%rbp
   100000f24:   48 83 ec 10             sub    $0x10,%rsp
   100000f28:   48 8d 05 6b 00 00 00    lea    0x6b(%rip),%rax        # 100000f9a <_printf$stub+0x20>
   100000f2f:   89 7d fc                mov    %edi,-0x4(%rbp)
   100000f32:   8b 75 fc                mov    -0x4(%rbp),%esi
   100000f35:   48 89 c7                mov    %rax,%rdi
   100000f38:   b0 00                   mov    $0x0,%al
   100000f3a:   e8 3b 00 00 00          callq  100000f7a <_printf$stub>
   100000f3f:   89 45 f8                mov    %eax,-0x8(%rbp)
   100000f42:   48 83 c4 10             add    $0x10,%rsp
   100000f46:   5d                      pop    %rbp
   100000f47:   c3                      retq
   100000f48:   0f 1f 84 00 00 00 00    nopl   0x0(%rax,%rax,1)
   100000f4f:   00

0000000100000f50 <_main>:
   100000f50:   55                      push   %rbp
   100000f51:   48 89 e5                mov    %rsp,%rbp
   100000f54:   48 83 ec 10             sub    $0x10,%rsp
   100000f58:   b8 8c 1c 61 67          mov    $0x67611c8c,%eax
   100000f5d:   c7 45 fc 00 00 00 00    movl   $0x0,-0x4(%rbp)
   100000f64:   89 7d f8                mov    %edi,-0x8(%rbp)
   100000f67:   48 89 75 f0             mov    %rsi,-0x10(%rbp)
   100000f6b:   89 c7                   mov    %eax,%edi
   100000f6d:   e8 ae ff ff ff          callq  100000f20 <_Oh>
   100000f72:   31 c0                   xor    %eax,%eax
   100000f74:   48 83 c4 10             add    $0x10,%rsp
   100000f78:   5d                      pop    %rbp
   100000f79:   c3                      retq

好吧,我不太明白参数传递过程,因为只有一个参数传递给了Oh函数,我可以理解这个

100000f58:  b8 8c 1c 61 67          mov    $0x67611c8c,%eax

那么下面的代码是做什么的?为什么选择rbp?它不是在X86-64组装中被抛弃了吗?如果是x86样式的程序集,如何使用clang生成x86-64样式程序集?如果它是x86,那没关系,任何人都可以逐行解释下面的代码吗?

100000f5d:  c7 45 fc 00 00 00 00    movl   $0x0,-0x4(%rbp)
100000f64:  89 7d f8                mov    %edi,-0x8(%rbp)
100000f67:  48 89 75 f0             mov    %rsi,-0x10(%rbp)
100000f6b:  89 c7                   mov    %eax,%edi
100000f6d:  e8 ae ff ff ff          callq  100000f20 <_Oh>

2 个答案:

答案 0 :(得分:3)

如果您启用了优化,可能会获得更清晰的代码,或者您可能没有。但是,这就是它的作用。

%rbp寄存器被用作帧指针,即指向堆栈原始顶部的指针。它保存在堆栈中,最后存储和恢复。它不是在x86_64中删除,而是在那里添加; 32位的等价物是%ebp

保存此值后,程序通过从堆栈指针中减去,从堆栈中分配16个字节。

然后有一个非常低效的系列副本,它将Oh()的第一个参数设置为printf()的第二个参数,并将格式字符串的常量地址(相对于指令指针)设置为printf()的第一个论点。请记住,在此调用约定中,第一个参数在%rdi(或%edi用于32位操作数)中传递,第二个参数在%rsi中传递。这可以简化为两个指令。

调用printf()后,程序(不必要地)将返回值保存在堆栈上,恢复堆栈和帧指针,然后返回。

main()中,有类似的代码来设置堆栈帧,然后程序保存argcargv(不必要),然后它围绕常量参数移动到{{1通过Oh进入第一个参数。这可以优化为单个指令。然后它会调用%eax。返回时,它将其返回值设置为0,清理堆栈,然后返回。

您要询问的代码执行以下操作:在堆栈上存储常量32位值0,将32位值Oh()保存在堆栈中,保存64位指针{{1在堆栈上(argc的第一个和第二个参数),并设置它将要调用argv的函数的第一个参数,它先前已经加载了一个常量。对于这个程序来说,这一切都是不必要的,但是如果需要在调用之后使用main()%eax,那么这些寄存器将被破坏时是必要的。没有充分的理由它使用两个步骤来加载常量而不是一个。

答案 1 :(得分:2)

正如Jester提到的,你仍然有框架指针(以帮助调试),所以逐步通过main:

0000000100000f50 <_main>:

首先我们输入一个新的堆栈帧,我们必须保存基本指针并将堆栈移动到新的基础。此外,在x86_64中,堆栈帧必须与16字节边界对齐(因此将堆栈指针移动0x10)。

       100000f50:   push   %rbp        
       100000f51:   mov    %rsp,%rbp
       100000f54:   sub    $0x10,%rsp

如您所述,x86_64通过寄存器传递参数,因此将参数加载到寄存器中:

       100000f58:   mov    $0x67611c8c,%eax

???需要帮助

       100000f5d:   movl   $0x0,-0x4(%rbp)

来自here:“寄存器RBP,RBX和R12-R15是被调用者保存寄存器”,所以如果我们想保存其他寄存器,那么我们必须自己做....

       100000f64:   mov    %edi,-0x8(%rbp)
       100000f67:   mov    %rsi,-0x10(%rbp)

我们不确定为什么我们不只是在%edi中加载它需要开始的调用,但我们现在最好将它移到那里。

       100000f6b:   mov    %eax,%edi

调用该函数:

       100000f6d:   callq  100000f20 <_Oh>

这是返回值(在%eax中传递),xor是比load 0更小的指令,因此是cmmon优化:

       100000f72:   xor    %eax,%eax

清理我们之前添加的堆栈帧(当我们不使用它们时,不确定为什么我们将这些寄存器保存在其中)

       100000f74:   add    $0x10,%rsp
       100000f78:   pop    %rbp
       100000f79:   retq