x86_64 icon indicating copy to clipboard operation
x86_64 copied to clipboard

Loading GDT crashes QEMU

Open lretq opened this issue 3 years ago • 4 comments

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);
    }
}

lretq avatar Aug 14 '22 16:08 lretq

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).

josephlr avatar Aug 17 '22 20:08 josephlr

With this GDT setup, I get a GPF with error code 24, do you know what this error code means?

lretq avatar Aug 19 '22 17:08 lretq

What do you mean by "this GDT setup"? Is it the one you described above, or the one I suggested

josephlr avatar Aug 19 '22 21:08 josephlr

The one you suggested.

lretq avatar Aug 21 '22 13:08 lretq

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?

TornaxO7 avatar Nov 08 '22 10:11 TornaxO7

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

Freax13 avatar Nov 08 '22 10:11 Freax13

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.

TornaxO7 avatar Nov 08 '22 11:11 TornaxO7

No, the GDT is wrong, it should two 16-bit, 32-bit and 64-bit descriptors each, instead of 6 64-bit descriptors.

Freax13 avatar Nov 08 '22 11:11 Freax13

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.

TornaxO7 avatar Nov 08 '22 11:11 TornaxO7

You want the following descriptors:

  1. 16-bit code: USER_SEGMENT, PRESENT, LIMIT_0_15, ACCESSED and EXECUTABLE
  2. 16-bit data: USER_SEGMENT, PRESENT, LIMIT_0_15, ACCESSED and WRITABLE
  3. 32-bit code: KERNEL_CODE32
  4. 32-bit data: KERNEL_DATA
  5. 64-bit code: KERNEL_CODE64
  6. 64-bit data: KERNEL_DATA

Freax13 avatar Nov 08 '22 15:11 Freax13

Thank you! Now it's working again :)

TornaxO7 avatar Nov 08 '22 20:11 TornaxO7

This would probably do, yes!

lretq avatar Nov 15 '22 20:11 lretq