Project 1: The 512-Byte Hello World Bootloader

Write the very first code that runs when a computer boots - a minimal bootloader that displays “Hello, Bare Metal!” using BIOS interrupts, all within exactly 512 bytes.

Quick Reference

Attribute Value
Difficulty ★★☆☆☆ Beginner
Time Estimate Weekend (6-12 hours)
Language x86 Assembly (NASM)
Prerequisites Basic hexadecimal, willingness to read x86 assembly
Key Topics BIOS boot process, Real Mode, MBR structure, INT 10h video services, segment:offset addressing

1. Learning Objectives

After completing this project, you will be able to:

  • Explain what happens in the first milliseconds after a computer powers on
  • Write x86 real mode assembly that executes without any operating system
  • Understand the Master Boot Record (MBR) structure and boot signature requirements
  • Use BIOS interrupts for basic I/O (video output via INT 10h)
  • Debug bare-metal code using QEMU and its monitor
  • Initialize CPU segment registers and set up a stack in real mode
  • Create bootable disk images and test them in emulators

2. Theoretical Foundation

2.1 Core Concepts

The Reset Vector and CPU Initialization

When you press the power button, the CPU doesn’t start executing your code immediately. Here’s the sequence:

  1. Power Supply Stabilization: The PSU converts AC to DC (3.3V, 5V, 12V rails) and waits for voltages to stabilize before sending the “Power Good” signal
  2. CPU Reset: All registers are set to known values. The instruction pointer points to the Reset Vector (0xFFFFFFF0 on x86)
  3. BIOS Execution: The CPU begins executing BIOS code from firmware ROM
  4. POST (Power-On Self Test): BIOS tests CPU, memory, and basic hardware
  5. Boot Device Search: BIOS reads the first sector (512 bytes) from the boot device to address 0x7C00
  6. Boot Signature Check: BIOS verifies bytes 510-511 are 0x55 0xAA
  7. Handoff: BIOS jumps to 0x7C00 - YOUR CODE NOW RUNS!
Power Button → PSU Stabilizes → CPU Reset → BIOS POST → Load MBR → Check 0xAA55 → JMP 0x7C00
                                                                                      ↑
                                                                              Your bootloader!

x86 Real Mode

When the BIOS hands control to your bootloader, the CPU is in 16-bit Real Mode - a compatibility mode that behaves like the original 8086 from 1978.

Real Mode Characteristics:

  • 16-bit registers (AX, BX, CX, DX, SI, DI, BP, SP)
  • Segment registers (CS, DS, ES, SS) for memory addressing
  • Maximum 1MB addressable memory (20-bit addresses)
  • No memory protection - any code can access any memory
  • No privilege levels - everything runs at maximum privilege
  • Direct hardware access via IN/OUT instructions
  • BIOS interrupts available for I/O

Memory Addressing (Segment:Offset):

Physical Address = (Segment × 16) + Offset

Example: CS=0x07C0, IP=0x0000
         Physical = 0x07C0 × 16 + 0x0000 = 0x7C00

Example: DS=0x0000, SI=0x7C50
         Physical = 0x0000 × 16 + 0x7C50 = 0x7C50

The Master Boot Record (MBR)

The MBR is the first 512 bytes of a bootable disk:

┌─────────────────────────────────────────────────────────────┐
│ Offset │ Size  │ Content                                    │
├────────┼───────┼────────────────────────────────────────────┤
│ 0x000  │ 446   │ Bootstrap Code (YOUR BOOTLOADER)           │
│ 0x1BE  │ 16    │ Partition Entry 1 (optional)               │
│ 0x1CE  │ 16    │ Partition Entry 2 (optional)               │
│ 0x1DE  │ 16    │ Partition Entry 3 (optional)               │
│ 0x1EE  │ 16    │ Partition Entry 4 (optional)               │
│ 0x1FE  │ 2     │ Boot Signature (0x55, 0xAA)                │
└────────┴───────┴────────────────────────────────────────────┘

Critical Rule: Without the 0xAA55 signature at bytes 510-511, the BIOS will NOT execute your code.

2.2 Why This Matters

