我们利用一个简单的c语言函数来认识函数栈帧的创建和销毁讲解。
- 函数栈帧的创建和销毁讲解
例:
a.寄存器分为eax,ebx,ecx,edx,以及ebp,和esp
其中ebp和esp这两个寄存器中存放的是地址
这两个地址是用来维护函数栈帧的。
b.每个函数调用,都会在栈区创建一个空间
有两个寄存器esp和ebp记住了两个地方的地址,在调用哪一个函数,ebp和esp就是在维护哪一个函数的函数的函数栈帧(无论是库函数还是自己创造的函数)
main函数也是被其他函数(__tmainCRtartup)调用的,然后__tmainCRtartup这个函数也是被mainCRtartup这个函数调用的。
c.调用总过程大概是这样的
调用总过程的具体是这样
先进行push(压栈)放入一个ebp在上面,然后esp是栈顶指针,esp向上走一步,然后因为上面是低地址,所以esp减小了。
就像下面这样
然后是mov操作,将esp的地址给ebp。
然后进行了sub操作,就是将前面的减去后面的,如下图:就是esp-0E4h,这样esp就上移到了某一个特殊的地方了吧
这样esp和ebp形成了新的范围,创建出来main函数的空间啦。
然后再进行三次push(压栈),分别压进去ebx,esi,edi这三个值,然后esp也先上移动了
然后就进行了lea操作了,就是将(ebp-0E4h)这个地址赋给edi,因为三次push之前的esp的地址就是(ebp-0E4h),
所以其实就是将三次push前的esp的地址赋给了edi了。
然后下面不是有两个mov操作嘛,第一个mov是将39h这么多个数据赋给了eca,然后第二个mov操作是将0ccccccch赋给了eax。然后rep stos这个创造是将edi以下ecx(39h)这么多个数据全都附上eax(0ccccccch)这个值。
这样就将main这个函数的空间创建好了。
再下来就是进入main括号里面的内容了,开始定义东西了。
将0A(16进制就是10)赋到ebp-8的地址
再将14h赋到ebp-14h这个地址,再将0赋给ebp-20h这个地址。
然后再下来,mov将ebp-14h的值赋给了eax,再将eax进行压栈,然后同样的道理,将ecx(ebp-)8压栈。
接下来就是执行call指令了,将call的下一条指令的目的地的地址进行压栈,并且在call的下一条指令做一个标志,后面刚好可以回来call指令的下一条指令。
然后就到了了add函数啦
然后又进行了push等系列的操作,开辟了add函数的空间。
而之前的ebp和esp也上移了
而其中x和与y是由a和b传值过去的(add(a,b)这个是由右向左传的,也就是b先传),第一个mov将ebp+8赋到了eax,然后进行add操作将ebp+0ch(这个是ebp+12)与eax相加
,然后将相加的结果赋给eax,然后再进行一个mov操作,将eax的值赋给ebp-8。
注:像这里形参不是在这个创建的,而是找到了传参的时候那个值的空间。这里的形参只是实参的一次拷贝,所以形参不会改变实参的值。
然后现在把z算出来了。
然后又进行进行了一次mov操作,将ebp-8赋给了eax。
注:注意到本例子是在add函数内部创建了变量z,那为啥这里临时变量z可以将值传过去main函数呢?(临时变量在函数使用完成后就会销毁。)
因为它现将ebp-8赋给eax,而eax是一个寄存器,寄存器是不会因为函数结束了,就迅速销毁的,所以可以将值传过去。
然后就要进行pop操作了,pop操作就是将edi等销毁(弹出)的,而esp向下移动了
然后mov,将ebp的地址赋给了esp,这样就将add的空间销毁了。
然后进行pop,将ebp这个销毁了,同时esp下移了,ebp也回到了main里面了,找到了main的ebp和esp。
进行ret操作后,就又跳了call指令的下一条指令的位置。
然后将进行add操作和mov操作,将esp+8来导致esp下移,这个add操作会导致esp+8和esp+12这两个形参销毁。
注:所以说形参什么时候销毁呢?
形参在这个add操作后销毁了,没了。
然后进行mov操作,将eax赋给ebp-20h,这里就实现了返回值带回来了。
同理,main函数的return也是这样返回了,就不多做解释。