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