x86_64
                                
                                 x86_64 copied to clipboard
                                
                                    x86_64 copied to clipboard
                            
                            
                            
                        Loading GDT crashes QEMU
Not a bug in the crate, I just can't figure out why this code caueses an exception/how to do it, a little hint or something would be very helpful.
I'm using the limine bootloader and its limine protocol, which requires some entries in its GDT table, see here (https://github.com/limine-bootloader/limine/blob/trunk/PROTOCOL.md#x86_64).
This is the code for creating the GDT table:
static ref GDT: (GlobalDescriptorTable<16>, Selectors) = {
        let mut gdt = GlobalDescriptorTable::<16>::empty();
// IDK If kernel code / data is valid
        let kernel_code_selector = gdt.append(Descriptor::UserSegment(
            DescriptorFlags::LIMIT_0_15.bits()
                | DescriptorFlags::PRESENT.bits()
                | DescriptorFlags::WRITABLE.bits()
                | DescriptorFlags::ACCESSED.bits()
                | DescriptorFlags::EXECUTABLE.bits()
                | DescriptorFlags::GRANULARITY.bits(),
        ));
        let kernel_data_selector = gdt.append(Descriptor::UserSegment(
            DescriptorFlags::LIMIT_0_15.bits()
                | DescriptorFlags::PRESENT.bits()
                | DescriptorFlags::WRITABLE.bits()
                | DescriptorFlags::ACCESSED.bits()
                | DescriptorFlags::GRANULARITY.bits(),
        ));
        let _user32_code_selector =
            gdt.append(Descriptor::UserSegment(DescriptorFlags::USER_CODE32.bits()));
        let _user32_data_selector = gdt.append(Descriptor::user_data_segment());
        let _user64_code_selector = gdt.append(Descriptor::user_code_segment());
        let _user64_data_selector = gdt.append(Descriptor::user_data_segment());
        let tss_selector = gdt.append(Descriptor::tss_segment(&TSS));
        (
            gdt,
            Selectors {
                kernel_code_selector,
                kernel_data_selector,
                tss_selector,
            },
        )
    };
// This is called at kernel entrypoint
pub fn init_gdt() {
    GDT.0.load();
// qemu exception here ( I think )
    unsafe {
        CS::set_reg(GDT.1.kernel_code_selector);
        SS::set_reg(GDT.1.kernel_data_selector);
        load_tss(GDT.1.tss_selector);
    }
}
My guess here is that your Kernel Code Segment is incorrect.
CS::set_reg must be used with an executable, LONG_MODE code segment. I would recommend doing something like:
let kernel_code_selector = gdt.append(Descriptor::kernel_code_segment());
let kernel_data_selector = gdt.append(Descriptor::kernel_data_segment());
These helper methods are used to avoid issues like you've encountered. For example, your kernel code segment is missing the required USER_SEGMENT and LONG_MODE flags (it also should probably set the LIMIT_16_19 flag).
With this GDT setup, I get a GPF with error code 24, do you know what this error code means?
What do you mean by "this GDT setup"? Is it the one you described above, or the one I suggested
The one you suggested.
I'm also trying to "fix" GDT, because after executing GDT.0.load(); I'm not able to use limine's terminal anymore. To be more specifique, this line crashes for me, if I call a print[ln]! after GDT.0.load():
pub fn init() {
    print!("GDT ... ");
    x86_64::instructions::interrupts::disable();
    GDT.0.load();
    unsafe {
        CS::set_reg(GDT.1.kcode_seg);
        DS::set_reg(GDT.1.kdata_seg);
        ES::set_reg(GDT.1.kdata_seg);
        FS::set_reg(GDT.1.kdata_seg);
        GS::set_reg(GDT.1.kdata_seg);
        SS::set_reg(GDT.1.kdata_seg);
    }
    // this line creates a `General Protection Faul` (three times) and then my OS triple-faults.
    println!("OK");
}
This is my current code:
lazy_static! {
    static ref GDT: (GlobalDescriptorTable, Selectors) = {
        let mut gdt = GlobalDescriptorTable::new();
        let kcode_seg = gdt.add_entry(Descriptor::kernel_code_segment());
        let kdata_seg = gdt.add_entry(Descriptor::kernel_data_segment());
        gdt.add_entry(Descriptor::kernel_code_segment());
        gdt.add_entry(Descriptor::kernel_data_segment());
        gdt.add_entry(Descriptor::kernel_code_segment());
        gdt.add_entry(Descriptor::kernel_data_segment());
        (gdt, Selectors {kcode_seg, kdata_seg})
    };
}
struct Selectors {
    kcode_seg: SegmentSelector,
    kdata_seg: SegmentSelector,
}
pub fn init() {
    print!("GDT ... ");
    x86_64::instructions::interrupts::disable();
    GDT.0.load();
    unsafe {
        CS::set_reg(GDT.1.kcode_seg);
        DS::set_reg(GDT.1.kdata_seg);
        ES::set_reg(GDT.1.kdata_seg);
        FS::set_reg(GDT.1.kdata_seg);
        GS::set_reg(GDT.1.kdata_seg);
        SS::set_reg(GDT.1.kdata_seg);
    }
    println!("OK");
}
any ideas?
Limine has some requirements for the GDT whenever writing to a terminal: https://github.com/limine-bootloader/limine/blob/trunk/PROTOCOL.md#x86_64-1
Hm... so it looks like that something else is wrong in my code. I don't know if this question is a bit off but do you have any ideas what other reason might be that I'm getting a triple fault than the GDT? Beacuse I assume that my GDT matches the requirements, at least I can't find any issues. The code which you're seeing the basically the first function which gets called after entering the main-function of my kernel.
No, the GDT is wrong, it should two 16-bit, 32-bit and 64-bit descriptors each, instead of 6 64-bit descriptors.
No, the GDT is wrong, it should two 16-bit, 32-bit and 64-bit descriptors each, instead of 6 64-bit descriptors.
That sounds reasonable. I'm reading it correctly, the Decsriptor enum doesn't provide any other descriptors than 64-bit descriptors so I'm not able to enter 16-bit and 32-bit descriptors, right? Or is it possible to use the first 16 bits as the "first" descriptor and so on? Because if I'm seeing it right, the values of each Descriptor just gets pushed into the table.
You want the following descriptors:
- 16-bit code: USER_SEGMENT,PRESENT,LIMIT_0_15,ACCESSEDandEXECUTABLE
- 16-bit data: USER_SEGMENT,PRESENT,LIMIT_0_15,ACCESSEDandWRITABLE
- 32-bit code: KERNEL_CODE32
- 32-bit data: KERNEL_DATA
- 64-bit code: KERNEL_CODE64
- 64-bit data: KERNEL_DATA
Thank you! Now it's working again :)
This would probably do, yes!