diff --git a/crates/sprout/Cargo.toml b/crates/sprout/Cargo.toml index 9c7a885..9a212f1 100644 --- a/crates/sprout/Cargo.toml +++ b/crates/sprout/Cargo.toml @@ -20,7 +20,7 @@ log.workspace = true [dependencies.uefi] workspace = true -features = ["alloc", "global_allocator", "logger", "panic_handler"] +features = ["alloc", "global_allocator", "panic_handler"] [dependencies.uefi-raw] workspace = true diff --git a/crates/sprout/src/logger.rs b/crates/sprout/src/logger.rs new file mode 100644 index 0000000..570bdde --- /dev/null +++ b/crates/sprout/src/logger.rs @@ -0,0 +1,94 @@ +//! Based on: https://github.com/rust-osdev/uefi-rs/blob/main/uefi/src/helpers/logger.rs + +use alloc::format; +use core::fmt::Write; +use core::ptr; +use core::sync::atomic::{AtomicPtr, Ordering}; +use log::{Log, Record}; +use uefi::proto::console::text::Output; + +/// The global logger object. +static LOGGER: Logger = Logger::new(); + +/// Logging mechanism for Sprout. +/// Must be initialized to be used, as we use atomic pointers to store the output to write to. +pub struct Logger { + writer: AtomicPtr, +} + +impl Default for Logger { + /// Creates a default logger, which is uninitialized with an output. + fn default() -> Self { + Self::new() + } +} + +impl Logger { + /// Create a new logger with an output not specified. + /// This will cause the logger to not print anything until it is configured. + pub const fn new() -> Self { + Self { + writer: AtomicPtr::new(ptr::null_mut()), + } + } + + /// Retrieves the pointer to the output. + /// SAFETY: This pointer might be null, it should be checked before use. + #[must_use] + fn output(&self) -> *mut Output { + self.writer.load(Ordering::Acquire) + } + + /// Sets the output to write to. + /// + /// # Safety + /// This function is unsafe because the output is technically leaked and unmanaged. + pub unsafe fn set_output(&self, output: *mut Output) { + self.writer.store(output, Ordering::Release); + } +} + +impl Log for Logger { + /// Enable the logger always. + fn enabled(&self, _metadata: &log::Metadata<'_>) -> bool { + true + } + + /// Log the specified `record` to the output if one is set. + fn log(&self, record: &Record) { + // Acquire the output. If one is not set, we do nothing. + let Some(output) = (unsafe { self.output().as_mut() }) else { + return; + }; + + // Format the log message. + let message = format!("{}", record.args()); + + // Iterate over every line, formatting the message and writing it to the output. + for line in message.lines() { + // The format writes the log level in front of every line of text. + let _ = writeln!(output, "[{:>5}] {}", record.level(), line); + } + } + + /// This log is not buffered, so flushing isn't required. + fn flush(&self) {} +} + +/// Initialize the logging environment, calling panic if something goes wrong. +pub fn init() { + // Retrieve the stdout handle and set it as the output for the global logger. + uefi::system::with_stdout(|stdout| unsafe { + // SAFETY: We are using the stdout handle to create a pointer to the output. + // The handle is global and is guaranteed to be valid for the lifetime of the program. + LOGGER.set_output(stdout); + }); + + // Set the logger to the global logger. + if let Err(error) = log::set_logger(&LOGGER) { + panic!("unable to set logger: {}", error); + } + + // Set the max level to the level specified by the log features. + log::set_max_level(log::STATIC_MAX_LEVEL); +} diff --git a/crates/sprout/src/main.rs b/crates/sprout/src/main.rs index 7732bcc..29d5718 100644 --- a/crates/sprout/src/main.rs +++ b/crates/sprout/src/main.rs @@ -51,18 +51,24 @@ pub mod extractors; /// generators: Runtime code that can generate entries with specific values. pub mod generators; -/// platform: Integration or support code for specific hardware platforms. -pub mod platform; +/// integrations: Code that interacts with other systems. +pub mod integrations; + +/// logger: Code for the logging mechanism of Sprout. +pub mod logger; /// menu: Display a boot menu to select an entry to boot. pub mod menu; -/// integrations: Code that interacts with other systems. -pub mod integrations; +/// options: Parse the options of the Sprout executable. +pub mod options; /// phases: Hooks into specific parts of the boot process. pub mod phases; +/// platform: Integration or support code for specific hardware platforms. +pub mod platform; + /// sbat: Secure Boot Attestation section. pub mod sbat; @@ -72,9 +78,6 @@ pub mod secure; /// setup: Code that initializes the UEFI environment for Sprout. pub mod setup; -/// options: Parse the options of the Sprout executable. -pub mod options; - /// utils: Utility functions that are used by other parts of Sprout. pub mod utils; @@ -385,6 +388,9 @@ fn run() -> Result<()> { fn efi_main() -> Status { // Initialize the basic UEFI environment. // If initialization fails, we will return ABORTED. + // NOTE: This function will also initialize the logger. + // The logger will panic if it is unable to initialize. + // It is guaranteed that if this returns, the logger is initialized. if let Err(error) = setup::init() { error!("unable to initialize environment: {}", error); return Status::ABORTED; @@ -394,7 +400,7 @@ fn efi_main() -> Status { let result = run(); if let Err(ref error) = result { // Print an error trace. - error!("sprout encountered an error"); + error!("sprout encountered an error:"); for (index, stack) in error.chain().enumerate() { error!("[{}]: {}", index, stack); } diff --git a/crates/sprout/src/setup.rs b/crates/sprout/src/setup.rs index 45de097..50a481a 100644 --- a/crates/sprout/src/setup.rs +++ b/crates/sprout/src/setup.rs @@ -1,8 +1,14 @@ +use crate::logger; use anyhow::{Context, Result}; /// Initializes the UEFI environment. pub fn init() -> Result<()> { - // Initialize the uefi internals. - uefi::helpers::init().context("unable to initialize uefi")?; + // Initialize the logger for Sprout. + // NOTE: This cannot use a result type as errors need to be printed + // using the logger, which is not initialized until this returns. + logger::init(); + + // Initialize further UEFI internals. + uefi::helpers::init().context("unable to initialize uefi environment")?; Ok(()) }