Rust OS with stable toolchain

2024-01-06 @rrobin

One thing that annoys me is that I cannot compile a freestanding operating system kernel without using a nightly rust toolchain or can I?

To compile a kernel we need to pass some special options to the rust compiler and arrange our code to:

This can be done in the nightly toolchain, which is seen is most tutorials out there. Usually core is provided by rustup or built using xargo. The core library uses unstable rust features and so it needs a nightly compiler, but we can work around this.

Building a kernel in Rust

Here is the piece of code we want to build, basically a loop that does nothing.

I have named the function _start since this is the default entry point for linkers, and used extern and no mangle so the symbol is available to the linker too. Finally the no_main and no_str attributes indicate we do not want to link against libstd nor do we have a main function.

My first attempt to build is:

The second error can be solved by compiling with the option -Cpanic=abort. The first error requires implementing a panic handler function (which is usually provided by std).

So we include this function, which just runs a loop:

and try again:

The compiler is calling the linker (gcc) and it is failing because it expects a main function to be present as well as __libc_start_main.

A second attempt passes the necessary linker options to allow us to build. This is the default gcc behaviour, we need to pass it options to disable this behaviour (-nostdlib):

This compilation succeeds, but it is not exactly what we want. It generates a static executable that actually runs the infinite loop. That is a Linux executable. We can see it even uses the link loader (using the file command)

The correct target for bare metal rust is x86_64-unknown-none. So lets try that

It failed because it needs two dependencies (core, compiler_builtins). The default toolchain installed in our system does not include the core crate for x86_64-unknown-none. The error message suggests using rustup to install it.

The dependency on compiler_builtins on core and compiler_builtins is implicitly added by rustc.

Compiling libcore for our target

core provides intrinsic functions. You can find its source in the rust-src component (under rust-src/library/core).

Building it requires a nightly toolchain because core relies on unstable Rust features. Instead of installing a nightly I'm relying on the stable toolchain I have. This can be done by setting the environment variable RUSTC_BOOTSTRAP=1, a trick used when bootstrapping rustc itself.

From the core source folder you can build it like this:

which should generate a libcore.rlib.

Compiling compiler_builtins for our target

Sadly I can't find compiler_builtins inside my rust-src folder. But this may be an issue with my own install.

I was able to find it vendored in the release tarball from

https://static.rust-lang.org/dist/rustc-1.73.0-src.tar.gz

compiler_builtins is also available on crates.io but I am using the same version vendored with rust 1.73.0. The vendored compiler_builtins is 0.1.100. While the latest version is 0.1.105.

Attempting to build compiler_builtins will try to find libcore, I will be using the one built earlier (-L argument):

It should be noted here that compiler_builtins has a build.rs build script. And the features will vary with the target. I gathered these via cargo -v.

Building against our core/compiler_builtins

Lets now attempt to build our program by addding the necessary -L flags:

This error is funny, it seems like rustc inferred the linker to be rust-lld (based on the target?). But I don't have it installed.

Lets specify the linker (gcc) manually then:

This builds, but we don't really want to generate an executable. Instead lets create a static library.

This will create a libosrc.a static library.

Incidentally, if you ever need rustc to print more information about the linking phase add the following environment variable

Actually booting this

x86_64 machines are particular in that we have to go through some work before we can run this code.

I wont go into the details here, IntermezzoOS has a really nice write up on to create a bootable ISO with assembly code that boots into long mode. We can use it here to run our _start function in the generated static library.

IntermezzoOS Booting up

References

https://doc.rust-lang.org/rustc/codegen-options/index.html

https://doc.rust-lang.org/rustc/platform-support/x86_64-unknown-none.html

https://os.phil-opp.com/freestanding-rust-binary/

https://doc.rust-lang.org/core/

https://wiki.osdev.org/Entering_Long_Mode_Directly

https://github.com/phil-opp/blog_os/blob/bef5f13560219e4402c995df4cc4d897bf2bca78/Makefile

https://intermezzos.github.io/book/