原文地址:https://blog.csdn.net/qchengsj/article/details/37918083
栈的概念:
每个任务(进程)有一个栈(stack),在这个进程中每个函数被调用时分别从这个栈占用一段区域,称为帧(frame)。寄存器esp指向当前整个栈的栈顶,寄存器 ebp指向当前帧的帧底。这里务必要区分清楚,栈相对于整个系统而言,调用栈(call stack)相对于某个进程而言,帧(stack frame)则是相对于某个函数而言。具体来说,call stack就是指存放某个程序的正在运行的函数的信息的栈。(Call stack)由( stack frames)组成,每个 (stack frame)对应于一个未完成运行的函数。
作用和性质:
栈用于维护函数调用的上下文,离开了栈函数调用就没法实现。栈被定义为一个特殊的容器:可以将数据压入栈内(push,入栈),也可以将栈内的数据弹出(pop,出栈),但是数据的进出必须遵守一条规则:先进后出,即先入栈的数据后出栈(FIFO)。在很多操作系统中,栈总是向下增长的,这里的增长是指数据个数的增长,但是地址总是从高地址向低地址延伸的。压栈操作使栈顶的地址减小,而出栈会使栈顶的地址增大。
如图,一个栈的实例。栈低的地址是0xbfffffff,而栈顶(esp寄存器)地址是0xbffffff4。当压栈时地址直接减小,而esp减小的值也等效于在栈上开辟空间;当出栈时地址直接增加,而esp增加的值等效于栈上回收空间。
每个函数的每次调用,都有它自己独立的一个栈帧,这个栈帧维护着函数调用所需要的各种信息。
函数栈帧中一般包含以下几方面内容:
(1) 函数的返回地址和参数:保存当前函数调用前的“断点”信息,也就是函数调用前的指令位置,以便在函数返回时能够恢复到函数被调用前的代码区中继续执行指令。
(2) 临时变量:包括函数的非静态局部变量以及编译器自动生成的其他临时变量。栈帧要为这些变量开辟内存空间。
(3) 保存的上下文:包括在函数调用前后需要保持不变的寄存器。
(4) 栈帧状态值:保存前栈帧的顶部和底部的地址,用于在本栈被弹出后恢复出上一个栈帧。
注:函数栈帧的大小并不固定,一般与其对应函数的局部变量多少有关。函数运行过程中,其栈帧大小也是在不停变化的。
一个函数的栈帧用ebp和esp这两个寄存器划定范围。寄存器esp始终指向栈的顶部,也即指向当前函数栈帧的顶部。而ebp寄存器指向函数栈帧的一个固定位置,所以ebp寄存器又被称为帧指针。这两个寄存器的详细操作方式为:函数调用时上一级调用者的帧底被压入当前ebp内容所指的地址,也就是当前帧的帧底位置保存了上一级调用者的ebp指针值(帧底),而每个ebp的前一个单元存放的就是当前函数的返回地址(它是由调用者在call指令中入的栈)。这样就可以根据当前ebp的值回溯出整个任务的调用栈(调用过程)。
常见的函数的栈帧示例如图:
在参数之后的数据(包括参数)即是当前函数的栈帧,ebp固定在图中所示的位置,不随这个函数的执行而变化,相反的,esp始终指向栈顶,因此随着函数的执行,esp会不断变化。固定不变的ebp可以用来定位函数栈帧中的各个数据。所以函数的返回地址可以表示为ebp-4,再往前的压入栈中的参数的地址为ebp-8,以此类推。
函数调用时栈帧相对应的变化:
•push:把ebp压入栈中(称为old ebp)
•movebp,esp:ebp= esp (这时ebp指向栈顶,而此时栈顶就是old ebp)
•【可选】sub esp,xxx:在栈上分配XXX字节的临时空间
•【可选】push xxx:如果有必要,保存名为XXX寄存器
•【可选】pop xxx:如有必要,恢复保存过的寄存器
•movesp,ebp:恢复esp同时回收局部变量空间
•popebp:从栈中恢复保存的ebp的值
•ret:从栈取得返回地址,并跳转到该位置。
用一个完整的函数调用例子用图形解析栈帧的变化情况,具体函数如下:
void foo(int x, int y)
{
~~~~~
Return;
}
int main()
{
foo(1,3);
return0;
}
具体图形分析如下:
图<1>:函数main调用foo函数前,栈的情况是说main的栈帧
图<2>: 函数main开始调用foo函数,栈的情况先是把变量3和1入栈,即(push 3;push 1;)
图<3>:执行call指令,调用foo函数。具体包括将call指令的下一条指令地址压入栈中和跳转到foo函数体执行。同 时foo函数要建立自己的栈帧,要执行push ebp;把main函数的ebp压入栈中(old ebp)。(mov ebp,esp;)即是ebp = esp(把ebp指向栈顶,而此时栈顶就是old ebp)。
图<4>:寄存器ebp与esp之间的区域就是属于foo函数栈帧,此部分要为局部变量分配空间,保存寄存器。函数foo栈 帧的空间增大。
图<5>:函数foo执行完后要恢复esp并回收局部变量空间,即执行(mov esp,ebp;)还要执行(pop ebp;)从栈中恢复 保存的ebp的值。可以看到ebp已经回到main函数的栈帧底部。
图<6>:从栈中取得返回地址,并跳转到该位置继续执行。即执行ret;指令。
图<7>:表示main函数回到原始状态接着执行。