C函数调用原理及函数栈帧分析

时间:2022-09-24 03:38:38

之前在看关于内联,宏和普通函数调用的时候想到了这个问题,感觉自己对函数调用底层的过程原理还是不太清楚,特意查了相关资料,这里分析记录下。

1、基础知识

1.1 基本概念

每个进程对应一个Call Stack(调用栈),而CallStack 又由许多Stack Frame(栈帧)组成,对应每个未完成的函数调用,由系统分配。

在多线程(任务)环境,CPU的栈指针指向的存储器区域就是当前使用的栈。切换线程的一个重要工作,就是将栈指针设为当前线程的栈栈顶地址。

栈是从高地址向低地址延伸的。每个函数的每次调用,都有它自己独立的一个栈帧,这个栈帧中维持着所需要的各种信息。寄存器ebp指向当前的栈帧的底部(高地址),寄存器esp指向当前的栈帧的顶部(地址地)。

函数调用时,需要准备好堆栈,而堆栈的作用就是:保存现场/上下文,传递参数。

具体来说:

1)保存寄存器的值,一般用的是push指令,将对应的某些寄存器的值,一个个放到堆栈中,把对应的值压入到堆栈里面,即所谓的压栈

然后待被调用的子函数执行完毕的时候,再调用pop,把堆栈中的一个个的值,赋值给对应的那些你刚开始压栈时用到的寄存器,把对应的值从堆栈中弹出去,即所谓的出栈

2)一种情况是,本身传递的参数就很少,就可以通过寄存器传送参数。

但是如果参数太多,寄存器不够用,那么就得把多余的参数堆栈中了。

1.2  esp,ebp,eip


1.EIP寄存器里存储的是CPU下次要执行的指令的地址 也就是调用完fun函数后,让CPU知道应该执行main函数中的printf("函数调用结束")语句了。 2.EBP寄存器里存储的是是栈的栈底指针,通常叫栈基址,这个是一开始进行fun()函数调用之前,由ESP传递给EBP的。(在函数调用前你可以这么理解:ESP存储的是栈顶地址,也是栈底地址。) 3.ESP寄存器里存储的是在调用函数fun()之后,栈的栈顶。并且始终指向栈顶。

堆栈是一种简单的数据结构,是一种只允许在其一端进行插入或删除的线性表。
允许插入或删除操作的一端称为栈顶,另一端称为栈底,对堆栈的插入和删除操作被称入栈出栈

有一组CPU指令可以实现对进程的内存实现堆栈访问。其中,POP指令实现出栈操作,PUSH指令实现入栈操作。

CPU的ESP寄存器存放当前线程的栈顶指针

EBP寄存器中保存当前线程的栈底指针

CPU的EIP寄存器存放下一个CPU指令存放的内存地址,当CPU执行完当前的指令后,从EIP寄存器中读取下一条指令的内存地址,然后继续执行。

esp是堆栈指针 
ebp是基址指

esp始终指向栈顶,ebp是在堆栈中寻址用的