第3章 TEE开发环境搭建:QEMU模拟器、OP-TEE源码与第一个TA

说实话,很多初学者一上来就对着真机开发板折腾,烧录、调试、看串口日志……效率其实很低。我个人习惯,在接触一个新平台时,先用模拟器把整个链路跑通。QEMU + OP-TEE 这套组合,就是目前最成熟的 TEE 开发模拟环境。

这一章,我们就把环境搭起来,编译出第一个 TA。你跟着我走一遍,后面几章的实验就都有地方跑了。

3.1 环境概览:我们要搭什么?

先看一张整体架构图,心里有个谱。

OP-TEE 开发环境整体架构 主机开发环境(Ubuntu 20.04/22.04) 交叉编译工具链 | OP-TEE源码 | 构建脚本 QEMU 模拟器(ARMv8-A 平台) 模拟 4 核 Cortex-A53 | GICv3 | UART | 安全内存 REE 侧(Linux) Linux Kernel | 用户态CA TEE 侧(OP-TEE OS) OP-TEE Core | 安全TA SMC 开发机运行 QEMU,QEMU 内同时启动 Linux 和 OP-TEE

说白了,你的开发机(Ubuntu)上跑 QEMU,QEMU 模拟出一块 ARM64 开发板。这块板子上同时运行两个世界:REE 侧跑 Linux,TEE 侧跑 OP-TEE OS。你的 CA(Client Application)在 Linux 用户态跑,TA(Trusted Application)在 OP-TEE 里跑。两边通过 SMC 指令切换。

核心要点:QEMU 模拟器帮你省掉了开发板硬件成本,而且调试起来比真机方便得多。我建议你先把这套环境玩熟,再考虑上板子。

3.2 前置依赖安装

先别急着下载源码。把工具链装好,后面才不卡壳。我在项目里见过有人漏装某个包,编译到一半报错,排查半天……嗯,提前装齐最省心。

# Ubuntu 20.04 / 22.04 通用
sudo apt-get update
sudo apt-get install -y \
    git wget curl \
    build-essential \
    python3 python3-pip \
    python3-cryptography \
    python3-pyelftools \
    ninja-build \
    ccache \
    libssl-dev \
    libgmp-dev \
    libmpc-dev \
    libmpfr-dev \
    device-tree-compiler \
    cpio \
    unzip \
    rsync \
    bc \
    bison \
    flex \
    swig \
    libtool \
    autoconf \
    automake \
    pkg-config \
    libglib2.0-dev \
    libpixman-1-dev \
    libfdt-dev \
    libncurses-dev \
    xterm

小技巧:ccache 一定要装。OP-TEE 全量编译一次要十几分钟,加上 ccache 后增量编译能缩短到一两分钟。我每次重编都靠它省时间。

3.3 下载 OP-TEE 源码

OP-TEE 官方维护了一个 repo 仓库,把所有组件(OP-TEE OS、OP-TEE Client、Linux Kernel、Buildroot、QEMU 补丁)组织在一起。我们直接用 repo 工具拉取。

3.3.1 安装 repo 工具

mkdir -p ~/bin
curl https://storage.googleapis.com/git-repo-downloads/repo > ~/bin/repo
chmod a+x ~/bin/repo
export PATH=~/bin:$PATH

3.3.2 初始化并同步代码

mkdir -p ~/optee-qemu
cd ~/optee-qemu

# 初始化 manifest(以 4.0.0 稳定版为例)
repo init -u https://github.com/OP-TEE/manifest.git -m qemu_v8.xml -b 4.0.0

# 同步所有子仓库(这一步会下载几个 GB 的数据)
repo sync -j$(nproc)

注意:repo sync 时间较长,取决于你的网络。如果中途失败,重新执行 repo sync 即可,它会断点续传。我曾经在公司内网拉了一下午才搞定……建议找个网络好的时段操作。

同步完成后,目录结构大致如下:

目录/文件 说明
optee_os/OP-TEE 内核(安全侧 OS)
optee_client/REE 侧的用户态库和 CA 框架
optee_test/官方测试用例(含 xtest)
linux/Linux 内核源码(已打 OP-TEE 补丁)
build/构建脚本和 Makefile
out/编译输出目录

3.4 编译整个系统

OP-TEE 的构建系统基于 Makefile,入口在 build/ 目录。我们直接执行:

cd ~/optee-qemu/build
make -j$(nproc) toolchains
make -j$(nproc)

第一行 make toolchains 会下载 ARM64 交叉编译工具链(aarch64-linux-gnu-*)。只需要执行一次。第二行才是真正的全量编译。

编译产物:编译完成后,out/bin/ 目录下会生成以下关键文件:

  • flash.bin — 包含 OP-TEE OS + 安全分区的固件镜像
  • Image — Linux 内核镜像
  • rootfs.cpio.gz — 根文件系统(含 CA 和 TA)
  • qemu-system-aarch64 — 编译好的 QEMU 模拟器

3.5 启动 QEMU 并验证

编译完成后,启动模拟器:

cd ~/optee-qemu/build
make run-only

这会弹出两个终端窗口:一个显示 REE 侧(Linux 控制台),一个显示 TEE 侧(OP-TEE 安全监控输出)。