This project matters because:

  1. Security Foundation: Every security vulnerability at boot level is catastrophic. Rootkits, bootkits, and firmware malware can hide from all OS-level security. Understanding boot is understanding the root of trust.

  2. Cloud Computing: Every EC2 instance, every container host, every virtual machine boots. Understanding this is understanding infrastructure at its core.

  3. Embedded Systems: Your car’s ECU, your router, your smart TV - all boot through code someone wrote. This is the foundation of embedded programming.

  4. Debugging Power: When Windows won’t start, understanding boot is the difference between fixing it and reinstalling. You become the person who can solve “impossible” problems.

  5. Interview Advantage: “I wrote a bootloader from scratch” instantly sets you apart. It demonstrates deep systems understanding that most programmers never achieve.

2.3 Historical Context

1981 - The Original IBM PC: The BIOS boot process was designed for the IBM PC. The 512-byte sector size came from standard floppy disk formats. The 0x7C00 load address was chosen to leave room for BIOS data structures (IVT at 0x0000, BDA at 0x0400) and provide space for the bootloader’s stack.

Why 0x7C00?: This address equals 32KB - 1KB. IBM engineers wanted the bootloader at a 1KB boundary, leaving 31KB below for BIOS use and allowing the bootloader to use the memory immediately below (from 0x0500 to 0x7BFF, about 30KB) for its own purposes.

Why 0xAA55?: This “magic number” (10101010 01010101 in binary) was chosen as a simple pattern that’s unlikely to appear by accident in uninitialized memory. The byte order (0x55 first, then 0xAA) is due to x86’s little-endian architecture.

2.4 Common Misconceptions

Misconception 1: “The bootloader runs in a normal environment”

  • Reality: There is NO operating system, NO standard library, NO runtime. You have raw CPU instructions and BIOS services. printf() doesn’t exist.

Misconception 2: “I can just write C code”

  • Reality: C code needs a stack (which you must set up), initialized data segments (which don’t exist), and typically a runtime. Pure C bootloaders require an assembly stub.

Misconception 3: “Memory addresses are virtual”

  • Reality: In real mode, all addresses are physical. There’s no MMU, no paging, no translation. Address 0x7C00 is physical RAM location 0x7C00.

Misconception 4: “The segment registers are initialized”

  • Reality: BIOS does NOT guarantee segment register values. DS, ES, SS might contain garbage. You MUST initialize them.

Misconception 5: “Debugging is like normal debugging”

  • Reality: There’s no GDB, no stack traces, no printf debugging. A bug typically manifests as a frozen screen or immediate reboot. You debug with QEMU monitor, hex dumps, and careful reasoning.

3. Project Specification

3.1 What You Will Build

A complete bootloader that:

  1. Fits in exactly 512 bytes
  2. Sets up segment registers and stack correctly
  3. Prints “Hello, Bare Metal!” to the screen using BIOS video services
  4. Halts the CPU cleanly after printing
  5. Contains the required 0xAA55 boot signature

3.2 Functional Requirements

  • Bootloader must boot successfully in QEMU
  • Display the message “Hello, Bare Metal!” on screen
  • Cursor should remain visible after the message
  • System should halt (not reboot or execute garbage)
  • Must work with qemu-system-x86_64 -drive format=raw,file=boot.bin

3.3 Non-Functional Requirements

  • File size must be EXACTLY 512 bytes
  • Last two bytes must be 0x55 0xAA (little-endian 0xAA55)
  • Code must be position-independent OR use ORG 0x7C00 correctly
  • Should work on QEMU, Bochs, and VirtualBox
  • Should theoretically work on real hardware (Legacy/CSM boot)

3.4 Example Usage / Output

# Assemble the bootloader
$ nasm -f bin boot.asm -o boot.bin

# Verify size and signature
$ ls -l boot.bin
-rw-r--r-- 1 user user 512 Jan 15 10:30 boot.bin

$ hexdump -C boot.bin | tail -n 1
000001f0  00 00 00 00 00 00 00 00  00 00 00 00 00 00 55 aa

# Run in QEMU
$ qemu-system-x86_64 -drive format=raw,file=boot.bin

