ARM AAPCS64
Arm64 aapcs解析之寄存器
基础概念
1.AAPCS (Procedure Call Standard for the Arm® Architecture)
Register | Special | Role in the procedure call standard |
---|---|---|
SP | The Stack Pointer. 堆栈指针 |
|
r30 | LR | The Link Register. 链接寄存器 |
r29 | FP | The Frame Pointer 堆栈指针寄存器 |
r19…r28 | Callee-saved registers 被调用方保存寄存器。调用方随意使用 |
|
r18 | The Platform Register, if needed; otherwise a temporary register. See notes. 平台寄存器 |
|
r17 | IP1 | The second intra-procedure-call temporary register (can be used by call veneers and PLT code); at other times may be used as a temporary register. 链接寄存器,非链接阶段随意使用 |
r16 | IP0 | The first intra-procedure-call scratch register (can be used by call veneers and PLT code); at other times may be used as a temporary register. 链接寄存器,非链接阶段随意使用 |
r9…r15 | Temporary registers 调用方保存,被调用方随意使用。 |
|
r8 | Indirect result location register 非直接寄存器,当返回值>=16字节,通过x8传递返回值。 |
|
r0…r7 | Parameter/result registers 用于传参 & 返回值,调用方和被调用方均不需要为其保存数据, |
分类
类别 | 寄存器示例 | 数量 | 位宽 | 主要用途 |
---|---|---|---|---|
通用寄存器 | X0 -X30 , XZR |
31 + 1 | 64/32 位 | 整型数据、参数传递、返回值 |
浮点/向量寄存器 | V0 -V31 (S0 /D0 ) |
32 | 128 位 | 浮点运算、SIMD 并行操作 |
特殊寄存器 | SP , PC , PSTATE |
若干 | 64/混合 | 栈管理、指令指针、状态控制 |
系统寄存器 | SCTLR_EL1 , TTBR0 |
数百 | 64/32 位 | 系统配置(MMU、中断、计时等) |
通用寄存器
x0 ~ x7
用于传参/返回数值
- Demo验证分析-Demo Link
- 传参:可见下方参数a1 ~ a8分别对应于x0
x7(图中写入的w0w7效果等价使用x0 ~ x7,原因是w0w7与x0x7共用同一个寄存器,区别在于位宽不一样) - 返回:可见下方test函数,将false返回值写入到x0中。
x8
用作返回大对象>=16字节,假如一个函数的返回值的大小大于16字节(Arm64下Vn向量寄存器的最大位宽为128),那么调用方需要传入一个x8寄存器,其中x8是函数返回值存放的地址。
其中x8通常是指向一块堆栈内存。具体逻辑可见下方。
- Demo验证
- 逻辑解释
可以发现main函数调用了testX8寄存器
其中testX8的返回值为20个字节,超过了最大的返回长度,因此需要通过x8寄存器做中转、
具体逻辑为:调用者需要开辟内存空间并将内存首地址写入到x8中,被调用者会将内存写入到x8下的内存中。
1 | main: |
x9 ~ x15
调用者保存的寄存器,对于被调用者可以认为是临时寄存器,随便用~不需要恢复到以前的状态值~
- Demo分析
如下方Demo可见testX9内会调用testX8方法。其中testX9中i < x, j < y 中的x,y均是临时存储在x9中。在调用testX8,方法体内部会修改x9的值,但并不会对程序造成任何影响。因为textX8作为被调用者可以任意使用x9~x15而作为调用者的testX9内部保存了x9的值,因此综合来看逻辑无任何异常。
x16 ~ x18
寄存器 | 用途 |
---|---|
x16 (IP0) |
过程间调用临时寄存器,动态链接或长跳转时专用; 否则为普通临时寄存器。 |
x17 (IP1) |
同上。 |
x18 |
由操作系统或运行时环境(如 Linux、iOS、Android 等)保留使用。 若平台未明确使用 x18 ,则可作为临时寄存器(调用方保存)。 |
- x16, x17
从前面的列表可得知这两个寄存器和动态链接相关。但是没有详细说明x16,x17的具体含义。下面根据一个demo简单分析两个寄存器的作用
1.demo源码
1 | // test.cpp |
2.反汇编结果
1 | 000000000001024c <_Z7testPltv>: |
- x18
平台寄存器,即不同平台用处可能不一样以Android平台为例Android ABIs
On Android, the platform-specific x18 register is reserved for ShadowCallStack and should not be touched by your code. Current versions of Clang default to using the
-ffixed-x18
option on Android, so unless you have hand-written assembler (or a very old compiler) you shouldn’t need to worry about this.ShadowCallStack(影子调用栈) 是 Android 引入的一种安全机制,用于防御 ROP(Return-Oriented Programming)攻击。其核心原理是:
- 备份返回地址:将函数的返回地址存储在一个独立的、受保护的 影子栈(Shadow Stack)中,而非传统调用栈。
- 运行时校验:在函数返回时,比较调用栈中的返回地址和影子栈中备份的地址,若不一致则触发安全异常。
默认情况下Android Clang 编译器会启用 -ffixed-x18 强制编译器避免使用 x18 寄存器,确保其专用于 ShadowCallStack。
x19~x28
被调用者保存寄存器。简单来说就是在使用这些寄存器以前需要对内容做保存,使用完后需要恢复。因为调用者可能存储了一些中间计算结果。
可以发现编译器会在使用X19~X29以前保存寄存器。
X29~X30
寄存器 | 功能 |
---|---|
X29 | 栈帧基地址(调试和变量定位) |
X30 | 返回地址(函数调用流程控制) |
- X29
函数在刚开始执行时,会将调用方的x29,x30寄存器进行保存。在访问和存入局部变量的时候会通过x29进行访问(此处局部变量为return)
- X30
在函数执行前会将X30的值进行保存。在执行完毕以后会恢复该值。其中X30的寄存器的作用如下:
函数调用过程中会调用bl指令进行跳转,但是于此同时会记录函数执行完毕以后的返回地址(当前执行的下一条指令地址)到X30中。当函数执行完毕以后会调用return指令返回上一个函数的执行此时会读取X30的值并将程序执行跳转到X30指向地址(此时不会修改X30的值)。
SP
堆栈指针,用于存放数据。
可以将其当成普通的寄存器进行内存空间的开辟和释放。由于堆栈的内存是反向增长的,因此可以通过对sp进行减法操作开辟内存,通过加法操作释放内存。
其他
为什么需要AAPCS规范?
The goals of the AAPCS64 are to:
- Support efficient execution on high-performance implementations of the Arm 64-bit Architecture.
- Clearly distinguish between mandatory requirements and implementation discretion.
其实核心就是支持在Arm64架构下高效执行应用程序。(前提是需要遵循规范。)
规范并不是强制的,我可以不遵守吗?
可以,比如如果你是手写汇编,你可以自行处置寄存器,你自己进行寄存器职责的划分。
但是如果不是有特殊的需求,建议还是遵循,毕竟长期以来的规范,经过时间的洗礼,性能稳定性都有一定的考量。
为什么需要那么多寄存器?
寄存器是CPU内部的元器件,当然也属于内存的范畴,而内存是有延迟的,就像金字塔一样Wikipedia
大多内存的延迟相比于CPU的执行速度来讲,都过于缓慢,然而寄存器处于存储金字塔的顶端,能满足CPU的高速计算。
因此更多的寄存器会显著提高程序的执行性能,同时更多的寄存器也意味着更高的造价。