OP-TEE学习记录(1)
作者:互联网
OP-TEE文件结构
-
build : OP-TEE的编译目录
-
Linux:Linux的内核代码,在driver/tee下面存放的是OP-TEE在REE侧的驱动,任何Linux用户空间调用CA接口都会经过驱动转发到TEE侧。
-
optee_benchmark: OP-TEE运行的性能测试工具,只保存CA端的代码,TA部分代码在OP-TEE OS中,作为静态TA集成到OP-TEE中。
-
optee_client: 包含CA程序的用户空间接口库的源代码。以及tee_supplicant守护进程的代码。tee_supplicant主要作用是响应和处理来自TEE侧的RPC请求。
-
optee_example: 包含了示例的TA和CA代码。
-
optee_os:OP-TEE OS相关代码,编译完成后会生成相应镜像文件。
-
toolchains:交叉编译工具链。
-
trusted-firmware-a: 安全引导启动过程。
启动过程理解
Qemu 模拟计算机加电启动后的运行流程可以分成若干个阶段,每个阶段均由一层软件负责,每一层软件的功能是进行它应当承担的初始化工作,并在此之后跳转到下一层软件的入口地址,也就是将计算机的控制权移交给了下一层软件。第一个阶段由固化在 Qemu 内的一小段汇编程序负责。在optee的makefile文件中将-bios 指定bl1.bin,因此第二阶段会跳转到ATF中的bl1中执行。设置
- bl1初始化EL3运行环境的异常中要求断,完成相关硬件初始化,验证bl2镜像文件,再进入bl2运行。
- bl2设定异常向量初始表,初始化内存,栈,平台等,验证bl31镜像文件,触发SMC异常再进入bl31运行
- 设置bl31初始化EL3中断向量,硬件初始化,初始化EL3支持的服务并获取TEE OS入口地址,执行TEE启动,获取bl33镜像文件信息,建立bl33运行环境,启动bl33。一般第一个REE侧镜像文件为BootLoader, BootLoader会加载Linux内核。
此处讲一下我对启动流程的基本理解。我认为不管是启动固件,内核,还是应用程序,他们的加载流程都很类似,他们本质上都是实现特定功能的代码,然后通过具体的规则进行编译和链接得到镜像文件(应用程序可能是ELF格式的文件),这些文件通常能给我们提供一些信息,例如入口地址(ELF文件还可以获得每段起始地址,大小等信息)。然后当将这些文件中加载到固定地址时,我们同时设置特定寄存器的值,例如将指令寄存器设置为入口地址的值,然后程序就可以自动运行了。同时,运行环境,硬件初始化等操作应该都是设置相关寄存器的值使得程序能正常运行,不同硬件架构有不同的要求。
OP-TEE软件框架理解设置
CA使用libteec库中提供的接口来实现对TEE侧TA中具体命令的调用。libteec库都在optee_client/libteec目录下。
tee_supplicant作为守护进程,在linux启动时会被作为后台程序启动,然后进入一个无限循环,监控、处理、回复TEE侧的RPC请求。
OP-TEE驱动是REE侧和TEE侧之间进行数据交互的桥梁。tee_supplicant和libteec库中提供的接口都会通过系统调用的方式陷入Linux内核空间,然后Linux内核根据传递的参数找到OP-TEE驱动,并命中驱动中的operation结构体中具体处理函数来完成实际操作。对于OP-TEE驱动,一般所会触发安全监控模式(SMC),并将参数带入Monitor模式或者EL3,在Monitor中执行正常世界状态与安全世界状态的切换,待状态切换完成后,会将驱动段带入的参数传递给OP-TEE中的线程进行进一步处理。
系统调用和SMC理解
在此,我简单说明一下我对系统调用的理解因为不同的体系结构可能存在不同的设置,并且认为SMC的原理可由系统调用类比理解。
为确保操作系统的安全,对应用程处理处理序而言,需要限制的主要有两个方面:
- 应用程序不能访问任意的地址空间(操作系统虚存管理)
- 应用程序不能执行某些可能破坏计算机系统的指令(特权级)
假设有了这样的限制,我们还需要确保应用程序能够得到操作系统的服务,即应用程序和操作系统还需要有交互的手段。使得低特权级软件只能做高特权级软件允许它做的,且超出低特权级软件能力的功能必须寻求高特权级软件的帮助。这样,高特权级软件(操作系统)就成为低特权级软件(一般应用)的软件执行环境的重要组成部分。
为了实现这样的特权级机制,需要进行软硬件协同设计。一个比较简洁的方法就是,处理器设置两个不同安全等级的执行环境:用户态特权级的执行环境和内核态特权级的执行环境。且明确指出可能破坏计算机系统的内核态特权级指令子集,规定内核态特权级指令子集中的指令只能在内核态特权级的执行环境中执行。处理器在执行指令前会进行特权级安全检查,如果在用户态执行环境中执行这些内核态特权级指令,会产生异常。
为了让应用程序获得操作系统的函数服务,采用传统的函数调用方式(即通常的 call
和 ret
指令或指令组合)将会直接绕过硬件的特权级保护检查。所以可以设计新和来咯的机器指令:执行环境调用(Execution Environment Call,简称 ecall
)和执行环境返回(Execution Environment Return,简称 eret
)):
ecall
:具有用户态到内核态的执行环境切换能力的函数调用指令eret
:具有内核态到用户态的执行环境切换能力的函数返回指令
硬件具有了这样的机制后,还需要操作系统的配合才能最终完成对操作系统自身的保护。首先,操作系统需要提供相应的功能代码,能在执行 eret
前准备和恢复用户态执行应用程序的上下文。其次,在应用程序调用 ecall
指令后,能够检查应用程序的系统调用参数,确保参数不会破坏操作系统。
那系统调用的具体实现方式是什么呢?我举个例子,在 RISC-V 调用规范中,约定寄存器 a0~a6
保存系统调用的参数, a0
保存系统调用的返回值。有些许不同的是寄存器 a7
用来传递 syscall ID,这是因为所有的 syscall 都是通过 ecall
指令触发的,除了各输入参数之外我们还额外需要一个寄存器来保存要请求哪个系统调用。示例代码如下。
llvm_asm!("ecall"
: "={x10}" (ret)
: "{x10}" (args[0]), "{x11}" (args[1]), "{x12}" (args[2]), "{x17}" (id)
: "memory"
: "volatile"
);
当 CPU 执行完一条指令(如 ecall
)并准备从用户特权级 陷入( Trap
)到 S 特权级的时候,硬件会自动完成如下这些事情:
- 寄存器会被修改为 CPU 当前的特权级(U/S)。
- 寄存器会被修改为 Trap 处理完成后默认会执行的下一条指令的地址。
- 寄存器分别会被修改成这次 Trap 的原因以及相关的附加信息。
- CPU 会跳转到寄存器所设置的 Trap 处理入口地址,并将当前特权级设置为 S ,然后从Trap 处理入口地址处开始执行。
这个trap的入口地址需要我们指定,可以用一段汇编完成,主要的功能就是保存上下文信息,然后跳转到一个异常处理的函数handler, handler一般通过x17寄存器的值得到系统调用号,做相应的处理。
CA调用流程
具体分析案例是optee_example中的hello_world.
可以看出主要调用流程为在client端的open和ioctl函数,这两个函数会陷入Linux内核空间调用具体tee驱动的相应函数:tee_open和tee_ioctl。在tee_ioctl中通过参数cmd来进行命令的switch从而调用相应的处理函数tee_ioctl_XXX,比如例中的tee_ioctl_version。在相应的处理函数tee_ioctl_XXX中会调用optee设备的相应函数,比如例中的optee_get_version来进行具体的操作,在例中为初始化ctx上下文中参数。
标签:调用,记录,特权,tee,TEE,内核,OP 来源: https://www.cnblogs.com/ppan-y/p/16381354.html