Project 5: FAT12 Filesystem Bootloader

Build a bootloader that reads the FAT12 filesystem, locates a kernel file by name, loads it into memory, and jumps to it—understanding how real bootloaders like MS-DOS navigate filesystems.

Quick Reference

Attribute Value
Difficulty ★★★★☆ Expert
Time Estimate 2-3 weeks
Language x86 Assembly (NASM)
Prerequisites Project 4 (Two-Stage Bootloader), understanding of linked data structures, disk I/O basics
Key Topics FAT12 structure, BIOS Parameter Block, cluster chains, 8.3 filenames, root directory parsing

1. Learning Objectives

After completing this project, you will be able to:

  1. Parse the BIOS Parameter Block (BPB) - Read and interpret the filesystem metadata stored in the boot sector
  2. Navigate the FAT table - Follow cluster chains using 12-bit packed entries
  3. Read the root directory - Search for files using the 8.3 filename format
  4. Convert cluster numbers to sector numbers - Understand the FAT12 disk geometry
  5. Load files from a FAT12 filesystem - Read arbitrary files into memory for execution
  6. Work within boot sector constraints - Implement complex logic in limited space
  7. Build a filesystem-aware bootloader - Create a bootloader that can load updated kernels without modification

2. Theoretical Foundation

2.1 Core Concepts

What is FAT12?

FAT12 (File Allocation Table, 12-bit) is the simplest FAT filesystem variant, originally designed for floppy disks. It’s still used today for small partitions, USB drives formatted as “superfloppy,” and EFI System Partitions on small media.

