当前位置: 首页 > news >正文

TEE-OS学习轨迹第十三篇:OP-TEE OS 编译构建体系架构

OP-TEE OS 构建体系全解析

基于 PLATFORM=vexpress-qemu_armv8a 平台、原生 kern.ld.S 链接脚本、tee-pager_v2.bin 分页镜像产物,从 Makefile 分层架构、编译流程、链接全链路、链接脚本安全设计四个维度完整拆解,同步对齐 TF-A 可信启动与 Android TEE 安全规范。

一、构建体系总览与正确目录结构

OP-TEE 是 ARM TrustZone 架构下的可信执行环境(TEE),对应 Android 系统的 TEE 服务子系统,采用CFG 宏驱动 + 分层 Makefile 继承的构建体系,所有功能裁剪、内存布局、硬件适配均通过编译宏控制,最终生成可被 TF-A 作为 BL32 加载的安全固件。

核心目录与文件对应关系

optee_os/ ├── Makefile # 顶层构建入口 ├── core/ │ └── arch/arm/ │ ├── arm.mk # ARM架构通用编译规则 │ ├── cpu/ # CPU核专属优化(cortex-a57等) │ ├── kernel/ │ │ ├── kern.ld.S # 你提供的核心态主链接脚本 │ │ ├── text_unpaged.ld.S # 分页:不可换页代码列表 │ │ ├── rodata_unpaged.ld.S # 分页:不可换页只读数据列表 │ │ ├── text_init.ld.S # 分页:初始化专属代码列表 │ │ └── rodata_init.ld.S # 分页:初始化专属数据列表 │ └── plat-vexpress/ # vexpress 平台家族专属目录 │ ├── conf.mk # 平台主配置文件(按 flavor 分支) │ ├── sub.mk # 平台源码列表与编译旗标 │ ├── main.c # 平台初始化入口 │ └── platform_config.h # 平台硬件参数头文件 ├── lib/ # 系统库 ├── ta/ # 内置可信应用 ├── ldelf/ # TA用户态加载器 └── out/ └── arm-plat-vexpress/ # 编译产物输出目录 ├── core/ ├── tee.elf └── tee-pager_v2.bin

二、Makefile 分层架构与目标生成逻辑

OP-TEE 采用三层继承式 Makefile 设计,自上而下逐层收敛配置、聚合源码,平台配置决定硬件基线,功能宏决定模块裁剪。

1. 顶层 Makefile:全局入口与调度

核心职责分为四步,是所有编译目标的总调度:
  1. 参数解析:解析命令行传入的 PLATFORM=vexpress-qemu_armv8a、DEBUG=、CROSS_COMPILE=、自定义 CFG_xxx 等参数
  2. 平台定位:根据 PLATFORM 变量,自动匹配并引入 core/arch/arm/$(PLATFORM).mk 平台配置文件
  3. 模块聚合:依次引入 core/core.mk、lib/lib.mk、ta/ta.mk、ldelf/ldelf.mk,聚合所有参与编译的源码与编译规则
  4. 目标分发:将 all 目标拆解为核心镜像、TA、ldelf 等子目标,按依赖顺序串行执行
核心逻辑片段:
# 加载指定平台的配置文件 include core/arch/arm/$(PLATFORM).mk # 加载各子系统构建规则 include core/core.mk include lib/lib.mk include ta/ta.mk include ldelf/ldelf.mk # 最终目标:生成分页固件 + 所有内置TA all: $(link-out-dir)/tee-pager_v2.bin ta_all ldelf_all

2. 平台配置层:硬件适配的核心

