Add Minimal Std implementation for UEFI

Implemented modules:
1. alloc
2. os_str
3. env
4. math

Tracking Issue: https://github.com/rust-lang/rust/issues/100499
API Change Proposal: https://github.com/rust-lang/libs-team/issues/87

This was originally part of https://github.com/rust-lang/rust/pull/100316. Since
that PR was becoming too unwieldy and cluttered, and with suggestion
from @dvdhrm, I have extracted a minimal std implementation to this PR.

Signed-off-by: Ayush Singh <ayushsingh1325@gmail.com>
This commit is contained in:
Ayush Singh 2022-12-18 09:54:54 +05:30 committed by Ayush Singh
parent 5a4e47ebed
commit 48c6ae0611
No known key found for this signature in database
GPG key ID: 05CEF5C789E55A74
24 changed files with 718 additions and 18 deletions

View file

@ -45,6 +45,7 @@ pub unsafe fn __rust_start_panic(_payload: &mut dyn PanicPayload) -> u32 {
} else if #[cfg(any(target_os = "hermit",
all(target_vendor = "fortanix", target_env = "sgx"),
target_os = "xous"
target_os = "uefi",
))] {
unsafe fn abort() -> ! {
// call std::sys::abort_internal

View file

@ -48,6 +48,10 @@ hermit-abi = { version = "0.3.2", features = ['rustc-dep-of-std'], public = true
[target.'cfg(target_os = "wasi")'.dependencies]
wasi = { version = "0.11.0", features = ['rustc-dep-of-std'], default-features = false }
[target.'cfg(target_os = "uefi")'.dependencies]
r-efi = { version = "4.1.0", features = ['rustc-dep-of-std', 'efiapi']}
r-efi-alloc = { version = "1.0.0", features = ['rustc-dep-of-std']}
[features]
backtrace = [
"gimli-symbolize",

View file

@ -39,6 +39,7 @@ fn main() {
|| target.contains("nto")
|| target.contains("xous")
|| target.contains("hurd")
|| target.contains("uefi")
// See src/bootstrap/synthetic_targets.rs
|| env::var("RUSTC_BOOTSTRAP_SYNTHETIC_TARGET").is_ok()
{
@ -51,7 +52,7 @@ fn main() {
// - mipsel-sony-psp
// - nvptx64-nvidia-cuda
// - arch=avr
// - uefi (x86_64-unknown-uefi, i686-unknown-uefi)
// - tvos (aarch64-apple-tvos, x86_64-apple-tvos)
// - JSON targets
// - Any new targets that have not been explicitly added above.
println!("cargo:rustc-cfg=feature=\"restricted-std\"");

View file

@ -263,7 +263,6 @@
#![cfg_attr(target_os = "xous", feature(slice_ptr_len))]
//
// Language features:
// tidy-alphabetical-start
#![feature(alloc_error_handler)]
#![feature(allocator_internals)]
#![feature(allow_internal_unsafe)]

View file

@ -142,6 +142,8 @@ pub mod solid;
#[cfg(target_os = "tvos")]
#[path = "ios/mod.rs"]
pub(crate) mod tvos;
#[cfg(target_os = "uefi")]
pub mod uefi;
#[cfg(target_os = "vita")]
pub mod vita;
#[cfg(target_os = "vxworks")]

View file

@ -0,0 +1,54 @@
//! UEFI-specific extensions to the primitives in `std::env` module
use crate::ffi::c_void;
use crate::ptr::NonNull;
use crate::sync::atomic::{AtomicPtr, Ordering};
use crate::sync::OnceLock;
// Position 0 = SystemTable
// Position 1 = ImageHandle
static GLOBALS: OnceLock<(AtomicPtr<c_void>, AtomicPtr<c_void>)> = OnceLock::new();
/// Initializes the global System Table and Image Handle pointers.
///
/// The standard library requires access to the UEFI System Table and the Application Image Handle
/// to operate. Those are provided to UEFI Applications via their application entry point. By
/// calling `init_globals()`, those pointers are retained by the standard library for future use.
/// The pointers are never exposed to any entity outside of this application and it is guaranteed
/// that, once the application exited, these pointers are never dereferenced again.
///
/// Callers are required to ensure the pointers are valid for the entire lifetime of this
/// application. In particular, UEFI Boot Services must not be exited while an application with the
/// standard library is loaded.
///
/// This function must not be called more than once.
#[unstable(feature = "uefi_std", issue = "100499")]
pub unsafe fn init_globals(handle: NonNull<c_void>, system_table: NonNull<c_void>) {
GLOBALS.set((AtomicPtr::new(system_table.as_ptr()), AtomicPtr::new(handle.as_ptr()))).unwrap()
}
/// Get the SystemTable Pointer.
/// Note: This function panics if the System Table and Image Handle is Not initialized
#[unstable(feature = "uefi_std", issue = "100499")]
pub fn system_table() -> NonNull<c_void> {
try_system_table().unwrap()
}
/// Get the SystemHandle Pointer.
/// Note: This function panics if the System Table and Image Handle is Not initialized
#[unstable(feature = "uefi_std", issue = "100499")]
pub fn image_handle() -> NonNull<c_void> {
try_image_handle().unwrap()
}
/// Get the SystemTable Pointer.
/// This function is mostly intended for places where panic is not an option
pub(crate) fn try_system_table() -> Option<NonNull<crate::ffi::c_void>> {
NonNull::new(GLOBALS.get()?.0.load(Ordering::Acquire))
}
/// Get the SystemHandle Pointer.
/// This function is mostly intended for places where panic is not an option
pub(crate) fn try_image_handle() -> Option<NonNull<crate::ffi::c_void>> {
NonNull::new(GLOBALS.get()?.1.load(Ordering::Acquire))
}

View file

@ -0,0 +1,7 @@
//! Platform-specific extensions to `std` for UEFI.
#![unstable(feature = "uefi_std", issue = "100499")]
pub mod env;
#[path = "../windows/ffi.rs"]
pub mod ffi;

View file

@ -6,7 +6,7 @@
// "static" is for single-threaded platforms where a global static is sufficient.
cfg_if::cfg_if! {
if #[cfg(all(target_family = "wasm", not(target_feature = "atomics")))] {
if #[cfg(any(all(target_family = "wasm", not(target_feature = "atomics")), target_os = "uefi"))] {
#[doc(hidden)]
mod static_local;
#[doc(hidden)]

View file

@ -47,6 +47,9 @@ cfg_if::cfg_if! {
} else if #[cfg(target_os = "xous")] {
mod xous;
pub use self::xous::*;
} 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::*;

View file

@ -0,0 +1,32 @@
//! Global Allocator for UEFI.
//! Uses [r-efi-alloc](https://crates.io/crates/r-efi-alloc)
use crate::alloc::{handle_alloc_error, GlobalAlloc, Layout, System};
const MEMORY_TYPE: u32 = r_efi::efi::LOADER_DATA;
#[stable(feature = "alloc_system_type", since = "1.28.0")]
unsafe impl GlobalAlloc for System {
unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
let system_table = match crate::os::uefi::env::try_system_table() {
None => return crate::ptr::null_mut(),
Some(x) => x.as_ptr() as *mut _,
};
if layout.size() > 0 {
unsafe { r_efi_alloc::raw::alloc(system_table, layout, MEMORY_TYPE) }
} else {
layout.dangling().as_ptr()
}
}
unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) {
let system_table = match crate::os::uefi::env::try_system_table() {
None => handle_alloc_error(layout),
Some(x) => x.as_ptr() as *mut _,
};
if layout.size() > 0 {
unsafe { r_efi_alloc::raw::dealloc(system_table, ptr, layout) }
}
}
}

View file

@ -0,0 +1,269 @@
//! Contains most of the shared UEFI specific stuff. Some of this might be moved to `std::os::uefi`
//! if needed but no point in adding extra public API when there is not Std support for UEFI in the
//! first place
use r_efi::efi::Guid;
use crate::io::{self, const_io_error};
use crate::mem::MaybeUninit;
use crate::os::uefi;
use crate::ptr::NonNull;
// Locate handles with a particular protocol GUID
/// Implemented using `EFI_BOOT_SERVICES.LocateHandles()`
pub(crate) fn locate_handles(mut guid: Guid) -> io::Result<Vec<NonNull<crate::ffi::c_void>>> {
fn inner(
guid: &mut Guid,
boot_services: NonNull<r_efi::efi::BootServices>,
buf_size: &mut usize,
buf: *mut r_efi::efi::Handle,
) -> io::Result<()> {
let r = unsafe {
((*boot_services.as_ptr()).locate_handle)(
r_efi::efi::BY_PROTOCOL,
guid,
crate::ptr::null_mut(),
buf_size,
buf,
)
};
if r.is_error() { Err(status_to_io_error(r)) } else { Ok(()) }
}
let boot_services = boot_services();
let mut buf_len = 0usize;
match inner(&mut guid, boot_services, &mut buf_len, crate::ptr::null_mut()) {
Ok(()) => unreachable!(),
Err(e) => match e.kind() {
io::ErrorKind::FileTooLarge => {}
_ => return Err(e),
},
}
// The returned buf_len is in bytes
let mut buf: Vec<r_efi::efi::Handle> =
Vec::with_capacity(buf_len / crate::mem::size_of::<r_efi::efi::Handle>());
match inner(&mut guid, boot_services, &mut buf_len, buf.as_mut_ptr()) {
Ok(()) => {
// SAFETY: This is safe because the call will succeed only if buf_len >= required
// length. Also, on success, the `buf_len` is updated with the size of bufferv (in
// bytes) written
unsafe { buf.set_len(buf_len / crate::mem::size_of::<r_efi::efi::Handle>()) };
Ok(buf.iter().filter_map(|x| NonNull::new(*x)).collect())
}
Err(e) => Err(e),
}
}
/// Open Protocol on a handle
/// Implemented using `EFI_BOOT_SERVICES.OpenProtocol()`
pub(crate) fn open_protocol<T>(
handle: NonNull<crate::ffi::c_void>,
mut protocol_guid: Guid,
) -> io::Result<NonNull<T>> {
let boot_services = boot_services();
let system_handle = uefi::env::image_handle();
let mut protocol: MaybeUninit<*mut T> = MaybeUninit::uninit();
let r = unsafe {
((*boot_services.as_ptr()).open_protocol)(
handle.as_ptr(),
&mut protocol_guid,
protocol.as_mut_ptr().cast(),
system_handle.as_ptr(),
crate::ptr::null_mut(),
r_efi::system::OPEN_PROTOCOL_GET_PROTOCOL,
)
};
if r.is_error() {
Err(status_to_io_error(r))
} else {
NonNull::new(unsafe { protocol.assume_init() })
.ok_or(const_io_error!(io::ErrorKind::Other, "null protocol"))
}
}
pub(crate) fn status_to_io_error(s: r_efi::efi::Status) -> io::Error {
use io::ErrorKind;
use r_efi::efi::Status;
// Keep the List in Alphabetical Order
// The Messages are taken from UEFI Specification Appendix D - Status Codes
match s {
Status::ABORTED => {
const_io_error!(ErrorKind::ConnectionAborted, "The operation was aborted.")
}
Status::ACCESS_DENIED => {
const_io_error!(ErrorKind::PermissionDenied, "Access was denied.")
}
Status::ALREADY_STARTED => {
const_io_error!(ErrorKind::Other, "The protocol has already been started.")
}
Status::BAD_BUFFER_SIZE => {
const_io_error!(
ErrorKind::InvalidData,
"The buffer was not the proper size for the request."
)
}
Status::BUFFER_TOO_SMALL => {
const_io_error!(
ErrorKind::FileTooLarge,
"The buffer is not large enough to hold the requested data. The required buffer size is returned in the appropriate parameter when this error occurs."
)
}
Status::COMPROMISED_DATA => {
const_io_error!(
ErrorKind::Other,
"The security status of the data is unknown or compromised and the data must be updated or replaced to restore a valid security status."
)
}
Status::CONNECTION_FIN => {
const_io_error!(
ErrorKind::Other,
"The receiving operation fails because the communication peer has closed the connection and there is no more data in the receive buffer of the instance."
)
}
Status::CONNECTION_REFUSED => {
const_io_error!(
ErrorKind::ConnectionRefused,
"The receiving or transmission operation fails because this connection is refused."
)
}
Status::CONNECTION_RESET => {
const_io_error!(
ErrorKind::ConnectionReset,
"The connect fails because the connection is reset either by instance itself or the communication peer."
)
}
Status::CRC_ERROR => const_io_error!(ErrorKind::Other, "A CRC error was detected."),
Status::DEVICE_ERROR => const_io_error!(
ErrorKind::Other,
"The physical device reported an error while attempting the operation."
),
Status::END_OF_FILE => {
const_io_error!(ErrorKind::UnexpectedEof, "The end of the file was reached.")
}
Status::END_OF_MEDIA => {
const_io_error!(ErrorKind::Other, "Beginning or end of media was reached")
}
Status::HOST_UNREACHABLE => {
const_io_error!(ErrorKind::HostUnreachable, "The remote host is not reachable.")
}
Status::HTTP_ERROR => {
const_io_error!(ErrorKind::Other, "A HTTP error occurred during the network operation.")
}
Status::ICMP_ERROR => {
const_io_error!(
ErrorKind::Other,
"An ICMP error occurred during the network operation."
)
}
Status::INCOMPATIBLE_VERSION => {
const_io_error!(
ErrorKind::Other,
"The function encountered an internal version that was incompatible with a version requested by the caller."
)
}
Status::INVALID_LANGUAGE => {
const_io_error!(ErrorKind::InvalidData, "The language specified was invalid.")
}
Status::INVALID_PARAMETER => {
const_io_error!(ErrorKind::InvalidInput, "A parameter was incorrect.")
}
Status::IP_ADDRESS_CONFLICT => {
const_io_error!(ErrorKind::AddrInUse, "There is an address conflict address allocation")
}
Status::LOAD_ERROR => {
const_io_error!(ErrorKind::Other, "The image failed to load.")
}
Status::MEDIA_CHANGED => {
const_io_error!(
ErrorKind::Other,
"The medium in the device has changed since the last access."
)
}
Status::NETWORK_UNREACHABLE => {
const_io_error!(
ErrorKind::NetworkUnreachable,
"The network containing the remote host is not reachable."
)
}
Status::NO_MAPPING => {
const_io_error!(ErrorKind::Other, "A mapping to a device does not exist.")
}
Status::NO_MEDIA => {
const_io_error!(
ErrorKind::Other,
"The device does not contain any medium to perform the operation."
)
}
Status::NO_RESPONSE => {
const_io_error!(
ErrorKind::HostUnreachable,
"The server was not found or did not respond to the request."
)
}
Status::NOT_FOUND => const_io_error!(ErrorKind::NotFound, "The item was not found."),
Status::NOT_READY => {
const_io_error!(ErrorKind::ResourceBusy, "There is no data pending upon return.")
}
Status::NOT_STARTED => {
const_io_error!(ErrorKind::Other, "The protocol has not been started.")
}
Status::OUT_OF_RESOURCES => {
const_io_error!(ErrorKind::OutOfMemory, "A resource has run out.")
}
Status::PROTOCOL_ERROR => {
const_io_error!(
ErrorKind::Other,
"A protocol error occurred during the network operation."
)
}
Status::PROTOCOL_UNREACHABLE => {
const_io_error!(ErrorKind::Other, "An ICMP protocol unreachable error is received.")
}
Status::SECURITY_VIOLATION => {
const_io_error!(
ErrorKind::PermissionDenied,
"The function was not performed due to a security violation."
)
}
Status::TFTP_ERROR => {
const_io_error!(ErrorKind::Other, "A TFTP error occurred during the network operation.")
}
Status::TIMEOUT => const_io_error!(ErrorKind::TimedOut, "The timeout time expired."),
Status::UNSUPPORTED => {
const_io_error!(ErrorKind::Unsupported, "The operation is not supported.")
}
Status::VOLUME_FULL => {
const_io_error!(ErrorKind::StorageFull, "There is no more space on the file system.")
}
Status::VOLUME_CORRUPTED => {
const_io_error!(
ErrorKind::Other,
"An inconstancy was detected on the file system causing the operating to fail."
)
}
Status::WRITE_PROTECTED => {
const_io_error!(ErrorKind::ReadOnlyFilesystem, "The device cannot be written to.")
}
_ => io::Error::new(ErrorKind::Uncategorized, format!("Status: {}", s.as_usize())),
}
}
/// Get the BootServices Pointer.
pub(crate) fn boot_services() -> NonNull<r_efi::efi::BootServices> {
let system_table: NonNull<r_efi::efi::SystemTable> = uefi::env::system_table().cast();
let boot_services = unsafe { (*system_table.as_ptr()).boot_services };
NonNull::new(boot_services).unwrap()
}
/// Get the BootServices Pointer.
/// This function is mostly intended for places where panic is not an option
pub(crate) fn try_boot_services() -> Option<NonNull<r_efi::efi::BootServices>> {
let system_table: NonNull<r_efi::efi::SystemTable> = uefi::env::try_system_table()?.cast();
let boot_services = unsafe { (*system_table.as_ptr()).boot_services };
NonNull::new(boot_services)
}

View file

@ -0,0 +1,9 @@
pub mod os {
pub const FAMILY: &str = "";
pub const OS: &str = "uefi";
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";
}

View file

@ -0,0 +1,155 @@
//! Platform-specific extensions to `std` for UEFI platforms.
//!
//! Provides access to platform-level information on UEFI platforms, and
//! exposes UEFI-specific functions that would otherwise be inappropriate as
//! part of the core `std` library.
//!
//! It exposes more ways to deal with platform-specific strings ([`OsStr`],
//! [`OsString`]), allows to set permissions more granularly, extract low-level
//! file descriptors from files and sockets, and has platform-specific helpers
//! for spawning processes.
//!
//! [`OsStr`]: crate::ffi::OsStr
//! [`OsString`]: crate::ffi::OsString
#![deny(unsafe_op_in_unsafe_fn)]
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/once.rs"]
pub mod once;
#[path = "../unsupported/os.rs"]
pub mod os;
#[path = "../windows/os_str.rs"]
pub mod os_str;
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;
pub(crate) mod common;
#[cfg(test)]
mod tests;
use crate::io as std_io;
use crate::os::uefi;
use crate::ptr::NonNull;
pub mod memchr {
pub use core::slice::memchr::{memchr, memrchr};
}
// SAFETY: must be called only once during runtime initialization.
// SAFETY: argc must be 2.
// SAFETY: argv must be &[Handle, *mut SystemTable].
pub unsafe fn init(argc: isize, argv: *const *const u8, _sigpipe: u8) {
assert_eq!(argc, 2);
let image_handle = unsafe { NonNull::new(*argv as *mut crate::ffi::c_void).unwrap() };
let system_table = unsafe { NonNull::new(*argv.add(1) as *mut crate::ffi::c_void).unwrap() };
unsafe { crate::os::uefi::env::init_globals(image_handle, system_table) };
}
// SAFETY: must be called only once during runtime cleanup.
// NOTE: this is not guaranteed to run, for example when the program aborts.
pub unsafe fn cleanup() {}
#[inline]
pub const fn unsupported<T>() -> std_io::Result<T> {
Err(unsupported_err())
}
#[inline]
pub const fn unsupported_err() -> std_io::Error {
std_io::const_io_error!(std_io::ErrorKind::Unsupported, "operation not supported on UEFI",)
}
pub fn decode_error_kind(code: i32) -> crate::io::ErrorKind {
use crate::io::ErrorKind;
use r_efi::efi::Status;
if let Ok(code) = usize::try_from(code) {
common::status_to_io_error(Status::from_usize(code)).kind()
} else {
ErrorKind::Uncategorized
}
}
pub fn abort_internal() -> ! {
if let (Some(boot_services), Some(handle)) =
(common::try_boot_services(), uefi::env::try_image_handle())
{
let _ = unsafe {
((*boot_services.as_ptr()).exit)(
handle.as_ptr(),
r_efi::efi::Status::ABORTED,
0,
crate::ptr::null_mut(),
)
};
}
// In case SystemTable and ImageHandle cannot be reached, use `core::intrinsics::abort`
core::intrinsics::abort();
}
// This function is needed by the panic runtime. The symbol is named in
// pre-link args for the target specification, so keep that in sync.
#[cfg(not(test))]
#[no_mangle]
pub extern "C" fn __rust_abort() {
abort_internal();
}
#[inline]
pub fn hashmap_random_keys() -> (u64, u64) {
get_random().unwrap()
}
fn get_random() -> Option<(u64, u64)> {
use r_efi::protocols::rng;
let mut buf = [0u8; 16];
let handles = common::locate_handles(rng::PROTOCOL_GUID).ok()?;
for handle in handles {
if let Ok(protocol) = common::open_protocol::<rng::Protocol>(handle, rng::PROTOCOL_GUID) {
let r = unsafe {
((*protocol.as_ptr()).get_rng)(
protocol.as_ptr(),
crate::ptr::null_mut(),
buf.len(),
buf.as_mut_ptr(),
)
};
if r.is_error() {
continue;
} else {
return Some((
u64::from_le_bytes(buf[..8].try_into().ok()?),
u64::from_le_bytes(buf[8..].try_into().ok()?),
));
}
}
}
None
}

View file

@ -0,0 +1,25 @@
use super::unsupported;
use crate::ffi::OsStr;
use crate::io;
use crate::path::{Path, PathBuf, Prefix};
pub const MAIN_SEP_STR: &str = "\\";
pub const MAIN_SEP: char = '\\';
#[inline]
pub fn is_sep_byte(b: u8) -> bool {
b == b'\\'
}
#[inline]
pub fn is_verbatim_sep(b: u8) -> bool {
b == b'\\'
}
pub fn parse_prefix(_p: &OsStr) -> Option<Prefix<'_>> {
None
}
pub(crate) fn absolute(_path: &Path) -> io::Result<PathBuf> {
unsupported()
}

View file

@ -0,0 +1,21 @@
use super::alloc::*;
#[test]
fn align() {
// UEFI ABI specifies that allocation alignment minimum is always 8. So this can be
// statically verified.
assert_eq!(POOL_ALIGNMENT, 8);
// Loop over allocation-request sizes from 0-256 and alignments from 1-128, and verify
// that in case of overalignment there is at least space for one additional pointer to
// store in the allocation.
for i in 0..256 {
for j in &[1, 2, 4, 8, 16, 32, 64, 128] {
if *j <= 8 {
assert_eq!(align_size(i, *j), i);
} else {
assert!(align_size(i, *j) > i + std::mem::size_of::<*mut ()>());
}
}
}
}

View file

@ -44,6 +44,8 @@ cfg_if::cfg_if! {
cfg_if::cfg_if! {
if #[cfg(any(target_os = "l4re",
target_os = "hermit",
target_os = "uefi",
feature = "restricted-std",
all(target_family = "wasm", not(target_os = "emscripten")),
target_os = "xous",