Description
I recently am re-writing part of my kernel to work with Limine and multiple CPUs. I set up the GDT and IDT and then did a int3
to test out the handler. It caused a triple fault. The cause of the problem turned out to be that Limine uses a different GDT layout than the GDT that my kernel loads, and a different CS
register value. Since I was adding entries to a InterruptDescriptorTable
before loading my kernel's GDT, the entries were using Limine's value for CS
. So I saw
pub unsafe fn set_handler_addr(&mut self, addr: VirtAddr) -> &mut EntryOptions {
use crate::instructions::segmentation::{Segment, CS};
let addr = addr.as_u64();
self.pointer_low = addr as u16;
self.pointer_middle = (addr >> 16) as u16;
self.pointer_high = (addr >> 32) as u32;
self.options = EntryOptions::minimal();
// SAFETY: The current CS is a valid, long-mode code segment.
unsafe { self.options.set_code_selector(CS::get_reg()) };
self.options.set_present(true);
&mut self.options
}
and it is loading the current CS value which I think can cause many unexpected bugs, especially since the function doesn't even document that it does this. Imo the IDT Rust wrapper should have consistent behavior and its value should not change depending on what GDT is loaded before it. My suggestion would be to change it to something like this:
pub unsafe fn set_handler_addr(&mut self, addr: VirtAddr, cs: SegmentSelector) -> &mut EntryOptions
that way when setting up a GDT and IDT, the following things can be done:
- The GDT is created
- The IDT is created with entries referencing the
CS
according to the created GDT - The GDT is loaded
- The IDT is loaded
And even though the change I'm requesting is a breaking change I think it's worth it. If you don't want a breaking change then I would request adding functions like set_handler_addr_with_cs
and set_handler_fn_with_cs
in addition to the current functions.
I kind of improved this in ChocolateLoverRaj@2c79ab5 but it is a messy commit