I am developing a x86 architecture based 64 bit Operating System. I have implemented GDT(Null, Kernel-code, kernel-data, user-code, user-data, tss) . Now I want to switch into user space(Lower half memory space Ring-3) So I wrote the below function
__attribute__((naked, noreturn))
void switch_to_user_mode(uint64_t stack_addr, uint64_t code_addr)
{
asm volatile(
"cli\n" // Disable Interrupt
"mov $0x23, %%ax \n" // User-mode data segment (ring 3)
"mov %%ax, %%ds \n"
"mov %%ax, %%es \n"
"mov %%ax, %%fs \n"
"mov %%ax, %%gs \n"
"push $0x23 \n" // Push USER_SS(DATA SEGMENT) Selector
"push %[stack] \n" // Push User Stack pointer
"pushf \n" // Push RFLAGS
"pop %%rax \n" // Taking RFLAGS into RAX
"or $0x200, %%rax \n" // Set IF flag to make automatic enabling Interrupt
"push %%rax \n" // Push Updated RFLAGS into the stack
"push $0x1B \n" // Push USER_CS(CODE SEGMENT)
"push %[entry] \n" // Push entry point address
"iret \n" // Interrupt Return to User Mode
:
: [stack] "r" (stack_addr), [entry] "r" (code_addr)
: "rax"
);
}
By using above function I switch userspace by following way
uint64_t create_user_function() {
void *user_code = (void *) uheap_alloc(0x1000);
// simple infinite loop machine code
uint8_t user_program[] = {
0xeb, 0xfe // Infinite loop: jmp $
};
printf("user_code or user_programe is null\n");
memcpy(user_code, (void*)&user_program, sizeof(user_program));
return (uint64_t)user_code; // Return the user-accessible function pointer
}
void init_user_mode(){
uint64_t code_addr = (uint64_t) create_user_function();
page_t *code_page = get_page(code_addr, 0, (pml4_t *)get_cr3_addr());
code_page->rw = 0; // Making readable only
code_page->nx = 0; // Making executable
code_page->user = 1; // Making User accessible
uint64_t stack_base_addr = ((uint64_t) uheap_alloc(STACK_SIZE));
for(uint64_t addr = stack_base_addr; addr < stack_base_addr + STACK_SIZE; addr += 0x1000){
page_t *_page = get_page(addr, 0, (pml4_t *)get_cr3_addr());
_page->rw = 1; // Making it read-writable
_page->nx = 1; // Making non-executable
_page->user = 1; // Making User accessible
}
uint64_t stack_top_addr = stack_base_addr + STACK_SIZE; // Set stack top
flush_tlb_all();
printf("Starting Switching to the user mode: code addr.- %x, stack addr.- %x\n", code_addr, stack_top_addr);
switch_to_user_mode(stack_top_addr, code_addr);
}
Now I am getting following Output:
[Info] Interrupt Based System Call initialized!
GDT Entry 0x0: Base=0x0 Limit=0x0 Access=0x0 Flags=0x0
GDT Entry 0x8: Base=0x0 Limit=0xFFFF Access=0x9A Flags=0xA
GDT Entry 0x10: Base=0x0 Limit=0xFFFF Access=0x93 Flags=0xA
GDT Entry 0x1B: Base=0x0 Limit=0xFFFF Access=0xFA Flags=0xA
GDT Entry 0x23: Base=0x0 Limit=0xFFFF Access=0xF2 Flags=0xA
GDT Entry 0x28: Base=0x8059A060 Limit=0x67 Access=0x8B Flags=0x0
Current stack address: 0xFFFF80007FF41FC0
Current rip address: 0xFFFFFFFF8000EF0A
Current rflags address: 0x286
Starting Switching to the user mode: code addr.- 0x1000, stack addr.- 0x7000
General protection fault (pushes an error code)
recieved interrupt: 13
Error Code: 0x0
CS: 0x8, RIP : 0xFFFFFFFF8000EA61
Stack (rsp = 0xFFFF80007FF41F70) Contents(First 26) :
[0xFFFF80007FF41F70] = 0x1000
[0xFFFF80007FF41F78] = 0x1B
[0xFFFF80007FF41F80] = 0x292
[0xFFFF80007FF41F88] = 0x7000
[0xFFFF80007FF41F90] = 0x23
[0xFFFF80007FF41F98] = 0xFFFFFFFF8000EBCD
[0xFFFF80007FF41FA0] = 0x7FF28030
[0xFFFF80007FF41FA8] = 0x7000
[0xFFFF80007FF41FB0] = 0x3000
[0xFFFF80007FF41FB8] = 0x7FF28008
[0xFFFF80007FF41FC0] = 0x1000
[0xFFFF80007FF41FC8] = 0x7000
[0xFFFF80007FF41FD0] = 0xFFFF80007FF41FF0
[0xFFFF80007FF41FD8] = 0xFFFFFFFF800080A1
[0xFFFF80007FF41FE0] = 0x0
[0xFFFF80007FF41FE8] = 0xFEBD5000
[0xFFFF80007FF41FF0] = 0x0
[0xFFFF80007FF41FF8] = 0x0
[0xFFFF80007FF42000] = 0x800000015CD00037
[0xFFFF80007FF42008] = 0xFFFF
[0xFFFF80007FF42010] = 0x0
[0xFFFF80007FF42018] = 0x0
[0xFFFF80007FF42020] = 0x0
[0xFFFF80007FF42028] = 0x0
[0xFFFF80007FF42030] = 0x0
[0xFFFF80007FF42038] = 0x0
System Halted!
The above stack contents showing
+---------------+
| RIP | 0x1000 |
+------+--------+
| CS | 0x1B |
+------+--------+
| RFLAGS | 0x292|
+------+--------+
| RSP | 0x7000 |
+------+--------+
| SS | 0x23 |
+------+--------+
| .... | .... |
Which states that switch_to_user_mode successfully placed all values but iret never switched from kernel space to userspace as the interrupted rip and rsp are belong kernel space.
I have tried to debug by GDB but it is not understandable for me. How can I resolve this issue?
Edit: gdt tss switch_to_user.c
iretqinstead ofiretStarting Switching to the user mode: code addr.- 0x1000, stack addr.- 0x7000system stalks .Which may be feasible due to loop in user code. Interrupt is not working I mean not receiving interrupt ,although interrupt bit set. I found a bug in set tss 64 bit base address.