Demilade Sonuga's blog
All postsGetting Started I
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
- Andrew Tanenbaum, Modern Operating Systems (https://www.amazon.com/Modern-Operating-Systems-Andrew-Tanenbaum/dp/013359162X)
- https://en.wikipedia.org/wiki/Operating_system
- https://doc.rust-lang.org/cargo/reference/unstable.html?highlight=%5Bunstable%5D#build-std
- https://doc.rust-lang.org/nomicon/panic-handler.html
- https://en.wikipedia.org/wiki/Runtime_(program_lifecycle_phase)
- https://doc.rust-lang.org/std/primitive.never.html
- https://doc.rust-lang.org/reference/types/never.html