函数栈帧的创建和销毁

本节我们将以两数相加的函数讲解函数栈帧的创建与销毁

#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>

int Add(int x, int y)
{
	int z = 0;
	z = x + y;
	return z;
}

int main()
{
	int a = 10;
	int b = 20;
	int c = 0;
	c = Add(a, b);

	printf("%d\n", c);
	return 0;
}

在介绍之前,我们先介绍关于寄存器的知识:

像eax,ebx,ecx,edx,ebp,esp这些都是CPU上通用寄存器的名称 

ebp,esp这俩个寄存器中存放的是地址,这俩个地址是用来维护函数栈帧的

每一个函数调用,都要在栈区创建空间,main函数也是被其他函数所调用的,所以调用main函数也要在栈区创建空间

在main函数中,调用了Add函数,所以Add函数也在栈区创建一块空间

下图为这段代码在栈区开辟空间图解:

27e09eea8b5348a1b82da20f0b12642b.png

当我们按(Fn)+(F10)开始调试代码后,在调试-->窗口-->反汇编 即可打开代码所对应的汇编代码,具体操作如下图:

4a73a28542354fa6a6aa9d2c48d74dac.png

接下来我们首先研究main函数的汇编代码

dfbbc47f69bc4e9eb36f393900eee358.png

第一列:001517C0......是该条指令的地址

简单介绍一下几条汇编指令

  • mov:传送指令,把一个字节,字或双字的操作数从源位置传送到目标位置,源操作数的内容不变。第一个操作数为目标位置,第二个操作数为源位置。(mov ebp,esp:把esp指针指向的值赋给ebp指针指向的内容)
  • push:压栈操作,将一个寄存器中的数据压入栈底
  • pop:弹栈操作,栈顶元素出栈且有一个寄存器接收数据,栈顶指针减一
  • add:加法,两个操作对象相加(寄存器的内容与数据相加)
  • sub:减法,两个操作对象相减(寄存器的内容与数据相减)
  • lea(load effective address装入有效地址),它的操作数就是地址,常见的用法:
  1. lea eax,[addr]:将表达式addr的值放入eax寄存器(右操作数为一个指针,该条指令与 mov eax,addr等价)
  2. lea eax,dword ptr[ebx]:将ebx的值赋给eax
  3. lea eax,c:其中c为一个int型的变量,该指令的意思是把c的地址写入eax中
  • rep(repeat):重复前缀指令,每执行一次,ecx减一,直到ecx减为0,重复执行结束
  • stos(store string):串存储指令,将eax中的数据传送到目的地址(默认为es:[edi])
  • call  标号:将下一条指令所在地址压栈,转到标号处执行指令
  • cmp:比较指令,相当于减法指令sub,它不保存结果,会影响相应的标志位
  • ret:子程序的返回指令
  • xor:异或操作,与 AND 指令及 OR 指令相同。两个操作数对应位遵循操作原则:如果两个位的值相同(同为 0 或同为 1),则结果位等于 0;否则结果位等于 1。如果两个操作数为寄存器,则相当于清0

 我们发现在创建变量之前,还有一段代码

c2e482ab0cb24ee094f5dbb056bc6e2f.png

push  ebp:把ebp压入栈

注意:ebp中存放的地址是维护栈底的指针,esp中存放的地址是维护栈顶的指针,每次压栈,esp会自动指向栈顶

执行完这一条语句后栈区状态如下图:

 9335823a321a45d9b80a446da19e2334.png

 mov    ebp,esp;把esp的值赋给ebp

 sub     esp,0E4h;esp的值减0E4h(十进制228)   

执行完这俩条语句后栈区状态如下图:

 d519f380f1754a38acaff3e8ae65f8fe.png

 push        ebx  
 push        esi  
 push        edi   

把ebx,esi,edi依次压入栈

执行完这三条语句后栈区状态如下图:

f98e10347d2e45ea813a42e2fae030d8.png

 lea  edi,[ebp-24h];把ebp-24h中的内容写入寄存器edi
 mov  ecx,9;ecx=9
 mov  eax,0CCCCCCCCh;把0CCCCCCCCh这个十六进制数写入eax
 rep stos  dword ptr es:[edi];将eax中的数据传送到es:[edi],且重复执行
 mov  ecx,15C003h;修改ecx的值为15C003h 
 call  0015131B ;将下一条指令所在地址001517E5压栈,程序转移到0015131B处

