Project 17: Write Your Own Limine/BOOTBOOT-style Bootloader

The CAPSTONE PROJECT of the Bootloader Deep Dive series: Build a complete, professional-grade bootloader that rivals open-source projects like Limine and BOOTBOOT. This is where everything you’ve learned comes together into a production-quality system.


Quick Reference

Attribute Value
Difficulty ★★★★★ Master (CAPSTONE)
Time Estimate 1-3 months
Language C + x86 Assembly (Alternative: Rust no_std)
Prerequisites All previous projects (1-16), significant C/systems programming experience
Key Topics BIOS/UEFI dual support, FAT32/ext2 filesystems, ELF parsing, boot protocol design, graphical boot menu, ACPI RSDP, memory mapping
Portfolio Value ★★★★★ Startup-Ready / Resume Gold
Real-World Equivalent Limine, BOOTBOOT, systemd-boot, GRUB (simplified)

1. Learning Objectives

By completing this capstone project, you will:

  1. Unify BIOS and UEFI Boot Paths: Design and implement a firmware abstraction layer that supports both legacy BIOS and modern UEFI from a single codebase, understanding why projects like Limine adopt this approach.

  2. Implement Multiple Filesystem Drivers: Build complete FAT32 and ext2 filesystem implementations from scratch, including directory traversal, long filename support, and file reading.

  3. Design and Implement a Boot Protocol: Create a standardized data structure that passes essential system information (memory map, framebuffer, ACPI tables) to the kernel, similar to Limine Protocol or Stivale2.

  4. Build a Graphical Boot Menu: Implement a visually appealing boot menu with keyboard navigation, configuration file parsing, and multiple boot entry support.

  5. Master ELF Loading: Parse and load complex ELF64 kernels with proper segment handling, BSS zeroing, and higher-half kernel support.

  6. Handle Hardware Discovery: Detect and pass ACPI RSDP, memory maps (E820/UEFI), framebuffer information, and other hardware details to the kernel.

  7. Write Production-Quality Systems Code: Apply defensive programming, error handling, and modular architecture patterns in a constrained environment.

  8. Create Portfolio-Ready Documentation: Document your boot protocol, architecture decisions, and provide a complete README suitable for open-source release.


2. Theoretical Foundation

2.1 Core Concepts

Why Modern Bootloaders Exist

The gap between firmware (BIOS/UEFI) and operating systems is wider than it appears. Firmware provides basic services, but kernels have specific requirements:

+-----------------------------------------------------------------------------+
|                    THE BOOTLOADER'S ROLE IN MODERN SYSTEMS                   |
+-----------------------------------------------------------------------------+
|                                                                              |
|   FIRMWARE (BIOS/UEFI)              BOOTLOADER                 KERNEL       |
|   +-------------------+          +-------------------+    +---------------+  |
|   | - Hardware init   |          | - Unified API     |    | - Expects:    |  |
|   | - Memory detect   |   --->   | - Filesystem      |    |   - Memory map|  |
|   | - Basic I/O       |          |   abstraction     |    |   - Framebuf  |  |
|   | - Load 1 file     |          | - ELF loading     |    |   - ACPI RSDP |  |
|   | - Different APIs! |          | - Config parsing  |    |   - Modules   |  |
|   +-------------------+          | - Boot menu       |    |   - Cmdline   |  |
|                                  | - Protocol def    |    +---------------+  |
|   BIOS: INT 13h, real mode       +-------------------+                       |
|   UEFI: EFI protocols, 64-bit                                                |
|                                                                              |
|   PROBLEM: Kernels don't want to handle both BIOS and UEFI directly        |
|   SOLUTION: Bootloader abstracts firmware, presents unified boot protocol   |
|                                                                              |
+-----------------------------------------------------------------------------+

The Limine Boot Protocol

Limine defines a request-response protocol where the kernel places request structures in its binary, and the bootloader fills them in:

+-----------------------------------------------------------------------------+
|                         LIMINE PROTOCOL ARCHITECTURE                          |
+-----------------------------------------------------------------------------+
|                                                                              |
|   KERNEL BINARY (ELF):                                                       |
|   +-----------------------------------------------------------------------+ |
|   | .limine_requests section:                                              | |
|   |                                                                        | |
|   |   struct limine_memmap_request {                                       | |
|   |       uint64_t id[4];           // Magic identifier                    | |
|   |       uint64_t revision;        // Request revision                    | |
|   |       struct limine_memmap_response *response;  // NULL initially     | |
|   |   };                                                                   | |
|   |                                                                        | |
|   |   struct limine_framebuffer_request { ... };                           | |
|   |   struct limine_rsdp_request { ... };                                  | |
|   |   struct limine_kernel_file_request { ... };                           | |
|   |   struct limine_module_request { ... };                                | |
|   |                                                                        | |
|   +-----------------------------------------------------------------------+ |
|                                                                              |
|   BOOTLOADER PROCESS:                                                        |
|   1. Load kernel ELF into memory                                            |
|   2. Scan for .limine_requests section or search for magic IDs              |
|   3. For each request found:                                                |
|      a. Allocate response structure                                         |
|      b. Fill in response data (memory map, framebuffer info, etc.)         |
|      c. Set request->response pointer to response structure                 |
|   4. Jump to kernel entry point                                             |
|                                                                              |
|   KERNEL STARTUP:                                                            |
|   ```c                                                                       |
|   if (memmap_request.response != NULL) {                                    |
|       // Bootloader provided memory map                                     |
|       for (int i = 0; i < memmap_request.response->entry_count; i++) {     |
|           // Process memory map entries                                     |
|       }                                                                      |
|   }                                                                          |
|   ```                                                                        |
|                                                                              |
|   BENEFITS:                                                                  |
|   - Kernel explicitly requests only what it needs                           |
|   - Backward compatible (new requests can be added)                         |
|   - Self-documenting (requests are in kernel source)                        |
|   - No fixed boot info structure address                                    |
|                                                                              |
+-----------------------------------------------------------------------------+

BOOTBOOT Protocol (Alternative Approach)

BOOTBOOT takes a simpler, fixed-structure approach:

+-----------------------------------------------------------------------------+
|                         BOOTBOOT PROTOCOL STRUCTURE                          |
+-----------------------------------------------------------------------------+
|                                                                              |
|   BOOTBOOT structure (fixed layout, passed at known address):                |
|                                                                              |
|   typedef struct {                                                           |
|       uint8_t  magic[4];           // "BOOT"                                |
|       uint32_t size;               // Size of this structure                |
|       uint8_t  protocol;           // Protocol version                      |
|       uint8_t  fb_type;            // Framebuffer type                      |
|       uint16_t numcores;           // Number of CPU cores                   |
|       uint16_t bspid;              // Bootstrap processor ID                |
|       int16_t  timezone;           // Timezone offset                       |
|       uint8_t  datetime[8];        // Current date/time                     |
|       uint64_t initrd_ptr;         // Initial ramdisk pointer               |
|       uint64_t initrd_size;        // Initial ramdisk size                  |
|       uint64_t fb_ptr;             // Framebuffer pointer                   |
|       uint32_t fb_size;            // Framebuffer size                      |
|       uint32_t fb_width;           // Framebuffer width                     |
|       uint32_t fb_height;          // Framebuffer height                    |
|       uint32_t fb_scanline;        // Bytes per scanline                    |
|                                                                              |
|       // Memory map follows structure                                        |
|       MMapEnt  mmap[];                                                       |
|   } __attribute__((packed)) BOOTBOOT;                                        |
|                                                                              |
|   Memory map entry:                                                          |
|   typedef struct {                                                           |
|       uint64_t ptr;                // Base address                          |
|       uint64_t size;               // Size in bytes (and type in upper 4)  |
|   } MMapEnt;                                                                 |
|                                                                              |
|   BOOTBOOT LOCATIONS:                                                        |
|   - BOOTBOOT structure: 0xFFFFFFFFFFE00000 (higher half)                    |
|   - Environment string:  0xFFFFFFFFFFE01000                                 |
|   - Framebuffer:         0xFFFFFFFFFE000000                                 |
|   - Kernel loaded at:    0xFFFFFFFFFFE02000 (or kernel-specified)          |
|                                                                              |
+-----------------------------------------------------------------------------+

Firmware Abstraction Layer Design

Supporting both BIOS and UEFI requires careful abstraction:

+-----------------------------------------------------------------------------+
|                       FIRMWARE ABSTRACTION ARCHITECTURE                      |
+-----------------------------------------------------------------------------+
|                                                                              |
|   +-----------------------------------------------------------------------+ |
|   |                        BOOTLOADER CORE                                 | |
|   |                                                                        | |
|   |   +-------------------+  +-------------------+  +-------------------+  | |
|   |   | Config Parser     |  | Boot Menu UI      |  | Kernel Loader     |  | |
|   |   +-------------------+  +-------------------+  +-------------------+  | |
|   |                                                                        | |
|   |   +-------------------+  +-------------------+  +-------------------+  | |
|   |   | FAT32 Driver      |  | ext2 Driver       |  | ELF Parser        |  | |
|   |   +-------------------+  +-------------------+  +-------------------+  | |
|   +-----------------------------------------------------------------------+ |
|                              |                                              |
|                              | Unified HAL API                              |
|                              v                                              |
|   +-----------------------------------------------------------------------+ |
|   |                    HARDWARE ABSTRACTION LAYER (HAL)                    | |
|   |                                                                        | |
|   |   typedef struct {                                                     | |
|   |       // Console                                                       | |
|   |       void (*print)(const char *s);                                    | |
|   |       void (*clear)(void);                                             | |
|   |       int  (*getchar)(void);                                           | |
|   |                                                                        | |
|   |       // Disk                                                          | |
|   |       int  (*read_sectors)(uint64_t lba, uint32_t count, void *buf);  | |
|   |                                                                        | |
|   |       // Memory                                                        | |
|   |       void* (*alloc_pages)(size_t count);                              | |
|   |       void  (*free_pages)(void *ptr, size_t count);                    | |
|   |       int   (*get_memory_map)(mmap_entry_t *buf, size_t *count);      | |
|   |                                                                        | |
|   |       // Graphics                                                      | |
|   |       int   (*get_framebuffer)(framebuffer_t *fb);                    | |
|   |       int   (*set_video_mode)(uint32_t width, uint32_t height);       | |
|   |                                                                        | |
|   |       // System                                                        | |
|   |       void* (*get_acpi_rsdp)(void);                                    | |
|   |       void  (*exit_boot_services)(void);                               | |
|   |   } hal_t;                                                             | |
|   |                                                                        | |
|   +-----------------------------------------------------------------------+ |
|                |                                   |                        |
|                v                                   v                        |
|   +-------------------------+         +-------------------------+           |
|   |     BIOS HAL            |         |     UEFI HAL            |           |
|   +-------------------------+         +-------------------------+           |
|   | - INT 10h for video     |         | - GOP for graphics      |           |
|   | - INT 13h for disk      |         | - Block I/O protocol    |           |
|   | - INT 15h E820 for mmap |         | - GetMemoryMap()        |           |
|   | - VBE for framebuffer   |         | - ConfigurationTable    |           |
|   | - Direct A20, GDT, etc  |         | - ExitBootServices()    |           |
|   +-------------------------+         +-------------------------+           |
|                                                                              |
+-----------------------------------------------------------------------------+

FAT32 Filesystem Structure

Understanding FAT32 is essential for reading configuration and kernel files:

