• 函数栈帧的创建和销毁


    函数栈帧的创建和销毁在不同编译器的环境下,原理大体是相同的。

    1.寄存器 

    eax
    ebx
    ecx
    edx
    以下两个寄存器中存放的是地址,这两个地址是用来维护函数栈帧的
    ebp栈底指针
    esp栈顶指针

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

     我们写如下代码作为示例:

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

    执行了上面的代码之后,我们的栈区空间中就会为我们的main开辟一块空间,其中ebp用来记录我们main所占空间的高地址,esp用来记录我们main空间所占的低地址 

    栈区的使用习惯是先使用高地址,然后使用低地址,也就是先使用我们下面的空间,再使用上面的空间

    在VS2013中,main函数也是被其他函数调用的:

    mainCRTStartup       调用了      __tmainCRTStartup     调用了main函数

    压栈是从栈顶放入一个元素push

    出栈是从栈顶弹出一个元素pop

     在我们的main函数之前,我们的  __tmainCRTStartup 函数已经在栈区了,我们的ebp和esp指针分别记录  __tmainCRTStartup函数的高地址和低地址(这张图的下面是高地址,上面是低地址)

    首先我们执行压栈操作:push ebp,将我们的ebp压入我们栈顶,此时我们的ebp中存储的是我们 __tmainCRTStartup的高地址

     

    当我们ebp入栈之后,我们的栈顶指针esp同时也随之上移 

     

     mov ebp,esp

    将esp的值赋给ebp,也就相当于将ebp的指针上移到下图的情况

     

    sub esp,0E4h 

    将esp减去0E4h,0E4h是一个八进制数字228

    也就相当于我们esp指针上移,如下图所示,其中我们发现我们的esp和ebp指针已经不再维护原来的__tmainCRTStartup空间,此时那块紫色的空间其实是为了给我们的main函数所开辟的空间

    push ebx

    push esi

    push edi

     依次将我们的ebx,esi,edi三个压入栈中,但是同时,我们需要注意到我们的栈顶指针esp会随着每一次入栈,及时地指向栈顶的空间

     

     lea edi,[ebp-0E4h]

    lea是load effective address,就是加载有效地址的意思。所以上面的代码就是将后面的这个有效的地址加载到edi里面去

    这里我们给ebp减去了0E4h,注意,我们这里的0E4h在之前就已经出现过了,是我们的main函数栈帧的空间,给我们的ebp减去0E4h就是往上移动到了main函数栈帧的顶部

     mov ecx,39h

    将39h放入ecx中

    mov eax,0CCCCCCCCh

    将0CCCCCCCCh放入eax中

    rep stos dword ptr es:[edi]

    将刚刚从edi开始的39h次的dword(double word双字,四个字节)的数据全部都改为0CCCCCCCCh,也就是将我们刚刚为main函数开辟好的内容全部都改成CCCCCCCC

     

     int a=10;

    mov dword ptr [ebp-8]0Ah

    将0Ah这个十六进制数字,也就是十进制中的10放到ebp-8的位置

     int b=20;

    dword ptr [ebp-14h],14h

    int c=0;

    dword ptr [ebp-20h],0

    同理

     c=Add(a,b);

    mov eax,dword ptr [ebp-14h]

    将ebp-14h,也就是b的值放到eax中去,也就是将20放入eax中

    push eax

    将eax压入栈中

     

     mov ecx,dword ptr [ebp-8]

    将ebp-8中的值放入ecx中,也就是将我们的a,10,放入ecx中

    push ecx

    将ecx入栈

    与上面一步同理,都是函数的传参

     

     call 00C210E1

    调用函数(call的地址为00C2144B),并且会将call指令的下一条指令的地址压入栈中。

    这里的意思就是call指令执行之后会跳到函数的地址,当从函数的地址处跳回来的时候,直接执行call的下一条指令

     

     接下来我们就进入了add函数内部

    下面的代码和我们的main函数创建的时候非常类似

    push ebp

    将ebp压入栈中

     

     

    mov ebp,esp

    将esp的值赋给ebp

    也就是说我们的ebp指向了esp的位置

     

    sub esp,0CCh

    给sub减去0CCh

    也就是给我们的add函数开辟出来的空间​​​​​​​

     ​​​​​​​

     

    push ebx

    push esi

    push edi

    分别将ebx,esi,edi压入栈中

     ​​​​​​​

     lea edi,[ebp+FFFFFF34h]

    加载有效地址,将ebp+FFFFFF34h的地址加载到我们的edi中

    mov ecx,33h

    将33h存入我们的ecx中

    mov eax,0CCCCCCCCh

    将0CCCCCCCCh赋值给eax这个寄存器

    rep stos dword ptr es:[edi]

    让我们把从edi这个位置开始向下到ebp中间全部赋值为0CCCCCCCCh

     

     int z=0;

    mov dword ptr [ebp-8],0

    将0放入ebp-8的位置,也就是我们的z​​​​​​​

     ​​​​​​​

    mov eax,dword ptr [ebp+8]

    将ebp+8的值放入eax中,也就是我们的a

    add eax,dword ptr [ebp+0Ch]

    将我们ebp+0Ch处的值加到我们的eax中去,也就是将我们的b加给a,这是,我们的eax为30

    dword ptr [ebp-8],eax

    加完之后,再将我们eax的值放入我们ebp-8的位置,也就是我们的z的地址

    这里我们可以注意到我们压栈是先将b压入,再压a,所以我们传参是从右向左传的

    也就是我们的add(a,b)是先将b压入,再将a压入的

    所以形参并不是我们在add函数内部创建的,而是在传参产生之后回去找到传参时的参数的

     

    return z;

    mov  eax,dword ptr [ebp-8]

    所以这里return z 的命令,也就是将我们的ebp-8的值放入我们的eax中,也就是现将我们的30放入我们的eax中,因为z一会儿出去之后就会被销毁,所以我们需要把我们计算的结果放入我们的寄存器中进行存储。()

    pop edi

    将栈顶的元素弹出放入我们的edi寄存器中,此时栈顶原本的数据就是edi,也就是说只是将edi的数据读取而已,下面两个也是如此

    ​​​​​​​

    pop esi

    pop ebx

     

     

    mov esp,ebp

    将ebp赋值给esp,也就是说我们的esp指向了与我们ebp相同的位置

     

     pop ebp

    将栈顶的元素弹出赋值给ebp,也就是我们原本的ebp-main的位置也就是说现在我们的ebp和esp重新维护我们的main函数的空间。

    ret 

    之前我们将call指令的下一条指令的地址给存了起来,现在我们ret一下,我们现在又成功找到了我们之前call指令的下一条指令

     add esp,8

    esp+8也就是让我们的栈顶指针下移动到我们b的下面,也就是将我们之前那两个已经使用过的形参x,y的空间销毁

     

    mov dword ptr [ebp-20h],eax 

    将eax的值放入ebp-20h的位置,也就是给我们的c赋值

     

     至此,我们add函数栈帧的创建和销毁就完成了

  • 相关阅读:
    LVS负载均衡群集-NAT模式实例
    JavaScript之数据类型、类型判断、类型转换
    K8s部署轻量级日志收集系统EFK(elasticsearch + filebeat + kibana)
    一窥Ripple的扩容野心:用侧链融入EVM生态
    Linux彻底卸载MySQL
    云原生技术管理实践:业务引领的DevOps持续交付体系
    ASP.NET Core 6框架揭秘实例演示[32]:错误页面的N种呈现方式
    JAVA计算机毕业设计校园互助系统(附源码、数据库)
    Java 基于微信小程序的农产品自主供销小程序
    Mysql函数
  • 原文地址:https://blog.csdn.net/weixin_62684026/article/details/125556586