User:No92/UEFI Bare Bones

From OSDev Wiki
Jump to navigation Jump to search
Difficulty level
Difficulty 2.png
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

See Also

Articles

External Links