vlambda博客
学习文章列表

C语言函数调用及栈帧结构

技术干货第一时间送达!

http://blog.csdn.net/qq_29403077/article/details/53205010

往期回顾




地址空间与物理内存

C语言函数调用及栈帧结构

二、栈帧的建立

首先要明白几个地方:每一个函数都有自己的栈帧空间,并且独占自己的栈帧空间, 当前正在运行的函数的栈帧总是在栈顶。Win32系统提供两个特殊的寄存器用于标识位于系统栈顶端的栈帧。

  • ESP:栈指针寄存器(extended stack pointer),其内存放着一个指针,该指针永远指向系统栈最上面一个栈帧的栈顶,即栈顶寄存器。

  • EBP:基址指针寄存器(extended base pointer),即栈底寄存器。

  • 栈帧状态值:保存前栈帧的顶部和底部(实际上只保存前栈帧的底部,前栈帧的顶部可以通过栈帧平衡计算得到),用于在本栈被弹出后恢复出上一个栈帧。

「注:函数栈帧的大小并不固定,一般与其对应函数的局部变量多少有关。函数运行过程中,其栈帧大小也是在不停变化的。除了与栈相关的寄存器外,我们还需要记住另一个至关重要的寄存器。」

int fun(int a,int b)

    int sum=0;
    sum=a+b;
    return sum;
}
int main()
{
   fun(3,4);
   printf("haha");
   return 0;
}

这里main是调用者(caller);fun是被调用者(Callee)在函数调用前,main正在用ESP和EBP寄存器指示它自己的栈帧。main把EAX,ECX和EDX压栈。这是一个可选的步骤,只在这三个寄存器内容需要保留的时候执行此步骤。接着,main把传递给fun的参数一一进栈,最后的参数最先进栈,这里也就解释了函数在压栈过程中是从右往左,即先压4,再压3。

C语言函数调用及栈帧结构

(1)这里首先main函数建立自己的栈帧结构;main()函数是由__tCRTStartup()函数调用的,所以mainCRTStratup()函数调用__tmainCRTStra()函数的时候就会从栈上为__tmainCRTStra()分配类似图中这么一块空间,因为我们现在要调用main()函数了,所以当然要先把__tmainCRTStartup()函数的运行状态保存下来,这样main()函数才能返回的时候才能找得到!。然后继续执行下一条语句:mov ebp,esp即把esp的值赋给ebp,这样,ebp也就指向了现在esp的位置 然后sub esp 0C0h 这样就为main函数开辟了一段空间然后将ebx、esi、edi寄存器压栈就形成如图所示:

C语言函数调用及栈帧结构
C语言函数调用及栈帧结构
C语言函数调用及栈帧结构

当函数fun,也就是被调用者取得程序的控制权,它必须做3件事:建立它自己的栈帧,为局部变量分配空间,最后,如果需要,保存寄存器EBX,ESI和EDI的值。首先fun必须建立它自己的栈帧。EBP寄存器现在正指向main的栈帧中的某个位置,这个值必须被保留,因此,EBP进栈。然后ESP的内容赋值给了 EBP。这使得函数的参数可以通过对EBP附加一个偏移量得到,而栈寄存器ESP便可以空出来做其他事情。如此一来,几乎所有的c函数都由如下两个指令开 始:

push ebp

mov ebp, esp

下一步,fun必须为它的局部变量分配空间,同时,也必须为它可能用到的一些临时变量分配 空间。比如,foo中的一些C语句可能包括复杂的表达式,其子表达式的中间值就必须得有地方存放。这些存放中间值的地方同城被称为临时的,因为他们可以为 下一个复杂表达式所复用 现在,局部变量和临时存储都可以通过基准指针EBP加偏移量找到了。最后,如果fun用到EBX,ESIEDI寄存器,则它必须在栈里保存它们。这里写图片描述

C语言函数调用及栈帧结构
C语言函数调用及栈帧结构

fun的函数体现在可以执行了。这其中也许有进栈、出栈的动作,栈指针ESP也会上下移动,但EBP是保持不变的。这意味着我们可以一直用[EBP+…]找到第一个参数,而不管在函数中有多少进出栈的动作。函数fun的执行也许还会调用别的函数,甚至递归地调用foo本身。然而,只要EBP寄存器在这些子调用返回时被恢复,就可以继续用EBP加上偏移量的方式访问实际参数,局部变量和临时存储。紧接着当被调用者执行完毕时将消除栈帧结构,调用pop指令。

C语言函数调用及栈帧结构

在把程序控制权返还给调用者前,被调用者foo必须先把返回值保存在EAX寄存器中。其次,foo必须恢复EBX,ESI和EDI寄存器的值。进栈和出栈操作的次数必须保持平衡。

C语言函数调用及栈帧结构

在程序控制权返回到调用者main)后,这时,传递给fun的参数通常已经不需要了。我们可以把参数一起弹出栈,这可以通过把栈指针实现:add esp, 8此时fun函数调用结束栈帧结构恢复至图一。如果在函数调用前,EAX,ECXEDX寄存器的值被保存在栈中,调用者main函数现在可以把它们弹出。这个动作之后,栈顶就回到了我们开始整个函数调用过程前的位置。这样整个函数的调用就结束了



9.25




C语言函数调用及栈帧结构

 
   
   
 
C语言函数调用及栈帧结构
点分享
点点赞
点在看