函数调用时的堆栈变化(实例)

函数调用时的堆栈变化

关于函数调用是的堆栈变化,在网上找到的资料大都是一些配图文字等,理解起来尚有些困难,不过建议大家还是先了解一下基本的原理,下面我主要通过一个调用函数的实例来讲解一下函数调用时的堆栈变化(Ps:图片有点糊,大家最好自己跟着做一遍叭)

测试函数如下:

#include <iostream>

int add_1912080143(int x, int y)
{
    
    
    int z = 0;
    z = x + y;
    return z;
}
void main()
{
    
    
    int n = 0;
    n = add_1912080143(1, 3);
    printf("%d\n", n);
}

1. 在VC++6.0中新建项目将函数代码复制上去

在这里插入图片描述

2.在函数调用处下断点,按F5进行调试

在这里插入图片描述

3.点击图示按钮查看程序的汇编代码

在这里插入图片描述

4.对汇编代码进行简单的分析

  1. int n = 0 的汇编代码为 mov dword ptr [ebp-4],0 这里n为局部变量所以在栈中存储,ebp为基址支持寄存器存储的是栈帧的起始地址在这里插入图片描述
  2. F10单步执行程序push 3, push 1这里是将调用函数的两个参数从右到左依次入栈
    在这里插入图片描述
  3. 继续F10单步执行,这里先不进入函数分析函数执行完之后的代码 add esp,8 这里是因为前面向栈中push了两个参数每个int占4字节,所以将esp(栈顶指针)加8个字节恢复到push参数之前的状态(注意:向栈中加数据是从高地址向低地址移动),mov dword ptr [ebp-4],eax将函数的返回值赋值给n,eax寄存器用来保存函数的返回值,[ebp-4]是n的值(这里的ebp是不变的因为在调用函数的时候会保存现场,具体过程可以看下面进入函数后的代码分析)
    在这里插入图片描述

5.对进入函数后的汇编代码进行分析

  1. 在call指令处按F11进入函数,这里注意一下执行完函数后的下一条指令的地址00401098
    在这里插入图片描述
  2. 按下F11后程序并没有立即跳转到代码区,我们查看查看一下栈中的内容,可以看到栈顶的数据为00401098(这里的存储方式采用小端存储注意顺序),而这正是执行完函数后的下一条指定的地址,由此我们判断这一步执行的操作时将程序执行完后的下一条指令地址入栈
    在这里插入图片描述
  3. F10继续执行来到代码区,首先将四个寄存器的值入栈保护现场,当函数执行完的时候再出栈,这也就是为什么4.3步中ebp的值不变的原因,然后mov ebp,esp sub esp,44h是为add函数开辟一段大小为44h的栈帧,ebp是栈帧的基址,esp为栈顶指针,这里add函数与main函数的栈帧是连续的,esp是main函数的栈顶指针所以可以将esp的值直接赋给ebp作为add函数栈帧的基址
    在这里插入图片描述
  4. 接下来的四条语句为初始化栈帧的操作,ebp-44h为esp的值,ecx通常用来计数,下面还有一条rep循环指令,这四条语句最终的效果就是将add栈帧的值全都初始化为CCh
    在这里插入图片描述
  5. 接下来分析程序执行时的三段语句
    第一段语句是局部变量的声明,ebp为add函数栈帧的基址,所以直接ebp-4的地址分给变量z
    第二段语句中[ebp+8]和[ebp+0ch]存储的都是最开始入栈的函数参数的值,这一步的结果存在eax中,然后将eax的值返回给[ebp-4]也就是变量z(ebp+和参数相关,ebp-和局部变量相关)
    第三段语句因为函数的返回值通常保存在eax寄存器中,而这个函数返回的是变量z,所以将变量z的值赋给eax
    在这里插入图片描述
  6. 最后一部分为恢复现场,与函数最开始保护现场的部分一一对应,ebp的值赋给esp
    在这里插入图片描述

附录

lea指令的用法
rep和stos指令的用法

猜你喜欢

转载自blog.csdn.net/shn111/article/details/124615180
今日推荐