SVM Hypervisor in Rust, Part 3
Getting the bootloader set up#
We will be creating our own bootloader for this, and it’ll be pretty simple. First, lets add some crates to make our lives easy.
Change directory into hv-uefi and do this:
cargo add elf --no-default-features
Next, is the UEFI crate itself.
cargo add uefi --features "alloc global_allocator logger panic_handler"
Let’s add some other stuff too just to make our lives a tiny bit easier.
cargo add log
cargo add x86
Next, open up cargo.toml and add these four lines:
[profile.dev]
panic = "abort"
[profile.release]
panic = "abort"
This changes the method when we recieve a panic so it aborts and sends a nice error message to the UEFI shell and serial output.
We’re not done with this setup yet, we still need to add some other things, most notably the toolchain file. Create a file called “rust_toolchain.toml” and put this in:
[toolchain]
target = x86_64-unknown-uefi
We’re basically done with the whole setup now. Now lets start writing the actual bootloader.
In src/main.rs, let’s get started.
#![no_std]
#![no_main]
use uefi::{entry, Status}
#[entry]
fn main() -> Status {
uefi::helpers::init().unwrap();
Status::SUCCESS
}
This initializes the helper functions like the global allocator, logging and stuff like that. If we were to write something with the log macros such as log! or trace! it would work, try it.
Next, let’s write some things to begin mapping the binary.
#![no_std]
#![no_main]
mod loader;
mod initial_paging;
mod bootinfo;
const FILE_NAME: &str = "hv.bin";
use log::info;
use uefi::{boot::{self, open_protocol_exclusive, ScopedProtocol}, entry, fs::{FileSystem, Path}, proto::{loaded_image::LoadedImage, media::fs::SimpleFileSystem}, CStr16, Status};
#[entry]
fn main() -> Status {
uefi::helpers::init().unwrap();
let loaded_image = open_protocol_exclusive::<LoadedImage>(boot::image_handle()).unwrap();
let sfs = boot::open_protocol_exclusive::<SimpleFileSystem>(loaded_image.device().unwrap()).unwrap();
Status::SUCCESS
}
Just like that, we have an easy way of accessing the SFS protocol. We also have a simple way of reading into a buffer, which I will show you soon.
Let’s create the beginning of the function that will map the HV binary into high memory:
#![no_std]
#![no_main]
mod loader;
mod initial_paging;
mod bootinfo;
const FILE_NAME: &str = "hv.bin";
use log::info;
use uefi::{boot::{self, open_protocol_exclusive, ScopedProtocol}, entry, fs::{FileSystem, Path}, proto::{loaded_image::LoadedImage, media::fs::SimpleFileSystem}, CStr16, Status};
fn map_hv_binary(sfs: ScopedProtocol<SimpleFileSystem>) {
let mut fs = FileSystem::new(sfs);
let mut name_buffer = [0; 7];
let cstr = CStr16::from_str_with_buf(FILE_NAME, &mut name_buffer).unwrap();
let path = Path::new(&cstr);
let buffer = fs.read(path).unwrap();
}
#[entry]
fn main() -> Status {
uefi::helpers::init().unwrap();
let loaded_image = open_protocol_exclusive::<LoadedImage>(boot::image_handle()).unwrap();
let sfs = boot::open_protocol_exclusive::<SimpleFileSystem>(loaded_image.device().unwrap()).unwrap();
map_hv_binary(sfs);
Status::SUCCESS
}
The UEFI Rust crate makes our lives so much easier! We have copied the file into a buffer.
In the next blog post, we’ll set up our paging implementation and how we’ll deal with the UEFI page tables being read only. See ya.