+-----------------------------------------------------------------------------+
|                          FAT32 FILESYSTEM LAYOUT                             |
+-----------------------------------------------------------------------------+
|                                                                              |
|   DISK LAYOUT:                                                               |
|   +-------+-------------+-------------+-------------+-----------------------+|
|   | MBR   | Reserved    | FAT 1       | FAT 2       | Data Region           ||
|   | or    | Sectors     | (File       | (Backup)    | (Clusters)            ||
|   | GPT   | (inc. BPB)  |  Alloc Tab) |             |                       ||
|   +-------+-------------+-------------+-------------+-----------------------+|
|           |             |                           |                        |
|           v             v                           v                        |
|   BIOS PARAMETER BLOCK (BPB) at sector 0:          DATA CLUSTERS:           |
|   +----------------------------------+             +---------------------+   |
|   | Offset | Size | Field            |             | Cluster 2           |   |
|   |--------|------|------------------|             | (Root dir for FAT32)|   |
|   | 0x00   | 3    | Jump instruction |             +---------------------+   |
|   | 0x03   | 8    | OEM Name         |             | Cluster 3           |   |
|   | 0x0B   | 2    | Bytes per sector |             +---------------------+   |
|   | 0x0D   | 1    | Sectors/cluster  |             | Cluster 4           |   |
|   | 0x0E   | 2    | Reserved sectors |             +---------------------+   |
|   | 0x10   | 1    | Number of FATs   |             | ...                 |   |
|   | 0x11   | 2    | Root entries (0) |                                       |
|   | 0x13   | 2    | Total sectors 16 |                                       |
|   | 0x15   | 1    | Media type       |                                       |
|   | 0x16   | 2    | FAT size 16 (0)  |                                       |
|   | 0x18   | 2    | Sectors per track|                                       |
|   | 0x1A   | 2    | Number of heads  |                                       |
|   | 0x1C   | 4    | Hidden sectors   |                                       |
|   | 0x20   | 4    | Total sectors 32 |                                       |
|   | 0x24   | 4    | FAT size 32      |  <-- FAT32 specific                   |
|   | 0x28   | 2    | Flags            |                                       |
|   | 0x2A   | 2    | Version          |                                       |
|   | 0x2C   | 4    | Root cluster     |  <-- Root dir start cluster          |
|   | 0x30   | 2    | FSInfo sector    |                                       |
|   | 0x32   | 2    | Backup boot sect |                                       |
|   | 0x34   | 12   | Reserved         |                                       |
|   | 0x40   | 1    | Drive number     |                                       |
|   | 0x41   | 1    | Reserved         |                                       |
|   | 0x42   | 1    | Boot signature   |                                       |
|   | 0x43   | 4    | Volume serial    |                                       |
|   | 0x47   | 11   | Volume label     |                                       |
|   | 0x52   | 8    | FS Type string   |                                       |
|   +----------------------------------+                                       |
|                                                                              |
|   FAT ENTRY FORMAT (32-bit):                                                 |
|   +-------------------------------------------------------------------+     |
|   | Bits 31-28 | Reserved (usually 0)                                  |     |
|   | Bits 27-0  | Next cluster number or special value                  |     |
|   |            |                                                        |     |
|   | Special values:                                                    |     |
|   | 0x00000000 = Free cluster                                          |     |
|   | 0x00000001 = Reserved                                              |     |
|   | 0x00000002 - 0x0FFFFFEF = Data cluster                             |     |
|   | 0x0FFFFFF0 - 0x0FFFFFF6 = Reserved                                 |     |
|   | 0x0FFFFFF7 = Bad cluster                                           |     |
|   | 0x0FFFFFF8 - 0x0FFFFFFF = End of chain (EOF)                       |     |
|   +-------------------------------------------------------------------+     |
|                                                                              |
|   DIRECTORY ENTRY (32 bytes):                                                |
|   +-------------------------------------------------------------------+     |
|   | Offset | Size | Field                                              |     |
|   |--------|------|---------------------------------------------------|     |
|   | 0x00   | 8    | Short name (8.3)                                  |     |
|   | 0x08   | 3    | Extension                                         |     |
|   | 0x0B   | 1    | Attributes                                        |     |
|   | 0x0C   | 1    | Reserved                                          |     |
|   | 0x0D   | 1    | Create time (10ms)                                |     |
|   | 0x0E   | 2    | Create time                                       |     |
|   | 0x10   | 2    | Create date                                       |     |
|   | 0x12   | 2    | Access date                                       |     |
|   | 0x14   | 2    | First cluster HIGH                                |     |
|   | 0x16   | 2    | Modify time                                       |     |
|   | 0x18   | 2    | Modify date                                       |     |
|   | 0x1A   | 2    | First cluster LOW                                 |     |
|   | 0x1C   | 4    | File size                                         |     |
|   +-------------------------------------------------------------------+     |
|                                                                              |
+-----------------------------------------------------------------------------+

ext2 Filesystem Structure

For Linux compatibility, understanding ext2 is crucial:

+-----------------------------------------------------------------------------+
|                          EXT2 FILESYSTEM LAYOUT                              |
+-----------------------------------------------------------------------------+
|                                                                              |
|   DISK LAYOUT:                                                               |
|   +----------+----------+----------+----------+----------+----------+       |
|   | Boot     | Block    | Block    | Block    | Block    | ...      |       |
|   | Block    | Group 0  | Group 1  | Group 2  | Group 3  |          |       |
|   | (1KB)    |          |          |          |          |          |       |
|   +----------+----------+----------+----------+----------+----------+       |
|                                                                              |
|   SUPERBLOCK (at byte 1024, in every block group):                          |
|   +-----------------------------------------------------------------------+ |
|   | Offset | Size | Field                                                  | |
|   |--------|------|--------------------------------------------------------| |
|   | 0x00   | 4    | s_inodes_count - Total inodes                          | |
|   | 0x04   | 4    | s_blocks_count - Total blocks                          | |
|   | 0x08   | 4    | s_r_blocks_count - Reserved blocks                     | |
|   | 0x0C   | 4    | s_free_blocks_count                                    | |
|   | 0x10   | 4    | s_free_inodes_count                                    | |
|   | 0x14   | 4    | s_first_data_block - First data block (0 or 1)         | |
|   | 0x18   | 4    | s_log_block_size - Block size = 1024 << value          | |
|   | 0x1C   | 4    | s_log_frag_size                                        | |
|   | 0x20   | 4    | s_blocks_per_group                                     | |
|   | 0x24   | 4    | s_frags_per_group                                      | |
|   | 0x28   | 4    | s_inodes_per_group                                     | |
|   | 0x2C   | 4    | s_mtime - Mount time                                   | |
|   | 0x30   | 4    | s_wtime - Write time                                   | |
|   | 0x38   | 2    | s_magic - 0xEF53                                       | |
|   | 0x3A   | 2    | s_state - FS state                                     | |
|   | 0x58   | 4    | s_inode_size - Inode structure size                    | |
|   +-----------------------------------------------------------------------+ |
|                                                                              |
|   BLOCK GROUP DESCRIPTOR (32 bytes each):                                   |
|   +-----------------------------------------------------------------------+ |
|   | Offset | Size | Field                                                  | |
|   |--------|------|--------------------------------------------------------| |
|   | 0x00   | 4    | bg_block_bitmap - Block bitmap block                   | |
|   | 0x04   | 4    | bg_inode_bitmap - Inode bitmap block                   | |
|   | 0x08   | 4    | bg_inode_table - First inode table block               | |
|   | 0x0C   | 2    | bg_free_blocks_count                                   | |
|   | 0x0E   | 2    | bg_free_inodes_count                                   | |
|   | 0x10   | 2    | bg_used_dirs_count                                     | |
|   +-----------------------------------------------------------------------+ |
|                                                                              |
|   INODE STRUCTURE (128 bytes minimum):                                      |
|   +-----------------------------------------------------------------------+ |
|   | Offset | Size | Field                                                  | |
|   |--------|------|--------------------------------------------------------| |
|   | 0x00   | 2    | i_mode - File type and permissions                     | |
|   | 0x02   | 2    | i_uid - Owner user ID                                  | |
|   | 0x04   | 4    | i_size - File size (lower 32 bits)                     | |
|   | 0x08   | 4    | i_atime - Access time                                  | |
|   | 0x0C   | 4    | i_ctime - Creation time                                | |
|   | 0x10   | 4    | i_mtime - Modification time                            | |
|   | 0x14   | 4    | i_dtime - Deletion time                                | |
|   | 0x18   | 2    | i_gid - Group ID                                       | |
|   | 0x1A   | 2    | i_links_count - Hard link count                        | |
|   | 0x1C   | 4    | i_blocks - 512-byte blocks count                       | |
|   | 0x20   | 4    | i_flags                                                | |
|   | 0x24   | 4    | i_osd1                                                 | |
|   | 0x28   | 60   | i_block[15] - Block pointers:                          | |
|   |        |      |   [0-11]  Direct blocks                                | |
|   |        |      |   [12]    Indirect block                               | |
|   |        |      |   [13]    Double indirect block                        | |
|   |        |      |   [14]    Triple indirect block                        | |
|   +-----------------------------------------------------------------------+ |
|                                                                              |
|   DIRECTORY ENTRY:                                                          |
|   +-----------------------------------------------------------------------+ |
|   | Offset | Size | Field                                                  | |
|   |--------|------|--------------------------------------------------------| |
|   | 0x00   | 4    | inode - Inode number                                   | |
|   | 0x04   | 2    | rec_len - Directory entry length                       | |
|   | 0x06   | 1    | name_len - Name length                                 | |
|   | 0x07   | 1    | file_type - File type                                  | |
|   | 0x08   | n    | name - File name (not null-terminated)                 | |
|   +-----------------------------------------------------------------------+ |
|                                                                              |
|   ROOT INODE: Always inode 2                                                |
|                                                                              |
+-----------------------------------------------------------------------------+

Boot Menu Architecture

A graphical boot menu requires careful UI design:

+-----------------------------------------------------------------------------+
|                         BOOT MENU UI ARCHITECTURE                            |
+-----------------------------------------------------------------------------+
|                                                                              |
|   SCREEN LAYOUT (text mode 80x25 or graphical):                             |
|   +-----------------------------------------------------------------------+ |
|   | +-------------------------------------------------------------------+ | |
|   | |                    MyBootloader v1.0                               | | |
|   | +-------------------------------------------------------------------+ | |
|   | |                                                                    | | |
|   | |   Select an operating system to boot:                              | | |
|   | |                                                                    | | |
|   | |   +--------------------------------------------------------------+ | | |
|   | |   | > Arch Linux                                             [1] | | | |
|   | |   +--------------------------------------------------------------+ | | |
|   | |   |   Windows 11 (via chainload)                             [2] | | | |
|   | |   +--------------------------------------------------------------+ | | |
|   | |   |   Arch Linux (fallback initramfs)                        [3] | | | |
|   | |   +--------------------------------------------------------------+ | | |
|   | |   |   UEFI Shell                                             [4] | | | |
|   | |   +--------------------------------------------------------------+ | | |
|   | |                                                                    | | |
|   | |   Use arrow keys to select, Enter to boot                          | | |
|   | |   Press 'e' to edit, 'c' for command line                          | | |
|   | |                                                                    | | |
|   | |   Timeout: 5 seconds                                               | | |
|   | +-------------------------------------------------------------------+ | |
|   | |  F1=Help  F2=Settings  TAB=Edit                                   | | |
|   | +-------------------------------------------------------------------+ | |
|   +-----------------------------------------------------------------------+ |
|                                                                              |
|   DATA STRUCTURES:                                                          |
|   ```c                                                                       |
|   typedef struct {                                                           |
|       char name[64];              // Display name                            |
|       char kernel_path[256];      // Path to kernel                          |
|       char initrd_path[256];      // Path to initrd (optional)               |
|       char cmdline[512];          // Kernel command line                     |
|       boot_type_t type;           // BOOT_ELF, BOOT_LINUX, BOOT_CHAINLOAD   |
|       char chainload_path[256];   // For chainloading                        |
|   } boot_entry_t;                                                            |
|                                                                              |
|   typedef struct {                                                           |
|       char title[64];             // Menu title                              |
|       uint32_t timeout;           // Timeout in seconds (0 = no timeout)    |
|       uint32_t default_entry;     // Default entry index                     |
|       uint32_t entry_count;       // Number of entries                       |
|       boot_entry_t entries[16];   // Boot entries                            |
|       uint32_t resolution_x;      // Preferred resolution                    |
|       uint32_t resolution_y;                                                 |
|       bool verbose;               // Verbose boot messages                   |
|   } boot_config_t;                                                           |
|   ```                                                                        |
|                                                                              |
|   INPUT HANDLING:                                                            |
|   - UP/DOWN: Navigate entries                                                |
|   - ENTER: Boot selected entry                                               |
|   - 1-9: Direct entry selection                                              |
|   - 'e': Edit entry (temporary)                                              |
|   - 'c': Command line mode                                                   |
|   - ESC: Cancel/return                                                       |
|   - TAB: Autocomplete (in edit mode)                                         |
|                                                                              |
+-----------------------------------------------------------------------------+

