目录
SGX初始化中,我们就碰到过切换上下文进入Enclave的场景
以【ECMD_INIT_ENCLAVE】为例,先大概描述一下切换上下文进入Enclave的具体过程
Switch模式进入Enclave执行ECMD_INIT_ENCLAVE任务【CEnclave::ecall】
先取一个【CTrustThread】用于ECALL Switch【CTrustThreadPool::acquire_thread】
如果不支持EDMM,就另外拿取一个TCS【CTrustThreadPool::_acquire_thread】
回到【CTrustThreadPool::acquire_thread】
提取【tcs_t】结构体,进一步执行ECALL【do_ecall】
进一步执行ECALL,切换上下文【enter_enclave(uRTS)/__morestack】
第一次进入Enclave(EENTER硬件指令),【ECMD_INIT_ENCLAVE】任务
当前已经在tRTS了,进一步去完成【ECMD_INIT_ENCLAVE】任务【enter_enclave(tRTS)】
对于用户编写的的ECALL函数,ECALL Switch过程是什么样的呢?
处理线程数据(、tRTS pthread),然后进一步ECALL【do_ecall(tRTS)】
查ECALL表,找到ECALL索引值对应的虚拟地址,然后执行之【trts_ecall】
SGX初始化中,我们就碰到过切换上下文进入Enclave的场景
在SGX初始化过程中,我们其实就碰到了需要切换上下文进入Enclave完成【ECMD_INIT_ENCLAVE】任务,这个任务主要是为了在tRTS内完成一些必要的SGX初始化工作。在《再回顾SGX初始化(三)》中我们提到过。
又比如在支持EDMM时,我们可能需要动态生成TCS,那么也需要切换上下文进入Enclave完成【ECMD_MKTCS】任务,用于动态生成TCS。在《再回顾SGX初始化(四)》、《构建动态TCS页》中我们提到过。
以【ECMD_INIT_ENCLAVE】为例,先大概描述一下切换上下文进入Enclave的具体过程
流程:【CEnclave::ecall(uRTS)】->【do_ecall(uRTS)】->【enter_enclave/__morestack(uRTS, AS)】->【enclave_entry(tRTS, AS)】->【enter_enclave(tRTS)】->【do_init_enclave(tRTS)】
【ECMD_INIT_ENCLAVE】任务是SGX初始化中重要的一步。不过,在tRTS里如何具体处理【ECMD_INIT_ENCLAVE】任务还请看《再回顾SGX初始化(三)》,这里主要是为了讲述ECALL Switch或者说如果切换上下文进入Enclave。
我们使用【CEnclave::ecall】作为一个常用的接口来切换上下文进入Enclave,虽然【CEnclave::ecall】只是一个封装。
int status = enclave->ecall(ECMD_INIT_ENCLAVE, NULL, reinterpret_cast<void *>(&info));
普及:ECALL索引值和tRTS内ECALL符号表
【CEnclave::ecall】的proc参数可选项如下,负数代表了进入Enclave的特殊目的是什么。
补充:【sgx_ecall(uRTS)】最终也是调用的【CEnclave::ecall】,增加的动作包括在【CEnclave::ecall】之前对Enclave的引用计数加一,在【CEnclave::ecall】之后对Enclave的引用计数减一。
而自然数(≥0的整数,这里的ECMD_ECALL只是一个示意,比如某个ECALL的Index值可能是7)则代表每一个用户编写的ECALL的Index索引值,这个Index索引值可以在tRTS中存储的ECALL符号表【g_ecall_table】中找到对应ECALL函数的虚拟地址,在tRTS中通过调用这个ECALL的虚拟地址完成ECALL的执行,并且在uRTS中直接使用ELRANGE中的虚拟地址会由PMH硬件和、EPCM数据结构判断这个访问控制是非法的,也就是SGX的中止页面语义。
/* ECALL command */
#define ECMD_ECALL 0
#define ECMD_INIT_ENCLAVE -1
#define ECMD_ORET -2
#define ECMD_EXCEPT -3
#define ECMD_MKTCS -4
#define ECMD_UNINIT_ENCLAVE -5
#define ECMD_ECALL_PTHREAD (-6) //linux-sgx v2.8所做的修改,Commit ID:9ddec08
这里举例说明0以上每一个整数都对应了一个ECALL函数,也就是说这个Index整数本质上就是一个索引值,用来在【g_ecall_table】索引具体的ecall地址。
【g_ecall_table】如下所示:
//位于SampleCode/SampleEnclave/Enclave/Enclave_t.c
SGX_EXTERNC const struct {
size_t nr_ecall;
struct {void* ecall_addr; uint8_t is_priv; uint8_t is_switchless;} ecall_table[32];
} g_ecall_table = {
32,
{
{(void*)(uintptr_t)sgx_ecall_type_char, 0, 0},
{(void*)(uintptr_t)sgx_ecall_type_int, 0, 0},
{(void*)(uintptr_t)sgx_ecall_type_float, 0, 0},
{(void*)(uintptr_t)sgx_ecall_type_double, 0, 0},
{(void*)(uintptr_t)sgx_ecall_type_size_t, 0, 0},
...
}
};
比如用户编写的【ecall_type_char】对应的Index就是【proc==0】。我们在uRTS中使用0索引值,切换上下文进入到Enclave后,在tRTS的【g_ecall_table】中查到【sgx_ecall_type_char】的虚拟地址,额外提醒一下【sgx_ecall_type_char】是Enclave内部的(也是我们用户编写的)【ecall_type_char】函数的壳。
//位于SampleCode/SampleEnclave/App/Enclave_u.c
sgx_status_t ecall_type_char(sgx_enclave_id_t eid, char val)
{
sgx_status_t status;
ms_ecall_type_char_t ms;
ms.ms_val = val;
status = sgx_ecall(eid, 0, &ocall_table_Enclave, &ms);
return status;
}
//位于SampleCode/SampleEnclave/Enclave/Enclave_t.c
static sgx_status_t SGX_CDECL sgx_ecall_type_char(void* pms)
{
CHECK_REF_POINTER(pms, sizeof(ms_ecall_type_char_t));
//
// fence after pointer checks
//
sgx_lfence();
ms_ecall_type_char_t* ms = SGX_CAST(ms_ecall_type_char_t*, pms);
sgx_status_t status = SGX_SUCCESS;
ecall_type_char(ms->ms_val);
return status;
}
再比如(用户编写的)【ecall_type_int】对应的Index就是【proc==1】。我们在uRTS中使用1索引值,切换上下文进入到Enclave后,在tRTS的g_ecall_table中查到【sgx_ecall_type_int】的虚拟地址,【sgx_ecall_type_int】是Enclave内部的【ecall_type_int】函数的壳。
//位于SampleCode/SampleEnclave/Enclave/Enclave_u.c
sgx_status_t ecall_type_int(sgx_enclave_id_t eid, int val)
{
sgx_status_t status;
ms_ecall_type_int_t ms;
ms.ms_val = val;
status = sgx_ecall(eid, 1, &ocall_table_Enclave, &ms);
return status;
}
//位于SampleCode/SampleEnclave/Enclave/Enclave_t.c
static sgx_status_t SGX_CDECL sgx_ecall_type_int(void* pms)
{
CHECK_REF_POINTER(pms, sizeof(ms_ecall_type_int_t));
//
// fence after pointer checks
//
sgx_lfence();
ms_ecall_type_int_t* ms = SGX_CAST(ms_ecall_type_int_t*, pms);
sgx_status_t status = SGX_SUCCESS;
ecall_type_int(ms->ms_val);
return status;
}
Switch模式进入Enclave执行ECMD_INIT_ENCLAVE任务【CEnclave::ecall】
【sgx_ecall(uRTS)】最终也是调用的【CEnclave::ecall】,增加的动作包括在【CEnclave::ecall】之前对Enclave的引用计数加一,在【CEnclave::ecall】之后对Enclave的引用计数减一。
sgx_status_t CEnclave::ecall(const int proc, const void *ocall_table, void *ms, const bool is_switchless)
【CEnclave::ecall】中首先需要上一个读写锁,这是因为可能同时有多个ECALL发生,这旨在保护CEnclave的成员变量(后面很多情况其实都需要开始考虑并发问题了)。当执行【ECMD_INIT_ENCLAVE】任务时,我们还没有初始化过Swtichless模式。
先取一个【CTrustThread】用于ECALL Switch【CTrustThreadPool::acquire_thread】
我们先取得一个【CTrustThread】(对应了一个TCS,前面提到过)。做法是从【CEnclave::m_thread_pool】中取得一个【CTrustThread】对象(实际上一个【CTrustThread】对象对应一个TCS,后面直接用【TCS_CTT】,【CTrustThread】首字母缩写,来指代【CTrustThread】)。
我们当前的ECALL是一个特殊的ECALL,是为了完成【ECMD_INIT_ENCLAVE】任务。
平台情况 | 所选择用来完成ECMD_INIT_ENCLAVE任务的线程 |
SGX支持EDMM等特性,那么就支持通用线程【CTrustThreadPool::m_utility_thread】这个特殊的【TCS_CTT] | 我们就用通用线程【m_utility_thread】来完成【ECMD_INIT_ENCLAVE】任务。 |
如果不支持通用线程【CTrustThreadPool::m_utility_thread】 | 调用【CTrustThreadPool::_acquire_thread】获得一个【TCS_CTT】来完成【ECMD_INIT_ENCLAVE】任务。 |
如果不支持EDMM,就另外拿取一个TCS【CTrustThreadPool::_acquire_thread】
具体做法如下:
先后顺序 | 操作 |
A. 从TCS_CTT缓存【m_thread_list】寻找可用【TCS_CTT】 | 首先试图从一个“【TCS_CTT】缓存”(【CTrustThreadPool::m_thread_list】,可以类比内核对堆管理时候的那个堆块的缓存,这里的缓存并不是指CPU内部的Cache)获取一个【TCS_CTT】,这样省去了获取【TCS_CTT】的很多繁琐的操作。这个缓存是线程ID到【TCS_CTT】的映射关系。 因此先获取一个线程ID,这个线程ID是和一个全局Key【g_tid_key】绑定的,当【g_tid_key】未绑定任何线程ID时,就用当前线程与【g_tid_key】绑定。然后到TCS_CTT缓存处【CTrustThreadPool::m_thread_list】去找指定线程ID对应的【TCS_CTT】,并且这个【TCS_CTT】不能是【CTrustThreadPool::m_utility_thread】(不过当前我们碰不到这个情况)。 |
B. 从【TCS_CTT】空闲集【m_free_thread_vector】寻找可用【TCS_CTT】 | 如果【m_thread_list】里面没有可用的对象缓存,那么就要去【m_free_thread_vector】(前面有提到过,代表空闲的【TCS_CTT】集)里面取一个出来,同时如果是从空闲【TCS_CTT】集里面取出来的对象,那么将这个【TCS_CTT】和前面获取的线程ID进行绑定,放到【TCS_CTT】缓存【CTrustThreadPool::m_thread_list】中,方便后续使用。 |
C. 回收【TCS_CTT】缓存中空闲项,用作【TCS_CTT】 | 如果连【m_free_thread_vector】中也没有可用的【TCS_CTT】,那么就进一步对【m_thread_list这个缓存中引用数【CTrustThread::m_reference】为0的【TCS_CTT】放回到【m_free_thread_vector】中(调用【CThreadPoolUnBindMode::garbage_collect】,因为我们使用Switchless扩展特性,因此采用的非Bind模式的TCS池)。然后再把空闲的【TCS_CTT】给用起来去完成【ECMD_INIT_ENCLAVE】任务。 这里的和A的区别在于,A是对已经绑定线程ID的引用数不为0的【TCS_CTT】行使用,而这里是是对引用数为0的【TCS_CTT】进行使用。 |
D. 没有任何可用【TCS_CTT】 | 如果没有【TCS_CTT】缓存空闲项可回收,那么返回NULL。 |
回到【CTrustThreadPool::acquire_thread】
我们对取得的【TCS_CTT】的引用数加1。
回到【CEnclave::ecall】
此时我们已经取得了【TCS_CTT】。之后对【CEnclave::m_ocall_table】更新为传参【ocall_table】(此时【ocall_table】为NULL,因为我们仅仅是为了【ECMD_INIT_ENCLAVE】任务)。紧接着调用【do_ecall】。
提取【tcs_t】结构体,进一步执行ECALL【do_ecall】
ret = do_ecall(proc, m_ocall_table, ms, trust_thread);
//位于psw/urts/linux/sig_handler.cpp
int do_ecall(const int fn, const void *ocall_table, const void *ms, CTrustThread *trust_thread)
从【TCS_CTT】里面提取出【tcs_t】结构体后,调用【enter_enclave】这个汇编函数。
进一步执行ECALL,切换上下文【enter_enclave(uRTS)/__morestack】
status = enter_enclave(tcs, fn, ocall_table, ms, trust_thread);
//位于psw/urts/linux/sig_handler.cpp
//trust_thread is saved at stack for ocall.
#define enter_enclave __morestack
extern "C" int enter_enclave(const tcs_t *tcs, const long fn, const void *ocall_table, const void *ms, CTrustThread *trust_thread);
如果不了解汇编可以查看《Red Hat Enterprise Linux 3》《x86 and amd64 instruction reference》
//位于psw/urts/linux/enter_enclave.S
DECLARE_GLOBAL_FUNC __morestack
//__morestack:
EENTER_PROLOG
movl frame_arg1, %edi /* fn */
#if defined(__x86_64__)
/* we defined fn as int, so we do sign extend.*/
movslq %edi, %rdi
#endif
mov frame_arg3, %xsi /* ms */
.Ldo_eenter:
# clean the upper bits of YMM registers
lea_symbol g_clean_ymm, %xbx
movl (%xbx), %ecx
cmpl $0, %ecx
je 1f
vzeroupper
1:
mov frame_arg0, %xbx /* tcs addr */
lea_pic .Lasync_exit_pointer, %xcx /* aep addr */
mov $SE_EENTER, %xax /* EENTER leaf */
.Leenter_inst:
ENCLU
...;some code
.size __morestack, .-__morestack
【DECLARE_GLOBAL_FUNC】是他们写的一个宏,目的是为了将这个汇编代码段标记为一个“全局函数”。其中【_CET_ENDBR】是根据环境选择endbr64或endbr32(Terminate an Indirect Branch in 64/32-bit Mode),详细信息可以在《Control-flow Enforcement Technology Specification》中搜索“endbr”。
【EENTER_PROLOG】也是一个宏,包括了CFI的相关选项的配置,通用目的寄存器和传参的保存,并预留一个栈槽用于保存一个指向xsave数据的栈空间,大小是【g_xsave_size】值(esp需要64字节对齐),留出一个影子栈空间给参数用,ES段中保存扩展X特性寄存器。将xsave数据通过XSAVEC指令(【g_xsave_enabled==1】,代表支持X特性)或FXSAVE(【g_xsave_enabled==0】)进行保存(调用【save_xregs】,传参是之前预留的xsave数据的栈空间)。总的来说这个宏的目的就是为了保存寄存器状态、X特性状态等。
接着回来讲【__morestack】做了什么。
【EENTER_PROLOG】之后,会开始设置XDI、XSI、YMM、XAX、XBX、XCX(如下表),然后调用ENCLU硬件指令。如果不了解ENCLU,可以参考《SGX软件栈(二)——硬件指令》
调用ENCLU硬件指令时 ,寄存器的值 | 值 |
XAX | 2,代表EENTER功能叶。 |
XBX | frame_arg0,代表tcs_t对象的地址。 |
XCX | .Lasync_exit_pointer(AEP)的地址。代表异步退出以后,处理完异常后,重新试图进入Enclave的跳板。类似于我们调用函数返回后的CALLER中的下一条指令地址。 |
XDI | frame_arg1,对应参数fn(我们这里是ECMD_INIT_ENCLAVE) |
XSI | frame_arg3,对应ms(marshalling structure,编排好的结构体) |
YMM | 根据XFRM_YMM_BITMASK来确定,是不是需要调用vzeroupper硬件指令清零YMM寄存器高位 |
第一次进入Enclave(EENTER硬件指令),【ECMD_INIT_ENCLAVE】任务
此时我们就进入Enclave环境了,并且此时是我们第一次进入Enclave。同时EENTER的进入点是【enclave_entry】(也就是【EnclaveBase+TCS.OENTRY】,Enclave文件的元数据中的TCS布局都已经把这些都安排好了。在形成Enclave文件时,由SignTool来完成),这也是我们在lds文件中标记的全局符号。
//位于sdk/trts/linux/trts_pic.S
/*
* ---------------------------------------------------------------------
* Function: enclave_entry
* The entry point of the enclave.
*
* Registers:
* XAX - TCS.CSSA
* XBX - the address of a TCS
* XCX - the address of the instruction following the EENTER
* XDI - the reason of entering the enclave
* XSI - the pointer to the marshalling structure
*/
DECLARE_GLOBAL_FUNC enclave_entry
可以看到除了XAX,寄存器的值代表的内容都是一致的。
当进入Enclave是为了处理异常时,XAX从ENCLU叶号替换成了【TCS.CSSA】(Current Slot Index of an SSA frame)的用途。我们这里并不是处理异常的情况,所以值为0,也可以说是因为我们刚进入Enclave,并没有之前因为AEX退出Enclave而使用SSA对Enclave内部寄存器进行保存。
如果进入Enclave是为了处理异常,比如说发生AEX,然后进入uRTS,OS完成初步异常处理,有些异常可能需要Enclave内部才能处理,那么就需要进入Enclave专门处理这个异常。
《CoSMIX: a compiler-based system for secure memory instrumentation and execution in enclaves》里面介绍的还不错。
进入Enclave的目的可以参考《进入Enclave的目的归类》。
首先对Flag寄存器标志位进行清除,使得初始化为如下。Flag可以参考《FLAGS register》
Flag | 值 |
OF | 0 |
SF | 0 |
AF | 0 |
CF | 0 |
ZF | 1 |
PF | 1 |
DF | 0 |
根据进入Enclave目的的不同,我们还需要分别对栈空间地址进行选择,(进入Enclave处理异常情况,还需要额外对CSSA进行保存)
进入Enclave目的 | 操作 |
常规(刚进入Enclave,包括【ECMD_INIT_ENCLAVE】任务) | XSP、XBP的值就是【XBX(tcs_t对象地址)- 1<<16 - STATIC_STACK_SIZE】,也就是说我们给刚进入Enclave的线程定一个栈空间 |
进入Enclave处理异常 | 需要跳转到【.Ldo_handler】将CSSA存到XDX寄存器上。同时栈空间的XSP、XBP寄存器也是设置为【XBX(tcs_t对象地址)- 1<<16 - STATIC_STACK_SIZE】 |
之前退出过并重进入Enclave | 使用之前的栈空间,将XSP、XBP设置为之前的栈空间 |
清除EFLAGS中的AC位(调用CLEAN_XFLAGS)。
将不可信世界的XSP压栈,将CSSA、TCS、XSI、XDI压栈。使用预制的SYNTHETIC_STATE(编译时指明放在.niprod Section)来重置X特性寄存器调用XRSTOR【g_xsave_enabled==1,代表支持X特性】、FXRSTOR硬件指令【g_xsave_enabled==0,代表不支持X特性】,目的是为了清理X特性寄存器。
调用tRTS内的【enter_enclave】这个C语言函数(前面用汇编主要是为了对上下文环境的切换),传参情况如下
传参 | 传参所处位置 |
Index(fn、proc) | 64位放到XDI;32位处于栈中 |
ms | 64位放到XSI;32位处于栈中 |
TCS | 64位放到XDX;32位处于栈中 |
CSSA | 64位放到XCI;32位处于栈中 |
当前已经在tRTS了,进一步去完成【ECMD_INIT_ENCLAVE】任务【enter_enclave(tRTS)】
extern "C" int enter_enclave(int index, void *ms, void *tcs, int cssa) __attribute__((section(".nipx")));
查询【g_enclave_state】状态(调用【sgx_is_enclave_crashed】,该函数被编译到.nipx节)判断Enclave是否崩溃状态,这个值初始为【ENCLAVE_INIT_NOT_STARTED】,因此目前不是【CRASH】状态。
else if(index == ECMD_INIT_ENCLAVE) { error = do_init_enclave(ms, tcs); }
然后调用【do_init_enclave】,去真正的执行【ECMD_INIT_ENCLAVE】任务。
在tRTS里如果处理【ECMD_INIT_ENCLAVE】任务还请看《再回顾SGX初始化(三)》,这里主要是为了讲述ECALL Switch或者说如果切换上下文进入Enclave。
对于用户编写的的ECALL函数,ECALL Switch过程是什么样的呢?
一开始的流程依然是【sgx_ecall(uRTS)】->【sgx_ecall(uRTS)】->【CEnclave::ecall(uRTS)】->【do_ecall(uRTS)】->【enter_enclave/__morestack(uRTS, AS)】->【enclave_entry(tRTS, AS)】->【enter_enclave(tRTS)】。
在【enter_enclave(tRTS)】中,由于我们目标是执行一个用户编写的ECALL,因此我们的Index是≥0的。稍微提一下,前面说到的ECMD_INIT_ENCLAVE、ECMD_ORET、ECMD_MKTCS、ECMD_UNINIT_ENCLAVE、ECMD_EXCEPT都有专门的处理函数。
对于试图执行用户编写的ECALL。我们首先初始化栈保护机制,然后调用【do_ecall】来在tRTS内执行ECALL。
extern "C" int enter_enclave(int index, void *ms, void *tcs, int cssa)
{
...
// Initialize stack guard if necessary
init_stack_guard(tcs);
error = do_ecall(index, ms, tcs);
...
}
初始化栈保护机制(tRTS)
static void __attribute__((section(".nipx"))) init_stack_guard(void *tcs)
通过线程上下文获取当前的线程数据(记录了线程栈信息、线程局部存储信息等)。如果没有对当前TCS的线程数据中的栈保护页初始化过,那么tRTS内部读取一个随机数来初始化栈保护页。我们前面提到过【ECMD_INIT_ENCLAVE】任务时,通用线程的线程数据被初始化过,但我们此时很可能用的是一个线程数据没有被初始化过的TCS。
处理线程数据(、tRTS pthread),然后进一步ECALL【do_ecall(tRTS)】
sgx_status_t do_ecall(int index, void *ms, void *tcs)
检查Enclave状态。
检查线程数据是否被初始化过。检查是否是UnBind TCS策略。检查这个TCS之前是不是给tRTS pthread线程用过,并且这个tRTS pthread线程现在已经退出了【SGX_PTHREAD_EXIT】。检查这个TCS是不是给tRTS pthread用的。按需初始化一下线程数据【do_init_thread】。
《SGXv2.8起Enclave内新增pthread库》从linux-sgx v2.8开始,支持在tRTS创建pthread线程,这个会涉及SGX内部开辟空间和分配TCS给这个pthread线程,同时SGX内部创建pthread线程也需要OCALL到uRTS,来创建好pthread线程并重新进入到tRTS中。
判断当前是不是Root ECALL。通过当前线程栈是否从栈基址开始增长来判断。像ECALL->OCALL->ECALL这种嵌套情况下,后面的ECALL就不是Root ECALL了,如果不是Root ECALL,那么TCS对应的线程栈上就有多个栈空间存在。如果不是Root ECALL,直接【trts_ecall】,只有Root ECALL需要开启tRTS pthread支持,因为非Root ECALL之前在Root ECALL阶段已经做过一次了。
由于从SGX v2.8开始支持tRTS pthread,因此这里默认试图开启tRTS pthread【调用_pthread_enabled】,构建一个空的TLS信息结构体【pthread_info_tls】(【pthread_info】类型)。(如果【_pthread_enabled】开启失败,直接【trts_ecall】)
pthread_info结构体成员变量 | 赋值 | 说明 |
m_local_storage | NULL | |
m_pthread | NULL | |
m_state | SGX_SUCCESS | TCS的TLS状态 |
m_mark | 初始为全零。紧接着存储【setjmp】获取的当前的上下文,作为一个上下文保存点。 未来【longjmp】函数会跳转到这里,此时伪装成【setjmp】返回,但是返回值非零。返回值非零,意味着ECALL内部调用了【pthread_exit】让该线程终止,因此需要清理线程栈,并把线程TCS的TLS状态置为【SGX_PTHREAD_EXIT】 | TCS的TLS上下文,【jmp_buf】类型 |
然后就调用【trts_ecall】进一步执行ECALL。这里有个小细节就是,用了【random_stack_advance】函数在栈空间中塞了一段无意义的随机大小的缓冲区,使得【trts_ecall】用到的EBP变得随机。
ECALL执行完成返回后,或者是【trts_ecall】执行完,也或者tRTS pthread调用【pthread_exit】然后跳转到【setjmp】“Fake”返回,总归是返回了。对于【pthread_exit】返回,将线程TCS的TLS状态置为【SGX_PTHREAD_EXIT】,标记下一次Root ECALL需要重置TCS。
/*
* Set the TCS's tls state variable to "SGX_PTHREAD_EXIT":
* 1. Pthread() create thread exits normally.
* 2. ECALL() is exited by calling pthread_exit().
*
* In future, the TCS will always be initialized no matter it's used by a new normal root ECALL() or it's used by a new pthread() create thread.
*
* As example: (In bind mode)
* 1. This TCS is used by pthread created thread. So the TCS's state will be set as "SGX_PTHREAD_EXIT" after the thread exits.
* 2. Then the same TCS is used by a normal root ECALL, the TCS will still be initialized because it's state was set as "SGX_PTHREAD_EXIT".
*
*/
_pthread_tls_store_state(SGX_PTHREAD_EXIT);
然后销毁TLS资源,调用【_pthread_tls_destructors】。
唤醒处于【pthread_join】的那个线程,调用【_pthread_wakeup_join】。
查ECALL表,找到ECALL索引值对应的虚拟地址,然后执行之【trts_ecall】
static sgx_status_t trts_ecall(uint32_t ordinal, void *ms)
如果是这个Enclave全局的第一次ECALL。先确保这个ECALL必须是Root ECALL。将所有静态TCS,链接到【g_tcs_node】链表(静态TCS地址用【g_tcs_cookie】异或加密)。然后在tRTS里面将【PT_LOAD】、【PT_GNU_RELRO】段和【ReservedMemMinSize】的虚拟地址访问控制权限用【trts_mprotect】设置好。全局对象的初始化,从动态段找到初始化函数数组,并逐一进行初始化操作。这些动作只需要执行一次,只在第一次ECALL才执行。
然后调用【get_func_addr】。对于常规ECALL,检查ECALL是否被允许,然后查ECALL表【g_ecall_table】,找到ECALL索引值对应的虚拟地址。对于tRTS pthread,返回【_pthread_thread_run】地址,这个函数最终会执行tRTS中【pthread_create】指定的入口函数。
用一下【sgx_lfence】,然后执行得到的虚拟地址,也就是现在才开始执行ECALL真正的内容。