conf.mk:硬件配置总入口
这是平台配置的核心文件,内部通过 PLATFORM_FLAVOR 做条件分支,为不同版型设置专属参数。典型结构如下:
# 默认版型 PLATFORM_FLAVOR ?= qemu_virt # 按版型分支配置 ifeq ($(PLATFORM_FLAVOR),qemu_armv8a) $(call force,CFG_TZDRAM_START,0x0E100000) $(call force,CFG_TZDRAM_SIZE,0x01000000) $(call force,CFG_TEE_CORE_NB_CORE,4) $(call force,CFG_WITH_PAGER,y) $(call force,CFG_GICV2,y) $(call force,CFG_PL011,y) endif ifeq ($(PLATFORM_FLAVOR),fvp) # FVP 板子的专属配置... endif # 引入 ARMv8 通用CPU配置 include core/arch/arm/cpu/cortex-armv8-0.mk
  • BL32 的加载地址 0x0E100000、分页模式、核心数,全部在此文件中定义
  • $(call force,...) 表示强制赋值,不可被外部参数覆盖,保证硬件参数一致性
sub.mk:平台源码列表
列出该平台需要编译的源文件与专属头文件路径:
platform-srcs += core/arch/arm/plat-vexpress/main.c platform-srcs += drivers/serial/pl011.c platform-srcs += drivers/gic/gicv2.c global-incdirs-y += core/arch/arm/plat-vexpress/

3. 核心构建层:core/core.mk

聚合所有核心态(S-EL1)源码,通过 CFG 宏做条件编译,核心逻辑:
# ARM64架构专属汇编入口、异常向量、上下文切换代码 core-arm64-srcs += core/arch/arm/kernel/entry_a64.S core-arm64-srcs += core/arch/arm/kernel/thread_a64.S # 通用核心子系统 core-subdirs += core/kernel core/mm core/tee core/crypto # 分页模式专属源码 ifeq ($(CFG_WITH_PAGER),y) core-srcs += core/arch/arm/kernel/pager.c core-srcs += core/arch/arm/kernel/tee_pager.c endif # 平台专属源码加入编译列表 core-srcs += $(platform-srcs)

完整包含链路(自顶向下)

顶层 Makefile ↓ core/core.mk ↓ core/arch/arm/arm.mk ↓ core/arch/arm/plat-vexpress/conf.mk ← 根据 PLATFORM_FLAVOR 加载对应硬件配置 ↓ core/arch/arm/plat-vexpress/sub.mk ← 加入平台专属源码 ↓ lib/lib.mk、ta/ta.mk、ldelf/ldelf.mk

顶层 Makefile 核心调度逻辑

参数解析与拆分:解析命令行参数,自动拆分 PLATFORM 为平台名与 flavor

# 自动拆分 PLATFORM=xxx-yyy 为 PLATFORM=xxx + PLATFORM_FLAVOR=yyy ifneq (,$(findstring -,$(PLATFORM))) ops := $(join PLATFORM PLATFORM_FLAVOR,$(addprefix =,$(subst -, ,$(PLATFORM)))) $(foreach op,$(ops),$(eval override $(op))) endif
  1. 架构与平台加载:根据 ARCH=arm 引入 ARM 架构规则,再根据 PLATFORM 引入对应平台的 conf.mk
  2. 模块聚合:依次引入核心、库、TA、加载器的构建规则,聚合所有源码
  3. 目标生成:定义 all 目标,依赖核心固件、所有 TA、ldelf 加载器

4. 分页模式专属打包规则

当 CFG_WITH_PAGER=y 时,不会直接生成单一 tee.bin,而是走分段拆分+头部封装的专用流程:
  1. 先链接出完整带符号的 tee.elf
  2. 用 objcopy 拆分出三类独立二进制:常驻段、初始化段、可分页段
  3. 通过 Python 打包脚本拼接 pager 头部、各段数据,生成 tee-pager_v2.bin
  4. 运行时由 pager 模块捕获缺页异常,按需加载可分页代码,最小化常驻内存攻击面

三、分特权级编译流程与安全编译设计

OP-TEE 严格按特权级分离编译,核心态(S-EL1)、用户态 TA(S-EL0)、加载器各自使用独立编译选项,所有编译旗标对齐 Android TEE 安全规范。

1. 编译选项优先级

