Skip to content

关于函数栈帧 StackFrame

Published: at 20:54Suggest Changes

Table of contents

Open Table of contents

关于函数栈帧 StackFrame

在 rCore 文档中对函数栈帧的解读中有两处不好理解,rafp.

首先可以先考虑一个进程的内存,应该有内核栈、用户栈、代码库、用户堆、数据和代码等等组成(参考《现代操作系统:原理与实现》)。如果问AI这个问题或者是网络上的blog,会说由代码段、数据段、BSS段、堆和栈组成。

内存区域存储内容权限动态性
代码段可执行指令(.text 段)只读+执行静态
数据段已初始化的全局/静态变量(.data可读+写静态
BSS 段未初始化的全局/静态变量(.bss可读+写静态(清零)
堆(Heap)动态分配的内存(malloc 等)可读+写动态增长
栈(Stack)函数调用栈帧、局部变量可读+写动态增长

函数的栈帧保存在区域中,执行的代码保存在代码段中。

关于一个栈帧包含什么,可以见函数调用过程中的栈帧与内存管理一篇。一个栈从高地址向低地址延申,fp 表示栈底所在的位置(高地址,Frame Pointer),sp 表示栈顶(低地址,Stack Pointer)。在32位的x86架构上这两个值分别用 ebpesp 表示,64位上是 rbprsp.

不妨使用一个简单的代码来探究。根据函数调用的内存对齐规则,故有sub rsp, 8一句。

操作对 rsp 的影响对齐状态
call mainrsp -= 8(压入返回地址)未对齐(rsp%16=8
sub rsp, 8rsp -= 8(主动调整)对齐(rsp%16=0
call printfrsp -= 8(压入返回地址)未对齐(rsp%16=8
add rsp, 8rsp += 8(恢复调整)对齐(rsp%16=0

li a1, -16 的作用是 将立即数 -16 加载到寄存器 a1 中,其他的指令可以参考运算与控制流指令When to use j, jal, Jr, jalr?.

注意RISC-V是小端序, 高位是 %hi(),低位 %lo().

说回 rafp 的作用。ra 字面意思是返回地址,指的是当前函数执行完毕后应返回的地址,在函数返回前要从栈中加载 ra,然后通过 ret/jr 跳转回调用者。也就是说 ra 是之前被调用处的一个中断的地址位置。fp (preview fp) 是父函数栈帧的帧指针,也就是要恢复的父函数栈帧基址。

ra 主要指的是函数调用,fp 是栈帧。

假设函数 A 调用函数 B

  1. 进入 B 时

    • B 的栈帧保存 A 的 ra(返回地址)和 A 的 fp(父帧指针)。

    • B 的 fp 被设置为当前栈帧的基址(如 sp + offset)。

  2. 退出 B 时

    • 从栈中恢复 A 的 ra 到 ra 寄存器,恢复 A 的 fp 到 fp 寄存器。

    • 调整 sp 释放 B 的栈空间,通过 ret 跳转回 A 继续执行。


Previous Post
rCoreloongArch 学习笔记