Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Change GDT and update CS while in long mode

I'm writing a simple home-made 64-bit OS, booting it via UEFI. This means that when my code starts executing, it is already in long mode, with paging enabled.

Now, after exiting the UEFI boot services, I want to replace all control structures built by UEFI with my own.

After successfully changing the contents of CR3 (paging structures), I successfully loaded a new GDT using lgdt.

The problem is that now, to correctly use this new GDT, I need to move a new value into CS. Online I found lots of tutorials on how to do that while switching from 32-bit to 64-bit, but almost nothing about long mode to long mode.

I think I should use a far jump, but I didn't manage to do that with this code (AT&T syntax):

mov %rax, %cr3   # load paging structures (it works)
lgdt 6(%rcx)     # load gdt (it works)
mov $100, %rsp   # update stack pointer (it works)

# now what I tried unsuccessfully:
pushw $8         # new code segment selector
pushq fun        # function to execute next
retfq            # far return (pops address and code segment)

Not having any IDT in place, this code triple faults at retfq.

EDIT: I checked my paging structures, and I'm quite sure they are not the cause of the problems. In fact, the code runs fine without the last three instructions. The problem is that I need a way to update the CS, that in my code still refers to the old segment built by UEFI. Is retfq the correct way of doing this? Or which other instruction should I use?

Thanks in advance.

like image 848
lodo Avatar asked Feb 08 '23 07:02

lodo


1 Answers

Looks like the main issue was a simple typo. In at&t syntax pushq fun and pushq $fun mean very different things, the former pushes the 8 bytes in memory at address fun while the latter pushes the address of fun (assuming it fits into a 32 bit sign extended immediate).

That said, lretq also expects the selector as a full 8-byte qword so pushw $8 should really pushq $8. The word-sized push will still work as long as the extra 6 bytes are readable, but it will unbalance the stack. This might not matter if you reload the stack pointer anyway.

An alternative code that avoids all of the above pitfalls could look like:

sub $16, %rsp
movq $8, 8(%rsp)
movabsq $fun, %rax
mov %rax, (%rsp)
lretq
like image 65
Jester Avatar answered Feb 15 '23 07:02

Jester