OS in Rust: An executable that runs on bare metal: Part-2

Reading Time: 5 minutes

This series pertains to create a basic Operating System and this article is for creating an executable that doesn’t link the standard library so that we can run it directly on bare metal.

Rust Programming Language for Beginners | Udemy

Steps to create a bare-metal executable:

  • Disable standard library
  • Define custom panic handler
  • Provide language items
  • Provide entry point
  • Build executable

Hi readers, in the previous article we have covered the first three steps, and this is the continuation of these steps so here we’ll continue on rest of the steps.

As we already discussed the need for runtime for any program that needs to be executed and how Rust is different from other languages in terms of executing a program.
Alright, before jumping into the entry point section let’s understand again what we need to perform for defining an entry point in our executable.

So here in Rust, execution starts in a C runtime library called crt0, then this C runtime invokes the entry point of Rust runtime which is marked by the start language item, then the Rust runtime calls the main function.

As of now, we are getting error like this:

This image has an empty alt attribute; its file name is screenshot-from-2020-09-19-16-25-06.png

Here, it requires the start language item, but the point here is start lang_item only used to define the entry point and here we do not have access to the Rust runtime as well as crt0(C runtime zero).

Provide entry point

We need to define our entry point however implementing start will not satisfy the requirement because we need to provide the support for crt0 as well. So what we’ll do is, we’ll directly overwrite the crt0 entry point.

The steps for overwrite the entry point in Rust are:

  • Remove main function
  • Provide custom start function
Remove main function

The idea for removing main function is, we don’t want to use the normal entry point chain, and main doesn’t make sense without a runtime that calls it because here we are providing our custom configuration.
To remove main function we need to use no_main attribute like this:

#![no_std]
#![no_main] // this attribute helps to remove main function

use core::panic::PanicInfo;

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

Okay, we have successfully removed the main function from our binary now the next step is to provide the custom start function where we’ll overwrite the operating system’s entry point.

Provide custom start function

Let’s provide our own start function:

#[no_mangle] // this attribute disables the name mangling
pub extern "C" fn _start() -> ! {
   loop {}
}

Let’s understand the functionality of this function.
Here we have used the no_mangle attribute, which disables the name_mangling. So why we need this? The requirement of this attribute is we need to tell the name of the entry point and if we don’t use this attribute then the compiler will create some cryptic symbol to make this name unique, but we don’t want to make this happen.

The next point is we have used the name of this function is _start because it is the default entry point name and we are overwriting the entry point.
Another this we have used is the extern "C", this is used to tell the compiler to use the C calling convention for this function.
And the same return type as we used in our panic function’s implementation, this is required because the entry point is not called by any function, but invoked directly by the operating system.
As of now, our goal is just to overwrite the entry point so we’ve provided the _start function and we just looped indefinitely.

Alright!!! we have now disabled the standard library, defined our custom panic handler, provided the language items, and we just overwrite the entry point as well.
So if we build our program we’ll some different error, i.e, related to the linker.
Here is the error which we got while building our binary:

This image has an empty alt attribute; its file name is screenshot-from-2020-09-20-15-11-16.png

The first thing that comes into our mind is why we are getting this linker error? Right?
So the reason behind this error is the default configuration of the linker assumes that our program depends on the C runtime, but it does not. That’s why we are getting this error. So to solve this error we need to configure the compiler in terms of not include C runtime, to do this we have two approaches:

  • by simply passing set of arguments to the linker, or
  • by building for a bare metal targe

As we are creating an executable that can run on bare metal to we go with the second approach where we’ll build for the bare metal target.

Build executable

So let’s try to build our binary for bare metal, before building it let’s understand the behavior of Rust in terms of building an executable. So by default Rust tries to build an executable that directly points to our system’s environment means it will run on your environment only. For example, binary for Linux, .exe for Windows and so on… and the environment is called the host of our system.

We can check the host of our system by using rustc --version --verbose.
As we are using the Linux Operating System, so our host is [x86_64-unknown-linux-gnu]. It means:
x86_64 : CPU architecture,
unknown : vendor,
linux : an operating system,
gnu : application binary interface

If we build our executable directly means building for host. In this case, our Rust compiler and linker assume that there is an underlying Operating System such as Linux in our case that uses the C runtime by default, which actually causes the linker errors for our executable.

To handle this, we will build our executable for a different environment with no underlying operating system (bare metal environment).

Let’s try to build our executable for thumbv7em-none-eabihf which describes the embedded ARM system. Here none denotes that it has no underlying operating system.

Alright!! we have target that is actually a bare metal and our executable. Now let’s try to build this.
So, before using the target we need to add this in rustup by using this command:

rustup target add thumbv7em-none-eabihf

Now, we are good to build our executable for this target. For building it we need to add --target with our cargo build, so command should be:

cargo build --target thumbv7em-none-eabihf

If we run this command, we’ll get the output like this:

This image has an empty alt attribute; its file name is screenshot-from-2020-09-20-16-26-09.png

This ouput denotes that we have successfully build our executable for bare metal.

This is all about building an executable for bare metal.
In further blogs, we’ll learn how to print something on the screen using our custom Operating System.

Stay tuned!!!

References:

If you want to read more content like this?  Subscribe Rust Times Newsletter and receive insights and latest updates, bi-weekly, straight into your inbox. Subscribe Rust Times Newsletter: https://bit.ly/2Vdlld7 .

This image has an empty alt attribute; its file name is screenshot-from-2020-06-08-11-00-35.png

Knoldus-blog-footer-image

Written by 

Pawan Singh Bisht is a Software Consultant at Knoldus Software LLP, having a strong experience of more than a year in the technology field. He has been well versed in the core implementation of Rust and Java. He loves to contribute towards the community which he attained from the community.