前置核心区分:两套向量表的分工
向量表/入口 | 所在文件 | 作用 | 调用时机 |
reset_vect_table | entry_a64.S | S-EL1 本地硬件异常向量表,写入VBAR_EL1,由 CPU 硬件识别,处理 S-EL1 内部的同步异常、IRQ、FIQ、SError | 启动初期配置,OP-TEE 运行全程生效 |
thread_vector_table | thread_optee_smc_a64.S | 交付给 ATF 的软件约定向量表,纯函数跳转表,是 EL3 切入 OP-TEE 业务的统一入口 | 冷启动完成后交付 ATF,运行时业务调用、电源管理、中断处理使用 |
_start | entry_a64.S | 主 CPU 冷启动唯一入口,完成完整内核环境搭建 | 系统上电后仅执行 1 次 |
cpu_on_handler | entry_a64.S | 次级 CPU 热启动入口,仅完成 CPU 本地硬件初始化 | 每个次级 CPU 上电时执行 1 次 |
一、主 CPU 冷启动全链路(OPTEED ↔ OP-TEE 双向完整流程)
阶段 1:OPTEED 侧构造异常现场,发起跳转
1.BL2 已将 OP-TEE 镜像加载到安全物理内存,BL31 通过 bl31_plat_get_next_image_ep_info 拿到 OP-TEE 的入口物理地址(即 _start 的物理地址)。
2.OPTEED 调用 opteed_init_optee_ep_state 构造 S-EL1 上下文:
- 将 _start 物理地址写入安全上下文的 ELR_EL3
- 配置 SPSR_EL3 为 S-EL1 特权级、AArch64 模式、中断屏蔽
- 启动参数(架构位数、内存范围、DTB 地址)写入 x0~x3
3.执行 eret 指令:硬件自动将上下文恢复到寄存器,特权级从 EL3 降到 S-EL1,PC 跳转到 _start。
阶段 2:OP-TEE 汇编级最早期初始化(_start前半段,MMU 开启前)
2.1 启动参数暂存 + 本地异常向量表配置
LOCAL_FUNC reset_vect_table , :, .identity_map, , nobti /* ----------------------------------------------------- * Current EL with SP0 : 0x0 - 0x180 * ----------------------------------------------------- */ SynchronousExceptionSP0: b SynchronousExceptionSP0 check_vector_size SynchronousExceptionSP0 .align 7 IrqSP0: b IrqSP0 check_vector_size IrqSP0 .align 7 FiqSP0: b FiqSP0 check_vector_size FiqSP0 .align 7 SErrorSP0: b SErrorSP0 check_vector_size SErrorSP0 /* ----------------------------------------------------- * Current EL with SPx: 0x200 - 0x380 * ----------------------------------------------------- */ .align 7 SynchronousExceptionSPx: b SynchronousExceptionSPx check_vector_size SynchronousExceptionSPx .align 7 IrqSPx: b IrqSPx check_vector_size IrqSPx .align 7 FiqSPx: b FiqSPx check_vector_size FiqSPx .align 7 SErrorSPx: b SErrorSPx check_vector_size SErrorSPx /* ----------------------------------------------------- * Lower EL using AArch64 : 0x400 - 0x580 * ----------------------------------------------------- */ .align 7 SynchronousExceptionA64: b SynchronousExceptionA64 check_vector_size SynchronousExceptionA64 .align 7 IrqA64: b IrqA64 check_vector_size IrqA64 .align 7 FiqA64: b FiqA64 check_vector_size FiqA64 .align 7 SErrorA64: b SErrorA64 check_vector_size SErrorA64 /* ----------------------------------------------------- * Lower EL using AArch32 : 0x0 - 0x180 * ----------------------------------------------------- */ .align 7 SynchronousExceptionA32: b SynchronousExceptionA32 check_vector_size SynchronousExceptionA32 .align 7 IrqA32: b IrqA32 check_vector_size IrqA32 .align 7 FiqA32: b FiqA32 check_vector_size FiqA32 .align 7 SErrorA32: b SErrorA32 check_vector_size SErrorA32 END_FUNC reset_vect_table FUNC _start , : /* * Temporary copy of boot argument registers, will be passed to * boot_save_args() further down. */ mov x19, x0 mov x20, x1 mov x21, x2 mov x22, x3 adr x0, reset_vect_table msr vbar_el1, x0 isb #ifdef CFG_PAN init_pan #endif set_sctlr_el1 isb #ifdef CFG_WITH_PAGER- 将 ATF 传入的 4 个启动参数暂存到 x19~x22(callee-saved 寄存器,C 函数不会破坏),后续传递给 C 初始化函数。
- 配置 S-EL1 本地硬件异常向量表 reset_vect_table 到 VBAR_EL1。此时向量表中所有入口都是死循环(b .),仅做异常兜底,防止启动早期异常导致 CPU 跑飞;后续内核初始化完成后会替换为完整异常处理。
2.2 早期系统寄存器与安全特性配置
#ifdef CFG_PAN init_pan #endif set_sctlr_el1 isb- init_pan:开启 ARMv8.1 PAN(Privileged Access Never)特权访问永不特性,防止 S-EL1 特权态意外访问用户态内存,阻断权限提升攻击路径。
- set_sctlr_el1:配置 SCTLR_EL1 系统寄存器,开启:
- 指令缓存(SCTLR_I)、栈对齐检查(SCTLR_SA)
- 可选项:WXN 写区域不可执行、地址对齐检查、MTE 内存标签、BTI 分支目标校验从启动最早期就开启内存保护与执行保护,缩小攻击面。
2.3 镜像布局修正与内存清零
#ifdef CFG_WITH_PAGER /* 带页交换机制:拷贝init段到__init_start,暂存哈希数据 */ adr x0, __init_start /* dst */ adr x1, __data_end /* src */ ... copy_init: ldp x3, x4, [x1, #-16]! stp x3, x4, [x0, #-16]! cmp x0, x2 shtu copy_init #else /* 无页交换:将嵌入数据搬到内核空闲内存尾部 */ ... #endif /* 清零BSS段 */ adr_l x0, __bss_start adr_l x1, __bss_end clear_bss: str xzr, [x0], #8 cmp x0, x1 b.lt clear_bss- 镜像编译时,初始化代码、嵌入数据会追加在镜像尾部,启动时必须反向拷贝到链接脚本指定的虚拟地址位置,保证后续 C 代码访问全局变量、调用 init 函数时地址正确。
- 清零 BSS 段:保证未初始化全局变量初值为 0,符合 C 语言标准,同时避免残留敏感数据。
2.4 物理重定位(可选)
adr_l x2, core_mmu_tee_load_pa adr x1, _start str x1, [x2] mov_imm x0, TEE_LOAD_ADDR sub x0, x1, x0 cbz x0, 1f bl relocate 1:- 计算实际加载物理地址与编译默认地址的偏移,调用 relocate 函数遍历重定位表,修正所有绝对地址符号,保证物理地址下访问全局变量正确。
2.5 栈与线程核心本地结构初始化
#if defined(CFG_DYN_CONFIG) /* 动态配置:分配临时栈与thread_core_local */ ... #else set_sp /* 初始化thread_core_local字段 */ bl thread_get_abt_stack ... #endif- set_sp 宏为每个 CPU 分配独立的栈:
- SP_EL0:临时运行栈,用于早期 C 函数调用
- SP_EL1:指向 thread_core_local 结构体,异常发生时自动切换到 SP_EL1,保存异常上下文,实现异常栈与业务栈隔离
- 初始化 thread_core_local:标记当前无运行线程、设置临时栈标志,为后续线程调度做准备。
2.6 缓存与控制台初始化
/* 清理初始化内存的缓存一致性 */ adr_l x0, __text_start adr_l x1, boot_cached_mem_end ldr x1, [x1] sub x1, x1, x0 bl dcache_cleaninv_range /* 开启串口控制台 */ bl console_init /* 保存启动参数到C语言全局变量 */ mov x0, x19 mov x1, x20 mov x2, x21 mov x3, x22 mov x4, #0 bl boot_save_args- 刷写数据缓存:保证后续开启 MMU 后,内存与缓存数据一致,避免出现一致性问题。
- 初始化调试串口:此后可以输出启动日志,进入可调试阶段。
- 保存启动参数:将 x19~x22 暂存的参数写入全局变量,后续内核初始化全程使用。
阶段 3:内存管理初始化与 MMU 开启
3.1 内存布局与页表构建
bl boot_mem_init /* 初始化内存分配器,划分安全内存区域 */ #ifdef CFG_MEMTAG bl boot_init_memtag /* 初始化MTE内存标签框架 */ #endif /* 生成ASLR随机偏移 */ #ifdef CFG_CORE_ASLR bl get_aslr_seed ... #endif /* 构建S-EL1页表 */ adr x1, boot_mmu_config bl core_init_mmu_map- boot_mem_init:初始化内核内存分配器,划分代码段、数据段、堆、栈的内存范围,配置安全内存属性。
- core_init_mmu_map:构建完整的 MMU 页表,配置各内存段的权限(只读、可读写、不可执行)、安全属性、缓存属性;若开启 ASLR,会随机化内核虚拟地址基址。
3.2 虚拟地址重定位(ASLR 场景)
ldr x0, boot_mmu_config + CORE_MMU_CONFIG_MAP_OFFSET cbz x0, 1f bl relocate 1:- 读取页表中的虚拟地址偏移,再次调用 relocate 修正所有绝对地址,保证 MMU 开启后虚拟地址访问正确。
3.3 开启 MMU,完成地址空间切换
bl __get_core_pos bl enable_mmu- 写入 TCR_EL1、MAIR_EL1、TTBR0_EL1 等内存管理寄存器,加载页表基地址。
- 无效化 TLB,保证旧的地址翻译全部失效。
- 置位 SCTLR_EL1.M 开启 MMU,此时 PC 仍在恒等映射段,物理地址=虚拟地址,不会触发缺页。
- 更新 VBAR_EL1:加上虚拟地址偏移,修正异常向量表的虚拟地址。
- 无效化指令缓存与分支预测器,保证指令流一致性。
- 开启指令缓存与数据缓存。
- 修正栈指针与返回地址:SP_EL0、SP_EL1、LR 全部加上虚拟地址偏移,完成从物理栈到虚拟栈的切换。
- 返回:此时 PC 已经是虚拟地址,整个 CPU 正式运行在 S-EL1 虚拟地址空间。
阶段 4:主 CPU 内核全量初始化(C 语言阶段)
bl boot_init_primary_early /* 早期平台硬件初始化:时钟、GPIO、GIC、TZASC */ #ifdef CFG_MEMTAG init_memtag_per_cpu /* 本CPU开启MTE内存标签检查 */ #endif bl boot_init_primary_late /* 核心子系统初始化:调度器、加密框架、安全存储 */ bl boot_init_primary_runtime /* 运行时服务初始化:TA加载器、驱动框架 */ #ifdef CFG_CORE_PAUTH /* 开启指针认证PAUTH */ ... #endif bl boot_init_primary_final /* 收尾初始化:内置TA加载、服务注册 */阶段 5:初始化收尾,交付向量表,返回 EL3
5.1 收尾清理
/* 刷缓存,保证次级CPU上电时内存数据一致 */ adr_l x0, __text_start adr_l x1, boot_cached_mem_end ldr x1, [x1] sub x1, x1, x0 bl dcache_cleaninv_range /* 释放boot线程,允许后续复用 */ bl thread_clr_boot_thread5.2 握手 ATF,交付向量表
/* 计算向量表的物理地址(减去虚拟偏移),保证EL3用物理地址能正确索引 */ ldr x0, boot_mmu_config + CORE_MMU_CONFIG_MAP_OFFSET adr x1, thread_vector_table sub x1, x1, x0 /* x0 = 初始化完成SMC号,x1 = 向量表基地址 */ mov x0, #TEESMC_OPTEED_RETURN_ENTRY_DONE smc #0 panic_at_smc_return- 核心细节:thread_vector_table 是虚拟地址,但 ATF 侧此时用物理地址跳转,因此需要减去虚拟地址偏移,得到物理地址后再通过 x1 传递给 EL3。
- 执行 smc #0 陷入 EL3,回到 OPTEED 的 opteed_smc_handler,命中 TEESMC_OPTEED_RETURN_ENTRY_DONE 分支。
阶段 6:OPTEED 侧完成绑定
二、次级 CPU 热启动全链路(PSCI CPU_ON)
阶段 1:OPTEED 侧触发与路由
- 非安全世界发起 PSCI_CPU_ON SMC,ATF PSCI 模块校验参数,调用 OPTEED 的 cpu_on 电源管理钩子。
- OPTEED 为目标 CPU 构造 S-EL1 上下文:ELR_EL3 设置为向量表中 cpu_on_entry 的物理地址,配置 SPSR 为 S-EL1。
- 给目标 CPU 上电、释放复位,CPU 从复位向量进入 EL3 暖启动入口,完成 EL3 基础初始化后,eret 切入 S-EL1 的 cpu_on_handler。
阶段 2:OP-TEE 侧cpu_on_handler本地初始化
FUNC cpu_on_handler , : mov x19, x0 mov x20, x1 mov x21, x30 /* 1. 设置本地异常向量表 */ adr x0, reset_vect_table msr vbar_el1, x0 isb /* 2. 配置SCTLR_EL1与PAN */ set_sctlr_el1 isb #ifdef CFG_PAN init_pan #endif /* 3. 开启异常接收 */ msr daifclr, #DAIFBIT_ABT /* 4. 开启MMU,切换到虚拟地址空间 */ bl __get_core_pos bl enable_mmu /* 5. 初始化本CPU栈与thread_core_local */ #if defined(CFG_DYN_CONFIG) ... #else set_sp #endif /* 6. 初始化本CPU安全特性 */ #ifdef CFG_MEMTAG init_memtag_per_cpu #endif #ifdef CFG_CORE_PAUTH init_pauth_secondary_cpu #endif /* 7. 调用C语言CPU上电处理函数 */ mov x0, x19 mov x1, x20 b boot_cpu_on_handler END_FUNC cpu_on_handler与冷启动的核心差异
- 无全局初始化:不拷贝镜像、不清 BSS、不构建页表、不初始化内存分配器、不初始化内核子系统——这些全局资源主 CPU 已经完成,次级 CPU 直接复用。
- 仅做本地初始化:
- 配置本 CPU 的系统寄存器、异常向量表
- 开启本 CPU 的 MMU、缓存、MTE、PAUTH 等硬件特性
- 初始化本 CPU 的私有栈与 thread_core_local 结构
- 初始化本 CPU 的 GIC 中断接口、缓存一致性
- 收尾返回:boot_cpu_on_handler 完成后,返回 SMC 号 TEESMC_OPTEED_RETURN_ON_DONE,陷入 EL3。
阶段 3:OPTEED 侧收尾
三、运行时 SMC 唤醒服务全链路
完整调用链路
1.非安全 SMC 陷入 EL3:非安全世界执行 smc #0,硬件陷入 EL3,ATF 根据 OEN 路由到 opteed_smc_handler。
2.OPTEED 路由转发:
- 保存非安全上下文
- 根据 SMC 类型(Fast/Yield),从已保存的 optee_vector_table 中取出对应入口地址
- 将参数拷贝到安全上下文
- eret 切入 S-EL1,跳转到向量表对应的 vector_fast_smc_entry / vector_std_smc_entry
/* * Vector table supplied to ARM Trusted Firmware (ARM-TF) at * initialization. * * Note that ARM-TF depends on the layout of this vector table, any change * in layout has to be synced with ARM-TF. */ FUNC thread_vector_table , : , .identity_map, , nobti b vector_std_smc_entry b vector_fast_smc_entry b vector_cpu_on_entry b vector_cpu_off_entry b vector_cpu_resume_entry b vector_cpu_suspend_entry b vector_fiq_entry b vector_system_off_entry b vector_system_reset_entry END_FUNC thread_vector_table DECLARE_KEEP_PAGER thread_vector_table LOCAL_FUNC vector_std_smc_entry , : , .identity_map readjust_pc bl thread_handle_std_smc /* * Normally thread_handle_std_smc() should return via * thread_exit(), thread_rpc(), but if thread_handle_std_smc() * hasn't switched stack (error detected) it will do a normal "C" * return. */ mov w1, w0 ldr x0, =TEESMC_OPTEED_RETURN_CALL_DONE smc #0 /* SMC should not return */ panic_at_smc_return END_FUNC vector_std_smc_entry LOCAL_FUNC vector_fast_smc_entry , : , .identity_map readjust_pc sub sp, sp, #THREAD_SMC_ARGS_SIZE store_xregs sp, THREAD_SMC_ARGS_X0, 0, 7 mov x0, sp bl thread_handle_fast_smc load_xregs sp, THREAD_SMC_ARGS_X0, 1, 8 add sp, sp, #THREAD_SMC_ARGS_SIZE ldr x0, =TEESMC_OPTEED_RETURN_CALL_DONE smc #0 /* SMC should not return */ panic_at_smc_return END_FUNC vector_fast_smc_entry调用到thread_handle_std_smc函数中:
uint32_t thread_handle_std_smc(uint32_t a0, uint32_t a1, uint32_t a2, uint32_t a3, uint32_t a4, uint32_t a5, uint32_t a6 __unused, uint32_t a7 __maybe_unused) { uint32_t rv = OPTEE_SMC_RETURN_OK; thread_check_canaries(); if (IS_ENABLED(CFG_NS_VIRTUALIZATION) && virt_set_guest(a7)) return OPTEE_SMC_RETURN_ENOTAVAIL; /* * thread_resume_from_rpc() and thread_alloc_and_run() only return * on error. Successful return is done via thread_exit() or * thread_rpc(). */ if (a0 == OPTEE_SMC_CALL_RETURN_FROM_RPC) { thread_resume_from_rpc(a3, a1, a2, a4, a5); rv = OPTEE_SMC_RETURN_ERESUME; } else { thread_alloc_and_run(a0, a1, a2, a3, 0, 0); rv = OPTEE_SMC_RETURN_ETHREAD_LIMIT; } if (IS_ENABLED(CFG_NS_VIRTUALIZATION)) virt_unset_guest(); return rv; }thread_resume_from_rpc
void thread_resume_from_rpc(uint32_t thread_id, uint32_t a0, uint32_t a1, uint32_t a2, uint32_t a3) { size_t n = thread_id; struct thread_core_local *l = thread_get_core_local(); bool found_thread = false; assert(l->curr_thread == THREAD_ID_INVALID); thread_lock_global(); if (n < CFG_NUM_THREADS && threads[n].state == THREAD_STATE_SUSPENDED) { threads[n].state = THREAD_STATE_ACTIVE; found_thread = true; } thread_unlock_global(); if (!found_thread) return; l->curr_thread = n; if (threads[n].have_user_map) { core_mmu_set_user_map(&threads[n].user_map); if (threads[n].flags & THREAD_FLAGS_EXIT_ON_FOREIGN_INTR) tee_ta_ftrace_update_times_resume(); } if (is_user_mode(&threads[n].regs)) tee_ta_update_session_utime_resume(); /* * Return from RPC to request service of a foreign interrupt must not * get parameters from non-secure world. */ if (threads[n].flags & THREAD_FLAGS_COPY_ARGS_ON_RETURN) { copy_a0_to_a3(&threads[n].regs, a0, a1, a2, a3); threads[n].flags &= ~THREAD_FLAGS_COPY_ARGS_ON_RETURN; } thread_lazy_save_ns_vfp(); if (threads[n].have_user_map) ftrace_resume(); l->flags &= ~THREAD_CLF_TMP; thread_resume(&threads[n].regs); /*NOTREACHED*/ panic(); }线程对象结构:
//实际上就是ARM 64的cpu寄存器组 struct thread_ctx_regs { uint64_t sp; uint64_t pc; uint64_t cpsr; uint64_t x[31]; uint64_t tpidr_el0; #if defined(CFG_TA_PAUTH) || defined(CFG_CORE_PAUTH) uint64_t apiakey_hi; uint64_t apiakey_lo; #endif }; //线程上下文对象 struct thread_ctx { struct thread_ctx_regs regs; enum thread_state state; vaddr_t stack_va_end; uint32_t flags; struct core_mmu_user_map user_map; bool have_user_map; #if defined(ARM64) || defined(RV64) vaddr_t kern_sp; /* Saved kernel SP during user TA execution */ #endif #ifdef CFG_CORE_PAUTH struct thread_pauth_keys keys; #endif #ifdef CFG_WITH_VFP struct thread_vfp_state vfp_state; #endif void *rpc_arg; struct mobj *rpc_mobj; struct thread_shm_cache shm_cache; struct thread_specific_data tsd; };函数的最后一步真正完成线程恢复的位置
清除临时栈标志
THREAD_CLF_TMP表示当前 CPU 使用临时栈;即将恢复业务线程,要切换到线程私有栈,因此先清除该标志。执行线程恢复调用汇编函数
thread_resume,传入线程的寄存器上下文结构体。该函数会完成:- 从上下文中恢复所有通用寄存器、系统寄存器、栈指针;
- 恢复 SPSR 程序状态寄存器;
- 执行
ERET异常返回,跳转到线程被挂起的指令位置,继续执行后续代码。
不可达兜底
thread_resume不会返回(直接 ERET 回到线程现场),因此后面的panic()是不可达代码;如果程序执行到这里,说明上下文恢复逻辑出现严重异常,直接触发系统崩溃,防止异常流继续执行带来安全风险。
3.OP-TEE 侧业务处理:
- 执行 readjust_pc 修正 ASLR 地址偏移(若开启)
- Fast SMC:原子执行快速调用处理函数,全程关中断、无线程切换
- Std SMC:分配线程、切换线程栈,执行 TA 调用、加密运算等耗时操作,支持中断抢占与 RPC 回调
4.结果返回:处理完成后执行 smc #0 陷入 EL3,OPTEED 将结果写回非安全上下文,eret 返回非安全世界。
为什么运行时不走_start?
- _start 是一次性启动入口,负责从裸机搭建完整运行环境;环境搭建完成后,重复执行会破坏已初始化的全局状态、内存布局、线程调度器。
- 运行时 OP-TEE 已经处于 S-EL1 虚拟地址空间,MMU、栈、中断、调度器全部就绪,只需通过线程向量表直接进入业务处理即可,无需重复初始化。
核心设计总结(Android 安全视角)
- 分层解耦:启动入口与运行时入口分离,全局初始化与本地初始化分离,既保证启动流程清晰,也避免热启动重复初始化带来的安全风险与性能损耗。
- 安全左移:MTE、PAUTH、PAN、WXN、BTI 等安全特性在启动最早期就开启,从第一条指令开始就处于安全防护状态,最大程度缩小攻击窗口。
- 恒等映射平滑过渡:所有启动早期代码放在 .identity_map 段,保证 MMU 开启前后地址连续,实现无断点的物理地址→虚拟地址切换,是嵌入式操作系统启动的经典设计。
- 严格 ABI 约定:与 ATF 仅通过 SMC 号、寄存器传参、固定偏移向量表交互,无编译期符号依赖,完美适配 Android 碎片化的平台与版本差异。