Build Pipeline
It took me quite a long time to truly understand what the best approach is to compile my kernel. There were a few requirements I had for myself:
- Rust code should be in rust files, ASM code should be in assembly files
- Assembly should be compiled with
nasm
- The boot should start at
_start
, which is in an assembly file
I do not think it is practical to have 99% in Rust. Assembly is great for having full control of your CPU - you know exactly what your code does. Assembly should mainly focus on booting and attaching systems to the kernel, while Rust should make use of the resources it gets as a kernel.
Build Process
This had a few unforeseen challenges. While compiling both Assembly & Rust separately is straightforward, combining them into one bin
file was quite challenging, especially for testing.
Here's what happens when running cargo run
:
-
First, it calls a Rust build script which:
- Compiles the necessary assembly code with
nasm
- Links it together with Rust
- Compiles the necessary assembly code with
-
Rust is compiled twice (in a way):
- The kernel itself is compiled as a library
- This library is then linked together with the assembly code into a binary file
- The resulting binary file can be used to execute the kernel in
qemu
-
After building,
cargo
uses a customrunner.sh
script that:- Launches
qemu
to execute the kernel binary - Applies different flags based on the mode (e.g.,
cargo test
runs without opening a window)
- Launches
Major Challenge
I encountered one significant challenge that took a long time to resolve. While cargo run
worked without issues, cargo test
would fail to find the multiboot
flags.
Initially, I was convinced the issue was with cargo
, not my linker. After using dumpobj
to investigate, I discovered that multiboot
wasn't present in the binary. The solution was adding a single line to my linker:
KEEP(*(.multiboot))
This forces the linker to keep the multiboot section in place, resolving the issue.