简介OP-TEE_my_test 是一份面向嵌入式安全开发者的实践型代码包聚焦 ARM 架构下可信执行环境 OP-TEE 中自定义 Trusted ApplicationTA与 Client ApplicationCA的集成与验证适合已具备 C 语言与 Linux 构建基础、希望深入 Secure World 与 Normal World 通信机制的中高级开发者。资源共 19 个文件约 15KB包含 4 个 makefile 与 3 个 mk 构建脚本、4 个头文件、3 个 C 源文件以及 3 个 msc 会话流程描述文件、1 个 shell 脚本和 1 份 README 说明覆盖 TA 与 CA 的源码、编译配置及 QEMU 部署脚本。目前已有 2338 人学习下载。通过该包可掌握 TA 目录组织、Makefile 属性定义、CA 调用接口、menuconfig 配置项以及 QEMU 启动参数设置并借助 msc 文件理解 open session、invoke command 与 close session 的完整交互流程为嵌入式安全系统开发积累可复用的调试与排错经验。1. 从一份 OP-TEE 测试工程说起它到底能跑通什么如果你正在做 ARM 平台的可信执行环境开发大概率绕不开 OP-TEE 这套开源方案。但官方仓库里的示例工程往往铺得很大编译一次要拉一堆依赖想快速验证一个最小可信应用反而费劲。我手里这份名为 OP-TEE_my_test 的工程就是一个把 OP-TEE 的 TATrusted Application可信应用与 CAClient Application客户端应用最小闭环单独拎出来的测试包。它解决的不是“教你从零搭 OP-TEE 全栈”的问题而是“我已经有 OP-TEE 运行环境了怎么用最短路径跑通一个自定义 TA 并验证正反向调用”的问题。适合已经能在 QEMU 或开发板上启动 optee_os、但还没亲手写过 TA 的从业者也适合想拿一个干净模板做二次开发的熟手。下面按“它是什么 → 怎么编译怎么跑 → 参数怎么调 → 坑在哪”的顺序拆开讲。2. 拆开 OP-TEE_my_test目录结构与 TA/CA 通信模型2.1 这份工程里到底放了什么OP-TEE_my_test 的目录组织遵循 OP-TEE 官方示例的惯例但做了大幅精简。常见做法是把它放在 optee_examples 同级目录下或者直接放进 optee_os 的 examples 路径里。核心目录通常包含三块host/放 CA 源码ta/放 TA 源码shared/放两边共用的头文件。CA 跑在普通世界Normal World的 Linux 用户态TA 跑在安全世界Secure World的 OP-TEE 内核里两者通过 TEE Client API 和 TEE Internal Core API 通信。先看目录骨架这是理解后续所有操作的基础OP-TEE_my_test/ ├── host/ │ ├── main.c # CA 入口负责打开会话、调用命令 │ ├── Makefile # 编译普通世界可执行文件 │ └── Makefile.linux # 部分工程会拆出平台相关部分 ├── ta/ │ ├── my_test_ta.c # TA 核心逻辑实现命令分发 │ ├── user_ta_header_defines.h # TA 的 UUID、栈大小、版本等元信息 │ ├── sub.mk # TA 编译规则 │ └── Makefile # 调用 OP-TEE 的 TA 构建框架 ├── shared/ │ └── my_test_ta.h # 定义命令 ID、共享结构体 └── Makefile # 顶层入口分别进入 host 和 ta这里有个关键点TA 的 UUID 必须全局唯一它写在user_ta_header_defines.h里CA 打开会话时用的也是同一个 UUID。很多新手第一次跑不通就是 CA 和 TA 的 UUID 对不上或者格式写错比如少了连字符、大小写不一致。UUID 在 OP-TEE 里是 TA 的身份标识不是随便填的字符串。2.2 TA 与 CA 的会话与命令模型OP-TEE 的 TA/CA 通信不是“函数调用”而是基于会话Session和命令Command的消息传递。CA 先通过TEEC_InitializeContext初始化上下文再用TEEC_OpenSession打开与指定 UUID 的 TA 的会话。会话建立后CA 通过TEEC_InvokeCommand发送命令 ID 和参数TA 侧在TA_InvokeCommandEntryPoint里根据命令 ID 分发处理。参数传递有四种类型TEEC_VALUE_INPUT、TEEC_VALUE_OUTPUT、TEEC_VALUE_INOUT用于传递小数据两个 32 位值TEEC_MEMREF_TEMP_INPUT、TEEC_MEMREF_TEMP_OUTPUT、TEEC_MEMREF_TEMP_INOUT用于传递共享内存缓冲区。理解这几种参数类型是写对 TA 的前提因为安全世界和普通世界的内存是隔离的不能直接传指针。下面这段是 CA 侧打开会话并调用命令的典型写法我把它拆成带注释的版本#include tee_client_api.h #include my_test_ta.h int main(void) { TEEC_Context ctx; TEEC_Session sess; TEEC_Operation op; TEEC_Result res; uint32_t err_origin; /* 1. 初始化 TEE 上下文连接 tee 驱动 */ res TEEC_InitializeContext(NULL, ctx); if (res ! TEEC_SUCCESS) return -1; /* 2. 用 TA 的 UUID 打开会话UUID 必须与 TA 侧一致 */ res TEEC_OpenSession(ctx, sess, my_test_ta_uuid, TEEC_LOGIN_PUBLIC, NULL, NULL, err_origin); if (res ! TEEC_SUCCESS) { TEEC_FinalizeContext(ctx); return -1; } /* 3. 准备命令参数这里用两个 32 位值做输入输出 */ memset(op, 0, sizeof(op)); op.paramTypes TEEC_PARAM_TYPES(TEEC_VALUE_INPUT, TEEC_VALUE_OUTPUT, TEEC_NONE, TEEC_NONE); op.params[0].value.a 0x10; // 输入参数 a op.params[0].value.b 0x20; // 输入参数 b /* 4. 调用命令 ID 为 TA_MY_TEST_CMD 的处理逻辑 */ res TEEC_InvokeCommand(sess, TA_MY_TEST_CMD, op, err_origin); if (res TEEC_SUCCESS) { printf(TA returned: 0x%x\n, op.params[1].value.a); } /* 5. 关闭会话并释放上下文 */ TEEC_CloseSession(sess); TEEC_FinalizeContext(ctx); return 0; }逻辑说明第 1 步的TEEC_InitializeContext第一个参数传 NULL 表示使用默认的 TEE 设备节点通常是/dev/tee0。第 2 步的TEEC_LOGIN_PUBLIC表示公共登录不需要用户身份这是测试工程最常用的方式。第 3 步的paramTypes用宏TEEC_PARAM_TYPES组合四个参数槽的类型这里只用了前两个。第 4 步的TA_MY_TEST_CMD是定义在shared/my_test_ta.h里的命令 IDTA 侧必须用同一个值。第 5 步的清理顺序不能反先关会话再释放上下文。参数说明op.params[0].value.a和.b是两个 32 位输入TA 侧通过param[0].value.a读取。输出参数放在op.params[1]TA 侧写入后 CA 侧才能读到。如果参数类型写成TEEC_VALUE_OUTPUT但 CA 侧没初始化TA 侧读到的可能是随机值这是常见翻车点。2.3 TA 侧的命令分发与内存校验TA 侧的入口是TA_InvokeCommandEntryPoint它接收会话上下文、命令 ID、参数数组和参数数量。TA 必须对每个命令做参数类型校验不能假设 CA 一定传对了。OP-TEE 提供了TEE_CheckMemoryAccessRights等接口但对于TEEC_VALUE_*类型的小数据主要靠param_types宏在编译期和运行期做基本检查。#include tee_internal_api.h #include tee_internal_api_extensions.h #include my_test_ta.h /* TA 的命令处理入口由 OP-TEE 内核调用 */ TEE_Result TA_InvokeCommandEntryPoint(void *sess_ctx, uint32_t cmd_id, uint32_t param_types, TEE_Param params[4]) { uint32_t exp_param_types; switch (cmd_id) { case TA_MY_TEST_CMD: /* 期望的参数类型第一个输入第二个输出 */ exp_param_types TEE_PARAM_TYPES(TEE_PARAM_TYPE_VALUE_INPUT, TEE_PARAM_TYPE_VALUE_OUTPUT, TEE_PARAM_TYPE_NONE, TEE_PARAM_TYPE_NONE); if (param_types ! exp_param_types) return TEE_ERROR_BAD_PARAMETERS; /* 读取 CA 传入的两个值并做简单运算 */ uint32_t a params[0].value.a; uint32_t b params[0].value.b; params[1].value.a a b; // 写回结果 return TEE_SUCCESS; default: return TEE_ERROR_NOT_SUPPORTED; } }逻辑说明exp_param_types用TEE_PARAM_TYPES构造期望类型必须与 CA 侧完全一致否则返回TEE_ERROR_BAD_PARAMETERS。params[0].value.a和.b是输入params[1].value.a是输出。TA 里不能直接用printf要用IMSG或DMSG宏输出日志这些日志会打到 OP-TEE 的串口或 logcat 里。参数说明TEE_PARAM_TYPE_VALUE_INPUT对应 CA 侧的TEEC_VALUE_INPUTTEE_PARAM_TYPE_VALUE_OUTPUT对应TEEC_VALUE_OUTPUT。如果 CA 侧用了TEEC_MEMREF_TEMP_INPUTTA 侧就要用TEE_PARAM_TYPE_MEMREF_INPUT并且通过params[0].memref.buffer和.size访问。类型不匹配是 TA 返回TEE_ERROR_BAD_PARAMETERS的首要原因。3. 编译与运行从源码到 QEMU 里的第一次调用3.1 编译环境与依赖确认在编译 OP-TEE_my_test 之前必须先确认 OP-TEE 的构建环境已经就绪。常见做法是使用 OP-TEE 官方提供的 build 仓库或者已经手动编译过 optee_os、optee_client 和 optee_test。关键依赖包括交叉编译工具链如arm-linux-gnueabihf-或aarch64-linux-gnu-、OP-TEE 的 TA 开发包optee_os里的ta目录和optee_client里的头文件、以及make和python3。我一般会先验证三个路径是否存在$OPTEE_OS指向 optee_os 源码根目录$OPTEE_CLIENT指向 optee_client 源码根目录$CROSS_COMPILE指向交叉编译器前缀。如果这三个没设对TA 编译会直接报找不到tee_internal_api.h或user_ta_header.h。# 确认交叉编译工具链可用 ${CROSS_COMPILE}gcc --version # 确认 OP-TEE 头文件路径存在 ls $OPTEE_OS/ta/include/tee_internal_api.h ls $OPTEE_CLIENT/libteec/include/tee_client_api.h # 确认 TA 开发包里的构建脚本存在 ls $OPTEE_OS/ta/mk/ta_dev_kit.mk逻辑说明第一条命令验证交叉编译器是否在 PATH 里。第二条和第三条验证 OP-TEE 的头文件路径如果这些文件不存在说明 optee_os 或 optee_client 没编译或没安装。第四条验证 TA 的构建框架脚本OP-TEE 的 TA 编译依赖ta_dev_kit.mk里的规则。参数说明CROSS_COMPILE的值取决于目标平台32 位 ARM 常用arm-linux-gnueabihf-64 位 ARM 常用aarch64-linux-gnu-。如果是在 QEMU 上跑通常用arm-linux-gnueabihf-。OPTEE_OS和OPTEE_CLIENT是环境变量不是 make 变量需要在 shell 里 export。3.2 编译 TA 与 CA 的完整命令OP-TEE_my_test 的顶层 Makefile 一般会分别进入ta/和host/编译。TA 编译需要指定目标平台和交叉编译器CA 编译需要链接libteec。下面是我常用的编译流程# 进入工程根目录 cd OP-TEE_my_test # 编译 TA指定目标平台为 QEMU 的 ARM 32 位 make -C ta \ CROSS_COMPILEarm-linux-gnueabihf- \ PLATFORMvexpress-qemu_virt \ O$PWD/out # 编译 CA链接 libteec make -C host \ CROSS_COMPILEarm-linux-gnueabihf- \ TEEC_EXPORT$OPTEE_CLIENT/out/export # 查看生成的文件 ls -l ta/*.ta host/my_test逻辑说明TA 编译的PLATFORM参数必须与 optee_os 编译时的平台一致否则 TA 的二进制格式可能不兼容。O$PWD/out指定输出目录避免污染源码树。CA 编译的TEEC_EXPORT指向 optee_client 编译后导出的头文件和库路径。最后生成的*.ta是 TA 的二进制host/my_test是 CA 可执行文件。参数说明PLATFORMvexpress-qemu_virt是 QEMU 虚拟平台的名称如果用的是真实开发板要换成对应的平台名如stm32mp1、rk3399等。CROSS_COMPILE必须与 optee_os 编译时一致否则 TA 加载会失败。TEEC_EXPORT的路径取决于 optee_client 的编译输出位置常见的是optee_client/out/export。3.3 在 QEMU 里加载 TA 并运行 CA编译完成后需要把 TA 和 CA 放进 QEMU 的文件系统里。OP-TEE 的 QEMU 环境通常使用optee_os/out/arm/下的rootfs或initramfs。TA 文件要放到/lib/optee_armtz/目录下CA 可执行文件放到/usr/bin/或/bin/下。然后启动 QEMU在普通世界的 shell 里运行 CA。# 将 TA 拷贝到 QEMU 文件系统的 TA 目录 cp ta/*.ta $OPTEE_OS/out/arm/rootfs/lib/optee_armtz/ # 将 CA 拷贝到普通世界的可执行目录 cp host/my_test $OPTEE_OS/out/arm/rootfs/usr/bin/ # 重新打包文件系统如果使用 initramfs cd $OPTEE_OS/out/arm/rootfs find . | cpio -o -H newc | gzip ../initramfs.cpio.gz # 启动 QEMU具体命令取决于 optee_os 的 build 脚本 make -C $OPTEE_OS run-only逻辑说明TA 文件必须放在/lib/optee_armtz/下OP-TEE 内核启动时会扫描这个目录并注册 TA。CA 可执行文件放在普通世界的/usr/bin/下QEMU 启动后可以在 shell 里直接运行。如果用的是 initramfs修改后需要重新打包否则 QEMU 还是用旧的镜像。参数说明/lib/optee_armtz/是 OP-TEE 默认的 TA 加载路径如果 optee_os 编译时改了CFG_TEE_TA_DIR路径可能不同。cpio打包命令里的-H newc指定格式gzip压缩后生成initramfs.cpio.gz。QEMU 启动命令因环境而异常见的是make run-only或直接调用qemu-system-arm带一堆参数。3.4 验证调用结果与日志查看CA 运行后如果一切正常会在普通世界串口打印TA returned: 0x30因为 0x10 0x20 0x30。如果失败需要看两个地方的日志普通世界的 CA 输出和安全世界的 OP-TEE 日志。OP-TEE 的日志通常通过串口输出QEMU 里会混在一起可以用-serial mon:stdio参数分开。# 在 QEMU 的普通世界 shell 里运行 CA my_test # 如果失败查看 OP-TEE 日志里的错误码 # 常见错误码0xffff0006 (TEE_ERROR_BAD_PARAMETERS) # 0xffff0008 (TEE_ERROR_ITEM_NOT_FOUND) # 0xffff000f (TEE_ERROR_NOT_SUPPORTED)逻辑说明my_test是 CA 可执行文件名运行后如果返回TEEC_SUCCESS并打印结果说明 TA/CA 通信正常。如果返回错误码需要对照 OP-TEE 的错误码表排查。0xffff0006通常是参数类型不匹配0xffff0008是 TA 没找到UUID 不对或 TA 没加载0xffff000f是命令 ID 没实现。参数说明OP-TEE 的错误码定义在tee_api_defines.h里TEE_SUCCESS是 0其他都是0xffffxxxx格式。CA 侧的TEEC_Result和 TA 侧的TEE_Result是同一套错误码。如果 CA 返回TEEC_ERROR_ITEM_NOT_FOUND优先检查 TA 文件是否在/lib/optee_armtz/下以及 UUID 是否匹配。4. 避坑与排查TA/CA 联调中最容易翻车的五个点4.1 现象CA 打开会话返回 0xffff0008TA 明明已经拷贝原因这个错误码是TEE_ERROR_ITEM_NOT_FOUND意思是 OP-TEE 内核没找到对应 UUID 的 TA。常见原因有三个TA 文件没放到/lib/optee_armtz/下TA 文件的 UUID 与 CA 请求的 UUID 不一致TA 文件编译时的平台与 optee_os 不匹配导致加载失败。解决先确认 TA 文件在文件系统里的路径正确再检查user_ta_header_defines.h里的TA_UUID和 CA 侧my_test_ta_uuid是否完全一致。UUID 是 16 字节格式如{0x12, 0x34, ...}大小写和连字符不影响但字节顺序不能错。最后确认 TA 编译的PLATFORM与 optee_os 一致。4.2 现象TA 返回 0xffff0006参数类型校验不通过原因TEE_ERROR_BAD_PARAMETERS表示 TA 侧收到的参数类型与期望不符。CA 侧用TEEC_PARAM_TYPES组合的类型TA 侧用TEE_PARAM_TYPES组合的类型两者必须一一对应。常见错误是 CA 侧用了TEEC_VALUE_INPUTTA 侧却期望TEE_PARAM_TYPE_MEMREF_INPUT或者参数槽的顺序写反了。解决把 CA 侧的op.paramTypes和 TA 侧的exp_param_types并排打印出来对比。注意TEEC_PARAM_TYPES和TEE_PARAM_TYPES的宏参数顺序都是四个槽从param0到param3。如果某个槽不用两边都要写NONE。另外TEEC_VALUE_INPUT对应TEE_PARAM_TYPE_VALUE_INPUTTEEC_MEMREF_TEMP_INPUT对应TEE_PARAM_TYPE_MEMREF_INPUT不能混用。4.3 现象TA 编译报错找不到 tee_internal_api.h原因TA 编译依赖 OP-TEE 的 TA 开发包头文件路径由ta_dev_kit.mk里的CFLAGS指定。如果OPTEE_OS环境变量没设或者ta_dev_kit.mk路径不对编译器就找不到头文件。解决确认OPTEE_OS指向 optee_os 源码根目录并且$OPTEE_OS/ta/include/tee_internal_api.h存在。如果 optee_os 还没编译先编译 optee_os 生成 TA 开发包。另外TA 的 Makefile 里通常有一行include $(OPTEE_OS)/ta/mk/ta_dev_kit.mk如果这行路径写错也会报同样的错。4.4 现象CA 运行时报找不到 libteec.so原因CA 可执行文件在运行时需要链接libteec.so如果 QEMU 文件系统里没有这个库或者LD_LIBRARY_PATH没设对就会报error while loading shared libraries: libteec.so。解决把 optee_client 编译生成的libteec.so拷贝到 QEMU 文件系统的/usr/lib/下并在运行 CA 前设置export LD_LIBRARY_PATH/usr/lib:$LD_LIBRARY_PATH。如果用的是静态链接编译 CA 时加-static可以避免这个问题但会增加可执行文件体积。4.5 现象TA 里的 IMSG 日志看不到原因TA 运行在安全世界它的日志默认输出到 OP-TEE 的串口而不是普通世界的终端。如果 QEMU 启动时没把 OP-TEE 的串口输出重定向到当前终端就看不到 TA 的日志。解决QEMU 启动参数里通常有-serial mon:stdio或-serial tcp:...确保 OP-TEE 的串口和普通世界的串口都输出到能看到的地方。另外TA 的日志级别由CFG_TEE_TA_LOG_LEVEL控制默认可能是1只输出错误改成4可以输出IMSG级别的调试信息。这个宏在 optee_os 编译时指定不是 TA 编译时。5. 进阶技巧用 TA 属性与共享内存做更真实的测试5.1 修改 TA 的栈大小与版本号TA 的元信息定义在user_ta_header_defines.h里除了 UUID还可以设置TA_FLAGS、TA_STACK_SIZE、TA_DATA_SIZE和TA_VERSION。默认的栈大小通常是 2KB 或 4KB如果 TA 里用了较大的局部数组或递归需要调大TA_STACK_SIZE。TA_DATA_SIZE控制 TA 的堆大小用TEE_Malloc分配的内存从这里出。/* user_ta_header_defines.h 里的关键宏 */ #define TA_UUID { 0x12345678, 0x9abc, 0xdef0, \ { 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0 } } #define TA_FLAGS (TA_FLAG_USER_MODE | TA_FLAG_EXEC_DDR) #define TA_STACK_SIZE (8 * 1024) /* 栈调到 8KB */ #define TA_DATA_SIZE (32 * 1024) /* 堆调到 32KB */ #define TA_VERSION 1.0.1逻辑说明TA_FLAGS里的TA_FLAG_USER_MODE表示 TA 运行在用户态TA_FLAG_EXEC_DDR表示代码在 DDR 里执行。TA_STACK_SIZE和TA_DATA_SIZE的单位是字节调大后 TA 的内存占用会增加但能避免栈溢出或堆分配失败。TA_VERSION是字符串可以在 CA 侧通过TEEC_GetTAPersistentTime之类的接口读取但更常见的是在 TA 内部用TA_VERSION宏做日志输出。参数说明TA_STACK_SIZE默认值因平台而异QEMU 上常见 2KB。如果 TA 里调用了TEE_Malloc但没检查返回值堆不够时会返回 NULL导致 TA 崩溃。TA_DATA_SIZE不是越大越好因为安全世界的内存有限过大会导致 TA 加载失败。5.2 用共享内存传递结构体数据TEEC_VALUE_*只能传两个 32 位值如果要传结构体或数组需要用TEEC_MEMREF_TEMP_*类型。CA 侧分配一块普通世界的内存把指针和大小传给 TATA 侧通过params[0].memref.buffer访问。OP-TEE 会在调用期间做内存映射TA 不能长期持有这个指针。/* CA 侧用共享内存传一个结构体 */ typedef struct { uint32_t cmd; uint32_t data[4]; } my_payload_t; my_payload_t payload { .cmd 0x01, .data {1, 2, 3, 4} }; memset(op, 0, sizeof(op)); op.paramTypes TEEC_PARAM_TYPES(TEEC_MEMREF_TEMP_INPUT, TEEC_MEMREF_TEMP_OUTPUT, TEEC_NONE, TEEC_NONE); op.params[0].tmpref.buffer payload; op.params[0].tmpref.size sizeof(payload); /* TA 侧读取共享内存 */ if (param_types ! TEE_PARAM_TYPES(TEE_PARAM_TYPE_MEMREF_INPUT, TEE_PARAM_TYPE_MEMREF_OUTPUT, TEE_PARAM_TYPE_NONE, TEE_PARAM_TYPE_NONE)) return TEE_ERROR_BAD_PARAMETERS; my_payload_t *in (my_payload_t *)params[0].memref.buffer; uint32_t in_size params[0].memref.size; if (in_size sizeof(my_payload_t)) return TEE_ERROR_SHORT_BUFFER; /* 处理 in-cmd 和 in-data */逻辑说明CA 侧的tmpref.buffer指向普通世界的内存tmpref.size是字节数。TA 侧通过memref.buffer拿到映射后的安全世界地址memref.size是 CA 传入的大小。TA 必须检查size是否足够否则可能读到越界数据。输出共享内存时TA 写入数据后CA 侧在TEEC_InvokeCommand返回后就能读到。参数说明TEEC_MEMREF_TEMP_INPUT表示临时共享内存只在本次调用期间有效。TA 不能把这个指针保存下来下次用否则会访问到已释放的内存。如果需要长期共享要用TEEC_RegisterSharedMemory注册持久共享内存但测试工程里很少这么做。TEE_ERROR_SHORT_BUFFER是 TA 侧发现缓冲区太小时返回的错误码CA 侧收到后应该扩大缓冲区重试。5.3 用脚本自动化编译与部署每次改完 TA 或 CA 都手动拷贝文件、重新打包 initramfs 很繁琐。我一般会写一个deploy.sh脚本把编译、拷贝、打包、启动 QEMU 串起来。这样改一行代码后跑一次脚本就能看到结果省去重复劳动。#!/bin/bash set -e # 编译 TA 和 CA make -C ta CROSS_COMPILEarm-linux-gnueabihf- \ PLATFORMvexpress-qemu_virt O$PWD/out make -C host CROSS_COMPILEarm-linux-gnueabihf- \ TEEC_EXPORT$OPTEE_CLIENT/out/export # 部署到 rootfs cp ta/*.ta $OPTEE_OS/out/arm/rootfs/lib/optee_armtz/ cp host/my_test $OPTEE_OS/out/arm/rootfs/usr/bin/ # 重新打包 initramfs cd $OPTEE_OS/out/arm/rootfs find . | cpio -o -H newc | gzip ../initramfs.cpio.gz # 启动 QEMU cd $OPTEE_OS make run-only逻辑说明set -e让脚本在任意命令失败时立即退出避免带着错误继续执行。编译和部署的顺序不能反先编译再拷贝。cpio打包时要在 rootfs 目录下执行否则路径会错。最后make run-only启动 QEMU具体命令取决于 optee_os 的 build 脚本。参数说明$OPTEE_OS和$OPTEE_CLIENT需要在脚本执行前 export或者写死在脚本里。PLATFORM和CROSS_COMPILE要与 optee_os 编译时一致。如果 QEMU 启动命令不是make run-only需要替换成实际命令比如直接调用qemu-system-arm带-kernel、-initrd等参数。从那以后我每次改 TA 的 UUID 或参数类型都强制先跑一遍deploy.sh再手动验证一次 CA 返回值因为这两个地方一旦不一致报错信息往往指向别处排查起来很费时间。希望帮到你。本文还有配套的精品资源点击获取