执行完这六条语句后,栈区的内容如下图

从ebp向低地址9个单元的内容被修改成cc cc cc cc

这几条指令的汇编代码在不同编译器下有区别,这里展示的是VS2019编译器下的运行结果

e871bc8a1c9440818bdc05a78756683e.png

8fa41def0a9540c38b0918c8699c7d36.png

mov  dword ptr [ebp-8],0Ah  

mov  dword ptr [ebp-14h],14h  
mov  dword ptr [ebp-20h],0  

执行完这三条语句后,a,b,c三个变量被创建并赋值

5075d9d338f149beaab139bc5217ceee.png

 接下来执行c = Add(a, b);我们可以看到函数传参过程

e4f2d11294f1456ebac36f6197aedee6.png

09254e0a27f5486aa2a96736c4bb6180.png

由以上汇编指令,我们可以看到函数调用时,传参方式是由后向前传参

在这段代码中,先传递变量b,再传递变量a,并对应有压栈操作

call  001513C0  ;将下一条指令所在地址00151807压栈,程序转移到001513C0处
add  esp,8  ;add指令:esp = esp + 8(十进制)

//栈顶指针加一个数,弹栈操作
mov  dword ptr [ebp-20h],eax  ;把eax中的值赋给ebp-20h单元

执行完这一段代码后,我们再次看一下内存分布情况

接下来我们使用快捷键(Fn)+F11进入Add函数

522b919209884aea88daf1e007902466.png

进入函数后,我们发现这一段汇编代码和main函数中开始的一段代码类似

这是在为Add函数创建栈帧 ,每一个函数调用都需要在栈区开辟一块空间

执行这段代码之后,从ebp向低地址3个单元的内容被修改成cc cc cc cc

栈区的分布如下图

函数栈帧创建完成后,进入正式的运算部分 

mov  dword ptr [ebp-8],0 ; ebp-8单元的内容赋值为0
mov  eax,dword ptr [ebp+8]  ;eax中的内容赋值为ebp+8单元的内容
add   eax,dword ptr [ebp+0Ch]  ;eax中的内容与ebp+0Ch单元的内容相加
mov  dword ptr [ebp-8],eax  ;ebp-8单元的内容赋值为eax单元的内容
mov  eax,dword ptr [ebp-8] ;eax单元的内容赋值为ebp-8单元的内容

//程序最终的结果为寄存器eax中存放30

这段代码是在Add函数中的

pop         edi  
pop         esi  
pop         ebx ;这三条指令将edi,esi,ebx弹栈 
add         esp,0CCh ;栈顶指针esp+0CCh

//释放栈区Add函数开辟的空间,与sub esp,0CCh对应
cmp         ebp,esp ;ebp-esp
call        00151244 ;将下一条指令所在地址001517B8压栈,程序转移到00151244处
mov         esp,ebp ; esp=ebp
pop         ebp  ;ebp弹栈
ret ;子程序调用结束 

接下来回到主程序 

add         esp,8  ;esp+8,弹栈操作
mov       dword ptr [ebp-20h],eax ;[ebp-20h] = [eax]  

    printf("%d\n", c);

mov         eax,dword ptr [ebp-20h]  ;[eax] = [ebp-20h]
push        eax  ;此时eax的内容为30
push        0B47B30h  
call        00B410CD  
add         esp,8 ;esp+8,弹栈操作 
    return 0;
xor         eax,eax ;寄存器eax清零

这部分和Add函数同理,当函数调用结束后,CPU会进行栈区空间的释放 

pop         edi  
pop         esi  
pop         ebx ;这三条指令将edi,esi,ebx弹栈 
add         esp,0E4h ;栈顶指针esp+0E4h

//释放栈区Add函数开辟的空间,与sub esp,0E4h对应
cmp         ebp,esp ;ebp-esp
call        00151244 ;将下一条指令所在地址001517B8压栈,程序转移到00151244处
mov         esp,ebp ; esp-ebp
pop         ebp  ;ebp弹栈
ret ;子程序调用结束,因为main函数也是被其他函数调用的

 当我们看到main has returned;exit somehow...时,证明整个程序已经运行结束

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值