关于栈帧的背景知识
栈在地址空间中是向下生长,向下生长是指是从内存高地址到低地址的路径延伸,栈有栈顶和栈底,所以栈顶的地址要比栈底的地址低,对于x86体系的CPU而言,其中寄存器ebp可称为“帧指针”或“基底指针”,通常指向栈底,寄存器esp可称为“栈指针”,通常指向栈顶。
ebp在未受改变之前始终指向栈帧的开始,也就是栈底。所以ebp的用途是在C堆栈中寻址用的。esp是会随着数据的入栈和出栈移动的,也就是说,esp始终指向栈顶。
在CPU内部还存在一种寄存器——PC指针,它指向正在执行指令的下一条指令的地址。CPU工作时,通过调用的函数分别完成三个步骤取指令,分析指令,执行指令。PC指针又称程序计数器,指向正在执行指令的下一条指令。
call:1.保存当前执行指令的下一条指令的地址。2.跳转到目标函数。
return:当调用函数时,保留PC,ebp,esp指针,调用完毕后,恢复PC,ebp,esp指针。
下面我将举个例子来对栈帧进行讲解:
define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<stdlib.h>
int fun(int x, int y)
{
int c = 0xcccccccc;
return c;
}
int main()
{
int a = 0xaaaaaaaa;
int b = 0xbbbbbbbb;
int ret = fun(a, b);
printf("you should running here!\n");
system("pause");
return 0;
}
(1)在main函数中调用fun函数,先将main函数的堆栈的栈底指针ebp入栈,保存之前任务的信息。
(2)然后将main函数的栈顶指针esp的值赋给ebp,作为新的基址,也就是fun函数的栈底。
(3)在这个基址(被调用fun函数的栈底)上开辟相应的空间用作被调用fun函数的栈空间,开辟时一般用sub指令。
(4)fun函数返回后,从当前栈帧的ebp即恢复为main函数的栈顶(esp),使栈顶恢复fun函数被调用前的位置,然后main函数再从恢复后的栈顶可弹出之前的ebp值,可以这么做是因为在调用fun函数之前保存了main函数的ebp,使得ebp恢复为调用fun函数之前main函数的栈底。这样,ebp和esp就都恢复了调用fun函数前的位置,也就是栈恢复fun函数调用前的状态。
函数调用过程栈帧结构图:
注意:这里当fun函数栈帧结构形成时,就会覆盖main函数的栈帧,当调用完成后,main函数的栈帧结构又会恢复。