
简介OP-TEE_my_test 项目压缩包聚焦ARM可信执行环境下的安全应用开发面向嵌入式安全与系统开发者适合学习OP-TEE中TrustletTA与Client ApplicationCA的实现、集成和验证流程。压缩包仅15KB包含19个文件以4个makefile、4个h头文件、3个c源码为核心辅以3个msc调用时序图、3个mk构建配置、1个md说明文档及1个sh构建脚本目录按TA、host客户端与doc文档清晰划分。该资源已在CSDN上获得2331人次浏览学习。TA端源码覆盖安全应用入口与命令处理实现host端提供客户端主程序与对应构建配置msc图分别描绘会话打开、命令调用、会话关闭等典型GlobalPlatform调用流程sh脚本可实现一键编译并部署到QEMU验证。开发者可借此掌握Secure World与Normal World的交互机制熟悉从源码、Makefile配置、menuconfig选项到编译部署、远程调试的完整安全应用添加路径。资源对需要快速搭建OP-TEE集成测试环境的中高级嵌入式开发者具有较强的参考与实用价值。1. 项目概述与核心动机1.1 为什么我会去碰 OP-TEE我最初接触到 OP-TEE 是因为在手头的 ARM 开发板上做安全启动相关的工作需要一种能区分“普通世界”Normal World和“安全世界”Secure World的隔离运行环境。OP-TEEOpen Portable Trusted Execution Environment是目前社区里最活跃的开源 TEE 实现之一配合 ARM TrustZone 硬件隔离机制能在 SoC 内部画出独立的安全内存区域让敏感代码和数据跑在一个与常规操作系统完全隔离的“安全核”里。我个人的体会是TEE 这个概念听起来很高大上但实际上手之后你会发现它比拼的不是算法多复杂而是对“边界”和“资源”的控制是否严谨。OP-TEE 解决的核心问题很简单——你的主系统REERich Execution Environment即使被攻破了攻击者也拿不到安全世界里保存的密钥、指纹数据或者支付凭证。这么一套机制对于做安全启动、DRM、移动支付、生物识别校验、密钥管理的开发者来说都属于绕不开的基础设施。1.2 这个测试项目想验证什么我把这个项目命名成OP-TEE_my_test本质上不追求在生产环境里直接落地而是要搭起一套可复现的最小工程用来验证这几件事的能力边界在目标开发板上把 OP-TEE OS 正常跑起来编写一个简单的 Trusted ApplicationTA跑通从普通世界到安全世界的调用链路验证共享内存传递参数的完整流程顺手记录编译、部署、调试过程中所有“文档里没写但实战中一定会碰到”的坑。如果你正准备在真实设备或者 QEMU 虚拟环境里尝试 OP-TEE这篇文章里的几乎所有结论都可以直接抄作业。2. 环境选型与编译链路解析2.1 开发环境与硬件选型思路我这次用的是QEMU v8 环境 64 位 ARM 架构来跑 OP-TEE。选模拟器而不是真机的最直接理由是OP-TEE 的构建系统中内置了对 QEMU 的支持一条编译命令就能生成包括BL1、BL2、BL31ATF、OP-TEE OS、U-Boot、Linux 内核和rootfs在内的完整镜像。真机当然更爽但是遇到硬件调试问题比如 TrustZone 地址空间配置错误、安全中断路由错误时定位成本会高出一个数量级。选择QEMUv8的时候有几个点值得留意尽量使用官方qemu_v8.xml对应的构建配置不要自己手改 Memory Map否则后续的共享内存地址很容易踩到对齐错位问题主机系统我用的 Ubuntu 22.04 LTS构建工具链包含gcc-aarch64-linux-gnu、clang、python3等官方提供了一键脚本检查依赖磁盘空间预留不少于 50GB因为完整编译一次会生成多个镜像文件和工具链缓存空间不足会导致莫名链接错误而错误信息并不会提示“磁盘满了”。2.2 编译链路的执行顺序整个构建系统基于repo管理多个 git 仓库我建议你按照下面的顺序在终端里执行mkdir op-tee cd op-tee repo init -u https://github.com/OP-TEE/manifest.git -m qemu_v8.xml repo sync -j8 cd build make toolchains -j$(nproc) make run -j$(nproc)这里最重要的一条经验不要跳过make toolchains这一步直接make run。我一开始图省事结果编译到 optee_os 的时候arm64 交叉编译器缺失导致一堆隐晦的类型错误。先构建工具链主要是为了把 aarch64 交叉编译环境和 SELinux 相关的工具都准备好。另外编译时-j参数不要配得太大8 线程以内比较稳太多并行容易随机崩溃。如果你的网络环境访问 GitHub 不稳定建议提前配置好代理缓存repo sync这一步下载的数据量很大中途断掉之后增量同步偶尔会卡在某个仓库上处理起来比较费神。我实测最稳妥的做法是给 git 配置一个本地缓存镜像或者干脆用压缩包整体同步到构建机再解压。2.3 为什么选择 64 位配置目前的 OP-TEE 主线对 64 位 ARM 架构支持得最完整配置QEMUv8编译出来的是 AArch64 状态下的 TEE 环境普通世界运行 64 位 Linux安全世界则同时支持 64 位 TA。相较 32 位配置64 位环境的优势在于物理地址范围大配置内存布局时更灵活TA 的栈空间和堆空间管理更规范OP-TEE OS 内部对 64 位处理器的线程切换支持更成熟。当然如果你用的是树莓派 3B 或者某些 32 位 ARM 开发板还是有对应的QEMUv7或FVP配置可用但调试和后续扩展的体验明显不如 64 位环境顺畅。3. 核心功能解析与 TA 开发要点3.1 从一个最小 TA 的结构讲起OP-TEE 里最核心的软件实体就是 TATrusted Application。通俗地讲REE 侧的应用CAClient Application把要保护的密钥、数据通过TEEC_InvokeCommand发给 TATA 在安全世界里完成计算再把结果返回。整个过程跨过了“普通世界/安全世界”的边界而 TA 本身运行在安全内存中主系统看不到也改不了它的内部状态。要写一个最小可用的 TA你至少需要完成三件事定义 UUID这是 TA 的唯一标识实现TA_CreateEntryPoint、TA_OpenSession、TA_InvokeCommand这几个入口函数在user_ta_header_defines.h里声明 TA 的堆栈大小和版本信息。我这次的测试 TA 实现了一个非常简单的功能接收普通世界传入的一个整数在安全世界里对它加上一个固定偏移值然后返回。目的是验证参数传递路径的通畅性以及共享内存读写是否正确。3.2 UUID 与命令 ID 的设计规范UUID 在 OP-TEE 生态里就像身份证号。不同 TA 之间的 UUID 一旦冲突系统会拒绝加载后注册的那一个。手动生成 UUID 时建议使用uuidgen命令uuidgen然后把生成的 16 字节值填到头文件里注意大小端排列。OP-TEE 内部的TEE_UUID结构体定义是按 RFC 4122 排列的如果你是从一些小论坛上的旧示例代码里复制 UUID很容易遇到大小端不对导致TEEC_ERROR_ITEM_NOT_FOUND的错误。命令 ID 方面0号一般保留给伪 TA 使用我们自定义的命令建议从1开始。单个 TA 最多支持的命令数量由TA_CMD_MAX限制日常开发不用操心但把它设计成宏定义、集中管理是个好习惯不然 TA 功能复杂之后改起来非常痛苦。3.3 Session 与 Context 的概念很多刚接触 OP-TEE 的人会被Context和Session搞混。我用一个生活化的类比说明一下Context相当于普通世界和 TEE 之间的一条“专线”。初始化时调用TEEC_InitializeContext成功之后后续所有会话都在这个上下文中排队传输Session相当于专线上的一条“连接”。客户端在打开会话时传入一个 TA 的 UUIDTEE 内部会加载对应 TA 并完成身份校验之后你在这个会话里发任何命令都直接交给同一个 TA 处理。实际写代码时标准流程是TEEC_Context ctx; TEEC_Session sess; TEEC_UUID uuid TA_MY_TEST_UUID; uint32_t origin; res TEEC_InitializeContext(NULL, ctx); res TEEC_OpenSession(ctx, sess, uuid, TEEC_LOGIN_PUBLIC, NULL, NULL, origin);这里面TEEC_LOGIN_PUBLIC表示客户端与 TA 之间不做额外的登录身份验证。如果你要对接安全等级更高的场景例如生物识别匹配就需要配置TEEC_LOGIN_USER或者更细粒度的登录策略并且 TA 侧要配合实现对应的TA_OpenSession分支逻辑。4. 实操过程与共享内存机制4.1 共享内存的正确打开方式OP-TEE 里共享内存是普通世界和安全世界都能访问的内存区域。但这里有个非常容易踩的坑不要直接传一个指向用户态缓冲区的指针给 TA然后让 TA 去读。标准做法是使用TEEC_AllocateSharedMemory分配一个显式的共享内存块填完数据之后再作为参数传给TEEC_InvokeCommand。我写的测试代码里参数传递结构是这样的TEEC_SharedMemory shm; shm.size sizeof(uint32_t); shm.flags TEEC_MEM_INPUT; res TEEC_AllocateSharedMemory(ctx, shm); uint32_t *input (uint32_t *)shm.buffer; *input 42; TEEC_Operation op; memset(op, 0, sizeof(op)); op.paramTypes TEEC_PARAM_TYPES(TEEC_MEMREF_TEMP_INPUT, TEEC_NONE, TEEC_NONE, TEEC_NONE); op.params[0].memref.parent shm; op.params[0].memref.offset 0; op.params[0].memref.size sizeof(uint32_t); res TEEC_InvokeCommand(sess, CMD_ADD_OFFSET, op, origin);然后 TA 侧在TA_InvokeCommand里uint32_t *in_val (uint32_t *)params[0].memref.buffer; uint32_t result *in_val MY_OFFSET;这里我遇到的一个硬核问题就是当使用TEEC_MEMREF_TEMP_INPUT这种临时内存引用时TA 看到的 buffer 是 OP-TEE 内部重新映射过的区域不能直接把它当作普通世界物理地址的延展来操作。共享内存的大小、对齐、缓存属性都必须在分配时设置好否则会在TA_InvokeCommand里收到TEEC_ERROR_BAD_FORMAT。4.2 Pager 机制与 TA 内存布局OP-TEE OS 内部有一个可选的 pager 机制启动时只把一部分核心代码放到物理内存里其余按需从镜像文件加载。这个机制对 TA 的影响是TA 的堆栈大小配置过于激进或者使用了大块静态缓冲区可能导致 TA 加载失败并伴随TEEC_ERROR_OUT_OF_MEMORY。在我这个测试 TA 里我特意把堆大小设成了 4KB栈大小 2KB足够验证基础流程。如果你的 TA 业务复杂、需要大块 RAM记得调整user_ta_header_defines.h里的宏定义同时确认 build 配置里的CFG_TZDRAM_SIZE足够容纳。别光改一个头文件我就曾经因为只调了堆大小、忘了改CFG_TZDRAM_SIZE导致 TA 启动时页面错误定位了半天。4.3 从 QEMU 启动到日志验证全流程编译成功之后执行make runQEMU 窗口弹出系统会经历 U-Boot 启动、ATF 加载、OP-TEE OS 初始化、Linux 内核启动这一整套流程。你可以在终端里敲以下命令开始跑测试# 启动后在 Linux 的串口终端中 modprobe optee optee_example_hello_world如果一切正常你会在串口输出里看到 TA 加载成功的日志。为了确认安全世界函数确实被执行我习惯在 TA 的TA_InvokeCommand里直接调用IMSG(my test TA invoked)这样会输出到 OP-TEE 的安全日志通道里和 Linux dmesg 的日志混在一起但你一眼能看出执行流确实跨过了安全边界。如果你想查看更详细的 OP-TEE 内部状态可以打开CFG_TEE_CORE_LOG_LEVEL4重新编译能看到线程切换、内存映射、SMC 调用细节。我一般只在调试阶段开 verbose 日志正式运行还是保持默认级别否则日志暴涨反而不利于观察关键信息。5. 常见问题排查与参数调优5.1 共享内存对齐问题的排查我在实验过程中遇到过至少三次共享内存对齐问题现象各有不同第一次是TEEC_AllocateSharedMemory之后直接写大块数据然后从 TA 读出来发现前几个字节是对的后面全是乱的——原因是缓存一致性没有处理干净第二次是把栈上的野指针传给了 TA结果 TA 侧读到的是乱值而且没有立即崩而是隔了几次调用之后才暴露排查这种问题时建议先把操作参数降级成最简单的方式用一个全局数组分配共享内存并把size缩到最小先跑通一个整数传递再做大数据量读写。另外可以通过make run前设置环境变量OPTEE_LOGGING1打开 TA 侧日志逐步加打印来定位。5.2 设备树配置导致的启动失败QEMU v8 的配置里optee节点对应的设备树描述位于qemu_v8.dts中其中有一个关键属性method smc表示操作系统通过 SMC 指令陷入 EL3再交由 OP-TEE 处理。如果你拿官方配置完全不动一般不会出问题。但我曾为了测试不同内存布局手动改过reg属性里的地址范围结果 Linux 内核起来后模块加载optee驱动一直报probe failed。最终定位到原因是设备树里声明的 OP-TEE 安全内存基地址和实际编译进去的CFG_TZDRAM_START不一致导致驱动无法初始化。所以任何内存布局改动都必须同时改设备树和 OP-TEE 构建配置两边对齐否则问题非常隐蔽。5.3 构建依赖与版本锁定OP-TEE 的多个仓库更新节奏不同步如果你直接用最新主线可能会遇到某个仓库的改动破坏另一个组件的编译。我试验下来最省心的方式是用manifest仓库锁定一个稳定 release 分支比如3.20.0在repo sync时尽量避免交叉混用不同版本仓库在自己代码里的头文件包含路径不要使用绝对路径要在ta_export.mk或子目录的sub.mk中引用相对路径。一旦出现版本错位常见的报错包括optee_os里的内部头文件找不到、libutee里结构体不一致导致编译报错等。这类问题排查起来很烦与其去网上搜补丁不如直接切回 release 版本。5.4 内存开销与 TA 大小优化最后聊一个细节TA 在安全世界里的内存占用是受限的。当你把 TA 的二进制文件放到 rootfs 中后OP-TEE 在加载 TA 时会把它从普通世界文件系统读到安全内存里。如果一个 TA 的功能很简单但链接进去的动态库很多加载时间会变长而且镜像体积膨胀。我这次测试 TA 的二进制控制在几十 KB 以内加载时间几乎可以忽略。如果你的 TA 需要用到加解密库记得按需包含不要默认链接所有 mbedTLS 模块不然 1MB 的 TA 在资源受限场景里是很痛苦的。6. 调试工具链与效率提升总结6.1 熟悉这几个调试工具在 OP-TEE 项目里最常见的调试方式有三个xtestOP-TEE 自带的测试套件适合验证整个 TEE 生态功能是否完整optee_example_hello_world最简单直接的示例程序适合验证环境跑通GDB 远程调试在 QEMU 里可以对optee_os做源码级调试但需要配置CFG_TEE_CORE_DEBUGy。如果你在 Windows 上开发、Linux 上编译建议直接用VSCode Remote-SSH连到构建机比本地装一堆交叉编译工具链省事得多。6.2 我实际踩坑后的总结这个项目做完之后我最大的感受是OP-TEE 本身是个设计得非常规矩的 TEE 实现规矩意味着对开发者要求也高。你如果只是把 TA 当普通 Linux 动态库来写很快就会被共享内存语义、安全内存限制、设备树匹配这些问题教育一通。在实际操作中最值得注意的三条经验始终先把最小用例跑通再上真实业务。共享内存、会话管理、参数编解码都验证无误后再回头加密码算法、多线程并发日志是你的第一捷径。OP-TEE 的日志系统区分了 REE 和 TEE别只在 Linux 侧看dmesg安全侧日志才是判断 TA 崩溃原因的关键版本锁定比什么都重要。无论是跑 QEMU 还是真机仓库版本组合固定之后至少要保存一份构建时间和 commit 记录出了问题才能精准回退。OP-TEE 是一个可以玩得很深的方向从最小的 TA 测试开始后续你可以继续扩展远程 attestation、安全存储、密钥协商等能力。我后面计划在这个最小工程的基础上做一块基于 TEE 的远程证明模块到时候再单独写一篇实践经验分享。本文还有配套的精品资源点击获取