目录

以SampleCode/Switchless为例讲解ECALL Switchless的代码流程

第一个Switchless ECALL,需要在tRTS端初始化一下Switchless模式

接下来构建ECALL任务

用Switchless的方法来使用Swtichless ECALL

工人线程做了什么?

tRTS端初始化ECALL管理器

工人线程执行ECALL【process_switchless_call】

Switchless ECALL的内容就这么多了


以SampleCode/Switchless为例讲解ECALL Switchless的代码流程

void ecall_empty_switchless(void) {}

【ecall_empty_switchless】是这个例子中用到的典型的Swtichless ECALL,函数里面啥也不执行。

那编译的时候怎么能够知道这个函数是使用ECALL Swtichless模式呢?总不能函数符号名里面加一个Swtichless就说明它是Switchless模式的ECALL吧。这可能是个方法,但是无形地会限制函数名的写法(比如Ordinary ECALL名字里就不能再写Switchless字眼了)。因此如果要把这个ECALL标记为Switchless模式,那么我们就需要在EDL文件(Enclave Definition Language)中进行标记声明。

SampleCode/Switchless的EDL文件如下

//位于SampleCode/Switchless/Enclave/Enclave.edl
enclave {from "sgx_tstdc.edl" import *;from "sgx_tswitchless.edl" import *;trusted {public void ecall_repeat_ocalls(unsigned long nrepeats, int use_switchless);public void ecall_empty(void);public void ecall_empty_switchless(void) transition_using_threads;};untrusted {void ocall_empty(void);void ocall_empty_switchless(void) transition_using_threads;};
};

我们可以看到【transition_using_threads】这个标记就是说明该函数需要改成Switchless的ECALL。

那么是由谁来识别这个标记并把这个函数转换成Switchless的ECALL呢?是由sgx_edger8r来做的。sgx_edger8r会对EDL文件里的内容进行识别,并对SGX的桥函数(E/OCALL)进行编排。EDL文件里面还有对桥函数参数属性的设置,请见《SGX软硬件栈(四)——桥函数》

sgx_edger8r会将桥函数(Switchless或Ordinary)编排成型,变成所谓的stub(Enclave_{u,t}.{c,h})。使得你原来可能是直接在Enclave写了个ECALL函数,而实际ECALL调用过程变成了APP->stub(uRTS)->EENTER->stub(tRTS)->Enclave->ECALL。

因此sgx_edger8r对【ecall_empty_switchless】调整以后。在uRTS的stub端,变成了如下样子:

sgx_status_t ecall_empty_switchless(sgx_enclave_id_t eid)
{sgx_status_t status;status = sgx_ecall_switchless(eid, 2, &ocall_table_Enclave, NULL);return status;
}

可以看到【ecall_empty_switchless】通过【sgx_ecall_switchless】来尝试执行ECALL。

第一个Switchless ECALL,需要在tRTS端初始化一下Switchless模式

extern "C"
sgx_status_t sgx_ecall_switchless(const sgx_enclave_id_t enclave_id, const int proc, const void *ocall_table, void *ms)
{return _sgx_ecall(enclave_id, proc, ocall_table, ms, true);
}
static
sgx_status_t _sgx_ecall(const sgx_enclave_id_t enclave_id, const int proc, const void *ocall_table, void *ms, const bool is_switchless)
{...result = enclave->ecall(proc, ocall_table, ms, is_switchless);...
}

可以看到【sgx_ecall_switchless】第一个参数是Enclave ID,第二个参数是ECALL的索引值,第三个参数是Enclave内部可能会用到的OCALL表,第四个参数是传参的Marshalling。

【_sgx_ecall】额外增加了一个参数说明是不是Switchless的ECALL。我们这里是true。如果是Ordinary的ECALL,那么就是false了。

【_sgx_ecall】是【CEnclave::ecall】的套壳。

虽然和ECALL Switchless/Ordinary模式一样调用的【CEnclave::ecall】,但是参数【is_switchless】指明是不是用Switchless模式,现在这里是true。

