环境
ubuntu22.04
ADS + optee-fvp
调用流程梳理 这里直接从optee-examples中最简单的hello world入手来看的,从宏观上来看整个调用流程是 :
1 CA --> optee client --> tee driver --> ATF --> TEE --> TA
根据个人的理解画了个省流版本的图,省略了部分调用
CA & TA 的工作流程
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 //1. 初始化context用于和TEE交互 res = TEEC_InitializeContext(NULL, &ctx); //2. 打开“会话”,此时TEE侧会验证并且加载对应的TA res = TEEC_OpenSession(&ctx, &sess, &uuid, TEEC_LOGIN_PUBLIC, NULL, NULL, &err_origin); //3. 交互,通过invoke command来触发,调用到TA里具体的逻辑 res = TEEC_InvokeCommand(&sess, TA_HELLO_WORLD_CMD_INC_VALUE, &op, &err_origin); //4. 使用完毕,关闭“会话” TEEC_CloseSession(&sess); // 5. 释放context对象 TEEC_FinalizeContext(&ctx);
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 1. 执行的入口,会话的另一端TA_CreateEntryPoint TA_OpenSessionEntryPoint 2. 交互,业务代码TEE_Result TA_InvokeCommandEntryPoint (void __maybe_unused *sess_ctx, uint32_t cmd_id, uint32_t param_types, TEE_Param params[4 ]) { (void )&sess_ctx; switch (cmd_id) { case TA_HELLO_WORLD_CMD_INC_VALUE: return inc_value(param_types, params); case TA_HELLO_WORLD_CMD_DEC_VALUE: return dec_value(param_types, params); default : return TEE_ERROR_BAD_PARAMETERS; } } 3. 交互完毕,关闭会话TA_CloseSessionEntryPoint TA_DestroyEntryPoint
1 2 3 4 5 6 7 8 9 TEEC_OpenSession -> TA_CreateEntryPoint TA_OpenSessionEntryPoint TEEC_InvokeCommand -> TA_InvokeCommandEntryPoint TEEC_CloseSession -> TA_CloseSessionEntryPoint TA_DestroyEntryPoint
源码阅读 TEEC_InitializeContext TEEC_InitializeContext → 打开tee driver,要用于通信了 ,主要是一些初始化的工作
1 2 3 TEEC_InitializeContext teec_open_dev ioctl (fd, TEE_IOC_VERSION, &vers)
注意此时的CMD是 TEE_IOC_VERSION
,对应执行的是 tee_ioctl_version
TEEC_OpenSession 1 2 3 4 5 6 7 8 9 10 11 TEEC_OpenSession(&ctx, &sess, &uuid, TEEC_LOGIN_PUBLIC, NULL , NULL , &err_origin); .... rc = ioctl(ctx->fd, TEE_IOC_OPEN_SESSION, &buf_data);
此时CMD是 TEE_IOC_OPEN_SESSION
,到tee driver中查看对应的处理逻辑 :
往后会调用到对应的handler:
1 2 rc = ctx->teedev->desc->ops->open_session(ctx, &arg, params);
在进TEE之前,传递的参数需要做转换,反过来也是;从REE往TEE走,其实是一个入口 do_call_with_arg,这些operations
都定义在:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 struct optee_ops { int (*do_call_with_arg)(struct tee_context *ctx, struct tee_shm *shm_arg, u_int offs); int (*to_msg_param)(struct optee *optee, struct optee_msg_param *msg_params, size_t num_params, const struct tee_param *params); int (*from_msg_param)(struct optee *optee, struct tee_param *params, size_t num_params, const struct optee_msg_param *msg_params); };
直接在目录中搜open_session
发现有两个实现,这里的话ffa_abi.c
中的应该是FF-A标准对应的那个实现,这里直接看smc的那个就行, 即linux/drivers/tee/optee/smc_abi.c
里 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 910 while (true ) {911 struct arm_smccc_res res ;912 913 trace_optee_invoke_fn_begin(¶m);914 optee->smc.invoke_fn(param.a0, param.a1, param.a2, param.a3,915 param.a4, param.a5, param.a6, param.a7,916 &res);917 trace_optee_invoke_fn_end(¶m, &res);918 919 if (res.a0 == OPTEE_SMC_RETURN_ETHREAD_LIMIT) {920 924 optee_cq_wait_for_completion(&optee->call_queue, &w);925 } else if (OPTEE_SMC_RETURN_IS_RPC(res.a0)) {926 cond_resched();927 param.a0 = res.a0;928 param.a1 = res.a1;929 param.a2 = res.a2;930 param.a3 = res.a3;931 optee_handle_rpc(ctx, rpc_arg, ¶m, &call_ctx);932 } else {933 rc = res.a0;934 break ;935 }936 }
中间这个 smc.invoke_fn
就是通过smc进入到ATF,然后ATF会转发到TEE处理
对于ATF来说,这是一个通过 SMC #0
过来的中断,这是core内部发生的,且异常等级发生了变化,所以应该是到了ATF的第三组向量表的sync中断处理程序处
这里细节就不深入看了,主要是为了梳理工作流程,ATF里会调用到系统启动的时候注册的optee的tspd来处理,(opteed_smc_handler 函数)
这个handler里会保存 non-secure的上下文,恢复secure的上下文,然后直接eret到TEE侧。
进入optee之后来到:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 53 uint32_t thread_handle_std_smc (uint32_t a0, uint32_t a1, uint32_t a2, 54 uint32_t a3, uint32_t a4, uint32_t a5,55 uint32_t a6 __unused, uint32_t a7 __maybe_unused)56 { .... 69 if (a0 == OPTEE_SMC_CALL_RETURN_FROM_RPC) {70 thread_resume_from_rpc(a3, a1, a2, a4, a5);71 rv = OPTEE_SMC_RETURN_ERESUME;72 } else {73 thread_alloc_and_run(a0, a1, a2, a3, 0 , 0 );74 rv = OPTEE_SMC_RETURN_ETHREAD_LIMIT;75 } ...
第一次走到 thread_alloc_and_run,传入参数是 thread_std_smc_entry
, 所以会执行到 thread_std_smc_entry
后续的流程 :
1 2 3 4 5 6 __thread_std_smc_entry std_smc_entry (a0, a1, a2, a3) ; std_entry_with_parg(...) call_entry_std tee_entry_std __tee_entry_std
至此,到了关键的逻辑:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 538 TEE_Result __tee_entry_std(struct optee_msg_arg *arg, uint32_t num_params)539 {540 TEE_Result res = TEE_SUCCESS;541 542 543 thread_set_foreign_intr(true );544 switch (arg->cmd) {545 case OPTEE_MSG_CMD_OPEN_SESSION:546 entry_open_session(arg, num_params);547 break ;548 case OPTEE_MSG_CMD_CLOSE_SESSION:549 entry_close_session(arg, num_params);550 break ;551 case OPTEE_MSG_CMD_INVOKE_COMMAND:552 entry_invoke_command(arg, num_params);553 break ;554 case OPTEE_MSG_CMD_CANCEL:555 entry_cancel(arg, num_params);556 break ;557 #ifndef CFG_CORE_FFA 558 #ifdef CFG_CORE_DYN_SHM 559 case OPTEE_MSG_CMD_REGISTER_SHM:560 register_shm(arg, num_params);561 break ;562 case OPTEE_MSG_CMD_UNREGISTER_SHM:563 unregister_shm(arg, num_params);564 break ;565 #endif 566 #endif 567 568 case OPTEE_MSG_CMD_DO_BOTTOM_HALF:569 if (IS_ENABLED(CFG_CORE_ASYNC_NOTIF))570 notif_deliver_event(NOTIF_EVENT_DO_BOTTOM_HALF);571 else 572 goto err;573 break ;574 case OPTEE_MSG_CMD_STOP_ASYNC_NOTIF:575 if (IS_ENABLED(CFG_CORE_ASYNC_NOTIF))576 notif_deliver_event(NOTIF_EVENT_STOPPED);577 else 578 goto err;579 break ;580 581 default :582 err:583 EMSG("Unknown cmd 0x%x" , arg->cmd);584 res = TEE_ERROR_NOT_IMPLEMENTED;585 }586 587 return res;588 }
这次的cmd是 open session所以走 entry_open_session
函数
1 2 3 4 373 res = tee_ta_open_session(&err_orig, &s, &tee_open_sessions, &uuid,374 &clnt_id, TEE_TIMEOUT_INFINITE, ¶m);
然后去加载对应的TA,在 tee_ta_open_session // tee_ta_manager.c
1 715 res = tee_ta_init_session(err, open_sessions, uuid, &s);
加载完毕之后,如果成功加载了,那就调用 ts_ctx->ops->enter_open_session(&s->ts_sess);
根据注册信息,应该是 user_ta_enter_open_session
调用到 user_ta_enter 函数,此时还是在optee里的,需要跳到TA去执行
1 2 3 4 5 166 res = thread_enter_user_mode(func, kaddr_to_uref(session),167 (vaddr_t )usr_params, cmd, usr_stack,168 utc->uctx.entry_func, utc->uctx.is_32bit,169 &utc->ta_ctx.panicked,170 &utc->ta_ctx.panic_code);
S-EL1 → S-EL0,应该是eret过去的
1 2 3 __thread_enter_user_mode(regs, exit_status0, exit_status1); b eret_to_el0 eret
跳转前设置好了上下文,所以eret后就回到了TA中执行,这就到了TA中的 TA_OpenSessionEntryPoint
TEEC_InvokeCommand 逻辑基本和上面OpenSession差不多,差别就在于传递的 InvokeCommand
所以最后是走到
1 2 user_ta_enter_invoke_cmd user_ta_enter (s, UTEE_ENTRY_FUNC_INVOKE_COMMAND, cmd) ;
然后调用到TA的 TEEC_InvokeCommand
函数
TEEC_CloseSession 1 2 3 4 5 6 7 8 9 10 11 12 13 void TEEC_CloseSession (TEEC_Session *session) { struct tee_ioctl_close_session_arg arg ; memset (&arg, 0 , sizeof (arg)); if (!session) return ; arg.session = session->session_id; if (ioctl(session->ctx->fd, TEE_IOC_CLOSE_SESSION, &arg)) EMSG("Failed to close session 0x%x" , session->session_id); }
也是类似的情况,调用到内核里tee_ioctl_close_session ,区别只是cmd不同,最后会一路到TA侧的 TA_CloseSessionEntryPoint
TEEC_FinalizeContext 关闭打开的驱动
1 2 3 4 5 void TEEC_FinalizeContext (TEEC_Context *ctx) { if (ctx) close(ctx->fd); }
调试 根据上面的流程梳理,只要在optee 往TA里跳的时候下个断,就能去分析TA了,然后再加载TA的符号就能快乐地debug了,没有源码那就纯黑盒调试TA了
结合optee的文档 里的描述,会用到TA的 .text段 LMA信息
1 2 $ objdump -h 8 aaaf200-2450 -11e4 -abe2-0002 a5d5c51b.elf | grep ".text" 1 .text 00012e5 c 00000020 00000020 00001020 2 **2
启动ADS,然后在加载tee的时候断住,加载tee的符号,参考我上一篇博客 就行了。
如果想调试全部的过程,按照文章把 Linux kernel、 bl31 runtime 的符号也加载进来就行了
1 b user_ta_enter_open_session
然后执行CA,可以观察到已经断下来了
其实这个时候TEE侧log已经看到了TA被加载到了哪里了,直接下断也可以的
但是没断下来且报错了,很奇怪的是eret之后 还是显示SEL1,我查看了currentel寄存器之后发现确实是在EL0的
问了下组里的大佬,这个反汇编窗口显示的ELxS/N
应该是这块内存的属性,而不是当前执行状态 (之前直接靠这个tag来做判断,看来是错的离谱了)
个人猜测 因为TA加载是optee做的,所以可能optee分配出来的内存就是EL1S,所以跑到TA的时候,反汇编窗口地址tag会显示EL1S
然后尝试加载符号就行了:
1 add-symbol-file /home/muhe/Study/optee-fvp/out-br/build/optee_examples_ext-1.0/hello_world/ta/out/8aaaf200-2450-11e4-abe2-0002a5d5c51b.elf 0x40060020
参考
https://blog.csdn.net/weixin_42135087/article/details/119384252
https://www.timesys.com/security/trusted-software-development-op-tee/
https://optee.readthedocs.io/en/latest/building/gits/optee_examples/optee_examples.html