2.2 Why This Matters

This capstone project synthesizes everything you’ve learned into a production-quality system:

Technical Mastery:

  • Complete understanding of x86 boot process (BIOS and UEFI)
  • Filesystem internals (FAT32, ext2)
  • Binary format parsing (ELF)
  • Low-level graphics programming
  • Hardware abstraction design
  • Protocol and ABI design

Career Impact:

  • Resume Differentiator: Very few developers have built a complete bootloader
  • Interview Confidence: Deep answers to systems programming questions
  • Open Source Foundation: Could become a real open-source project
  • Embedded Systems Ready: Skills transfer directly to firmware development

Real-World Relevance:

  • Understand how Limine, GRUB, systemd-boot work internally
  • Debug boot failures at the deepest level
  • Contribute to boot-related open source projects
  • Develop custom boot solutions for specialized systems

2.3 Historical Context

Modern bootloaders evolved from simple MBR loaders:

Timeline of Bootloader Evolution:

1981: IBM PC - Simple MBR bootstrap, loads OS directly
1983: MS-DOS - IO.SYS/MSDOS.SYS loading from FAT
1994: LILO (Linux Loader) - First configurable Linux bootloader
1995: GRUB Legacy - GNU Grand Unified Bootloader
2000: U-Boot - Universal bootloader for embedded
2005: UEFI 2.0 - Modern firmware specification
2006: GRUB2 - Complete rewrite with scripting
2019: Limine - Modern, minimal boot protocol
2020: systemd-boot - Simple UEFI boot manager
2023: Your Bootloader - Understanding it all!

2.4 Common Misconceptions

Misconception 1: “Supporting BIOS and UEFI means twice the code”

  • Reality: With proper abstraction, most code is shared. Only the HAL implementation differs.

Misconception 2: “You need to write a full filesystem driver”

  • Reality: You only need read support. Writing, journaling, etc. are unnecessary for a bootloader.

Misconception 3: “Boot protocols are arbitrary”

  • Reality: Good boot protocols are carefully designed for backward compatibility and extensibility.

Misconception 4: “Graphics in a bootloader is too complex”

  • Reality: VBE (BIOS) and GOP (UEFI) provide simple framebuffer access. No GPU driver needed.

Misconception 5: “This project is too large for one person”

  • Reality: Limine and BOOTBOOT are primarily single-author projects. Focus and iteration make it achievable.

3. Project Specification

3.1 What You Will Build

A complete bootloader with these capabilities:

+-----------------------------------------------------------------------------+
|                      YOUR BOOTLOADER FEATURE SET                             |
+-----------------------------------------------------------------------------+
|                                                                              |
|   FIRMWARE SUPPORT:                                                          |
|   [x] Legacy BIOS boot (MBR-based)                                          |
|   [x] UEFI boot (ESP-based)                                                 |
|   [x] Common codebase with firmware abstraction                             |
|                                                                              |
|   FILESYSTEM SUPPORT:                                                        |
|   [x] FAT32 (full long filename support)                                    |
|   [x] ext2 (no journal, read-only)                                          |
|   [x] ISO9660 (optional, for CD boot)                                       |
|                                                                              |
|   KERNEL LOADING:                                                            |
|   [x] ELF64 parsing with segment loading                                    |
|   [x] Higher-half kernel support                                            |
|   [x] Linux kernel bzImage support (optional)                               |
|   [x] Multiboot2 header detection (optional)                                |
|                                                                              |
|   BOOT PROTOCOL:                                                             |
|   [x] Custom boot protocol (Limine-inspired or custom)                      |
|   [x] Memory map passing                                                    |
|   [x] Framebuffer information                                               |
|   [x] ACPI RSDP pointer                                                     |
|   [x] Kernel command line                                                   |
|   [x] Module/initrd loading                                                 |
|                                                                              |
|   USER INTERFACE:                                                            |
|   [x] Configuration file parsing (limine.cfg style)                         |
|   [x] Graphical boot menu                                                   |
|   [x] Keyboard navigation                                                   |
|   [x] Timeout with default entry                                            |
|   [x] Boot splash/logo (optional)                                           |
|                                                                              |
|   ADVANCED FEATURES:                                                         |
|   [x] Chainloading other bootloaders                                        |
|   [x] UEFI Secure Boot support (optional)                                   |
|   [x] Multiple monitor support (optional)                                   |
|                                                                              |
+-----------------------------------------------------------------------------+

3.2 Functional Requirements

ID Requirement Priority Notes
FR-01 Boot successfully on BIOS systems Must Have Stage 1 MBR + Stage 2
FR-02 Boot successfully on UEFI systems Must Have EFI application
FR-03 Read FAT32 filesystem Must Have Config + kernel files
FR-04 Read ext2 filesystem Should Have Linux root partitions
FR-05 Parse configuration file Must Have Boot entries, settings
FR-06 Display graphical boot menu Must Have Entry selection
FR-07 Handle keyboard input Must Have Navigation, selection
FR-08 Parse ELF64 executables Must Have Kernel loading
FR-09 Load kernel segments correctly Must Have Code, data, BSS
FR-10 Pass memory map to kernel Must Have E820/UEFI map conversion
FR-11 Set up framebuffer Must Have VBE/GOP
FR-12 Pass framebuffer info to kernel Must Have Address, size, format
FR-13 Find and pass ACPI RSDP Must Have Hardware discovery
FR-14 Support kernel command line Should Have Boot parameters
FR-15 Load initrd/modules Should Have Initial ramdisk
FR-16 Support higher-half kernels Should Have Address space setup
FR-17 Implement timeout with default Should Have Auto-boot
FR-18 Chainload other bootloaders Nice to Have Windows boot
FR-19 Long filename support in FAT32 Nice to Have Better usability
FR-20 UEFI Secure Boot signing Nice to Have Enterprise use

3.3 Non-Functional Requirements

ID Requirement Target
NFR-01 Stage 1 size < 446 bytes (MBR code area)
NFR-02 Stage 2 BIOS size < 64 KB (reasonable)
NFR-03 UEFI application size < 512 KB
NFR-04 Boot time (QEMU) < 3 seconds to menu
NFR-05 Supported resolutions 640x480 to 1920x1080
NFR-06 Memory usage < 16 MB during boot
NFR-07 Configuration file Human-readable, < 64 KB
NFR-08 Code documentation Comprehensive comments
NFR-09 Error messages Clear, actionable

3.4 Example Configuration File

# myboot.cfg - Boot configuration file
# Place in /boot/ or /EFI/BOOT/ on your boot partition

TIMEOUT=5
DEFAULT_ENTRY=0
GRAPHICS_MODE=1024x768x32

:Arch Linux
    PROTOCOL=limine
    KERNEL_PATH=/boot/vmlinuz-linux
    KERNEL_CMDLINE=root=/dev/sda2 ro quiet
    MODULE_PATH=/boot/initramfs-linux.img

:Arch Linux (Fallback)
    PROTOCOL=limine
    KERNEL_PATH=/boot/vmlinuz-linux
    KERNEL_CMDLINE=root=/dev/sda2 ro
    MODULE_PATH=/boot/initramfs-linux-fallback.img

:Windows 11
    PROTOCOL=chainload
    CHAINLOAD_PATH=/EFI/Microsoft/Boot/bootmgfw.efi

:Custom Kernel (Development)
    PROTOCOL=limine
    KERNEL_PATH=/boot/mykernel.elf
    KERNEL_CMDLINE=debug serial=ttyS0

3.5 Example Boot Output

Boot Menu (Text Mode):

+==============================================================================+
|                          MyBootloader v1.0                                   |
+==============================================================================+
|                                                                              |
|   Select an operating system to boot:                                        |
|                                                                              |
|   +------------------------------------------------------------------------+ |
|   | > Arch Linux                                                        [1]| |
|   +------------------------------------------------------------------------+ |
|   |   Arch Linux (Fallback)                                             [2]| |
|   +------------------------------------------------------------------------+ |
|   |   Windows 11                                                        [3]| |
|   +------------------------------------------------------------------------+ |
|   |   Custom Kernel (Development)                                       [4]| |
|   +------------------------------------------------------------------------+ |
|                                                                              |
|   Press ENTER to boot, 'e' to edit, 'c' for console                          |
|                                                                              |
|   Automatic boot in 5 seconds...                                             |
+==============================================================================+

Boot Process Output (Verbose Mode):

MyBootloader v1.0
================

[INFO] Firmware: UEFI 2.7, Vendor: American Megatrends
[INFO] Detected boot partition: FAT32, 512 MB
[INFO] Configuration loaded: /EFI/BOOT/myboot.cfg
[INFO] Found 4 boot entries

User selected: Arch Linux

[INFO] Loading kernel: /boot/vmlinuz-linux (8.2 MB)
[INFO] Kernel format: Linux bzImage
[INFO] Loading initrd: /boot/initramfs-linux.img (32.1 MB)
[INFO] Setting up framebuffer: 1920x1080x32 @ 0xE0000000
[INFO] Memory map: 15 entries, 16251 MB usable
[INFO] ACPI RSDP found at 0x000E0000

[INFO] Preparing kernel handoff...
[INFO] Exiting boot services...
[INFO] Jumping to kernel entry point 0x1000000

[Kernel takes over]

3.6 Real World Outcome

Upon completion, you will have:

  1. A Working Bootloader: Can boot real operating systems (your own kernel, Linux with work)
  2. Open Source Ready: Well-documented codebase suitable for GitHub release
  3. Portfolio Centerpiece: Demonstrates deep systems programming mastery
  4. Extensible Foundation: Can add new features (network boot, Secure Boot, etc.)
  5. Industry Knowledge: Understanding on par with professional firmware developers

4. Solution Architecture

4.1 High-Level Design

