CPU 复位后的初始状态 #
Configuration signal #
CPU 复位后,会进入某种指定的初始状态,初始状态由 configuration signals 部分决定。Configuration signal 是 SoC 给 CPU 传递一组信号,决定了软件的起始执行地址(RVBARADDRx)、执行状态(AA64nAA32,AArch64 or AArch32)、数据读写字节序(CFGEND)等状态:
| Signal | Direction | Description |
|---|---|---|
| AA64nAA32[CN:0] | Input | Register width state: 0 = AArch32, 1 = AArch64. |
| CFGEND[CN:0] | Input | Endianness configuration at reset. It sets the initial value of SCTLR_EL3.EE and SCTLR_S.EE: 0 = LOW, 1 = HIGH. |
| CFGTE[CN:0] | Input | Enabling T32 exceptions. It sets the initial value of SCTLR.TE: 0 = LOW, 1 = HIGH. |
| CLUSTERIDAFF1[7:0] | Input | Value read in MPIDR.Aff1. This signal is sampled only during processor reset. |
| CLUSTERIDAFF2[7:0] | Input | Value read in MPIDR.Aff2. This signal is sampled only during processor reset. |
| CRYPTODISABLE[CN:0] | Input | Disabling the Cryptographic Extensions. |
| RVBARADDRx[39:2] | Input | Reset Vector Base Address for executing in 64-bit state. |
| VINITHI[CN:0] | Input | Location of the exception vectors at reset (AArch32). It sets the initial value of SCTLR.V: 0 = Exception vectors start at address 0x00000000, 1 = Exception vectors start at address 0xFFFF0000. |
Configuration signal 一般由硬件指定,但也可以设计为软件可配。比如,假设 SoC 内部有 CPU0~CPU7,SoC 启动后默认只复位了 CPU0,那么 CPU0 上运行的软件可以通过配置其他 CPU configuration signal 的寄存器,改变 configuration signal 的默认值。
架构的保证 #
另外,架构上还保证,CPU 复位后:
- 屏蔽所有异步异常 (IRQs、FIQs、SError),即 PSTATE 中 DAIF 的 A、I、F 位为 1
- 禁用 EL3 的 MMU 和 caches,即 SCTLR_EL3 中的 M、C、I 位为 0
之所以屏蔽异步异常,是因为异步异常会使 PC 跳转至 VBAR_EL3 指向的向量表的某个表项,而 VBAR_EL3 在 CPU 复位后处于 UNKNOWN 状态(AArch64 下),其指向的向量表的内容更是不可知。如果在软件配置 VBAR_EL3 之前没有屏蔽异步中断,而恰巧有一个异步中断到来,那么 PC 大概率会跳转到一个没有合法指令的地址,并因为非法指令(undefined instructions)反复引发同步异常,出现“跑飞”的情况。
禁用 MMU 的原因和屏蔽同步异常的原因类似。CPU 复位后地址转换相关寄存器配置尚未完成。在复位时,TTBR_EL3 和 TCR_EL3 以及其他与地址转换相关的寄存器通常处于未定义状态。如果此时没有屏蔽 MMU,处理器执行内存访问操作时,会因为地址映射错误而进入 page fault 同步异常的死循环里。
那架构为什么要禁用 caches 呢?这是因为架构不保证 CPU 复位后 caches 的状态,复位后 cache entry 可能 valid 但其 entry 数据是错误的,这时会出项 cache hit 但 cache 与内存不一致的情况。为避免这种情况,所以架构上禁用了 Cache。当然,各家 IP 在实现 CPU 时,可以对 caches 有更严格的要求,比如要求所有 cache entry 复位后都是 invalid。
初始内存属性和权限 #
大家知道,ARMv8 提供两大类内存属性 —— Device 与 Normal 及各种访问权限,CPU 在访问不同内存属性和权限的内存时,会有不同的行为。而内存属性是一般是由 page table、MAIR、 TCR 决定,但 page table 和 TCR 需要开启 MMU 才生效。那么,复位后默认禁用 MMU 的 CPU,如何决定内存的属性呢?对此,根据文档 Learn the architecture - AArch64 memory attributes and properties MMU disabled 章,当 stage 1 MMU 被禁用时:
- 所有数据访问(data access)属性为 Device-nGnRnE
- 取指令(instruction fetch)操作 Cache 属性由 SCTLR_ELx.I (instruction cacheability control) 位决定
- 所有内存权限均为可读、可写、可执行
个人理解,相比于开启 MMU 后,关闭 MMU 后的内存属性粒度进一步细化,LSU(Load/Store Unit) 看待所有内存的属性为 Device-nGnRnE,而 IFU(Instruction Fetch Unit)看待所有内存的属性为 Normal。这点可以在 Learn the architecture - ARMv8-A memory systems Device memory 章也可以得到印证:
Trying to execute code from a region marked as Device is UNPREDICTABLE. When an instruction can result in UNPREDICTABLE behavior, the ARM architecture can specify a narrow range of permitted behaviors. This is defined as a number of CONSTRAINED UNPREDICTABLE behaviors. The implementation can either handle the instruction fetch as if it were to a memory location with the normal Non-cacheable attribute, or it can take a permission fault.
也就是说,在 CPU 执行标记为 Device 内存中的代码时,要么会 permission fault,要么将这段内存视作 Normal Non-cacheable。CPU 复位后显然不能因为 MMU 关闭而导致执行代码发生 permission fault,那只能认为,取指令操作时 IFU 将内存视作 Normal 属性。
所有数据访问属性被设置为 Device, 除了会降低软件预期性能外,更为重要的是,它会对使 ARM 某些功能失效,破坏软件正确运行的前置条件(precondition)。比如,ARMv8 提供了非对齐访问的功能(SCTLR_ELx.A),减弱了对软件地址对齐的要求,开启后能缩减程序体积(无需开启 mno-unaligned-access),所以很多软件会开启非对齐访问功能。但是,ARMv8 提供的非对齐访问的功能仅在 Normal 内存生效,对 Device 内存上的非对齐访问依然会产生同步异常。这就会导致一种奇怪的现象发生:相同的二进制代码,在开启 MMU 后能正常运行,但在开启 MMU 前却会异常。但理论上,MMU 是否开启(虚拟/物理地址空间)应该对应用软件透明。一些攻击者也可以利用这一现象做一些信息搜集。