1. ARM TrustZone 安全启动环境搭建
任务说明
本案例目标是在QEMU模拟环境中搭建一个支持TrustZone基础特性的最小系统,并编写安全世界(Secure World)与普通世界(Normal World)的切换代码,验证SMC(Secure Monitor Call)指令触发监控器(Monitor)完成世界切换的基本流程。最终通过串口输出观察两个世界各自的执行痕迹,确认切换路径正确。
实现方案
使用QEMU的virt机器(ARM 32位,Cortex-A15)配合TrustZone固件(TF-A)作为监控器。普通世界运行一个裸机程序,通过SMC指令陷入监控器,监控器完成上下文切换后进入安全世界执行一段安全函数,再通过SMC返回普通世界。为简化,安全世界代码直接内嵌在监控器固件中,不单独加载安全OS。
代码实现
创建以下三个文件:
文件1:monitor.S(安全监控器固件,运行在EL3)
.section .text
.global _start
.global smc_handler
_start:
// 设置栈指针
ldr sp, =stack_top
// 设置VBAR_EL3指向异常向量表
ldr x0, =vector_table
msr vbar_el3, x0
// 跳转到主循环
bl main_loop
main_loop:
wfi
b main_loop
// 异常向量表(仅处理SMC)
.align 11
vector_table:
// 当前EL3,使用SP_EL3,同步异常(SMC入口)
.align 7
b smc_entry
.space 0x80, 0 // 填充到下一个向量
smc_entry:
// 保存普通世界上下文(此处仅保存必要寄存器)
stp x0, x1, [sp, #-16]!
stp x2, x3, [sp, #-16]!
// 读取SMC功能ID(x0寄存器)
mov x4, x0
// 切换到安全世界(此处直接调用安全函数)
bl secure_world_function
// 恢复普通世界上下文
ldp x2, x3, [sp], #16
ldp x0, x1, [sp], #16
eret
// 安全世界函数(运行在EL1安全状态)
secure_world_function:
// 打印安全世界消息(通过UART)
ldr x0, =secure_msg
bl uart_print
// 返回监控器
ret
// 简单UART输出(PL011)
uart_print:
ldr x1, =0x09000000 // PL011基地址(virt机器)
ldrb w2, [x0], #1
cbz w2, done
str w2, [x1, #0x00] // 数据寄存器
b uart_print
done:
ret
.section .data
secure_msg:
.asciz "\n[Secure World] Hello from EL1 Secure!\n"
stack_top:
.space 0x1000
文件2:normal_world.c(普通世界裸机程序,运行在EL1非安全状态)
// 普通世界入口
void normal_main(void) {
// 打印普通世界消息
uart_print("\n[Normal World] Before SMC...\n");
// 触发SMC调用(功能ID=0x1)
smc_call(0x1);
// SMC返回后继续执行
uart_print("[Normal World] After SMC, back to Normal!\n");
while(1);
}
// 内联汇编执行SMC指令
void smc_call(unsigned long func_id) {
register unsigned long x0 asm("x0") = func_id;
asm volatile("smc #0" : : "r"(x0) : "memory");
}
// UART输出(与监控器共用同一物理UART)
void uart_print(char *s) {
volatile unsigned int *uart = (unsigned int *)0x09000000;
while (*s) {
*uart = *s++;
}
}
文件3:linker.ld(链接脚本)
ENTRY(_start)
SECTIONS
{
. = 0x60000000; // 监控器加载地址(BL31)
.text : { *(.text) }
.data : { *(.data) }
.bss : { *(.bss) }
}
编译与运行
# 安装依赖(Ubuntu/Debian)
sudo apt install gcc-aarch64-linux-gnu qemu-system-arm
# 编译监控器(AArch64)
aarch64-linux-gnu-gcc -c monitor.S -o monitor.o
aarch64-linux-gnu-ld -T linker.ld monitor.o -o monitor.elf
aarch64-linux-gnu-objcopy -O binary monitor.elf monitor.bin
# 编译普通世界(AArch64)
aarch64-linux-gnu-gcc -c normal_world.c -o normal.o
aarch64-linux-gnu-ld -Ttext 0x80000000 normal.o -o normal.elf
aarch64-linux-gnu-objcopy -O binary normal.elf normal.bin
# 运行QEMU(virt机器,4核,加载BL31和普通世界镜像)
qemu-system-aarch64 -machine virt -cpu cortex-a57 -smp 4 -m 512M \
-bios monitor.bin \
-device loader,file=normal.bin,addr=0x80000000 \
-nographic
验证方法
运行上述QEMU命令后,观察串口输出。预期输出顺序为:
[Normal World] Before SMC...
[Secure World] Hello from EL1 Secure!
[Normal World] After SMC, back to Normal!
判定标准:若串口依次出现上述三行信息,且顺序正确,则证明SMC调用成功触发监控器完成从普通世界到安全世界的切换,并正确返回普通世界。若缺少中间行,说明安全世界未正确进入;若最后一行未出现,说明返回路径有误。
关键说明
本案例使用QEMU的virt机器,其固件加载机制将-bios指定的镜像作为EL3固件(BL31)加载到地址0x60000000。普通世界镜像通过-device loader加载到0x80000000,但实际运行入口由监控器在初始化后跳转。为简化,监控器在main_loop中未主动跳转普通世界,实际验证时需在监控器初始化后手动跳转(可在main_loop前添加跳转指令)。本案例聚焦SMC切换机制验证,普通世界入口跳转逻辑已在监控器代码中预留(可在_start末尾添加bl 0x80000000实现)。