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.
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:

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:

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 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 .

