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:
- Parse the BIOS Parameter Block (BPB) - Read and interpret the filesystem metadata stored in the boot sector
- Navigate the FAT table - Follow cluster chains using 12-bit packed entries
- Read the root directory - Search for files using the 8.3 filename format
- Convert cluster numbers to sector numbers - Understand the FAT12 disk geometry
- Load files from a FAT12 filesystem - Read arbitrary files into memory for execution
- Work within boot sector constraints - Implement complex logic in limited space
- 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
-
“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).
-
“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.
-
“FAT entries are byte-aligned” - FAT12 entries are 12 bits, meaning two entries share 3 bytes. This bit packing is a major implementation challenge.
-
“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:
- Boots from a FAT12-formatted floppy image
- Reads the BIOS Parameter Block to understand disk layout
- Searches the root directory for a file named “KERNEL.BIN”
- Reads the FAT table to follow the file’s cluster chain
- Loads the complete kernel file into memory at a specified address
- 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:
- True filesystem awareness - Unlike hardcoded sector bootloaders, your code understands FAT12 structure
- Updateable kernel - Change KERNEL.BIN without modifying the bootloader
- Production technique - The same approach used by MS-DOS boot code
- 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= ReservedSectorCountroot_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
- 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?
- 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)
- 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)
- 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
- Calculate where the root directory starts (sector number)
- Calculate where the data area starts (sector number)
- If KERNEL.BIN starts at cluster 2, what sector is that?
- If the FAT entry for cluster 2 is 0x003, what does that mean?
- If the FAT entry for cluster 3 is 0xFFF, what does that mean?
Answers:
- RootDirStart = 1 + (2 * 9) = sector 19
- RootDirSectors = (224 * 32) / 512 = 14, DataStart = 19 + 14 = sector 33
- Sector = 33 + (2 - 2) * 1 = sector 33
- Next cluster is 3 (file continues)
- 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:
- First, just parse and print BPB values
- Then, search and find KERNEL.BIN (print “Found!”)
- Then, load and jump (kernel prints its message)
5.8 The Interview Questions They’ll Ask
- “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+).”
- “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.”
- “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.”
- “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.”
- “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
- 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
- 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)
- Kernel load address
- Common choices: 0x10000 (64KB), 0x100000 (1MB)
- Must not conflict with boot code or BIOS areas
- 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
- Support Long Filenames (LFN)
- Parse VFAT long filename entries
- Reconstruct UTF-16 names from multiple entries
- Add Path Support
- Navigate subdirectories
- Support paths like /BOOT/KERNEL.BIN
- Implement Boot Menu
- List .BIN files in root directory
- Let user select which to boot
- Support FAT16/FAT32
- Handle 16-bit and 32-bit cluster entries
- Adjust for different BPB layouts
- 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
- OSDev Wiki - FAT - Essential reference
- Microsoft FAT Specification - Official spec
- Bran’s Kernel Development Tutorial - Bootloader basics
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:
- Functional Bootloader
- Boots from FAT12 floppy image
- Finds and loads KERNEL.BIN
- Jumps to loaded kernel
- Demonstrates Understanding
- Code comments explain BPB parsing
- Code comments explain FAT12 calculations
- README documents the boot process
- Tested Correctly
- Works in QEMU
- Works in Bochs
- Kernel updates work (new kernel boots without bootloader change)
- 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 sourcekernel.asm- Test kernel sourceMakefile- Build automationfloppy.img- Bootable imageREADME.md- Documentation and usage instructions