探秘函数栈帧:『 揭开函数栈帧创建与销毁的神秘面纱 』(三)

简介: 探秘函数栈帧:『 揭开函数栈帧创建与销毁的神秘面纱 』

2.对main函数中的代码进行分析

接下来,我们正式进入对代码进行分析了~~

image.png

🔔mov dword ptr [ebp-8],0Ah:mov指令,将0Ah的值(0Ah转换十进制是10),赋给ebp-8指向的位置,此时ebp-8就是为a变量开辟的空间,值为10。(那么,我们在这里是不是就可以知道,如果局部变量没有初始化,那么它的值就是一个随机值,只不过在这里表示的是0xcccccccc),0xcccccccc如果打印成文本就是“烫烫烫”。


image.png

📌图形展示:

image.png

我们再往下调试一步,


image.png


🔔mov dword ptr [ebp-14h],14h:将14h(14转换成二进制是20),赋给ebp-14h指向的位置,ebp-14h指向的空间就是为变量b开辟的一块空间,值为20image.png


📌图形展示:image.png

🔔mov dword ptr [ebp-20h],0 :将0(0转换成二进制是0),赋给ebp-20h指向的位置,ebp-20h指向的空间就是为变量b开辟的一块空间,值为0image.pngimage.png

📌图形展示: image.png

到此,我们应该就明确了局部变量是怎么创建和初始化的吧~,接下来我们继续往下看,

来到调用Add函数的部分image.png

🔔mov eax,dword ptr [ebp-14h] 又是mov指令,将ebp-14h的值,也就是b的值给到eax,鼠标悬停到eax上面,我们可以看到eax的值为0x00000014 ,eax的值就是20

🔔push eax 对eax进行压栈操作,esp往上走一步image.png

🔔mov ecx,dword ptr [ebp-8] 又是mov指令,将ebp-8的值,也就是a的值给到ecx,鼠标悬停到ecx上面,我们可以看到ecx的值为0x0000000a ,ecx的值就是10

🔔push ecx 对ecx进行压栈操作,esp再往上走一步image.png

📌图形展示: image.png

走到这里的时候,想必大部分读者都会认为这是函数的传递参数吧,答案的确是的,那么后面的结果如何呢?我们继续往下看,

🔔call 00C210E1 接下来就是调用Add函数,此时需要按 「F11」键image.png

调用Add函数指令之后,我们再次观察esp减少了,变成0x008ffaab 这个地址里面放进了00c21450这个值,这个值恰好是call指令下面一条指令的地址,为什么呢,其实这里就是Add函数调用结束需要回到call指令下一条继续执行,所以需要记录call下面的一条指令。image.png

📌图形展示: image.png

3.探究Add函数栈帧的创建

继续「F11」之后,进入Add函数,此时就是准备为Add函数创建函数栈帧空间image.png

🔔push ebp:对ebp进行压栈操作,把指向main函数的ebp寄存器压入栈顶image.png

🔔mov ebp,esp:把esp寄存器中的值移动到ebp,此时由原来ebp指向main函数栈帧空间移动到esp指向的空间位置,

📌图形展示:

image.png

🔔sub esp,0CCh:将esp减去0CCh,esp寄存器此时再次向下增长,此时esp与ebp之间的空间就是为Add函数预开辟好的栈帧,

📌图形展示:  image.png

🔔push ebx、push esi、push edi:三次压栈操作,和main函数开辟栈帧时一样,这里就不多说了,直接上图~

📌图形展示:    🔔push ebx、push esi、push edi:三次压栈操作,和main函数开辟栈帧时一样,这里就不多说了,直接上图~ 


image.png


🔔 接下来lea指令、mov指令、rep stos指令,是让Add函数的空间都初始化为0CCCCCCCC这样的值image.pngimage.png


📌图形展示:  image.png

🔔mov dword ptr [ebp-8],8:接下来就可以给局部变量z赋予空间了,mov指令就是将0赋值给ebp-8指向的空间里,image.pngimage.png

📌图形展示:   image.png

z = x + y,这个代码怎么分析呢?难道我们会再次赋予两个空间给x和y吗?其实不然,我们继续往下看


🔔mov eax,dword ptr ebp+8:将ebp+8空间里的值移动到eax当中去。ebp+8得到的地址值增大了,我们在图形中往下寻找,找到ebp+8指向的位置,咦,不就是我们之前将ecx寄存器进行压栈操作压入main函数栈帧上面的吗,ecx寄存器里放的就是10啊,那此时eax里面的值放的就是10


🔔add eax,dword ptr ebp+0Ch:ebp+0Ch的值就是ebp+12,将ebp+12所指向的空间里的值,值为20加到eax寄存器中,寄存器中的值就是20了。


🔔mov dword ptr [ebp-8],eax:该指令将eax的值放到ebp-8的位置,即将30赋予给局部变量Z空间里。

image.png

所以到这里我们知道,形参并不会在函数内部进行创建,逻辑其实是调用Add函数时,将a和b的值进行了压栈操作,然后进入到Add函数里面时,寄存器就会找到对应压栈时压的值,ebp+8、ebp+12 里的值就是a、b的一份临时拷贝,也就对应了形参x、y值。所以我们就能通透理解形参是实参的一份临时拷贝,修改形参并不会影响实参!


📌图形展示:  

image.png

那么看到这里,怎么将z的值进行返回呢?我们继续往下看image.png

