various: (ab)use the new syscall wrappers and symbols; drop libc entirely

Signed-off-by: NotAShelf <raf@notashelf.dev>
Change-Id: I19ecd9801cf6e04adcedd3003d9fc59d6a6a6964
This commit is contained in:
raf 2026-03-27 23:45:30 +03:00
commit d6977bafe5
Signed by: NotAShelf
GPG key ID: 29D95B64378DB4BF
3 changed files with 55 additions and 35 deletions

View file

@ -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:
# <https://github.com/rui314/mold?tab=readme-ov-file#how-to-use>
[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" ]

View file

@ -142,22 +142,34 @@ impl<T> Drop for OnceLock<T> {
}
}
// 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() }))
}

View file

@ -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() {}