Expected QEMU window:

Hello, Bare Metal!
_

The cursor blinks on a black screen with your message.

3.5 Real World Outcome

When you complete this project:

  • You will have written the very first code that runs on a (virtual) machine
  • You will understand what “bare metal” programming actually means
  • You will be able to explain the boot process to anyone
  • You will have a foundation for all subsequent bootloader projects
  • You will have resume-worthy evidence of low-level systems knowledge

4. Solution Architecture

4.1 High-Level Design

┌─────────────────────────────────────────────────────────────┐
│                     BOOT SEQUENCE                            │
├─────────────────────────────────────────────────────────────┤
│                                                              │
│  BIOS                                                        │
│    │                                                         │
│    ▼                                                         │
│  ┌───────────────────────────────────────────────────────┐  │
│  │            BOOTLOADER (512 bytes at 0x7C00)            │  │
│  ├───────────────────────────────────────────────────────┤  │
│  │                                                         │  │
│  │  1. Initialize Segment Registers (DS=ES=SS=0)          │  │
│  │     └── XOR AX, AX; MOV DS, AX; MOV ES, AX; etc.       │  │
│  │                                                         │  │
│  │  2. Set Up Stack (SS:SP = 0:0x7C00)                    │  │
│  │     └── MOV SP, 0x7C00 (stack grows DOWN)              │  │
│  │                                                         │  │
│  │  3. Print Loop                                          │  │
│  │     └── Load message address into SI                   │  │
│  │     └── For each character:                            │  │
│  │         └── LODSB (load byte, advance SI)              │  │
│  │         └── Check for null terminator                  │  │
│  │         └── INT 10h, AH=0Eh (teletype output)          │  │
│  │                                                         │  │
│  │  4. Halt                                                │  │
│  │     └── HLT; JMP $ (infinite loop)                     │  │
│  │                                                         │  │
│  │  5. Data                                                │  │
│  │     └── "Hello, Bare Metal!", 0                        │  │
│  │                                                         │  │
│  │  6. Padding + Signature                                 │  │
│  │     └── TIMES 510-($-$$) DB 0                          │  │
│  │     └── DW 0xAA55                                       │  │
│  │                                                         │  │
│  └───────────────────────────────────────────────────────┘  │
│                                                              │
└─────────────────────────────────────────────────────────────┘

4.2 Key Components

  1. Segment Initialization Block: Sets DS, ES, SS to known values (typically 0)
  2. Stack Setup: Points SP to a safe location below 0x7C00
  3. Print Routine: Uses LODSB instruction to iterate through null-terminated string
  4. BIOS Video Call: INT 10h with AH=0Eh for teletype output
  5. Halt Loop: HLT instruction in an infinite loop
  6. Message Data: Null-terminated ASCII string
  7. Padding: Zeros to reach byte 510
  8. Boot Signature: The magic 0xAA55 word

4.3 Data Structures

Message String:

msg: db 'Hello, Bare Metal!', 0
     ;   H  e  l  l  o  ,     B  a  r  e     M  e  t  a  l  !  \0
     ; 48 65 6C 6C 6F 2C 20 42 61 72 65 20 4D 65 74 61 6C 21 00

MBR Layout:

Offset    Contents
0x0000    JMP instruction (skip over data if any) or start of code
...       Your bootloader code (~440 bytes usable)
0x01F0    Message string (if placed at end)
0x01FE    0x55 (first byte of signature)
0x01FF    0xAA (second byte of signature)

4.4 Algorithm Overview

ALGORITHM: Print String in Real Mode

1. CLI (disable interrupts while setting up stack)
2. XOR AX, AX
3. MOV DS, AX    ; DS = 0 for data access
4. MOV ES, AX    ; ES = 0
5. MOV SS, AX    ; SS = 0
6. MOV SP, 0x7C00 ; Stack below bootloader
7. STI (re-enable interrupts)

8. MOV SI, message_address

9. LOOP:
   a. LODSB           ; AL = [DS:SI], SI++
   b. OR AL, AL       ; Test for zero
   c. JZ DONE         ; If zero, exit loop
   d. MOV AH, 0x0E    ; BIOS teletype function
   e. MOV BH, 0       ; Page 0
   f. INT 0x10        ; Call BIOS
   g. JMP LOOP

