Hello Everyone; in my last post, I set up the development environment for working on adding Rust support for UEFI. In this post, I will get a restricted version of std (basically a glorified core + alloc) to work for the x86_64 UEFI target. We will be starting with the no_std
hello_world program from the last post.
Add env module for UEFI in std
Add uefi Module
First, I will add uefi
module under library/std/src/sys
. To do this, we will create a new file library/std/src/sys/uefi/mod.rs
. We will point everything other than the env
module to the unsupported module. The contents of library/std/src/sys/uefi/mod.rs
are given below:
#![deny(unsafe_op_in_unsafe_fn)]
#[path = "../unsupported/alloc.rs"]
pub mod alloc;
#[path = "../unsupported/args.rs"]
pub mod args;
#[path = "../unix/cmath.rs"]
pub mod cmath;
pub mod env;
#[path = "../unsupported/fs.rs"]
pub mod fs;
#[path = "../unsupported/io.rs"]
pub mod io;
#[path = "../unsupported/locks/mod.rs"]
pub mod locks;
#[path = "../unsupported/net.rs"]
pub mod net;
#[path = "../unsupported/os.rs"]
pub mod os;
#[path = "../windows/os_str.rs"]
pub mod os_str;
#[path = "../unix/path.rs"]
pub mod path;
#[path = "../unsupported/pipe.rs"]
pub mod pipe;
#[path = "../unsupported/process.rs"]
pub mod process;
#[path = "../unsupported/stdio.rs"]
pub mod stdio;
#[path = "../unsupported/thread.rs"]
pub mod thread;
#[path = "../unsupported/thread_local_key.rs"]
pub mod thread_local_key;
#[path = "../unsupported/time.rs"]
pub mod time;
#[path = "../unsupported/common.rs"]
#[deny(unsafe_op_in_unsafe_fn)]
mod common;
pub use common::*;
As you can see, cmath
, os_str
, and path
point to the unix
or windows
module instead of unsupported
. This is because unsupported
does not provide any definition for those. The cmath
unix module API can be provided by compiler-builtins
, so that should work. However, os_str
and path
are simply placeholders for now.
Implement env module
The env module is very basic and we just need to define a few constants for it. Here are the contents of library/std/src/sys/uefi/env.rs
:
pub mod os {
pub const FAMILY: &str = "";
pub const OS: &str = "";
pub const DLL_PREFIX: &str = "";
pub const DLL_SUFFIX: &str = "";
pub const DLL_EXTENSION: &str = "";
pub const EXE_SUFFIX: &str = ".efi";
pub const EXE_EXTENSION: &str = "efi";
}
Export UEFI module contents
This can be done by adding the following lines to library/std/sys/mod.rs
:
} else if #[cfg(target_family = "wasm")] {
mod wasm;
pub use self::wasm::*;
+ } else if #[cfg(target_os = "uefi")] {
+ mod uefi;
+ pub use self::uefi::*;
} else if #[cfg(all(target_vendor = "fortanix", target_env = "sgx"))] {
mod sgx;
pub use self::sgx::*;
And we now have the env
module implemented. These constants are exposed under std::env::consts
.
We will now need to build the toolchain again:
./x.py build --stage 1
stage1 should now be pointing to this new toolchain.
Now, we will try to print these constants to UEFI in a very primitive manner (since io, strings, etc have not been implemented yet).
Compile hello_world with std
Firstly, we will remove the no_std
attribute and panic_handler
from src/main.rs
. Then we will update .cargo/config.toml
with the following contents:
[unstable]
build-std = ["std", "compiler_builtins"]
build-std-features = ["compiler-builtins-mem"]
On trying to compile the project now, we get the following error:
error[E0463]: can't find crate for `panic_abort`
error[E0658]: use of unstable library feature 'restricted_std'
|
= help: add `#![feature(restricted_std)]` to the crate attributes to enable
Some errors have detailed explanations: E0463, E0658.
For more information about an error, try `rustc --explain E0463`.
error: could not compile `hello_world` due to 2 previous errors
The first error is a bug in build-std. It doesn’t know how to handle panic_abort vs panic_unwind, so it doesn’t use either crate, resulting in the above error. It can be fixed by adding panic_abort
to .cargo/config.toml
.
The second error can be fixed by adding the restricted_std
feature to the src/main.rs
.
Trying to build now and ………. It fails. We get the following error:
error: linking with `rust-lld` failed: exit status: 1
|
= note: "rust-lld" "-flavor" "link" "/NOLOGO" "/entry:efi_main" "/subsystem:efi_application" "/var/home/ayush/Documents/Programming/Rust/uefi/hello_world/target/target/debug/deps/hello_world-bf96c91d419b97ff.3aumljakx58twtt1.rcgu.o" "/var/home/ayush/Documents/Programming/Rust/uefi/hello_world/target/target/debug/deps/hello_world-bf96c91d419b97ff.11wx9iybsb4s2x54.rcgu.o" "/LIBPATH:/var/home/ayush/Documents/Programming/Rust/uefi/hello_world/target/target/debug/deps" "/LIBPATH:/var/home/ayush/Documents/Programming/Rust/uefi/hello_world/target/debug/deps" "/LIBPATH:/var/home/ayush/Documents/Programming/Rust/rust/build/x86_64-unknown-linux-gnu/stage1/lib/rustlib/target/lib" "/var/home/ayush/Documents/Programming/Rust/uefi/hello_world/target/target/debug/deps/libr_efi-44f0e2c98ca397ed.rlib" "/var/home/ayush/Documents/Programming/Rust/uefi/hello_world/target/target/debug/deps/libstd-0f82fcd1446bb823.rlib" "/var/home/ayush/Documents/Programming/Rust/uefi/hello_world/target/target/debug/deps/libpanic_abort-6267bb336da2fa77.rlib" "/var/home/ayush/Documents/Programming/Rust/uefi/hello_world/target/target/debug/deps/librustc_demangle-12ba4dfb1ce0974e.rlib" "/var/home/ayush/Documents/Programming/Rust/uefi/hello_world/target/target/debug/deps/libstd_detect-af3466d26b0b584c.rlib" "/var/home/ayush/Documents/Programming/Rust/uefi/hello_world/target/target/debug/deps/libhashbrown-282ed18a03cc159e.rlib" "/var/home/ayush/Documents/Programming/Rust/uefi/hello_world/target/target/debug/deps/librustc_std_workspace_alloc-d47af0fe9b191470.rlib" "/var/home/ayush/Documents/Programming/Rust/uefi/hello_world/target/target/debug/deps/libunwind-8ad628841136827a.rlib" "/var/home/ayush/Documents/Programming/Rust/uefi/hello_world/target/target/debug/deps/libcfg_if-f793ff480fd551b6.rlib" "/var/home/ayush/Documents/Programming/Rust/uefi/hello_world/target/target/debug/deps/liblibc-249d18e9ef84acfd.rlib" "/var/home/ayush/Documents/Programming/Rust/uefi/hello_world/target/target/debug/deps/liballoc-4b1b3794d0343e8a.rlib" "/var/home/ayush/Documents/Programming/Rust/uefi/hello_world/target/target/debug/deps/librustc_std_workspace_core-3a7d1ce70363f171.rlib" "/var/home/ayush/Documents/Programming/Rust/uefi/hello_world/target/target/debug/deps/libcore-73e7a0474be04fb7.rlib" "/var/home/ayush/Documents/Programming/Rust/uefi/hello_world/target/target/debug/deps/libcompiler_builtins-4f95060227077e02.rlib" "/NXCOMPAT" "/LIBPATH:/var/home/ayush/Documents/Programming/Rust/rust/build/x86_64-unknown-linux-gnu/stage1/lib/rustlib/target/lib" "/OUT:/var/home/ayush/Documents/Programming/Rust/uefi/hello_world/target/target/debug/deps/hello_world-bf96c91d419b97ff.efi" "/OPT:REF,NOICF" "/DEBUG" "/NODEFAULTLIB"
= note: rust-lld: error: undefined symbol: __CxxFrameHandler3
>>> referenced by libstd-0f82fcd1446bb823.rlib(std-0f82fcd1446bb823.std.ea9c30f6-cgu.2.rcgu.o):(.xdata)
>>> referenced by libstd-0f82fcd1446bb823.rlib(std-0f82fcd1446bb823.std.ea9c30f6-cgu.2.rcgu.o):(.xdata)
error: could not compile `hello_world` due to previous error
This error is, well, a bit weird. We can fix the build for now by providing a blank implementation of __CxxFrameHandler3
, but this needs more research. The following lines need to be added to src/main.rs
:
#[no_mangle]
pub extern "C" fn __CxxFrameHandler3() {}
The application builds and runs fine now.
Print EXE_EXTENSION constant
Now we will print EXE_EXTENSION to the console. Since we do not have io and string implemented yet, we will have to do it in a primitive way using u16
arrays. The final src/main.rs
is given below:
#![no_main]
#![feature(restricted_std)]
use r_efi::efi;
use std::env::consts;
#[no_mangle]
pub extern "C" fn __CxxFrameHandler3() {}
fn print_efi(s: &[u16], st: *mut efi::SystemTable) -> Result<(), r_efi::base::Status> {
let r = unsafe { ((*(*st).con_out).output_string)((*st).con_out, s.as_ptr() as *mut efi::Char16) };
if r.is_error() {
Err(r)
} else {
Ok(())
}
}
fn print_newline(st: *mut efi::SystemTable) -> Result<(), r_efi::base::Status> {
let mut s = [0;2];
create_const_uefi_str("\n", &mut s);
print_efi(&s, st)
}
fn create_const_uefi_str(const_str: &str, uefi_array: &mut [u16]) {
let mut i = 0;
for v in const_str.bytes() {
uefi_array[i] = v as u16;
i += 1;
}
uefi_array[i] = 0x0000u16;
}
fn print_env_constants(st: *mut efi::SystemTable) -> Result<(), r_efi::base::Status> {
let mut exe_extension_heading = [0; 16];
create_const_uefi_str("exe_extension: ", &mut exe_extension_heading);
print_efi(&exe_extension_heading, st)?;
let mut exe_extension = [0; 5];
create_const_uefi_str(consts::EXE_EXTENSION, &mut exe_extension);
print_efi(&exe_extension, st)?;
print_newline(st)
}
#[export_name = "efi_main"]
pub extern "C" fn main(_h: efi::Handle, st: *mut efi::SystemTable) -> efi::Status {
let r = print_env_constants(st);
if let Err(x) = r {
return x;
}
efi::Status::SUCCESS
}
Here is the program running under qemu:
With this, we are using our new std
for UEFI.
Edit
The __CxxFrameHandler3
blank implementation is no longer required in the master
branch. I was previously basing my changes on the v1.61.0 tag. However, from now, I am going to work on master directly.
Conclusion
Technically, we are now using std
(even though none of it has yet been implemented). Now I will slowly start implementing parts of std
starting with allocation. I also wanted to find a way to use the normal Rust main
function instead of the current efi_main
. However, this still does not seem possible (see #29633). So, let’s get allocation working and replace all the arrays with vectors in this code.
Consider supporting me if you like my work.