My-Operating-System-OpenOS

GRUB Multiboot Header Fix - Technical Documentation

Problem Statement

When booting OpenOS with GRUB via ISO, the following errors occurred:

error: no multiboot header found.
error: you need to load the kernel first.

Root Cause Analysis

1. The Multiboot Specification Requirement

The Multiboot v1 specification (used by GRUB) requires:

2. What Was Wrong

Before the fix:

$ readelf -S openos.bin (BEFORE)
  [Nr] Name              Type            Addr     Off    Size
  [ 1] .multiboot        PROGBITS        00100000 002720 00000c
  [ 2] .text             PROGBITS        00000000 000080 0018b6

Problems identified:

  1. .multiboot section was at file offset 0x2720 (10016 bytes) - far beyond the 8KB limit
  2. .multiboot had VMA 0x00100000, while .text had VMA 0x00000000
  3. The linker script had ALIGN(4K) between sections, creating a large gap
  4. .multiboot was NOT included in any LOAD segment (not loaded into memory)

3. Why This Happened

The original linker script:

SECTIONS
{
  . = 1M;
  
  .multiboot :
  {
    *(.multiboot)
  }
  
  .text ALIGN(4K) :  /* <-- This ALIGN creates a gap! */
  {
    *(.text*)
  }
}

When the linker processes this:

  1. Places .multiboot at 1M (VMA 0x100000)
  2. Then aligns the next section to 4K boundary
  3. This causes .text to start at a different address (0x0)
  4. The linker creates separate LOAD segments for disjoint memory regions
  5. Since .multiboot has no following sections at 0x100000, it’s not loaded
  6. Result: multiboot header exists in the ELF file but at wrong offset and not loaded

The Fix

Modified Linker Script

File: Kernel2.0/linker.ld

SECTIONS
{
  . = 1M;
  
  /* Place multiboot header FIRST in .text section */
  .text :
  {
    *(.multiboot)    /* Multiboot header comes first */
    *(.text*)        /* Then all code */
  }
  
  .rodata ALIGN(4K) :
  {
    *(.rodata*)
  }
  
  /* ... rest of sections ... */
}

What Changed

  1. Removed separate .multiboot section - it’s now part of .text
  2. Removed ALIGN(4K) between multiboot and text - they’re now contiguous
  3. Multiboot header is placed first via *(.multiboot) before *(.text*)

After the Fix

$ readelf -S openos.bin (AFTER)
  [Nr] Name              Type            Addr     Off    Size
  [ 0] .text             PROGBITS        00100000 000080 0018c6
  [ 1] .rodata           PROGBITS        00102000 002080 0006a0

$ readelf -l openos.bin (AFTER)
  LOAD           0x000080 0x00100000 0x00100000 0x026a0 0x1079b0
  
  Section to Segment mapping:
    00     .text .rodata .bss   <-- .text (with multiboot) is now in LOAD segment!

Results:

Technical Details

ELF File Layout

An ELF file has this structure:

[ELF Header (52 bytes @ 0x00)]
[Program Headers (LOAD segments)]
[.text section @ 0x80]  <-- Multiboot header is here now!
  - Multiboot header (12 bytes)
  - _start code
  - Rest of code
[.rodata section]
[.data section]
[Section Headers]

Why Offset 0x80?

Multiboot Header Values

/* In boot.S */
.set MAGIC,    0x1BADB002       /* Multiboot magic number */
.set FLAGS,    ALIGN | MEMINFO  /* 0x00000003 */
.set CHECKSUM, -(MAGIC + FLAGS) /* 0xE4524FFB */

.section .multiboot
    .align 4
    .long MAGIC      /* 0x1BADB002 */
    .long FLAGS      /* 0x00000003 */
    .long CHECKSUM   /* 0xE4524FFB */

GRUB scans the first 8KB for this pattern:

Offset: 0x80
Bytes:  02 b0 ad 1b  03 00 00 00  fb 4f 52 e4
        [  MAGIC  ]  [  FLAGS  ]  [CHECKSUM ]

Verification Steps

1. Check with grub-file

$ grub-file --is-x86-multiboot Kernel2.0/openos.bin
$ echo $?
0  # Success!

2. Check section layout

$ objdump -h Kernel2.0/openos.bin
Idx Name          Size      VMA       LMA       File off  Algn
  0 .text         000018c6  00100000  00100000  00000080  2**4

3. Check LOAD segments

$ readelf -l Kernel2.0/openos.bin
LOAD  0x000080 0x00100000 0x00100000 0x026a0 0x1079b0 RWE 0x20

4. Verify multiboot header bytes

$ hexdump -C Kernel2.0/openos.bin -s 0x80 -n 12
00000080  02 b0 ad 1b 03 00 00 00  fb 4f 52 e4  |.........OR.|

5. Python verification

import struct

with open('Kernel2.0/openos.bin', 'rb') as f:
    f.seek(0x80)
    magic, flags, checksum = struct.unpack('<III', f.read(12))
    
    assert magic == 0x1BADB002, "Wrong magic"
    assert flags == 0x00000003, "Wrong flags"
    assert checksum == (-(magic + flags) & 0xFFFFFFFF), "Wrong checksum"
    assert 0x80 < 8192, "Not within 8KB"
    
    print("✓ All checks passed!")

6. Test ISO boot

$ make iso
$ qemu-system-i386 -cdrom openos.iso
# Should boot without "no multiboot header found" error

Key Takeaways

For OSDev Engineers

  1. Never use ALIGN() between multiboot and text sections - it breaks GRUB detection
  2. Always place multiboot header in the first LOAD segment - GRUB only scans loaded memory
  3. Keep multiboot within first 8KB - this is non-negotiable per Multiboot spec
  4. Use grub-file --is-x86-multiboot to verify before testing
  5. Check with readelf -l to ensure section is in LOAD segment

Common Mistakes

❌ Separate .multiboot section with ALIGN(4K) after it
❌ Placing .multiboot at different VMA than .text
❌ Not including .multiboot in any LOAD segment
❌ Assuming linker will “figure it out”

✅ Include .multiboot as first part of .text section
✅ No alignment gap between multiboot and code
✅ Verify with proper tools before testing

GCC Linking Command

The Makefile already uses the correct command:

gcc -T linker.ld -o openos.bin -m32 \
    -ffreestanding -nostdlib -static \
    -Wl,--nmagic -Wl,-z,noexecstack \
    boot.o kernel.o [other objects...]

Key flags:

References

Author Notes

This fix demonstrates why low-level systems programming requires precise understanding of:

Always verify assumptions with tools before assuming the build is correct!