函数栈帧的创建与销毁

        函数调用需要先为函数开辟一个栈帧空间,就像在创建变量时,会向操作系统申请相应大小的n个字节的空间

        cpu中的两个寄存器ebp,esp分别指向函数栈帧的栈底位置和栈顶位置,两个寄存器目前的指向取决于程序目前正在执行哪个函数,就拿下面的程序分析:

目前的程序运行到11行,执行在main函数的栈帧内,打开内存,找一下ebp和esp的位置:

 

 ebp指向main函数的栈底位置,esp指向main函数的栈顶位置:

 从F6A0到F680,一共是32个字节,mian()函数栈帧的总大小就是32个字节.

        首先我们需要清楚,main()函数其实也是被别的函数调用,调试开始后我们观察下栈帧:

 如图:main()函数被_tmainCRTSrartup()函数调用,_tmainCRTSrartup()函数又是被mainCRTStarup()函数调用,抽象出来内存如图所示:

 

        目前我们只需要知道main()函数也是被其他函数调用的就可以,至于调用它的函数实现的什么功能,我们不需要知道.

        要观察函数栈帧具体是怎样创建和销毁的和了解ebp和esp两个寄存器是如何对函数栈帧进行维护的,我们需要转到汇编代码:

 让我们解释下汇编代码,当程序运行到这里的时候,从花括号到第一个变量a之间的汇编代码就是c语言花括号的含义,他被解释成汇编代码实际上就是在为main()函数的栈帧创建进行准备工作:
 

 

        我们还是先把函数栈帧入抽象一下:


push   ebp:

       他表示将ebp寄存器压栈,ebp的数值就是当前函数栈帧的栈底地址,元素入栈后,栈顶指针向上移动:

 观察esp的值,向上走一步,上面是低地址,esp的值会减少:

 可以看到刚好减少了四个字节:同样可以观察到esp内的值:

move  ebp,esp : 将ebp移动到esp的位置

 

 

 

sub   esp  0E4h:esp指针减去0E4h(228)

        图中,上面是低地址,下面是高地址,esp减去一个值,表示esp向上走了,减去的这个值就是为main()函数开辟的栈帧的空间大小.

 

 push  ebx ,push  esi,push  edi: 将ebx,esi,edi三个寄存器的值压栈:

lea  edi [ ebp + FFFFFF1ch] : 方括号内的内容还可以表示成 ebp-0E4h

        前面的一条mov指令,将ebp移动到esp的位置后,又给esp减去了0E4h

lea的全拼是 load effective address ,加载有效地址,即edi中存放的地址实际是main()函数的栈顶位置.

mov ecx 39h,mov  eax 0xcccccccch: 将39h和0xcccccccch分别加载到ecx和eax寄存器

rep  stos dword ptr es:[edi]  :  将edi开始位置的39h个dword字节的空间都改成eax中的值

 上述三条指令实际是对main()函数空间的初始化.

c中的int a =10 编译后的指令: 将0Ah(10)放到ebp-8的位置

 

 

 

 接下来的两条指令实现的是一样的功能:分别将14h和0放入到ebp-14和ebp-20h的位置:

 

 

 

 接下来就是函数调用:

 

将ebp-14h位置的值加载到eax寄存器

eax压栈

将ebp-8位置的值加载到ecx

ecx压栈

上述四条指令就是在传参.

call 00C210E1,call指令实际上就是将call指令的下一条指令的地址压栈:

        这条指令决定了函数被调用完以后如何返回

 接下来就来到了Add函数内部:

 函数栈帧的准备工作与前面main()函数栈帧的准备工作步骤相同:

1.ebp地址压栈

2.ebp来到esp的位置

3.esp减去一个值,即为函数开辟栈帧空间

4.ebi,esi,edi 三个寄存器压栈,此时esp指向edi位置,edi中的内容是三个寄存器压栈前的地址

5.将edi内位置开始的ecx个dword字节的空间初始化为eax内值的大小

6.开始执行函数内的代码

这里main()函数中a 的值传给x,b的值传给y,在调用Add()函数之前,已经将a,b参数压栈,同时还有call指令的下一条指令的地址和main()函数栈底地址:

mov ,dword ptr [ebp+8]:就是将参数给赋值给eax寄存器

 将运算结果返回:

 函数内部执行完的结果在ebp-8的位置,这里执行return,就是想刚才的运算结果赋值给eax寄存器,随后函数栈帧空间被销毁.

 

最后的花括号,出栈三个寄存器,每pop一次,esp++一下,然后将ebp的值赋值给esp,

 

 esp维护的不再是add函数的栈帧空间,注意这里的pop ebp,此处的值是main()函数的栈底地址,pop给ebp,那么ebp就来到了main()函数栈底的位置.

这里的ret:在形参压栈完之后,我们将call指令的下一条指令的地址压栈,此时ret返回的就是call的下一条指令

        通过上文,希望你能够大致了解,函数栈帧是如何创建的,参数是怎么传递的,以及函数调用完之后是如何返回的.

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值