返回值汇总表位于《Intel® 64 and IA-32 Architectures Software Developer's Manual Combined Volumes 2A Vol》的Table 3-8. Information Returned by CPUID Instruction。
这个表占了足足19页,吓人。不过很丰富,很有用。
官网下载地址如下,注意是在2A卷中。
下完以后搜索Table 3-8. Information Returned by CPUID Instruction就能快速找到。
正常来说,Enclave内部是不可以使用CPUID指令的(SGX手册上有说)。但实际中Enclave内部却有cpuid硬件的直接使用,挺奇怪?难道是SGX更新了?
至于为什么Enclave内部为什么不能用CPUID硬件指令,相关资料挺少,我的猜测是CPUID硬件指令需要系统权限(但似乎并不是)或者存在某些安全影响(什么安全影响尚不清楚)。
总之,Enclave内部不能使用cpuid指令,不可信世界是可以任意使用cpuid指令。
如下几个是壳函数,最终会调用实际执行CPUID的函数
//位于/psw/urts/cpu_features.h
inline void sgx_cpuid(unsigned int in_eax, unsigned int *eax, unsigned int *ebx, unsigned int *ecx, unsigned int *edx)
{
int cpu_info[4] = {0};
__cpuid(cpu_info, in_eax);
*eax = cpu_info[0];
*ebx = cpu_info[1];
*ecx = cpu_info[2];
*edx = cpu_info[3];
}
//位于/psw/urts/cpu_features.h
inline void sgx_cpuidex(unsigned int in_eax, unsigned int leaf, unsigned int *eax, unsigned int *ebx, unsigned int *ecx, unsigned int *edx)
{
int cpu_info[4] = {0};
__cpuidex(cpu_info, in_eax, leaf);
*eax = cpu_info[0];
*ebx = cpu_info[1];
*ecx = cpu_info[2];
*edx = cpu_info[3];
}
下面这个函数是一个OCALL函数,专门承载Enclave内部对CPUID指令调用的需求,因为Enclave内部是不能使用CPUID硬件指令。这个OCALL已经被写到了psw/urts/linux/urts{_internal}.lds符号文件、common/inc/sgx_tstdc.edl这个Enclave接口描述文件中,方便Enclave内部调用。
//psw/urts/se_ocalls.cpp
extern "C" void sgx_oc_cpuidex(int cpuinfo[4], int leaf, int subleaf)
{
__cpuidex(cpuinfo, leaf, subleaf);
}
上述几个壳函数都是最终调用__cpuid{ex}这个壳函数
//位于common/inc/internal/linux/cpuid_gnu.h
static inline void __cpuidex(int a[4], int b, int c)
{
a[0] = b;
a[2] = c;
cpuid(&a[0], &a[1], &a[2], &a[3]);
}
//位于common/inc/internal/linux/cpuid_gnu.h
static inline void __cpuid(int a[4], int b)
{
a[0] = b;
a[2] = 0;
cpuid(&a[0], &a[1], &a[2], &a[3]);
}
__cpuid{ex}壳函数最终会调用下面这个cpuid函数,并执行CPUID硬件指令。这些函数定义下common文件夹下面,这个common指代的是什么意思。
//位于common/inc/internal/linux/cpuid_gnu.h
/* This is a PIC-compliant version of CPUID */
static inline void cpuid(int *eax, int *ebx, int *ecx, int *edx)
{
#if defined(__x86_64__)
asm("cpuid"
: "=a" (*eax),
"=b" (*ebx),
"=c" (*ecx),
"=d" (*edx)
: "0" (*eax), "2" (*ecx));
#else
/*on 32bit, ebx can NOT be used as PIC code*/
asm volatile ("xchgl %%ebx, %1; cpuid; xchgl %%ebx, %1"
: "=a" (*eax), "=r" (*ebx), "=c" (*ecx), "=d" (*edx)
: "0" (*eax), "2" (*ecx));
#endif
}
//位于sdk/tlibc/gen/se_cpuid.c
sgx_status_t sgx_cpuid(int cpuinfo[4], int leaf)
{
return sgx_cpuidex(cpuinfo, leaf, 0);
}
//位于sdk/tlibc/gen/se_cpuid.c
sgx_status_t sgx_cpuidex(int cpuinfo[4], int leaf, int subleaf)
{
if (cpuinfo == NULL)
return SGX_ERROR_INVALID_PARAMETER;
return sgx_oc_cpuidex(cpuinfo, leaf, subleaf);
}
Enclave内部的壳函数会通过sgx_oc_cpuidex这个OCALL函数切换到不可新世界调用CPUID硬件指令
下面这两个函数位于sdk内(sdk是Enclave内部程序所依赖的静态库),这就很奇怪了,为什么Enclave内部会需要定义这两个函数,难道能直接用?
//位于sdk/gperftools/gperftools-2.7/src/base/atomicops-internals-x86.cc
// Inline cpuid instruction. In PIC compilations, %ebx contains the address
// of the global offset table. To avoid breaking such executables, this code
// must preserve that register's value across cpuid instructions.
#if defined(__i386__)
#define cpuid(a, b, c, d, inp) \
asm ("mov %%ebx, %%edi\n" \
"cpuid\n" \
"xchg %%edi, %%ebx\n" \
: "=a" (a), "=D" (b), "=c" (c), "=d" (d) : "a" (inp))
#elif defined (__x86_64__)
#define cpuid(a, b, c, d, inp) \
asm ("mov %%rbx, %%rdi\n" \
"cpuid\n" \
"xchg %%rdi, %%rbx\n" \
: "=a" (a), "=D" (b), "=c" (c), "=d" (d) : "a" (inp))
#endif
//位于sdk/libcapable/linux/sgx_capable.cpp
/* __cpuid(unsinged int info[4], unsigned int leaf, unsigned int subleaf); */
/* Because gcc's __get_cpuid() intrinsic is difficult to work with */
#define __cpuid(x,y,z) asm volatile("cpuid":"=a"(x[0]),"=b"(x[1]),"=c"(x[2]),"=d"(x[3]):"a"(y),"c"(z))