函数栈帧(上)

简介: 函数栈帧(上)

一 函数栈帧的创建及销毁


1 什么是函数栈帧?


1 什么是函数?

函数就是对于一个独立功能的抽象。

2 什么是函数栈帧?

函数栈帧是函数调用时在栈区中临时开辟的一块内存空间

3 这些空间的作用是什么?

存放函数参数和函数返回值

存放临时变量

保存上下文信息(寄存器)


2 理解函数栈帧能够解决什么问题呢?


1 局部变量是如何创建的?

2 为什么局部变量不初始化内容是随机的?

3 函数调用时参数时如何传递的?传参的顺序是怎样的?

4 函数的形参和实参分别是怎样实例化的?

5 函数的返回值是如何带会的?

这五个问题都会在文章的最后意义解答


3 函数栈帧的创建和销毁解析


1 什么是栈区?

就是那些由编译器在需要的时候分配,在不需要的时候自动清除变量的存储区

2 什么是压栈?什么是出栈?

压栈:向栈区中添加一个元素

出栈:从栈区中删除一个元素

3 栈区原则 先进后出


二 认识相关寄存器和汇编指令


1 预备知识


1 寄存器的概念:寄存器是CPU内部用来存放数据的一些小型存储区域,用来暂时存放参与运算的数据和运算结果

重点是 1 在cpu中

2 暂时存放参与运算的数据和运算结果


2 相关寄存器


eax:通用寄存器,保留临时数据,常用于返回值

ebx:通用寄存器,保留临时数据

ebp:栈底寄存器

esp:栈顶寄存器

eip:指令寄存器,保存当前指令的下一条指令的地址


这里我们暂时只需要记住ebp栈底寄存器 和esp栈顶寄存器就好


3 相关汇编命令


mov:数据转移指令

push:数据入栈,同时esp栈顶寄存器也要发生改变

pop:数据弹出至指定位置,同时esp栈顶寄存器也要发生改变

sub:减法命令

add:加法命令

call:函数调用,1. 压入返回地址 2. 转入目标函数

jump:通过修改eip,转入目标函数,进行调用

ret:恢复返回地址,压入eip,类似pop eip命令


4 预备知识


  1. 每一次函数调用,都要为本次函数调用开辟空间,就是函数栈帧的空间。
  2. 这块空间的维护是使用了2个寄存器: esp和 ebp ebp记录的是栈底的地址esp记录的是栈顶的地址。

如下图所示:


9221dce2290b4a6e8b04928dce0368c9.png


函数调用堆栈演示代码:


#include <stdio.h>
int Add(int x, int y)
{
int z = 0;
z = x + y;
return z;
}
int main()
{
int a = 3;
int b = 5;
int ret = 0;
ret = Add(a, b);
printf("%d\n", ret);
return 0;
}


2 准备环境


这段代码,如果我们在VS2019编译器上调试,调试进入Add函数后,我们就可以观察到函数的调用堆栈 (右击勾选【显示外部代码】),如下图:


d63277fa3f724b0bb43065a0194608e5.png

函数调用堆栈是反馈函数调用逻辑的,那我们可以清晰的观察到, main 函数调用之前,是由 invoke_main 函数来调用main函数。

在 invoke_main 函数之前的函数调用我们就暂时不考虑了。


那我们可以确定, invoke_main 函数应该会有自己的栈帧, main 函数和 Add 函数也会维护自己的栈 帧,每个函数栈帧都有自己的 ebp和 esp来维护栈帧空间。

那接下来我们从main函数的栈帧创建开始讲解


为了让我们研究函数栈帧的过程足够清晰,不要太多干扰,我们可以关闭下面的选项,让汇编代码中排 除一些编译器附加的代码:


35e17e90fb364f8fbaaf9d5f635d7dee.png


转到反汇编


int main()
{
//函数栈帧的创建
00BE1820  push  ebp
00BE1821  mov ebp,esp
00BE1823  sub esp,0E4h
00BE1829  push  ebx
00BE182A  push  esi
00BE182B  push  edi
00BE182C  lea edi,[ebp-24h]
00BE182F  mov ecx,9
00BE1834  mov eax,0CCCCCCCCh
00BE1839  rep stos  dword ptr es:[edi]
//main函数中的核心代码
int a = 3;
00BE183B  mov         dword ptr [ebp-8],3
int b = 5;
00BE1842  mov         dword ptr [ebp-14h],5
int ret = 0;
00BE1849  mov         dword ptr [ebp-20h],0
ret = Add(a, b);
00BE1850  mov         eax,dword ptr [ebp-14h]
00BE1853  push        eax
00BE1854  mov         ecx,dword ptr [ebp-8]
00BE1857  push        ecx
00BE1858  call        00BE10B4
00BE185D  add         esp,8
00BE1860  mov         dword ptr [ebp-20h],eax
printf("%d\n", ret);
00BE1863  mov         eax,dword ptr [ebp-20h]
00BE1866  push        eax
00BE1867  push        0BE7B30h
00BE186C  call        00BE10D2
00BE1871  add         esp,8
return 0;
00BE1874  xor         eax,eax
}
相关文章
|
分布式计算 分布式数据库 Spark
17张图带你彻底理解Hudi Upsert原理
17张图带你彻底理解Hudi Upsert原理
1112 1
|
Java Sentinel
【熔断限流组件resilience4j和hystrix】
【熔断限流组件resilience4j和hystrix】
567 0
用户态和内核态是如何切换的?
【10月更文挑战第28天】用户态和内核态的切换是通过系统调用指令、异常和中断等机制来实现的。这些机制确保了应用程序能够在需要时请求内核提供的服务,同时也保证了内核能够对系统资源进行有效的管理和保护,维护系统的稳定性和安全性。通过准确地保存和恢复上下文信息,实现了用户态和内核态之间的无缝切换,为计算机系统的正常运行提供了有力保障。
|
Java API 开发工具
【Azure Developer】使用 Microsoft Authentication Libraries (MSAL) 如何来获取Token呢 (通过用户名和密码方式获取Access Token)
【Azure Developer】使用 Microsoft Authentication Libraries (MSAL) 如何来获取Token呢 (通过用户名和密码方式获取Access Token)
437 0
|
NoSQL Linux
看懂GDB调试核心:剖析ptrace原理及其应用场景!(上)
看懂GDB调试核心:剖析ptrace原理及其应用场景!
|
Unix Linux
linux中在进程之间传递文件描述符的实现方式
linux中在进程之间传递文件描述符的实现方式
|
JavaScript 前端开发 Java
什么是深拷贝,什么是浅拷贝
什么是深拷贝,什么是浅拷贝
319 0
|
Ubuntu
./autogen.sh: 5: ./autogen.sh: autoreconf: not found
./autogen.sh: 5: ./autogen.sh: autoreconf: not found
605 1
|
开发框架 编解码 移动开发
从网页到应用:探索在线生成App的方法
本文介绍了如何在线生成App,将网页封装成App的技术方法和步骤。通过使用特定的工具和框架,开发者可以将现有的网页转化为功能完善的移动应用程序,提供更好的用户体验和便捷的访问方式。
483 0

热门文章

最新文章