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

By completing this project, you will:

  1. Understand the x86 boot process: Know exactly what happens from power-on to your code executing
  2. Master 16-bit real mode: Understand segment:offset addressing, the 1MB memory limit, and CPU limitations
  3. Program using BIOS interrupts: Use INT 0x10 (video), INT 0x13 (disk), INT 0x16 (keyboard) for I/O
  4. Write position-independent code: Handle the ORG directive and understand relocation
  5. Work within extreme constraints: Fit meaningful code in exactly 512 bytes
  6. Debug at the lowest level: Use QEMU’s debugging features and manual instruction tracing
  7. 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:

  1. Every PC boots this way: Even modern UEFI systems often support legacy BIOS boot
  2. Foundation for OS development: You can’t write an OS without understanding boot
  3. Debugging boot issues: If boot fails, knowing the process helps diagnose problems
  4. Security research: Bootkits and rootkits operate at this level
  5. Embedded systems: Many x86 embedded systems use similar boot mechanisms
  6. 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:

  1. Initializes the execution environment correctly for real mode
  2. Prints a welcome message using BIOS teletype output
  3. Demonstrates segment addressing by loading data from different segments
  4. Reads keyboard input and displays typed characters
  5. Shows basic disk information (optional: loads additional sectors)
  6. 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:

  1. Working boot sector that can be written to a USB drive and boot real hardware
  2. Deep understanding of how every PC starts up
  3. Foundation for building a bootloader that loads a kernel
  4. Debugging skills for the lowest level of system software
  5. 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
# 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:

  1. What is the physical address of msg if ORG is 0x7C00?
  2. What value is in SI after mov si, msg?
  3. How many times does the loop execute?
  4. What does jmp $ assemble to?

5.7 Hints in Layers

Hint 1: Starting Point (Conceptual Direction)

The boot sector must:

  1. Establish known-good segment register values
  2. Set up a stack for CALL/INT to work
  3. Use BIOS services since that’s all you have
  4. 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

  1. “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
  2. “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
  3. “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
  4. “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
  5. “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

  1. BIOS/Firmware Development: Companies like AMI, Phoenix, Insyde
  2. Bootloader Development: GRUB, syslinux, Windows Boot Manager
  3. Security Research: Bootkit analysis, secure boot bypass research
  4. Embedded Systems: Industrial PCs, kiosks, specialized hardware
  5. 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

Reference Materials

Tools


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:

  1. Binary Requirements
    • boot.bin is 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)
  2. 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)
  3. Code Quality
    • Assembly is well-commented
    • Each section has a clear purpose
    • No dead or unreachable code
  4. 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.