User:No92/UEFI Bare Bones
From OSDev Wiki
Difficulty level |
---|
Medium |
We will be creating a minimal example capable of booting on UEFI, printing "Hello World" and waiting for a keystroke before returning.
Contents |
Prerequisites
- clang and lld (specifically lld-link)
- qemu-system-x86_64
- make, git, wget, …
Setup
We will be utilizing Zircon's headers for UEFI, as they offer a clean interface without some insane build system (TianoCore) or ugly hacks (gnu-efi). You can extract them from the source yourself (they are located at system/private/efi
, or use this mirror. Place them in the kernel/include directory.
Makefile
Next, set up a Makefile with the following contents:
CC := clang LD := lld-link-6.0 EMU := qemu-system-x86_64 CFLAGS := -ffreestanding -flto -fno-stack-protector -fpic -fshort-wchar -Ikernel/include -MMD -mno-red-zone -std=c11 -target x86_64-pc-win32-coff -Wall -Wextra LDFLAGS := -subsystem:efi_application -nodefaultlib -dll EMUFLAGS := -drive if=pflash,format=raw,file=bin/OVMF.fd -drive format=raw,file=fat:rw:bin/hdd -M accel=kvm:tcg -net none -serial stdio OBJ := kernel/kernel.o KERNEL := bin/hdd/efi/boot/bootx64.efi OVMF_URL := https://dl.bintray.com/no92/vineyard-binary/OVMF.fd OVMF_BIN := OVMF.fd OVMF := bin/$(OVMF_BIN) $(KERNEL): $(OBJ) mkdir -p $(dir $@) $(LD) $(LDFLAGS) -entry:efi_main $< -out:$@ %.o: %.c $(CC) $(CFLAGS) -c $< -o $@ -include $(OBJ:.o=.d) test: $(KERNEL) $(OVMF) $(EMU) $(EMUFLAGS) $(OVMF): mkdir -p bin wget $(OVMF_URL) -O $(OVMF) -qq clean: rm -f $(KERNEL) rm -f $(OBJ) $(OBJ:.o=.d) .PHONY: test clean
Why do I need this option?
- -ffreestanding: lets the compiler know that you are not in a hosted environment
- -flto: enable lld to use LTO (link-time optimization)
- -fno-stack-protector: the Stack-Smashing Protector needs (trivial) runtime support, which isn't provided here
- -fpic: UEFI requires the resulting binary to be position-independent
- -fshort-wchar: UEFI specifies wchar to be 16 bits long
- -mno-red-zone: you should disable this for x86_64 kernel code
- -target x86_64-pc-win32-coff: UEFI applications are some variation of Windows' PE binaries
kernel/kernel.c
#include <efi/boot-services.h> #include <efi/runtime-services.h> #include <efi/system-table.h> #include <efi/types.h> #include <stdbool.h> /* I'm too lazy to type this out five times */ #define ERR(x) if(EFI_ERROR((x))) return (x) efi_status efi_main(efi_handle handle __attribute__((unused)), efi_system_table *st) { efi_status status; efi_input_key key; /* reset the watchdog timer */ st->BootServices->SetWatchdogTimer(0, 0, 0, NULL); ERR(status); /* clear the screen */ status = st->ConOut->ClearScreen(st->ConOut); ERR(status); /* print 'Hello World' */ status = st->ConOut->OutputString(st->ConOut, L"Hello World"); ERR(status); /* flush console input buffer */ status = st->ConIn->Reset(st->ConIn, false); ERR(status); /* poll for a keystroke */ while((status = st->ConIn->ReadKeyStroke(st->ConIn, &key)) == EFI_NOT_READY); ERR(status); return EFI_SUCCESS; }
Building
Run make test to build the kernel and run it in QEMU.
What's next?
- getting a memory map
- setting up a display mode with the Graphics Output Protocol
- getting the hell out of UEFI with
ExitBootServices