I am working on enabling Intel SGX on a unikernel that does not have a native ring 3 support. Hence in order to invoke the user-mode SGX instruction I need to implement a ring switch routine. I followed the JamesM's tutorial( 10.-User Mode (jamesmolloy.co.uk) , which is a 32-bit solution) to drafted a long-mode version:
void switch_to_ring3()
{
asm volatile(" \
mov $0x23, %rax; \
mov %rax, %ds; \
mov %rax, %es; \
mov %rsp, %rax; \
push $0x23; \
push %rax; \
pushf; \
push $0x1B; \
push $1f; \
iretq; \
1: \
");
return;
}
I am sure that I have set up GDT entries properly and 0x23/0x1B is exactly the indexes of user-mode code/data descriptors, in which the code descriptor value is 0xaffb000000ffff
and the data descriptor value is 0xaff3000000ffff
.
What's strange is that the iretq
can be executed successfully, and the rip
register could go to the next instruction of the iretq
, which is a nop
if I disabled the optimization and a ret
if I enabled the optimization. However, when executing the next instruction, it will die without any output (my unikernel has an exception handler, even if for unhandled exceptions, it will output something). I try to use GDB to debug and GDB said that the program received SIGQUIT.
I checked the registers but find nothing wrong, cs is 0x1b, ss, ds and es are 0x23, and rip
points correctly to the next instruction of iretq
.
I am really confused about why it receives SIGQUIT. If some exception happened, it should output the dump message, or at least qemu log will track some 'check_exception' message, but the log is empty. Everything seems okay, correct segment registers, correct rsp/rbp/rip
, the kernel code segment is user-accessible by setting the conformed bit of its descriptor, and the high/low base address in all descriptors are pointed to 0x0.
Being trapped in this problem for a whole day but cannot find any solution. I hope someone here could save my life T_T
I finally fixed it by setting U/S bit for all kernel code/data pages. Thanks for all of your comments @prl @PeterCordes !