所有旗标按以下层级叠加,后者覆盖前者,保证平台与安全选项优先级最高:
工具链默认选项 → ARM架构通用选项 → vexpress通用配置 → QEMU专属配置 → CFG功能宏 → 用户自定义参数

交叉编译工具链与 TF-A 一致,使用 aarch64-none-elf-gcc,通过 CROSS_COMPILE 参数指定。

2. 核心态(S-EL1)编译:最高安全等级

运行在安全异常等级 1,是 OP-TEE 的可信内核,编译选项偏向最小攻击面与安全加固:
  • 架构约束:-march=armv8-a -mstrict-align -mgeneral-regs-only,禁用浮点单元,精简安全态上下文切换,避免浮点寄存器泄露
  • 安全加固:-fstack-protector-all -fno-common -fno-exceptions -fno-unwind-tables,开启全量栈保护、禁止公共块、禁用异常机制,符合 Android 安全编译规范
  • 位置无关:-fpie,核心态编译为位置无关可执行,支持 ASLR 地址随机化
  • 优化策略:DEBUG 模式 -O0 -g 关闭优化、保留调试符号;Release 模式 -Os -flto 链接时优化,最小化镜像体积与攻击面
  • 产物输出:所有 .c/.S 编译为 .o 目标文件,输出到 out/arm-plat-vexpress-qemu_armv8a/core/ 对应子目录,自动生成 .d 依赖文件支持增量编译

3. 用户态(S-EL0)编译:TA 沙箱约束

所有内置 TA 与 ldelf 加载器运行在安全用户态,编译约束更严格:
  • 纯 PIC 编译:-fPIC -fPIE,位置无关代码,运行时动态加载到 TA 内存池,地址不固定
  • 沙箱隔离:禁用系统调用、禁用全局变量共享、强制栈边界检查,保证单个 TA 被攻破后无法越权访问内核或其他 TA
  • 自动签名:编译完成后用 TA 签名私钥对镜像签名,加载时强制验签,防止 TA 被篡改

四、完整链接执行流程

kern.ld.S 不会直接传给链接器,必须经过「预处理 → 全量链接 → 段拆分 → 封装打包」四步,最终生成可被 TF-A 加载的固件。

步骤1:C 预处理器生成纯链接脚本

OP-TEE 的链接脚本是支持 C 预处理器的汇编级文件,必须先展开所有宏、头文件、条件编译,才能生成可用的 ld 脚本:
cpp -P \ -Icore/include -Icore/arch/arm/include \ -Icore/arch/arm/plat-vexpress/ \ -DCFG_WITH_PAGER=y \ -DCFG_TZDRAM_START=0x0E100000 \ -DTEE_LOAD_ADDR=0x0E100000 \ -DSMALL_PAGE_SIZE=4096 \ core/arch/arm/kernel/kern.ld.S \ > out/arm-plat-vexpress-qemu_armv8a/core/kern.ld
这一步是配置驱动内存布局的核心:平台差异、功能开关、安全特性全部通过宏展开体现在最终链接规则中。

步骤2:全量链接生成 ELF 文件

调用交叉链接器,使用生成的 kern.ld,把所有核心态 .o、系统库 .a 合并为完整的带符号 ELF:
aarch64-none-elf-ld -T kern.ld -Map tee.map \ --start-group $(core_objs) $(lib_objs) --end-group \ -o tee.elf
  • 生成的 tee.elf 包含完整符号表、调试信息、段信息,是调试与安全分析的核心文件
  • tee.map 是内存映射文件,可查看每个符号、每个段的精确地址,常用于安全审计与边界校验

步骤3:分页模式段拆分

开启分页后,使用 objcopy 按段属性拆分出三类独立二进制:
# 提取常驻核心段(unpaged):系统运行全程驻留内存 aarch64-none-elf-objcopy -O binary \ -j .text -j .rodata -j .data -j .bss \ tee.elf core.bin # 提取初始化段(init):启动后可回收为堆内存 aarch64-none-elf-objcopy -O binary \ -j .text_init -j .rodata_init \ tee.elf init.bin # 提取可分页段(pageable):按需加载,不常驻内存 aarch64-none-elf-objcopy -O binary \ -j .rodata_pageable -j .text_pageable \ tee.elf pageable.bin

