So, I am beginning the development of a x86_64 hobby kernel and I found this code to load the GDT (Global Descriptor Table), but I don't understand what it does.
load_gdt:
lgdt [rdi]
mov ax, 0x10
mov ss, ax
mov ds, ax
mov es, ax
mov rax, qword .trampoline
push qword 0x8
push rax
o64 retf
.trampoline:
ret
I know it loads my gdt descriptor from the rdi register (register of the first parameter of a function call in sysv abi) but I don't know why I need to set all segment registers to 0x10 and what black magic is doing the rest ?
CodePudding user response:
Once a (new) GDT exists, setting segment regs to selectors that index the GDT is what you do next; that's why you wanted a GDT in the first place. Or wanted your own GDT if you're replacing an old one, e.g. from UEFI. (Since you're already in 64-bit mode, there must already be a GDT; mov rax, .trampoline
is a strange choice vs. a RIP-relative LEA, but involves a REX prefix so would decode wrong in 32-bit mode.)
The last few are setting CS with a far jump, done by pushing a new CS:RIP and doing a far-ret to pop it into CS:RIP. It needs to be 64-bit operand-size to make sure you pop a 64-bit RIP, not a 32-bit EIP from the stack.
(You can't of course mov
to CS; that would be a jump because it would change where code-fetch is coming from. x86 only allows writing E/RIP or CS:E/RIP via jump instructions like call / jmp or retf.)