I'm working on a simple kernel that follows the multiboot specification. This is for a class project, so I can't post my code directly, but for my question, it's sufficient to say that we're using a modified version of the multiboot sample code.
I'm trying to set the global descriptor table register (GDTR) to point to the appropriate address. In order to do so, I've been following the GDT Tutorial from the OSDev wiki. In the tutorial, their sample code for flat protected mode simply loads two values off the stack and puts those into the GDTR. This confuses me because I thought the GDTR should be set before the stack is initialized. I don't know where the ESP would be pointing if the kernel hasn't initialized it yet. I suppose it's possible that GRUB sets it to something before jumping to any of the code in boot.S, but I haven't been able to find any documentation to suggest that.
tl;dr - Why does the OSDev GDT Tutorial retrieve data from an address relative to ESP when loading the address and size of the global descriptor table?
You can't do much in protected mode without a properly set up GDT anyway and GRUB obviously must do it not only for you but also for itself. The words
‘CS’ Must be a 32-bit read/execute code segment with an offset of ‘0’ and a limit of ‘0xFFFFFFFF’.
imply a GDT with a properly set up code segment descriptor and the CS register loaded with the selector selecting this descriptor.
The setGdt subroutine accepts its arguments on the stack. This makes it handy for calling from C code (32-bit x86 C/C++ compilers such as gcc and Microsoft Visual C++ and several others support this calling convention; see cdecl).
However, before you call setGdt, even before you push arguments onto the stack, you need to set up the stack because of this language:
‘ESP’ The OS image must create its own stack as soon as it needs one.
Note that the sample code from that page's boot.S file does it:
/* The size of our stack (16KB). */
#define STACK_SIZE 0x4000
...
multiboot_entry:
/* Initialize the stack pointer. */
movl $(stack + STACK_SIZE), %esp
...
/* Our stack area. */
.comm stack, STACK_SIZE
Should be pretty self-explanatory.
Now, the segment base address and the segment limit (along with segment access rights) get cached in the CPU when you load a selector into a segment register. And so, changing GDT or GDTR underneath running code will have no effect until the next load into a segment register.