+-----------------------------------------------------------------------------+
|                    COMPLETE BOOTLOADER ARCHITECTURE                          |
+-----------------------------------------------------------------------------+
|                                                                              |
|   +-----------------------------------------------------------------+       |
|   |                      BOOT PARTITION                              |       |
|   |   /EFI/BOOT/BOOTX64.EFI       (UEFI bootloader)                 |       |
|   |   /boot/myboot-bios.bin       (BIOS stage 2)                    |       |
|   |   /boot/myboot.cfg            (Configuration)                   |       |
|   |   /boot/vmlinuz-*             (Kernels)                         |       |
|   |   /boot/initramfs-*           (Initrd images)                   |       |
|   +-----------------------------------------------------------------+       |
|                                    |                                         |
|                                    v                                         |
|   +-----------------------------------------------------------------+       |
|   |                    BOOTLOADER PHASES                             |       |
|   |                                                                  |       |
|   |  BIOS PATH:                    UEFI PATH:                       |       |
|   |  +------------------+          +------------------+              |       |
|   |  | Stage 1 (MBR)    |          | EFI Application  |              |       |
|   |  | - Load stage 2   |          | - Direct entry   |              |       |
|   |  +--------+---------+          +--------+---------+              |       |
|   |           |                             |                        |       |
|   |           v                             v                        |       |
|   |  +------------------+          +------------------+              |       |
|   |  | Stage 2          |          | UEFI Init        |              |       |
|   |  | - Mode setup     |          | - Get services   |              |       |
|   |  | - A20, GDT       |          | - Get GOP/disk   |              |       |
|   |  +--------+---------+          +--------+---------+              |       |
|   |           |                             |                        |       |
|   |           +-------------+---------------+                        |       |
|   |                         |                                        |       |
|   |                         v                                        |       |
|   |              +---------------------+                             |       |
|   |              | Common Bootloader   |                             |       |
|   |              | Core (shared code)  |                             |       |
|   |              +----------+----------+                             |       |
|   |                         |                                        |       |
|   +-----------------------------------------------------------------+       |
|                             |                                                |
|                             v                                                |
|   +-----------------------------------------------------------------+       |
|   |                    BOOTLOADER CORE                               |       |
|   |                                                                  |       |
|   |  +-----------------+  +-----------------+  +-----------------+  |       |
|   |  | Config Parser   |  |  Boot Menu UI   |  | Kernel Loader   |  |       |
|   |  | - INI-style     |  |  - Display      |  | - ELF parser    |  |       |
|   |  | - Entry parse   |  |  - Input        |  | - Segment load  |  |       |
|   |  | - Validation    |  |  - Timeout      |  | - Protocol      |  |       |
|   |  +-----------------+  +-----------------+  +-----------------+  |       |
|   |                                                                  |       |
|   |  +-----------------+  +-----------------+  +-----------------+  |       |
|   |  | FAT32 Driver    |  | ext2 Driver     |  | Protocol Impl   |  |       |
|   |  | - BPB parse     |  | - Superblock    |  | - Memory map    |  |       |
|   |  | - Cluster chain |  | - Inode read    |  | - Framebuffer   |  |       |
|   |  | - Dir traverse  |  | - Block read    |  | - ACPI/modules  |  |       |
|   |  +-----------------+  +-----------------+  +-----------------+  |       |
|   |                                                                  |       |
|   +-----------------------------------------------------------------+       |
|                             |                                                |
|                             v                                                |
|   +-----------------------------------------------------------------+       |
|   |               HARDWARE ABSTRACTION LAYER (HAL)                   |       |
|   |                                                                  |       |
|   |  Interface:              BIOS Impl:           UEFI Impl:        |       |
|   |  +---------------+       +---------------+    +---------------+ |       |
|   |  | hal_print()   |  -->  | INT 10h/VGA   | or | ConOut        | |       |
|   |  | hal_getchar() |  -->  | INT 16h       | or | ConIn         | |       |
|   |  | hal_read_disk |  -->  | INT 13h       | or | BlockIO       | |       |
|   |  | hal_get_mmap()|  -->  | INT 15h E820  | or | GetMemoryMap  | |       |
|   |  | hal_alloc()   |  -->  | Manual        | or | AllocatePages | |       |
|   |  | hal_get_fb()  |  -->  | VBE           | or | GOP           | |       |
|   |  | hal_get_rsdp()|  -->  | Search mem    | or | ConfigTable   | |       |
|   |  | hal_exit_bs() |  -->  | (N/A)         | or | ExitBootSvc   | |       |
|   |  +---------------+       +---------------+    +---------------+ |       |
|   |                                                                  |       |
|   +-----------------------------------------------------------------+       |
|                             |                                                |
|                             v                                                |
|   +-----------------------------------------------------------------+       |
|   |                         KERNEL                                   |       |
|   |   Receives: Boot protocol structure with all system info        |       |
|   +-----------------------------------------------------------------+       |
|                                                                              |
+-----------------------------------------------------------------------------+

4.2 Directory Structure

mybootloader/
├── Makefile                     # Main build file
├── README.md                    # Documentation
├── LICENSE                      # Open source license
│
├── src/
│   ├── common/                  # Shared code (firmware-agnostic)
│   │   ├── boot_protocol.h      # Boot protocol definitions
│   │   ├── boot_protocol.c      # Protocol implementation
│   │   ├── config_parser.h      # Configuration parsing
│   │   ├── config_parser.c
│   │   ├── boot_menu.h          # Boot menu UI
│   │   ├── boot_menu.c
│   │   ├── elf_loader.h         # ELF parsing and loading
│   │   ├── elf_loader.c
│   │   ├── fat32.h              # FAT32 filesystem driver
│   │   ├── fat32.c
│   │   ├── ext2.h               # ext2 filesystem driver
│   │   ├── ext2.c
│   │   ├── string.h             # String utilities (no stdlib)
│   │   ├── string.c
│   │   ├── print.h              # printf-like functions
│   │   ├── print.c
│   │   └── hal.h                # HAL interface definition
│   │
│   ├── bios/                    # BIOS-specific code
│   │   ├── stage1.asm           # MBR bootloader (512 bytes)
│   │   ├── stage2_entry.asm     # Stage 2 entry point
│   │   ├── bios_hal.c           # BIOS HAL implementation
│   │   ├── bios_disk.c          # INT 13h disk access
│   │   ├── bios_video.c         # VBE framebuffer
│   │   ├── bios_memory.c        # E820 memory map
│   │   ├── a20.c                # A20 gate handling
│   │   ├── gdt.c                # GDT setup
│   │   └── linker_bios.ld       # BIOS linker script
│   │
│   └── uefi/                    # UEFI-specific code
│       ├── main.c               # UEFI entry point
│       ├── uefi_hal.c           # UEFI HAL implementation
│       ├── uefi_disk.c          # Block I/O protocol
│       ├── uefi_video.c         # GOP framebuffer
│       ├── uefi_memory.c        # Memory services
│       └── linker_uefi.ld       # UEFI linker script
│
├── include/
│   ├── types.h                  # Basic types (uint32_t, etc.)
│   ├── efi.h                    # UEFI definitions
│   └── elf.h                    # ELF format definitions
│
├── tools/
│   ├── mkimg.sh                 # Disk image creation
│   ├── sign_efi.sh              # Secure Boot signing
│   └── test_kernel/             # Test kernel for verification
│       ├── kernel.c
│       ├── linker.ld
│       └── Makefile
│
├── config/
│   └── myboot.cfg.example       # Example configuration
│
└── build/                       # Build output
    ├── bios/
    │   ├── stage1.bin
    │   └── stage2.bin
    ├── uefi/
    │   └── BOOTX64.EFI
    └── disk.img                 # Test disk image

4.3 Key Data Structures

// =============================================================================
// BOOT PROTOCOL STRUCTURES
// =============================================================================

#define BOOT_PROTOCOL_MAGIC 0x4D59424F4F54  // "MYBOOT"

// Memory map entry (unified format)
typedef struct {
    uint64_t base;
    uint64_t length;
    uint32_t type;      // 1=usable, 2=reserved, 3=ACPI reclaimable,
                        // 4=ACPI NVS, 5=bad, 6=bootloader reclaimable
    uint32_t attributes;
} __attribute__((packed)) mmap_entry_t;

// Framebuffer information
typedef struct {
    uint64_t address;           // Physical address
    uint32_t width;             // Width in pixels
    uint32_t height;            // Height in pixels
    uint32_t pitch;             // Bytes per scanline
    uint16_t bpp;               // Bits per pixel
    uint8_t  red_mask_size;
    uint8_t  red_mask_shift;
    uint8_t  green_mask_size;
    uint8_t  green_mask_shift;
    uint8_t  blue_mask_size;
    uint8_t  blue_mask_shift;
    uint8_t  memory_model;      // 1=RGB, 2=indexed
    uint8_t  reserved;
} __attribute__((packed)) framebuffer_t;

// Module/initrd information
typedef struct {
    uint64_t address;           // Physical address where loaded
    uint64_t size;              // Size in bytes
    char     path[256];         // Original path
    char     cmdline[256];      // Module command line
} __attribute__((packed)) module_t;

// Main boot information structure
typedef struct {
    uint64_t magic;             // BOOT_PROTOCOL_MAGIC
    uint32_t version;           // Protocol version (1.0 = 0x0100)
    uint32_t flags;             // Feature flags

    // Memory map
    uint64_t mmap_address;      // Pointer to mmap_entry_t array
    uint32_t mmap_entry_count;
    uint32_t mmap_entry_size;

    // Framebuffer
    framebuffer_t framebuffer;

    // ACPI
    uint64_t rsdp_address;      // ACPI RSDP physical address

    // Kernel info
    uint64_t kernel_physical;   // Where kernel was loaded
    uint64_t kernel_virtual;    // Kernel virtual base (if higher-half)

    // Command line
    char cmdline[512];

    // Modules
    uint32_t module_count;
    module_t modules[16];

    // Boot time
    uint64_t boot_time;         // Unix timestamp

    // Reserved for future
    uint64_t reserved[8];
} __attribute__((packed)) boot_info_t;


// =============================================================================
// CONFIGURATION STRUCTURES
// =============================================================================

typedef enum {
    BOOT_PROTOCOL_CUSTOM,       // Our protocol
    BOOT_PROTOCOL_LIMINE,       // Limine protocol
    BOOT_PROTOCOL_MULTIBOOT2,   // Multiboot2
    BOOT_PROTOCOL_LINUX,        // Linux boot protocol
    BOOT_PROTOCOL_CHAINLOAD     // Chainload another bootloader
} boot_protocol_t;

typedef struct {
    char name[64];              // Display name in menu
    boot_protocol_t protocol;   // Boot protocol to use
    char kernel_path[256];      // Path to kernel file
    char cmdline[512];          // Kernel command line
    char initrd_path[256];      // Path to initrd (optional)
    char chainload_path[256];   // For chainloading
    uint32_t module_count;
    char module_paths[8][256];  // Additional modules
} boot_entry_t;

typedef struct {
    // Display settings
    char title[64];
    uint32_t timeout_seconds;
    uint32_t default_entry;

    // Graphics
    uint32_t preferred_width;
    uint32_t preferred_height;
    uint32_t preferred_bpp;

    // Behavior
    bool verbose;
    bool serial_console;
    uint32_t serial_port;
    uint32_t serial_baud;

    // Boot entries
    uint32_t entry_count;
    boot_entry_t entries[16];
} config_t;


// =============================================================================
// HARDWARE ABSTRACTION LAYER
// =============================================================================

typedef struct {
    // Console I/O
    void (*print)(const char *str);
    void (*print_char)(char c);
    int  (*get_char)(void);
    bool (*char_available)(void);
    void (*clear_screen)(void);
    void (*set_cursor)(int x, int y);

    // Disk I/O
    int (*read_sectors)(uint8_t disk, uint64_t lba,
                        uint32_t count, void *buffer);
    int (*get_disk_info)(uint8_t disk, uint64_t *total_sectors,
                         uint32_t *sector_size);

    // Memory management
    void* (*alloc_pages)(size_t count);
    void  (*free_pages)(void *ptr, size_t count);
    int   (*get_memory_map)(mmap_entry_t *buffer, size_t *count);
    size_t (*get_total_memory)(void);

    // Graphics
    int  (*set_video_mode)(uint32_t width, uint32_t height, uint32_t bpp);
    int  (*get_framebuffer)(framebuffer_t *fb);
    int  (*query_video_modes)(void *buffer, size_t *count);

    // System information
    void* (*get_acpi_rsdp)(void);
    uint64_t (*get_timestamp)(void);

    // Firmware exit
    void (*exit_boot_services)(void);
    void (*reboot)(void);
    void (*shutdown)(void);
} hal_t;