10. DONE:
    a. HLT            ; Halt CPU
    b. JMP DONE       ; In case of interrupt, halt again

5. Implementation Guide

5.1 Development Environment Setup

Required Tools:

# macOS
brew install nasm qemu

# Ubuntu/Debian
sudo apt update
sudo apt install nasm qemu-system-x86

# Fedora/RHEL
sudo dnf install nasm qemu-system-x86

# Verify installation
nasm -version    # Should show NASM version 2.14+
qemu-system-x86_64 --version    # Should show QEMU version 6.0+

Optional Tools:

# Hex viewer/editor
brew install hexdump    # macOS (usually pre-installed)
apt install xxd         # Linux

# Bochs (more accurate than QEMU, better for debugging)
brew install bochs
apt install bochs bochs-x

5.2 Project Structure

bootloader-hello/
├── boot.asm           # Main bootloader source
├── Makefile           # Build automation
├── boot.bin           # Compiled binary (generated)
└── README.md          # Your notes

Simple Makefile:

all: boot.bin

boot.bin: boot.asm
	nasm -f bin boot.asm -o boot.bin

run: boot.bin
	qemu-system-x86_64 -drive format=raw,file=boot.bin

debug: boot.bin
	qemu-system-x86_64 -drive format=raw,file=boot.bin -monitor stdio

clean:
	rm -f boot.bin

.PHONY: all run debug clean

5.3 The Core Question You’re Answering

“What is the very first code that runs when a computer boots, and how does it work without an operating system?”

This project answers the fundamental question of how a computer transitions from powered-off hardware to running code. You’ll discover:

  • How the BIOS/firmware finds and executes your code
  • Why the first sector of a disk is special (Master Boot Record)
  • How to interact with hardware when there’s no OS, no standard library, and no runtime
  • What “bare metal” programming actually means in practice

This is the foundation of understanding every boot process, from embedded systems to modern UEFI systems.

5.4 Concepts You Must Understand First

Before writing code, ensure you can answer these self-assessment questions:

Memory Addressing at Boot (0x7C00):

  • Q: Why does the BIOS load the bootloader to 0x7C00 specifically?
  • A: Historical convention from IBM PC (32KB - 1KB for boot sector)

x86 Real Mode Operation:

  • Q: What are the limitations of real mode?
  • A: 1MB addressable memory, 16-bit registers, no memory protection

Segment:Offset Addressing:

  • Q: What’s the physical address of 0x07C0:0x0000?
  • A: (0x07C0 × 16) + 0x0000 = 0x7C00

BIOS Interrupts:

  • Q: How do you print a character using BIOS?
  • A: INT 10h with AH=0Eh, character in AL

MBR Structure:

  • Q: Why is the boot signature 0xAA55?
  • A: BIOS checks these bytes to verify sector is bootable (little-endian: bytes are 0x55, 0xAA)

5.5 Questions to Guide Your Design

Memory and Addressing:

  1. How does ORG 0x7C00 affect label addresses in your code?
  2. Why must you initialize segment registers (DS, ES, SS)?
  3. Where should you place your stack, and why?

BIOS Interaction:

  1. How do you print characters using INT 10h?
  2. What state does the BIOS leave the system in when it jumps to 0x7C00?

Code Structure:

  1. How do you ensure your bootloader is exactly 512 bytes?
  2. Should your bootloader halt, hang, or loop after printing?

Debugging:

  1. If nothing appears on screen, what could be wrong?

5.6 Thinking Exercise

Before writing any code, trace through the boot process mentally:

  1. Power-On (T=0): CPU starts executing BIOS code from firmware ROM

  2. BIOS Boot Search (T=2 seconds):
    • BIOS reads first sector (512 bytes) to memory address 0x7C00
    • BIOS checks if bytes 510-511 equal 0x55, 0xAA
    • If yes: Jump to 0x7C00
  3. Your Code Executes (T=2.1 seconds):
    • CPU instruction pointer (IP) = 0x7C00, Code Segment (CS) = 0x0000
    • If your first instruction is segment setup, trace what each register becomes
    • If msg is at offset 30 from start, SI will load 0x7C00 + 30 = 0x7C1E
  4. Printing “Hello”:
    • You load ‘H’ (0x48) into AL
    • You call INT 10h with AH=0Eh
    • BIOS writes ‘H’ to video memory, cursor advances

