目录
继《再回顾SGX初始化(三)——uRTS维护Enclave、tRTS完成Enclave构建收尾确认工作》之后,我们又回到了uRTS的__create_enclave函数。此时uRTS、tRTS两边都已经完成了Enclave初始化工作,也就是说Enclave内外两边都已经搞定了Enclave初始化所需要的工作。
那么就继续__create_enclave函数的漫漫长征路。
裁剪页放到Trimmed列表(需要支持EDMM)
if (SGX_SUCCESS != (ret = loader.post_init_action_commit(layout_start, layout_end, 0)))
由于前面uRTS已经发出裁剪请求,并且Enclave已经EACCEPT了。
那么就发SGX_IOC_ENCLAVE_NOTIFY_ACCEPT信号给SGX驱动,让SGX驱动将裁剪页放到Trimmed列表(意味着裁剪页现在开始可以被自由移除),并且这些裁剪页也会从用于管理已分配Enclave虚拟页的基数树上删除掉。
/**
* sgx_ioc_page_notify_accept() - Pages defined in range will be moved to
* the trimmed list, i.e. they can be freely removed from now. These pages
* should have PT_TRIM page type and should have been eaccepted priorly
* @arg range address of pages
*/
long sgx_ioc_page_notify_accept(struct file *filep, unsigned int cmd, unsigned long arg)
设置内存访问控制属性
if(SGX_SUCCESS != (ret = loader.set_memory_protection()))
使用ELF解析器(使用mprotect)设置ELRANGE中各个Section的访问属性以及各个Segment对应的访问属性【ElfParser::set_memory_protection,先对每个Section按照最开始记录的Section对象中声明的虚拟地址空间访问属性进行mprotect设置,再对每个Segment按照ElfW(Phdr).p_flags记录的那样进行虚拟地址空间访问属性设置】。
如果元数据版本较新,并且当前环境支持EDMM,那么将PT_GNU_RELRO、PT_LOAD(不可写的并且可能会有重定位过程的)段交由SGX驱动(SGX_IOC_ENCLAVE_EMODPR,SGX会调用EMODPR)将Enclave Page设置为读执行(RX)权限,相比于mprotect是对页表机制里面的访问属性进行设置,Ring0权限的EMODPR硬件指令会对管理EPC属性的EPCM进行设置。rsrv内存用EMODPR改为RW。不支持EDMM等情况,就不进行改动。
对上下文相关的内存,用mprotect将内存访问控制属性设置为RW。其中rsrv内存在不支持EDMM情况下,不要用mprotect改变它的属性。
针对EREMOVE掉的页,需要用mprotect设置为PROT_NONE,不然这个页一旦被访问,会发生总线异常(sigbus exception),因为这个EPC页已经被EREMOVE硬件指令给从当前Enclave中去除,EPCM也没有再维护EREMOVE掉的页的信息。
上面所涉及到的ELRANGE的页本身在ECREATE、EADD、EREMOVE时就设置或更改了基本的EPCM项,这里主要是进行调整,以及在进程虚拟地址空间用mprotect进行访问控制设置。
if(!get_enclave_creator()->is_EDMM_supported(get_enclave_id()) && (layout->entry.id == LAYOUT_ID_RSRV_MIN ||layout->entry.id == LAYOUT_ID_RSRV_INIT))
//Don't change the rsrv memory's attributes if platform isn't support EDMM
continue;
//Here: URTS will change rsrv memory's attributes to RW forcely although it's signed by sign_tool as RWX (<ReservedMemExecutable>1</ReservedMemExecutable>).
填充TCS最小池
ret = enclave->fill_tcs_mini_pool_fn();
如果支持EDMM,就填充一下TCS最小池(就是说我们最小要填充指定数量【CTrustThreadPool.m_tcs_min_pool,源自config.xml文件里的说明】的TCS池项,这些数量是必须的,而额外的动态TCS这里不做填充)。这个函数的做法挺有趣,在第一次调用该函数的时候,起一个pthread线程,完成TCS最小池的填充,之后这个线程会休眠,当下次调用该函数时,唤醒这个线程去填充TCS最小池,这里是一个循环,每唤醒一次就循环一轮。这种做法的其中一个原因也是因为后续有时也会进行填充TCS最小池。
最终填充TCS最小池的操作是由如下函数完成
sgx_status_t CTrustThreadPool::fill_tcs_mini_pool()
如果有动态当前未分配具体空间的TCS,并且已有的空闲TCS,还不足最小数量的要求,那么就需要构建动态TCS【之前是个未分配空间的TCS】填充到TCS最小池。《构建动态TCS页》讲述了动态TCS构建过程。
然后唤醒一下其他线程,其他线程(比如主线程)可能一直等待新的空闲的TCS。并且有时空闲TCS数量不足最小池的大小,使得我们需要再填充TCS最小池。不过我们这里是第一次填充TCS最小池,为的就是让TCS最小池至少先足数。
Switchless初始化
到此,SGX初始化就全部结束了
最后将【CLoader::m_enclave_id】,赋值给创建SGX的API中的参数【global_eid】,让编程人员后续可以使用这个【global_eid】调用ECALL函数,主要原因也是因为一个进程可以创建多个Enclave,这个ID是为了区分一个进程内的多个Enclave。
ret = sgx_create_enclave_ex(ENCLAVE_FILENAME, SGX_DEBUG_FLAG, NULL, NULL, &global_eid, NULL, SGX_CREATE_ENCLAVE_EX_SWITCHLESS, enclave_ex_p);
有空我调整一下章节的编排