环境 
  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