┌─────────────────────────────────────────────────────────────────────────────┐
│                         FAT12 Disk Layout                                    │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                              │
│   Sector 0                                                                   │
│   ┌──────────────────────────────────────────────────────────────────────┐  │
│   │                        Boot Sector                                    │  │
│   │  ┌─────────────────────────────────────────────────────────────────┐ │  │
│   │  │ Bytes 0-2:   Jump instruction (EB xx 90 or E9 xx xx)            │ │  │
│   │  │ Bytes 3-10:  OEM Name ("MSWIN4.1" for compatibility)            │ │  │
│   │  │ Bytes 11-35: BIOS Parameter Block (BPB)                         │ │  │
│   │  │   - BytesPerSector (usually 512)                                │ │  │
│   │  │   - SectorsPerCluster (1, 2, 4, 8...)                           │ │  │
│   │  │   - ReservedSectorCount (includes boot sector)                  │ │  │
│   │  │   - NumberOfFATs (usually 2)                                    │ │  │
│   │  │   - RootEntryCount (number of root directory entries)           │ │  │
│   │  │   - TotalSectors16                                              │ │  │
│   │  │   - SectorsPerFAT                                               │ │  │
│   │  │ Bytes 36-61: Extended BPB (optional)                            │ │  │
│   │  │ Bytes 62-509: Boot code                                         │ │  │
│   │  │ Bytes 510-511: Boot signature (0x55, 0xAA)                      │ │  │
│   │  └─────────────────────────────────────────────────────────────────┘ │  │
│   └──────────────────────────────────────────────────────────────────────┘  │
│                                                                              │
│   Reserved Sectors (if ReservedSectorCount > 1)                             │
│   ┌──────────────────────────────────────────────────────────────────────┐  │
│   │                    (Usually just the boot sector)                     │  │
│   └──────────────────────────────────────────────────────────────────────┘  │
│                                                                              │
│   FAT #1 (File Allocation Table)                                            │
│   ┌──────────────────────────────────────────────────────────────────────┐  │
│   │  Entry 0: Media type (0xFF0 for floppy)                              │  │
│   │  Entry 1: End of chain marker (0xFFF)                                │  │
│   │  Entry 2: First data cluster's next pointer                          │  │
│   │  Entry 3: ...                                                        │  │
│   │  Each entry is 12 bits (two entries packed in 3 bytes)               │  │
│   └──────────────────────────────────────────────────────────────────────┘  │
│                                                                              │
│   FAT #2 (Backup copy of FAT #1)                                            │
│   ┌──────────────────────────────────────────────────────────────────────┐  │
│   │                    (Identical to FAT #1)                              │  │
│   └──────────────────────────────────────────────────────────────────────┘  │
│                                                                              │
│   Root Directory                                                             │
│   ┌──────────────────────────────────────────────────────────────────────┐  │
│   │  Entry 0: 32 bytes (filename, extension, attributes, cluster, size) │  │
│   │  Entry 1: 32 bytes                                                   │  │
│   │  ...                                                                 │  │
│   │  Entry N: 32 bytes (where N = RootEntryCount - 1)                    │  │
│   │  Fixed size: (RootEntryCount * 32) / BytesPerSector sectors          │  │
│   └──────────────────────────────────────────────────────────────────────┘  │
│                                                                              │
│   Data Area (Clusters 2, 3, 4, ...)                                         │
│   ┌──────────────────────────────────────────────────────────────────────┐  │
│   │  Cluster 2: (First usable cluster - cluster numbering starts at 2)  │  │
│   │  Cluster 3: ...                                                      │  │
│   │  Cluster N: ...                                                      │  │
│   │  Files are stored as chains of clusters linked via FAT entries       │  │
│   └──────────────────────────────────────────────────────────────────────┘  │
│                                                                              │
└─────────────────────────────────────────────────────────────────────────────┘

The BIOS Parameter Block (BPB)

The BPB is the heart of FAT filesystem metadata. Your bootloader must parse this to understand the disk layout:

┌─────────────────────────────────────────────────────────────────────────────┐
│                      BIOS Parameter Block (BPB)                              │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                              │
│   Offset  Size  Name                  Description                            │
│   ──────────────────────────────────────────────────────────────────────    │
│   0x0B    2     BytesPerSector        Usually 512                            │
│   0x0D    1     SectorsPerCluster     Power of 2 (1, 2, 4, 8, 16, 32, 64)   │
│   0x0E    2     ReservedSectorCount   Sectors before first FAT              │
│   0x10    1     NumberOfFATs          Usually 2 (for redundancy)             │
│   0x11    2     RootEntryCount        Max entries in root directory         │
│   0x13    2     TotalSectors16        Total sectors (if < 65536)            │
│   0x15    1     MediaType             0xF0=floppy, 0xF8=hard disk           │
│   0x16    2     SectorsPerFAT         Sectors per FAT table                 │
│   0x18    2     SectorsPerTrack       For CHS addressing                    │
│   0x1A    2     NumberOfHeads         For CHS addressing                    │
│   0x1C    4     HiddenSectors         Sectors before partition              │
│   0x20    4     TotalSectors32        Total sectors (if >= 65536)           │
│                                                                              │
│   Extended BPB (FAT12/16):                                                   │
│   0x24    1     DriveNumber           0x00=floppy, 0x80=hard disk           │
│   0x25    1     Reserved              (Used by Windows NT)                  │
│   0x26    1     BootSignature         0x29 if extended BPB present          │
│   0x27    4     VolumeSerialNumber    Random ID                             │
│   0x2B    11    VolumeLabel           "NO NAME    " if empty                │
│   0x36    8     FileSystemType        "FAT12   " or "FAT16   "              │
│                                                                              │
└─────────────────────────────────────────────────────────────────────────────┘

Calculating Key Offsets

Understanding these calculations is essential:

┌─────────────────────────────────────────────────────────────────────────────┐
│                    FAT12 Layout Calculations                                 │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                              │
│   FAT Region Start:                                                          │
│   ───────────────────────────────────────────────────────────────────────   │
│   FAT_Start_Sector = ReservedSectorCount                                    │
│                                                                              │
│   Example: ReservedSectorCount = 1                                          │
│   FAT starts at sector 1 (right after boot sector)                          │
│                                                                              │
│   Root Directory Start:                                                      │
│   ───────────────────────────────────────────────────────────────────────   │
│   Root_Dir_Sector = ReservedSectorCount + (NumberOfFATs × SectorsPerFAT)   │
│                                                                              │
│   Example: 1 + (2 × 9) = sector 19                                          │
│                                                                              │
│   Root Directory Size (in sectors):                                          │
│   ───────────────────────────────────────────────────────────────────────   │
│   Root_Dir_Sectors = (RootEntryCount × 32) / BytesPerSector                 │
│                                                                              │
│   Example: (224 × 32) / 512 = 14 sectors                                    │
│                                                                              │
│   Data Area Start:                                                           │
│   ───────────────────────────────────────────────────────────────────────   │
│   Data_Start_Sector = Root_Dir_Sector + Root_Dir_Sectors                    │
│                                                                              │
│   Example: 19 + 14 = sector 33                                              │
│                                                                              │
│   Cluster to Sector Conversion:                                              │
│   ───────────────────────────────────────────────────────────────────────   │
│   Sector = Data_Start_Sector + (Cluster - 2) × SectorsPerCluster            │
│                                                                              │
│   WHY "-2"? Cluster numbering starts at 2, not 0!                           │
│   Cluster 0 and 1 are reserved in the FAT table                             │
│                                                                              │
│   Example: Cluster 5 → 33 + (5 - 2) × 1 = sector 36                         │
│                                                                              │
└─────────────────────────────────────────────────────────────────────────────┘

The 12-bit FAT Entry Challenge

FAT12 uses 12 bits per entry, which means two entries share 3 bytes. This requires careful bit manipulation:

┌─────────────────────────────────────────────────────────────────────────────┐
│                      FAT12 Entry Packing                                     │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                              │
│   Two 12-bit entries fit in 3 bytes:                                        │
│                                                                              │
│   Entry N and Entry N+1:                                                     │
│   ┌──────────┬──────────┬──────────┐                                        │
│   │  Byte 0  │  Byte 1  │  Byte 2  │                                        │
│   │ xxxxxxxx │ YYYYxxxx │ YYYYYYYY │                                        │
│   └──────────┴──────────┴──────────┘                                        │
│       │            │ │        │                                              │
│       │            │ └────────┴──── Entry N+1 (12 bits): Y                  │
│       └────────────┴────────────── Entry N (12 bits): x                     │
│                                                                              │
│   Reading Entry N (when N is even):                                          │
│   ─────────────────────────────────                                          │
│   offset = N + (N / 2) = N × 3 / 2                                          │
│   value = (FAT[offset+1] & 0x0F) << 8 | FAT[offset]                         │
│   (Lower nibble of byte 1, all of byte 0)                                    │
│                                                                              │
│   Reading Entry N (when N is odd):                                           │
│   ────────────────────────────────                                           │
│   offset = N + (N / 2) = N × 3 / 2                                          │
│   value = FAT[offset+1] << 4 | (FAT[offset] >> 4)                           │
│   (All of byte 1, upper nibble of byte 0)                                    │
│                                                                              │
│   Special Values:                                                            │
│   ────────────────                                                           │
│   0x000       = Free cluster                                                 │
│   0x001       = Reserved                                                     │
│   0x002-0xFEF = Next cluster in chain                                       │
│   0xFF0-0xFF6 = Reserved                                                     │
│   0xFF7       = Bad cluster                                                  │
│   0xFF8-0xFFF = End of cluster chain (EOF)                                  │
│                                                                              │
└─────────────────────────────────────────────────────────────────────────────┘

Directory Entry Format

Each file in the root directory (and subdirectories) has a 32-byte entry:

┌─────────────────────────────────────────────────────────────────────────────┐
│                      Directory Entry (32 bytes)                              │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                              │
│   Offset  Size  Name              Description                                │
│   ─────────────────────────────────────────────────────────────────────     │
│   0x00    8     Filename          8 characters, space-padded                │
│   0x08    3     Extension         3 characters, space-padded                │
│   0x0B    1     Attributes        See below                                 │
│   0x0C    1     Reserved          (Used by Windows NT)                      │
│   0x0D    1     CreateTimeTenth   10ms units                                │
│   0x0E    2     CreateTime        Packed time                               │
│   0x10    2     CreateDate        Packed date                               │
│   0x12    2     LastAccessDate    Packed date                               │
│   0x14    2     FirstClusterHigh  High 16 bits (FAT32 only, 0 for FAT12)   │
│   0x16    2     WriteTime         Packed time                               │
│   0x18    2     WriteDate         Packed date                               │
│   0x1A    2     FirstClusterLow   Starting cluster number                   │
│   0x1C    4     FileSize          Size in bytes                             │
│                                                                              │
│   8.3 Filename Format:                                                       │
│   ────────────────────                                                       │
│   "KERNEL  BIN"  ← 8 chars + 3 chars, uppercase, space-padded              │
│    ^^^^^^^^ ^^^                                                              │
│    Filename Ext                                                              │
│                                                                              │
│   Special first-byte values:                                                 │
│   0x00 = Entry is free and no entries follow                                │
│   0xE5 = Entry is deleted (available for reuse)                             │
│   0x05 = Actual first char is 0xE5 (escape code)                            │
│                                                                              │
│   Attributes byte:                                                           │
│   ────────────────                                                           │
│   Bit 0 = Read-only                                                          │
│   Bit 1 = Hidden                                                             │
│   Bit 2 = System                                                             │
│   Bit 3 = Volume label                                                       │
│   Bit 4 = Subdirectory                                                       │
│   Bit 5 = Archive                                                            │
│   0x0F = Long filename entry (LFN)                                          │
│                                                                              │
└─────────────────────────────────────────────────────────────────────────────┘

2.2 Why This Matters

Real bootloaders don’t hardcode sector numbers—they understand filesystems. This is how:

  • MS-DOS found IO.SYS on the boot floppy
  • Windows finds bootmgr on the system partition
  • GRUB loads its configuration and kernel from /boot
  • UEFI loads .efi files from the EFI System Partition

A filesystem-aware bootloader means you can update the kernel without touching the bootloader. Just replace KERNEL.BIN on the disk!

2.3 Historical Context

FAT12 was created by Microsoft in 1977 for the original MS-DOS. The “12” refers to the 12-bit cluster addresses, limiting it to 4,084 clusters. This was sufficient for 360KB and 1.44MB floppies but led to FAT16 (1984) and FAT32 (1996) for larger media.

The FAT filesystem’s simplicity made it the universal format for decades. Even today, EFI System Partitions use FAT, USB drives default to FAT32, and SD cards are formatted as FAT.

2.4 Common Misconceptions

  1. “Cluster 0 is the first data cluster” - No! Cluster numbering starts at 2. Entries 0 and 1 in the FAT table hold special values (media type and reserved).

  2. “The root directory is inside the data area” - In FAT12/16, the root directory is at a fixed location BEFORE the data area. Only in FAT32 is it a regular cluster chain.

  3. “FAT entries are byte-aligned” - FAT12 entries are 12 bits, meaning two entries share 3 bytes. This bit packing is a major implementation challenge.

  4. “The boot sector is read-only metadata” - No! The boot sector contains executable code (your bootloader) AND the BPB. They coexist, which is why the boot sector starts with a jump instruction.


3. Project Specification

3.1 What You Will Build

A bootloader that:

  1. Boots from a FAT12-formatted floppy image
  2. Reads the BIOS Parameter Block to understand disk layout
  3. Searches the root directory for a file named “KERNEL.BIN”
  4. Reads the FAT table to follow the file’s cluster chain
  5. Loads the complete kernel file into memory at a specified address
  6. Jumps to the loaded kernel to begin execution

3.2 Functional Requirements

Requirement Description
FR-1 Parse the BPB from the boot sector to determine filesystem layout
FR-2 Calculate root directory and data area locations from BPB values
FR-3 Search root directory entries for filename “KERNEL.BIN”
FR-4 Read the starting cluster number from the directory entry
FR-5 Follow the cluster chain in the FAT table until EOF
FR-6 Load each cluster’s data to sequential memory addresses
FR-7 Jump to the loaded kernel’s entry point
FR-8 Display status messages during loading

3.3 Non-Functional Requirements

Requirement Description
NFR-1 Boot code must fit in 512 bytes OR use two-stage approach
NFR-2 Must work with standard 1.44MB FAT12 floppy images
NFR-3 Must handle kernels larger than one cluster
NFR-4 Error messages for: file not found, disk read error
NFR-5 Should work in QEMU, Bochs, and on real hardware

3.4 Example Usage / Output

# Create a FAT12 floppy image:
$ dd if=/dev/zero of=floppy.img bs=1024 count=1440
$ mkfs.fat -F 12 floppy.img

# Copy your kernel to the floppy:
$ mcopy -i floppy.img kernel.bin ::KERNEL.BIN

# Install your bootloader (preserve BPB):
$ dd if=boot.bin of=floppy.img bs=1 count=3 conv=notrunc
$ dd if=boot.bin of=floppy.img bs=1 skip=62 seek=62 conv=notrunc

# Run it:
$ qemu-system-x86_64 -fda floppy.img

# Output:
FAT12 Bootloader
Searching for KERNEL.BIN...
Found at cluster 2
Loading kernel (5 clusters)...
Jumping to kernel!
[Kernel] Hello from the loaded kernel!

3.5 Real World Outcome

When complete, your bootloader demonstrates:

  1. True filesystem awareness - Unlike hardcoded sector bootloaders, your code understands FAT12 structure
  2. Updateable kernel - Change KERNEL.BIN without modifying the bootloader
  3. Production technique - The same approach used by MS-DOS boot code
  4. Data structure skills - Parsing BPB, following linked structures (FAT chains)

4. Solution Architecture

4.1 High-Level Design

┌─────────────────────────────────────────────────────────────────────────────┐
│                    FAT12 Bootloader Architecture                             │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                              │
│   Boot Flow:                                                                 │
│                                                                              │
│   ┌─────────────┐    ┌──────────────┐    ┌───────────────┐                 │
│   │  BIOS loads │    │ Parse BPB,   │    │ Search root   │                 │
│   │  sector 0   │───►│ calculate    │───►│ directory for │                 │
│   │  to 0x7C00  │    │ offsets      │    │ KERNEL.BIN    │                 │
│   └─────────────┘    └──────────────┘    └───────┬───────┘                 │
│                                                   │                         │
│                                          ┌───────▼───────┐                  │
│                                          │ Read starting │                  │
│                                          │ cluster from  │                  │
│                                          │ dir entry     │                  │
│                                          └───────┬───────┘                  │
│                                                   │                         │
│   ┌─────────────┐    ┌──────────────┐    ┌───────▼───────┐                 │
│   │ Jump to     │    │ Load cluster │    │ Read FAT to   │                 │
│   │ kernel at   │◄───│ data to      │◄───│ get next      │                 │
│   │ 0x10000     │    │ memory       │    │ cluster       │                 │
│   └─────────────┘    └──────────────┘    └───────────────┘                 │
│         ▲                    │                   │                          │
│         │                    │           ┌───────▼───────┐                  │
│         │                    │           │ Is next       │                  │
│         │                    │           │ cluster EOF?  │                  │
│         │                    │           └───────┬───────┘                  │
│         │                    │                   │                          │
│         │                    │  ┌────────────────┼────────────────┐         │
│         │                    │  │ No             │ Yes            │         │
│         │                    │  ▼                ▼                │         │
│         │                    │ Loop             Done!             │         │
│         └────────────────────┴───────────────────┘                │         │
│                                                                              │
└─────────────────────────────────────────────────────────────────────────────┘

4.2 Key Components

Component 1: BPB Parser

Reads the BIOS Parameter Block and calculates key offsets:

  • fat_start = ReservedSectorCount
  • root_dir_start = fat_start + (NumberOfFATs * SectorsPerFAT)
  • data_start = root_dir_start + (RootEntryCount * 32 / BytesPerSector)

Component 2: Root Directory Scanner

Iterates through root directory entries:

  • Reads sectors from root_dir_start
  • Compares each entry’s 11-byte name with “KERNEL BIN”
  • Returns starting cluster if found

Component 3: FAT Chain Follower

Reads the FAT table to traverse cluster chains:

  • Handles 12-bit packed entries
  • Detects EOF markers (0xFF8-0xFFF)
  • Returns next cluster or EOF indication

Component 4: Cluster Loader

Converts cluster numbers to sectors and loads data:

  • sector = data_start + (cluster - 2) * SectorsPerCluster
  • Uses INT 13h to read sectors
  • Advances load address by cluster size

4.3 Data Structures

; In-memory BPB data (extracted from boot sector)
BytesPerSector:      dw 0
SectorsPerCluster:   db 0
ReservedSectors:     dw 0
NumberOfFATs:        db 0
RootEntryCount:      dw 0
SectorsPerFAT:       dw 0

; Calculated values
FATStart:            dw 0
RootDirStart:        dw 0
DataStart:           dw 0

; Working variables
CurrentCluster:      dw 0
LoadAddress:         dw 0

4.4 Algorithm Overview

1. BIOS loads us to 0x7C00
2. Set up stack at 0x7C00 (grows downward)
3. Parse BPB from our own boot sector:
   - Read BytesPerSector (offset 0x0B)
   - Read SectorsPerCluster (offset 0x0D)
   - Read ReservedSectors (offset 0x0E)
   - Read NumberOfFATs (offset 0x10)
   - Read RootEntryCount (offset 0x11)
   - Read SectorsPerFAT (offset 0x16)
4. Calculate offsets:
   - FATStart = ReservedSectors
   - RootDirStart = FATStart + (NumberOfFATs * SectorsPerFAT)
   - RootDirSectors = (RootEntryCount * 32) / BytesPerSector
   - DataStart = RootDirStart + RootDirSectors
5. Load FAT table into memory buffer
6. Load root directory and search:
   For each 32-byte entry:
     If first byte == 0: no more entries
     If first byte == 0xE5: deleted, skip
     Compare bytes 0-10 with "KERNEL  BIN"
     If match: extract cluster from bytes 0x1A-0x1B
7. Load kernel via cluster chain:
   While cluster < 0xFF8:
     sector = DataStart + (cluster - 2) * SectorsPerCluster
     Read SectorsPerCluster sectors to LoadAddress
     LoadAddress += SectorsPerCluster * BytesPerSector
     cluster = FAT[cluster]  (12-bit lookup)
8. Jump to kernel at load address

5. Implementation Guide

5.1 Development Environment Setup

Required Tools:

# Assembler
sudo apt install nasm

# FAT tools
sudo apt install mtools dosfstools

# Emulator
sudo apt install qemu-system-x86

# Hex editor (optional)
sudo apt install hexedit bless

Verify FAT tools:

# Create test floppy image
dd if=/dev/zero of=test.img bs=1024 count=1440
mkfs.fat -F 12 test.img
mdir -i test.img  # Should show empty directory

5.2 Project Structure

fat12-bootloader/
├── boot.asm           # Main bootloader code
├── kernel.asm         # Test kernel (prints message)
├── Makefile           # Build automation
├── floppy.img         # Output floppy image
└── README.md          # Documentation

5.3 The Core Question You’re Answering

“How do bootloaders locate and load files from a filesystem, and what data structures enable a simple boot sector to navigate complex disk layouts to find an arbitrarily-located kernel file?”

This question reveals why filesystem-aware bootloaders are superior to hardcoded sector loading. A FAT12 bootloader must:

  • Parse metadata (BPB) to understand disk geometry
  • Navigate a linked data structure (FAT cluster chain)
  • Search a directory structure (root directory entries)
  • Handle files of any size (multi-cluster loading)

This is the foundation of how all real bootloaders work—from MS-DOS to GRUB to Windows Boot Manager.

5.4 Concepts You Must Understand First

  1. Disk sector addressing (from Project 4)
    • Self-assessment: How do you read sector N using INT 13h? What’s the difference between CHS and LBA?
  2. Little-endian byte order
    • Self-assessment: If bytes at offset 0x0B are “00 02”, what is BytesPerSector?
    • Answer: 0x0200 = 512 (little-endian: low byte first)
  3. Bit manipulation for 12-bit values
    • Self-assessment: Given 3 bytes [AB CD EF], what are the two 12-bit values?
    • Answer: Entry 0 = 0xDAB (CD & 0x0F) « 8 AB, Entry 1 = 0xEFC (EF « 4 CD » 4)
  4. Directory structure traversal
    • Self-assessment: How do you detect end of directory entries? What does first byte 0xE5 mean?

5.5 Questions to Guide Your Design

BPB Parsing:

  • Where in the boot sector is the BPB located?
  • Which fields are 8-bit vs 16-bit?
  • Do you need all BPB fields or just a subset?

Space Management:

  • Will your code fit in 512 bytes?
  • If not, how will you implement two-stage loading?
  • Where will you buffer the FAT table in memory?

FAT12 Entry Reading:

  • How will you handle the even/odd cluster number cases?
  • What value indicates end of cluster chain?
  • Where will you store the FAT table in memory?

Error Handling:

  • What should happen if KERNEL.BIN is not found?
  • What if a disk read fails?
  • Can you print error messages in 512 bytes?

5.6 Thinking Exercise

Before coding, trace through this scenario by hand:

Given a FAT12 floppy with:

  • BytesPerSector = 512
  • SectorsPerCluster = 1
  • ReservedSectors = 1
  • NumberOfFATs = 2
  • RootEntryCount = 224
  • SectorsPerFAT = 9
  1. Calculate where the root directory starts (sector number)
  2. Calculate where the data area starts (sector number)
  3. If KERNEL.BIN starts at cluster 2, what sector is that?
  4. If the FAT entry for cluster 2 is 0x003, what does that mean?
  5. If the FAT entry for cluster 3 is 0xFFF, what does that mean?

Answers:

  1. RootDirStart = 1 + (2 * 9) = sector 19
  2. RootDirSectors = (224 * 32) / 512 = 14, DataStart = 19 + 14 = sector 33
  3. Sector = 33 + (2 - 2) * 1 = sector 33
  4. Next cluster is 3 (file continues)
  5. End of file (0xFFF is EOF marker)

5.7 Hints in Layers

Hint 1: Starting Point (Conceptual)

Your bootloader shares the same sector with the BPB. The structure is:

  • Bytes 0-2: Jump instruction over BPB
  • Bytes 3-61: BPB (filled by mkfs.fat)
  • Bytes 62-509: Your code
  • Bytes 510-511: 0x55, 0xAA

When you dd your bootloader onto a FAT12 image, you must PRESERVE the BPB:

dd if=boot.bin of=floppy.img bs=1 count=3 conv=notrunc
dd if=boot.bin of=floppy.img bs=1 skip=62 seek=62 conv=notrunc

Hint 2: BPB Access (More Specific)

Since you’re loaded at 0x7C00, and the BPB is in your own sector, access it directly:

mov ax, [0x7C00 + 0x0B]  ; BytesPerSector
mov al, [0x7C00 + 0x0D]  ; SectorsPerCluster
mov ax, [0x7C00 + 0x0E]  ; ReservedSectors
; etc.

Or define labels in your code:

org 0x7C00
jmp short start
nop
; BPB follows at offset 3
OEMName:           times 8 db 0
BytesPerSector:    dw 0
SectorsPerCluster: db 0
; ...

Hint 3: FAT12 Entry Reading (Technical)

For cluster N, calculate the byte offset and handle 12-bit extraction:

; Input: AX = cluster number
; Output: AX = next cluster
read_fat_entry:
    mov cx, ax          ; CX = cluster
    mov bx, ax
    shr bx, 1           ; BX = cluster / 2
    add bx, ax          ; BX = cluster + cluster/2 = offset in FAT
    add bx, fat_buffer  ; BX = address in FAT buffer
    mov ax, [bx]        ; Read 16 bits (covers both cases)

    test cx, 1          ; Even or odd cluster?
    jz .even
    shr ax, 4           ; Odd: shift right 4 bits
    jmp .done
.even:
    and ax, 0x0FFF      ; Even: mask upper 4 bits
.done:
    ret

Hint 4: Testing and Debugging

Create a minimal test kernel:

; kernel.asm
org 0x10000  ; Load address
start:
    mov si, msg
    call print
    jmp $
print:
    lodsb
    or al, al
    jz .done
    mov ah, 0x0E
    int 0x10
    jmp print
.done:
    ret
msg: db "Kernel loaded successfully!", 0

Test incrementally:

  1. First, just parse and print BPB values
  2. Then, search and find KERNEL.BIN (print “Found!”)
  3. Then, load and jump (kernel prints its message)

5.8 The Interview Questions They’ll Ask

  1. “Explain the structure of a FAT12 filesystem. How do you locate a file?”
    • What they’re testing: Understanding of filesystem layout
    • Strong answer: “FAT12 has four regions: boot sector with BPB, FAT table(s), root directory, and data area. To find a file, parse the BPB for offsets, scan root directory entries for matching 8.3 filename, get starting cluster, then follow cluster chain in FAT until EOF marker (0xFF8+).”
  2. “Why does cluster numbering start at 2, not 0?”
    • What they’re testing: Attention to detail, understanding of FAT structure
    • Strong answer: “FAT entries 0 and 1 are reserved. Entry 0 holds the media type byte (e.g., 0xFF0 for floppy), entry 1 holds an end-of-chain marker. The first usable cluster is 2, so all cluster-to-sector calculations subtract 2.”
  3. “How do you handle the 12-bit packing in FAT12?”
    • What they’re testing: Bit manipulation skills
    • Strong answer: “Two 12-bit entries share 3 bytes. For even cluster N at byte offset N×3/2: lower 12 bits = (byte[1] & 0x0F) « 8 byte[0]. For odd cluster: upper 12 bits = byte[1] « 4 byte[0] » 4. I read 16 bits and mask/shift based on cluster parity.”
  4. “What makes a filesystem-aware bootloader better than hardcoded sector loading?”
    • What they’re testing: Understanding of practical bootloader design
    • Strong answer: “Hardcoded sector loading breaks when files are moved, defragmented, or updated. Filesystem-aware loading means you can update KERNEL.BIN without touching the bootloader. The kernel can be any size, anywhere on disk. This is how production bootloaders work.”
  5. “How would you extend this to support subdirectories?”
    • What they’re testing: Ability to extend knowledge
    • Strong answer: “Subdirectories are cluster chains like files, but contain directory entries instead of data. Read the subdirectory’s starting cluster, load its data, and parse 32-byte entries. Recursively follow subdirectories to navigate paths like /BOOT/KERNEL.BIN.”

5.9 Books That Will Help

Topic Book Chapter/Section
FAT Filesystem Structure OSDev Wiki - FAT Full article
Cluster Chain Traversal “Operating Systems: Three Easy Pieces” - Arpaci-Dusseau Chapter 40 (File System Implementation)
8.3 Filename Format Microsoft FAT Specification Section 6 (Directory Entry)
BIOS Parameter Block “Practical File System Design” - Dominic Giampaolo Chapter 2 (Disk Organization)
x86 Assembly “PC Assembly Language” - Paul Carter Chapter 1-4

5.10 Implementation Phases

Phase 1: Boot and Parse (Days 1-3)

  • Create boot sector skeleton with proper jump and BPB structure
  • Extract and print BPB values (BytesPerSector, etc.)
  • Calculate and print FAT/root/data area offsets
  • Verify calculations with known FAT12 image

Phase 2: Directory Search (Days 4-6)

  • Load root directory sectors
  • Iterate through 32-byte entries
  • Implement 8.3 filename comparison
  • Print “Found!” when KERNEL.BIN located
  • Extract starting cluster from directory entry

Phase 3: FAT Traversal (Days 7-10)

  • Load FAT table into memory buffer
  • Implement 12-bit entry reader
  • Follow cluster chain, printing each cluster number
  • Detect EOF marker

Phase 4: Kernel Loading (Days 11-14)

  • Convert clusters to sectors
  • Load each cluster to incrementing memory address
  • Jump to loaded kernel
  • Test with real kernel file

5.11 Key Implementation Decisions

  1. Single-stage vs Two-stage
    • 512 bytes is very tight for FAT12 traversal
    • Two-stage: stage 1 loads stage 2, stage 2 handles FAT
    • Single-stage: extreme optimization required
  2. FAT buffer location
    • FAT12 table is up to 6KB (12 sectors)
    • Load to unused memory (e.g., 0x7E00)
    • Or load sectors on-demand (slower but fits in 512 bytes)
  3. Kernel load address
    • Common choices: 0x10000 (64KB), 0x100000 (1MB)
    • Must not conflict with boot code or BIOS areas
  4. Error handling strategy
    • Minimal: print single char and halt
    • Verbose: print error message (costs bytes)
    • Debug: use Bochs logging

6. Testing Strategy

6.1 Unit Tests (Manual)

Test BPB Parsing:

# Create FAT12 image and examine with hex editor
mkfs.fat -F 12 test.img
hexdump -C test.img | head -5
# Verify your code extracts matching values

Test Directory Search:

# Create file and verify it's found
mcopy -i test.img kernel.bin ::KERNEL.BIN
# Run bootloader, should print "Found!"

Test FAT Traversal:

# Create multi-cluster file
dd if=/dev/urandom of=bigfile bs=2048 count=1
mcopy -i test.img bigfile ::BIGFILE.BIN
# Bootloader should print cluster chain

6.2 Integration Tests

Complete Boot Test:

# Build everything
make clean all

# Test in QEMU
qemu-system-x86_64 -fda floppy.img

# Test in Bochs (more accurate)
bochs -f bochsrc.txt

# Test kernel update works
mcopy -o -i floppy.img newkernel.bin ::KERNEL.BIN
qemu-system-x86_64 -fda floppy.img  # Should load new kernel

6.3 Edge Cases

Test Case Expected Behavior
KERNEL.BIN missing Print “Not found” and halt
Empty disk Print “Not found” and halt
Kernel at cluster 2 Load correctly
Kernel spanning multiple clusters Load all clusters
Fragmented kernel Follow FAT chain correctly
Maximum size kernel Load without overflow

7. Common Pitfalls & Debugging

7.1 Pitfall: Overwriting BPB

Symptom: Image no longer recognized as FAT12 Cause: dd if=boot.bin of=floppy.img conv=notrunc overwrites BPB Fix: Preserve bytes 3-61:

dd if=boot.bin of=floppy.img bs=1 count=3 conv=notrunc
dd if=boot.bin of=floppy.img bs=1 skip=62 seek=62 conv=notrunc

7.2 Pitfall: Wrong Cluster Calculation

Symptom: Wrong data loaded or crash Cause: Forgot to subtract 2 from cluster number Fix: sector = DataStart + (cluster - 2) * SectorsPerCluster

7.3 Pitfall: 12-bit Entry Misread

Symptom: Garbage cluster numbers, infinite loop Cause: Wrong bit shift/mask for even/odd entries Fix: Carefully test with known FAT contents

7.4 Pitfall: Filename Comparison

Symptom: File not found even when present Cause: Lowercase filename, wrong padding Fix: Ensure uppercase “KERNEL BIN” (8+3 chars, space-padded)

7.5 Debugging Techniques

# Examine FAT table
dosfsck -v floppy.img

# Dump root directory
mdir -i floppy.img

# Debug in Bochs with logging
bochs -q 'log: bochs.log' 'debug: action=report'

# QEMU debug output
qemu-system-x86_64 -fda floppy.img -d int,cpu_reset

8. Extensions & Challenges

  1. Support Long Filenames (LFN)
    • Parse VFAT long filename entries
    • Reconstruct UTF-16 names from multiple entries
  2. Add Path Support
    • Navigate subdirectories
    • Support paths like /BOOT/KERNEL.BIN
  3. Implement Boot Menu
    • List .BIN files in root directory
    • Let user select which to boot
  4. Support FAT16/FAT32
    • Handle 16-bit and 32-bit cluster entries
    • Adjust for different BPB layouts
  5. Add Error Recovery
    • Retry failed reads
    • Use backup FAT if primary is corrupt

9. Real-World Connections

9.1 MS-DOS Boot Process

MS-DOS’s boot sector (1981-2000) used exactly this technique:

  • Parse BPB from boot sector
  • Search root directory for IO.SYS
  • Load via FAT12 cluster chain
  • Your code replicates what ran on millions of PCs

9.2 Modern EFI System Partition

Even UEFI uses FAT:

  • EFI System Partition is FAT12/16/32
  • UEFI firmware navigates FAT to find .efi files
  • Same concepts, different code (UEFI drivers vs BIOS interrupts)

9.3 USB Drive Booting

When BIOS boots from USB:

  • USB drive formatted as FAT
  • BIOS loads boot sector from FAT partition
  • Your FAT12 bootloader skills apply directly

10. Resources

Primary References

Tools

  • mtools - Manipulate FAT images
  • dosfstools - Create/check FAT filesystems
  • Bochs - x86 emulator with debugging

Books

  • “Operating Systems: Three Easy Pieces” - Arpaci-Dusseau (Chapter 40)
  • “Practical File System Design” - Dominic Giampaolo (Chapter 2)
  • “PC Assembly Language” - Paul Carter (Free PDF)

11. Self-Assessment Checklist

Conceptual Understanding

  • I can draw the FAT12 disk layout from memory
  • I can explain why cluster numbering starts at 2
  • I understand the 12-bit packing algorithm
  • I can calculate root directory and data area offsets

Practical Skills

  • I can parse BPB values from a boot sector
  • I can search root directory for a filename
  • I can read FAT entries (both even and odd)
  • I can follow a cluster chain to EOF

Implementation Milestones

  • Bootloader boots and parses BPB correctly
  • Root directory search finds KERNEL.BIN
  • FAT traversal follows cluster chain
  • Complete kernel loads and executes
  • Kernel updates work without bootloader changes

12. Submission / Completion Criteria

Your project is complete when:

  1. Functional Bootloader
    • Boots from FAT12 floppy image
    • Finds and loads KERNEL.BIN
    • Jumps to loaded kernel
  2. Demonstrates Understanding
    • Code comments explain BPB parsing
    • Code comments explain FAT12 calculations
    • README documents the boot process
  3. Tested Correctly
    • Works in QEMU
    • Works in Bochs
    • Kernel updates work (new kernel boots without bootloader change)
  4. Clean Implementation
    • No hardcoded sector numbers
    • Proper error handling (file not found, read error)
    • Fits in 512 bytes OR clean two-stage design

Deliverables:

  • boot.asm - Bootloader source
  • kernel.asm - Test kernel source
  • Makefile - Build automation
  • floppy.img - Bootable image
  • README.md - Documentation and usage instructions