5

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.

lodo
  • 2,314
  • 19
  • 31
  • 1
    This is not enough information. See [mcve]. How do you know the things work that you claim? Side note, you should use `pushq $8` since the `retfq` will expect a qword. Also I hope you know that the `rsp` still refers to the current (cached) stack segment so that's what the `push` and `retf` will use. Finally, using an unaligned `rsp` is generally frowned upon, even if the cpu will take it. – Jester Dec 14 '15 at 13:13
  • @Jester I know for sure that some instructions work, because I tested them independently and the code ran fine. Also, the old stack segment is fine, because it covers all the address space (in fact, in long mode, all segments cover the entire address space and are used only for access rights; segment limit is always ignored; segment base is valid only for `fs` and `gs`). – lodo Dec 14 '15 at 13:20
  • Yes, but paging might map the address `100` into some unaccessible memory. – Jester Dec 14 '15 at 13:21
  • @Jester You're right. I should have specified that I checked my paging structures multiple times, and they appear ok. – lodo Dec 14 '15 at 13:22
  • 1
    Famous last words `and they appear ok`. You'd be surprised how often what appears to be okay is the part that is at fault (and is the part that is left out of code snippets). I'm with @Jester that you should provide an MCV example. – Michael Petch Dec 14 '15 at 15:32
  • @MichaelPetch I'd rather not post the code that creates the paging structures, because that's more than a hundred lines of code, interleaved with other functionalities. Can I ask you if you think that those last 3 lines are the correct way of changing the CS (given that there aren't bugs in the GDT or paging structures)? – lodo Dec 14 '15 at 15:40
  • I would be inclined to use `pushq $8` but beyond that it should work – Michael Petch Dec 14 '15 at 16:22
  • I may be mistaken that the retfq in 64-bit long mode variant required 2 quadwords on the stack (one for selector and other for offset). – Michael Petch Dec 14 '15 at 16:35
  • @MichaelPetch no, you are correct (I also pointed this out). Should not cause a problem assuming the memory is readable since the top 48 bits are discarded anyway. – Jester Dec 14 '15 at 16:36
  • 2
    Umm, did you mean `pushq $fun` maybe? Also note that push immediate only does sign extended 32 bits. The code I use is more like: `sub $16, %rsp; movq $8, 8(%rsp); movabsq $fun, %rax; mov %rax, (%rsp); lretq`. – Jester Dec 14 '15 at 16:41
  • @Jester I didn't read your first comment beyond MCVE duh - sorry. – Michael Petch Dec 14 '15 at 16:45
  • @MichaelPetch yeah I agree. The other comment was for @lodo ... that he simply missed the `$` sign. – Jester Dec 14 '15 at 16:48
  • @Jester @MichaelPetch I feel very bad... changing `pushq fun` to `pushq $fun` solved the issue... Sorry guys, I stole your time for a stupid error... – lodo Dec 14 '15 at 16:50
  • If @Jester wants to make his comment an answer, I'll accept it. Thank you again for your time. – lodo Dec 14 '15 at 16:50

1 Answers1

9

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
Jester
  • 56,577
  • 4
  • 81
  • 125