从汇编视角看函数调用

C语言函数如下

int bar(int c, int d)
{
    int e = c + d;
    return e;
}
int foo(int a, int b)
{
    return bar(a, b);
}
int main(void)
{
    foo(2, 5);
    return 0;
}

让我们从汇编的角度跟踪函数的执行,main对应的汇编函数如下:

Dump of assembler code for function main:
12      {
   0x0000000000400529 <+0>:     55      push   %rbp
   0x000000000040052a <+1>:     48 89 e5        mov    %rsp,%rbp

13          foo(2, 5);
=> 0x000000000040052d <+4>:     be 05 00 00 00  mov    $0x5,%esi
   0x0000000000400532 <+9>:     bf 02 00 00 00  mov    $0x2,%edi
   0x0000000000400537 <+14>:    e8 ce ff ff ff  callq  0x40050a 

14          return 0;
   0x000000000040053c <+19>:    b8 00 00 00 00  mov    $0x0,%eax

15      }
   0x0000000000400541 <+24>:    5d      pop    %rbp
   0x0000000000400542 <+25>:    c3      retq

End of assembler dump.

刚刚进入main函数,而且已经建立调用stack时,对应的寄存器状态为:

(gdb) info registers rbp rsp
rbp 0x7fffffffdc80 0x7fffffffdc80
rsp 0x7fffffffdc80 0x7fffffffdc80

执行三条指令(si 3),进入foo函数之后,汇编指令如下:

Dump of assembler code for function foo:
8       {
=> 0x000000000040050a <+0>:     55      push   %rbp
   0x000000000040050b <+1>:     48 89 e5        mov    %rsp,%rbp
   0x000000000040050e <+4>:     48 83 ec 08     sub    $0x8,%rsp
   0x0000000000400512 <+8>:     89 7d fc        mov    %edi,-0x4(%rbp)
   0x0000000000400515 <+11>:    89 75 f8        mov    %esi,-0x8(%rbp)

9           return bar(a, b);
   0x0000000000400518 <+14>:    8b 55 f8        mov    -0x8(%rbp),%edx
   0x000000000040051b <+17>:    8b 45 fc        mov    -0x4(%rbp),%eax
   0x000000000040051e <+20>:    89 d6   mov    %edx,%esi
   0x0000000000400520 <+22>:    89 c7   mov    %eax,%edi
   0x0000000000400522 <+24>:    e8 c9 ff ff ff  callq  0x4004f0 

10      }
   0x0000000000400527 <+29>:    c9      leaveq
   0x0000000000400528 <+30>:    c3      retq

End of assembler dump.

此时的函数堆栈如下:此时,尚未切换栈基地址,但是栈顶地址已经发生变化(因为call将main中的返回地址入栈)

(gdb) info registers rbp rsp
rbp 0x7fffffffdc80 0x7fffffffdc80
rsp 0x7fffffffdc78 0x7fffffffdc78

接下来两句,建立新函数的堆栈;50e~515是取得函数实参的过程,这三句汇编等价于push edi, push esi. 指令执行到0518行,寄存器的状态

(gdb) info registers rbp rsp
rbp 0x7fffffffdc70 0x7fffffffdc70
rsp 0x7fffffffdc68 0x7fffffffdc68

输入命令si 9, 进入到新的函数调用栈。

(gdb) disassemble /rm
Dump of assembler code for function bar:
3       {
=> 0x00000000004004f0 <+0>:     55      push   %rbp
   0x00000000004004f1 <+1>:     48 89 e5        mov    %rsp,%rbp
   0x00000000004004f4 <+4>:     89 7d ec        mov    %edi,-0x14(%rbp)
   0x00000000004004f7 <+7>:     89 75 e8        mov    %esi,-0x18(%rbp)

4           int e = c + d;
   0x00000000004004fa <+10>:    8b 45 e8        mov    -0x18(%rbp),%eax
   0x00000000004004fd <+13>:    8b 55 ec        mov    -0x14(%rbp),%edx
   0x0000000000400500 <+16>:    01 d0   add    %edx,%eax
   0x0000000000400502 <+18>:    89 45 fc        mov    %eax,-0x4(%rbp)

5           return e;
   0x0000000000400505 <+21>:    8b 45 fc        mov    -0x4(%rbp),%eax

6       }
   0x0000000000400508 <+24>:    5d      pop    %rbp
   0x0000000000400509 <+25>:    c3      retq

End of assembler dump.

此时的堆栈状态是:

(gdb) info registers rbp rsp
rbp 0x7fffffffdc70 0x7fffffffdc70
rsp 0x7fffffffdc60 0x7fffffffdc60

此时f0~f1仍然是建立新的函数调用stack。注意,接下来的几句,并没有进行函数stack的扩展,因为没有更深的函数调用。(另外,和对fun函数的调用不同,这里多了508对应的pop操作, 实际上,在fun函数中,是leave操作,这个操作等价于mov esp,ebp; pop ebp. 这里为什么没有将ebp的值传递给esp呢?因为ebp和esp是相同的)

Enter的作用相当push ebp和mov ebp,esp
这后面两句大家很熟悉吧?函数开始一般都是这两句
Leave的作用相当
mov esp,ebp和pop ebp

你可能感兴趣的:(程序原理与操作系统,汇编函数,程序原理,c语言)