🔔  mov eax,dword ptr [ebp-8]:这一操作指令的意思是将ebp-8空间里的值,也就是z的值,赋值给eax寄存器中,因为出函数作用域,局部变量z会被销毁!而eax寄存器中的数据是不会立马销毁。


ok,以上Add函数执行完毕,我们接下来执行返回操作了,也就是函数栈帧逐步销毁的过程了。


三、函数栈帧的销毁过程

🔔pop 出Add函数之后,有三次pop出栈操作,将edi、esi、ebx寄存器中的数据从栈顶弹出

image.png

📌图形展示:image.png

🔔mov esp,ebp:将ebp寄存器中的值赋值到esp寄存器中,esp指向的位置就是ebp寄存器指向的位置


🔔pop ebp: ebp此时指向的位置是在为main函数开辟函数栈帧空间时压栈的ebp,在Add函数调用完毕之后,回到main函数的栈帧空间,那么main函数栈帧的栈底在哪里呢?这是对当前的ebp寄存器进行pop出栈操作,此时就返回到了main函数栈帧的栈底


🔽此时esp寄存器和ebp寄存器又正式开始维护main函数的栈帧空间

image.pngimage.png

🔔接下来就是ret指令 ret就是返回呀,没错,根据00C21450这个地址返回,也就是当时调用Add函数call指令的一条指令的地址,将这个地址pop一下,就回到了call指令的下一条继续执行image.png

📌图形展示: image.png

🔔add esp,8:  此时是esp的地址加8,地址增大,空间减小,相当于当时形参x、y两个变量的空间由操作系统回收了,image.png

🔔mov dword ptr [ebp-20h],eax: 这条指令的意思就是将eax的值赋给ebp-20h所指向的空间,我们观察图,可以看到ebp-20h不就是为局部变量c开辟的一块空间吗?c的值就是当初出Add函数作用域时,存放到eax存储器中的数据,这个值就是30!


📌图形展示:  

image.png

这样整个Add函数销毁的过程就很清晰啦,main函数的栈帧销毁过程就不再赘述了~

目录
相关文章
|
2月前
|
存储 安全 C语言
深度剖析c语言程序 -- 函数栈帧的创建和销毁(纯肝货)-2
深度剖析c语言程序 -- 函数栈帧的创建和销毁(纯肝货)-2
|
2月前
|
存储 编译器 C语言
深度剖析c语言程序 -- 函数栈帧的创建和销毁(纯肝货)-1
深度剖析c语言程序 -- 函数栈帧的创建和销毁(纯肝货)-1
|
程序员 编译器 C语言
细谈函数栈帧的创建与销毁
我们在写C语言代码时,经常会把一个独立的功能抽象为函数,所以C程序是以函数为基本单位的。那函数如何调用?函数的返回值如何返回的?函数参数是如何传递的?这些问题都与函数栈帧有关系。
141 0
|
9月前
|
存储 机器学习/深度学习 编译器
函数栈帧深度剖析(一篇带你牢牢掌握函数栈帧)(一)
函数栈帧深度剖析(一篇带你牢牢掌握函数栈帧)(一)
179 0
|
9月前
|
存储 编译器
函数栈帧深度剖析(一篇带你牢牢掌握函数栈帧)(二)
函数栈帧深度剖析(一篇带你牢牢掌握函数栈帧)(二)
68 0
|
11月前
|
存储 编译器 程序员
C语言代码函数栈帧的创建与销毁(修炼内功)
目录 在前期的学习中我们可能有很多困惑 例如:局部变量是怎么创建的 为什么局部变量的值是随机值 函数是怎么样传参的 传参的顺序是什么 形参和实参的关系是什么 函数调用是怎么做的 函数掉调用结束后怎么返回的 这篇博客我们来修炼自己的内功,掌握好这篇博客的大部分知识就已经很不错了 我们用到VS2013这个编译器,目的是为了看到更详细的函数封装内容 提示不要使用太过高级的编译器,因为越高级的编译器越不容易观察。同时这里需要注意的是在不同的编译器下,函数调用过程中栈帧的创建是略有差异的,不是完全相同的,具体细节取决于编译器
|
11月前
|
C语言
抽丝剥茧C语言(中阶)函数栈帧的创建与销毁——图解(下)
抽丝剥茧C语言(中阶)函数栈帧的创建与销毁——图解
|
11月前
|
编译器 C语言
抽丝剥茧C语言(中阶)函数栈帧的创建与销毁——图解(上)
抽丝剥茧C语言(中阶)函数栈帧的创建与销毁——图解
|
11月前
|
存储 编译器 C语言
函数栈帧的创建与销毁(反汇编万字讲解)
局部变量是怎么创建的? 为什么未初始化的局部变量的值是随机值? 函数是怎样传参的?传参的顺序是怎样的? 形参和实参是什么关系? 函数调用是怎样做的? 函数调用后是怎样返回的? 我们本章就来研讨这个问题,掌握了函数栈帧的创建和销毁更有利于后期的学习这里建议大家要从头往后一个内容一个内容看,因为这里每一个部分关联性很强!
【函数栈帧的创建和销毁】 -- 神仙级别底层原理,你学会了吗?(2)
1.函数的调用方式 相信你对调用函数一点都不陌生,但是在调用函数的过程中,却存在着很多你无法见到的东西,这是底层信息,想要理解透彻,就得深入底层去观察。 本文以一个最简单的加法函数为例,深入讲解内存空间中的每一条指令。