步骤4:封装生成最终固件

调用专用 Python 打包脚本,按 Pager V2 格式拼接头部元数据、常驻段、初始化段、可分页段,生成最终的 tee-pager_v2.bin。该文件就是放入 FIP 包的 BL32 镜像,日志中 BL32 大小 0x7B238 就是该文件的精确字节数。

五、kern.ld.S 链接脚本深度解析

基于原生源码,从地址锚点、段布局、安全设计、分页机制四个维度逐段拆解。

1. 头部配置与依赖

#include <mm/core_mmu.h> // 提供页大小、MMU配置宏 #include <platform_config.h> // 平台硬件参数,核心地址全部来自这里 #include <util.h>
#include <mm/core_mmu.h> // 提供页大小、MMU配置宏 #include <platform_config.h> // 平台硬件参数,核心地址全部来自这里 #include <util.h> OUTPUT_FORMAT(CFG_KERN_LINKER_FORMAT) // elf64-littleaarch64 OUTPUT_ARCH(CFG_KERN_LINKER_ARCH) // aarch64 ENTRY(_start) // 镜像入口函数
  • 入口点 _start 对应汇编启动入口,地址等于镜像基地址 TEE_LOAD_ADDR,也就是 TF-A 跳转到 BL32 的目标地址
  • 所有宏都来自头文件与平台配置,保证链接脚本和代码使用同一套地址定义,避免不一致风险

2. 核心地址锚点(可信启动对齐关键)

