ARM 学习笔记(一)
本篇文章,皆以 ARMV7-A 架构为例进行讲解。ARM 手册使用的是:
《ARM Architecture Reference Manual ARMv7-A and ARMv7-R edition》
1、ARM processor modes
关于 ARM 架构下(ARMV7/ARMV8)的 CPU modes 和 Exception Level,有篇文章总结的很好,简洁凝练,这里直接放链接:ARM CPU modes和Exception Level
- usr:用户模式
- fiq:处理FIQ快速中断模式
- irq:处理IRQ中断模式
- svc:Supervisor Mode 监视模式
- mon:监视模式,主要用来进行 security 和 no-security 模式的切换
- abt:Abort Mode 所有同内存保护相关的异常均在这种模式下执行
- und:处理无效指令的异常
- sys:系统模式
1.1 Security Extension
ARMv7-A 架构提供 ID 寄存器,可以从中判断当前是否实现了 Security Extension
// C代码中(内核态)读取 ID_PFR1 寄存器:
unsigned int pfr1;
asm volatile ("mrc p15, 0, %0, c0, c1, 1" : "=r" (pfr1));
unsigned int security = (pfr1 >> 4) & 0xF;
对于已经实现了 Security Extension 的处理器来说,是一定存在 secure 和 no-secure 这两种状态的。
两种状态的切换,可以通过操作寄存器 SCR.NS
来进行。
注意,这两种状态的切换,必须在 monitor 模式下进行!!!
✅ 示例:启动后从 Secure World 跳转到 Linux 内核(NS)
// 假设当前在 Monitor 模式
ldr r0, =0x13 // SVC mode, ARM state
msr spsr_cxsf, r0 // 设置返回目标模式(SVC, ARM)
ldr lr, =0x80008000 // Linux 内核入口地址// 设置 SCR.NS = 1,进入 Non-Secure World
mrc p15, 0, r1, c1, c1, 0
orr r1, r1, #1
mcr p15, 0, r1, c1, c1, 0eret // 跳转到 NS World 中执行
no-secure 切换到 secure 模式亦是如此,都需要经过 monitor 模式。
1.2 Monitor mode
Monitor 模式,如何进入 Monitor 模式呢?它对应的代码实现、代码入口到底在哪呢?在 Linux 内核中么?我们带着这几个问题,深入了解下 Monitor mode。
首先,如何进入 Monitor 模式呢?回答:使用 SMC
这个 ARM 系统指令。当硬件执行 SMC
系统指令后,会产生一个异常,随后跳转到 Monitor 模式下的异常向量表的 0x08 偏移的表项上,执行 Secure Monitor Call
异常处理函数。
SMC<c> #<imm4>
- < c >:表示条件码。例如
SMC
+ 条件码可以为:SMCNE
、SMCEQ
等 - imm4:表示 4 位立即数
SMC
后面可以跟一个 4 位的立即数,Armv7a 中并没有定义这个立即数是干什么的,一般都为 0。和 SVC
指令一样,SMC
指令的参数都保存在寄存器中。
关于 Monitor 模式对应的代码实现,通常是放在 BL31 固件中。因为这里会涉及到 ARM 的安全启动,所以不做过多的拓展。后面会单独开一个章节讲讲 ARM 的安全启动。
我们只需要知道,Monitor 模式对应的代码实现,不在 Linux 内核中而在 BL31 中。BL31 通常是由芯片厂商开发,一般都会遵循一个统一的标准:ARM Trusted Firmware, ATF。
总结,由 SMC
指令产生异常,异常处理在 BL31 中。同时通过寄存器传参,根据各种参数的组合,明确这次 SMC
调用想要做的事。然后由 BL31 去完成。
拓展——SMC 的其它用处:
Linux 内核中的 SMC
的使用,常见的,就是多核启动过程中,
// 启动 CPU#1
arm_smccc_smc(PSCI_CPU_ON, // Function ID0x101, // 目标 CPU MPIDR0x80080000, // 次核启动地址0, 0, 0, 0, 0, // 其它参数&res);
2、ARM core registers
ARMv7 在每种异常模式下,都有其对应的寄存器。
- R0-R12:可用于常见操作期间存储临时值、指针(内存位置)等等。例如 R0,在算术运算期间可以称为累加器,或用于存储调用的函数时返回的结果。R7 在进行系统调用时非常有用,因为它存储了系统号(详见 ARM-Linux 系统调用)。R11(FP)可帮助我们跟踪作为帧指针的堆栈上的边界(详见 ARM 栈和函数调用)。此外,ARM 上的函数调用约定函数的前四个参数存储在寄存器 r0-r3 中
- SP(或 R13)是堆栈指针。C 和 C++ 编译器始终使用 SP 作为堆栈指针。不鼓励将 SP 用作通用寄存器。在 Thumb 中,SP 被严格定义为堆栈指针。(详见 ARM 栈和函数调用)
- 在用户模式下,LR(或R14)用作链接寄存器,用于在调用子程序时存储返回地址。如果返回地址存储在堆栈中,它也可以用作通用寄存器。在异常处理模式中,LR 保存异常的返回地址,或者如果在异常内执行子例程调用,则保存子例程返回地址。如果返回地址存储在堆栈中,LR 可以用作通用寄存器
- CPSR:状态寄存器:在它下面你可以看到工作状态标志,用户模式,中断标志,溢出标志,进位标志,零标志位,符号标志。这些标志代表了CPSR寄存器中特定的位,并根据CPSR的值进行设置,如果标志位有效则会进行加粗。N、Z、C 和 V 位与 x86 上的 EFLAG 寄存器中的 SF、ZF、CF 和 OF 位相同
- SPSR 程序保存状态寄存器(saved program status register)SPSR用于保存CPSR的状态,以便异常返回后恢复异常发生时的工作状态。当特定的异常发生时,这个寄存器用于存放当前程序状态寄存器的内容。在异常退出时,可以用 SPSR 来恢复 CPSR。
3、Exception vectors and the exception base address
3.1 概念
B1.8.1 Exception vectors and the exception base address 章节:
When an exception is taken, processor execution is forced to an address that corresponds to the type of exception. This address is called the exception vector for that exception
由上不难看出,异常向量表,是当异常产生时,硬件强制 PC 跳转到异常向量表对应的表项去执行。异常向量表大概长这个样子:
3.2 异常向量表的位置
B1.8.1 Exception vectors and the exception base address 章节已经做了很好的解释
对于不支持安全扩展的硬件来说,其异常向量表的地址,只能是 0x00000000 或者 0xFFFF0000(开启 MMU 后是虚拟地址)。
对于支持安全扩展的硬件来说,实际上是存在三个异常向量表的:
- Monitor 模式专有异常向量表,基址通过
MVBAR
寄存器去设置 - Secure 状态异常向量表,基址通过
VBAR
寄存器去设置 - no-secure 状态异常向量表,基址通过
VBAR
寄存器去设置
VBAR 在 secure 和 no-secure 两种状态下,虽然名字一样,但是是两个不同的寄存器
3.3 Linux 源码中的异常向量表
我们看下 Linux 代码中是如何定义这个异常向量表的:
arch\arm\kernel\entry-armv.S:
.section .vectors, "ax", %progbitsW(b) vector_rstW(b) vector_und
ARM( .reloc ., R_ARM_LDR_PC_G0, .L__vector_swi )
THUMB( .reloc ., R_ARM_THM_PC12, .L__vector_swi )W(ldr) pc, .W(b) vector_pabtW(b) vector_dabtW(b) vector_addrexcptnW(b) vector_irqW(b) vector_fiq
W(b) vector_rst
→ 等价于 b vector_rstARM( .reloc ., R_ARM_LDR_PC_G0, .L__vector_swi )
:给当前位置生成一个重定位记录,类型为 LDR 形式的 PC 跳转,目标是 .L__vector_swi 标签THUMB( .reloc ., R_ARM_THM_PC12, .L__vector_swi )
:同理,这是 Thumb 模式下使用的重定位方式W(ldr) pc, .
→ 等价于 ldr pc, .
可以看到,和 Table B1-3 相同,每种模式占用 4 个字节,作为一个异常向量表的表项。每个表项的内容,是一句汇编指令 b 异常处理入口
,跳转到对应的异常处理入口。
4、CPSR/SPSRs 寄存器
-
M[4:0]:
-
T:Thumb 异常状态标志。当该位为 1 时,程序运行于 Thumb 状态,否则运行于 ARM 状态
-
F:FIQ mask bit,控制 FIQ 中断的使能与禁能
-
I:IRQ mask bit,控制 IRQ 中断的使能与禁能
-
A:Asynchronous abort mask bit。The possible values of each bit are:
- 0 Exception not masked.
- 1 Exception masked
-
E:表示当前 CPU 的执行态数据访问的大小端模式(通常默认都是小端,不建议修改)
- 0 Little-endian operation.
- 1 Big-endian operation.
-
N:Negative condition flag
-
Z:Zero condition flag
-
C:Carry condition flag
-
V:Overflow condition flag
4.1 该寄存器在进入异常时的变化
在 ARMv7 手册的 B1.8.5 Processor state on exception entry 章节,详细的介绍了,在进入异常时,该寄存器的一个变化。
- On exception entry,
CPSR.M
is set to the value for the mode to which the exception is taken - Table B1-10 shows the cases where
CPSR.{A, I, F}
bits are set to 1 on an exception entry, and how this depends on the mode and security state to which an exception is taken. If the table entry for a particular mode and security state does not define a value for aCPSR.{A, I, F}
bit then that bit is unchanged by the exception entry
4.2 关于 SPSRs
The purpose of an SPSR is to record the pre-exception value of the CPSR. On taking an exception, the CPSR is copied to the SPSR of the mode to which the exception is taken. Saving this value means the exception handler can:
- On exception return, restore the CPSR to the value it had immediately before the exception was taken.
- Examine the value that the CPSR had when the exception was taken, for example to determine the instruction set state and privilege level
in which the instruction that caused an Undefined Instruction
exception was executed
SRSRs 主要目的是,在异常发生时,记录/保存 CPSR 的状态。当异常发生时,硬件会自动拷贝一份 CPSR 的数据到对应异常模式的 SRSRs 中。这样做意味着异常处理程序可以:
- 在异常处理结束后,可以软件/手动去恢复 CPSR 的一个状态
- 可以判断导致“未定义指令异常(Undefined Instruction Exception)”的那条指令,是在什么指令集状态(ARM、Thumb)和模式下执行的
4.3 操作 CPSR 的指令 MSR/MRS
- MRS 指令:reads the system register into Xd。可以用来对状态寄存器 CPSR 和 SPSR 进行读操作
MRS Xd, <system register>
- MSR 指令: writes Xn to the system register。可以用来对状态寄存器 CPSR 和 SPSR 进行写操作
MSR <system register>, Xn