
OP-TEE OS 构建体系全解析基于 PLATFORMvexpress-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全局入口与调度核心职责分为四步是所有编译目标的总调度参数解析解析命令行传入的 PLATFORMvexpress-qemu_armv8a、DEBUG、CROSS_COMPILE、自定义 CFG_xxx 等参数平台定位根据 PLATFORM 变量自动匹配并引入 core/arch/arm/$(PLATFORM).mk 平台配置文件模块聚合依次引入 core/core.mk、lib/lib.mk、ta/ta.mk、ldelf/ldelf.mk聚合所有参与编译的源码与编译规则目标分发将 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_all2. 平台配置层硬件适配的核心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.mkBL32 的加载地址 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# 自动拆分 PLATFORMxxx-yyy 为 PLATFORMxxx PLATFORM_FLAVORyyy ifneq (,$(findstring -,$(PLATFORM))) ops : $(join PLATFORM PLATFORM_FLAVOR,$(addprefix ,$(subst -, ,$(PLATFORM)))) $(foreach op,$(ops),$(eval override $(op))) endif架构与平台加载根据 ARCHarm 引入 ARM 架构规则再根据 PLATFORM 引入对应平台的 conf.mk模块聚合依次引入核心、库、TA、加载器的构建规则聚合所有源码目标生成定义 all 目标依赖核心固件、所有 TA、ldelf 加载器4. 分页模式专属打包规则当 CFG_WITH_PAGERy 时不会直接生成单一 tee.bin而是走分段拆分头部封装的专用流程先链接出完整带符号的 tee.elf用 objcopy 拆分出三类独立二进制常驻段、初始化段、可分页段通过 Python 打包脚本拼接 pager 头部、各段数据生成 tee-pager_v2.bin运行时由 pager 模块捕获缺页异常按需加载可分页代码最小化常驻内存攻击面三、分特权级编译流程与安全编译设计OP-TEE 严格按特权级分离编译核心态S-EL1、用户态 TAS-EL0、加载器各自使用独立编译选项所有编译旗标对齐 Android TEE 安全规范。1. 编译选项优先级所有旗标按以下层级叠加后者覆盖前者保证平台与安全选项优先级最高工具链默认选项 → ARM架构通用选项 → vexpress通用配置 → QEMU专属配置 → CFG功能宏 → 用户自定义参数交叉编译工具链与 TF-A 一致使用 aarch64-none-elf-gcc通过 CROSS_COMPILE 参数指定。2. 核心态S-EL1编译最高安全等级运行在安全异常等级 1是 OP-TEE 的可信内核编译选项偏向最小攻击面与安全加固架构约束-marcharmv8-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 加载的固件。步骤1C 预处理器生成纯链接脚本OP-TEE 的链接脚本是支持 C 预处理器的汇编级文件必须先展开所有宏、头文件、条件编译才能生成可用的 ld 脚本cpp -P \ -Icore/include -Icore/arch/arm/include \ -Icore/arch/arm/plat-vexpress/ \ -DCFG_WITH_PAGERy \ -DCFG_TZDRAM_START0x0E100000 \ -DTEE_LOAD_ADDR0x0E100000 \ -DSMALL_PAGE_SIZE4096 \ core/arch/arm/kernel/kern.ld.S \ out/arm-plat-vexpress-qemu_armv8a/core/kern.ld这一步是配置驱动内存布局的核心平台差异、功能开关、安全特性全部通过宏展开体现在最终链接规则中。步骤2全量链接生成 ELF 文件调用交叉链接器使用生成的 kern.ld把所有核心态 .o、系统库 .a 合并为完整的带符号 ELFaarch64-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_START0x0E100000和 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数据与未初始化段段名属性作用安全设计.dataRW 可读写已初始化全局变量、静态变量双地址设计ROM地址/RAM地址支持重定位与地址随机化.bssRW 可读写未初始化全局变量不占用 bin 文件体积启动时清零避免残留敏感数据.noziNOLOAD 不加载MMU页表、内核栈、临时缓冲区不初始化避免清零开销每个CPU核心独立栈空间防止栈溢出跨核破坏.heap1NOLOAD 不加载内核堆内存按16KB大页对齐匹配MMU L1页表粒度4. 分页模式专属段CFG_WITH_PAGERy这是 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 自身代码常驻内存大部分功能代码按需加载即使某段代码存在漏洞也只有被加载时才会暴露在攻击面中提升整体安全水位