栈帧---函数调用原理

来源:互联网 发布:ubuntu支持中文吗 编辑:程序博客网 时间:2024/05/18 18:54

一、 什么是栈帧?

    什么是栈帧,首先引用百度百科的经典解释:“栈帧也叫过程活动记录,是编译器用来实现过程/函数调用的一种数据结构”。

    实际上,可以简单理解为:栈帧就是存储在用户栈上的(当然内核栈同样适用)每一次函数调用涉及的相关信息的记录单元。也许这样感觉更复杂了,好吧,让我们从栈开始来理解什么是栈帧...

二、栈帧

    栈帧表示程序的函数调用记录,而栈帧又是记录在栈上面,很明显栈上保持了N个栈帧的实体,(实际上我们这里说的栈帧是软件上的概念,据说有硬件概念,不是很了解),那就可以说栈帧将栈分割成了N个记录块,但是这些记录块大小不是固定的,因为栈帧不仅保存诸如:函数入参、出参、返回地址和上一个栈帧的栈底指针等信息,还保存了函数内部的自动变量(甚至可以是动态分配内存,alloca函数就可以实现,但在某些系统中不行),因此,不是所有的栈帧的大小都相同。

三、下面我通过一个简单的实例来分析函数调用原理------栈帧

首先应该明白,栈是从高地址向低地址延伸的。每个函数的每次调用,都有它自己独立的一个栈帧,这个栈帧中维持着所需要的各种信息。

寄存器ebp指向当前的栈帧的底部(高地址)

寄存器esp指向当前的栈帧的顶部(地址地)

PC指针:永远指向当前运行程序指令的下一条指令

下图为典型的存取器安排,观察栈在其中的位置


见上图,————黑色线指向的是调用者函数main()

               ————蓝色线指向的是被调用者函数fun()

               ————红色线指向的的是函数fun()返回时,恢复到调用函数fun()之前的状态

根据这简单的代码,然后在观察上面的栈帧图,我们来看函数调用过程的栈帧原理

#include<stdio.h>#include<windows.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 runing here!\n");system("pause");return 0;}

我们可以通过观察上面这个代码和栈帧图还有下面反汇编的代码发现

函数调用过程如下:

(1)先将调用者main()函数的堆栈的基址(ebp)入栈,以保存之前任务的信息。

(2)然后将调用者main()函数的栈顶指针(esp)的值赋给ebp,作为新的基址(即被调用者函数fun()的栈底)。

(3)然后在这个基址(fun()函数的栈底)上开辟(一般用sub命令)相应的空间用作被调用者fun()函数的栈空间。

(4)函数fun()返回后,从当前栈帧的ebp即恢复为调用者函数main()的栈顶(esp),使栈顶恢复函数fun()被调用钱的位置,然后调用者main()再从恢复后的栈顶弹出之前的ebp值(可以这么做是因为这个值在函数调用前前一步被压入栈”main():retaddr”)。这样,ebp和esp就都恢复了调用函数fun()之前的位置,也就是栈恢复函数fun()调用前的状态。



下面是一个反汇编的代码,我们知道任何函数都只有一份ebp和esp,可以通过内存窗口发现ebp和esp永远保存最新当前函数的数值,用函数时,原函数的ebp和esp要保存起来,以便返回时恢复。还可以发现a和b,是b先实例化,再是a实例化,形参实例化是从右往左。

call命令的作用:1.保存当前正在运行时指令的下一条指令的地址到栈结构中

                          2.跳转到指定的函数入口(jmp)



1 0