在 Linux 控制台登录(默认 root 无密码),然后运行官方测试:

# 在 QEMU 的 Linux 终端内执行
xtest

如果看到一堆 PASS,说明环境搭建成功。我第一次跑通时,看到满屏的 PASS,心里踏实多了。

调试小技巧:如果 xtest 卡住或报错,先检查 /dev/tee0 和 /dev/teepriv0 是否存在。这两个设备节点是 OP-TEE 驱动注册的,没有它们说明驱动没加载。

3.6 构建第一个 TA

环境跑通了,我们来写一个最简单的 TA。它只做一件事:接收两个整数,返回它们的和。

3.6.1 TA 源码结构

一个 TA 至少包含三个文件:

文件 作用
ta.cTA 主体逻辑,实现命令处理函数
ta.hTA 的 UUID 和命令 ID 定义
sub.mk构建描述文件,告诉编译器要编译哪些源文件

3.6.2 创建 TA 目录

mkdir -p ~/optee-qemu/out/ta/hello_world
cd ~/optee-qemu/out/ta/hello_world

3.6.3 编写头文件 ta.h

#ifndef TA_HELLO_WORLD_H
#define TA_HELLO_WORLD_H

/* UUID: 8aaaf200-2450-11e4-abe2-0002a5d5c51b */
#define TA_HELLO_WORLD_UUID \
    { 0x8aaaf200, 0x2450, 0x11e4, \
      { 0xab, 0xe2, 0x00, 0x02, 0xa5, 0xd5, 0xc5, 0x1b } }

/* 命令 ID */
#define TA_HELLO_WORLD_CMD_ADD    0

#endif

3.6.4 编写 TA 主体 ta.c

#include <tee_internal_api.h>
#include <tee_internal_api_extensions.h>
#include "ta.h"

TEE_Result TA_CreateEntryPoint(void)
{
    return TEE_SUCCESS;
}

void TA_DestroyEntryPoint(void)
{
}

TEE_Result TA_OpenSessionEntryPoint(uint32_t param_types,
                                    TEE_Param params[4],
                                    void **session_ctx)
{
    (void)param_types;
    (void)params;
    (void)session_ctx;
    return TEE_SUCCESS;
}

void TA_CloseSessionEntryPoint(void *session_ctx)
{
    (void)session_ctx;
}

TEE_Result TA_InvokeCommandEntryPoint(void *session_ctx,
                                      uint32_t cmd_id,
                                      uint32_t param_types,
                                      TEE_Param params[4])
{
    (void)session_ctx;

    if (cmd_id == TA_HELLO_WORLD_CMD_ADD) {
        /* 从参数中取出两个整数 */
        uint32_t a = params[0].value.a;
        uint32_t b = params[0].value.b;
        uint32_t result = a + b;

        /* 把结果写回参数 */
        params[0].value.a = result;
        return TEE_SUCCESS;
    }

    return TEE_ERROR_BAD_PARAMETERS;
}

3.6.5 编写构建描述 sub.mk

srcs-y += ta.c

3.6.6 编译 TA

回到 build/ 目录,执行:

cd ~/optee-qemu/build
make TA_DEV_KIT_DIR=../optee_os/out/arm-plat-vexpress/export-usr \
     O=../out \
     ta_hello_world

编译成功后,在 out/ta/hello_world/ 下会生成 8aaaf200-2450-11e4-abe2-0002a5d5c51b.ta 文件。这个 .ta 文件就是你的第一个可信应用。

常见错误:如果编译报错找不到 tee_internal_api.h,说明 TA_DEV_KIT_DIR 路径不对。检查一下 optee_os/out/arm-plat-vexpress/export-usr 是否存在。我刚开始也在这卡过,路径写错了。

3.7 将 TA 部署到 QEMU 中测试

把编译好的 .ta 文件打包进根文件系统,重新启动 QEMU:

cd ~/optee-qemu/build
make run

登录后,在 Linux 终端里手动加载 TA:

# 在 QEMU 的 Linux 终端内
mkdir -p /lib/optee_armtz
cp /mnt/ta/hello_world/*.ta /lib/optee_armtz/

然后写一个简单的 CA 程序调用它(这里不展开 CA 代码,后续章节会详细讲)。你只需要知道:TA 已经成功部署到安全世界了。

验证方法:在 OP-TEE 的启动日志中搜索你的 TA UUID(8aaaf200-2450-11e4-abe2-0002a5d5c51b)。如果看到类似 TA registered 的信息,说明 TA 加载成功。

3.8 本章小结

这一章我们做了三件事:

  • 在 Ubuntu 上搭建了 QEMU + OP-TEE 的完整开发环境
  • 全量编译并启动了模拟器,验证了 xtest 测试通过
  • 从零编写并编译了第一个 TA(加法器)

环境搭好了,后面的事情就好办了。下一章我们会深入 TA 的接口规范,看看 TA 和 CA 之间到底怎么传数据、怎么处理内存。不过那是后话,你先把这个环境跑熟,多编译几次,感受一下整个流程。

个人建议:第一次编译全量环境时,泡杯咖啡等着。编译完成后,把 out/ 目录备份一下。后面如果改坏了,直接恢复备份,不用重编。这招我用了好几年,很管用。