// Global HAL instance (set during init)
extern hal_t *g_hal;


// =============================================================================
// FILESYSTEM INTERFACE
// =============================================================================

typedef struct fs_ops fs_ops_t;

typedef struct {
    const fs_ops_t *ops;
    void *private_data;
    uint8_t disk;
    uint64_t partition_start;
    uint64_t partition_size;
} filesystem_t;

typedef struct {
    char name[256];
    uint64_t size;
    uint32_t attributes;
    bool is_directory;
} dir_entry_t;

typedef struct {
    filesystem_t *fs;
    uint64_t size;
    uint64_t position;
    void *private_data;
} file_t;

struct fs_ops {
    const char *name;
    int (*mount)(filesystem_t *fs);
    void (*unmount)(filesystem_t *fs);
    int (*open)(filesystem_t *fs, const char *path, file_t *file);
    void (*close)(file_t *file);
    int (*read)(file_t *file, void *buffer, size_t size, size_t *bytes_read);
    int (*seek)(file_t *file, uint64_t offset);
    int (*readdir)(filesystem_t *fs, const char *path,
                   dir_entry_t *entries, size_t *count);
};


// =============================================================================
// ELF STRUCTURES (subset for loading)
// =============================================================================

typedef struct {
    uint8_t  e_ident[16];
    uint16_t e_type;
    uint16_t e_machine;
    uint32_t e_version;
    uint64_t e_entry;
    uint64_t e_phoff;
    uint64_t e_shoff;
    uint32_t e_flags;
    uint16_t e_ehsize;
    uint16_t e_phentsize;
    uint16_t e_phnum;
    uint16_t e_shentsize;
    uint16_t e_shnum;
    uint16_t e_shstrndx;
} __attribute__((packed)) Elf64_Ehdr;

typedef struct {
    uint32_t p_type;
    uint32_t p_flags;
    uint64_t p_offset;
    uint64_t p_vaddr;
    uint64_t p_paddr;
    uint64_t p_filesz;
    uint64_t p_memsz;
    uint64_t p_align;
} __attribute__((packed)) Elf64_Phdr;

#define PT_NULL    0
#define PT_LOAD    1
#define PT_DYNAMIC 2
#define PT_INTERP  3
#define PT_NOTE    4

#define PF_X 0x1    // Execute
#define PF_W 0x2    // Write
#define PF_R 0x4    // Read

#define EM_X86_64  62
#define ELFCLASS64 2
#define ELFDATA2LSB 1

4.4 Algorithm Overview

Main Boot Flow

FUNCTION main_boot():
    // Phase 1: Hardware Abstraction Initialization
    IF firmware == BIOS:
        hal = init_bios_hal()
    ELSE IF firmware == UEFI:
        hal = init_uefi_hal()

    // Phase 2: Early Console
    hal->clear_screen()
    hal->print("MyBootloader v1.0\n")

    // Phase 3: Find and Mount Boot Partition
    FOR each disk:
        FOR each partition on disk:
            IF try_mount_fat32(partition) OR try_mount_ext2(partition):
                IF file_exists("/boot/myboot.cfg") OR
                   file_exists("/EFI/BOOT/myboot.cfg"):
                    boot_fs = mounted filesystem
                    BREAK

    IF boot_fs == NULL:
        panic("No bootable filesystem found")

    // Phase 4: Parse Configuration
    config = parse_config(boot_fs, config_path)
    IF config.entry_count == 0:
        panic("No boot entries configured")

    // Phase 5: Display Boot Menu
    selected_entry = display_boot_menu(config)

    // Phase 6: Load Kernel
    entry = config.entries[selected_entry]
    SWITCH entry.protocol:
        CASE BOOT_PROTOCOL_CUSTOM:
        CASE BOOT_PROTOCOL_LIMINE:
            kernel_info = load_elf_kernel(boot_fs, entry.kernel_path)
            IF entry.initrd_path:
                load_module(boot_fs, entry.initrd_path, &boot_info.modules[0])
            prepare_boot_info(&boot_info, kernel_info, entry.cmdline)

        CASE BOOT_PROTOCOL_CHAINLOAD:
            IF firmware == UEFI:
                chainload_efi(entry.chainload_path)
            ELSE:
                chainload_bios(entry.chainload_path)
            RETURN  // Never reaches here

    // Phase 7: Set Up Framebuffer
    IF hal->set_video_mode(config.preferred_width,
                           config.preferred_height,
                           config.preferred_bpp) == SUCCESS:
        hal->get_framebuffer(&boot_info.framebuffer)

    // Phase 8: Gather System Information
    hal->get_memory_map(mmap_buffer, &boot_info.mmap_entry_count)
    boot_info.mmap_address = mmap_buffer
    boot_info.rsdp_address = hal->get_acpi_rsdp()
    boot_info.boot_time = hal->get_timestamp()

    // Phase 9: Exit Boot Services (UEFI only)
    hal->exit_boot_services()

    // Phase 10: Jump to Kernel
    kernel_entry = kernel_info.entry_point
    jump_to_kernel(kernel_entry, &boot_info)

    // Never returns

Configuration Parsing Algorithm

FUNCTION parse_config(fs, path):
    file = fs->open(path)
    content = fs->read_all(file)
    fs->close(file)

    config = new config_t()
    current_entry = NULL

    FOR each line in content:
        line = trim_whitespace(line)

        // Skip comments and empty lines
        IF line[0] == '#' OR line[0] == '\0':
            CONTINUE

        // Global setting
        IF line starts with "TIMEOUT=":
            config.timeout_seconds = parse_int(line + 8)
        ELSE IF line starts with "DEFAULT_ENTRY=":
            config.default_entry = parse_int(line + 14)
        ELSE IF line starts with "GRAPHICS_MODE=":
            parse_resolution(line + 14, &config.preferred_width,
                           &config.preferred_height, &config.preferred_bpp)

        // New boot entry
        ELSE IF line[0] == ':':
            current_entry = &config.entries[config.entry_count++]
            strcpy(current_entry->name, line + 1)

        // Entry setting
        ELSE IF current_entry != NULL:
            IF line starts with "PROTOCOL=":
                current_entry->protocol = parse_protocol(line + 9)
            ELSE IF line starts with "KERNEL_PATH=":
                strcpy(current_entry->kernel_path, line + 12)
            ELSE IF line starts with "KERNEL_CMDLINE=":
                strcpy(current_entry->cmdline, line + 15)
            ELSE IF line starts with "MODULE_PATH=":
                strcpy(current_entry->initrd_path, line + 12)
            ELSE IF line starts with "CHAINLOAD_PATH=":
                strcpy(current_entry->chainload_path, line + 15)

    RETURN config

ELF Loading Algorithm

FUNCTION load_elf_kernel(fs, path):
    // Read ELF header
    file = fs->open(path)
    read(file, &ehdr, sizeof(Elf64_Ehdr))

    // Validate ELF
    IF ehdr.e_ident[0:4] != [0x7F, 'E', 'L', 'F']:
        ERROR("Not a valid ELF file")
    IF ehdr.e_ident[4] != ELFCLASS64:
        ERROR("Not a 64-bit ELF")
    IF ehdr.e_machine != EM_X86_64:
        ERROR("Not x86-64 architecture")

    // Find memory requirements
    min_addr = UINT64_MAX
    max_addr = 0

    FOR i = 0 TO ehdr.e_phnum:
        seek(file, ehdr.e_phoff + i * ehdr.e_phentsize)
        read(file, &phdr, sizeof(Elf64_Phdr))

        IF phdr.p_type == PT_LOAD:
            IF phdr.p_vaddr < min_addr:
                min_addr = phdr.p_vaddr
            IF phdr.p_vaddr + phdr.p_memsz > max_addr:
                max_addr = phdr.p_vaddr + phdr.p_memsz

    // Allocate memory
    total_size = max_addr - min_addr
    pages = (total_size + 0xFFF) / 0x1000

    IF min_addr >= 0xFFFF800000000000:
        // Higher-half kernel - need to set up paging
        phys_base = hal->alloc_pages(pages)
        setup_higher_half_paging(phys_base, min_addr, pages)
    ELSE:
        // Lower-half kernel - identity map
        phys_base = min_addr
        // Ensure identity mapping exists

    // Load segments
    FOR i = 0 TO ehdr.e_phnum:
        seek(file, ehdr.e_phoff + i * ehdr.e_phentsize)
        read(file, &phdr, sizeof(Elf64_Phdr))

        IF phdr.p_type == PT_LOAD:
            // Calculate load address
            load_addr = phys_base + (phdr.p_vaddr - min_addr)

            // Read file contents
            seek(file, phdr.p_offset)
            read(file, load_addr, phdr.p_filesz)

            // Zero BSS
            IF phdr.p_memsz > phdr.p_filesz:
                memset(load_addr + phdr.p_filesz, 0,
                       phdr.p_memsz - phdr.p_filesz)

    fs->close(file)

    RETURN {
        entry_point: ehdr.e_entry,
        physical_base: phys_base,
        virtual_base: min_addr,
        size: total_size
    }

5. Implementation Guide

5.1 The Core Question You’re Answering

“How do you build a complete, production-quality bootloader that abstracts firmware differences, provides a user-friendly interface, and reliably hands off to any operating system kernel?”

This question encompasses:

  • Firmware abstraction: How do you support both BIOS and UEFI?
  • Filesystem implementation: How do you read files without an OS?
  • Protocol design: How do you communicate with the kernel?
  • User experience: How do you build a menu in bare metal?
  • Reliability: How do you handle errors gracefully?

5.2 Concepts You Must Understand First

Before implementing, verify you understand these concepts:

Concept Self-Check Question Reference
BIOS boot What address does BIOS load the MBR? Why 0x7C00? Projects 1-4
UEFI boot What’s the difference between Boot Services and Runtime Services? Projects 7-8
Protected Mode Why do we need a GDT? What’s the A20 gate? Project 3
Long Mode Why is paging mandatory in Long Mode? Project 6
FAT32 How do you follow a cluster chain? FAT32 spec
ext2 How do you read a file given its inode? ext2 documentation
ELF What’s the difference between sections and segments? Project 8
VBE/GOP How do you get a linear framebuffer? Projects 7, 14
E820/UEFI mmap How do you get the memory map? Project 2
ACPI Where is the RSDP and what does it contain? ACPI spec

5.3 Questions to Guide Your Design

Architecture:

  1. How will you organize code to maximize sharing between BIOS and UEFI?
  2. What’s the interface between the HAL and bootloader core?
  3. How will filesystems be registered and selected?

Configuration:

  1. What file format will you use? INI, custom, or TOML-like?
  2. Where should the config file be located?
  3. What happens if the config file is missing or malformed?

Boot Menu:

  1. Text mode or graphical mode?
  2. How will you handle long entry names?
  3. What keyboard shortcuts are essential?

Kernel Loading:

  1. How will you handle higher-half kernels?
  2. What if the kernel is too large for available memory?
  3. How will you handle relocatable kernels?

Error Handling:

  1. What’s recoverable vs fatal?
  2. How do you display errors without a working console?
  3. Should you fall back to a default entry on error?

5.4 Thinking Exercise

Before coding, design the following on paper:

Exercise 1: Boot Protocol Design

Design your boot protocol structure:

  1. What information does a kernel absolutely need?
  2. What’s nice to have?
  3. How do you make it extensible for future additions?
  4. How do you handle version differences?

Exercise 2: Filesystem Abstraction

Design the filesystem interface:

  1. What operations are needed?
  2. How do you handle different path separators (/ vs \)?
  3. How do you detect filesystem type?

Exercise 3: Error Recovery Flow

Map out what happens when:

  1. Configuration file is missing
  2. Kernel file not found
  3. Memory allocation fails
  4. Video mode setup fails
  5. ExitBootServices fails (UEFI)

