本文主要介绍Armv8-A架构的栈操作,涉及AArch64执行状态的关键寄存器、栈操作的ARM64汇编指令和一些编程约定。函数调用必然涉及到 出栈和入栈,理解了ARM64的栈操作,对更深入理解C语言的函数调用大有裨益;也能够协助debug一些诡异的Bug。
AArch64寄存器
通用寄存器
Armv8-A提供了31个64-bit通用寄存器,在所有的异常级别都可以访问。
在AArch64
执行状态,每个寄存器(X0-X30)
都是64位宽的。每个64-bit通用寄存器(X0-X30) 都有一个32-bit形式(W0-W30)
。32-bit的W寄存器是对应64-bit X 寄存器的低32位。 也就是说,W0组成X0的低32位,W1组成X1的低32位。如下图所示:
X0 ~ X30
:访问寄存器的全部64位W0 ~ W30
:访问寄存器的低32位- 从W寄存器读取会忽略对应X寄存器的高32位,并且保持它们不变
- 向W寄存器写入会讲X寄存器的高32位设置为0;所以写入
0xFFFFFFFF
到W0会将X0的值设置为0x00000000FFFFFFFF
特殊寄存器
除了31个通用寄存器X0~X30, Armv8还提供了几个特殊寄存器。
Name | Size | Description |
---|---|---|
WZR | 32 bits | 32-bit Zero register |
XZR | 64 bits | 64-bit Zero register |
WSP | 32 bits | 32-bit Current stack pointer |
SP | 64 bits | 64-bit Current stack pointer |
PC | 64 bits | Program counter |
特别说明以下寄存器,这些寄存器是完成栈操作的关键寄存器。
SP
:Stack Pointer,栈顶寄存器,保存当前栈顶地址;FP(X29)
:Frame Pointer,栈基址寄存器,保存当前栈底地址;使用X29
寄存器;LR(X30)
:Link Register,保存跳转指令 bl 后面的下一条指令的地址;使用X30
寄存器;PC
:保存将要执行的下一条指令的地址,不允许直接访问;
当在AArch64状态执行时,异常返回状态保存在以下专用寄存器中:
- Exception Link Register(ELR):异常链接寄存器,保存在异常返回后要执行的指令地址;
- Saved Processor State Register(SPSR):异常发生时,保存处理器执行状态;
每个异常级别都有各自的专用寄存器,如下表所示:
Registers | EL0 | EL1 | EL2 | EL3 |
---|---|---|---|---|
SP | SP_EL0 | SP_EL1 | SP_EL2 | SP_EL3 |
ELR | ELR_EL1 | ELR_EL2 | ELR_EL3 | |
SPSR | SPSR_EL1 | SPSR_EL2 | SPSR_EL3 |
AArch64过程调用标准
ARM规定了过程调用的标准,称作 Procedure Call Standard for the ARM 64-bit Architecture(AArch64),即AAPCS64。AAPCS64对一些寄存器在过程调用中扮演的角色进行了约定,且看下面的表格。
Register | Special | Role in the procedure call standard |
---|---|---|
SP | Stack Pointer,栈顶指针 | |
R30 | LR | Link Register,过程调用返回后要执行的指令地址 |
R29 | FP | Frame Pointer,栈帧指针,即栈底指针 |
R19…R28 | Callee-saved registers,由被调用者保存 | |
R18 | Platform Register;否则作为临时寄存器 | |
R17 | IP1 | |
R16 | IP0 | |
R9…R15 | 临时寄存器 | |
R8 | Indirect result location register,保存非直接返回结果的内存地址 | |
R0…R7 | Parameter registers,存放过程调用的前8个参数 | |
R0 | Result register,保存过程的直接返回值 |
注:这里的R泛指寄存器,包括X和W。
X0 ~ X7
分别用于存放方法调用的前 8 个参数;如果参数超过 8 个,多余的参数需要通过栈来传递;X0
一般用于保存方法的直接返回值;- 如果方法返回一个大的数据结构,则将数据结构的地址保存在
X8
;