zoukankan      html  css  js  c++  java
  • C++函数调用栈的变化分析

    程序中栈的基础知识

    栈是向下生长的

    向下生长指的是从内存的高地址-->低地址的方向拓展。

    栈有栈底和栈顶,从上面可以知道栈顶的地址是比栈底的要低的。

    对于X86体系的CPU而言,大概需要知道以下基础知识:

    1. ebp寄存器:一般叫做基址指针或者帧指针
    2. esp寄存器:一般叫做栈指针
    3. ebp在没有改变之前始终指向栈底,ebp主要用于在堆栈中寻址
    4. esp会随着数据入栈和出栈变化,esp始终指向栈顶

    函数调用的过程描述

    若函数A调用函数B,那么A函数一般叫做调用者,B函数一般为被调用者,函数调用过程可以做如下描述

    1. 现将函数A的堆栈基址ebp入栈,用于保存之前任务信息
    2. 然后将函数A的栈顶指针esp的值赋给ebp,用作新的基址(这里就是函数B的栈底)
    3. 紧接着在新的ebp基础上开辟相应的空间当做被调用者B的栈空间,开辟空间一般用sub指令;
    4. 函数B返回后,从当前栈底ebp恢复为调用者A的栈顶esp,使得栈顶恢复成函数B被调用前的位置;
    5. 最后调用者A从恢复的栈顶弹出之前的ebp值(因为在函数调用前一步被压入堆栈);这样ebpesp都变成了调用函数B前的位置;

    示意图如下所示
    funcstack

    简单例子

    函数调用示例代码

    一个简单的函数调用例子

    #include <iostream>
    
    int __cdecl Add(int a, int b)
    {
        return a + b;
    }
    
    int main()
    {
        auto res = Add(2, 3);
        std::cout << "2 + 3 = " << res << std::endl;
        std::cout << "Hello World!
    ";
    }
    

    函数调用过程汇编解析

    1. main函数调用Add函数之前,main函数的栈帧情况如下所示
      main stack

    2. 当main函数调用Add函数的时候,汇编如下

        auto res = Add(2, 3);
    
    00E12618  push        3  
    
    00E1261A  push        2  
    
    00E1261C  call        Add (0E111D6h)  
    
    00E12621  add         esp,8  
    
    00E12624  mov         dword ptr [res],eax  
    
    
    1. 从调用Add函数的汇编语言中大概可以得出调用函数的大概模式就是如下:
    push parameter_n
    push parameter_...
    push parameter_1
    
    call funcName; 调用函数funcName, 加你个返回地址填入栈,并且跳转到funcName
    

    main函数调用Add函数的栈示意图如下:
    use add stack

    call        Add (0E111D6h) 进入Add函数之后,汇编语言如下所示

    int __cdecl Add(int a, int b)
    {
    
    00E12300  push        ebp  
    
    00E12301  mov         ebp,esp  
    
    00E12303  sub         esp,0C0h  
    
    00E12309  push        ebx  
    
    00E1230A  push        esi  
    
    00E1230B  push        edi  
    
    00E1230C  lea         edi,[ebp-0C0h]  
    
    00E12312  mov         ecx,30h  
    
    00E12317  mov         eax,0CCCCCCCCh  
    
    00E1231C  rep stos    dword ptr es:[edi]  
    
    00E1231E  mov         ecx,offset _44E0C52E_AnalyseFunc@cpp (0E1F026h)  
    
    00E12323  call        @__CheckForDebuggerJustMyCode@4 (0E11280h)  
    
        return a + b;
    
    00E12328  mov         eax,dword ptr [a]  
    
    00E1232B  add         eax,dword ptr [b]  
    
    }
    
    00E1232E  pop         edi  
    
    00E1232F  pop         esi  
    
    00E12330  pop         ebx  
    
    00E12331  add         esp,0C0h  
    
    00E12337  cmp         ebp,esp  
    
    00E12339  call        __RTC_CheckEsp (0E1128Ah)  
    
    00E1233E  mov         esp,ebp  
    
    00E12340  pop         ebp  
    
    00E12341  ret  
    

    在Add函数的汇编语言中可以看到开始的前3句,这里做如下解释

    00E12300  push        ebp; 进入新的函数,新函数也需要一个栈帧了,就必须将main函数的栈帧底部全部保存起来,栈顶则是作为一个新函数的栈底
    
    00E12301  mov         ebp,esp;上一个栈帧顶部就是这个栈帧的底部
    
    00E12303  sub         esp,0C0h;为当前栈帧开辟相应的空间
    
    1. main函数进入Add函数的示意图如下所示
      add stack

    当Add函数执行完之后,将执行ret 指令返回,并且esp指向Add函数栈帧底部(就是main 函数栈帧顶部), 紧接着就是从弹出保存的ebp恢复现场,这样就回到了调用Add函数之前的状态。

  • 相关阅读:
    Android Developers:使ListView滑动流畅
    Nexus 搭建maven 私有仓库
    Eclipse 配置Maven以及修改默认Repository
    maven常用命令介绍
    maven 相关
    session机制详解以及session的相关应用
    正确理解web交互中的cookie与session
    前端开发中Cookie那些事儿
    html转义表
    常用的Linux命令
  • 原文地址:https://www.cnblogs.com/zuixime0515/p/13663796.html
Copyright © 2011-2022 走看看