[Solved] Returning to ring 0 with an interrupt triple faults
Posted: Sun Jun 21, 2020 10:43 pm
EDIT: See my post below with the register dumps from QEMU.
I'm sure I'm doing something stupid, but I've been trying to fix this for the better part of a day and I cannot figure it out for the life of me.
In my x86 OS, I've successfully set up paging (with a higher-half kernel) and software preemptive multitasking, although at the moment, only ring 0 processes work properly. When the PIT generates an interrupt for my kernel to preempt, it pushes all of the current task's registers onto the stack, loads the page table for the next process, sets the correct stack pointer for the next process, pops the next process's registers off the stack, and then IRETs. This has been working fine, until I tried to begin switching to ring 3.
I'm able to get into ring 3 just fine by pushing the correct segment registers to the stack along with a the stack pointer and ss. Stepping through it with GDB and qemu, I can tell that execution within ring 3 is working fine right up until I call an interrupt. As soon as an interrupt is called, execution immediately jumps to 0xe05b (which is weird, because absolutely nothing is mapped there). Calling different interrupts always jumps to 0xe05b.
Could there be something wrong with my TSS, or am I switching to usermode wrong? Could it be because I'm switching to usermode with an IRET from my PIT interrupt that I use to preempt? Scanning the intel manual hasn't gotten me anywhere, although I am a bit tired
Here's my code for setting up my TSS (It's a bit messy, because I forwent using my convenience methods to set up GDT entries for the TSS so that I could be sure everything was right, and I used https://wiki.osdev.org/Getting_to_Ring_3 for it):
(After that code, my GDT is flushed and ltr is used)
And here's the handler for the PIT interrupt that preempts:
And finally, here's preempt():
(I realize most of the inline assembly in preempt() could be moved into my standalone assembly, but it was working before so I'm too scared to touch it
I'm sure I'm doing something stupid, but I've been trying to fix this for the better part of a day and I cannot figure it out for the life of me.
In my x86 OS, I've successfully set up paging (with a higher-half kernel) and software preemptive multitasking, although at the moment, only ring 0 processes work properly. When the PIT generates an interrupt for my kernel to preempt, it pushes all of the current task's registers onto the stack, loads the page table for the next process, sets the correct stack pointer for the next process, pops the next process's registers off the stack, and then IRETs. This has been working fine, until I tried to begin switching to ring 3.
I'm able to get into ring 3 just fine by pushing the correct segment registers to the stack along with a the stack pointer and ss. Stepping through it with GDB and qemu, I can tell that execution within ring 3 is working fine right up until I call an interrupt. As soon as an interrupt is called, execution immediately jumps to 0xe05b (which is weird, because absolutely nothing is mapped there). Calling different interrupts always jumps to 0xe05b.
Could there be something wrong with my TSS, or am I switching to usermode wrong? Could it be because I'm switching to usermode with an IRET from my PIT interrupt that I use to preempt? Scanning the intel manual hasn't gotten me anywhere, although I am a bit tired
Here's my code for setting up my TSS (It's a bit messy, because I forwent using my convenience methods to set up GDT entries for the TSS so that I could be sure everything was right, and I used https://wiki.osdev.org/Getting_to_Ring_3 for it):
Code: Select all
uint32_t base = (size_t) (&TaskManager::tss) - HIGHER_HALF; //HIGHER_HALF is 0xC0000000, AKA where the kernel starts in vmem
uint32_t limit = base + sizeof(TSS);
// Now, add our TSS descriptor's address to the GDT.
gdt[5].limit_low = limit & 0xFFFFu;
gdt[5].base_low = base & 0xFFFFFFu;
gdt[5].access.bits.accessed = true;
gdt[5].access.bits.read_write = false;
gdt[5].access.bits.direction = false;
gdt[5].access.bits.executable = true;
gdt[5].access.bits.type = false;
gdt[5].access.bits.ring = 3;
gdt[5].access.bits.present = true;
gdt[5].flags_and_limit.bits.limit_high = (limit & 0xF0000u) >> 16u;
gdt[5].flags_and_limit.bits.zero = 0;
gdt[5].flags_and_limit.bits.size = false;
gdt[5].flags_and_limit.bits.granularity = false;
gdt[5].base_high = ( base & 0xFF000000u) >> 24u;
memset(&TaskManager::tss, 0, sizeof(TSS));
TaskManager::tss.ss0 = 0x10; //Kernel data
TaskManager::tss.esp0 = (size_t) stack; //Kernel stack
And here's the handler for the PIT interrupt that preempts:
Code: Select all
push eax
mov eax, 0x20
out 0x20, al ;EOI
pop eax
cmp byte [tasking_enabled], 0 ;tasking_enabled is a variable in my C++ code
jne preempt_do
iret
preempt_do:
call preempt
iret
Code: Select all
//push current_proc process' Registers on to its stack
asm volatile("push %eax");
asm volatile("push %ebx");
asm volatile("push %ecx");
asm volatile("push %edx");
asm volatile("push %esi");
asm volatile("push %edi");
asm volatile("push %ebp");
asm volatile("push %ds");
asm volatile("push %es");
asm volatile("push %fs");
asm volatile("push %gs");
asm volatile("mov %%esp, %%eax":"=a"(current_proc->registers.esp));
//pop all of next process' Registers off of its stack
current_proc = current_proc->next;
asm volatile("movl %0, %%cr3": : "r"(current_proc->page_directory_loc)); //Load page directory for process
asm volatile("mov %0, %%esp" :: "r"(current_proc->registers.esp));
asm volatile("pop %gs");
asm volatile("pop %fs");
asm volatile("pop %es");
asm volatile("pop %ds");
asm volatile("pop %ebp");
asm volatile("pop %edi");
asm volatile("pop %esi");
asm volatile("pop %edx");
asm volatile("pop %ecx");
asm volatile("pop %ebx");
asm volatile("pop %eax");