Project 5: x86 Bootloader - Real Mode
Build a 512-byte boot sector that the BIOS loads and executes at 0x7C00, printing messages using BIOS interrupts and demonstrating complete control of the machine from power-on.
Quick Reference
| Attribute | Value |
|---|---|
| Difficulty | Expert |
| Time Estimate | 1-2 weeks |
| Language | x86 Assembly (NASM) |
| Prerequisites | x86 assembly basics, memory segments, hexadecimal |
| Key Topics | Real mode, BIOS interrupts, boot sector, 0x7C00, 0xAA55 signature |
Table of Contents
- 1. Learning Objectives
- 2. Theoretical Foundation
- 3. Project Specification
- 4. Solution Architecture
- 5. Implementation Guide
- 6. Testing Strategy
- 7. Common Pitfalls & Debugging
- 8. Extensions & Challenges
- 9. Real-World Connections
- 10. Resources
- 11. Self-Assessment Checklist
- 12. Submission / Completion Criteria
1. Learning Objectives
By completing this project, you will:
- Understand the x86 boot process: Know exactly what happens from power-on to your code executing
- Master 16-bit real mode: Understand segment:offset addressing, the 1MB memory limit, and CPU limitations
- Program using BIOS interrupts: Use INT 0x10 (video), INT 0x13 (disk), INT 0x16 (keyboard) for I/O
- Write position-independent code: Handle the ORG directive and understand relocation
- Work within extreme constraints: Fit meaningful code in exactly 512 bytes
- Debug at the lowest level: Use QEMU’s debugging features and manual instruction tracing
- Prepare for protected mode: Understand why and how to transition beyond real mode
2. Theoretical Foundation
2.1 Core Concepts
What is Real Mode?
Real mode is the CPU’s initial operating mode at power-on. It’s a direct descendant of the original 8086 processor from 1978:
+-----------------------------------------------------------------------+
| REAL MODE CHARACTERISTICS |
+-----------------------------------------------------------------------+
| |
| Memory Model: |
| +------------------+ |
| | Segment:Offset | Physical Address = Segment * 16 + Offset |
| | 16-bit : 16-bit | Maximum addressable: 0xFFFF:0xFFFF = 1MB + 64KB|
| +------------------+ |
| |
| Example: 0x07C0:0x0000 = 0x7C00 (boot sector load address) |
| 0x0000:0x7C00 = 0x7C00 (same physical address!) |
| |
| Registers (all 16-bit in real mode): |
| +------+------+------+------+------+------+------+------+ |
| | AX | BX | CX | DX | SI | DI | BP | SP | |
| +------+------+------+------+------+------+------+------+ |
| | CS | DS | ES | SS | FS | GS | |
| +------+------+------+------+------+------+ |
| |
| Limitations: |
| - No memory protection (any code can access any memory) |
| - No virtual memory or paging |
| - Only 1MB addressable (20-bit address bus) |
| - No privilege levels (everything runs at "ring 0") |
| - Interrupts use hardcoded IVT at 0x0000 |
| |
+-----------------------------------------------------------------------+
The Boot Process in Detail
+-----------------------------------------------------------------------+
| x86 BOOT SEQUENCE (BIOS) |
+-----------------------------------------------------------------------+
| |
| Step 1: Power-On |
| ┌─────────────────────────────────────────────────────────────────┐ |
| │ - CPU reset sets CS:IP to 0xF000:0xFFF0 (ROM BIOS entry) │ |
| │ - All registers have undefined values except CS:IP │ |
| │ - CPU is in real mode, interrupts disabled │ |
| └─────────────────────────────────────────────────────────────────┘ |
| │ |
| ▼ |
| Step 2: BIOS POST (Power-On Self-Test) |
| ┌─────────────────────────────────────────────────────────────────┐ |
| │ - Test RAM (you see the memory count) │ |
| │ - Initialize video hardware │ |
| │ - Detect and initialize storage devices │ |
| │ - Set up Interrupt Vector Table (IVT) at 0x0000 │ |
| │ - Set up BIOS Data Area (BDA) at 0x0400 │ |
| └─────────────────────────────────────────────────────────────────┘ |
| │ |
| ▼ |
| Step 3: Boot Device Selection |
| ┌─────────────────────────────────────────────────────────────────┐ |
| │ - BIOS checks boot order (floppy, CD, HDD, USB, network) │ |
| │ - For each device, try to read first sector (512 bytes) │ |
| │ - Check for boot signature: bytes 510-511 must be 0x55, 0xAA │ |
| └─────────────────────────────────────────────────────────────────┘ |
| │ |
| ▼ |
| Step 4: Load Boot Sector |
| ┌─────────────────────────────────────────────────────────────────┐ |
| │ - Read 512 bytes from sector 0 to memory address 0x7C00 │ |
| │ - DL register contains boot drive number (0x00=floppy, 0x80=HD) │ |
| │ - Jump to 0x0000:0x7C00 (your code starts executing!) │ |
| └─────────────────────────────────────────────────────────────────┘ |
| │ |
| ▼ |
| Step 5: Your Boot Sector Runs |
| ┌─────────────────────────────────────────────────────────────────┐ |
| │ - You're now in control! │ |
| │ - Segment registers (DS, ES, SS) are UNDEFINED - set them! │ |
| │ - BIOS services still available via INT instructions │ |
| │ - Load more code or switch to protected mode │ |
| └─────────────────────────────────────────────────────────────────┘ |
| |
+-----------------------------------------------------------------------+
Memory Map at Boot Time
+-----------------------------------------------------------------------+
| REAL MODE MEMORY MAP |
+-----------------------------------------------------------------------+
| |
| 0x00000000 ┌────────────────────────────────────┐ |
| │ Interrupt Vector Table │ 1 KB |
| │ 256 entries x 4 bytes each │ |
| 0x00000400 ├────────────────────────────────────┤ |
| │ BIOS Data Area (BDA) │ 256 bytes |
| 0x00000500 ├────────────────────────────────────┤ |
| │ │ |
| │ FREE MEMORY │ |
| │ (Available for your use) │ ~29 KB |
| │ │ |
| 0x00007C00 ├────────────────────────────────────┤ ◄── YOUR CODE HERE |
| │ Boot Sector (512 bytes) │ |
| 0x00007E00 ├────────────────────────────────────┤ |
| │ │ |
| │ FREE MEMORY │ |
| │ (For stack, loaded code) │ ~480 KB |
| │ │ |
| 0x0007FFFF ├────────────────────────────────────┤ |
| │ Extended BIOS Data Area │ ~1 KB |
| 0x0009FFFF ├────────────────────────────────────┤ |
| │ │ |
| │ Conventional Memory Top │ |
| 0x000A0000 ├────────────────────────────────────┤ |
| │ Video Memory (VGA) │ 128 KB |
| 0x000C0000 ├────────────────────────────────────┤ |
| │ Video BIOS ROM │ 32 KB |
| 0x000C8000 ├────────────────────────────────────┤ |
| │ BIOS Expansions │ |
| 0x000F0000 ├────────────────────────────────────┤ |
| │ System BIOS ROM │ 64 KB |
| 0x000FFFFF └────────────────────────────────────┘ ◄── 1 MB limit |
| |
+-----------------------------------------------------------------------+
BIOS Interrupt Services
The BIOS provides services through software interrupts. Each interrupt number corresponds to a service category:
+-----------------------------------------------------------------------+
| KEY BIOS INTERRUPTS |
+-----------------------------------------------------------------------+
| |
| INT 0x10 - Video Services |
| ┌─────────────────────────────────────────────────────────────────┐ |
| │ AH = 0x00: Set video mode │ |
| │ AL = mode (0x03 = 80x25 text, 0x13 = 320x200 VGA) │ |
| │ │ |
| │ AH = 0x01: Set cursor shape │ |
| │ CH = start line, CL = end line │ |
| │ │ |
| │ AH = 0x02: Set cursor position │ |
| │ BH = page, DH = row, DL = column │ |
| │ │ |
| │ AH = 0x03: Get cursor position │ |
| │ Returns: DH = row, DL = column │ |
| │ │ |
| │ AH = 0x0E: Teletype output (print character, advance cursor) │ |
| │ AL = character, BH = page, BL = color (graphics) │ |
| │ │ |
| │ AH = 0x13: Write string │ |
| │ ES:BP = string, CX = length, DH/DL = row/col │ |
| └─────────────────────────────────────────────────────────────────┘ |
| |
| INT 0x13 - Disk Services |
| ┌─────────────────────────────────────────────────────────────────┐ |
| │ AH = 0x00: Reset disk system │ |
| │ DL = drive number │ |
| │ │ |
| │ AH = 0x02: Read sectors │ |
| │ AL = sector count, CH = cylinder (low 8 bits) │ |
| │ CL = sector (bits 0-5) + cylinder high (bits 6-7) │ |
| │ DH = head, DL = drive, ES:BX = buffer │ |
| │ Returns: AH = status, AL = sectors read, CF = error │ |
| │ │ |
| │ AH = 0x08: Get drive parameters │ |
| │ DL = drive, Returns: CH/CL = max cyl/sect, DH = heads│ |
| │ │ |
| │ AH = 0x41: Check extensions present (for LBA access) │ |
| │ AH = 0x42: Extended read (LBA mode) │ |
| └─────────────────────────────────────────────────────────────────┘ |
| |
| INT 0x16 - Keyboard Services |
| ┌─────────────────────────────────────────────────────────────────┐ |
| │ AH = 0x00: Wait for keypress │ |
| │ Returns: AH = scan code, AL = ASCII character │ |
| │ │ |
| │ AH = 0x01: Check for keypress (non-blocking) │ |
| │ Returns: ZF = 1 if no key, else AH/AL as above │ |
| │ │ |
| │ AH = 0x02: Get keyboard flags │ |
| │ Returns: AL = shift flags │ |
| └─────────────────────────────────────────────────────────────────┘ |
| |
| INT 0x15 - System Services |
| ┌─────────────────────────────────────────────────────────────────┐ |
| │ AH = 0x88: Get extended memory size (in KB above 1MB) │ |
| │ AX = 0xE820: Get memory map (essential for kernel development) │ |
| │ AH = 0x24: Enable/disable A20 line │ |
| └─────────────────────────────────────────────────────────────────┘ |
| |
+-----------------------------------------------------------------------+
2.2 Why This Matters
Understanding the boot process is fundamental because:
- Every PC boots this way: Even modern UEFI systems often support legacy BIOS boot
- Foundation for OS development: You can’t write an OS without understanding boot
- Debugging boot issues: If boot fails, knowing the process helps diagnose problems
- Security research: Bootkits and rootkits operate at this level
- Embedded systems: Many x86 embedded systems use similar boot mechanisms
- Virtualization: Hypervisors must emulate or handle this process
2.3 Historical Context
The boot sector format dates back to IBM PC in 1981:
+-----------------------------------------------------------------------+
| EVOLUTION OF x86 BOOT |
+-----------------------------------------------------------------------+
| |
| 1981: IBM PC with 8088 |
| ├── Real mode only, 640KB conventional memory |
| ├── BIOS loads 512-byte boot sector to 0x7C00 |
| └── Boot signature 0xAA55 established |
| |
| 1985: 80386 introduces protected mode |
| ├── Still boots in real mode for compatibility |
| ├── Bootloader must switch to protected mode |
| └── 32-bit flat memory model becomes possible |
| |
| 1990s-2000s: Advanced bootloaders |
| ├── LILO, GRUB become standard Linux bootloaders |
| ├── Chain-loading and multi-boot become common |
| └── MBR partition table format dominant |
| |
| 2005+: UEFI begins replacing BIOS |
| ├── GPT partition table for large disks |
| ├── Secure Boot for signed bootloaders |
| └── Legacy BIOS boot still supported (CSM mode) |
| |
| Today: Both BIOS and UEFI boot still relevant |
| ├── Understanding BIOS boot essential for OS internals |
| └── Many embedded/industrial systems still use BIOS boot |
| |
+-----------------------------------------------------------------------+
2.4 Common Misconceptions
| Misconception | Reality |
|---|---|
| “BIOS loads code at 0x7C00 because that’s special” | 0x7C00 was chosen because it leaves 30KB below for interrupt handling and gives ~480KB above for loading more code |
| “The boot sector must be exactly 512 bytes” | Yes, but only 510 bytes are usable - last 2 are the signature |
| “CS is always 0 when boot sector runs” | BIOS implementations vary; some set CS:IP to 0x0000:0x7C00, others to 0x07C0:0x0000 |
| “You can use 32-bit instructions in real mode” | You can, but operand/address size prefixes are needed, and segments are still 16-bit |
| “INT 0x10 is slow” | Yes, but it’s the only portable way to do video output before you write VGA drivers |
3. Project Specification
3.1 What You Will Build
A complete 512-byte boot sector that:
- Initializes the execution environment correctly for real mode
- Prints a welcome message using BIOS teletype output
- Demonstrates segment addressing by loading data from different segments
- Reads keyboard input and displays typed characters
- Shows basic disk information (optional: loads additional sectors)
- Enters an infinite loop to prevent random execution
3.2 Functional Requirements
| Requirement | Description |
|---|---|
| FR-1 | Boot successfully in QEMU and on real hardware (if available) |
| FR-2 | Display at least one string using BIOS INT 0x10 |
| FR-3 | Set up segment registers (DS, ES, SS) properly |
| FR-4 | Establish a working stack below 0x7C00 |
| FR-5 | Include the 0xAA55 boot signature at bytes 510-511 |
| FR-6 | Fit entirely within 512 bytes |
3.3 Non-Functional Requirements
| Requirement | Description |
|---|---|
| NFR-1 | Assembly code must be well-commented |
| NFR-2 | Build with a single nasm command |
| NFR-3 | Work with QEMU without any special options |
| NFR-4 | Handle edge cases gracefully (stuck in loop, not crash) |
3.4 Example Usage / Output
# Assemble the boot sector
$ nasm -f bin boot.asm -o boot.bin
# Verify size and signature
$ ls -la boot.bin
-rw-r--r-- 1 user user 512 Dec 29 10:00 boot.bin
$ xxd boot.bin | tail -2
000001f0: 0000 0000 0000 0000 0000 0000 0000 55aa ..............U.
# Run in QEMU
$ qemu-system-x86_64 -drive format=raw,file=boot.bin
# QEMU window displays:
================================================================================
HELLO FROM THE BOOT SECTOR!
================================================================================
Boot drive: 0x80 (First HDD)
Press any key to continue...
You pressed: 'A'
System halted. Power off to restart.
3.5 Real World Outcome
After completing this project, you will have:
- Working boot sector that can be written to a USB drive and boot real hardware
- Deep understanding of how every PC starts up
- Foundation for building a bootloader that loads a kernel
- Debugging skills for the lowest level of system software
- Portfolio piece demonstrating systems programming expertise
4. Solution Architecture
4.1 High-Level Design
+-----------------------------------------------------------------------+
| BOOT SECTOR STRUCTURE |
+-----------------------------------------------------------------------+
| |
| Offset 0x000 |
| ┌─────────────────────────────────────────────────────────────────┐ |
| │ INITIALIZATION CODE │ |
| │ - Set up segment registers (DS, ES, SS) │ |
| │ - Set up stack pointer (SP) │ |
| │ - Save boot drive number (DL) │ |
| └─────────────────────────────────────────────────────────────────┘ |
| │ |
| ▼ |
| ┌─────────────────────────────────────────────────────────────────┐ |
| │ VIDEO SETUP │ |
| │ - Optional: Set video mode (INT 0x10, AH=0x00) │ |
| │ - Clear screen or set cursor position │ |
| └─────────────────────────────────────────────────────────────────┘ |
| │ |
| ▼ |
| ┌─────────────────────────────────────────────────────────────────┐ |
| │ PRINT MESSAGE │ |
| │ - Point SI to message string │ |
| │ - Loop: load byte, call teletype (INT 0x10, AH=0x0E) │ |
| │ - Repeat until null terminator │ |
| └─────────────────────────────────────────────────────────────────┘ |
| │ |
| ▼ |
| ┌─────────────────────────────────────────────────────────────────┐ |
| │ KEYBOARD INPUT │ |
| │ - Wait for key (INT 0x16, AH=0x00) │ |
| │ - Display key pressed │ |
| └─────────────────────────────────────────────────────────────────┘ |
| │ |
| ▼ |
| ┌─────────────────────────────────────────────────────────────────┐ |
| │ HALT LOOP │ |
| │ - CLI (disable interrupts) │ |
| │ - HLT (halt CPU until interrupt) │ |
| │ - JMP back to HLT (in case of NMI) │ |
| └─────────────────────────────────────────────────────────────────┘ |
| │ |
| ┌─────────────────────────────────────────────────────────────────┐ |
| │ DATA SECTION │ |
| │ - Welcome message string │ |
| │ - Other strings as needed │ |
| └─────────────────────────────────────────────────────────────────┘ |
| │ |
| Offset 0x1FE |
| ┌─────────────────────────────────────────────────────────────────┐ |
| │ BOOT SIGNATURE │ |
| │ - Bytes: 0x55, 0xAA │ |
| └─────────────────────────────────────────────────────────────────┘ |
| Offset 0x200 (512 bytes total) |
| |
+-----------------------------------------------------------------------+
4.2 Key Components
| Component | Purpose | Location |
|---|---|---|
| Initialization | Set up CPU state for reliable execution | Bytes 0-20 |
| Print routine | Output characters via BIOS | Bytes 20-50 |
| Main logic | Display messages, handle input | Bytes 50-200 |
| Data section | String constants | Bytes 200-510 |
| Boot signature | 0x55AA magic number | Bytes 510-511 |
4.3 Data Structures
+-----------------------------------------------------------------------+
| BOOT SECTOR DATA LAYOUT |
+-----------------------------------------------------------------------+
| |
| String format: Null-terminated ASCII |
| ┌─────────────────────────────────────────────────────────────────┐ |
| │ 'H' 'e' 'l' 'l' 'o' ' ' 'W' 'o' 'r' 'l' 'd' '!' 0x00 │ |
| │ 48 65 6C 6C 6F 20 57 6F 72 6C 64 21 00 │ |
| └─────────────────────────────────────────────────────────────────┘ |
| |
| BIOS Parameter Block (if emulating floppy): |
| ┌─────────────────────────────────────────────────────────────────┐ |
| │ Offset 0x00: JMP instruction (3 bytes) │ |
| │ Offset 0x03: OEM identifier (8 bytes) │ |
| │ Offset 0x0B: Bytes per sector (2 bytes) │ |
| │ ... (more fields for FAT filesystem compatibility) │ |
| └─────────────────────────────────────────────────────────────────┘ |
| |
| Register state at entry: |
| ┌─────────────────────────────────────────────────────────────────┐ |
| │ DL = boot drive number (0x00 = floppy, 0x80+ = hard disk) │ |
| │ CS:IP = varies (0x0000:0x7C00 or 0x07C0:0x0000) │ |
| │ DS, ES, SS = undefined (MUST be set by boot sector) │ |
| │ SP = undefined (MUST be set, recommend below 0x7C00) │ |
| └─────────────────────────────────────────────────────────────────┘ |
| |
+-----------------------------------------------------------------------+
4.4 Algorithm Overview
+-----------------------------------------------------------------------+
| BOOT SECTOR EXECUTION FLOW |
+-----------------------------------------------------------------------+
| |
| 1. Entry Point (CPU jumps here from BIOS) |
| ├── Disable interrupts (CLI) - for safety during setup |
| ├── Set DS = ES = 0x0000 (or 0x07C0 for relative addressing) |
| ├── Set SS = 0x0000 |
| ├── Set SP = 0x7C00 (stack grows down from boot sector) |
| ├── Save DL (boot drive) to memory or register |
| └── Enable interrupts (STI) |
| |
| 2. Clear Screen (optional but professional) |
| ├── INT 0x10, AH=0x00, AL=0x03 (set 80x25 text mode) |
| └── Or: INT 0x10, AH=0x06, AL=0 (scroll up = clear) |
| |
| 3. Print Welcome Message |
| ├── SI = address of string |
| ├── LODSB (load byte from [SI] into AL, increment SI) |
| ├── If AL == 0, done |
| ├── INT 0x10, AH=0x0E (teletype output) |
| └── Loop back to LODSB |
| |
| 4. Wait for Keypress |
| ├── INT 0x16, AH=0x00 (wait for key) |
| ├── AL = ASCII code, AH = scan code |
| └── Print the character with INT 0x10 |
| |
| 5. Halt |
| ├── CLI (disable interrupts) |
| ├── HLT (halt CPU) |
| └── JMP to HLT (in case of NMI waking CPU) |
| |
+-----------------------------------------------------------------------+
5. Implementation Guide
5.1 Development Environment Setup
Required Tools
# Install NASM assembler
# On Ubuntu/Debian:
sudo apt install nasm
# On macOS:
brew install nasm
# On Windows:
# Download from https://www.nasm.us/
# Install QEMU emulator
# On Ubuntu/Debian:
sudo apt install qemu-system-x86
# On macOS:
brew install qemu
# Verify installations
nasm --version # Should show NASM version
qemu-system-x86_64 --version # Should show QEMU version
Optional but Recommended
# xxd for hex dumps
sudo apt install xxd # Usually pre-installed
# GDB for debugging (with QEMU's GDB stub)
sudo apt install gdb
# objdump for disassembly verification
sudo apt install binutils
5.2 Project Structure
bootloader-project/
├── boot.asm # Main boot sector source code
├── Makefile # Build automation
├── README.md # Project documentation
└── test/
└── run.sh # QEMU test script
Makefile
# Makefile for boot sector project
NASM = nasm
QEMU = qemu-system-x86_64
boot.bin: boot.asm
$(NASM) -f bin $< -o $@
run: boot.bin
$(QEMU) -drive format=raw,file=boot.bin
debug: boot.bin
$(QEMU) -drive format=raw,file=boot.bin -s -S &
gdb -ex "target remote localhost:1234" -ex "set architecture i8086"
clean:
rm -f boot.bin
.PHONY: run debug clean
5.3 The Core Question You’re Answering
“How does code start running on a computer that has no operating system, and how do you write that first code?”
This question leads to understanding:
- CPU reset behavior and initial state
- BIOS firmware’s role in hardware initialization
- The boot sector protocol (512 bytes, 0x7C00, 0xAA55)
- Real mode limitations and capabilities
- BIOS services as the only available I/O
5.4 Concepts You Must Understand First
Before implementing, verify you understand these concepts:
| Concept | Self-Assessment Question | Book Reference |
|---|---|---|
| Segment:Offset | How does 0x07C0:0x0000 equal 0x7C00? | OSDev Wiki - Segment Addressing |
| Stack Direction | Why does SP start high and decrement? | x86 Architecture Manual |
| BIOS Interrupts | What’s the difference between INT and CALL? | Ralph Brown’s Interrupt List |
| Little Endian | How is 0xAA55 stored in memory? | Any x86 book |
| Instruction Encoding | Why does [BITS 16] matter? |
NASM Documentation |
5.5 Questions to Guide Your Design
Initialization:
- What segment register values ensure correct data access?
- Where should the stack be placed and why?
- What if BIOS sets CS to 0x07C0 instead of 0x0000?
Output:
- What does INT 0x10, AH=0x0E expect in each register?
- How do you handle newlines (0x0A, 0x0D)?
- What color/attribute values are used?
Input:
- What’s the difference between INT 0x16 AH=0x00 and AH=0x01?
- How do you handle special keys (arrows, function keys)?
Structure:
- How do you ensure the binary is exactly 512 bytes?
- Where do you place data to avoid code overlap?
5.6 Thinking Exercise
Before coding, trace through this execution manually:
[BITS 16]
[ORG 0x7C00]
xor ax, ax ; AX = 0
mov ds, ax ; DS = 0
mov si, msg ; SI = address of msg
.loop:
lodsb ; AL = [DS:SI], SI++
test al, al ; Set flags based on AL
jz .done ; Jump if AL == 0
mov ah, 0x0E ; BIOS teletype
int 0x10 ; Call BIOS
jmp .loop ; Repeat
.done:
jmp $ ; Infinite loop
msg: db 'Hi', 0
Questions to answer:
- What is the physical address of
msgif ORG is 0x7C00? - What value is in SI after
mov si, msg? - How many times does the loop execute?
- What does
jmp $assemble to?
5.7 Hints in Layers
Hint 1: Starting Point (Conceptual Direction)
The boot sector must:
- Establish known-good segment register values
- Set up a stack for CALL/INT to work
- Use BIOS services since that’s all you have
- End with the magic 0xAA55 signature
Focus on getting the structure right before adding features.
Hint 2: Next Level (More Specific Guidance)
[BITS 16] ; Generate 16-bit code
[ORG 0x7C00] ; Tell assembler our load address
start:
; Segment setup
; Stack setup
; Call print routine
; Halt
print_string:
; Load character
; Check for null
; Call INT 0x10
; Loop
message: db 'Hello', 13, 10, 0 ; CR LF terminated
times 510-($-$$) db 0 ; Pad to 510 bytes
dw 0xAA55 ; Boot signature
Hint 3: Technical Details (Approach/Pseudocode)
; Segment initialization - handle both CS=0 and CS=0x07C0 cases
cli ; Disable interrupts during setup
xor ax, ax ; AX = 0
mov ds, ax ; DS = 0 (data segment)
mov es, ax ; ES = 0 (extra segment)
mov ss, ax ; SS = 0 (stack segment)
mov sp, 0x7C00 ; SP = 0x7C00 (stack below our code)
sti ; Re-enable interrupts
; Far jump to normalize CS:IP (optional but recommended)
jmp 0x0000:normalized
normalized:
; Now CS = 0, IP = offset of normalized
; Print routine
print:
mov si, message ; Point to string
.loop:
lodsb ; AL = [DS:SI++]
or al, al ; Check for null (same as test al, al)
jz .done ; If zero, we're done
mov ah, 0x0E ; BIOS teletype function
mov bh, 0 ; Page 0
int 0x10 ; Call BIOS
jmp .loop ; Next character
.done:
ret
Hint 4: Tools/Debugging (Verification Methods)
# Assemble and check size
nasm -f bin boot.asm -o boot.bin
ls -la boot.bin # Must be exactly 512 bytes
# Verify boot signature
xxd boot.bin | tail -1 # Should end with 55aa
# Disassemble to verify
ndisasm -b 16 boot.bin | head -20
# Run with QEMU debug output
qemu-system-x86_64 -drive format=raw,file=boot.bin -d int,cpu_reset
# Debug with GDB
qemu-system-x86_64 -drive format=raw,file=boot.bin -s -S &
gdb -ex "target remote :1234" -ex "set architecture i8086" \
-ex "break *0x7c00" -ex "continue"
5.8 The Interview Questions They’ll Ask
- “Why 0x7C00?”
- Historical IBM PC design choice
- Leaves 30KB below for BIOS/IVT, plenty above for loading code
- Allows boot sector to set up stack below itself
- “What’s the difference between real mode and protected mode?”
- Real mode: 16-bit, 1MB limit, no protection, segment:offset
- Protected mode: 32-bit, 4GB, memory protection, flat/segmented
- Transition requires GDT setup and CR0 modification
- “How do you read more than 512 bytes at boot?”
- Use INT 0x13 to read additional sectors
- Load to an address above the boot sector
- Jump to the loaded code
- This is what real bootloaders (GRUB) do
- “What’s the role of the A20 line?”
- 8086 had 20-bit addressing (1MB)
- 80286 added more lines but needed compatibility
- A20 gate masks address line 20 for 8086 wraparound
- Must enable A20 to access memory above 1MB
- “How would you debug a boot sector on real hardware?”
- Serial port output (if available)
- Screen output with status codes
- LED patterns (if GPIO available)
- Boot into known-good environment, then test
5.9 Books That Will Help
| Topic | Book | Chapter |
|---|---|---|
| x86 Real Mode | “Write Great Code, Volume 1” - Hyde | Ch. 4-6 |
| Boot Process | “Operating Systems: From 0 to 1” - Tu | Ch. 2 |
| BIOS Interrupts | Ralph Brown’s Interrupt List | Online Reference |
| x86 Assembly | “Programming from the Ground Up” | Ch. 3-4 |
| OS Development | OSDev Wiki | Bootloader section |
5.10 Implementation Phases
Phase 1: Minimal Boot (1-2 days)
- Get “Hello” printed on screen
- Verify boot signature works
- Test in QEMU
Phase 2: Proper Initialization (1-2 days)
- Set up all segment registers
- Establish stack
- Handle CS normalization
Phase 3: Features (2-3 days)
- Add keyboard input
- Display boot drive information
- Print formatted output (hex numbers, etc.)
Phase 4: Polish & Extension (2-3 days)
- Add color output
- Load additional sectors
- Prepare for protected mode transition
5.11 Key Implementation Decisions
| Decision | Option A | Option B | Recommendation |
|---|---|---|---|
| ORG directive | 0x7C00 | 0x0000 | Use 0x7C00 - matches physical address |
| Segment setup | DS=0 | DS=0x07C0 | Use DS=0 - simpler, matches ORG |
| Stack location | Below 0x7C00 | Above 0x7E00 | Below 0x7C00 - standard convention |
| String format | Null-terminated | Length-prefixed | Null-terminated - simpler loops |
6. Testing Strategy
Unit Testing Approach
Since this is assembly without a test framework, use verification scripts:
#!/bin/bash
# test_boot.sh
# Test 1: Size check
SIZE=$(stat -f %z boot.bin 2>/dev/null || stat -c %s boot.bin)
if [ "$SIZE" -ne 512 ]; then
echo "FAIL: Size is $SIZE, expected 512"
exit 1
fi
echo "PASS: Size is 512 bytes"
# Test 2: Boot signature
SIG=$(xxd -s 510 -l 2 -p boot.bin)
if [ "$SIG" != "55aa" ]; then
echo "FAIL: Signature is $SIG, expected 55aa"
exit 1
fi
echo "PASS: Boot signature present"
# Test 3: No 32-bit instructions (basic check)
if ndisasm -b 16 boot.bin | grep -q "o32"; then
echo "WARN: 32-bit operand override found"
fi
echo "All tests passed!"
Integration Testing
Test in QEMU with various options:
# Standard boot
qemu-system-x86_64 -drive format=raw,file=boot.bin
# Boot as floppy
qemu-system-x86_64 -fda boot.bin
# Boot with serial output (for debugging)
qemu-system-x86_64 -drive format=raw,file=boot.bin \
-serial stdio -display none
# Test with different memory sizes
qemu-system-x86_64 -drive format=raw,file=boot.bin -m 64M
Real Hardware Testing
# Write to USB drive (DANGEROUS - verify device!)
# First, identify your USB device
lsblk
# Then write (replace /dev/sdX with your device)
sudo dd if=boot.bin of=/dev/sdX bs=512 count=1
sudo sync
# Boot computer from USB
7. Common Pitfalls & Debugging
Pitfall 1: Segment Register Assumptions
Symptom: Code works in some emulators but not others, or not on real hardware.
Cause: Assuming segment registers are initialized by BIOS.
Fix:
; ALWAYS initialize segment registers
xor ax, ax
mov ds, ax
mov es, ax
mov ss, ax
mov sp, 0x7C00
Pitfall 2: Forgetting the Boot Signature
Symptom: “No bootable device” error.
Cause: Missing or incorrect 0xAA55 signature.
Fix:
times 510-($-$$) db 0 ; Pad with zeros
dw 0xAA55 ; Magic number (little endian!)
Pitfall 3: Stack Overflow into Code
Symptom: Random crashes, code corruption.
Cause: Stack grows into boot sector code.
Fix:
; Place stack below boot sector
mov sp, 0x7C00 ; Stack at 0x7C00, grows DOWN to 0x0500
; Leave ~30KB for stack before hitting BIOS data
Pitfall 4: Wrong Addressing Mode
Symptom: Prints garbage or wrong data.
Cause: Using [msg] when you need msg, or vice versa.
Fix:
mov si, msg ; SI = address of msg (correct)
; NOT: mov si, [msg] ; SI = first bytes of msg (wrong!)
; When you need the value:
mov al, [si] ; AL = byte at address SI
Pitfall 5: Not Handling CS Variants
Symptom: Works in QEMU, fails on real hardware.
Cause: Some BIOSes use CS:IP = 0x07C0:0x0000 instead of 0x0000:0x7C00.
Fix:
; Normalize CS to 0
jmp 0x0000:start_normalized
start_normalized:
; Now CS is definitely 0
Debugging Techniques
# 1. QEMU monitor (Ctrl+Alt+2 in QEMU window)
info registers # Show all registers
x/10i 0x7c00 # Disassemble at 0x7C00
xp/10x 0x7c00 # Show hex dump
# 2. GDB remote debugging
qemu-system-x86_64 -drive format=raw,file=boot.bin -s -S &
gdb
(gdb) target remote :1234
(gdb) set architecture i8086
(gdb) break *0x7c00
(gdb) continue
(gdb) info registers
(gdb) x/10i $pc
# 3. Trace interrupts
qemu-system-x86_64 -drive format=raw,file=boot.bin -d int
# 4. Serial debugging (add to boot sector)
; Output to COM1 port 0x3F8
mov dx, 0x3F8
mov al, 'X'
out dx, al
8. Extensions & Challenges
Extension 1: Load a Second Stage (Intermediate)
Expand your bootloader to load additional sectors from disk:
; Load sectors 2-5 to address 0x7E00
mov ah, 0x02 ; Read sectors
mov al, 4 ; Number of sectors
mov ch, 0 ; Cylinder 0
mov cl, 2 ; Start at sector 2
mov dh, 0 ; Head 0
mov dl, [boot_drive]; Boot drive
mov bx, 0x7E00 ; Load address
int 0x13 ; Call BIOS
jc disk_error ; Check for error
jmp 0x7E00 ; Jump to loaded code
Extension 2: Graphical Output (Intermediate)
Use VGA mode 0x13 (320x200, 256 colors):
; Set VGA mode
mov ax, 0x0013
int 0x10
; Direct framebuffer access at 0xA000:0x0000
mov ax, 0xA000
mov es, ax
xor di, di
mov al, 0x04 ; Color (red)
mov cx, 64000 ; 320 * 200 pixels
rep stosb ; Fill screen
Extension 3: Memory Map Query (Advanced)
Get the memory map for kernel development:
; INT 0x15, EAX=0xE820 - Get memory map
mov di, memory_map
xor ebx, ebx ; Continuation value
.e820_loop:
mov eax, 0xE820
mov ecx, 24 ; Entry size
mov edx, 0x534D4150 ; 'SMAP' signature
int 0x15
jc .e820_done
add di, 24
test ebx, ebx
jnz .e820_loop
.e820_done:
Extension 4: A20 Line Enable (Advanced)
Enable addressing above 1MB:
; Try BIOS method
mov ax, 0x2401
int 0x15
jnc .a20_done
; Try keyboard controller method
call .wait_kbd
mov al, 0xAD ; Disable keyboard
out 0x64, al
call .wait_kbd
mov al, 0xD0 ; Read output port
out 0x64, al
call .wait_kbd_data
in al, 0x60
push ax
call .wait_kbd
mov al, 0xD1 ; Write output port
out 0x64, al
call .wait_kbd
pop ax
or al, 2 ; Set A20 bit
out 0x60, al
call .wait_kbd
mov al, 0xAE ; Enable keyboard
out 0x64, al
call .wait_kbd
.a20_done:
9. Real-World Connections
How This Relates to Professional Software
| This Project | Real-World Equivalent |
|---|---|
| Boot sector structure | GRUB Stage 1 (first 512 bytes) |
| BIOS interrupt usage | Early Linux kernel boot code |
| Loading additional sectors | GRUB Stage 1.5/2 loading |
| Screen output | Early boot messages |
| Hardware detection | BIOS data area parsing |
Industry Applications
- BIOS/Firmware Development: Companies like AMI, Phoenix, Insyde
- Bootloader Development: GRUB, syslinux, Windows Boot Manager
- Security Research: Bootkit analysis, secure boot bypass research
- Embedded Systems: Industrial PCs, kiosks, specialized hardware
- Virtualization: Hypervisor boot code (Xen, KVM startup)
Modern Relevance
While UEFI is replacing BIOS, understanding legacy boot is still valuable:
- Many systems still use Legacy/CSM boot
- UEFI boot follows similar conceptual patterns
- Debugging boot issues requires understanding both
- Embedded systems often use BIOS-style boot
- Virtual machines frequently emulate BIOS boot
10. Resources
Official Documentation
Tutorials and Guides
- Writing a Simple Operating System from Scratch - Nick Blundell
- OSDev Wiki - Bootloader
- Real Mode Assembly Tutorial
Reference Materials
Tools
- NASM Assembler
- QEMU Emulator
- Bochs Emulator - More accurate BIOS emulation
- GDB Debugger
11. Self-Assessment Checklist
Before considering this project complete, verify:
Understanding
- I can explain what happens from power-on to my code executing
- I understand segment:offset addressing and can calculate physical addresses
- I know why the boot sector must be 512 bytes with 0xAA55 signature
- I can describe what each BIOS interrupt I use does
- I understand why segment registers must be initialized manually
Implementation
- My boot sector is exactly 512 bytes
- The boot signature 0xAA55 is at the correct location
- All segment registers are properly initialized
- The stack is set up correctly and doesn’t overlap code
- My code works in QEMU
Skills
- I can assemble code with NASM and verify the binary
- I can debug boot code using QEMU and GDB
- I can read and interpret hex dumps of my binary
- I can modify the code to add new features
- I understand how to extend this into a real bootloader
12. Submission / Completion Criteria
Your project is complete when:
- Binary Requirements
boot.binis exactly 512 bytes- Last two bytes are 0x55, 0xAA (in that order in the file)
- First instruction is at offset 0 (or after a JMP for BPB)
- Functional Requirements
- Boots successfully in QEMU
- Displays at least one message on screen
- Handles keyboard input (wait for key or read key)
- Halts cleanly (no crash or random execution)
- Code Quality
- Assembly is well-commented
- Each section has a clear purpose
- No dead or unreachable code
- Documentation
- README explains how to build and run
- Key design decisions are documented
- Memory map is described
Verification Commands
# Build
nasm -f bin boot.asm -o boot.bin
# Verify size
[ $(stat -c %s boot.bin) -eq 512 ] && echo "Size OK"
# Verify signature
[ "$(xxd -s 510 -l 2 -p boot.bin)" = "55aa" ] && echo "Signature OK"
# Run
qemu-system-x86_64 -drive format=raw,file=boot.bin
Congratulations! Completing this project means you understand how computers boot at the lowest level. This is knowledge that separates systems programmers from application developers. You’re now ready to build on this foundation by transitioning to protected mode (Project 6) and eventually building a complete operating system kernel.