sgx_status_t CEnclave::ecall(const int proc, const void *ocall_table, void *ms, const bool is_switchless)
{...if (m_switchless){// we need to pass ocall_table pointer to the enclave when initializing switchless on trusted side.if (m_first_ecall && ocall_table){// can create race condition here if we have several threads initiating "first" ecall// so it is possible the first switchless ecall will fallbackm_first_ecall = false;// we are setting the flag here, cause otherwise it will create deadlock in sl_on_first_ecall_func_ptr()g_sl_funcs.sl_on_first_ecall_func_ptr(m_switchless, m_enclave_id, ocall_table);}//Do switchless ECall in a switchless wayif (is_switchless){int need_fallback = 0;sgx_status_t ret = SGX_ERROR_UNEXPECTED;ret = g_sl_funcs.sl_ecall_func_ptr(m_switchless, proc, ms, &need_fallback);if (likely(!need_fallback)){se_rdunlock(&m_rwlock);return ret;}}}...
}

针对(Enclave ID所对应的)Enclave的第一个Swtichless ECALL,我们需要将【ocall_table】事先传入Enclave中。此处调用的【g_sl_funcs.sl_on_first_ecall_func_ptr】函数指针指向的是【sl_uswitchless_on_first_ecall】函数,这函数最终目的是让tRTS也保管一份uSwitchless管理器,这个uSwitchless管理器原来只在uRTS保留过一份。tRTS中的uSwitchless管理器保存到tRTS的全局变量【g_uswitchless_handle】。

// holds the pointer to untrusted structure describing switchless configuration
// pointer is assigned only after all necessary checks
struct sl_uswitchless* g_uswitchless_handle = NULL;

具体说来,这个【sl_uswitchless_on_first_ecall】函数准备一下参数,以【sl_call_once】的方式(只调用一次的方式)调用【init_tswitchless】->【sl_init_switchless(uRTS, stub)】->切换上下文进入到tRTS(EENTER)->【sl_init_switchless(tRTS, stub)】

(这里提到的stub是由sgx_edger8r生成的放在Enclave{u,t}.{c,h}里桥函数接口,在开发者眼里,我们可以直接调用ECALL_A,但是实际上完成的是这样的过程:【ECALL_A(uRTS)】->【ECALL_A stub(uRTS)】->切换上下文进入到tRTS(EENTER)->【ECALL_A stub(tRTS)】->【ECALL_A(tRTS)】)

/* Override the weak symbol defined in tRTS */
sgx_status_t sl_init_switchless(void* _handle_u)
{...if (lock_cmpxchg_ptr((void*)&g_uswitchless_handle, NULL, (void*)handle_u) != NULL)...
}

【sl_init_switchless(tRTS, stub)】中会让uSwitchless管理器存放到tRTS的【g_uswitchless_handle】。

之后在uRTS中,【init_tswitchless】函数将OCALL表挂到uSwitchless管理器上以及uSwitchless管理器所包含的OCALL管理器上。此时,uSwitchless管理器可以标记uRTS、tRTS中的Switchless初始化都完成了。

tRTS的Switchless既然初始化完了,那么就一次性的唤醒所有{t,u}Worker线程们。之前uRTS端Switchless初始化时,工人线程们激活后都在睡觉。

接下来构建ECALL任务

所使用的【g_sl_funcs.sl_ecall_func_ptr】函数指针指向【sl_uswitchless_do_switchless_ecall】(此时我们在uRTS)

/* sgx_ecall_switchless() from uRTS calls this function to do the real job */
sgx_status_t sl_uswitchless_do_switchless_ecall(void* _switchless,const unsigned int ecall_id,void* ecall_ms,int* need_fallback)

首先确保一下tRTS端的Switchless初始化完毕了,并且tRTS端有tWorker工人线程,数量不能为0。如果有睡觉的tWorker工人线程,那么全部给叫醒。

构造ECALL任务【struct sl_call_task call_task】

【struct sl_call_task call_task】成员变量 赋值 说明

status

SL_INIT,代表ECALL任务刚初始

ECALL任务状态

func_id

ecall_id

ECALL索引值

func_data

ecall_ms

ECALL的Marshalling参数

ret_code

SGX_ERROR_UNEXPECTED

ECALL的返回值

调用【sl_call_mngr_call】,通过信号线让tWorker工人线程执行Switchless ECALL。信号线的初始化在《SGX初始化中,uRTS端的Switchless模式的初始化》

如果Switchless ECALL时,没有可用的tWorker工人线程,那么通过回退Fallback成Ordinary ECALL,切换上下文执行ECALL。

用Switchless的方法来使用Swtichless ECALL

static inline int sl_call_mngr_call(struct sl_call_mngr* mngr, struct sl_call_task* call_task, uint32_t max_tries)
{/*Used to make actual switchless call by both enclave & untrusted codemngr:      points to ECALL or OCALL manager. For enclave, OCALL mngr and all its content are checkeds in sl_mngr_clone() functionsee init_tswitchless_ocall_mngr()call_task: contains all the information of function to be called,when called by enclave to make OCALL, call_task resides on enclaves stack*/.../* Allocate a free signal line to send signal */...// copy task data to internal array accessable by both sides (trusted & untrusted).../* Send a signal so that workers will access the buffer for switchless call* requests. Here, a memory barrier is used to make sure the buffer is* visible when the signal is received on other CPUs. */...// wait till the other side has picked the task for processing.../* The request must has been accepted. Now wait for its completion */...// copy the return code...
}

这个函数中,首先申请一个空闲的信号线【struct sl_siglines.free_lines】(一个Bit位代表一个信号)。将ECALL任务的状态从【SL_INIT】改成【SL_SUBMITTED】。

将ECALL任务复制到uSwitchless管理器保存的ECALL管理器的任务池中。前面说到过uRTS和tRTS都能访问这个存储在uRTS的uSwitchless管理器的地址。

【sgx_mfence】确保工人线程能够看到任务池中的ECALL任务。然后发送(本质是变量设置)信号线【struct sl_siglines.event_lines】的信号一个Bit位代表一个信号),tWorker会收到信号(本质是对信号线变量循环检测)并开始执行ECALL任务。

