Skip to content

Bootloader

Enrico Fraccaroli (Galfurian) edited this page Jan 29, 2026 · 3 revisions

The first code executed when MentOS starts.

Overview

The bootloader is responsible for:

  1. Minimal CPU setup - Establish a stack and enter C code
  2. Multiboot Handling - Use the Multiboot header/info provided by GRUB/QEMU
  3. Kernel Relocation & Paging - Map low memory and relocate the embedded kernel ELF
  4. Kernel Transfer - Jump to the kernel entry point (kmain())

Location

boot/
├── src/
│   ├── boot.S         ← Assembly entry point
│   ├── boot.c         ← Main bootloader logic
│   └── multiboot.c    ← Multiboot spec handling
└── 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 boot_entry (defined in boot.S)

2. Assembly Entry (boot.S)

; boot/src/boot.S
section .text
global boot_entry
boot_entry:
    ; Disable interrupts
    cli
    
    ; Set up stack
    mov esp, stack_top

    ; Pass arguments to boot_main(magic, header, esp)
    push esp  ; initial stack pointer
    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 boot parameters (magic, header, initial stack)
  • Calls boot_main()

3. Bootloader Main (boot.c)

In boot/src/boot.c, boot_main() performs the boot-time setup needed to transfer control to the kernel:

  • Builds a boot_info_t structure (kernel bounds, modules end, memory layout)
  • Sets up temporary paging mappings (identity-maps low memory and maps the kernel at its virtual base)
  • Relocates the embedded kernel ELF to its virtual addresses
  • Calls boot_kernel(stack, entry, &boot_info) to jump to the kernel

Note: GDT/IDT setup happens later inside the kernel (see kernel/src/descriptor_tables/).

4. Transfer to Kernel

// In boot.S: boot_kernel(stack, entry, boot_info)
// Sets ESP, pushes boot_info, and calls 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_header
.align 4
multiboot_header:
    .long MULTIBOOT_HEADER_MAGIC
    .long MULTIBOOT_HEADER_FLAGS
    .long MULTIBOOT_CHECKSUM

Constants:

  • MULTIBOOT_HEADER_MAGIC: 0x1BADB002
  • MULTIBOOT_HEADER_FLAGS: Requested features (page align + memory info)
  • MULTIBOOT_CHECKSUM: -(MAGIC + FLAGS)

Multiboot Information

The bootloader receives a multiboot_info_t from GRUB (see kernel/inc/multiboot.h):

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

Boot-Time Paging

The bootloader builds temporary page tables that:

  • Identity-map low memory (so the bootloader can keep running)
  • Map the kernel's virtual address range to its physical location

This setup happens in boot/src/boot.c via __setup_boot_paging() and is enabled before jumping to the kernel.

Build Process

Compilation

Use the CMake targets:

cd build
make bootloader.bin

Output

build/mentos/bootloader.bin contains:

  • Bootloader code
  • Embedded kernel.bin (as a linked binary blob)

Linker Scripts

Boot Linker Script (boot.lds)

Key excerpts (see boot/linker/boot.lds for full script):

ENTRY(boot_entry)

. = 0x00100000;
_bootloader_start = .;

.multiboot : {
    *(.multiboot_header)
}

.text : { *(.text) }
.rodata : { *(.rodata*) }
.data : { *(.data) }
.bss  : { *(.bss*) }

_bootloader_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:

  • magic: Multiboot bootloader magic (0x2BADB002 expected)
  • header: Pointer to multiboot information structure
  • esp: Initial stack pointer

Actions:

  1. Build boot_info_t (kernel bounds, modules end, memory layout)
  2. Set up boot-time paging
  3. Relocate the kernel ELF image
  4. Call boot_kernel()

__setup_boot_paging()

Creates the temporary page tables used during early boot.

__relocate_kernel_image()

Copies the ELF segments of the embedded kernel image to their virtual addresses.

boot_kernel()

Assembly helper that switches the stack pointer and calls the kernel entry.

Debugging Bootloader

Using GDB

# Terminal 1
make qemu-gdb

# Terminal 2 (from build/)
gdb --quiet --command=gdb.run

Debug Output

Add debug prints (serial console):

// In boot.c
__debug_puts("[bootloader] Start...\n");

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_header 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