Draw the memory map:

Address Range    | Contents
-----------------|---------------------------------
0x0000 - 0x03FF  | BIOS Interrupt Vector Table
0x0400 - 0x04FF  | BIOS Data Area
0x0500 - 0x7BFF  | Free (your stack can go here)
0x7C00 - 0x7DFF  | Your bootloader (512 bytes)
0x7E00 - 0x9FFFF | Free
0xA0000 - 0xFFFFF| Video memory, BIOS ROM, etc.

5.7 Hints in Layers

Use these progressive hints only when stuck. Try each level before moving to the next.

Hint 1: Starting with the Skeleton

Your bootloader needs this basic structure:

[BITS 16]           ; We're in 16-bit Real Mode
[ORG 0x7C00]        ; BIOS loads us here

start:
    ; Initialize segment registers here
    ; Set up stack here
    ; Your code to print message here
    ; Infinite loop or halt here

message: db 'Hello, Bare Metal!', 0

; Padding and boot signature here

Hint 2: Setting Up Segments

At boot, segment registers have undefined values:

start:
    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  ; Stack grows DOWN from 0x7C00

Hint 3: Printing Loop

Print each character using INT 10h:

    mov si, message    ; SI points to string

print_loop:
    lodsb              ; Load byte at [SI] into AL, increment SI
    cmp al, 0          ; Check for null terminator
    je done            ; If zero, we're done

    mov ah, 0x0E       ; Teletype output function
    mov bh, 0          ; Page 0
    int 0x10           ; Call BIOS video service

    jmp print_loop     ; Repeat for next character

done:
    hlt                ; Halt the CPU
    jmp done           ; If interrupts wake CPU, halt again

Hint 4: Padding and Signature

Ensure exactly 512 bytes with signature at end:

times 510-($-$$) db 0    ; Pad with zeros to byte 510
dw 0xAA55                ; Boot signature (little-endian)

Understanding the syntax:

  • $: Current position
  • $$: Start of section (with ORG, equals 0x7C00)
  • $-$$: Bytes written so far
  • 510-($-$$): Remaining bytes to reach 510
  • times: Repeat instruction
  • dw 0xAA55: Write word (2 bytes) in little-endian

Hint 5: Debugging with QEMU

# Does it boot?
qemu-system-x86_64 -drive format=raw,file=boot.bin

# Is code running? (add at start: jmp start)
# If QEMU hangs, code is executing

# Test single character:
start:
    mov ah, 0x0E
    mov al, 'A'
    int 0x10
    jmp $

# Inspect with QEMU monitor:
qemu-system-x86_64 -drive format=raw,file=boot.bin -monitor stdio
(qemu) info registers
(qemu) x/20i 0x7C00      # Disassemble
(qemu) x/512xb 0x7C00    # Hex dump

Hint 6: Complete Working Example (Last Resort)

[BITS 16]
[ORG 0x7C00]

start:
    ; Initialize segments
    cli                 ; Disable interrupts while setting up stack
    xor ax, ax
    mov ds, ax
    mov es, ax
    mov ss, ax
    mov sp, 0x7C00      ; Stack grows down from bootloader
    sti                 ; Re-enable interrupts

    ; Print message
    mov si, msg
.loop:
    lodsb
    or al, al           ; Check for null (sets ZF if AL=0)
    jz .done
    mov ah, 0x0E        ; Teletype function
    mov bh, 0           ; Page 0
    mov bl, 0x07        ; Light gray color
    int 0x10
    jmp .loop

.done:
    hlt
    jmp .done

msg: db 'Hello, Bare Metal!', 0

times 510-($-$$) db 0
dw 0xAA55

5.8 The Interview Questions They’ll Ask

