This is a simple bootloader for x86_64 arch, for learning purposes.
Install the following packages:
sudo pacman -S nasm qemu-system-x86 tigervnc| Package | Purpose | Minimum Version |
|---|---|---|
nasm |
x86 assembler for boot.s |
2.16+ |
qemu-system-x86 |
QEMU emulator (provides qemu-system-x86_64) |
8.0+ |
tigervnc |
Provides vncviewer for graphical QEMU output |
1.14+ |
Build bootloader.bin
makeLaunch in QEMU with VNC output (opens vncviewer automatically and closes QEMU on exit)
make runClean build artifacts
make clean- https://www.joe-bergeron.com/posts/Writing%20a%20Tiny%20x86%20Bootloader/
- https://pdos.csail.mit.edu/6.828/2018/xv6/book-rev11.pdf
The code enters 16-bit Real mode, which is the simplest x86 CPU operating mode, active at startup (reset). It uses 16-bit registers, a 20-bit address bus (limiting accessible memory to 1 MB), and addresses memory via segment:offset pairs. There's no memory protection, no privilege levels, and no virtual memory — any code can access any memory or I/O port. Real mode is what DOS and legacy BIOS bootloaders run in.
We need to start with 0x07C0 as this address is the starting point for
The last 2 bytes of the 512 bytes sector are 0xAA55, which represent the boot signature.
In x86 Real mode the following registers are used:
| Category | Register(s) | Purpose |
|---|---|---|
| General-purpose | AX | Accumulator, commonly used for arithmetic and I/O. |
| General-purpose | BX | Base register, often used for memory addressing. |
| General-purpose | CX | Count register, used in loops and shifts. |
| General-purpose | DX | Data register, used with AX in some operations and I/O. |
| General-purpose | SI | Source index, used in string and memory operations. |
| General-purpose | DI | Destination index, used in string and memory operations. |
| General-purpose | BP | Base pointer, typically used to access stack-based data. |
| General-purpose | SP | Stack pointer, points to the top of the stack. |
| Segment | CS | Code segment, used with IP to locate instructions. |
| Segment | DS | Data segment, default segment for many data accesses. |
| Segment | ES | Extra segment, often used in string instructions. |
| Segment | SS | Stack segment, used with SP/BP. |
| Segment | FS | Extra segment register on later x86 CPUs. |
| Segment | GS | Extra segment register on later x86 CPUs. |
| Instruction pointer | IP | Offset of the next instruction within CS. |
| Flags | FLAGS | Status and control bits for CPU operations. |
| 8-bit subregisters | AH / AL | High and low 8-bit parts of AX. |
| 8-bit subregisters | BH / BL | High and low 8-bit parts of BX. |
| 8-bit subregisters | CH / CL | High and low 8-bit parts of CX. |
| 8-bit subregisters | DH / DL | High and low 8-bit parts of DX. |
Notes on nasm assembly:
| Instruction / Directive | What it does |
|---|---|
bits 16 |
Tells NASM to assemble 16-bit code (Real mode). |
mov dest, src |
Copies src into dest. Can move between registers, memory, and immediates. |
call label |
Pushes the return address onto the stack and jumps to label. |
ret |
Pops the return address from the stack and jumps back after a call. |
push val |
Decrements SP and stores val on the stack. |
pop reg |
Loads a value from the stack into reg and increments SP. |
pusha |
Pushes all 16-bit general-purpose registers (AX, CX, DX, BX, SP, BP, SI, DI) onto the stack. |
popa |
Restores all registers saved by pusha (in reverse order). |
add dest, src |
Adds src to dest and stores the result in dest. Used here to clean arguments off the stack (add sp, 2). |
cli |
Clears the interrupt flag — CPU ignores maskable hardware interrupts. |
hlt |
Stops the CPU until the next external interrupt (or reset). |
int n |
Triggers software interrupt number n. BIOS provides services via int 0x10 (video), int 0x13 (disk), etc. |
or dest, src |
Performs a bitwise OR. Used here as or al, 0 to set the zero flag without changing the value (tests for zero). |
je label |
Jumps to label if the zero flag is set (i.e. if the previous operation resulted in zero). |
jmp label |
Unconditionally jumps to label. |
db val |
Defines a byte (or bytes) of data. msg: db "Hello", 0 stores the string followed by a null terminator. |
dw val |
Defines a word (2 bytes) of data. dw 0xAA55 places the boot signature at the end of the sector. |
times count directive |
Repeats directive count times. times 510-($-$$) db 0 fills the rest of the 512-byte sector with zeros. |