函数的调用过程,栈帧的创建和销毁

来源:互联网 发布:c语言getch和getchar 编辑:程序博客网 时间:2024/06/05 05:25

我在通过调试时,看反汇编上的步骤来分析函数的调用过程,栈帧的创建和销毁。

原码如下:

#include <stdio.h>int Add(int x,int y){int sum = 0;sum = x+y;return (sum);}int main(){int a = 2;int b = 3;int ret = 0;ret = Add(a,b);return 0;}

按f10,并转到反汇编

转到反汇编时,看到各种没见过的标识符,一脸蒙蔽,所以我们先了解这些标识符都是干嘛的。

esp:esp寄存器里存储的是在调用函数之后,栈的栈顶。并且始终指向栈顶

ebp:ebp寄存器里存储的是是栈的栈底指针,通常叫栈基址,这个是一开始进行函数调用之前,由esp传递给ebp的。(在函数调用前你可以这么理解:esp存储的是栈顶地址,也是栈底地址。)

push:压入操作,把一个32位的操作数压入堆栈中,这个操作在32位机中会使得esp被减4(字节),esp通常是指向栈顶的,这里顶部是地址小的区域,那么,压入堆栈的数据越多,esp也就越来越小

mov:数据传送。第一个参数是目的操作数,第二个参数是源操作数,就是把源操作数拷贝到目的一份。

lea:取得第二个参数地址后放入到前面的寄存器(第一个参数)中。

rep stosrep指令的目的是重复其上面的指令.ECX的值是重复的次数.
STOS指令的作用是将eax中的值拷贝到ES:EDI指向的地址

———————————————————————————————————————————————————

这些简单的指令了解了以后我们来用图片和文字解释

1.

2.开始调用main函数;执行第一条指令

push       ebp      //压栈,放入一个ebp在栈顶,而esp始终指向栈顶,所以如图


3.执行第二条语句

mov      ebp,esp    //将esp赋予ebp ,ebp和esp暂时指向同一位置。


4.执行第三条语句

sub        esp,0E4h    //将esp减去0E4h,0E4h是16进制数,由于开辟的栈空间是由高到低,所以esp往上走0E4h.


5.执行push      ebx

  push esi

  push edi  //这里的 ebx,esi ,edi,为三个寄存器,分别压栈。此时为main开辟的空间不够了,就再往上加三块。



6.接下来执行

leaedi,[ebp-0E4h]     //看上图,将ebx的地址传到edi中

movecx,39h//将39h数值传给ecx寄存器中

moveax,0CCCCCCCCh//将0CCCCCCCCh传给eax寄存器中

rep stosdword ptr es:[edi]//从edi中的地址(相当于ebx)向下拷贝eax中内容,ecx次




到这里main 函数调用完成。

———————————————————————————————————————————————————

再看自己想写出的逻辑


1.当给a赋值2时,我们看一下内存变化


在ebp-4位置上赋值2。而此时a=2局部变量已经创建,局部变量在栈上创建,说明现在开辟的空间就在栈上,所以这块空间叫做栈帧。


依次给变量b,ret赋值3,0.

2.准备调用Add函数


将b变量的值赋给eax,并将eax压栈,将a变量值赋给ecx,并压栈,

call指令下一条指令的地址,并压栈(函数调用完用它返回)


此时按F11进入Add函数。

———————————————————————————————————————————————————


1.执行前10条指令(和开辟main函数空间类似)

push ebp//ebp压栈,此时ebp为main函数栈帧中的ebp

mov  ebp,esp  //将esp传给ebp

sub   esp,0cch//给Add函数预开辟一块空间

//...//初始化空间0ccccccccch


2.给变量sum初始化并传值。

mov dword ptr [epb-8],0 //给ebp-8处给sum初始化0,为什么是ebp-8不是ebp-4呢,这是因为VS2010在Debug模式下,int变量占用12个字节。可以这样认为,Debug模式下,在int变量的附近增加了8个字节,用于存储调试信息。当我们把模式设为Release,就会发现栈上连续定义的int变量,地址相差4个字节。

moveax,dword ptr [epb+8]//把eax+8处的值(看上图ebp下两格处a),传给eax

addeax,dword ptr [epb+0ch]//把eax加上 ebp+0ch处的值(看上图ebp下三格处b),传给eax

movdword ptr [epb-8],eax//把eax的值传给sum

给sum传值完成


此时 return (sum),并且

moveax,dword ptr [epb-8]//将sum的值存在寄存器eax中

接下来出栈。。不容易。。。微笑

———————————————————————————————————————————————————


前三行指令,分别将寄存器pop出栈,

movesp,ebp//将esp栈顶下移

popebp//将ebp出栈,此时下面还有一ebp,相当于下移。


ret//回到call指令的下一条指令。相当于将call指令pop出栈。


此时转跳到了esp指向处(call指令下条指令)

———————————————————————————————————————————————————


addesp,8//esp向下跳两格

movdword ptr [ebp-20h],eax//将eax,也就是之前sum的值5传给ebp-20h处,看之前的图,ret就是ebp-20处。所以就酱sum的值传给了ret

———————————————————————————————————————————————————

就算完成了吧。

小结:仔细思考,慢慢推,规律就出来了。最重要就是耐心。

3 0