栈在程序运行中有重要的地位,栈保存了一个函数调用所需要的维护信息,这通常被称为栈帧或活动记录。
在i386中,一个函数的活动记录用ebp
和esp
这两个寄存器划定活动范围:
-
esp
寄存器始终指向栈的顶部,也就指向了当前函数的活动记录的顶部。 -
ebp
指向了活动记录的栈的底部,ebp
又被称为帧指针。
随着函数的执行,esp
会不断的变化;固定不变的ebp
可以用来定位函数活动记录的各个数据。
栈帧一般包括如下内容:
- 函数的参数和返回地址。
- 临时变量,包括函数的局部变量和编译器自动生成的临时变量
- 保存的上下文,函数调用前后需要保持不变的寄存器
C 语言调用约定:
在C语言中,函数参数是从右到左的顺序入栈的,同时参数是在栈中传递的, EAX,ECX 和 EDX 寄存器是由调用者保存的,其余的寄存器由被调用者保存,函数的返回值存储在 EAX 寄存器中。由调用者清理栈空间。
那么下面一段函数调用是怎么完成的呢?
int subtract(int a, int b)
{
return a - b;
}
int main()
{
int a = 10;
int b = 20;
int sub = subtract(a, b);
printf("sub: %d\n", a - b);
}
结合函数调用约定那么汇编代码大概如下:
int subtract(int a, int b)
{
64a: 55 push %rbp
64b: 48 89 e5 mov %rsp,%rbp
64e: 89 7d fc mov %edi,-0x4(%rbp)
651: 89 75 f8 mov %esi,-0x8(%rbp)
return a - b;
654: 8b 45 fc mov -0x4(%rbp),%eax
657: 2b 45 f8 sub -0x8(%rbp),%eax
}
65a: 5d pop %rbp
65b: c3 retq
000000000000065c <main>:
int main()
{
65c: 55 push %rbp
65d: 48 89 e5 mov %rsp,%rbp
660: 48 83 ec 10 sub $0x10,%rsp
int a = 10;
664: c7 45 f4 0a 00 00 00 movl $0xa,-0xc(%rbp)
int b = 20;
66b: c7 45 f8 14 00 00 00 movl $0x14,-0x8(%rbp)
int sub = subtract(a, b);
672: 8b 55 f8 mov -0x8(%rbp),%edx
675: 8b 45 f4 mov -0xc(%rbp),%eax
678: 89 d6 mov %edx,%esi
67a: 89 c7 mov %eax,%edi
67c: e8 c9 ff ff ff callq 64a <_Z8subtractii>
681: 89 45 fc mov %eax,-0x4(%rbp)
printf("sub: %d\n", sub);
684: 8b 45 fc mov -0x4(%rbp),%eax
687: 89 c6 mov %eax,%esi
689: 48 8d 3d a4 00 00 00 lea 0xa4(%rip),%rdi # 734 <_IO_stdin_used+0x4>
690: b8 00 00 00 00 mov $0x0,%eax
695: e8 86 fe ff ff callq 520 <printf@plt>
}
-
push %rbp
mov %rsp, %rbp
-
sub $0x10,%rsp
movl $0xa,-0xc(%rbp)
movl $0x14,-0x8(%rbp)
-
mov -0x8(%rbp), %edx
mov -0xc(%rbp), %eax
mov %edx, %esi
mov %eax, %edi
准备参数。
-
callq 64a <_Z8subtractii>
CPU执行call
指令时,进行2步操作:
1、将当前的IP或CS和IP压入栈中
2、转移,这里跳转到subtract函数的机器指令 -
push %rbp
mov %rsp, %rbp
-
mov %edi, -0x4(%rbp)
mov %esi, -0x8(%rbp)
准备参数,参与计算
-
mov -0x4(%rbp),%eax
sub -0x8(%rbp),%eax
将 eax 寄存器的值和 -0x8(%rbp) 地址的值相减,将结果赋值给 eax
-
pop %rbp
恢复到main函数栈帧
-
retq
CPU执行ret
指令时,进行2步操作:
1、将当前的栈中的值赋值给IP寄存器,这里回到了main函数执行流。
2、出栈 -
mov %eax, -0x4(%rbp)
将 eax 的值赋值给 -0x4(%rbp)