飞腾的函数调用GCC分析
1. 堆栈和栈帧
64位的通用寄存器虽然可以随意被使用;但是针对函数调用,GCC编译器对通用寄存器的使用要符合下表的软件约定。
寄存器 | 说明 |
---|---|
X0…X7 | 函数调用参数,其中X0也用于函数返回值 |
X8 | 保留 |
X9…X15 | 临时寄存器,使用时不保存到栈帧中 |
X16…X18 | 保留 |
X19…X28 | 临时寄存器,使用时保存到栈帧中 |
X29 | 栈帧寄存器FP,使用时需要保存 |
X30 | 链接寄存器LR,保存函数返回地址 |
X31 | 零寄存器XZR,该寄存器并不真正存在,WZR是对应的32位 |
注:如果是32/16/8位的变量,就使用Wn寄存器形式。
C语言程序的函数动态调用的关系体现在堆栈内的栈帧关系上,参见下图。
- 每个函数最开始时会创建自己的栈帧区,在函数退出之前会撤销栈帧区。每个还没有退出的函数,在堆栈中都有对应的栈帧;当函数退出之后,对应的栈帧也就不存在。
- 调用函数的栈帧和被调函数的栈帧在堆栈里是上下紧挨布局。
- 堆栈寄存器SP指向当前正在运行的函数栈帧起始地址。
标准栈帧包括本地局部参数区、函数输入参数区、X19-X28寄存器保存区、64位返回地址和64位前一个栈帧地址。栈帧的长度都是C语言编译器GCC静态计算
的。
输入参数区、本地变量区、或者X19-X28寄存器变量区,属于可选的;即栈帧中相应的区依赖于函数实现的具体情况。例如,函数实现中用到X19-X28临时寄存器时,那么该函数就会提前将用到的临时寄存器保存到栈帧区中,函数返回前恢复X19-X28临时寄存器的值。
标准栈帧对应的函数实现,会调用其他子函数;当函数实现中没有调用其他子函数时,对应的栈帧就不用保存64位返回地址和64位前一个栈帧地址,直接用寄存器X30和X29。
C语言标准函数执行的第一步是先把链接寄存器X30和X29保存在当前栈帧的底部,同时将堆栈寄存器SP都指向当前栈帧底部;然后将Xn/Wn(n=0/1/…)依次保存到函数输入参数区。在函数返回之前,先将返回值X0/W0准备好,再将前一栈帧地址和函数返回地址读取到寄存器X29和X30,最后执行ret返回指令。