AArch64 调用约定
平台:
qemu-aarch64;aarch64-linux-gnu-gcc -O1 -g -fno-inline -static(一份加-fomit-frame-pointer,一份保留x29)
工具:gdbsp;JSON/gdbsp/aarch64-add1-nofp.json(37 步)、/gdbsp/aarch64-add1-fp.json(39 步),都从main到exit
本文:对着下一条要执行的指令,看通用寄存器和$sp附近每个字
配套代码:code/arm64-call-stack/
目录
AAPCS64 里,前八个整数参数放在 x0~x7(int 用对应的 wN),再多出来的由调用者写到栈上,每个槽 8 字节,即使类型是 4 字节的 int。RV32 ilp32 同样把第 9、10 个参数放在 $sp 上,槽宽是 4 字节。add2 有十个参数,进函数时 0(sp)、8(sp) 就是 9 和 10。
1. 对应的 C
int add2(int a, int b, int c, int d, int e, int f, int g, int h, int i, int j) {
return a + b + c + d + e + f + g + h + i + j;
}
int add1(int a, int b) {
return add2(a, b, 3, 4, 5, 6, 7, 8, 9, 10);
}
int main(int argc, char *argv[]) {
volatile int local = 0xffff;
return add1(1, 2);
}add1 还要再 bl add2,得先把返回地址从 x30 存到栈上。add2 是叶函数,进来之后直接用寄存器和 ldr 做加法。
2. 单步回放
| 编译 | 步数 | 回放 |
|---|---|---|
-fomit-frame-pointer | 37 | 无帧指针 |
保留 x29 | 39 | 有帧指针 |
3. 如何做栈回溯
有帧指针
AArch64 的帧指针是 x29。非叶函数序言会把旧的 x29 和返回地址 x30 成对存进栈,再让 x29 对准这对值。有如下规则:
[x29] = 上一层 x29,[x29, #8] = 本函数的 x30。
高地址
[x29+8] 本函数的 x30
[x29] 上一层 x29 ← x29 寄存器指这里
…
低地址这对 16 字节内部永远是「x30 在上、旧 x29 在下」。省略号只表示地址更低的方向。
顺着 [x29] 走到 0 就是整条链。
无帧指针
只能根据序言里面保存的 x30 函数返回地址来做栈回溯。
栈帧示意图
示意地址从 168 往下每格 8 字节;红色是 main 这一帧,绿色是 add1 这一帧。
x30main 保存的 x30 返回地址136x29main 保存的 x29;main 的 x29 指向这里128x30add1 保存的 x30 返回地址120136add1 保存的 x29;add1 的 x29 指向这里11210传给 add2 的第 10 个参数1049传给 add2 的第 9 个参数,当时的 spx30main 保存的 x30 返回地址128120x30add1 保存的 x30 返回地址11210传给 add2 的第 10 个参数1049传给 add2 的第 9 个参数,当时的 sp有 x29 时,120 这一槽里的 136 接到 main 的帧记录,再沿 [x29] 走到 0。无 x29 时 main 的 x30 在自己帧底(136),add1 的 x30 在 [sp,#16](120);128 是 sub sp, #0x20 多出来的未用槽。
4. 局部变量如何传递
aarch64 用 w0~w7 传递整数参数,超出的由调用者写到栈上。
5. 返回值如何传递
aarch64 默认使用 w0 传递函数返回值。