浅谈函数的调用及栈帧的创建与销毁

我们在写程序的过程中经常使用函数,但是函数具体是怎样被调用的,我们可能不是很清楚。接下来以简单的add函数来举例说明

#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<Windows.h>
int add(int x,int y)
{
	int z = 0;
	z = x + y;
	return z;
}
int main()
{
	int a = 10;
	int b = 20;
	int ret=add(a,b);
	printf("ret=%d",ret  );
	system("pause");
	return 0;
}

将以上内容反汇编,通过汇编指令来观察:
main函数部分反汇编:
int main()
{
001F1470  push        ebp  
001F1471  mov         ebp,esp  
001F1473  sub         esp,0E4h  
001F1479  push        ebx  
001F147A  push        esi  
001F147B  push        edi  
001F147C  lea         edi,[ebp-0E4h]  
001F1482  mov         ecx,39h  
001F1487  mov         eax,0CCCCCCCCh  
001F148C  rep stos    dword ptr es:[edi]  
 int a = 10;
001F148E  mov         dword ptr [a],0Ah  
 int b = 20;
001F1495  mov         dword ptr [b],14h  
 int ret=add(a,b);
001F149C  mov         eax,dword ptr [b]  
001F149F  push        eax  
 int ret=add(a,b);
001F14A0  mov         ecx,dword ptr [a]  
001F14A3  push        ecx  
001F14A4  call        _add (01F11E5h)  
001F14A9  add         esp,8  
001F14AC  mov         dword ptr [ret],eax 

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

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

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

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

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

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

图解:
调用main函数之前

开始调用main函数
push  exp   //压栈


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



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


  push  ebx

  push esi

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




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

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

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

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


给a,b赋值


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

准备调用add函数
	int ret=add(a,b);
001F149C  mov         eax,dword ptr [b]  
001F149F  push        eax  
	int ret=add(a,b);
001F14A0  mov         ecx,dword ptr [a]  
001F14A3  push        ecx  
001F14A4  call        _add (01F11E5h)  
001F14A9  add         esp,8  
001F14AC  mov         dword ptr [ret],eax  
	printf("ret=%d",ret  );
001F14AF  mov         esi,esp  
001F14B1  mov         eax,dword ptr [ret]  
001F14B4  push        eax  
001F14B5  push        1F5858h  
001F14BA  call        dword ptr ds:[1F9118h]  
001F14C0  add         esp,8  
001F14C3  cmp         esi,esp  
001F14C5  call        __RTC_CheckEsp (01F1140h)  

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

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


add函数部分反汇编
int add(int x,int y)
{
001F13D0  push        ebp  
001F13D1  mov         ebp,esp  
001F13D3  sub         esp,0CCh  
001F13D9  push        ebx  
001F13DA  push        esi  
001F13DB  push        edi  
001F13DC  lea         edi,[ebp-0CCh]  
001F13E2  mov         ecx,33h  
001F13E7  mov         eax,0CCCCCCCCh  
001F13EC  rep stos    dword ptr es:[edi]  
 int z = 0;
001F13EE  mov         dword ptr [z],0  
 z = x + y;
001F13F5  mov         eax,dword ptr [x]  
001F13F8  add         eax,dword ptr [y]  
001F13FB  mov         dword ptr [z],eax  
 return z;
001F13FE  mov         eax,dword ptr [z]  
}


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

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

mov  ebp,esp  //将esp传给ebp

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



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

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

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

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

mov dword ptr [epb-8],eax //把eax的值传给z

给z传值完成


此时 return z,并且

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

接下来出栈



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

mov esp,ebp//将esp栈顶下移

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


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


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


add esp,8//esp向下跳两格

mov dword ptr [ebp-20h],eax//将eax,也就是之前的值30传给ebp-20h处,看之前的图,ret就是ebp-20处。所以就将z的值传给了ret

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值