ECALL调用者线程然后循环等到ECALL任务被接受【SL_ACCEPTED】、被完成【SL_DONE】。

等到ECALL被完成之后,调用者线程就返回了。

那么ECALL任务的另一端,接收ECALL任务并执行的tWorker线程作了什么呢?

工人线程做了什么?

之前说到工人线程,以tWorker为例,uRTS初始化Switchless时候处于睡眠状态,当tRTS初始化Switchless时,{t,u}Worker两种工人线程会被唤醒。此时工人线程都还处于uRTS,对于tWorker来说,他需要进入到tRTS中去。

tWorker被唤醒之后,会进入【tworker_process_calls(uRTS)】->【sl_run_switchless_tworker(uRTS)】->切换上下文进入Enclave->【sl_run_switchless_tworker(tRTS)】。

/*=========================================================================* Process Fast ECalls*========================================================================*//* The builtin ECMD_RUN_SWITCHLESS_TWORKER ECall calls this function eventually */
// This is a worker's thread function, which polls the event_lines bitmap for incoming
// switchless ECALLs requests
sgx_status_t sl_run_switchless_tworker()

以【sl_once_t】只调用一次的方式调用在tRTS内部初始化ECALL管理器【init_tswitchless_ecall_mngr】,uRTS端初始化Switchless时只初始化了uRTS端的E/OCALL管理器,tRTS也需要一份E/OCALL管理器。

tRTS端初始化ECALL管理器

// initialize enclave's ecall manager
static uint64_t init_tswitchless_ecall_mngr(void* param)
{...// g_uswitchless_handle is checked in sl_init_switchless()...// clone ecall manager structure allocated outside enclave, performing all the relevant checks...// abort in case of bogus initialization...// allocate switchless ecalls table inside the enclave...
}

将uRTS的ECALL管理器连同它的信号线管理器克隆一份存到tRTS的【g_ecall_mngr】,主要是地址层面(仍属于uRTS)。

拷贝的信息不包括ECALL信号线的【free_lines】和tWorker处理ECALL的函数句柄【process_switchless_call】,uRTS的ECALL管理器的信号线管理器的处理ECALL的函数句柄是NULL(空),而不是【process_switchless_call】。

另外一个没有拷贝的是ECALL表【fcall_table】,这个ECALL表需要tRTS在Enclave内部重新创建一个出来,并注册到tRTS的【g_ecall_mngr】上。【fcall_table】的来源就是sgx_edger8r生成的存在stub【Enclave_t.c】中的【g_ecall_table】。

tRTS端初始化ECALL管理器的内容就这么多。

之后就是试图让工人线程执行ECALL任务【sl_call_mngr_process】->【sl_siglines_process_signals】->查看所有信号线每个Bit确定是否有信号来了->【process_switchless_call】。并且工人线程执行ECALL的失败重试次数是传参指定的或者默认(20000)的。

工人线程执行ECALL【process_switchless_call】

static inline void process_switchless_call(struct sl_siglines* siglns, uint32_t line)
{/*Main processing function which is called by a worker thread(trusted or untrusted)from sl_siglines_process_signals() function when there is a pending request for a switchless ECALL/OCALL.siglns points to the untrusted data structure with pending requests bitmap,siglns is checked by sl_siglines_clone() function before use by the enclaveline indicates which bit is set*/...
}

【process_switchless_call】首先将CALL管理器和CALL任务取出,然后将状态设置为【SL_ACCEPTED】。

然后用CALL任务中的索引值从【sl_call_table_t.func】(来源于stub【Enclave_t.c】中的【g_ecall_table】)获取ECALL函数的虚拟地址。

然后执行ECALL ID对应的ECALL。

Switchless ECALL的内容就这么多了

工人线程执行完ECALL后,将ECALL任务的状态设置为【SL_DONE】。

工人线程再次回到自己的Main Loop。

uRTS端等待ECALL执行完毕的线程,当看到任务状态变成了【SL_DONE】就返回了。

