diff --git a/.cargo/config.toml b/.cargo/config.toml index c406f76..7a7a98f 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -1,3 +1,5 @@ -# https://github.com/rui314/mold?tab=readme-ov-file#how-to-use +# Link with Mold, and without libc! We use nostartfiles to avoid the C runtime +# See: +# [target.'cfg(target_os = "linux")'] -rustflags = [ "-C", "link-arg=-fuse-ld=mold", "-C", "link-arg=-lc", "-C", "link-arg=-lgcc_s" ] +rustflags = [ "-C", "link-arg=-fuse-ld=mold", "-C", "link-arg=-nostartfiles" ] diff --git a/crates/lib/src/lib.rs b/crates/lib/src/lib.rs index 7da92a0..84fe5ab 100644 --- a/crates/lib/src/lib.rs +++ b/crates/lib/src/lib.rs @@ -142,22 +142,34 @@ impl Drop for OnceLock { } } -// Access to the environ pointer (provided by libc startup code) -unsafe extern "C" { - static environ: *const *const u8; -} +// Store the environment pointer internally,initialized from `main()`. This +// helps avoid the libc dependency *completely*. +static ENVP: AtomicPtr<*const u8> = AtomicPtr::new(core::ptr::null_mut()); -/// Gets an environment variable by name (without using std). +/// Initialize the environment pointer. Must be called before any `getenv()` +/// calls. This is called from `main()` with the calculated `envp`. /// /// # Safety /// -/// This function reads from the environ global which is initialized -/// by the C runtime before `main()` is called. +/// envp must be a valid null-terminated array of C strings, or null if +/// no environment is available. +#[inline] +pub unsafe fn init_env(envp: *const *const u8) { + ENVP.store(envp.cast_mut(), Ordering::Release); +} + +/// Gets the current environment pointer. +#[inline] +#[must_use] +fn get_envp() -> *const *const u8 { + ENVP.load(Ordering::Acquire) +} + +/// Gets an environment variable by name without using std or libc by reading +/// from the environment pointer set by [`init_env`]. #[must_use] pub fn getenv(name: &str) -> Option<&'static [u8]> { - // SAFETY: environ is set up by the C runtime before main() runs - // and remains valid for the lifetime of the program - let envp = unsafe { environ }; + let envp = get_envp(); if envp.is_null() { return None; } @@ -237,6 +249,7 @@ impl UtsName { if unsafe { sys_uname(uts.as_mut_ptr()) } != 0 { return Err(Error::last_os_error()); } + Ok(Self(unsafe { uts.assume_init() })) } diff --git a/microfetch/src/main.rs b/microfetch/src/main.rs index 0bdc022..25e855c 100644 --- a/microfetch/src/main.rs +++ b/microfetch/src/main.rs @@ -3,40 +3,49 @@ extern crate alloc; -use microfetch_alloc::BumpAlloc; -use microfetch_asm::sys_write; -#[cfg(not(test))] -use {core::panic::PanicInfo, microfetch_asm::sys_exit}; +use core::panic::PanicInfo; +use microfetch_alloc::BumpAllocator; +// Re-export libc replacement functions from asm crate +pub use microfetch_asm::{memcpy, memset, strlen}; +use microfetch_asm::{sys_exit, sys_write}; + +// Global allocator #[global_allocator] -static ALLOCATOR: BumpAlloc = BumpAlloc::new(); +static ALLOCATOR: BumpAllocator = BumpAllocator::new(); -/// Receives argc and argv directly. The C runtime will call this after -/// initializing the environment. Cool right? +/// Main application entry point. Called by the asm crate's entry point +/// after setting up argc, argv, and envp. /// /// # Safety /// /// argv must be a valid pointer to an array of argc C strings. #[unsafe(no_mangle)] pub unsafe extern "C" fn main(argc: i32, argv: *const *const u8) -> i32 { - // SAFETY: argc and argv are provided by the C runtime and are valid + // Calculate envp from argv. On Linux, envp is right after argv on the stack + // but I bet 12 cents that there will be at least one exception. + let argc_usize = usize::try_from(argc).unwrap_or(0); + let envp = unsafe { argv.add(argc_usize + 1) }; + + // Initialize the environment pointer unsafe { - match microfetch_lib::run(argc, argv) { - Ok(()) => 0, - Err(e) => { - // Print error message to stderr (fd 2) - let msg = alloc::format!("Error: {e}\n"); - let _ = sys_write(2, msg.as_ptr(), msg.len()); - 1 - }, - } + microfetch_lib::init_env(envp); + } + + // Run the main application logic + match unsafe { microfetch_lib::run(argc, argv) } { + Ok(()) => 0, + Err(e) => { + let msg = alloc::format!("Error: {e}\n"); + let _ = unsafe { sys_write(2, msg.as_ptr(), msg.len()) }; + 1 + }, } } #[cfg(not(test))] #[panic_handler] fn panic(_info: &PanicInfo) -> ! { - // Write "panic" to stderr and exit const PANIC_MSG: &[u8] = b"panic\n"; unsafe { let _ = sys_write(2, PANIC_MSG.as_ptr(), PANIC_MSG.len()); @@ -44,11 +53,7 @@ fn panic(_info: &PanicInfo) -> ! { } } -// FIXME: Stubs for Rust exception handling symbols needed when using alloc with -// panic=abort These are normally provided by the unwinding runtime, but we're -// using panic=abort. I don't actually think this is the correct approach, but I -// cannot think of anything better. - +// Stubs for Rust exception handling #[cfg(not(test))] #[unsafe(no_mangle)] const extern "C" fn rust_eh_personality() {}