SECTIONS { . = TEE_LOAD_ADDR; ASSERT(!(TEE_LOAD_ADDR & (SMALL_PAGE_SIZE - 1)), "text start should be page aligned") __text_start = .;
  • 地址锚点:整个镜像的链接起始地址由 TEE_LOAD_ADDR 决定,直接映射平台配置的 CFG_TZDRAM_START=0x0E100000,和 TF-A BL32 加载地址 100% 对齐
  • 页对齐断言:强制要求起始地址按 4KB 页边界对齐,符合 MMU 内存映射硬件要求,是内存安全的基础
  • __text_start 作为代码段起始符号,供代码中做边界校验使用

3. 通用常驻段(全模式生效)

这部分代码和数据系统运行全程常驻内存,不会被换出,是最小可信攻击面。
(1).text可执行代码段
.text : { KEEP(*(.text._start)) // 强制保留启动入口,放在最开头 __identity_map_init_start = .; *(.identity_map.data) // MMU开启前访问的数据(恒等映射) *(.identity_map .identity_map.*) // MMU开启前执行的初始化代码 __identity_map_init_end = .; #ifdef CFG_WITH_PAGER *(.text) #include <text_unpaged.ld.S> // 仅引入不可换页的核心代码 #else *(.text .text.*) // 非分页模式包含全部代码 #endif . = ALIGN(8); } __text_end = .;
  • 安全设计:代码段只读可执行,和后续数据段权限严格隔离,实现 NX(不可执行)防护,防止代码注入攻击
  • 恒等映射区:MMU 开启前运行的代码,物理地址=虚拟地址,是启动初期的临界安全区
  • text_unpaged.ld.S 枚举了所有必须常驻的核心代码(异常向量、调度核心、pager 自身逻辑),分页模式下不会被换出,避免递归缺页异常
(2).rodata只读数据段
.rodata : ALIGN(8) { __rodata_start = .; #ifdef CFG_WITH_PAGER *(.rodata .rodata.__unpaged .rodata.__unpaged.*) #include <rodata_unpaged.ld.S> // 仅保留不可换页的核心常量 #else *(.rodata .rodata.*) KEEP(*(SORT(.scattered_array*))); // 散列注册数组(驱动、TA自动注册) #endif . = ALIGN(8); __rodata_end = .; }
  • 分页模式下只保留核心常量、配置表,大部分只读数据放到可分页段,减小常驻体积
  • scattered_array 是 OP-TEE 的核心注册机制,驱动、TA 接口通过散列数组自动注册,无需手动修改入口表,降低代码修改引入的安全风险
(3)数据与未初始化段
段名
属性
作用
安全设计
.data
RW 可读写
已初始化全局变量、静态变量
双地址设计(ROM地址/RAM地址),支持重定位与地址随机化
.bss
RW 可读写
未初始化全局变量
不占用 bin 文件体积,启动时清零,避免残留敏感数据
.nozi
NOLOAD 不加载
MMU页表、内核栈、临时缓冲区
不初始化,避免清零开销;每个CPU核心独立栈空间,防止栈溢出跨核破坏
.heap1
NOLOAD 不加载
内核堆内存
按16KB大页对齐,匹配MMU L1页表粒度

4. 分页模式专属段(CFG_WITH_PAGER=y

这是 tee-pager_v2.bin 的核心设计,通过分段换页最小化常驻内存攻击面。
(1)初始化段
.text_init : { __text_init_start = .; #include <text_init.ld.S> // 初始化专属代码列表 KEEP(*(.text.startup.*)); . = ALIGN(8); __text_init_end = .; } .rodata_init : { __rodata_init_start = .; #include <rodata_init.ld.S> __rodata_init_end = .; }
  • 仅在系统启动阶段执行,包含驱动初始化、外设配置、服务注册代码
  • 初始化完成后,该段内存可以被回收用作堆内存,进一步释放安全内存空间
(2)可分页段
.rodata_pageable : ALIGN(8) { __rodata_pageable_start = .; *(.rodata*) __rodata_pageable_end = .; } .text_pageable : ALIGN(8) { __text_pageable_start = .; *(.text*) . = ALIGN(SMALL_PAGE_SIZE); __text_pageable_end = .; }
  • 这部分代码/数据不常驻内存,运行时由 pager 模块根据缺页异常按需加载到物理页
  • 所有段按 4KB 页边界对齐,支持 MPU/MMU 按页设置权限,实现细粒度内存保护
(3)分页安全断言
脚本末尾包含强制内存校验:
ASSERT(TEE_LOAD_ADDR >= TEE_RAM_START, "Load address before start of physical memory") ASSERT(TEE_LOAD_ADDR < (TEE_RAM_START + TEE_RAM_PH_SIZE), "Load address after end of physical memory") ASSERT((TEE_RAM_START + TEE_RAM_PH_SIZE - __init_end) > SMALL_PAGE_SIZE * 2 + (__pageable_end - __pageable_start) / 4096 * 32 + ..., "Too few free pages to initialize paging")
  • 校验加载地址在安全物理内存范围内,防止越界
  • 校验剩余空闲页足够支撑分页表、重定位表开销,避免启动时内存不足导致的安全漏洞

5. 收尾:虚拟内存符号与丢弃段

(1)虚拟内存映射符号
脚本末尾定义了一系列 __vcore_* 开头的全局符号,对应 MMU 映射后的虚拟地址区域:
  • __vcore_unpg_rx_start/size:不可换页可执行区域的虚拟地址与大小
  • __vcore_unpg_rw_start/size:不可换页可读写区域的虚拟地址与大小
  • 这些符号供 MMU 初始化代码使用,按区域设置不同的页表权限(RX/RO/RW),实现段级权限隔离
(2)重定位与丢弃校验
  • .rel/.rela 重定位段:支持 ASLR 与物理重定位;非重定位模式下断言其大小为0,防止意外的重定位项引入安全风险
  • /DISCARD/ 丢弃段:删除 .comment、.eh_frame、.interp 等无用段,最小化镜像体积与攻击面

六、Android 安全视角:设计价值与 TF-A 可信启动对齐

1. 信任链的严格对齐

  • 加载地址 TEE_LOAD_ADDR 与 TF-A BL32 加载地址严格一致,入口点 _start 位于镜像起始位置,保证 BL31 跳转后直接执行可信入口
  • BL2 验签的对象就是完整的 tee-pager_v2.bin 全量字节,验签范围与镜像文件逐字节对应,信任链连续无断点

2. 内存安全的多层防护

符合 Android TEE 安全规范要求:
  • 段权限隔离:代码段可执行不可写、数据段可写不可执行,实现 NX 防护
  • 栈隔离:每个 CPU 核心独立栈空间,链接时固定地址,避免栈溢出跨核破坏
  • 页对齐与MPU支持:所有段按页边界对齐,支持按页设置权限,实现细粒度内存保护
  • ASLR 支持:位置无关编译 + 重定位段,支持地址随机化,降低漏洞利用成功率

3. 分页机制的安全意义

分页模式不仅是节省内存,更重要的是最小化常驻攻击面
  • 仅核心异常处理、调度、pager 自身代码常驻内存,大部分功能代码按需加载
  • 即使某段代码存在漏洞,也只有被加载时才会暴露在攻击面中,提升整体安全水位
http://www.jsqmd.com/news/1065674/

相关文章:

  • 3个简单步骤解锁AtlasOS GPU隐藏性能:让你的显卡发挥100%实力
  • 2026年京东云 618 活动 Hermes Agent/OpenClaw配置Token Plan部署保姆级攻略
  • 矢量干涉整形:单次曝光实现无散斑全息显示的技术原理与实践
  • 知识图谱与大语言模型:破解制造业AI黑盒,实现可解释决策
  • 资深刑事诉讼律师谷东,费用合理,服务优质 - mypinpai
  • MCP协议详解:让AI听懂工程上下文的通信标准
  • Debian 10 自建CA实战:从OpenSSL到easy-rsa的可信根构建
  • C-GenReg:基于生成式先验的零样本点云配准原理与实践
  • ColdFire DSP库实战:IIR滤波器在嵌入式传感器信号处理中的应用
  • 2026年2-6月连续5月成为最佳商城小程序搭建工具全面测评
  • Ubuntu 12.04 LEMP搭建实战:nginx配置与mysql安装配置教程
  • 济南AI培训机构哪家好,首选莫瑶教育 - 职业学校推荐官
  • Ubuntu 18.04 搭建稳定 Python 编程环境实战指南
  • 2026年省心的热水器生产厂家行业全景分析 - mypinpai
  • Intel微码更新与VRS/L1D侧信道攻击防护实战指南
  • Debian 10私有CA实战:构建合规、可审计的生产级PKI基础设施
  • Shipyard 2.0.10 在 CoreOS 上的 TLS 部署本质是技术债陷阱
  • Ubuntu 18.04 安装 MongoDB:apt+systemctl+ufw 协同部署指南
  • 2026年成立多年的螺纹钢批发企业实力测评,小散工程合作优选 - mypinpai
  • STM32与ESP8266协同开发的底层原理与工程实践
  • 2026免费录音转文字工具保姆级教程:电脑手机都能用,无付费限制
  • HTML超链接工程化实践:从可访问到SEO友好的生产级指南
  • VR-Reversal:零成本将3D视频转换为交互式2D体验的终极指南
  • 2026年广受信赖的驾照培训学校,资质齐全省心选择 - mypinpai
  • JavaScript正则实战:从表单校验到日志提取的7个高频场景
  • (2026最新)来宾防水补漏正规公司甄选推荐:漏水检测维修-暗管漏水精准定位检测漏水点-卫生间/厨房/屋顶/阳台/渗漏水维修-本地人必选的正规测漏公司 - 即刻修防水
  • STM32F103+ESP8266稳定联网实战:透传模式与TCP通信底层解析
  • 智能模型视图控制器员中的业务逻辑与界面分离
  • 如何轻松实现高效文件管理:QuickLook文件夹预览插件全面指南
  • Linux sed进阶:地址寻址、模式空间与管道协同实战