ECALL Swtichless调用及tRTS端Swtichless初始化相关推荐

  1. 【错误记录】Flutter 混合开发报错 ( Android 端与 Flutter 端 EventChannel 初始化顺序错误导致无法通信 | EventChannel 通信流程 )

    文章目录 一. 报错信息 二. Android 端与 Flutter 端 EventChannel 注册与监听流程 三. 解决方案 一. 报错信息 在 Android 端初始化 EventChanne ...

  2. 验证 Boost.Optional 复制构造函数不会尝试调用从模板化参数初始化构造函数的元素类型

    验证 Boost.Optional 复制构造函数不会尝试调用从模板化参数初始化构造函数的元素类型 实现功能 C++实现代码 实现功能 验证 Boost.Optional 复制构造函数不会尝试调用从模板 ...

  3. 为什么 TCP 三次握手期间,客户端和服务端的初始化序列号要求不一样?

    大家好,我是小林. 为什么 TCP 三次握手期间,客户端和服务端的初始化序列号要求不一样的呢? 接下来,我一步一步给大家讲明白,我觉得应该有不少人会有类似的问题,所以今天在肝一篇! 正文 为什么 TC ...

  4. 移动端css初始化(非常好用)

    移动端css初始化(非常好用) @charset "utf-8"; /* 禁用iPhone中Safari的字号自动调整 / html { -webkit-text-size-adj ...

  5. 移动端css初始化文件

    移动端css初始化文件 除了自己写之外,可以使用normalize .css 链接地址:https://necolas.github.io/normalize.css/ 有详细文档,模块化的样式.

  6. nacos集成dubbo实现远程服务调用多服务端2

    文章目录 一.版本选取.需求和项目简述 1. 版本选取 2. 项目模块说明 2. 需求说明 二.需求实战-依赖初始化 2.1. 创建maven父工程EShopParent 2.2. 创建子模块Dubb ...

  7. 跨域调用webapi web端跨域调用webapi

    web端跨域调用webapi 在做Web开发中,常常会遇到跨域的问题,到目前为止,已经有非常多的跨域解决方案. 通过自己的研究以及在网上看了一些大神的博客,写了一个Demo 首先新建一个webapi的 ...

  8. 【服务通信自定义srv调用3----客户端的优化】

    客户端的优化 服务通信自定义srv调用,客户端随意提交两个数,完成数的相加.也就是实现参数的动态提交: 1.格式:rosrun xxxx xxxx 12 34 2.节点执行时候,需要获取命令中的参数, ...

  9. netty源码学习之服务端客户端初始化

    文章目录 1. AbstractBootstrap类简介 1.1. 核心方法 2. netty服务端创建 2.1. 服务端启动入口 2.2. doBind()方法 2.3. netty服务初始化 2. ...

最新文章

  1. 【转】java提高篇(十)-----详解匿名内部类
  2. rest-framework:权限组件
  3. tomcat的简单认识
  4. JQuery Attributes 方法说明
  5. 【渝粤教育】广东开放大学 土木工程材料 形成性考核 (22)
  6. 初学Java(苏州实训之旅)
  7. 基于Vue学生选课管理系统
  8. Python黑帽子_hack与渗透测试编程之道 第三章代码
  9. catia中的螺旋伞齿轮画法_聚焦:螺旋伞齿轮画法要领
  10. 华为流程解析流程的端到端
  11. 一政网:公务员到底是做什么的?
  12. java unbox_Java ValueConversions.unbox方法代码示例
  13. BT源代码学习心得(六):跟踪服务器(Tracker)的代码分析(初始化)
  14. 2016Android公司面试题
  15. JavaScript|表格隔行变色(可作模板copy)
  16. 两年滴滴和头条的后端开发经验!字字都是肺腑之言!
  17. BZOJ1415【NOI2005】聪聪和可可
  18. data-toggle,data-target,data-dismiss
  19. 深入了解磁传感器和加速度传感器
  20. K3s集群部署+rancher部署并导入K3s集群

热门文章

  1. es 搜索引擎 倒排索引 lucene 基础概念
  2. 程序员如何说一口流利英语?她只用一招……
  3. 如何在手机上使用脚本
  4. seo高手推荐的学习seo的最好的网站
  5. 数据挖掘之jieba模块使用(读取单个文本内容(txt,word,pdf),对文章进行分词(中文)统计每个词语出现的次数并按从大到小排序,同时通过停用词库排除停用词)
  6. 如何用计算机cmd算数,计算命令:怎么在CMD的计算器命令是什么?
  7. Java:利用递归求解分桔子问题
  8. 校招面试C++后台开发岗大概学习到什么程度?
  9. C++_list快速学习入门(中英文结合)
  10. 语音分类入门案例: 英文数字音频分类