Boot to Pixels
You'll go from an empty file to your own code drawing your own logo on your real screen — no BIOS, no bootloader you didn't write, nothing underneath you but the firmware. This is the whole toolchain and the first real win.
Here's the promise, stated plainly: by the end of this module you'll hold a USB stick you made, plug it into a laptop, and watch your code take over the screen. Not in an emulator — on the metal. Everything in this module is free. Let's build it.
1.1 Why UEFI, and why everything you've read is about BIOS
Almost every OS-dev tutorial you've found starts in 16-bit real mode, fighting the A20 line and writing a bootsector in assembly. That's not because it's the right way to start in 2026 — it's because those tutorials were written for legacy BIOS, and everyone copied everyone.
Modern machines don't boot like that. Here's what actually happens when you press power:
- The firmware (UEFI) initializes the hardware and hands control to a program — normally a bootloader like GRUB, which then loads Linux or Windows.
- That program is just a file on a FAT partition, at a known path. If you put your file there, the firmware runs your code.
- By the time your code runs, you're already in 64-bit long mode. Paging is on with an identity map. The A20 gate is a non-issue. The firmware hands you a pile of services and a memory map.
So the "hard 16-bit bootsector" everyone starts with is a history lesson, not a prerequisite. We skip it entirely. Your OS is the program the firmware loads. That's the whole idea, and it's why this path is both more modern and — genuinely — easier to get running.
firmware → your BOOTX64.EFI → your OS. There is no GRUB, no bootsector, no real mode. Everything in this module is about making that one arrow work, then drawing to the screen the firmware handed you.
1.2 The smallest program the firmware will run
A UEFI application is a PE/COFF binary with an entry point you name yourself. No main,
no C runtime, no libc. The firmware calls your efi_main and hands you two things: a
handle representing your own image, and a pointer to the system table — the root of every
service UEFI offers.
Here's the entire first program. It disarms the watchdog (critical — see the warning), prints one line, and parks:
EFI_STATUS efi_main(EFI_HANDLE ImageHandle, EFI_SYSTEM_TABLE *SystemTable) {
SysTable = SystemTable; // stash it before anything can fail
SelfImage = ImageHandle; // this is "us" as far as firmware is concerned
// KILL THE WATCHDOG. FIRST. BEFORE ANYTHING ELSE.
// UEFI arms a 5-minute reset before calling you, assuming you're a
// bootloader that hands off quickly. You're an OS that doesn't — so
// if you're still alive at 5:00, the firmware reboots the machine.
SysTable->BootServices->SetWatchdogTimer(0, 0, 0, 0); // 0 = disable
print(L"ForgeOS-style bootstrap alive.\r\n"); // \r\n — firmware wants both
print(L"Firmware vendor: ");
print(SysTable->FirmwareVendor); // proves we're reading real firmware data
for (;;) { } // park forever — "it worked" must look different from "it crashed"
return EFI_SUCCESS;
}
The symptom is maddening: your OS runs fine for five minutes, then black-screens and reboots with no panic, no log, nothing. That's the firmware watchdog, not your code. Disable it on the very first line and never think about it again.
The build is two commands — clang targeting PE, then lld-link. The flags matter, and this is exactly the kind of thing the tutorials leave out:
clang -target x86_64-windows \ # UEFI binaries are PE/COFF, not ELF
-ffreestanding \ # no libc, no crt0, no malloc
-fshort-wchar \ # UEFI strings are UTF-16 — without this, L"x" is garbage
-mgeneral-regs-only \ # no SSE: firmware hasn't set up its state yet
-c 01_bootstrap.c -o boot.obj
lld-link -subsystem:efi_application -entry:efi_main \
-nodefaultlib -dll boot.obj -out:BOOTX64.EFI
That -fshort-wchar flag is a classic trap: leave it off and every string you pass
to the firmware is silently the wrong width, and nothing prints. -mgeneral-regs-only
is the other one — a stray SSE register in a memcpy will fault before you've printed a word.
1.3 A USB stick that boots
The firmware looks for a specific file at a specific path on a FAT-formatted partition. Put your
BOOTX64.EFI there and any UEFI machine will boot it, no configuration, no NVRAM entry:
USB stick (FAT32)
└── EFI
└── BOOT
└── BOOTX64.EFI ← the fallback path firmware runs with no boot entry
That exact name — BOOTX64.EFI in /EFI/BOOT/ — is the "removable media" fallback
every UEFI firmware checks. It's what makes the stick portable between machines. Format the stick as
FAT32, drop the file in that path, and it's bootable.
- On Windows: use
diskpartto make a FAT32 partition, or the deploy script the course ships. - Secure Boot: turn it off in firmware settings for now. (Signing your loader is a later topic; it's not needed to learn.)
- Boot menu: most machines have a one-time boot key (F12 / F9 / Esc) — pick the USB device.
"How do I get my thing off the emulator and onto a real machine" is the question free tutorials answer worst. It's three files in a folder with the right name. Now you know.
1.4 QEMU: your fast feedback loop
Reflashing a USB stick for every change is slow. QEMU with the OVMF UEFI firmware image gives you a full UEFI environment in a window that boots in a second. You'll do 95% of development here and only flash the stick to confirm on real hardware.
qemu-system-x86_64 \
-bios OVMF.fd \ # the UEFI firmware — NOT SeaBIOS
-drive file=usb.img,format=raw \ # your FAT image with BOOTX64.EFI
-net none
QEMU is fast and forgiving. Real hardware is slow and honest. Things that work in the emulator and die on metal: uncached framebuffers, timing assumptions, DMA alignment, firmware quirks. Use QEMU to iterate. Use the laptop to find out what's actually true. You'll feel this for real in the very next lesson.
1.5 Graphics Output Protocol — getting the framebuffer
UEFI is built entirely out of protocols — structs of function pointers you request by a 128-bit GUID. Graphics is the Graphics Output Protocol (GOP). You ask the firmware to locate it, pick the best video mode, and it hands you the physical address of a linear framebuffer: raw pixels you write directly.
status = LocateProtocol(&gEfiGraphicsOutputProtocolGuid, 0, (void**)&Gop);
// cache AFTER SetMode — it replaces Info and FrameBufferBase.
FrameBuffer = (UINT32*)(UINTN)Gop->Mode->FrameBufferBase; // raw pixels
ScreenW = Gop->Mode->Info->HorizontalResolution; # what you SEE
ScreenH = Gop->Mode->Info->VerticalResolution;
Pitch = Gop->Mode->Info->PixelsPerScanLine; // ← the one that bites you
The obvious way to find a pixel is buf[y * ScreenW + x]. It works in QEMU, and on about half of real machines, and then on someone's laptop the entire image comes out sheared diagonally.
Hardware scanlines are often wider than the visible area — a 1366-wide panel might have 1408-pixel scanlines for alignment. PixelsPerScanLine is the true stride; HorizontalResolution is only what you can see. They're equal often enough to fool you and different often enough to ruin your week. Always index with pitch.
1.6 Pixels, rectangles, and your logo on real glass
With the framebuffer cached, a pixel is one write. Build draw_rect on top, clip once up front (never per-pixel — that's the whole frame budget at 1440p), and draw your first frame:
void put_pixel(int x, int y, UINT32 color) {
if (x < 0 || x >= ScreenW || y < 0 || y >= ScreenH) return; // no MMU to save you
FrameBuffer[y * Pitch + x] = color; // Pitch, never ScreenW
}
void draw_rect(int x, int y, int w, int h, UINT32 color) {
// clip ONCE, up front — then the inner loop is a bare store
if (x < 0) { w += x; x = 0; }
if (y < 0) { h += y; y = 0; }
if (x + w > ScreenW) w = ScreenW - x;
if (y + h > ScreenH) h = ScreenH - y;
for (int r = 0; r < h; r++) {
UINT32 *line = FrameBuffer + (UINT64)(y+r) * Pitch + x;
for (int c = 0; c < w; c++) line[c] = color; // no branch, no bounds check
}
}
// the payoff frame — your brand colours, dead centre
draw_rect(0, 0, ScreenW, ScreenH, 0x00070B12); // dark navy fill
draw_rect(cx-120, cy-40, 240, 80, 0x0035C6D8); // cyan slab
draw_rect(cx-100, cy-20, 200, 40, 0x00E8873F); // ember inset
Boot that in QEMU — instant. Then flash it to the stick and boot the laptop. On real hardware that full-screen fill might be visibly slow, while QEMU did it instantly. Nothing is wrong with your code:
Firmware sometimes leaves the framebuffer mapped uncached. Every 4-byte pixel write then goes straight over the bus with no batching — a 1440p fill becomes 3.7 million individual transactions. Brutal on metal, invisible in QEMU. The fix (marking the range write-combining) is the single biggest performance lever in the whole OS — and it's the first thing you'll do in Module 02.
- A toolchain that builds a
BOOTX64.EFIfrom C - A USB stick that boots your code on a real UEFI machine
- A QEMU + OVMF loop for fast iteration
- The framebuffer located, with pitch handled correctly
- Your own logo drawn on your own screen — in an emulator and on real hardware
That's the hardest part behind you — not because the code was long, but because "get my own code onto real hardware under UEFI" is the wall most people never get over. You're past it. From here it's building up: memory, interrupts, a compositor, windows, files, the network — and eventually DOOM.
Module 02 — Memory & Interrupts
Build your own physical memory manager from the firmware's map, install an IDT, and wire up the timer and keyboard so the machine finally responds to you. The rest of the course is where the OS comes to life.
See pricing — from $9/mo or $149 once →