Basic Understanding

  1. “What is the boot signature and why is it required?”
    • Good Answer: 0xAA55 (stored as 0x55 0xAA in little-endian) at bytes 510-511. BIOS checks this to verify the sector is bootable. Without it, BIOS won’t execute the code.
    • Red Flag: “It’s just a magic number” (no understanding of purpose)
  2. “Why is the bootloader loaded at 0x7C00 specifically?”
    • Good Answer: Historical convention from original IBM PC. Located 32KB - 1KB into memory, leaving room below for BIOS data structures.
    • Red Flag: “I don’t know, that’s just where it goes”
  3. “What CPU mode does the bootloader run in?”
    • Good Answer: 16-bit Real Mode. No memory protection, only 1MB addressable, segment:offset addressing.
    • Follow-up: “How do you switch to Protected Mode?” (Load GDT, set PE bit in CR0)

Technical Details

  1. “Explain segment:offset addressing. What’s the physical address of 0x07C0:0x0010?”
    • Good Answer: Physical = (Segment × 16) + Offset = (0x07C0 × 16) + 0x0010 = 0x7C10
    • Insight: Multiple segment:offset pairs can point to same physical address
  2. “How do you print to the screen in a bootloader?”
    • Good Answer: BIOS INT 10h, AH=0Eh (teletype output). Or write directly to video memory at 0xB8000.
    • Red Flag: “Use printf” (shows lack of bare-metal understanding)
  3. “What happens if you forget to set the Data Segment (DS) register?”
    • Good Answer: DS might contain garbage, so data reads will access wrong memory, causing incorrect behavior or crashes.
    • Red Flag: “It will still work” (it won’t)
  4. “Why do you need ORG 0x7C00 directive?”
    • Good Answer: Tells assembler where code will run, so it calculates label addresses correctly. Without it, addresses are wrong.
    • Alternative: Set segment registers to 0x07C0 and use ORG 0.

Problem-Solving

  1. “Your bootloader compiles but nothing appears on screen. Debugging steps?”
    • Good Answer:
      1. Verify 512-byte size and 0xAA55 signature with hexdump
      2. Add infinite loop at start to verify execution
      3. Test INT 10h with single character
      4. Check DS register initialization
      5. Use QEMU monitor to inspect registers
  2. “How would you load a second stage bootloader from disk?”
    • Good Answer: Use BIOS INT 13h (disk services). AH=02h reads sectors. Specify cylinder, head, sector, and destination buffer.
  3. “What’s the maximum size of a single-stage bootloader?”
    • Good Answer: 446 bytes of code (with partition table), or 510 bytes (without). Must reserve 2 bytes for 0xAA55.

5.9 Books That Will Help

Topic Book Specific Chapter/Section Why It Helps
x86 Assembly Basics “Low-Level Programming” by Igor Zhirkov Chapter 2: Assembly Language Covers registers, instructions, NASM syntax
Real Mode and Segmentation “PC Assembly Language” by Paul A. Carter Chapter 1 (Sections 1.3-1.4) Explains segment:offset with examples. Free online.
BIOS Interrupts “The Art of Assembly Language” by Randall Hyde Chapter 12: Interrupts How software interrupts work
Boot Process “Computer Systems: A Programmer’s Perspective” by Bryant & O’Hallaron Chapter 7, Section 7.9 How code gets loaded and executed
MBR and Disk Layout “Operating Systems: Three Easy Pieces” by Arpaci-Dusseau Chapter 40 Disk sectors and boot sectors
Practical Tutorial “Writing a Simple Operating System from Scratch” by Nick Blundell Pages 1-20 Step-by-step bootloader with diagrams

Online Resources:

5.10 Implementation Phases

Phase 1: Minimal Boot (2 hours)

  • Create boot.asm with just signature
  • Verify QEMU boots without “No bootable device” error
  • Milestone: QEMU window opens

Phase 2: Code Execution (2 hours)

  • Add infinite loop at 0x7C00
  • Verify QEMU hangs (not reboots)
  • Milestone: Code is executing

Phase 3: Single Character (2 hours)

  • Add INT 10h to print ‘A’
  • Milestone: ‘A’ appears on screen

