First devlog where the screen looks exactly the same before and after.
Turns out that’s the interesting part: the GDT is the table the CPU reads on every single memory access, and doing it right means nothing visible happens.
What I built
-
A real VGA text driver (
vga.c):putchar,write, line wrap,\n, and scrolling. The surprise: there is no hardware scroll in VGA text mode, scrolling means copying all 2000 cells up one row yourself and blanking the last one. I’d always assumed the terminal did that for you, it’s aforloop. -
The GDT (
gdt.c,gdt.h): three entries: a null one (mandatory, so an uninitialized segment register faults immediately instead of doing something random), one for code, one for data. Base 0 and limit 4GB on both, the flat model. You can’t turn segmentation off on x86, so you make it irrelevant instead. -
Replacing GRUB’s table.: GRUB leaves you a working GDT, otherwise the kernel wouldn’t run at all, but it lives in GRUB’s memory and GRUB is gone. My stack grows,
.bssexpands, an allocator shows up eventually, and one day something writes over it. From that moment the CPU reads garbage on every memory access. -
Shift and mask code for a 40 year old compatibility hack.: A descriptor is 8 bytes with the base address split into three non-contiguous chunks and the limit split in two. The 286 had 24 bit addresses in 1982, the 386 needed 8 more bits of base and 4 more of limit and couldn’t move any existing field without breaking every OS already written, so the new bits got wedged into the spare bytes at the end.
__attribute__((packed))isn’t optional here, one byte of padding and the CPU misreads everything. -
The assembly loader (
gdt_flush.s):lgdtplus reloading all five data segment registers, because each one caches the descriptor internally and changing the table doesn’t touch registers already loaded. -
A jump that goes nowhere.:
mov cs, axdoes not exist:cssays which segment your code is in andeipsays where in it, two halves of one address, so changing onlycslands the next instruction fetch at the old offset inside the new segment. They have to change together, and the only instruction that does that is a far jump. The idiom is jumping to the very next line. I disassembled it to convince myself: the jump sits at0x15, is 7 bytes long, ends at0x1c, and its destination is0x1c. The jump is the vehicle, loadingcsis the cargo. -
Proof that something invisible happened. Nothing changed on screen so I asked the CPU through the QEMU monitor:
GDT= 001042f4 00000017, andnmsays my table is at001042f4withsizeof(gdt) - 1= 23 =0x17.CSreads[-R-], readable not writable,DSreads[-WA], writable not executable. I set the data access byte to0x92and the CPU reports93: that’s the accessed bit, hardware turns it on by itself the first time the segment is used.
Look forward for the next steps
Comments 0
No comments yet. Be the first!
Sign in to join the conversation.