5.5 Hints in Layers

Hint 1: Starting Point (Conceptual)

Click to reveal Hint 1 Don't try to build everything at once. Build in phases: **Phase 1**: Get a minimal UEFI application running that prints "Hello" and waits **Phase 2**: Add BIOS stage 1 and stage 2 that also prints "Hello" **Phase 3**: Implement the HAL interface **Phase 4**: Add FAT32 filesystem driver **Phase 5**: Load and execute a simple test ELF **Phase 6**: Add configuration parsing **Phase 7**: Add boot menu **Phase 8**: Add ext2 support **Phase 9**: Polish and add advanced features Each phase should be testable independently.

Hint 2: HAL Implementation Pattern (More Specific)

Click to reveal Hint 2 ```c // hal.h - Interface definition typedef struct hal_ops { void (*print)(const char *s); int (*read_sectors)(uint8_t disk, uint64_t lba, uint32_t count, void *buf); // ... other operations } hal_ops_t; extern const hal_ops_t *g_hal; // bios_hal.c - BIOS implementation static void bios_print(const char *s) { while (*s) { __asm__ volatile( "int $0x10" : : "a"(0x0E00 | *s), "b"(0x0007) ); s++; } } static int bios_read_sectors(uint8_t disk, uint64_t lba, uint32_t count, void *buf) { // Use INT 13h extended read (AH=0x42) struct disk_address_packet { uint8_t size; uint8_t reserved; uint16_t count; uint32_t buffer; uint64_t lba; } __attribute__((packed)) dap = { .size = 16, .reserved = 0, .count = count, .buffer = (uint32_t)buf, // Must be in real mode addressable memory .lba = lba }; int result; __asm__ volatile( "int $0x13" : "=a"(result) : "a"(0x4200), "d"(disk), "S"(&dap) : "memory" ); return (result & 0xFF00) ? -1 : 0; } static const hal_ops_t bios_hal = { .print = bios_print, .read_sectors = bios_read_sectors, // ... }; void init_bios_hal(void) { g_hal = &bios_hal; } // uefi_hal.c - UEFI implementation static EFI_SYSTEM_TABLE *gST; static EFI_BOOT_SERVICES *gBS; static void uefi_print(const char *s) { CHAR16 buf[256]; int i = 0; while (*s && i < 255) { buf[i++] = *s++; } buf[i] = 0; gST->ConOut->OutputString(gST->ConOut, buf); } static int uefi_read_sectors(uint8_t disk, uint64_t lba, uint32_t count, void *buf) { // Use EFI_BLOCK_IO_PROTOCOL EFI_BLOCK_IO_PROTOCOL *bio = get_block_io(disk); if (!bio) return -1; EFI_STATUS status = bio->ReadBlocks( bio, bio->Media->MediaId, lba, count * bio->Media->BlockSize, buf ); return EFI_ERROR(status) ? -1 : 0; } static const hal_ops_t uefi_hal = { .print = uefi_print, .read_sectors = uefi_read_sectors, // ... }; void init_uefi_hal(EFI_SYSTEM_TABLE *st) { gST = st; gBS = st->BootServices; g_hal = &uefi_hal; } ```

Hint 3: FAT32 Implementation (Technical)

Click to reveal Hint 3 ```c // fat32.c - Core FAT32 implementation typedef struct { // From BPB uint16_t bytes_per_sector; uint8_t sectors_per_cluster; uint16_t reserved_sectors; uint8_t num_fats; uint32_t sectors_per_fat; uint32_t root_cluster; // Calculated uint32_t fat_start_sector; uint32_t data_start_sector; uint32_t total_clusters; // For filesystem_t uint8_t disk; uint64_t partition_lba; } fat32_private_t; static uint32_t cluster_to_lba(fat32_private_t *fat, uint32_t cluster) { return fat->data_start_sector + (cluster - 2) * fat->sectors_per_cluster + fat->partition_lba; } static uint32_t get_next_cluster(fat32_private_t *fat, uint32_t cluster) { // Each FAT entry is 4 bytes uint32_t fat_offset = cluster * 4; uint32_t fat_sector = fat->fat_start_sector + (fat_offset / fat->bytes_per_sector); uint32_t offset_in_sector = fat_offset % fat->bytes_per_sector; uint8_t sector_buf[512]; g_hal->read_sectors(fat->disk, fat->partition_lba + fat_sector, 1, sector_buf); uint32_t entry = *(uint32_t *)(sector_buf + offset_in_sector); return entry & 0x0FFFFFFF; // Mask off reserved bits } static int fat32_read_cluster_chain(fat32_private_t *fat, uint32_t start_cluster, void *buffer, size_t max_size, size_t *bytes_read) { uint32_t cluster = start_cluster; size_t offset = 0; size_t cluster_size = fat->sectors_per_cluster * fat->bytes_per_sector; while (cluster >= 2 && cluster < 0x0FFFFFF8) { if (offset + cluster_size > max_size) break; uint64_t lba = cluster_to_lba(fat, cluster); g_hal->read_sectors(fat->disk, lba, fat->sectors_per_cluster, (uint8_t *)buffer + offset); offset += cluster_size; cluster = get_next_cluster(fat, cluster); } *bytes_read = offset; return 0; } // Long filename support requires parsing LFN entries // Each directory entry can be: // - Standard 8.3 entry (attribute != 0x0F) // - LFN entry (attribute == 0x0F, contains 13 UTF-16 chars) static int parse_directory_entry(uint8_t *entry, dir_entry_t *out, char *lfn_buffer) { uint8_t attr = entry[0x0B]; if (attr == 0x0F) { // LFN entry - extract characters uint8_t seq = entry[0] & 0x3F; bool is_last = (entry[0] & 0x40) != 0; int base = (seq - 1) * 13; // Characters at offsets: 1-10 (5 chars), 14-25 (6 chars), 28-31 (2 chars) // (UTF-16LE, we just take low byte for ASCII) for (int i = 0; i < 5; i++) lfn_buffer[base + i] = entry[1 + i*2]; for (int i = 0; i < 6; i++) lfn_buffer[base + 5 + i] = entry[14 + i*2]; for (int i = 0; i < 2; i++) lfn_buffer[base + 11 + i] = entry[28 + i*2]; if (is_last) { lfn_buffer[base + 13] = '\0'; } return 0; // Not complete entry yet } // Standard entry if (entry[0] == 0x00) return -1; // End of directory if (entry[0] == 0xE5) return 0; // Deleted entry if (lfn_buffer[0] != '\0') { // We have a long filename strcpy(out->name, lfn_buffer); lfn_buffer[0] = '\0'; // Reset } else { // Use 8.3 name int j = 0; for (int i = 0; i < 8 && entry[i] != ' '; i++) out->name[j++] = entry[i]; if (entry[8] != ' ') { out->name[j++] = '.'; for (int i = 8; i < 11 && entry[i] != ' '; i++) out->name[j++] = entry[i]; } out->name[j] = '\0'; } out->attributes = attr; out->is_directory = (attr & 0x10) != 0; out->size = *(uint32_t *)(entry + 0x1C); // Starting cluster uint32_t cluster_hi = *(uint16_t *)(entry + 0x14); uint32_t cluster_lo = *(uint16_t *)(entry + 0x1A); // Store in private data for later use return 1; // Valid entry } ```

Hint 4: Boot Menu Implementation (UI Pattern)

Click to reveal Hint 4 ```c // boot_menu.c - Boot menu implementation typedef struct { config_t *config; int selected_index; int scroll_offset; int visible_entries; uint64_t timeout_start; bool timeout_active; } menu_state_t; static void draw_menu(menu_state_t *state) { g_hal->clear_screen(); // Draw title g_hal->set_cursor(0, 0); g_hal->print("╔════════════════════════════════════════════════════════════════════════╗\n"); g_hal->print("║ "); g_hal->print(state->config->title); g_hal->print(" ║\n"); g_hal->print("╠════════════════════════════════════════════════════════════════════════╣\n"); g_hal->print("║ ║\n"); g_hal->print("║ Select an operating system to boot: ║\n"); g_hal->print("║ ║\n"); // Draw entries for (int i = 0; i < state->visible_entries && i + state->scroll_offset < state->config->entry_count; i++) { int entry_idx = i + state->scroll_offset; bool is_selected = (entry_idx == state->selected_index); g_hal->print("║ "); if (is_selected) { g_hal->print("▶ "); } else { g_hal->print(" "); } char buf[64]; snprintf(buf, sizeof(buf), "%-60s [%d]", state->config->entries[entry_idx].name, entry_idx + 1); g_hal->print(buf); g_hal->print(" ║\n"); } // Padding for (int i = state->config->entry_count; i < state->visible_entries; i++) { g_hal->print("║ ║\n"); } // Instructions g_hal->print("║ ║\n"); g_hal->print("║ Use ↑↓ to select, ENTER to boot, 'e' to edit, 'c' for console ║\n"); g_hal->print("║ ║\n"); // Timeout if (state->timeout_active) { uint64_t elapsed = (g_hal->get_timestamp() - state->timeout_start) / 1000; int remaining = state->config->timeout_seconds - elapsed; if (remaining > 0) { char timeout_buf[64]; snprintf(timeout_buf, sizeof(timeout_buf), "║ Automatic boot in %d seconds... ║\n", remaining); g_hal->print(timeout_buf); } } g_hal->print("╚════════════════════════════════════════════════════════════════════════╝\n"); } static int handle_input(menu_state_t *state) { if (!g_hal->char_available()) { return -1; // No input } state->timeout_active = false; // Cancel timeout on any input int key = g_hal->get_char(); switch (key) { case 0x48: // Up arrow (scan code) case 'k': // Vim-style if (state->selected_index > 0) { state->selected_index--; if (state->selected_index < state->scroll_offset) { state->scroll_offset = state->selected_index; } } break; case 0x50: // Down arrow case 'j': // Vim-style if (state->selected_index < state->config->entry_count - 1) { state->selected_index++; if (state->selected_index >= state->scroll_offset + state->visible_entries) { state->scroll_offset++; } } break; case '\r': // Enter case '\n': return state->selected_index; // Boot selected case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': { int idx = key - '1'; if (idx < state->config->entry_count) { return idx; // Boot directly } } break; case 'e': // Edit entry (not implemented in this hint) break; case 'c': // Console mode (not implemented in this hint) break; } return -1; // No boot action } int display_boot_menu(config_t *config) { menu_state_t state = { .config = config, .selected_index = config->default_entry, .scroll_offset = 0, .visible_entries = 6, .timeout_start = g_hal->get_timestamp(), .timeout_active = (config->timeout_seconds > 0) }; while (1) { draw_menu(&state); // Check timeout if (state.timeout_active) { uint64_t elapsed = (g_hal->get_timestamp() - state.timeout_start) / 1000; if (elapsed >= config->timeout_seconds) { return state.selected_index; } } int result = handle_input(&state); if (result >= 0) { return result; } // Small delay to avoid busy-waiting // (In real implementation, use timer or HLT) } } ```

Hint 5: Higher-Half Kernel Support (Advanced)

