// Start code adapted from https://gitlab.redox-os.org/redox-os/relibc/blob/master/src/start.rs use core::slice; use alloc::{ borrow::ToOwned, boxed::Box, collections::BTreeMap, string::{String, ToString}, vec::Vec, }; use object::{ NativeEndian, elf::{self, PT_DYNAMIC, PT_PHDR}, read::elf::{Dyn as _, ProgramHeader as _}, }; use crate::{ c_str::CStr, header::{ elf::{AT_BASE, AT_ENTRY, AT_PHDR, AT_PHENT, AT_PHNUM}, unistd, }, ld_so::{ dso::{ DT_RELR, DT_RELRENT, DT_RELRSZ, Dyn, ProgramHeader, Rel, Rela, Relocation, RelocationKind, Relr, apply_relr, }, linker::DebugFlags, }, platform::{auxv_iter, get_auxvs, types::c_char}, start::Stack, sync::mutex::Mutex, }; use super::{ PATH_SEP, access::accessible, debug::_r_debug, linker::{Config, Linker}, tcb::Tcb, }; use generic_rt::ExpectTlsFree; #[cfg(target_pointer_width = "32")] pub const SIZEOF_EHDR: usize = 52; #[cfg(target_pointer_width = "64")] pub const SIZEOF_EHDR: usize = 64; unsafe fn get_argv(mut ptr: *const usize) -> (Vec, *const usize) { //traverse the stack and collect argument vector let mut argv = Vec::new(); while unsafe { *ptr != 0 } { let arg = unsafe { *ptr }; match unsafe { CStr::from_ptr(arg as *const c_char).to_str() } { Ok(arg_str) => argv.push(arg_str.to_owned()), _ => { eprintln!("ld.so: failed to parse argv[{}]", argv.len()); unistd::_exit(1); } } ptr = unsafe { ptr.add(1) }; } (argv, ptr) } unsafe fn get_env(mut ptr: *const usize) -> (BTreeMap, *const usize) { //traverse the stack and collect argument environment variables let mut envs = BTreeMap::new(); while unsafe { *ptr != 0 } { let env = unsafe { *ptr }; if let Ok(arg_str) = unsafe { CStr::from_ptr(env as *const c_char).to_str() } { let mut parts = arg_str.splitn(2, '='); if let Some(key) = parts.next() && let Some(value) = parts.next() { envs.insert(key.to_owned(), value.to_owned()); } } ptr = unsafe { ptr.add(1) }; } (envs, ptr) } #[allow(unsafe_op_in_unsafe_fn)] unsafe fn adjust_stack(sp: &'static mut Stack) { let mut argv = sp.argv() as *mut usize; // Move arguments loop { let next_argv = argv.add(1); let arg = *next_argv; *argv = arg; argv = next_argv; if arg == 0 { break; } } // Move environment loop { let next_argv = argv.add(1); let arg = *next_argv; *argv = arg; argv = next_argv; if arg == 0 { break; } } // Move auxiliary vectors loop { let next_argv = argv.add(1); let kind = *next_argv; *argv = kind; argv = next_argv; let next_argv = argv.add(1); let value = *next_argv; *argv = value; argv = next_argv; if kind == 0 { break; } } sp.argc -= 1; } fn resolve_path_name( name_or_path: &str, envs: &BTreeMap, ) -> Option<(String, String)> { if accessible(name_or_path, unistd::F_OK).is_ok() { return Some(( name_or_path.to_string(), name_or_path .split("/") .collect::>() .last() .unwrap() .to_string(), )); } if name_or_path.split("/").collect::>().len() != 1 { return None; } let env_path = envs.get("PATH")?; for part in env_path.split(PATH_SEP) { let path = if part.is_empty() { format!("./{}", name_or_path) } else { format!("{}/{}", part, name_or_path) }; if accessible(&path, unistd::F_OK).is_ok() { return Some((path.to_string(), name_or_path.to_string())); } } None } #[unsafe(no_mangle)] pub unsafe extern "C" fn relibc_ld_so_start( sp: &'static mut Stack, ld_entry: usize, dynamic: *const Dyn, ) -> usize { // Relocate ourselves. // // This function is very delicate as it must **not** contain relocations itself. References to // external symbols **cannot** be made until `stage2()` so, this function might not be very // elegant. // // At this stage the TCB is not setup either so `expect_notls` must be used instead of `expect` // and `unwrap`. let mut at_phdr = None; let mut at_phnum = None; let mut at_phent = None; let mut at_base = None; let mut at_entry = None; for [kind, value] in unsafe { auxv_iter(sp.auxv().cast::()) } { match kind { AT_PHDR => at_phdr = Some(value as *const ProgramHeader), AT_PHNUM => at_phnum = Some(value), AT_PHENT => at_phent = Some(value), AT_BASE => at_base = Some(value), AT_ENTRY => at_entry = Some(value), _ => {} } } let at_phdr = at_phdr.expect_notls("`AT_PHDR` must be present"); let at_phnum = at_phnum.expect_notls("`AT_PHNUM` must be present if `AT_PHDR` is"); let at_phent = at_phent.expect_notls("`AT_PHENT` must be present if `AT_PHDR` is"); assert!(!at_phdr.is_null() && at_phnum != 0 && at_phent == size_of::()); let phdrs = unsafe { slice::from_raw_parts(at_phdr, at_phnum) }; let at_entry = at_entry.expect_notls("`AT_ENTRY` must be present"); let at_base = at_base.unwrap_or_default(); let self_base = if at_base != 0 { at_base } else { let ph = phdrs .iter() .find(|ph| ph.p_type(NativeEndian) == PT_DYNAMIC) .unwrap(); unsafe { dynamic.byte_sub(ph.p_vaddr(NativeEndian) as usize) as usize } }; let is_manual = at_entry == ld_entry; // Whether the dynamic linker was invoked as a command. let mut i = dynamic; let mut rela_ptr = None; let mut rela_len = None; let mut relr_ptr = None; let mut relr_len = None; let mut rel_ptr = None; let mut rel_len = None; loop { let entry = unsafe { &*i }; let val = entry.d_val(NativeEndian); let ptr = val as *const u8; match entry.d_tag(NativeEndian) as u32 { elf::DT_NULL => break, elf::DT_RELA => rela_ptr = Some(ptr.cast::()), elf::DT_RELASZ => rela_len = Some(val as usize / size_of::()), elf::DT_RELAENT => { assert_eq!(val as usize, size_of::(),); } elf::DT_REL => rel_ptr = Some(ptr.cast::()), elf::DT_RELSZ => rel_len = Some(val as usize / size_of::()), elf::DT_RELENT => { assert_eq!(val as usize, size_of::()); } DT_RELR => relr_ptr = Some(ptr.cast::()), DT_RELRSZ => relr_len = Some(val as usize / size_of::()), DT_RELRENT => { assert_eq!(val as usize, size_of::()); } _ => {} } i = unsafe { i.add(1) }; } unsafe fn get_array<'a, T>( ptr: Option<*const T>, len: Option, base_addr: usize, ) -> &'a [T] { if let Some(ptr) = ptr { let len = len.expect_notls("dynamic entry was present without it's corresponding size"); unsafe { core::slice::from_raw_parts(ptr.byte_add(base_addr), len) } } else { &[] } } fn do_relocs<'a, T>(relocs: &'a [T], self_base: usize) where Relocation: From<&'a T>, { for reloc in relocs { let reloc: Relocation = reloc.into(); let ptr = (reloc.offset + self_base) as *mut usize; if reloc.kind == RelocationKind::RELATIVE { unsafe { *ptr = self_base + reloc.addend.unwrap_or_default() }; } } } let rela = unsafe { get_array::(rela_ptr, rela_len, self_base) }; let rel = unsafe { get_array::(rel_ptr, rel_len, self_base) }; do_relocs(rela, self_base); do_relocs(rel, self_base); unsafe { let relr = get_array(relr_ptr, relr_len, self_base); apply_relr(self_base as *const u8, relr); } let mut base_addr = None; if !is_manual { // if we are not running in manual mode, then the main // program is already loaded by the kernel and we want // to use it. on redox, we treat it the same. for ph in phdrs.iter() { if ph.p_type(NativeEndian) == PT_PHDR { assert!(base_addr.is_none(), "`PT_PHDR` cannot occur more than once"); base_addr = Some(unsafe { phdrs .as_ptr() .cast::() .sub(ph.p_vaddr(NativeEndian) as usize) } as usize); } } } stage2(sp, self_base, is_manual, base_addr) } fn stage2( sp: &'static mut Stack, self_base: usize, is_manual: bool, base_addr: Option, ) -> usize { // Setup TCB for ourselves. unsafe { #[cfg(target_os = "redox")] let auxv = sp.auxv().cast(); #[cfg(target_os = "redox")] let thr_fd = crate::platform::get_auxv_raw(auxv, redox_rt::auxv_defs::AT_REDOX_THR_FD) .expect_notls("no thread fd present"); let tcb = Tcb::new(0).expect_notls("[ld.so]: failed to allocate bootstrap TCB"); tcb.activate( #[cfg(target_os = "redox")] Some( redox_rt::proc::FdGuard::new(thr_fd) .to_upper() .expect_notls("failed to move thread fd to upper table"), ), ); #[cfg(target_os = "redox")] { let proc_fd = crate::platform::get_auxv_raw(auxv, redox_rt::auxv_defs::AT_REDOX_PROC_FD) .expect_notls("no proc fd present"); let ns_fd = crate::platform::get_auxv_raw(auxv, redox_rt::auxv_defs::AT_REDOX_NS_FD) .filter(|&fd| fd != usize::MAX) .map(|fd| { redox_rt::proc::FdGuard::new(fd) .to_upper() .expect_notls("failed to move ns fd to upper table") }); redox_rt::initialize( redox_rt::proc::FdGuard::new(proc_fd) .to_upper() .expect_notls("failed to move proc fd to upper table"), ns_fd, ); redox_rt::signal::setup_sighandler(&tcb.os_specific, true); } } // We get the arguments, the environment, and the auxilary vector let (argv, envs, auxv) = unsafe { let argv_start = sp.argv() as *mut usize; let (argv, argv_end) = get_argv(argv_start); let (envs, envs_end) = get_env(argv_end.add(1)); let auxv = get_auxvs(envs_end.add(1)); (argv, envs, auxv) }; unsafe { crate::platform::OUR_ENVIRON.unsafe_set( envs.iter() .map(|(k, v)| { let mut var = Vec::with_capacity(k.len() + v.len() + 2); var.extend(k.as_bytes()); var.push(b'='); var.extend(v.as_bytes()); var.push(b'\0'); let mut var = var.into_boxed_slice(); let ptr = var.as_mut_ptr(); core::mem::forget(var); ptr.cast() }) .chain(core::iter::once(core::ptr::null_mut())) .collect::>(), ); crate::platform::environ = crate::platform::OUR_ENVIRON.unsafe_mut().as_mut_ptr(); } // we might need global lock for this kind of stuff _r_debug.lock().r_ldbase = self_base; let name_or_path = if is_manual { // ld.so is run directly by user and not via execve() or similar systemcall println!("argv: {:#?}", argv); println!("envs: {:#?}", envs); println!("auxv: {:#x?}", auxv); if sp.argc < 2 { eprintln!("ld.so [executable] [arguments...]"); unistd::_exit(1); } unsafe { adjust_stack(sp) }; argv[1].to_string() } else { argv[0].to_string() }; // TODO: Fix memory leak, although minimal. #[cfg(target_os = "redox")] unsafe { crate::platform::init_inner(auxv); } let (path, _name) = match resolve_path_name(&name_or_path, &envs) { Some((p, n)) => (p, n), None => { eprintln!("[ld.so]: failed to locate '{name_or_path}'"); unistd::_exit(1); } }; let config = Config::from_env(&envs); if config.debug_flags.contains(DebugFlags::LOAD) { println!("[ld.so]: relocated self at {self_base:#x}!"); if let Some(base_addr) = base_addr { println!("[ld.so]: executable has been already loaded at {base_addr:#x?}"); } } let mut linker = Linker::new(config); let entry = match linker.load_program(&path, base_addr) { Ok(entry) => entry, Err(err) => { eprintln!("[ld.so]: failed to link '{path}': {err:?}"); eprintln!("[ld.so]: enable debug output with `LD_DEBUG=all` for more information"); unistd::_exit(1); } }; if let Some(tcb) = unsafe { Tcb::current() } { tcb.linker_ptr = Box::into_raw(Box::new(Mutex::new(linker))); } if is_manual { eprintln!("[ld.so]: entry '{path}': {entry:#x}"); } entry }