Writing a UEFI Bootloader
Bootloaders already covers writing a legacy 512-byte boot sector and staged loading; a UEFI application is a different path entirely, loaded and executed directly by firmware with no GRUB, no legacy boot sector, and no staged handoff of the kind that article describes, since UEFI firmware itself already provides most of what a legacy bootloader’s early stages exist to build up from scratch.
A PE32+ binary, not raw ELF
Section titled “A PE32+ binary, not raw ELF”UEFI firmware only knows how to load and execute the PE32+ format, the same executable format Windows uses, not ELF, which is what a kernel’s own binary is typically built as. A kernel targeting UEFI directly therefore needs its bootloader stage built and linked as a genuine PE32+ executable (-target x86_64-pc-win32-coff and an appropriate linker script under a GNU toolchain, or natively under an EFI-aware toolchain), exporting a specific entry point signature firmware expects to call directly.
EFI_STATUS EFIAPI efi_main(EFI_HANDLE image_handle, EFI_SYSTEM_TABLE *system_table) { // system_table gives access to Boot Services, Runtime Services, // and the console, from the very first instruction ...}system_table is the entry point’s gateway to everything UEFI offers: Boot Services (memory allocation, protocol lookup, the memory map), Runtime Services (a smaller set that remains callable even after the kernel has taken over, such as reading the real-time clock), and a basic text-mode console usable for early diagnostic output before any framebuffer has been located at all.
Boot Services: the memory map and a framebuffer
Section titled “Boot Services: the memory map and a framebuffer”Before transferring control to the kernel proper, a UEFI bootloader typically needs two things from Boot Services: the memory map, obtained through GetMemoryMap, describing which physical regions are usable, reserved, or already occupied by firmware or the bootloader’s own allocations, and a framebuffer, located through the Graphics Output Protocol (GOP), the same protocol VGA & Framebuffers already mentions in passing as UEFI’s counterpart to VBE.
EFI_GRAPHICS_OUTPUT_PROTOCOL *gop;system_table->BootServices->LocateProtocol(&gop_guid, NULL, (void**)&gop);
uint32_t width = gop->Mode->Info->HorizontalResolution;uint32_t height = gop->Mode->Info->VerticalResolution;uint64_t fb_base = gop->Mode->FrameBufferBase;Both are obtained the same way essentially every UEFI facility is: locating a protocol by its GUID through Boot Services, then calling functions through that protocol’s own function-pointer table, a pattern consistent across memory services, graphics, disk I/O, and everything else Boot Services exposes, rather than each facility having its own unrelated calling convention.
ExitBootServices: the point of no return
Section titled “ExitBootServices: the point of no return”ExitBootServices is the single most consequential call in the entire boot sequence: after it succeeds, every Boot Services function pointer becomes invalid, calling any of them produces undefined behavior, and the kernel is entirely on its own for memory management, further hardware discovery, and everything else Boot Services was providing up to that moment.
EFI_MEMORY_DESCRIPTOR *map;UINTN map_size, map_key, desc_size;UINT32 desc_version;
system_table->BootServices->GetMemoryMap(&map_size, map, &map_key, &desc_size, &desc_version);// ... allocate a buffer of map_size and call GetMemoryMap again to fill it ...
system_table->BootServices->ExitBootServices(image_handle, map_key);// from this point on, no Boot Services call is validThe call requires the exact map_key from the most recent GetMemoryMap call, and fails if any intervening allocation (even one made by the bootloader’s own code) has changed the memory map since that key was obtained, which is why the conventional pattern calls GetMemoryMap again, immediately before ExitBootServices, specifically to guarantee the key matches the map as it exists at that exact instant. Runtime Services remain callable after this point (the kernel isn’t entirely without any firmware assistance), but Boot Services, and the console, framebuffer discovery, and memory allocation functions that came with them, do not.
Implementation notes
Section titled “Implementation notes”Because UEFI’s own memory allocator is unavailable after ExitBootServices, a kernel needs its own frame allocator, already covered separately, ready to take over using exactly the memory map obtained just before that call, since no further map can be requested afterward to fill in anything missed. A UEFI bootloader also has to be placed at a firmware-recognized path on a FAT32 EFI System Partition (conventionally \EFI\BOOT\BOOTX64.EFI as the fallback default BIOS vs. UEFI already names) rather than embedded in a boot sector the way a legacy bootloader is, which is itself a partition and filesystem UEFI firmware already knows how to read without any driver the kernel provides.
References
Section titled “References”- ^ UEFI Forum, UEFI Specification, Chapter 4 (Boot Services), Chapter 8 (Runtime Services), and Chapter 12 (Graphics Output Protocol).
See also
Section titled “See also”- BIOS vs. UEFI: the high-level comparison this article’s boot path fits under.
- VGA & Framebuffers: the GOP framebuffer discovery mentioned there in one sentence, covered here in full from the boot-flow side.
- Physical Memory: the frame allocator that has to take over using the memory map obtained just before ExitBootServices.