Phase 4: String Printing (2 hours)

  • Add message and print loop
  • Initialize segment registers
  • Milestone: “Hello, Bare Metal!” appears

Phase 5: Polish (2 hours)

  • Add clean halt loop
  • Test on multiple emulators
  • Document and comment code

5.11 Key Implementation Decisions

  1. ORG 0x7C00 vs Segment Setup: Using ORG 0x7C00 with DS=0 is simpler than ORG 0 with DS=0x07C0. Choose ORG 0x7C00.

  2. Stack Location: Stack at 0x7C00 growing down is safe (30KB of space). Alternative: 0x9FC00 (just below EBDA).

  3. Interrupt State: CLI before stack setup prevents interrupts during vulnerable period. STI after is optional for this project.

  4. Color Attribute: BL=0x07 (light gray on black) is safe default. BL=0x0F (white on black) is brighter.

  5. Halt vs Loop: HLT in a loop is power-efficient. JMP $ works but wastes CPU cycles.


6. Testing Strategy

6.1 Unit Testing

Since bootloaders can’t use traditional unit tests, use incremental verification:

; Test 1: Signature only
times 510 db 0
dw 0xAA55
; Expected: QEMU opens window

; Test 2: Infinite loop
start: jmp start
times 510-($-$$) db 0
dw 0xAA55
; Expected: QEMU hangs (code executing)

; Test 3: Single char
mov ah, 0x0E
mov al, 'X'
int 0x10
jmp $
; Expected: 'X' on screen

6.2 Integration Testing

# Test 1: File size
ls -l boot.bin | awk '{print $5}'
# Expected: 512

# Test 2: Signature check
xxd boot.bin | tail -n 1 | grep "55 aa"
# Expected: Match found

# Test 3: QEMU boot
qemu-system-x86_64 -drive format=raw,file=boot.bin 2>&1 | grep -i error
# Expected: No errors

# Test 4: Alternative emulators
qemu-system-i386 -drive format=raw,file=boot.bin
bochs -q -f bochsrc.txt

6.3 Debugging Techniques

QEMU Monitor Commands:

qemu-system-x86_64 -drive format=raw,file=boot.bin -monitor stdio

(qemu) info registers      # Show CPU state
(qemu) x/20i 0x7C00        # Disassemble 20 instructions
(qemu) x/512xb 0x7C00      # Hex dump bootloader
(qemu) xp/2xb 0x7DFE       # Check signature location

GDB with QEMU:

# Terminal 1
qemu-system-x86_64 -s -S -drive format=raw,file=boot.bin

# Terminal 2
gdb
(gdb) target remote :1234
(gdb) break *0x7C00
(gdb) continue
(gdb) info registers
(gdb) x/20i $eip

Bochs Debugging:

bochs -q
<bochs:1> break 0x7C00
<bochs:2> c
<bochs:3> r          # Show registers
<bochs:4> u 0x7C00   # Disassemble

7. Common Pitfalls & Debugging

Problem 1: Black screen - nothing happens, QEMU hangs

  • Root Cause: Code executing but not producing output
  • Fix: Add infinite loop as last instruction
  • Quick Test: mov byte [0xB8000], 'X' should show X in corner

Problem 2: QEMU says ‘No bootable device’

  • Root Cause: Missing or incorrect boot signature
  • Fix: hexdump -C boot.bin | tail -n 1 must show 55 aa
  • Quick Test: ls -l boot.bin must show 512 bytes

Problem 3: Garbage characters instead of message

  • Root Cause: DS not initialized
  • Fix: Add xor ax, ax; mov ds, ax at start
  • Quick Test: Print single character first

Problem 4: Only first character prints

  • Root Cause: Loop logic broken or string not null-terminated
  • Fix: Ensure db 'message', 0 (trailing zero)
  • Quick Test: Check with hexdump that string ends with 00

Problem 5: System reboots immediately

  • Root Cause: No halt loop, CPU executes garbage
  • Fix: End with hlt; jmp $-1
  • Quick Test: Add HLT at every exit point

