Clearing interrupts causes a crash
Posted: Fri Apr 09, 2021 12:26 pm
I'm following the GDT Tutorial page from the wiki and appear to have gotten my descriptor table up and working, but the one oddity I'm running into is that clearing interrupts with cli causes an instant crash.
My kernel is basically the same as what's in the bare bones page. I'm running it under Hyper-V. It starts from GRUB. It sets up a stack in the .bss section, then transfers control to C code. The C code initialises VGA and prints some debug output so I can see the state of the registers and segment addresses. I then build 6 GDT entries: a null entry, code and data segments for DLP0 and DLP3, and a TSS entry (which I'm not using for anything just yet).
I wanted to specify separate code and data segments for ring 0, so I could get separation of executable pages and read/write pages, but I haven't quite figured out the linker scripts to ensure that everything goes into the right places for that. So for now my DLP0 code and data entries point at 0x00000000, with the code entry being 0x0FFFFFFF long and the data entry being 0x1FFFFFFF long. The .data and .bss sections get loaded at 0x10000000, so they're outside of the executable segment.
I'm applying the GDT as follows:
This is called as follows:
(side note: in the wiki, the example says that the code assumes it's being called with sizeof(GDT), but as I understand it the size should be one less than that due to the way that the size field works)
This appears to work fine. Dumping the segment registers shows that they've changed over. Code continues to execute just fine. I suspect that it's not good practice to return back to C between applying the GDT and reloading the segment registers, but I don't think it causes any issues for now based on the disassembly I've looked at.
However, the wiki page also says that interrupts should be cleared before applying the GDT. I can see why this would be important - you don't want interrupts landing mid-way through your segment reload and messing everything up. So I used the cli/sti mnemonics to clear interrupts during this process, and it crashes immediately - I presume with a triple fault.
To rule things out, I added a cli followed by sti in the _start assembly, way before I even get close to setting up the GDT. This also causes an immediate crash.
I'm a bit confused as to why this occurs. Reading the instruction reference for cli, specifically the cases in which a GPF can be raised, I suspect I might be missing a key detail about protected-mode virtual interrupts, which I'm not particularly familiar with. What's going on here?
My kernel is basically the same as what's in the bare bones page. I'm running it under Hyper-V. It starts from GRUB. It sets up a stack in the .bss section, then transfers control to C code. The C code initialises VGA and prints some debug output so I can see the state of the registers and segment addresses. I then build 6 GDT entries: a null entry, code and data segments for DLP0 and DLP3, and a TSS entry (which I'm not using for anything just yet).
I wanted to specify separate code and data segments for ring 0, so I could get separation of executable pages and read/write pages, but I haven't quite figured out the linker scripts to ensure that everything goes into the right places for that. So for now my DLP0 code and data entries point at 0x00000000, with the code entry being 0x0FFFFFFF long and the data entry being 0x1FFFFFFF long. The .data and .bss sections get loaded at 0x10000000, so they're outside of the executable segment.
I'm applying the GDT as follows:
Code: Select all
apply_gdt:
mov eax, dword ptr [esp + 4]
mov dword ptr [gdtr + 2], eax
mov ax, word ptr [esp + 8]
mov word ptr [gdtr], ax
lgdt [gdtr]
ret
reload_gdt:
jmp 0x08:reload_CS
reload_CS:
mov ax, 0x10
mov ds, ax
mov es, ax
mov fs, ax
mov gs, ax
mov ss, ax
ret
Code: Select all
void gdt_apply(void)
{
apply_gdt(KernelGDT, sizeof(KernelGDT) - 1);
reload_gdt();
}
This appears to work fine. Dumping the segment registers shows that they've changed over. Code continues to execute just fine. I suspect that it's not good practice to return back to C between applying the GDT and reloading the segment registers, but I don't think it causes any issues for now based on the disassembly I've looked at.
However, the wiki page also says that interrupts should be cleared before applying the GDT. I can see why this would be important - you don't want interrupts landing mid-way through your segment reload and messing everything up. So I used the cli/sti mnemonics to clear interrupts during this process, and it crashes immediately - I presume with a triple fault.
To rule things out, I added a cli followed by sti in the _start assembly, way before I even get close to setting up the GDT. This also causes an immediate crash.
I'm a bit confused as to why this occurs. Reading the instruction reference for cli, specifically the cases in which a GPF can be raised, I suspect I might be missing a key detail about protected-mode virtual interrupts, which I'm not particularly familiar with. What's going on here?