User:No92/UEFI Bare Bones
Jump to navigation
Jump to search
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.
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