Skip to content

Bootloader

Enrico Fraccaroli edited this page Jan 28, 2026 · 4 revisions

The first code executed when MentOS starts.

Overview

The bootloader is responsible for:

  1. CPU Initialization - Set up GDT, enable protected mode
  2. Multiboot Handling - Parse boot information from GRUB
  3. Kernel Loading - Load the embedded kernel binary into memory
  4. Kernel Transfer - Jump to kernel entry point (kmain())

Location

boot/
├── src/
│   ├── boot.S         ← Assembly entry point
│   ├── boot.c         ← Main bootloader logic
│   └── multiboot.c    ← Multiboot spec handling
├── inc/               ← Headers
└── linker/
    ├── boot.lds       ← Bootloader linker script
    └── kernel.lds     ← Kernel linker script

Boot Sequence

1. BIOS/GRUB Loads Bootloader

  • BIOS or GRUB loads bootloader.bin into memory
  • Execution begins at _start (defined in boot.S)

2. Assembly Entry (boot.S)

; boot/src/boot.S
.section .text
.global _start
_start:
    ; Disable interrupts
    cli
    
    ; Set up stack
    mov esp, stack_top
    
    ; Push multiboot info
    push ebx  ; Multiboot info structure
    push eax  ; Multiboot magic number
    
    ; Call C code
    call boot_main
    
    ; Halt if boot_main returns
    cli
.hang:
    hlt
    jmp .hang

Actions:

  • Disables interrupts
  • Sets up stack pointer
  • Pushes multiboot magic and info pointer
  • Calls boot_main()

3. Bootloader Main (boot.c)

// boot/src/boot.c
void boot_main(uint32_t multiboot_magic, multiboot_info_t *multiboot_info)
{
    // 1. Parse multiboot information
    multiboot_read_info(multiboot_magic, multiboot_info);
    
    // 2. Set up GDT (Global Descriptor Table)
    setup_gdt();
    
    // 3. Set up IDT (Interrupt Descriptor Table) - basic
    setup_idt();
    
    // 4. Enable paging (virtual memory)
    setup_paging();
    
    // 5. Load kernel binary (embedded in bootloader.bin)
    load_kernel();
    
    // 6. Jump to kernel entry point
    jump_to_kernel();
}

4. Transfer to Kernel

// Jump to kernel's kmain()
void (*kernel_entry)() = (void(*)())KERNEL_ENTRY_POINT;
kernel_entry();

Multiboot Specification

MentOS uses the Multiboot specification to be compatible with GRUB and other bootloaders.

Multiboot Header

// boot/src/boot.S
.section .multiboot
.align 4
multiboot_header:
    .long MULTIBOOT_MAGIC
    .long FLAGS
    .long CHECKSUM

Constants:

  • MULTIBOOT_MAGIC: 0x1BADB002
  • FLAGS: Requested features
  • CHECKSUM: -(MAGIC + FLAGS)

Multiboot Information

Parsed in multiboot_read_info():

typedef struct multiboot_info {
    uint32_t flags;           // Available info
    uint32_t mem_lower;       // Lower memory (KB)
    uint32_t mem_upper;       // Upper memory (KB)
    uint32_t boot_device;     // Boot device
    uint32_t cmdline;         // Kernel command line
    uint32_t mods_count;      // Number of modules
    uint32_t mods_addr;       // Modules address
    // ... more fields
} multiboot_info_t;

Information Provided:

  • Memory map (physical RAM available)
  • Boot device
  • Kernel command line
  • Loaded modules (if any)
  • Framebuffer information

GDT Setup

The Global Descriptor Table (GDT) defines memory segments for protected mode.

// Set up GDT with kernel code/data segments
gdt_entry_t gdt[GDT_SIZE];

// Null descriptor
gdt[0] = {0, 0, 0, 0, 0, 0};

// Kernel code segment (ring 0)
gdt[1] = {0xFFFF, 0, 0, 0x9A, 0xCF, 0};

// Kernel data segment (ring 0)
gdt[2] = {0xFFFF, 0, 0, 0x92, 0xCF, 0};

// User code segment (ring 3)
gdt[3] = {0xFFFF, 0, 0, 0xFA, 0xCF, 0};