Problem 6: NASM error ‘symbol undefined’

  • Root Cause: Typo in label name
  • Fix: Generate listing file: nasm -l boot.lst boot.asm
  • Quick Test: Check case sensitivity of labels

Problem 7: File not exactly 512 bytes

  • Root Cause: Wrong padding calculation
  • Fix: Use times 510-($-$$) db 0 then dw 0xAA55
  • Quick Test: Add/remove code, recheck size

Problem 8: Works in QEMU but not real hardware

  • Root Cause: Real hardware more strict about timing/initialization
  • Fix: Test with Bochs first, then VirtualBox
  • Quick Test: Ensure Legacy/CSM mode in BIOS

8. Extensions & Challenges

Extension 1: Colored Output

  • Use BL register to set foreground/background colors
  • Colors: 0=black, 1=blue, 2=green, 4=red, 7=white, 14=yellow
  • Challenge: Print “Hello” in different colors

Extension 2: Clear Screen First

  • Use INT 10h, AH=00h to set video mode (clears screen)
  • Or INT 10h, AH=06h to scroll window

Extension 3: Print at Specific Position

  • Use INT 10h, AH=02h to set cursor position
  • Center the message on screen

Extension 4: Keyboard Input

  • Use INT 16h, AH=00h to wait for keypress
  • Print the pressed key
  • Challenge: Simple echo loop

Extension 5: Print Numbers

  • Convert binary to ASCII digits
  • Display memory address 0x7C00 as “7C00”
  • Challenge: Print in decimal instead of hex

Extension 6: Blinking Cursor

  • Write a character, wait, write space, wait
  • Challenge: Implement using PIT (timer) instead of busy loop

9. Real-World Connections

GRUB Stage 1: Your bootloader is essentially GRUB’s boot.img - the minimal code that fits in the MBR and loads the rest.

MS-DOS Boot Sector: The original DOS boot sector did exactly this - load IO.SYS from disk and execute it.

Windows Boot Manager: bootmgr is loaded by similar code in the Windows MBR (or EFI loader for UEFI systems).

Linux: The kernel’s arch/x86/boot/header.S contains similar real-mode setup code.

Secure Boot: Modern systems add cryptographic verification at this stage - the UEFI firmware validates signatures before executing boot code.


10. Resources

Primary References

  • Intel 64 and IA-32 Architectures Software Developer’s Manual, Volume 1
  • NASM Manual (https://www.nasm.us/doc/)
  • OSDev Wiki (https://wiki.osdev.org)

Online Tutorials

  • OSDev Wiki: Babystep1 (https://wiki.osdev.org/Babystep1)
  • Writing a Simple Operating System by Nick Blundell (free PDF)
  • Bran’s Kernel Development Tutorial

Source Code Examples

  • https://github.com/cfenollosa/os-tutorial
  • https://github.com/mame82/LockBootloader
  • GRUB source code: boot.S

11. Self-Assessment Checklist

Before moving to Project 2, verify:

  • Can you explain why the bootloader loads at 0x7C00?
  • Can you calculate a physical address from segment:offset?
  • Can you describe what INT 10h, AH=0Eh does?
  • Can you explain why segment registers must be initialized?
  • Can you write the bootloader without looking at hints?
  • Can you debug a non-printing bootloader systematically?
  • Can you explain the MBR structure to a colleague?
  • Can you modify the message and rebuild successfully?
  • Have you tested on at least two emulators (QEMU + Bochs/VirtualBox)?

12. Submission / Completion Criteria

Your implementation is complete when:

  • boot.bin is exactly 512 bytes
  • hexdump -C boot.bin | tail -n 1 shows 55 aa at the end
  • QEMU displays “Hello, Bare Metal!” and cursor blinks
  • System halts cleanly (doesn’t reboot)
  • Works with qemu-system-x86_64 -drive format=raw,file=boot.bin
  • Works with qemu-system-i386 -drive format=raw,file=boot.bin
  • Code is commented explaining each section
  • You can rebuild from scratch without referencing hints
  • You can explain the code to someone else

Congratulations! You’ve written the very first code that runs when a computer boots. This is the foundation of all systems programming. Proceed to Project 2 to learn about memory detection.