Skip to content

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 ​

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-pointer37无帧指针
保留 x2939有帧指针

3. 如何做栈回溯 ​

有帧指针 ​

AArch64 的帧指针是 x29。非叶函数序言会把旧的 x29 和返回地址 x30 成对存进栈,再让 x29 对准这对值。有如下规则:

[x29] = 上一层 x29,[x29, #8] = 本函数的 x30。

text
高地址
[x29+8]   本函数的 x30
[x29]     上一层 x29      ← x29 寄存器指这里
…
低地址

这对 16 字节内部永远是「x30 在上、旧 x29 在下」。省略号只表示地址更低的方向。

顺着 [x29] 走到 0 就是整条链。

无帧指针 ​

只能根据序言里面保存的 x30 函数返回地址来做栈回溯。

栈帧示意图 ​

示意地址从 168 往下每格 8 字节;红色是 main 这一帧,绿色是 add1 这一帧。

有帧指针
168main 入口时的 sp1600x0000ffff00000000main 的局部变量 local152144x30main 保存的 x30 返回地址136x29main 保存的 x29;main 的 x29 指向这里128x30add1 保存的 x30 返回地址120136add1 保存的 x29;add1 的 x29 指向这里11210传给 add2 的第 10 个参数1049传给 add2 的第 9 个参数,当时的 sp
无帧指针
168main 入口时的 sp1600x0000ffff00000000main 的局部变量 local152144136x30main 保存的 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 传递函数返回值。

基于 VitePress 构建