Release mode messes up the multiboot header

Hello, for a school project I’m making a small kernel in Zig 0.12. It works perfectly fine in Debug mode but for wathever reason when I compile it in any Release mode and then boot it in grub I get the error no multiboot header found.
Here’s my boot code :

const ALIGN = 1 << 0;
const MEMINFO = 1 << 1;
const MAGIC = 0x1BADB002;
const FLAGS = ALIGN | MEMINFO;

const MultiBoot = packed struct
{
    magic: i32 = MAGIC,
    flags: i32,
    checksum: i32,
    _: i32 = 0, // doesn't compile without it in 0.12
};

export var multiboot align(4) linksection(".multiboot") = MultiBoot
{
    .flags = FLAGS,
    .checksum = -(MAGIC + FLAGS),
};

export var kernel_stack: [16 * 1024]u8 align(16) linksection(".bss") = undefined;

extern fn kmain() void;

export fn _start() align(16) linksection(".text.boot") callconv(.Naked) noreturn
{
    // Setup the stack and call kernel
    asm volatile (
        \\ movl %[stk], %esp
        \\ call kmain
        :
        : [stk] "{ecx}" (@intFromPtr(&kernel_stack) + @sizeOf(@TypeOf(kernel_stack))),
    );
    while(true) {}
}

and here’s my linker script :

ENTRY(_start)

SECTIONS
{
        . = 2M;

        .text : ALIGN(4K)
        {
                *(.multiboot)
                *(.text)
        }

        .rodata : ALIGN(4K)
        {
                *(.rodata)
        }

        .data : ALIGN(4K)
        {
                *(.data)
        }

        .bss : ALIGN(4K)
        {
                *(COMMON)
                *(.bss)
        }
}

I don’t really know if this is a compiler issue. Any help would be pleased. This is not a real issue as my kernel works fine in debug, I just find it weird.

I even tried to replace the multiboot header by a comptime inline asm in the boot code with no success:

comptime
{
    asm (
        \\ .set ALIGN,    1 << 0
        \\ .set MEMINFO,  1 << 1
        \\ .set FLAGS,    ALIGN | MEMINFO
        \\ .set MAGIC,    0x1BADB002
        \\ .set CHECKSUM, -(MAGIC + FLAGS)
        \\
        \\ .section .multiboot
        \\ .align 4
        \\ .long MAGIC
        \\ .long FLAGS
        \\ .long CHECKSUM
    );
}

Up ! I still have this problem :confused:

Hey Kbz-8, I think the linker is discarding the .multiboot section since the multiboot symbol isn’t referenced or used anywhere. Of course, a Multiboot-compliant bootloader will be looking for the magic number in the header, but the linker doesn’t know that.

To force the linker to keep the .multiboot section in the final output, you can wrap the *(.multiboot) input section description in a KEEP command like so: KEEP(*(.multiboot)). You can find mentions of KEEP in sources like the OSDev Wiki (Multiboot - OSDev Wiki) or in examples like the linker script in Andrew Kelley’s x86 kernel example (HellOS/linker.ld).

The reason your kernel works fine in debug mode but not in release mode is that Zig, by default, passes --gc-sections to the linker for non-debug ELF executables and libraries: zig/src/link/Elf.zig. This option enables garbage collection of unused input sections by the linker. So, Zig itself isn’t really responsible (which is why using inline assembly didn’t make a difference), it’s just telling the linker to remove unused sections to keep your kernel small.

3 Likes