函数调用过程探究

栈与栈帧

在操作系统中,栈(Stack)是一种用于管理函数调用和局部变量的数据结构。它采用后进先出(Last-In-First-Out,LIFO)的原则,类似于我们日常生活中的堆叠物品的方式。栈通常是在内存中分配一块连续的空间,用于存储函数调用时的临时数据。

栈帧(Stack Frame)是栈中的一个重要概念,也被称为活动记录(Activation Record)或者帧(Frame)。每当一个函数被调用时,就会在栈上创建一个新的栈帧,用于存储该函数的局部变量、函数参数、返回地址等信息。

栈空间由高地址往低地址增长。

当一个函数被调用时,操作系统会为该函数分配一块新的栈帧,并将其推入栈顶。栈帧包含以下几个重要的部分:

  1. 局部变量区域:用于存储函数内部定义的局部变量。这些变量在函数执行期间被使用,并在函数返回后自动释放。
  2. 函数参数:用于存储函数调用时传递给函数的参数。这些参数在函数内部可以被访问和使用。
  3. 返回地址:指向函数调用者的下一条指令地址,用于在函数执行完毕后返回到正确的位置继续执行。
  4. 上一个栈帧指针:指向调用当前函数的上一个栈帧的地址,用于函数返回后恢复调用者的上下文。

当函数执行完毕后,当前的栈帧会被销毁,栈指针会回退到上一个栈帧,继续执行调用者函数的剩余部分。

栈的使用使得函数调用变得简单和高效,因为每个函数都有自己的独立空间来存储局部变量和参数,避免了相互之间的干扰。栈帧的概念则提供了一种有效的方式来组织和管理函数调用过程中的数据。

寄存器

常见寄存器如下,一般32位寄存器以%e开头如%eax,64位寄存器以%r开头,如%rax。

  •   sp 栈顶指针
  •   bp 栈帧基指针
  •   di
  •   si
  •   ax 通常存放函数返回值
  •   bx
  •   cx
  •   ip 要执行指令的地址

除了这些之外还有很多通用寄存器以及特定用途寄存器,就不在这里一一列出了。

函数调用约定

函数调用约定主要约定的以下三点:

  • 函数参数是如何传递的
  • 堆栈清理由谁操作的
  • 返回值是如何返回的

返回值:默认以ax寄存器保存返回值

堆栈清理与参数传递:

根据微软的文档,x86-32bit平台不同的函数调用规范如下:

关键字

堆栈清理

参数传递

__cdecl

调用方

在堆栈上按相反顺序推送参数(从右到左)

__clrcall

不适用

按顺序将参数加载到 CLR 表达式堆栈上(从左到右)。

__stdcall

被调用方

在堆栈上按相反顺序推送参数(从右到左)

__fastcall

被调用方

存储在寄存器中,然后在堆栈上推送

__thiscall

被调用方

在堆栈上推送;存储在 ECX 中的 this 指针

__vectorcall

被调用方

存储在寄存器中,然后按相反顺序在堆栈上推送(从右到左)

其中__cdecl为C/C++的默认调用约定,__stdcall为win32特有。

x64 应用程序 使用类似 fastcall x64调用约定。 系统在调用堆栈上分配空间作为影子存储,供被调用方保存这些寄存器。详情参见x64调用约定

示例

C

int add(int a, int b){

    return a+b;

}

int fun(){

    int a=1;

    int b=2;

    int sum = add(a,b);

    sum+=1;

    return sum;

}

ASM from Compiler Expoler linux-x64-gcc

 

add(int, int):

  pushq %rbp

  movq %rsp, %rbp

  movl %edi, -4(%rbp) //从寄存器接收参数

  movl %esi, -8(%rbp) //从寄存器接收参数

  movl -4(%rbp), %edx

  movl -8(%rbp), %eax

  addl %edx, %eax

  popq %rbp

  ret

fun():

  pushq %rbp //保存上个函数栈帧的栈帧基指针

  movq %rsp, %rbp

  subq $16, %rsp

  movl $1, -4(%rbp) //局部变量

  movl $2, -8(%rbp) //局部变量

  movl -8(%rbp), %edx

  movl -4(%rbp), %eax

  movl %edx, %esi //传递参数到寄存器

  movl %eax, %edi //传递参数到寄存器

  call add(int, int) // 将函数返回地址保存并跳转

  movl %eax, -12(%rbp)

  addl $1, -12(%rbp)

  movl -12(%rbp), %eax //将函数返回值保存在eax中

  leave //相当于 movq %rbp, %rsp 和 popq %rbp;恢复上个函数的rpb和rsp指针

  ret //相当于pop %rip; 恢复到调用者函数的剩余部分继续执行

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值