• 函数栈帧的创建与销毁(保姆级讲解)


    局部变量是怎么创建的?

    在为main函数开辟栈帧空间时,在一定范围内初始化成0CCCCC,再把里面0CCCC的一些开辟空间给局部变量使用。

    为什么局部变量的值是随机值?

    因为我们在为main函数开辟栈帧空间时,会将一定范围内空间初始成0CCCCCC里面什么也没有,所以如果局部变量不给初始化,局部就会进入随意开辟栈帧空间,就是为随机值或者是烫烫烫。

    函数是怎么传参的?传参的顺序是怎样的?

    eax(b)和ecx(a)进行压栈,先传的b再传的a,从右向左。

    形参和实参是什么关系?

    形参是实参的一份临时拷贝,它们的值是相同的,但所使用的空间是不同的,所以形参的改变不影响实参,形参确实只是实参的一份临时拷贝。

    函数调用是怎么做的?

    下面我画图所解释的非常清楚了。(如果不明白的同学可以私信互相交流下)

    函数调用是结束后怎么返回的?

    由函数一步一步建立空间,再一步一步销毁空间返回

    用保存call指令下一条指令地址与ebp-main函数的保存位置进行寄存器返回值

    用寄存器eax返回最终的值。


    知道和函数栈帧的创建和销毁就都会了,其实就是修炼了自己的内功,也能搞懂后期更多的知识。
    进入正题
    今天讲解使用的环境是VS2019
    同时在不同的编译器下,函数调用过程中栈帧的创建是略有差异的,具体细节取决于编译器的实现。

    首先我们要了解什么是函数栈帧

    函数栈帧就是在函数调用过程中,程序为函数所开辟的栈空间,函数一般放在栈区。

    而编译器为了方便动态内存管理,一般划分为了三个区域:栈区 堆区 静态区

    而什么又是栈呢?

    栈的概念及结构
    栈:一种特殊的线性表,其只允许在 固定的一端 进行 插入和删除 元素操作。 进行数据插入和删除 操作的一端称为 栈顶 ,另一端称为 栈底 。栈中的数据元素遵守 后进先出 LIFO (Last in First Out) 的原则。
    压栈:栈的插入操作叫做进栈/压栈/入栈(Push), 入数据在栈顶
    出栈: 栈的删除操作叫做出栈(Pop)。 出数据也在栈顶 。
    特点:栈只能在栈顶进行插入和删除。

    为了更加清楚了解函数栈帧,我们还需要了解下以下寄存器

    eax :保留临时数据,常用于返回值

    ebx:保留临时数据

    ecx

    edx

    ebp:栈低指针

    esp:栈顶指针

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

    每一个函数调用,都要在栈区创建一个空间。

    接下来以如下图代码来解释函数栈帧的创建与销毁过程

    1. #include
    2. int Add(int x, int y)
    3. {
    4. int z = 0;
    5. z = x + y;
    6. return z;
    7. }
    8. int main()
    9. {
    10. int a = 10;
    11. int b = 20;
    12. int c = 0;
    13. c=Add(a, b);
    14. printf("%d\n", c);
    15. return 0;
    16. }

    在调用main函数时ebp和esp会维护main函数的函数栈帧。

    当我按F10进入调试,并且打开调用堆栈


    我们可以看见函数的调用关系,调用堆栈是反应函数的调用关系的

    由调试我们可以看见main函数是被invoke_main()函数调用的

    而Add函数是被main()函数调用的

    那么理所当然的invoke_main函数与Add函数也是有自己的函数栈帧空间的,并且由ebp和esp来维护函数栈帧空间。

    F10进入调试点击右键转到反汇编

    如下图所示是此次代码的反汇编指令 

    但我们为了方便更加清晰的观察,再次点右键取消显示符号名。

     

    main函数第一条反汇编指令是Push(压栈) ebp。

    如果是ebp压栈那么ebp的值是会变小的 因为是由高地址到低地址

    这是原来的值

    当我push以后的值,果然变小了

     所以是真的压进去了吗?我们可以通过内存来查看

    当我们查看esp内存的地址时,ebp确实压进去了,因为esp内存的值是ebp的地址

    fc f9 f3 00  VS编译器一般是:小端字节存储 低位字节数据放低地址处 高位字节数据放高地址处 

    mov的意思是把esp的值给ebp 我们依然可以通常调试来查看是不是这样的

    sub是subtraction减法的缩写。就是给esp减去0E4h 

     用16进制显示就是228

     当我们给esp减去所对应的值时,那么esp不能再指向原来的位置,而是指向了上一块的某块区域

    当我们查看内存ebp和esp

    ebp

    esp

    这些内存空间都是为main函数所开辟的空间 

    接下来是在栈顶压3个元素 

    随着压栈esp也会随着压栈指向位置会发生变化 

     

    VS2019栈区内存存放习惯:先放高地址,再放低地址

    下面esp的值会随着压栈 esp位置也产生了变化

    lea指令是=load effective address. 意思是加载有效地址

     这个指令有效果的其实是rep stos意思是把edi开始下面将内存空间改成OCCCCCCCCh 以双倍字节开辟 dword=double word  es:[edi]把edi开始下面所有空间以双字节开辟成0CCCCCCCCh。

    edi到ebp开辟内存空间

    esp-24h的值如下图

     

     将十进制数0Ah放进ebp-8 就是相当于把10放到了ebp-8里面

    如果不给a初始值那么就是随机值,因此在为main函数开辟空间时使用的就是CCCCCC的值,所以会出现烫烫烫(字符串 字符)或者随机值(变量)。

     

    0a 00 00 00  就是10的十六进制存储 内存存储一般是十六进制存储

    又是隔着两个字节存放的C的值0 (不同编译器存放位置不同,取决于编译器)

     

    把20的值给eax再把eax压栈压进去

    下一步指令把10给ecx然后再把ecx进行压栈

     按F11进入call令

    内存中存放的是下一条add指令的地址00171987

     当我们再按一次F11会跳到Add函数的反汇编指令当中去

    这和main函数的反汇编极其相似,这是在为Add函数开辟函数栈帧空间

     第一步Push压栈 第二步mov esp给ebp 第三步把0cch 给esp 相当于esp又往上走了

     

     

     

    再进行压栈 

    随着压栈esp的值也变化 esp的指向位置也随着变化

    把ebp-0ch值给edi 把3给ecx 然后从edi开始下面所有位置改成0CCCCCCCH

     

     

     把0放到ebp-8 

     

     

     

     

    ebp+0ch相当于+12 epb+12

    通过调试过程看见传参是从右向左,先传的b再传的a

    最后返回的时候把ebp-8的值也就是z的值给了寄存器eax

    下面指令pop三次 esp也随着产生位置变化

    当pop了三次esp的值 增加了3次

    当pop三次要返回main函数 那么Add创建的函数栈帧就要销毁 这几个指令完成了把esp的值给ebp

    再pop ebp 我们这个位置所保存main函数的ebp-main 就是为了函数返回时找到main函数的ebp

    返回以后ebp和esp又开始维护了main函数的函数栈帧空间

    这条指令就是为了执行call指令下一条add的功能 弹出了add指针地址

    所以我们在开辟函数栈帧时保存了add指令地址

     当我们再按F10就回到了main函数Add的位置

    main函数add指令 00171987与我们刚才在函数栈帧保存的call指令下一条指令的地址一模一样,也就是add指令函数的地址。就是为了方便回来,简直就是荣归故里!设计的太牛了!

    给esp+8

    随着esp+8形参的空间也销毁了。

    把eax 30的值给ebp-20h 就是刚才c的位置

    然后在程序结束时寄存器会返回所对应的值。

  • 相关阅读:
    一文带你了解RabbitMQ
    SpringBoot SpringBoot 开发实用篇 4 数据层解决方案 4.14 ES 索引操作
    性能测试常见分类
    无监督模型预训练论文、代码汇总
    【iOS逆向与安全】插件开发之某音App直播间自动发666
    【设计模式】六、【创建性模式】揭秘单例模式:从生活例子到Java代码
    Spring Boot中ConfigurationProperties注解的详解说明
    OpenAI打破文本和图像次元壁,提出基于对比学习的多模态预训练模型CLIP
    交换机之trunk access hybrid 以及vlan深入理解
    SpringCloud - Sleuth分布式请求链路跟踪
  • 原文地址:https://blog.csdn.net/weixin_74795859/article/details/133822552