6.4. 函数栈布局

LoongArch ABI规定,函数栈向下增长,也就是朝更低地址方向扩展。函数入口处,栈指针应对齐到16字节边界;通过栈传递的第一个实参位于函数入口栈指针偏移量为0的位置,后续参数存放在更高地址。函数栈在程序运行期间动态分配,用于保存函数调用过程中需要维护的信息,包括返回地址、参数、临时变量和栈位置信息等。典型函数栈如图5-14所示。

典型的函数栈

图5-14中,GPR表示通用寄存器,即LoongArch中的32个整数寄存器;GPR[sp]表示通用寄存器sp。每次进入函数时,sp都会在预先指定的内存区域中从高地址向低地址移动n字节,以分配函数栈空间。这里要求n必须是16的倍数。典型的栈空间分配指令如下:

addi.d 	$sp, $sp, -48

这条加法指令等价于C语言表达式sp = sp - 48,表示申请48字节栈空间。栈空间分配指令通常出现在函数开头;函数返回时,再将sp向高地址移动48字节释放该空间:

addi.d 	$sp, $sp, 48

函数栈空间分配后,如果函数内部寄存器不够用,或需要发生函数调用,就可以把部分数据保存到栈空间中,这通常称为进栈;需要使用时,再从栈空间加载回寄存器,这通常称为出栈。

栈空间按进程分配。进程是系统进行资源分配和调度的基本单位。系统在进程启动时会指定一块固定大小的栈空间,用于保存该进程的函数参数和局部变量。sp的初始值指向这块固定栈空间的栈底。进程中的每一次函数调用,都会通过移动sp指针在该空间中划分出一块区域作为函数栈,sp指向栈顶。函数栈也称为栈帧(Stack Frame),通用寄存器fp(r22)指向当前函数栈的栈底。fp也称为帧指针(Frame Pointer)。sp和fp共同限定每个函数的栈边界,如图5-15所示。

进程栈数栈

图5-15中,每个函数栈内部的fp都指向调用者函数的栈顶。当需要回溯函数调用关系或进行动态栈管理时,可以通过当前函数中的sp和fp得到上一个函数的sp和fp,并沿调用链继续回溯,直到第一个函数。