ATF bl31 初探
→ 冷启动路径(primary CPU,初始化环境、调用bl31_setupbl31_main→ 热启动路径(secondary CPU / resume,快速进入 PSCI)。都会收敛到 el3_exit,从 EL3 跳到 BL32(OP-TEE)或 BL33(U-Boot/Linux)。
平时天天看soc启动,但是一想启动的细节就不太清楚,今天准备针对性的梳理一下Arm 安全启动的部分流程,可能不是特别到位,但是需要一次一次梳理循序渐进。
-
bl31初始化
-
bl31_entrypoint.S
总体背景
-
BL31 运行在 EL3(最高特权级,Secure Monitor)。
-
它的作用:
-
初始化 EL3 自身(寄存器、栈、C 运行环境)。
-
初始化 MMU、内存、异常向量。
-
为后续的 BL32 (OP-TEE, 可选) 和 BL33 (U-Boot/Linux kernel) 提供服务。
-
负责 PSCI(CPU 上电/关机/休眠管理)。
-
所以这份代码就是 BL31 的入口函数,CPU 从前一级 Bootloader 跳进来后执行。
bl31_entrypoint
(冷启动入口)
func bl31_entrypoint
-
这是冷启动入口,只有 primary CPU(通常是 CPU0) 执行。
保存传参
-
把前级 Bootloader(BL2 或 BL1.5)传入的寄存器参数保存下来,后续会传给
bl31_setup()
。
公共初始化
el3_entrypoint_common ...
-
调用一个宏,负责初始化 EL3 的基础环境(不同宏参数表示是否做某些动作):
-
_init_sctlr
→ 是否初始化 SCTLR_EL3(MMU/Cache控制寄存器)。 -
_warm_boot_mailbox
→ 是否设置热启动唤醒用的 mailbox。 -
_secondary_cold_boot
→ 是否允许次级 CPU 在冷启动参与启动。 -
_init_memory
→ 是否初始化内存。 -
_init_c_runtime
→ 是否初始化 C 环境(BSS 清零,全局变量准备)。 -
_exception_vectors
→ 设置异常向量表。 -
_pie_fixup_size
→ PIE 模式修正大小(位置无关代码支持)。
-
这里分两种情况:
-
!RESET_TO_BL31
:说明从 BL2 跳进来,不需要重复初始化太多。 -
RESET_TO_BL31
:说明 CPU 复位地址直接指到 BL31(不经过 BL2),那就要做更多初始化。
调用 bl31_setup
-
把启动参数传给 C 语言的
bl31_setup()
,做更复杂的初始化,比如:-
初始化控制台串口。
-
初始化平台层(platform-specific init)。
-
准备 Secure Payload Dispatcher(和 OP-TEE 交互)。
-
可选:启用 Pointer Authentication (PAC)
bl pauth_init_enable_el3
-
如果启用了 ARMv8.3 的指针认证功能,则在 EL3 里设置 APIAKey_EL1。
调用 C 主函数
bl bl31_main
-
进入 BL31 的主逻辑(C 实现),负责加载 BL32/BL33,设置上下文。
确保数据对次级 CPU 可见
-
清理
.data
和.bss
段对应的缓存,保证 primary CPU 初始化的全局变量对 secondary CPU 可见。
退出 EL3
b el3_exit
-
走统一的
el3_exit
路径,准备跳转到下一级世界。
bl31_warm_entrypoint
(热启动入口)
func bl31_warm_entrypoint
-
用于 次级 CPU 上电,或者 CPU 从 suspend 恢复。
流程和 bl31_entrypoint
类似,但有区别:
runtime instrumentation(可选)
-
如果启用了性能测量,会记录时间戳。
调用 el3_entrypoint_common
-
和冷启动不同,热启动跳过了大部分初始化:
-
不初始化内存。
-
不初始化 C runtime。
-
不区分 primary/secondary。
-
只保证必要的寄存器和异常向量可用。
-
开启 MMU
bl bl31_plat_enable_mmu
-
启动当前 CPU 的 MMU。
-
注意这里有个条件:如果平台没有硬件缓存一致性,那么不能立刻打开 D-cache,否则可能破坏 coherency。
可选:GPT/RME 初始化
bl gpt_enable
-
如果启用了 ARMv9 RME(Realm Management Extension),要设置 GPT 数据结构。
可选:启用 PAC
bl pauth_init_enable_el3
进入 PSCI 的热启动入口
bl psci_warmboot_entrypoint
-
次级 CPU 调用 PSCI 初始化代码,加入 coherency,等待调度。
runtime instrumentation (退出时的时间戳)
mrs x0, cntpct_el0
str x0, [x19]
最终退出
b el3_exit
总结
-
bl31_entrypoint
→ 冷启动路径(primary CPU,初始化环境、调用bl31_setup
+bl31_main
)。 -
bl31_warm_entrypoint
→ 热启动路径(secondary CPU / resume,快速进入 PSCI)。 -
都会收敛到
el3_exit
,从 EL3 跳到 BL32(OP-TEE)或 BL33(U-Boot/Linux)。
-
bl31_main.c
bl31_setup()
初始化阶段:
-
特性检测:确认编译启用的特性(Pointer Auth、SVE 等)是否 CPU 支持。
-
早期串口:
plat_setup_early_console()
提供 early log。 -
平台早期初始化:
bl31_early_platform_setup2()
(芯片厂商平台定制)。 -
架构初始化:
bl31_plat_arch_setup()
,设置页表、MMU 等。 -
上下文内存报告:打印 secure / non-secure context 内存占用。
bl31_main()
核心逻辑:
-
扩展寄存器管理:初始化 EL3 扩展寄存器。
-
平台初始化:
bl31_platform_setup()
,加载 GIC/DSU 等外设驱动。 -
GIC 初始化:
gic_init
+gic_cpuif_enable
。 -
库初始化:
bl31_lib_init()
(主要是上下文管理 cm_init)。 -
异常框架:
ehf_init()
(Exception Handling Framework)。 -
运行时服务:
runtime_svc_init()
注册所有 service,如 PSCI。
-
BL32 / RMM 初始化:
-
如果
bl32_init
已注册(SPD 调用bl31_register_bl32_init()
),调用 BL32 初始化。 -
如果启用 RME,调用
rmm_init
。
-
-
准备下一个镜像:
bl31_prepare_next_image_entry()
。-
决定进入 Secure World(BL32)还是 Normal World(BL33)。
-
设置下一个世界的上下文(cm_init_my_context)。
-
调用
cm_prepare_el3_exit[_ns]()
。
-
-
平台 runtime setup。
-
等待 ERET 返回 → 跳入 Linux 或 OP-TEE。
-
bl31跳转至optee
runtime_svc_init()
做了三件事:
-
校验所有 Runtime Service 描述符 确保 OEN、handler 合法。
-
调用服务初始化函数 让像 PSCI、OP-TEE 接口等做好准备。
-
建立 OEN → Service 索引表 后续 SMC 调用时快速路由到正确的 service handler。
-
optee初始化
_start
的整体职责
这是 OP-TEE 的 EL1 Secure Monitor 下的入口:
-
保存 boot 参数(x0-x3)
-
设置异常向量表 (VBAR_EL1 = reset_vect_table)
-
初始化控制寄存器 SCTLR_EL1(打开 I-cache、SA、SPAN、可选 BTI/PAUTH/MEMTAG)
-
清零
.bss
,搬运.init
段或boot_embdata
数据
-
建立 每 CPU 栈 (SP_EL0, SP_EL1) 和
thread_core_local
结构 -
启动 MMU 和 cache,初始化内存分布
-
进入 C 世界 (
boot_init_*
系列函数)
-
最终通过 SMC 通知 ATF/BL31 “OP-TEE 初始化完成,可以切到 normal world”
栈初始化逻辑
在 OP-TEE 里,每个 CPU 有两套栈:
-
SP_EL0 (线程栈) → 临时栈(stack_tmp),后面切换为 thread 里的 runtime 栈
-
SP_EL1 (core-local 栈) →
thread_core_local[cpu_id]
结构,用来保存 per-cpu 状态,比如 abort 栈、当前 thread id、flags
代码片段:
所以每个 core 启动时都有独立的 SP_EL0/EL1,保证并发安全。
MMU 启动 (enable_mmu
)
OP-TEE 在 EL1 Secure 开启自己的 MMU,地址空间和 Linux (EL1 NS) 是隔离的。
Secondary CPU 上电 (cpu_on_handler
)
这对应 PSCI 的 CPU_ON
,当 Linux 想 bring-up 一个核时,通过 ATF → SMC → OP-TEE:
每个新核起来后,走一遍和 primary 类似的初始化:设置异常向量表、打开 MMU、设置 per-core 栈,然后交给 C 代码 (boot_cpu_on_handler)。
最终返回 BL31
当 primary core 完成初始化,会:
这就是通过 SMC 返回到 BL31,告诉 ATF “OP-TEE 已初始化完成,可以调度 Linux”。
-
bl31跳转至bl33
准备bl33的镜像,等下一个eret去跳过去
查看一下el3 exit都干了那些事情,在哪里eret的
el3_exit
的任务是:
在 EL3(BL31)处理完初始化/调度逻辑后,把 EL3 的运行时上下文保存好、从 per-world 上下文里恢复到进入时的寄存器状态(包括 SCR_EL3、SPSR_EL3、ELR_EL3 等),然后执行
ERET
,把控制权交给 lower EL(通常是 EL1 非安全的 BL33,或 EL1/EL2 的 BL32/secure payload)。
它不是简单的 eret
— 必须先把 EL3 自己临时使用的状态(stack、控制寄存器、补丁、workaround 状态等)恢复到“入口时”的样子,保证返回后的环境一致且安全。
-
入口断言(可选)
-
断言:进入
el3_exit
时必须处于SP_EL0
(EL3 runtime 使用 SP_EL0 模式)。这是函数对调用约定的假设,便于后面统一操作栈指针和存放上下文的位置。
-
保存当前
SP_EL0
(EL3 runtime stack),切换到SP_EL3
-
把当前
sp
(此时是 SP_EL0)保存到内存(EL3 上下文结构的CTX_RUNTIME_SP
字段)。保存它的目的是:这个 runtime stack 在将来再次进入 EL3(处理 SMC)时要恢复。 -
然后通过
msr spsel, #MODE_SP_ELX
切换到SP_EL3
,因为从现在起要使用 EL3 的栈框架去恢复更多寄存器(EL3 专用的存放区通常在 SP_EL3 所指向的栈上)。
注:ARMv8 有两个栈选择(SP_EL0 与 SP_ELx),在 EL3 内部可切换以访问不同的栈/上下文布局。
-
恢复
CPTR_EL3
(协处理器访问控制)
-
get_per_world_context x9
:加载指向当前 security-world 的 per-world context 的指针到x9
。 -
从该上下文读取
CPTR_EL3
(保存在两个寄存器对里),写回cptr_el3
。这把协处理器访问控制恢复到进入 EL3 前的状态(比如对浮点/Neon/其他架构扩展的访问控制)。
-
恢复平台/实现相关寄存器(MPAM 等)与漏洞缓解状态
-
restore_mpam3_el3
:若平台使用 MPAM(Memory Partitioning And Monitoring),在 EL3 需要恢复 MPAM 的寄存器状态。 -
CVE-2018-3639(Spectre v4)相关动态缓解:如果当初进入 EL3 时做了某个动态 workaround(比如安装了一个小函数),返回时需要按栈中记录的函数指针去调用以恢复/撤销状态。这里通过读取早先保存的位置并
blr x17
来执行恢复函数。
-
同步错误(platform-specific)
#if IMAGE_BL31
synchronize_errors
#endif
-
做实现/平台相关的错误同步(确保系统错误状态在返回前一致),通常是架构/平台上的清理或同步步骤。
-
从 EL3 上下文恢复关键系统寄存器(SPSR_EL3/ELR_EL3/SCR_EL3/MDCR_EL3)
-
这些是返回最关键的寄存器:
-
SPSR_EL3
:保存目标异常等级的 PSTATE(中断屏蔽、标志、模式位等)。写入后ERET
会用它来确定返回到哪个 EL/哪种栈与中断屏蔽状态。 -
ELR_EL3
:异常返回时的目标 PC(lower EL 的入口地址)。 -
SCR_EL3
:Secure Configuration Register,包含 NS(non-secure)位、HCE、SMD、RES1/RES0、中断映射等,必须在返回前恢复到进入 EL3 前的值,否则会进入错误的安全世界/属性。 -
MDCR_EL3
:Monitor Debug/PMU control(和性能监测/调试相关),恢复它以使 lower EL 的 PMU/monitor 行为一致。
-
恢复顺序重要:先把寄存器值加载到通用寄存器,再一次性写回系统寄存器,保证恢复恢复成进入 EL3 的精确状态。
-
恢复与页表相关的系统寄存器(PTW / translation walker regs)
restore_ptw_el1_sys_regs
-
还原 page-table walker 相关的 EL1 系统寄存器(例如 TCR/TTBR/MAIR 等,具体实现里可能只还原 Walker 用到的部分),确保 lower EL 的地址转换行为正确。
-
这一步对 MMU/页表一致性至关重要,特别是当 BL31 在 EL3 时临时改变了这些寄存器。
-
恢复通用寄存器、PMCR_EL0、PAUTH 等(并恢复 LR)
bl restore_gp_pmcr_pauth_regs
ldr x30, [sp, #CTX_GPREGS_OFFSET + CTX_GPREG_LR]
-
restore_gp_pmcr_pauth_regs
(一个子例程):-
恢复一批通用寄存器(x0..x??,按上下文保存的顺序),
-
恢复
PMCR_EL0
(Performance Monitor Control Register)等非 EL3 专属的系统寄存器, -
恢复 Pointer Authentication 的 key 寄存器(如果启用),即 APIAKey/ AUTH registers。
-
-
之后显式从栈里加载 saved LR(x30)到
x30
,以恢复返回地址。
-
清 EL3 标志(IMAGE_BL31 特殊)
#ifdef IMAGE_BL31
str xzr, [sp, #CTX_EL3STATE_OFFSET + CTX_NESTED_EA_FLAG]
#endif
-
如果是 BL31 镜像,清除记录的“在 EL3 嵌套异常激活(EA)”标志,表明正要退出 EL3。
-
exception_return
宏 —— 执行最终的返回(ERET
)
exception_return
-
这个宏最终会展开为设置必要的寄存器(已经准备好 SPSR_EL3、ELR_EL3、并配置好要传给 lower EL 的通用寄存器 x0..x3),然后执行
ERET
。 -
ERET
的效果:-
根据
SPSR_EL3
恢复 PSTATE(包含目标 EL、中断屏蔽位等), -
将 PC 设置为
ELR_EL3
,从而跳转到 lower EL(BL33/BL32 的入口)。
-
最后终于看到eret的汇编指令了
ARM 在 Spectre 类攻击之后发现:
-
eret
之后,CPU 可能会 错误推测继续执行后续的指令(即使这些指令本不该执行)。 -
如果攻击者能通过 cache side-channel 观测到这些推测执行的痕迹,就可能泄露敏感数据。
-
所以在
eret
之后需要一个 speculation barrier(推测屏障) 来保证安全。 -
FEAT_SB 是 ARMv8.5-A 引入的 Speculation Barrier 特性。
-
如果 CPU 支持这个 feature,ATF 就用
SB
指令(在这里宏替代成sb_barrier_insn
)。 -
SB
指令作用:阻止 所有可能错误路径的推测,比 DSB/ISB 更彻底。
-
-
如果 CPU 不支持 FEAT_SB:
-
退而求其次,用:
-
dsb nsh
(Data Synchronization Barrier, Non-shareable):保证之前的所有内存访问完成。 -
isb
(Instruction Synchronization Barrier):清空 pipeline,保证之后的指令不会受之前推测的影响。
-
-
这样确保 eret
之后不会再去推测执行 EL3 后面的代码,避免敏感信息外泄。
更多推荐
所有评论(0)