Demilade Sonuga's blog

All posts

Getting Started I

2022-10-22 · 17 min read

Now, before we can get to the fun stuff, we first need to do some setup and configuration.

If you don't have a rust compiler, download one from (https://www.rust-lang.org/tools/install).

Create a new Rust project and open it in your favorite text editor.

cargo new blasterball
code blasterball

Your blasterball directory should look like this now

blasterball/
|   src/
|   |   main.rs
|   .gitignore
|   Cargo.toml

and the main.rs file should contain

fn main() {
println!("Hello, world!");
}

We're writing this game for bare metal hardware so when our program starts, there will be no OS giving us any cushy abstractions like processes or threads or standard input or standard output. There will be no unlimited memory, which is an illusion often conjured by modern OSes to make processes (running programs) believe they can use all the memory they want. There won't even be heap allocators, ... or a heap at all. All the abstractions we use (save for the ones provided by the core library) will be built by us.

Such applications need some special configuration to get moving, since rustc, by default, generates code for the platform it's built on.

We need to tell rustc that the generated code is not going to be run with the OS we're developing with. To do this, create a new .cargo directory in your project's root and create a config.toml file in it.

Type the following into config.toml

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

This tells cargo to generate code for x86_64 UEFI platforms.

If you try building the project now, you will get this error

[demilade@fedora blasterball]$ cargo build
   Compiling blasterball v0.1.0 (/home/demilade/Documents/blasterball)
error[E0463]: can't find crate for `std`
  |
  = note: the `x86_64-unknown-uefi` target may not be installed
  = help: consider downloading the target with `rustup target add x86_64-unknown-uefi`

error: cannot find macro `println` in this scope
 --> src/main.rs:2:5
  |
2 |     println!("Hello, world!");
  |     ^^^^^^^

error: requires `sized` lang_item

For more information about this error, try `rustc --explain E0463`.
error: could not compile `blasterball` due to 3 previous errors

The compiler is telling us that it can't find the standard library or the println! macro. This is because we've told it to build for an environment with no OS. println! is in the standard library and the standard library uses OS-specific services to which we don't have access, so we can't use the standard library. To tell rust this, we modify the main.rs file

#![no_std] // NEW

fn main() {
// DELETED: println!("Hello, world!");
}

If you try building again, you'll get another error

[demilade@fedora blasterball]$ cargo build
   Compiling blasterball v0.1.0 (/home/demilade/Documents/blasterball)
error[E0463]: can't find crate for `core`
  |
  = note: the `x86_64-unknown-uefi` target may not be installed
  = help: consider downloading the target with `rustup target add x86_64-unknown-uefi`

error[E0463]: can't find crate for `compiler_builtins`

error: requires `sized` lang_item

For more information about this error, try `rustc --explain E0463`.
error: could not compile `blasterball` due to 3 previous errors

Now, it's telling us it can't find the core or compiler_builtins library. To resolve this, we modify our .cargo/config.toml

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

[unstable] # New
build-std = ["core", "compiler_builtins"] # New
build-std-features = ["compiler-builtins-mem"] # New

The new additions tell the compiler to build the core and compiler_builtins library from the source. Before we can proceed, you need to add the rust-src component and switch to a nightly version of Rust.

rustup component add rust-src
rustup override set nightly

Building the project again:

[demilade@fedora blasterball]$ cargo build
   Compiling core v0.0.0 (/home/demilade/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core)
   Compiling compiler_builtins v0.1.73
   Compiling rustc-std-workspace-core v1.99.0 (/home/demilade/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/rustc-std-workspace-core)
   Compiling blasterball v0.1.0 (/home/demilade/Documents/blasterball)
error: `#[panic_handler]` function required, but not found

error: could not compile `blasterball` due to previous error

And another error. This one's telling us that the panic_handler function can't be found.

"Panicking" is what Rust does when it encounters an irrecoverable error. It's Rust's idea of runtime error. Well, apparently, the function that defines what to do upon a panic was in the standard library and is now no longer available to us. So we need to write our own.

Open up main.rs and add

#![no_std]

fn main() {
}

// NEW:
#[panic_handler]
fn panic_handler(panic_info: &core::panic::PanicInfo) -> ! {
loop {}
}

This tells the compiler that the function panic_handler which we just defined should be called during a panic. The ! type is the never type. Functions that have this return type are called diverging functions, and, as you may have figured out by now, they never return. Our panic_handler is required to have this return type because panics are unrecoverable errors. After a running program panics, it cannot resume normal operation anymore.

Building the project again

[demilade@fedora blasterball]$ cargo build
Compiling blasterball v0.1.0 (/home/demilade/Documents/blasterball)
warning: unused variable: `panic_info`
--> src/main.rs:8:18
|
8 | fn panic_handler(panic_info: &core::panic::PanicInfo) -> ! {
| ^^^^^^^^^^ help: if this is intentional, prefix it with an underscore: `_panic_info`
|
= note: `#[warn(unused_variables)]` on by default

error: requires `start` lang_item

warning: `blasterball` (bin "blasterball") generated 1 warning
error: could not compile `blasterball` due to previous error; 1 warning emitted

Another error. Requires start lang_item. The start lang_item defines the entry point of the program, but we have to remember that this is a bare metal application we're developing here. No OS is going to call our main function, so resolving this requires an understanding of how the computer, exactly, is going to get to the point of running our program. This leads us to the next step in our journey, which is understanding how the computer starts up.

Take Away

  • We need to configure rustc so it generates code for an OS-less machine. We do this with the .cargo/config.toml file we wrote.
  • Panic handlers are called on panics, and we need our custom one because there is no standard library to provide us with a ready-made one.

Your code should be looking like this now:

Directory view:

blasterball/
| .cargo/
| | config.toml
| src/
| | main.rs
| .gitignore
| Cargo.lock
| Cargo.toml

config.toml contents

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

[unstable]
build-std = ["core", "compiler_builtins"]
build-std-features = ["compiler-builtins-mem"]

main.rs contents

#![no_std]

fn main() {

}

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

In the Next Post

We will be looking at what the computer does when it is powered on.

References