一、ARMv8寄存器介绍
本文我来给大家介绍一下ARMv8的寄存器部分,ARMv8中有34个寄存器,包括31个通用寄存器、一个栈指针寄存器SP(X31),一个程序计数器寄存器PC,一个处理器状态寄存器PSTATE(在ARMv7架构中使用程序状态寄存器(Current Program Status Register,CPSR)来表示当前的处理器状态(processor stste),而在ARMv8里使用PSTATE寄存器来表示)。
寄存器 | 位数 | 描述 |
---|---|---|
X0-X30 | 64bit | 通用寄存器,如果有需要可以当作32bit使用:W0-W30 |
FP(X29) | 64bit | 保存栈帧地址(栈底指针) |
LR(X30) | 64bit | 程序链接寄存器,保存子程序结束后需要执行的下一条指令 |
SP | 64bit | 保存栈顶指针,使用SP/WSP来进行对SP寄存器的访问。 |
PC | 64bit | 程序计数器,俗称PC指针,总是指向即将要执行的下一条指令,在arm64中,软件不能修改PC寄存器 |
PSTATE | 64bit | 状态寄存器,用于保存处理器的当前状态信息。 |
二、通用寄存器
AArch64执行状态支持31个64位的通用寄存器,分别是X0-X30寄存器,而AArch32状态支持16个32位的通用寄存器。除用于数据运算和存储之外,通用寄存器还可以在函数调用过程中起到特殊作用,ARM64体系结构的函数调用标准和规范对此有所约定,如下图所示。
- X0 - X7: 这8个寄存器通常用作函数参数寄存器,在函数调用时用来传递前8个参数,若参数个数大于8,就采用栈来传递。64位的函数返回值通常存放在X0寄存器中,128位的返回结果存在X0和X1两个寄存器中。
- X8: 间接结果位置寄存器,用于保存子函数的返回地址。在一些情况下,X8可以用于普通的临时寄存器,用于存储中间计算结果。
- X9 - X15: 通常被用作临时变量和中间计算结果的存储。调用者有责任在函数调用前保存它们的值,以免被覆盖。函数返回后,调用者也需要恢复这些寄存器的值。
- X16、X17: X16 (IP0, Intra-Procedure-call scratch register 0)这个寄存器通常被用作临时寄存器,用于存储函数内部的中间计算结果。它在函数调用过程中可能会被修改,所以调用者需要自行保存和恢复。X17与X16类似。
- X18: X18 (Platform register)这个寄存器通常被用作平台相关的寄存器,其用途取决于具体的硬件平台和软件环境。在某些系统中,X18 可能被用作过程链接表(PLT)指针,用于动态链接。在其他系统中,X18 可能被用作线程局部存储(Thread Local Storage)的指针。
- X19 - X28: 通常用于存储函数的局部变量和中间计算结果。被调用的函数有责任在返回前保存和恢复这些寄存器的值,确保调用者可以继续使用。
- X29: Frame Pointer (FP) 寄存器,它的主要作用是指向当前函数的栈帧(Stack Frame),方便访问函数内部的局部变量和参数。当一个函数被调用时,x29 寄存器会被设置为指向该函数的栈帧起始地址。这样可以通过 x29 寄存器轻松访问函数内部的局部变量和参数,而不需要依赖 x30 寄存器(Link Register)中存储的返回地址。
- X30: Link Register (LR),它的主要作用是在函数调用时存储函数的返回地址,以便函数执行完毕后能够正确地返回到调用点。当一个函数被调用时,CPU 会将当前执行点的地址保存到 x30 寄存器中。这样在函数执行完毕后,只需要从 x30 寄存器中恢复返回地址,就可以正确地返回到调用点。注意:当一个函数被调用时,CPU 会自动将当前执行点的地址(也就是函数调用语句的下一条指令地址)保存到 x30 寄存器中。
X9 - X15和X19 - X28这两组寄存器的区别:
x9 到 x15 则主要用作临时变量和中间计算结果的存储。x19 到 x28 通常用作函数的局部变量和中间计算结果的存储。对于 x9 到 x15 这些"caller-saved"寄存器,调用者(caller)有责任在函数调用前保存它们的值,并在调用后恢复。对于 x19 到 x28 这些"callee-saved"寄存器,被调用的函数(callee)有责任在返回前保存和恢复它们的值。使用"callee-saved"寄存器通常可以减少对栈的访问,提高性能。但同时也增加了函数调用时保存和恢复寄存器的开销。
在简单讲一下函数的局部变量和临时变量之间的区别:
作用域(Scope)不同: 局部变量(Local variables)是定义在函数内部的变量,它们的作用域仅限于该函数内部。临时变量(Temporary variables)通常是用于存储中间计算结果的变量,它们的作用域更加局限,可能只在某个代码块或表达式内部。
生命周期不同: 局部变量的生命周期与函数的执行周期相同,即在函数调用时创建,在函数返回时销毁。临时变量的生命周期则更短暂,通常仅在表达式或代码块的执行期间存在。
存储位置不同: 局部变量通常存储在函数的栈帧(Stack Frame)中,以便函数调用时能够访问。临时变量通常存储在寄存器或者栈上,以减少内存访问开销。
使用目的不同: 局部变量用于存储函数内部需要持久保存的数据,如参数、返回值、中间计算结果等。临时变量主要用于存储一些中间计算结果,在表达式或代码块结束后可以丢弃。
优化方式不同: 编译器通常会尽量将局部变量存储在寄存器中,以提高访问效率。对于临时变量,编译器则更加倾向于直接使用寄存器,避免不必要的内存访问。
什么是栈帧:
栈帧是函数调用时在内存栈上分配的一块区域,用于存储函数的局部变量和参数等信息。每当一个函数被调用时,CPU 会在内存栈上为该函数创建一个新的栈帧。栈帧通常包含以下几个部分:
返回地址: 当前函数返回时需要跳转的地址,通常是调用函数的下一条指令。返回地址通常是由调用指令自动压入栈的。
函数参数: 传递给当前函数的参数值,它们需要被保存在栈帧中。当函数返回时,这些参数值需要被恢复。
局部变量: 函数内部声明的局部变量,它们的生命周期仅限于函数的执行期间。这些变量需要被保存在栈帧中,以免被其他函数覆盖。
寄存器备份: 在函数调用过程中,需要保存一些重要的寄存器值,以便在返回时恢复。这些寄存器可能包括函数返回地址寄存器、帧指针寄存器等。
动态分配的内存: 函数在执行过程中动态分配的内存空间,需要在栈帧中保存相关信息。这些动态分配的内存会在函数返回时被自动释放。
X0到X30是31个通用整形寄存器。每个寄存器可以存取一个64位大小的数。当使用X0 - X30时,它是一个64位的数。当使用W0 - W30访问时,访问的是这些寄存器的低32位,如下图:
三、 PSTAE寄存器
在ARMv7架构中使用程序状态寄存器(Current Program Status Register,CPSR)来表示当前的处理器状态(processor stste),而在ARMv8里使用PSTATE寄存器来表示。下面我们来看一下AArch64中PSTATE各字段所代表的含义。
注意: PSTATE(process state)是一些状态位,一些位仅在aarch32 state下使用、一些位仅在aarch64 state下使用、一些位可以同时在aarch32/aarch64 state下使用;我们在这里仅分析AArch64。
分类 | 字段 | 描述 |
---|---|---|
条件标志位 | N(Negative) | 负数标志位。在结果是有符号的二进制补码的情况下,如果结果为负数,则N = 1;如果结果为非负数,则N = 0 |
条件标志位 | Z(Zero) | 0标志位。如果结果为0,则Z = 1;如果结果为非 0 ,则Z = 0 |
条件标志位 | C(Carry) | 进位标志位。当发生无符号数溢出时,C=1.其他情况下,C = 0 |
条件标志位 | V(Overflow) | 有符号溢出标志位。对于加/减法指令,在操作数和结果是有符号的整数时,如果发生溢出,则V=1;如果未发生溢出,则V = 0.对于其它指令,V通常不发生变化 |
运行状态控制 | SS(Software Step) | 软件单步。该步为1,说明在异常处理中使能了软件单步功能,当前处于软件单步调试模式 |
运行状态控制 | IL(ILLegal Execution State) | 不合法的异常状态,表示当前执行状态是非法的,会导致异常 |
运行状态控制 | nRW(Non-Secure Read/Write) | 在 AArch32 架构中:当 nRW = 0 时,表示当前处于"安全"状态。当 nRW = 1 时,表示当前处于"非安全"状态。在 AArch64 架构中:当 nRW = 0 时,表示当前处于 AArch64 执行状态。当 nRW = 1 时,表示当前处于 AArch32 执行状态。 |
运行状态控制 | EL(Exception Level) | 当前异常等级。 0:表示EL0;1:表示EL1;2:表示EL2;3:表示EL3;决定了程序的特权访问权限 |
运行状态控制 | SP(Stack Pointer selection) | 选择SP寄存器。当运行在EL0时,处理器选择EL0的SP寄存器,即SP_EL0;当处理器运行在其他异常等级时,处理器可以选择使用SP_EL0或者对应的SP_ELn寄存器 |
异常掩码标志位 | D(Debug exception disabled) | 调试位。使能该位可以在异常处理过程中打开调试断点和软件单步等功能 |
异常掩码标志位 | A(SError interrupt disabled) | 表示当前禁用了SError中断。SError中断是一种系统错误中断 |
异常掩码标志位 | I(IRQ interrupt disabled) | 表示当前禁用了普通IRQ中断。 |
异常掩码标志位 | F(FRQ interrupt disabled) | 表示当前禁用了快速FRQ中断。 |
访问权限 | PAN(Privileged Access Never) | ARMv8.1的扩展特性1:在EL1或者EL2访问属于EL0的虚拟地址时会触发一个访问权限错误。CPU会阻止特权模式下的代码访问用户空间的内存;0:不支持该功能,需要软件来模拟 |
访问权限 | UAO(User Access Override) | 用户特权访问覆盖标志位,是ARMv8.2的扩展特性。1:当运行在EL1或者EL2时,没有特权的加载存储指令可以和有特权的加载存储指令一样访问内存,如LDTR指令。0:不支持该功能 |
在aarch64中,只可以通过MSR/MRS
指令访问特殊寄存器(special-purpose)的方式读写PSTATE中的位。除了这些特殊寄存器中表示的位,PSTTAE的其它位都是不能访问的。
这些特殊的寄存器包含: NZCV、DAIF、CurrentEL、SPSel、PAN、UAO
,可以使用MRS/MSR指令,直接读写这些寄存器。
例如我们可以使用MSR 指令直接写入 DAIFSET 或 DAIFCLR 指令操作(注:DAIFSET 和 DAIFCLR 是两个特殊的指令操作,用于设置和清除 DAIF 寄存器中的某些位。)
MSR DAIFSET, #0x2 // 设置 DAIF 寄存器的 I 位(IRQ 屏蔽位)
MSR DAIFCLR, #0x2 // 清除 DAIF 寄存器的 I 位(IRQ 屏蔽位)
上述指令实际上会修改DAIF寄存器的对应位
特殊寄存器 | PSTATE 位 |
---|---|
NZCV | N,Z,C,V |
DAIF | D,A,I,F |
CurrentEL | EL |
SPSel | SP |
PAN | PAN |
UAO | UAO |
我们可以认为这些位其实还是都在PSTATE寄存器中,然后相关的位被抽象到了NZCV,DAIF、CurrentEL、SPSel、PAN、UAO
寄存器中,方便指令访问。
四、特殊寄存器
下面我们来看一下ARMv8中的特殊寄存器。
1. 零寄存器
ARMv8体系结构提供两个零寄存器(zero register),这些寄存器的内容全是0,可以用作源寄存器,也可以用作目标寄存器。WZR是32位的零寄存器,XZR是64位的零寄存器。
2. PC寄存器
PC寄存器(Program Counter)通常用来指向当前运行指令的下一条指令的地址,用于控制程序中指令的运行顺序,但是我们不能通过指令来直接访问它。
PC 寄存器的主要作用包括:
- 保存当前指令的地址:PC 寄存器会始终保存当前正在执行的指令的地址。每当执行一条指令,PC 寄存器的值都会自动加 4(或加 2,取决于指令宽度)以指向下一条要执行的指令。
- 支持分支跳转:当执行一条分支指令(如 B、BL 等)时,CPU 会将 PC 寄存器的值更新为分支目标地址,从而实现程序控制流的转移。
- 参与异常处理:当发生异常(如中断、系统调用等)时,硬件会自动保存当前 PC 的值到 ELR 寄存器,以便于事后恢复执行。
3. SP寄存器
ARMv8体系结构支持4个异常等级,每一个异常等级都有一个专门的SP(Stack Pointer)寄存器SP_ELn, 如处理器运行在ELI时选择SP_EL1寄存器作为SP寄存器。
- SP_EL0: ELO下的SP寄存器。
- SP_EL1: ELI下的SP寄存器。
- SP_EL2: EL2下的SP寄存器。
- SP_EL3: EL3下的SP寄存器。
当处理器运行在比ELO高的异常等级时,处理器可以访问如下寄存器。
- 当前异常等级对应的SP寄存器SP_ELs
- ELO对应的SP寄存器SP_ELO可以当作一个临时寄存器,如Linux内核使用该寄存器 存放进程中task_struct数据结构的指针。
注意:当处理器运行在EL0时,它只能访问SP_EL0,而不能访问其他高级的SP寄存器。
SP 寄存器的主要作用包括:
- 管理程序栈:SP 寄存器存放着当前程序栈的顶部地址。当执行压栈和出栈操作时,SP 寄存器的值会相应地增加或减少。这样可以确保程序栈的正确使用。
- 支持函数调用:在函数调用过程中,CPU 会自动保存返回地址、函数参数、局部变量等信息到程序栈中。SP 寄存器的值会随之动态变化,以管理这些栈帧。
- 参与中断/异常处理:当发生中断或异常时,CPU 会自动保存当前上下文(包括 SP 寄存器的值)到栈中,并切换到特定的异常处理模式。这样可以确保异常返回后能够恢复程序的执行状态。
4. 保存处理状态寄存器
当我们运行一个异常处理程序时,处理器的处理状态会保存到保存处理状态寄存器(Saved Program Status Register, SPSR)里。这个寄存器类似于ARMv7架构中的CPSR,当异常将要发生时,处理器会把PSTATE寄存器的值暂时保存到SPSR里;当异常处理完成并返回时,再把SPSR的值恢复到PSTATE寄存器。SPSR的重要字段如下图所示。
SPSR的重要字段
字段 | 描述 |
---|---|
N(Negative) | 负数标志位 |
Z(Zero) | 零标志位 |
C(Carry) | 进位标志位 |
V(Overflow) | 有符号数溢出标志位 |
DAT(Data Independent Timing) | 与数据无关的指令时序,ARMv8.4的扩展特性 |
UAO(User Access Override) | 用户特权访问覆盖标志位,ARMv8.2的扩展特性 |
PAN(Privileged Access Never) | 特权模式禁止访问(Privileged Access Never)位,ARMv8.1的扩展特性 |
SS(Software Step) | 表示是否使能软件单步功能。若该位为1,说明在异常处理中使能了软件单步功能 |
IL(ILLegal Execution State) | 不合法的异常状态 |
D(Debug exception disabled) | 调试位。使能该位可以在异常处理过程中打开调试断点和软件单步等功能 |
A(SError interrupt disabled) | 用来屏蔽系统错误 |
I(IRQ interrupt disabled) | 用来屏蔽IRQ |
F(FRQ interrupt disabled) | 用来屏蔽FIQ |
M[4] | 用来表示异常处理过程中处于哪个执行状态,若为0,表示AArch64状态 |
M[3:0] | 异常模式 |
5. ELR寄存器
ELR(Exception Link Register) 寄存器存放了异常返回的地址。
ELR寄存器的主要作用包括:
- 保存异常发生前的程序计数器(PC)值:当处理器进入异常模式(如中断、系统调用等)时,ELR 寄存器会自动保存异常发生前的 PC 值。这样在异常处理完成后,就可以从 ELR 中恢复 PC 值,继续执行异常前的指令序列。
- 支持异常处理的返回:在异常处理程序中,可以使用 ERET 指令从异常返回。ERET 指令会从 ELR 寄存器中恢复 PC 值,从而实现对异常的正确返回。
6. CurrentEL寄存器
该寄存器表示PSTATE寄存器中的EL字段,其中保存了当前异常等级。使用MRS指令可 以读取当前异常等级。
- 0: 表示 EL0
- 1: 表示 EL1
- 2: 表示 EL2
- 3: 表示 EL3
7. DAIF寄存器
该寄存器表示PSTATE寄存器中的{D, A, I, F}字段。
8. SPSel寄存器
该寄存器表示PSTATE寄存器中的SP字段,用于在SP_EL0和SP_ELn中选择SP寄存器。
9. PAN寄存器
PAN寄存器表示PSTATE寄存器中的PAN (Privileged Access Never,特权禁止访问)字段。 可以通过MSR和MRS指令来设置PAN寄存器。当内核态拥有访问用户态内存或者执行用户态程序的能力时,攻击者就可以利用漏洞轻松地执行用户的恶意程序。为了修复这个漏洞, 在ARMV8.1中新增了 PAN特性,防止内核态恶意访问用户态内存。如果内核态需要访问用 户态内存,那么需要主动调用内核提供的接口,例如copy_from_user()
或者copy_to_user()
函数。
PAN寄存器的值如下。
- 0:表示在内核态可以访问用户态内存。
- 1:表示在内核态访问用户态内存会触发一个访问权限异常。
10. UAO寄存器
该寄存器表示PSTATE寄存器中的UAO (User Access Override,用户访问覆盖)字段。我 们可以通过MSR和MRS指令设置UAO寄存器。
特权指令:如 LDR、STR 等,可以访问任意内存区域,包括特权保护的区域。
非特权指令:如 LDTR、STTR 等,通常只能访问用户空间的内存区域,受到限制。
- 当 UAO = 0 时,非特权指令(如 LDTR、STTR)的行为与特权指令(如 LDR、STR)是不同的,受到内存访问权限的限制。
- 当 UAO = 1 时,非特权指令(如 LDTR、STTR)的行为与特权指令(如 LDR、STR)是一致的,即"用户访问被覆盖"。也就是说,即使是在用户模式(EL0)下执行非特权指令,也能够访问特权保护的内存区域,就像在特权模式(EL1/EL2)下执行特权指令一样。
使用场景:
UAO = 1 的机制通常用于支持一些特殊的安全需求,例如在特权模式下执行用户空间代码、处理敏感的内存区域等。
11. NZCV寄存器
该寄存器表示PSTATE寄存器中的{N, Z, C, V}字段。
五、系统寄存器
除上面介绍的通用寄存器和特殊寄存器之外,ARMv8体系结构还定义了很多的系统寄存
器,通过访问和设置这些系统寄存器来完成对处理器不同的功能配置。在ARMv7体系结构中,
我们需要通过访问CP15协处理器来间接访问这些系统寄存器,而在ARMv8体系结构中没有协
处理器,可直接访问系统寄存器。ARMv8体系结构支持如下7类系统寄存器;
- 通用系统控制寄存器
- 调试寄存器
- 性能监控寄存器
- 活动监控寄存器
- 统计扩展寄存器
- RAS寄存器
- 通用定时器寄存器
我们对这7类系统寄存器分别做一个简单的介绍
1. 通用系统控制寄存器
通用系统控制寄存器:通用系统控制寄存器是指那些控制处理器整体状态和行为的关键系统寄存器。它们对系统的运行状态、保护机制、异常处理等进行配置和管理。
主要功能:
- 处理器状态控制:如程序状态寄存器(PSTATE)
- 内存管理单元(MMU)配置:如页表基地址寄存器
- 异常和中断处理:如中断控制寄存器
- 电源管理:如电源控制寄存器
- 性能监控:如性能计数器
2. 调试寄存器
调试寄存器:调试寄存器是CPU中用于支持软件调试的系统寄存器。它们提供了各种调试功能,如设置断点、监视变量值、跟踪指令执行等。
主要功能:
- 设置和管理软件断点
- 监视指定内存地址或寄存器的值
- 控制单步执行、步过、步入等调试行
- 记录和跟踪指令执行流程
3. 性能监控寄存器
性能监控寄存器:性能监控寄存器是 CPU 中用于监测和记录处理器性能指标的特殊寄存器。它们提供了对处理器内部事件和状态的可见性,用于性能分析和优化。
主要功能:
- 记录各种微架构事件的计数:如缓存命中率、分支预测准确率等
- 监控处理器的使用状态: 如 CPU 利用率、内存带宽使用情况等
- 提供性能监控信息供软件使用: 帮助进行性能调优。
4. 活动监控寄存器
活动监控寄存器:活动监控寄存器是 CPU 中用于记录和监测处理器及系统内部活动情况的特殊寄存器。它们提供了对处理器资源使用状态的可见性,用于系统行为分析和优化。
主要功能:
- 记录各种处理器资源的使用情况:如指令执行、内存访问、I/O 操作等
- 监控系统重要事件的发生情况:如中断、异常、上下文切换等
- 提供系统活动信息供软件使用:帮助进行系统行为分析和优化
5. 统计扩展寄存器:
统计扩展寄存器: 统计扩展寄存器是 CPU 中提供增强统计功能的系统寄存器。它们可以记录和统计处理器内部各种事件和状态信息,为性能分析和优化提供详细数据支持。
主要功能:
- 提供更多的性能事件计数器,扩展基本性能监控功能
- 支持更精细的事件类型选择和计数配置
- 记录处理器微架构级别的统计数据,如缓存行为、分支预测等
- 支持复杂的性能事件关联和统计分析
6. RAS寄存器
RAS寄存器:RAS (Reliability, Availability and Serviceability) 寄存器是 CPU 中提供增强统计功能的系统寄存器。它们可以记录和统计处理器内部各种事件和状态信息,为性能分析和优化提供详细数据支持。
主要功能:
- 记录各种系统错误事件,如内存错误、总线错误、异常等
- 提供错误事件的详细信息,如错误类型、位置、严重程度等
- 支持错误事件的检测、隔离和修复
- 实现系统健康状态的监控和报告
7. 通用定时器寄存器
通用定时器寄存器:通用定时器寄存器是 CPU 中用于实现基础定时功能的系统寄存器。它们提供了系统软件和应用程序使用的基础定时服务。
主要功能:
- 提供系统级的基本定时功能,如周期性中断、延迟计数等
- 支持多个独立的通用定时器,满足不同应用场景的需求
- 提供定时器的配置、启停、状态查询等基本控制功能
- 可以与系统中断机制集成,支持定时事件的异步通知
注意:
系统寄存器支持不同的异常等级的访问,通常系统寄存器会使用"Reg_EL"”的方式来表示。
- Reg_ELl:处理器处于ELI、EL2以及EL3时可以访问该寄存器。
- Reg_EL2:处理器处于EL2和EL3时可以访问该寄存器。
大部分系统寄存器不支持处理器处于EL0时访问,但也有一些例外,如CTR_EL0。
程序可以通过MSR和MRS指令访问系统寄存器。
mrs X0, TTBRO_EL1 //把 TTBRO_EL1 的值复制到 X。寄存器
msr TTBRO_EL1, X0 //把 X0 寄存器的值复制到 TTBRO_EL1
关于ARMv8寄存器的部分就写到这里了,如有错误和遗漏,欢迎大家评论区来补充,我会及时更新!