Click to reveal Hint 5 ```c // paging.c - Set up paging for higher-half kernels // For a kernel loaded at virtual 0xFFFFFFFF80000000 // We need to set up page tables that map: // - Identity map first 4GB (or needed range) for bootloader // - Map kernel virtual range to physical load address // Simplified 2MB page approach: #define PAGE_PRESENT (1 << 0) #define PAGE_WRITE (1 << 1) #define PAGE_SIZE (1 << 7) // 2MB page typedef struct { uint64_t entries[512]; } __attribute__((aligned(4096))) page_table_t; static page_table_t pml4; static page_table_t pdpt_low; // For identity mapping static page_table_t pdpt_high; // For higher-half static page_table_t pd_low[4]; // 4 PDs = 4GB identity map static page_table_t pd_high; // For kernel mapping void setup_higher_half_paging(uint64_t kernel_phys, uint64_t kernel_virt, size_t kernel_pages) { // Clear all tables memset(&pml4, 0, sizeof(pml4)); memset(&pdpt_low, 0, sizeof(pdpt_low)); memset(&pdpt_high, 0, sizeof(pdpt_high)); memset(pd_low, 0, sizeof(pd_low)); memset(&pd_high, 0, sizeof(pd_high)); // Set up PML4 entries // Entry 0: Low memory (identity map) pml4.entries[0] = (uint64_t)&pdpt_low | PAGE_PRESENT | PAGE_WRITE; // Entry 511 (index for 0xFFFF8000_00000000 and up): Higher half // Index = (vaddr >> 39) & 0x1FF int pml4_high_idx = (kernel_virt >> 39) & 0x1FF; pml4.entries[pml4_high_idx] = (uint64_t)&pdpt_high | PAGE_PRESENT | PAGE_WRITE; // Set up PDPT for low memory (identity map first 4GB with 1GB pages) // Or use 2MB pages for more granularity for (int i = 0; i < 4; i++) { pdpt_low.entries[i] = (uint64_t)&pd_low[i] | PAGE_PRESENT | PAGE_WRITE; // Fill PD with 2MB identity-mapped pages for (int j = 0; j < 512; j++) { uint64_t phys = (uint64_t)(i * 512 + j) * 0x200000; // 2MB pd_low[i].entries[j] = phys | PAGE_PRESENT | PAGE_WRITE | PAGE_SIZE; } } // Set up PDPT for higher half // PDPT index for 0xFFFFFFFF80000000 = ((vaddr >> 30) & 0x1FF) = 510 int pdpt_high_idx = (kernel_virt >> 30) & 0x1FF; pdpt_high.entries[pdpt_high_idx] = (uint64_t)&pd_high | PAGE_PRESENT | PAGE_WRITE; // Map kernel pages // PD index = ((vaddr >> 21) & 0x1FF) int pd_start = (kernel_virt >> 21) & 0x1FF; for (size_t i = 0; i < kernel_pages; i++) { uint64_t phys = kernel_phys + i * 0x200000; pd_high.entries[pd_start + i] = phys | PAGE_PRESENT | PAGE_WRITE | PAGE_SIZE; } // Load new page tables // (This must be done in assembly to properly switch CR3) } // Assembly to switch page tables and jump to kernel void jump_to_kernel_asm(uint64_t pml4_phys, uint64_t entry_point, uint64_t boot_info_ptr); // In assembly: // jump_to_kernel_asm: // mov cr3, rdi ; Load new PML4 // mov rdi, rdx ; First argument = boot_info_ptr // jmp rsi ; Jump to entry point ```

Hint 6: Memory Map Conversion (UEFI to Unified)

Click to reveal Hint 6 ```c // memory.c - Memory map handling // UEFI memory types to our types static uint32_t uefi_to_mmap_type(uint32_t uefi_type) { switch (uefi_type) { case EfiReservedMemoryType: case EfiUnusableMemory: return 2; // Reserved case EfiLoaderCode: case EfiLoaderData: case EfiBootServicesCode: case EfiBootServicesData: return 6; // Bootloader reclaimable case EfiRuntimeServicesCode: case EfiRuntimeServicesData: return 2; // Reserved (runtime services) case EfiConventionalMemory: return 1; // Usable case EfiACPIReclaimMemory: return 3; // ACPI reclaimable case EfiACPIMemoryNVS: return 4; // ACPI NVS case EfiMemoryMappedIO: case EfiMemoryMappedIOPortSpace: case EfiPalCode: return 2; // Reserved default: return 2; // Unknown = reserved } } int convert_uefi_mmap(EFI_MEMORY_DESCRIPTOR *uefi_map, UINTN map_size, UINTN desc_size, mmap_entry_t *out, size_t *out_count) { size_t count = 0; size_t max_count = *out_count; uint8_t *ptr = (uint8_t *)uefi_map; uint8_t *end = ptr + map_size; while (ptr < end && count < max_count) { EFI_MEMORY_DESCRIPTOR *desc = (EFI_MEMORY_DESCRIPTOR *)ptr; out[count].base = desc->PhysicalStart; out[count].length = desc->NumberOfPages * 4096; out[count].type = uefi_to_mmap_type(desc->Type); out[count].attributes = 0; count++; ptr += desc_size; } // Sort by base address (simple bubble sort - map is usually small) for (size_t i = 0; i < count - 1; i++) { for (size_t j = 0; j < count - i - 1; j++) { if (out[j].base > out[j + 1].base) { mmap_entry_t temp = out[j]; out[j] = out[j + 1]; out[j + 1] = temp; } } } // Merge adjacent entries of same type size_t merged = 0; for (size_t i = 0; i < count; i++) { if (merged > 0 && out[merged - 1].type == out[i].type && out[merged - 1].base + out[merged - 1].length == out[i].base) { // Merge out[merged - 1].length += out[i].length; } else { // Copy if (merged != i) { out[merged] = out[i]; } merged++; } } *out_count = merged; return 0; } // E820 map (BIOS) is already in similar format int convert_e820_mmap(e820_entry_t *e820_map, size_t count, mmap_entry_t *out, size_t *out_count) { for (size_t i = 0; i < count && i < *out_count; i++) { out[i].base = e820_map[i].base; out[i].length = e820_map[i].length; out[i].type = e820_map[i].type; // E820 types match our types out[i].attributes = e820_map[i].acpi_extended; } *out_count = count; return 0; } ```

5.6 The Interview Questions They’ll Ask

For a capstone project of this magnitude, expect deep technical questions:

  1. “Walk me through your bootloader’s architecture from power-on to kernel execution.”
    • Discuss firmware detection, HAL initialization, filesystem mounting, config parsing, menu display, kernel loading, protocol setup, and handoff.
  2. “How do you handle the differences between BIOS and UEFI in your codebase?”
    • Explain HAL abstraction, compile-time vs runtime detection, shared core code.
  3. “Explain your FAT32 implementation. How do you follow a cluster chain?”
    • FAT entries, cluster to LBA conversion, EOF detection, long filename handling.
  4. “What’s in your boot protocol? How did you decide what to include?”
    • Memory map, framebuffer, ACPI, modules, extensibility, version handling.
  5. “How do you handle higher-half kernels?”
    • Page table setup, identity mapping preservation, entry point address handling.
  6. “What happens if ExitBootServices fails?”
    • Memory map key mismatch, retry loop, final error handling.
  7. “How would you add Secure Boot support?”
    • Signing, certificate chains, MOK, shim approach.
  8. “What was the hardest bug you encountered and how did you debug it?”
    • Triple faults, silent failures, memory corruption, etc.

5.7 Books That Will Help

Concept Book Chapters Why It Helps
Overall systems “Computer Systems: A Programmer’s Perspective” Ch. 1, 7, 9 Foundational understanding
x86 Architecture Intel 64 and IA-32 SDM Vol. 3A, Ch. 2, 4, 5 Authoritative CPU reference
UEFI “Beyond BIOS” by Zimmer et al. Ch. 1-5, 10 UEFI architecture from creators
ELF Format “Practical Binary Analysis” Ch. 2 ELF parsing details
FAT32 Microsoft FAT32 Specification All Official spec
ext2 “The Design of the UNIX Operating System” Ch. 4-5 Filesystem concepts
Bootloaders “Low-Level Programming” by Zhirkov Ch. 4-6 Direct bootloader guidance
OS Concepts “Operating Systems: Three Easy Pieces” Virtualization chapters Memory, paging

5.8 Implementation Phases

Phase 1: Foundation (Week 1-2)

  • Set up build system for BIOS and UEFI targets
  • Implement minimal BIOS stage 1 (MBR loader)
  • Implement minimal UEFI entry point
  • Create HAL interface header
  • Implement basic console output for both platforms
  • Milestone: “Hello World” boots on both BIOS and UEFI

Phase 2: HAL Implementation (Week 3-4)

  • BIOS: INT 13h disk read
  • BIOS: E820 memory map
  • BIOS: VBE framebuffer
  • UEFI: Block I/O protocol
  • UEFI: GetMemoryMap
  • UEFI: GOP framebuffer
  • Milestone: All HAL functions working on both platforms

Phase 3: FAT32 Filesystem (Week 5-6)

  • BPB parsing
  • FAT table reading
  • Cluster chain following
  • Directory traversal
  • File reading
  • Long filename support (optional first pass)
  • Milestone: Can read files from FAT32 partition

Phase 4: Configuration & Menu (Week 7-8)

  • Config file format design
  • Config parser implementation
  • Boot entry structure
  • Menu display
  • Keyboard input handling
  • Timeout handling
  • Milestone: Boot menu displays and accepts input

Phase 5: ELF Loading (Week 9-10)

  • ELF header validation
  • Program header parsing
  • Segment loading
  • BSS zeroing
  • Entry point extraction
  • Milestone: Can load and display info about ELF file

Phase 6: Boot Protocol (Week 11-12)

  • Design boot info structure
  • Memory map conversion and passing
  • Framebuffer info passing
  • ACPI RSDP finding
  • Command line handling
  • Module loading
  • Milestone: Complete boot info structure populated

Phase 7: Kernel Handoff (Week 13-14)

  • ExitBootServices handling (UEFI)
  • Page table setup for higher-half (if needed)
  • Jump to kernel entry
  • Test with custom test kernel
  • Milestone: Test kernel runs and prints received boot info

Phase 8: ext2 Support (Week 15-16)

  • Superblock parsing
  • Block group descriptors
  • Inode reading
  • Directory traversal
  • File reading
  • Milestone: Can boot kernel from ext2 partition

Phase 9: Polish & Advanced (Week 17+)

  • Error handling improvements
  • Chainloading support
  • Entry editing
  • Better graphics
  • Documentation
  • Real hardware testing
  • Milestone: Production-ready bootloader

6. Testing Strategy

6.1 Unit Testing (Host-side)

Test filesystem and parsing code on your development machine:

// test/test_fat32.c
#include <stdio.h>
#include <assert.h>
#include "../src/common/fat32.c"

// Mock HAL for testing
static uint8_t test_disk_image[1024 * 1024];  // 1MB test image

static int mock_read_sectors(uint8_t disk, uint64_t lba,
                            uint32_t count, void *buf) {
    memcpy(buf, test_disk_image + lba * 512, count * 512);
    return 0;
}

void test_fat32_mount() {
    // Load test FAT32 image
    FILE *f = fopen("test_fat32.img", "rb");
    fread(test_disk_image, 1, sizeof(test_disk_image), f);
    fclose(f);

    // Set up mock HAL
    hal_ops_t mock_hal = { .read_sectors = mock_read_sectors };
    g_hal = &mock_hal;

    // Test mount
    filesystem_t fs = { .disk = 0, .partition_start = 0 };
    int result = fat32_mount(&fs);
    assert(result == 0);

    // Test file open
    file_t file;
    result = fat32_open(&fs, "/test.txt", &file);
    assert(result == 0);

    // Test file read
    char buffer[100];
    size_t bytes_read;
    result = fat32_read(&file, buffer, 100, &bytes_read);
    assert(result == 0);
    assert(strncmp(buffer, "Hello, FAT32!", 13) == 0);

    printf("test_fat32_mount: PASSED\n");
}

int main() {
    test_fat32_mount();
    // More tests...
    return 0;
}

6.2 Integration Testing (QEMU)

#!/bin/bash
# test/integration_test.sh

set -e

echo "Building bootloader..."
make clean all

echo "Creating test disk image..."
dd if=/dev/zero of=test_disk.img bs=1M count=128
# Set up partition, format FAT32, copy files...

