Skip to content

Booting the kernel

Felix Brüggemann edited this page Nov 7, 2024 · 1 revision

GRUB

The GRand Unified Bootloader implements a specification called Multiboot, which is a set of conventions for how a kernel should get loaded into memory. By following its specifications, we can let GRUB load our kernel. We achieve this using something called a "header". It contains information following the Multiboot format, which GRUB will read and follow. To create the kernel binary which will be loaded by GRUB you will need:

Boot code

The header is defined in the boot.s file where its also described with comments

This header then needs to be assembled with nasm:

nasm -f elf64 multiboot_header.s

-f elf64: Specifies ELF output file format, i.e how framework defining how exactly the bits will be laid out in the object file, multiboot_header.o.

Kernel Code

Note: Building with no main is a Rust Nightly feature! main.rs:

#![no_std]
#![no_main]

use core::panic::PanicInfo;

#[panic_handler]
fn panic(_info: &PanicInfo) -> ! {
	loop {}
}

#[no_mangle]
pub extern "C" fn kernel_main() -> ! {
	loop{}
}

Since we are compiling for kernel space, where there is no such thing as a main function, we specify #![no_main] as global attribute, letting Cargo know not to look for it. Instead, we have the _start() symbol, which we will later reference in the linking process. Rust also requires us to have a panic handler, defined with the #[panic_handler] local attribute.

Cargo.toml:

[package]
name = "kfs"
version = "0.1.0"
edition = "2021"

[profile.dev]
panic = "abort"

[profile.release]
panic = "abort"

Nothing crazy here, just specifying the panic behavior, since it will not be set by default due to #![no_std].

.cargo/config.toml:

[unstable]
build-std = ["core"]

[build]
target = "x86_64-unknown-none"

This is our Cargo config, specifying which crates are to be included into our binary. The [build] directive sets the build's target architecture, x86_64, and unkown-none since we are building a custom kernel.

We will need to link our multiboot header and our actual kernel code into one final executable - we need cargo to give us an object file (.o). This can be achieved using Cargo's rustc sub-module, and telling it to emit mentioned object file:

cargo rustc --release -- -emit=obj

This will output our object file into target/x86_64-unknown-none/release/deps/kfs-*.o.

Linking

We will now link multiboot_header.o and kfs-*.o into one binary. For this, we will create a linker script called linker.ld.

We can now use this script to link our binaries like follows:

ld --nmagic --output=kernel --script=linker.ld multiboot_header.o kfs-*.o

--nmagic:

  • Turns off automatic page alignment, reducing the binary file's size.

ISO

ISO File System Structure

iso/
|___boot
   |___grub
   |   |___grub.cfg
   |___kernel

iso/boot/grub/grub.cfg:

set timeout=0
set default=0

menuentry "kfs" {
	multiboot2 /boot/kernel
	boot
}

This file configures GRUB. GRUB lets us load different operating systems, displaying a menu of choices on boot. Each menuentry is one of those choices.

iso/boot/kernel is just the binary we created in the linking process.

We can now create our .iso file using grub-mkrescue:

grub-mkrescue -o kfs.iso ./iso

Running the Kernel

To run our kernel, we use QEMU. QEMU is a system emulator. Run:

qemu-system-x86_64 -cdrom kfs.iso -boot d -nographic

x86_64:

  • Specifies the QEMU variant.

-cdrom kfs.iso:

  • Start QEMU with CD-ROM drive, its contents being kfs.iso.

-boot d

  • Makes QEMU boot from CD-ROM directly instead of trying Hard Disk and Floppy first.

-nographic:

  • Runs the kernel in text mode.

Resources

Clone this wiki locally