Boot stub → protected mode → first Rust
You'll build: the first two links of the boot chain — a 512-byte 16-bit
assembly stub (stage0) that prepares the machine, and a 32-bit Rust
bootloader that it hands control to.
How an x86 PC boots
When a BIOS PC powers on, the CPU is in 16-bit real mode — the same
instruction set as a 1978 8086, with 1 MiB of addressable memory and direct
access to BIOS services. The BIOS finds a bootable disk, loads its first
512-byte sector (the boot sector, or MBR) to physical address 0x7C00, and
jumps there. That's it. Everything else is our job.
We can't write that first sector in Rust: rustc has no 16-bit code
generator. So the only assembly in this whole course is a tiny stub,
stage0.asm, that does the few things which must happen in 16-bit real mode,
then switches the CPU into 32-bit protected mode and jumps into Rust.
stage0 must, in order:
- Collect the memory map. The BIOS
INT 15h, AX=0xE820service reports the machine's RAM regions. It's only callable in real mode, so we grab it now and stash it in low memory for the Rust stages to read later. - Load the rest off disk. Using
INT 13h(also real-mode-only), read the 32-bit Rust bootloader and the kernel from disk into memory. After we leave real mode there is no BIOS disk service — so we load everything up front. - Enable the A20 line. A legacy quirk: address bit 20 is forced to 0 until
you enable it, so memory above 1 MiB would wrap. One
outto port0x92. - Enter protected mode. Load a flat 32-bit GDT, set
CR0.PE, and far-jump to reloadCS. The CPU now decodes 32-bit instructions and can address 4 GiB. - Jump to Rust at a fixed address (
0x8000).
From 0x8000 onward it's Rust — #![no_std], #![no_main], one _start that
the linker pins to the load address. In this stage the Rust bootloader just
proves it's alive: it drives the serial port itself (the kernel isn't running
yet) and prints [PASS] pm_rust.
vs. Linux: the shape is identical. Linux's real-mode entry lives in
arch/x86/boot/(header.S,main.c) — it too gathers the E820 map (viaINT 15h), enables A20, and switches to protected mode inpmjump.S, before decompressing and entering the kernel proper. GRUB/the boot protocol play the role ourstage0plays. We're building a miniature of the real thing.
Why a separate 32-bit target
The kernel is built for x86_64-unknown-none. The bootloader runs before
long mode, in 32-bit protected mode, so it needs its own target:
os/boot/x86-none-32.json — a 32-bit x86 ELF, no MMX/SSE (the FPU/vector units
aren't set up this early), panic = "abort". It's built with build-std
(there's no prebuilt core for this target) and -Zjson-target-spec.