echo "Testing BIOS boot..."
timeout 30 qemu-system-x86_64 \
    -drive format=raw,file=test_disk.img \
    -serial stdio \
    -no-reboot \
    -nographic \
    2>&1 | tee bios_output.log

grep -q "MyBootloader" bios_output.log || { echo "BIOS boot FAILED"; exit 1; }
echo "BIOS boot: PASSED"

echo "Testing UEFI boot..."
timeout 30 qemu-system-x86_64 \
    -bios /usr/share/OVMF/OVMF_CODE.fd \
    -drive format=raw,file=test_disk.img \
    -serial stdio \
    -no-reboot \
    -nographic \
    2>&1 | tee uefi_output.log

grep -q "MyBootloader" uefi_output.log || { echo "UEFI boot FAILED"; exit 1; }
echo "UEFI boot: PASSED"

echo "All integration tests PASSED"

6.3 Test Matrix

Test Case BIOS UEFI Expected Result
Boot to menu [ ] [ ] Menu displays correctly
Navigate menu [ ] [ ] Arrow keys work
Timeout boot [ ] [ ] Auto-boots default entry
Load ELF kernel [ ] [ ] Kernel receives boot info
Load from FAT32 [ ] [ ] File read correctly
Load from ext2 [ ] [ ] File read correctly
Memory map [ ] [ ] Valid entries passed
Framebuffer [ ] [ ] Graphics mode works
ACPI RSDP [ ] [ ] Valid RSDP passed
Module loading [ ] [ ] Modules at correct addresses
Higher-half kernel [ ] [ ] Kernel runs at high address
Chainload [ ] [ ] Other bootloader runs
Missing config [ ] [ ] Appropriate error
Invalid kernel [ ] [ ] Error message

7. Common Pitfalls & Debugging

Pitfall 1: UEFI ExitBootServices Loop

Symptom: ExitBootServices never succeeds, loops forever.

Root Cause: Memory map key changes between GetMemoryMap and ExitBootServices.

Fix:

// CORRECT pattern
UINTN map_size = 0;
UINTN map_key, desc_size;
UINT32 desc_ver;
EFI_MEMORY_DESCRIPTOR *map = NULL;

// First call to get required size
gBS->GetMemoryMap(&map_size, NULL, &map_key, &desc_size, &desc_ver);
map_size += 2 * desc_size;  // Buffer for changes

// Allocate BEFORE the retry loop
gBS->AllocatePool(EfiLoaderData, map_size, (void **)&map);

// Retry loop - NO allocations inside!
while (1) {
    UINTN size = map_size;
    EFI_STATUS status = gBS->GetMemoryMap(&size, map, &map_key,
                                          &desc_size, &desc_ver);
    if (EFI_ERROR(status)) break;

    status = gBS->ExitBootServices(ImageHandle, map_key);
    if (!EFI_ERROR(status)) break;  // Success!

    // Only continue if key mismatch
    if (status != EFI_INVALID_PARAMETER) break;
}

Pitfall 2: FAT32 Cluster Chain Infinite Loop

Symptom: Reading file never completes, hangs.

Root Cause: Not detecting end-of-chain marker correctly.

Fix:

// Check for ALL end-of-chain values
bool is_end_of_chain(uint32_t cluster) {
    cluster &= 0x0FFFFFFF;  // Mask reserved bits
    return cluster >= 0x0FFFFFF8;  // EOF range
}

// Also check for invalid clusters
bool is_valid_cluster(uint32_t cluster, uint32_t total_clusters) {
    cluster &= 0x0FFFFFFF;
    return cluster >= 2 && cluster < total_clusters + 2;
}

Pitfall 3: ELF Higher-Half Addressing

Symptom: Triple fault when jumping to kernel.

Root Cause: Page tables don’t map the kernel’s virtual address.

Fix: See Hint 5 for proper page table setup. Also ensure:

  • Identity mapping covers bootloader code
  • Higher-half mapping covers kernel virtual range
  • CR3 is loaded after tables are set up
  • No code runs between CR3 load and jump

Pitfall 4: BIOS Real Mode/Protected Mode Confusion

Symptom: Disk reads fail or return garbage after mode switch.

Root Cause: INT 13h only works in real mode with real mode segment:offset addressing.

Fix: Either:

  1. Do all disk reads in real mode before switching
  2. Switch back to real mode for disk reads (complex)
  3. Use BIOS extensions with unreal mode

Pitfall 5: ext2 Block Addressing

Symptom: Wrong data read from files.

Root Cause: Confusion between block numbers and byte offsets.

Fix:

// Block 0 is special (boot block for 1KB block sizes)
// Superblock is at byte 1024, which is:
// - Block 1 for 1KB blocks
// - Block 0 offset 1024 for larger blocks

uint64_t block_to_byte(ext2_t *ext2, uint32_t block) {
    return (uint64_t)block * ext2->block_size;
}

// For reading inode data blocks:
uint64_t get_block_lba(ext2_t *ext2, uint32_t block_num) {
    return ext2->partition_lba +
           (block_num * ext2->block_size) / 512;
}

Debugging Techniques

QEMU Monitor:

# Start with monitor
qemu-system-x86_64 -monitor stdio ...

(qemu) info registers     # CPU state
(qemu) info mem           # Page tables
(qemu) x /32xb 0x7c00    # Examine memory
(qemu) xp /32xb 0x1000   # Physical memory

Serial Debugging:

// Add early in boot process
void serial_init(void) {
    outb(0x3F8 + 1, 0x00);
    outb(0x3F8 + 3, 0x80);
    outb(0x3F8 + 0, 0x03);
    outb(0x3F8 + 1, 0x00);
    outb(0x3F8 + 3, 0x03);
}

void serial_putc(char c) {
    while (!(inb(0x3F8 + 5) & 0x20));
    outb(0x3F8, c);
}

void debug_print(const char *msg) {
    while (*msg) serial_putc(*msg++);
}

Run with: qemu-system-x86_64 -serial stdio ...


8. Extensions & Challenges

After completing the base bootloader:

Extension 1: Secure Boot Support

Implement UEFI Secure Boot compatibility:

  • Sign your bootloader with a custom key
  • Enroll key in firmware
  • Verify kernel signature before loading

Extension 2: Network Boot (PXE/HTTP)

Add network boot capability:

  • BIOS: PXE stack
  • UEFI: HTTP boot protocol
  • Load config and kernel from network

Extension 3: Encrypted Partition Support

Support LUKS-encrypted partitions:

  • Password prompt at boot
  • Key derivation
  • Decrypt partition for reading

Extension 4: Multiple Display Support

Handle multiple monitors:

  • Enumerate all GOP handles
  • Set mode on all displays
  • Pass multi-framebuffer info

Extension 5: Recovery Mode

Add recovery features:

  • Memory test
  • Disk tools
  • Network diagnostics
  • Shell access

Extension 6: Theme System

Customizable boot appearance:

  • Background images
  • Custom fonts
  • Color schemes
  • Progress indicators

9. Real-World Connections

How Production Bootloaders Compare

Feature Your Bootloader Limine GRUB2 systemd-boot
BIOS support Yes Yes Yes No
UEFI support Yes Yes Yes Yes
FAT32 Yes Yes Yes Yes
ext2/3/4 Basic ext2 Yes Yes No
Boot protocol Custom Limine Multiboot2 N/A
Config format INI-like INI-like Script N/A (auto-detect)
Graphical menu Yes Yes Yes Text
Secure Boot Optional Yes Yes Yes
Code size ~50KB ~100KB ~2MB ~100KB

Career Applications

This project prepares you for:

  1. Firmware Engineering: Write BIOS/UEFI firmware
  2. OS Development: Foundation for kernel development
  3. Embedded Systems: Boot process knowledge transfers
  4. Security Research: Boot-level security understanding
  5. Cloud Infrastructure: Understanding of VM boot process
  6. DevOps: Deep debugging of boot issues

10. Resources

Primary References

Resource URL Description
OSDev Wiki - Boot wiki.osdev.org/Boot Comprehensive boot reference
Limine Source github.com/limine-bootloader Modern bootloader reference
BOOTBOOT Source gitlab.com/bztsrc/bootboot Minimal bootloader reference
UEFI Specification uefi.org/specifications Official UEFI docs
Limine Protocol Limine Protocol Spec Boot protocol design
FAT32 Spec Microsoft FAT32 Official FAT spec
ext2 Documentation ext2.sourceforge.net ext2 filesystem reference

Communities


11. Self-Assessment Checklist

Understanding (Theory)

  • I can explain why bootloaders need to support both BIOS and UEFI
  • I understand FAT32 cluster chain traversal
  • I can describe ext2 inode structure
  • I know what information kernels need from bootloaders
  • I understand ELF segment loading
  • I can explain higher-half kernel mapping

Implementation (Code)

  • BIOS boot path works
  • UEFI boot path works
  • HAL abstracts firmware differences
  • FAT32 driver reads files
  • ext2 driver reads files (if implemented)
  • Configuration parses correctly
  • Boot menu displays and navigates
  • ELF kernels load and execute
  • Memory map passed correctly
  • Framebuffer set up correctly
  • ACPI RSDP found and passed

Quality (Production-Ready)

  • Error handling is comprehensive
  • Code is well-documented
  • README is complete
  • Works on real hardware (tested)
  • No memory leaks or corruption
  • Edge cases handled

12. Completion Criteria

Your capstone project is complete when:

Minimum Viable Product (Bronze)

  1. Boots on BIOS or UEFI (one platform)
  2. Reads FAT32 filesystem
  3. Parses configuration file
  4. Displays boot menu
  5. Loads ELF kernel
  6. Passes basic boot info (memory map, framebuffer)
  7. Works in QEMU

Full Implementation (Silver)

  1. Boots on both BIOS and UEFI
  2. HAL abstracts firmware differences
  3. FAT32 with long filename support
  4. ext2 filesystem support
  5. Complete boot protocol
  6. Module/initrd loading
  7. Higher-half kernel support
  8. Timeout and default entry
  9. Works on real hardware

Production Quality (Gold)

  1. All Silver requirements
  2. Chainloading support
  3. Entry editing
  4. Console/shell mode
  5. Comprehensive error handling
  6. Full documentation
  7. Test suite
  8. Open-sourced with CI/CD

Excellence (Platinum)

  1. All Gold requirements
  2. Secure Boot support
  3. Custom graphical theme
  4. Network boot capability
  5. Encryption support
  6. Community adoption
  7. Contributions from others

Conclusion

You’ve reached the summit of the Bootloader Deep Dive series. This capstone project integrates everything:

  • Real Mode to Long Mode (Projects 1-6)
  • UEFI architecture (Projects 7-8)
  • Hardware interaction (Projects 9-10)
  • Debugging skills (Project 11)
  • Multi-boot concepts (Project 12)
  • Network boot (Project 13)
  • Graphics programming (Project 14)
  • Security considerations (Project 15)
  • Interactive systems (Project 16)

Your bootloader isn’t just a project—it’s a complete system that bridges the gap between firmware and operating systems. Few developers ever write code that runs this close to the hardware.

What you’ve achieved:

  • Deep understanding of computer boot process
  • Practical firmware development skills
  • Production-quality systems programming
  • Portfolio piece that demonstrates mastery

Where to go next:

  • Contribute to Limine or other boot projects
  • Write your own operating system kernel
  • Explore firmware development (coreboot, UEFI)
  • Specialize in embedded systems
  • Pursue security research in boot/firmware

“The bootloader is where software meets silicon. Master it, and you understand computing at its deepest level.”


Congratulations on completing the Bootloader Deep Dive series!


Project 17 of 17 in the Bootloader Deep Dive series (CAPSTONE)

Previous: Project 16 - Interactive Shell Series Complete