// User data segment (ring 3)
gdt[4] = {0xFFFF, 0, 0, 0xF2, 0xCF, 0};

// Load GDT
lgdt(&gdt_ptr);

Segments:

  • Ring 0 (kernel): Full memory access, all instructions
  • Ring 3 (user): Limited access, restricted instructions

Paging Setup

Enable virtual memory with identity mapping:

// Set up page directory and page tables
page_directory[0] = &page_table[0] | 0x3; // Present, R/W

// Identity map first 4MB (kernel)
for (int i = 0; i < 1024; i++) {
    page_table[i] = (i * 0x1000) | 0x3; // Present, R/W
}

// Load page directory
asm volatile("mov %0, %%cr3" :: "r"(page_directory));

// Enable paging
uint32_t cr0;
asm volatile("mov %%cr0, %0" : "=r"(cr0));
cr0 |= 0x80000000; // PG bit
asm volatile("mov %0, %%cr0" :: "r"(cr0));

Build Process

Compilation

# Compile bootloader sources
gcc -c boot/src/boot.S -o build/boot/boot.o
gcc -c boot/src/boot.c -o build/boot/boot.o

# Compile kernel (separately)
# ... kernel compilation ...

# Embed kernel.bin as object
objcopy -I binary -O elf32-i386 kernel.bin kernel.bin.o

# Link bootloader + embedded kernel
ld -T boot/linker/boot.lds boot/*.o kernel.bin.o -o bootloader.bin

Output

build/mentos/bootloader.bin:

  • Bootloader code
  • Embedded kernel.bin (as data section)
  • Combined size: ~1.1MB

Linker Scripts

Boot Linker Script (boot.lds)

ENTRY(_start)

SECTIONS
{
    . = 0x100000; /* Load at 1MB */
    
    .text : {
        *(.multiboot)  /* Multiboot header first */
        *(.text)       /* Code */
    }
    
    .rodata : { *(.rodata*) }
    .data : { *(.data) }
    .bss : { *(.bss) }
    
    /* Embedded kernel binary */
    .kernel : {
        kernel_start = .;
        *(.kernel_binary)
        kernel_end = .;
    }
}

Kernel Linker Script (kernel.lds)

Links the standalone kernel.bin (used by kernel compilation).

Key Functions

boot_main()

Main bootloader entry point from assembly.

Parameters:

  • multiboot_magic: Magic number (0x2BADB002 expected)
  • multiboot_info: Pointer to multiboot information structure

Actions:

  1. Validate multiboot magic
  2. Parse memory map
  3. Initialize CPU (GDT, IDT, paging)
  4. Load kernel
  5. Jump to kernel

multiboot_read_info()

Parse boot information from GRUB.

Extracts:

  • Available memory (lower and upper)
  • Memory map entries
  • Boot device
  • Kernel command line

setup_gdt()

Set up Global Descriptor Table with kernel and user segments.

setup_paging()

Enable virtual memory with identity mapping for the first 4MB.

load_kernel()

Load the embedded kernel binary into memory at the correct address.

jump_to_kernel()

Transfer control to kernel's kmain() function.

Debugging Bootloader

Using GDB

# Terminal 1
make qemu-gdb

# Terminal 2
gdb build/mentos/bootloader.bin
(gdb) target remote :1234
(gdb) break boot_main
(gdb) continue

Debug Output

Add debug prints (serial console):

// In boot.c
printk("Bootloader: multiboot_magic = %x\n", multiboot_magic);
printk("Bootloader: memory = %d KB\n", multiboot_info->mem_upper);

Inspect with objdump

objdump -d build/mentos/bootloader.bin | less

Common Issues

"Multiboot magic invalid"

  • GRUB didn't load properly
  • Multiboot header is missing or incorrect
  • Check .multiboot section in assembly

"Kernel not found"

  • Kernel binary wasn't embedded correctly
  • Check linker script
  • Verify kernel.bin.o creation

"Page fault during boot"

  • Paging setup incorrect
  • Identity mapping missing
  • Check page table entries

"Triple fault" (QEMU resets)

  • CPU exception during boot
  • Usually GDT or paging issue
  • Use make qemu-gdb to catch early

References

Next Steps


Previous: Architecture | Next: Kernel

Clone this wiki locally