I decided to write an EFI loader with gnu-efi. About 17 years ago I rolled my own MBR bootloader and used the similar recursive page mapping scheme in the kernel. Decided to upgrade to 64-bit MP.
Relevant code is posted here.
The issue is that upon switching cr3, Qemu resets (I guess triple fault). There's something it doesn't like, but I've been logging extensively and everything looks correct.
Is there something I'm missing?
My assumptions are that EFI sets most stuff up for me, and when I change the page tables it should "just work." My thoughts go to maybe the calling convention into asm is wonky, or there is more protection setup to do.
Help?? Lol
Explanation of the code:
- Outside virtual.c there is some minimal setup to get the relevant files into memory (initrd.img) and locate kernel.elf.
- load_kernel() starts the work (skip measure_kernel(), I think I can factor that out)
- create_page_tables() creates a master page, initializing it using map_page() to recursively map it into itself.
- map_virtual_address_space() takes various things and maps them in using the new page tables, keeping a running "next_page" variable:
- Maps the kernel by calling in map_kernel(). This function maps in sections of the ELF, applying the associated page permissions.
- Maps stacks, with one unmapped guard page and several stack pages, per cpu (obtained with an EFI function abused in get_mp_info().
- Maps in the frame buffer (obtained outside this module; the screen was cleared to 0x181825, part of the catppuccin color palette).
- Maps in the initrd image.
- Identity maps parts of the EFI loader state, including the code and data (applying the no-execute bit to the data pages as in other data pages I've mapped). This uses a temporary memory map from EFI that I allocate and then free.
- Prints out a final sanity check tracing the virtual addresses through the page tables, into the bytes of the physical pages.
- load_kernel() then gets the final memory map (get_memmap()), and calls enter_kernel(), which passes the memmap key to EFI ExitBootServices(), and then calls trampoline() in arch/amd64/asm.S.
- The parameters (page_table, stack_pointer, boot_info, kernel_entry) I assume correspond to (rdi, rsi, rdx, rcx).
- I:
- clear interrupts,
- set the stack pointer,
- make the boot info rdi so it can be passed to the kernel,
- set cr3 (this is where the bug is exposed), and
- jump to the entry point.