diff --git a/src/header/dl-tls/mod.rs b/src/header/dl-tls/mod.rs index 095f1edbd0..de591772be 100644 --- a/src/header/dl-tls/mod.rs +++ b/src/header/dl-tls/mod.rs @@ -29,14 +29,14 @@ pub unsafe extern "C" fn __tls_get_addr(ti: *mut dl_tls_index) -> *mut c_void { tcb.dtv_mut().unwrap().len() ); - if tcb.dtv_mut().unwrap_or_default().len() < masters.len() { + if tcb.dtv_mut().len() < masters.len() { // Reallocate DTV. tcb.setup_dtv(masters.len()); } let dtv_index = ti.ti_module as usize - 1; - if tcb.dtv_mut().unwrap()[dtv_index].is_null() { + if tcb.dtv_mut()[dtv_index].is_null() { // Allocate TLS for module. let master = &masters[dtv_index]; @@ -50,10 +50,10 @@ pub unsafe extern "C" fn __tls_get_addr(ti: *mut dl_tls_index) -> *mut c_void { unsafe { core::ptr::copy_nonoverlapping(master.ptr, module_tls, master.len) }; // Set the DTV entry. - tcb.dtv_mut().unwrap()[dtv_index] = module_tls; + tcb.dtv_mut()[dtv_index] = module_tls; } - let mut ptr = tcb.dtv_mut().unwrap()[dtv_index]; + let mut ptr = tcb.dtv_mut()[dtv_index]; if ptr.is_null() { panic!( diff --git a/src/header/dlfcn/mod.rs b/src/header/dlfcn/mod.rs index b12f834b42..e1230c7dcd 100644 --- a/src/header/dlfcn/mod.rs +++ b/src/header/dlfcn/mod.rs @@ -3,17 +3,30 @@ #![deny(unsafe_op_in_unsafe_fn)] use core::{ - ptr, str, + ptr::{self, NonNull}, + str, sync::atomic::{AtomicUsize, Ordering}, }; -use crate::{c_str::CStr, ld_so::tcb::Tcb, platform::types::*}; +use alloc::sync::Arc; -pub const RTLD_LAZY: c_int = 0x0001; -pub const RTLD_NOW: c_int = 0x0002; -pub const RTLD_GLOBAL: c_int = 0x0100; +use crate::{ + c_str::CStr, + ld_so::{ + linker::{ObjectHandle, ObjectScope, Resolve}, + tcb::Tcb, + }, + platform::types::*, +}; + +pub const RTLD_LAZY: c_int = 1 << 0; +pub const RTLD_NOW: c_int = 1 << 1; +pub const RTLD_NOLOAD: c_int = 1 << 2; +pub const RTLD_GLOBAL: c_int = 1 << 8; pub const RTLD_LOCAL: c_int = 0x0000; +pub const RTLD_DEFAULT: *mut c_void = 0 as *mut c_void; // XXX: cbindgen doesn't like ptr::null_mut() + static ERROR_NOT_SUPPORTED: CStr = c_str!("dlfcn not supported"); #[thread_local] @@ -42,6 +55,19 @@ pub unsafe extern "C" fn dladdr(addr: *mut c_void, info: *mut Dl_info) -> c_int #[no_mangle] pub unsafe extern "C" fn dlopen(cfilename: *const c_char, flags: c_int) -> *mut c_void { //TODO support all sort of flags + let resolve = if flags & RTLD_NOW == RTLD_NOW { + Resolve::Now + } else { + Resolve::Lazy + }; + + let scope = if flags & RTLD_GLOBAL == RTLD_GLOBAL { + ObjectScope::Global + } else { + ObjectScope::Local + }; + + let noload = flags & RTLD_NOLOAD == RTLD_NOLOAD; let filename = if cfilename.is_null() { None @@ -69,19 +95,19 @@ pub unsafe extern "C" fn dlopen(cfilename: *const c_char, flags: c_int) -> *mut let cbs_c = linker.cbs.clone(); let cbs = cbs_c.borrow(); - let id = match (cbs.load_library)(&mut linker, filename) { - Err(err) => { + match (cbs.load_library)(&mut linker, filename, resolve, scope, noload) { + Ok(handle) => handle.as_ptr().cast_mut(), + Err(error) => { ERROR.store(ERROR_NOT_SUPPORTED.as_ptr() as usize, Ordering::SeqCst); - return ptr::null_mut(); + ptr::null_mut() } - Ok(id) => id, - }; - - id as *mut c_void + } } #[no_mangle] pub unsafe extern "C" fn dlsym(handle: *mut c_void, symbol: *const c_char) -> *mut c_void { + let handle = ObjectHandle::from_ptr(handle); + if symbol.is_null() { ERROR.store(ERROR_NOT_SUPPORTED.as_ptr() as usize, Ordering::SeqCst); return ptr::null_mut(); @@ -89,6 +115,9 @@ pub unsafe extern "C" fn dlsym(handle: *mut c_void, symbol: *const c_char) -> *m let symbol_str = unsafe { str::from_utf8_unchecked(CStr::from_ptr(symbol).to_bytes()) }; + // FIXME(andypython): just call obj.scope.get_sym() directly or search the + // global scope. The rest is unnecessary as Linker::get_sym() does not + // depend on the Linker state. let tcb = match unsafe { Tcb::current() } { Some(tcb) => tcb, None => { @@ -105,7 +134,7 @@ pub unsafe extern "C" fn dlsym(handle: *mut c_void, symbol: *const c_char) -> *m let linker = unsafe { (&*tcb.linker_ptr).lock() }; let cbs_c = linker.cbs.clone(); let cbs = cbs_c.borrow(); - match (cbs.get_sym)(&linker, handle as usize, symbol_str) { + match (cbs.get_sym)(&linker, handle, symbol_str) { Some(sym) => sym, _ => { ERROR.store(ERROR_NOT_SUPPORTED.as_ptr() as usize, Ordering::SeqCst); @@ -128,10 +157,16 @@ pub unsafe extern "C" fn dlclose(handle: *mut c_void) -> c_int { ERROR.store(ERROR_NOT_SUPPORTED.as_ptr() as usize, Ordering::SeqCst); return -1; }; + + let Some(handle) = ObjectHandle::from_ptr(handle) else { + ERROR.store(ERROR_NOT_SUPPORTED.as_ptr() as usize, Ordering::SeqCst); + return -1; + }; + let mut linker = unsafe { (&*tcb.linker_ptr).lock() }; let cbs_c = linker.cbs.clone(); let cbs = cbs_c.borrow(); - (cbs.unload)(&mut linker, handle as usize); + (cbs.unload)(&mut linker, handle); 0 } diff --git a/src/ld_so/callbacks.rs b/src/ld_so/callbacks.rs index 1d7d7a05f4..64dc41dc08 100644 --- a/src/ld_so/callbacks.rs +++ b/src/ld_so/callbacks.rs @@ -1,12 +1,13 @@ -use super::linker::Linker; +use super::linker::{Linker, ObjectHandle, ObjectScope, Resolve}; use crate::platform::types::c_void; use alloc::boxed::Box; use goblin::error::Result; pub struct LinkerCallbacks { - pub unload: Box, - pub load_library: Box) -> Result>, - pub get_sym: Box Option<*mut c_void>>, + pub unload: Box, + pub load_library: + Box, Resolve, ObjectScope, bool) -> Result>, + pub get_sym: Box, &str) -> Option<*mut c_void>>, } impl LinkerCallbacks { @@ -19,14 +20,20 @@ impl LinkerCallbacks { } } -fn unload(linker: &mut Linker, lib_id: usize) { - linker.unload(lib_id) +fn unload(linker: &mut Linker, handle: ObjectHandle) { + linker.unload(handle) } -fn load_library(linker: &mut Linker, name: Option<&str>) -> Result { - linker.load_library(name) +fn load_library( + linker: &mut Linker, + name: Option<&str>, + resolve: Resolve, + scope: ObjectScope, + noload: bool, +) -> Result { + linker.load_library(name, resolve, scope, noload) } -fn get_sym(linker: &Linker, lib_id: usize, name: &str) -> Option<*mut c_void> { - linker.get_sym(lib_id, name) +fn get_sym(linker: &Linker, handle: Option, name: &str) -> Option<*mut c_void> { + linker.get_sym(handle, name) } diff --git a/src/ld_so/dso.rs b/src/ld_so/dso.rs index 62d20aae81..c2cc36790a 100644 --- a/src/ld_so/dso.rs +++ b/src/ld_so/dso.rs @@ -1,11 +1,13 @@ +//! See . + use super::{ debug::{RTLDDebug, _r_debug}, - linker::Symbol, + linker::{Scope, Symbol}, tcb::Master, }; use crate::{ - header::{errno::STR_ERROR, sys_mman}, - platform::{types::c_void, Pal, Sys, ERRNO}, + header::sys_mman, + platform::{types::c_void, Pal, Sys}, }; use alloc::{ collections::BTreeMap, @@ -14,7 +16,8 @@ use alloc::{ }; use core::{ mem::{size_of, transmute}, - ptr, slice, + ptr::{self, NonNull}, + slice, }; #[cfg(target_pointer_width = "32")] use goblin::elf32::{ @@ -33,12 +36,34 @@ use goblin::elf64::{ sym, }; use goblin::{ - elf::Elf, + elf::{ + dynamic::DT_PLTGOT, + sym::{STB_GLOBAL, STB_WEAK}, + Dynamic, Elf, + }, error::{Error, Result}, }; +#[derive(Debug, PartialEq)] +#[repr(u8)] +pub enum SymbolBinding { + /// Global symbols are visible to all object files being combined. One + /// file's definition of a global symbol will satisfy another file's + /// undefined reference to the same global symbol. + Global = STB_GLOBAL, + /// Weak symbols resemble global symbols, but their definitions have lower + /// precedence. + Weak = STB_WEAK, +} + +impl SymbolBinding { + #[inline] + pub fn is_global(&self) -> bool { + matches!(self, Self::Global) + } +} + /// Use to represent a library as well as all the symbols that is loaded withen it. -#[derive(Default)] pub struct DSO { pub name: String, pub id: usize, @@ -56,7 +81,13 @@ pub struct DSO { pub fini_array: (usize, usize), pub tls_module_id: usize, pub tls_offset: usize, - pub use_count: usize, + + pub dynamic: Option, + pub dynsyms: Vec, + + pub scope: Scope, + /// Position Independent Executable. + pub pie: bool, } impl DSO { @@ -88,33 +119,67 @@ impl DSO { elf.header.e_entry as usize }; let dso = DSO { - name: name, - id: id, - use_count: 1, - dlopened: dlopened, - entry_point: entry_point, + name, + id, + dlopened, + entry_point, runpath: DSO::get_runpath(&path, &elf)?, - mmap: mmap, - global_syms: global_syms, - weak_syms: weak_syms, + mmap, + global_syms, + weak_syms, dependencies: elf.libraries.iter().map(|s| s.to_string()).collect(), - init_array: init_array, - fini_array: fini_array, + init_array, + fini_array, tls_module_id: if tcb_master.is_some() { tls_module_id } else { 0 }, - tls_offset: tls_offset, + tls_offset, + + pie: is_pie_enabled(&elf), + dynamic: elf.dynamic.map(|dynamic| dynamic), + dynsyms: elf.dynsyms.iter().collect(), + + scope: Scope::local(), }; - return Ok((dso, tcb_master)); + + Ok((dso, tcb_master)) } - pub fn get_sym(&self, name: &str) -> Option<(Symbol, bool)> { + /// Global Offset Table + pub(super) fn got(&self) -> Option> { + let Some(dynamic) = self.dynamic.as_ref() else { + return None; + }; + + let object_base_addr = self.mmap.as_ptr() as u64; + + let got = if let Some(ptr) = { + dynamic + .dyns + .iter() + .find(|r#dyn| r#dyn.d_tag == DT_PLTGOT) + .map(|r#dyn| r#dyn.d_val) + } { + if self.pie { + (object_base_addr + ptr) as *mut usize + } else { + ptr as *mut usize + } + } else { + assert_eq!(dynamic.info.jmprel, 0); + return None; + }; + + Some(NonNull::new(got).expect("global offset table")) + } + + pub fn get_sym(&self, name: &str) -> Option<(Symbol, SymbolBinding)> { if let Some(value) = self.global_syms.get(name) { - Some((*value, true)) + Some((*value, SymbolBinding::Global)) } else if let Some(value) = self.weak_syms.get(name) { - Some((*value, false)) + Some((*value, SymbolBinding::Weak)) } else { None } @@ -147,8 +212,8 @@ impl DSO { Some(entry) => { let runpath = elf .dynstrtab - .get(entry.d_val as usize) - .ok_or(Error::Malformed("Missing RUNPATH in dynstrtab".to_string()))??; + .get_at(entry.d_val as usize) + .ok_or(Error::Malformed("Missing RUNPATH in dynstrtab".to_string()))?; let base = dirname(path); return Ok(Some(runpath.replace("$ORIGIN", &base))); } @@ -249,7 +314,7 @@ impl DSO { // Copy data for ph in elf.program_headers.iter() { let voff = ph.p_vaddr % ph.p_align; - let vaddr = (ph.p_vaddr - voff) as usize; + // let vaddr = (ph.p_vaddr - voff) as usize; let vsize = ((ph.p_memsz + voff) as usize).next_multiple_of(ph.p_align as usize); match ph.p_type { @@ -361,8 +426,8 @@ impl DSO { } let name: String; let value: Symbol; - if let Some(name_res) = elf.dynstrtab.get(sym.st_name) { - name = name_res?.to_string(); + if let Some(name_res) = elf.dynstrtab.get_at(sym.st_name) { + name = name_res.to_string(); value = if is_pie_enabled(elf) { Symbol { base: mmap.as_ptr() as usize, @@ -404,7 +469,7 @@ impl DSO { .iter() .filter(|s| s.sh_type == SHT_INIT_ARRAY || s.sh_type == SHT_FINI_ARRAY) { - let addr = if is_pie_enabled(&elf) { + let addr = if is_pie_enabled(elf) { mmap_addr + section.vm_range().start } else { section.vm_range().start @@ -422,7 +487,7 @@ impl DSO { impl Drop for DSO { fn drop(&mut self) { self.run_fini(); - unsafe { Sys::munmap(self.mmap.as_mut_ptr() as *mut c_void, self.mmap.len()) }; + unsafe { Sys::munmap(self.mmap.as_mut_ptr() as *mut c_void, self.mmap.len()).unwrap() }; } } diff --git a/src/ld_so/linker.rs b/src/ld_so/linker.rs index 8d0c91f1ad..1ac0b59e92 100644 --- a/src/ld_so/linker.rs +++ b/src/ld_so/linker.rs @@ -2,11 +2,16 @@ use alloc::{ collections::BTreeMap, rc::Rc, string::{String, ToString}, + sync::{Arc, Weak}, vec::Vec, }; -use core::{cell::RefCell, mem::transmute, ptr}; +use core::{ + cell::RefCell, + mem::transmute, + ptr::{self, NonNull}, +}; use goblin::{ - elf::{program_header, reloc, sym::STT_TLS, Elf}, + elf::{dynamic::DT_STRTAB, program_header, reloc, sym::STT_TLS, Elf}, error::{Error, Result}, }; @@ -19,10 +24,12 @@ use crate::{ unistd::F_OK, }, io::{self, Read}, + ld_so::dso::SymbolBinding, platform::{ - types::{c_int, c_void}, + types::{c_char, c_int, c_uint, c_void}, Pal, Sys, }, + sync::Mutex, }; use super::{ @@ -31,9 +38,19 @@ use super::{ debug::{RTLDState, _dl_debug_state, _r_debug}, dso::{is_pie_enabled, DSO}, tcb::{Master, Tcb}, - ExpectTlsFree, PATH_SEP, + PATH_SEP, }; +// FIXME: ERROR HANDLING IS WRONG! +// load_library may return an error which may have some variants heap allocated. +// eg Error::Malformed(String::from("failed to locate 'libfoo.so'")) +// therefore dropping it in dlopen would cause heap corruption as it was allocated +// by the dynamic linker's and was freed by the program. +// do better errors than just goblin::Error::Malformed + +// TODO: rwlock? +static GLOBAL_SCOPE: Mutex = Mutex::new(Scope::global()); + /// Same as [`crate::fs::File`], but does not touch [`crate::platform::ERRNO`] as the dynamic /// linker does not have thread-local storage. struct File { @@ -68,13 +85,172 @@ impl Symbol { } } +#[derive(Debug, Default, Copy, Clone, PartialEq)] +pub enum Resolve { + /// Resolve all undefined symbols immediately. + #[cfg_attr(not(target_arch = "x86_64"), default)] + Now, + /// Perform lazy binding (i.e. symbols will be resolved when they are first + /// used). + #[cfg_attr(target_arch = "x86_64", default)] + Lazy, +} + +#[derive(Debug, Copy, Clone, PartialEq)] +pub enum ObjectScope { + Global, + Local, +} + +pub enum Scope { + Global { + objs: Vec>, + }, + Local { + owner: Option>, + objs: Vec>, + }, +} + +impl Scope { + #[inline] + const fn global() -> Self { + Self::Global { objs: Vec::new() } + } + + #[inline] + pub(super) const fn local() -> Self { + Self::Local { + owner: None, + objs: Vec::new(), + } + } + + fn set_owner(&mut self, obj: Weak) { + match self { + Self::Global { .. } => panic!("attempted to set global scope owner"), + Self::Local { ref mut owner, .. } => { + assert!(owner.is_none(), "attempted to change local scope owner"); + *owner = Some(obj); + } + } + } + + fn add(&mut self, target: &Arc) { + match self { + Self::Global { objs } => { + let target = Arc::downgrade(&target); + for obj in objs.iter() { + if Weak::ptr_eq(&obj, &target) { + return; + } + } + + objs.push(target); + } + + Self::Local { objs, .. } => { + for obj in objs.iter() { + if Arc::ptr_eq(obj, &target) { + return; + } + } + + objs.push(target.clone()); + } + } + } + + fn get_sym(&self, name: &str) -> Option<(Symbol, SymbolBinding, Arc)> { + let mut res = None; + + let get_sym = |obj: Arc| { + if let Some((sym, binding)) = obj.get_sym(name) { + if binding.is_global() { + return Some((sym, binding, obj.clone())); + } + + res = Some((sym, binding, obj.clone())); + } + + None + }; + + match self { + Self::Global { objs } => objs.iter().map(|o| o.upgrade().unwrap()).find_map(get_sym), + Self::Local { owner, objs } => { + let owner = owner + .as_ref() + .expect("local scope without owner") + .upgrade() + .expect("local scope owner was dropped"); + + core::iter::once(owner) + .chain(objs.iter().cloned()) + .find_map(get_sym) + } + } + .or_else(|| res) + } + + fn move_into(&self, other: &mut Self) { + // FIXME: move not copy? as afiak u cannot downgrade from a global to a local scope. + match (self, other) { + (Self::Local { owner, objs }, Self::Global { objs: other_objs }) => { + let owner = owner.as_ref().expect("local scope without owner"); + other_objs.push(owner.clone()); + other_objs.extend(objs.iter().map(|o| Arc::downgrade(o))); + } + + _ => unreachable!(), + } + } +} + +// Used by dlfcn.h +// +// We need this as the handle must be created and destroyed with the dynamic +// linker's allocator. +pub struct ObjectHandle(*const DSO); + +impl ObjectHandle { + #[inline] + fn new(obj: Arc) -> Self { + Self(Arc::into_raw(obj)) + } + + #[inline] + fn into_inner(&self) -> Arc { + unsafe { Arc::from_raw(self.0) } + } + + #[inline] + pub fn as_ptr(&self) -> *const c_void { + self.0.cast() + } + + #[inline] + pub fn from_ptr(ptr: *const c_void) -> Option { + NonNull::new(ptr as *mut DSO).map(|ptr| Self(ptr.as_ptr())) + } +} + +impl AsRef for ObjectHandle { + #[inline] + fn as_ref(&self) -> &DSO { + unsafe { &*self.0 } + } +} + bitflags::bitflags! { #[derive(Debug, Default)] pub struct DebugFlags: u32 { - /// Displays what objects and where they are being loaded. + /// Display what objects and where they are being loaded. const BASE_ADDRESS = 1 << 1; - /// Displays library search paths. + /// Display library search paths. const SEARCH = 1 << 2; + /// Display scope information. + const SCOPES = 1 << 3; } } @@ -82,6 +258,8 @@ bitflags::bitflags! { pub struct Config { debug_flags: DebugFlags, library_path: Option, + /// Resolve symbols at program startup. + bind_now: bool, } impl Config { @@ -94,6 +272,7 @@ impl Config { flags |= match opt { "baseaddr" => DebugFlags::BASE_ADDRESS, "search" => DebugFlags::SEARCH, + "scopes" => DebugFlags::SCOPES, _ => { eprintln!("[ld.so]: unknown debug flag '{}'", opt); DebugFlags::empty() @@ -105,11 +284,13 @@ impl Config { }) .unwrap_or(DebugFlags::empty()); - let library_path = env.get("LD_LIBRARY_PATH").cloned(); - Self { debug_flags, - library_path, + library_path: env.get("LD_LIBRARY_PATH").cloned(), + bind_now: env + .get("LD_BIND_NOW") + .map(|value| !value.is_empty()) + .unwrap_or_default(), } } } @@ -120,18 +301,18 @@ pub struct Linker { next_object_id: usize, next_tls_module_id: usize, tls_size: usize, - objects: BTreeMap, + objects: BTreeMap>, name_to_object_id_map: BTreeMap, pub cbs: Rc>, } -const root_id: usize = 1; +const ROOT_ID: usize = 1; impl Linker { pub fn new(config: Config) -> Self { Self { config, - next_object_id: root_id, + next_object_id: ROOT_ID, next_tls_module_id: 1, tls_size: 0, objects: BTreeMap::new(), @@ -141,72 +322,163 @@ impl Linker { } pub fn load_program(&mut self, path: &str, base_addr: Option) -> Result { - self.load_object(path, &None, base_addr, false)?; - return Ok(self.objects.get(&root_id).unwrap().entry_point); + let dso = self.load_object( + path, + &None, + base_addr, + false, + if self.config.bind_now { + Resolve::Now + } else { + Resolve::default() + }, + ObjectScope::Global, + )?; + Ok(dso.entry_point) } - pub fn load_library(&mut self, name: Option<&str>) -> Result { + pub fn load_library( + &mut self, + name: Option<&str>, + resolve: Resolve, + scope: ObjectScope, + noload: bool, + ) -> Result { + trace!( + "[ld.so] dlopen({:?}, {:#?}, {:#?}, noload={})", + name, + resolve, + scope, + noload + ); + + if noload && resolve == Resolve::Now { + // Do not perform lazy binding anymore. + // * Check if loaded with Resolve::Now and if so, early return. + // * If not, resolve all symbols now. + todo!("resolve symbols now!"); + } + match name { Some(name) => { if let Some(id) = self.name_to_object_id_map.get(name) { let obj = self.objects.get_mut(id).unwrap(); - obj.use_count += 1; - return Ok(*id); - } else { + + // We may be upgrading the object from a local scope to the + // global scope. + if scope == ObjectScope::Global { + if self.config.debug_flags.contains(DebugFlags::SCOPES) { + eprintln!("[ld.so]: moving {} into the global scope", obj.name); + } + + let mut global_scope = GLOBAL_SCOPE.lock(); + obj.scope.move_into(&mut global_scope); + } + + Ok(ObjectHandle::new(obj.clone())) + } else if !noload { let parent_runpath = &self .objects - .get(&root_id) + .get(&ROOT_ID) .and_then(|parent| parent.runpath.clone()); - let lib_id = self.next_object_id; - self.load_object(name, parent_runpath, None, true)?; - return Ok(lib_id); - } - } - None => return Ok(root_id), - } - } - - pub fn get_sym(&self, lib_id: usize, name: &str) -> Option<*mut c_void> { - match self.objects.get(&lib_id) { - Some(obj) => { - return obj.get_sym(name).map(|(s, strong)| { - if s.sym_type != STT_TLS { - s.as_ptr() - } else { - unsafe { - let mut tls_index = dl_tls_index { - ti_module: obj.tls_module_id as u64, - ti_offset: s.value as u64, - }; - __tls_get_addr(&mut tls_index) - } - } - }); - } - _ => { - return None; - } - } - } - - pub fn unload(&mut self, lib_id: usize) { - if let Some(obj) = self.objects.get_mut(&lib_id) { - if obj.dlopened { - if obj.use_count == 1 { - let obj = self.objects.remove(&lib_id).unwrap(); - for dep in obj.dependencies.iter() { - self.unload(*self.name_to_object_id_map.get(dep).unwrap()); - } - self.name_to_object_id_map.remove(&obj.name); - drop(obj); + Ok(ObjectHandle::new(self.load_object( + name, + parent_runpath, + None, + true, + if self.config.bind_now { + Resolve::Now + } else { + resolve + }, + scope, + )?)) } else { - obj.use_count -= 1; + // FIXME: LoadError? + // Err(Error::Malformed(format!( + // "object '{}' has not yet been loaded", + // name + // ))) + Ok(ObjectHandle(ptr::null())) } } + + None => Ok(ObjectHandle::new( + self.objects + .get(&ROOT_ID) + .expect("root object missing") + .clone(), + )), } } + pub fn get_sym(&self, handle: Option, name: &str) -> Option<*mut c_void> { + let guard; + + if let Some(handle) = handle.as_ref() { + &handle.as_ref().scope + } else { + guard = GLOBAL_SCOPE.lock(); + &*guard + } + .get_sym(name) + .map(|(symbol, _, obj)| { + if symbol.sym_type != STT_TLS { + symbol.as_ptr() + } else { + let mut tls_index = dl_tls_index { + ti_module: obj.tls_module_id as u64, + ti_offset: symbol.value as u64, + }; + + unsafe { __tls_get_addr(&mut tls_index) } + } + }) + } + + pub fn unload(&mut self, handle: ObjectHandle) { + let obj = handle.into_inner(); + if !obj.dlopened { + return; + } + + trace!( + "[ld.so] unloading {} (sc={}, wc={})", + obj.name, + Arc::strong_count(&obj), + Arc::weak_count(&obj) + ); + + // One for the reference we have and the other for the one in the + // objects map. + if Arc::strong_count(&obj) == 2 { + // Remove from the global scope. + match *GLOBAL_SCOPE.lock() { + Scope::Global { ref mut objs } => { + objs.retain(|o| !Weak::ptr_eq(o, &Arc::downgrade(&obj))); + } + + _ => unreachable!(), + } + + let _ = self.objects.remove(&obj.id).unwrap(); + for dep in obj.dependencies.iter() { + self.unload(ObjectHandle::new( + self.objects + .get(self.name_to_object_id_map.get(dep).unwrap()) + .unwrap() + .clone(), + )); + } + self.name_to_object_id_map.remove(&obj.name); + assert!(Arc::strong_count(&obj) == 1); + drop(obj); + } + + // obj is dropped here. + } + pub fn fini(&self) { for obj in self.objects.values() { obj.run_fini(); @@ -219,14 +491,23 @@ impl Linker { runpath: &Option, base_addr: Option, dlopened: bool, - ) -> Result<()> { + resolve: Resolve, + scope: ObjectScope, + ) -> Result> { + let resolve = if cfg!(target_arch = "x86_64") { + resolve + } else { + // Lazy binding is not currently supported on non-x86_64 architectures. + Resolve::Now + }; + unsafe { _r_debug.state = RTLDState::RT_ADD }; _dl_debug_state(); let mut new_objects = Vec::new(); let mut objects_data = Vec::new(); let mut tcb_masters = Vec::new(); - self.load_objects_recursive( + let loaded_dso = self.load_objects_recursive( path, runpath, base_addr, @@ -234,6 +515,8 @@ impl Linker { &mut new_objects, &mut objects_data, &mut tcb_masters, + None, + scope, )?; unsafe { @@ -304,7 +587,7 @@ impl Linker { if cfg!(any(target_arch = "x86", target_arch = "x86_64")) { // Below the TP - tcb.dtv_mut().unwrap()[dtv_idx] = tcb_ptr.cast::().sub(obj.tls_offset); + tcb.dtv_mut()[dtv_idx] = tcb_ptr.cast::().sub(obj.tls_offset); } else { // FIMXE(andypython): Make it above the TP // @@ -312,8 +595,7 @@ impl Linker { // tcb_ptr.add(1).cast::().add(obj.tls_offset); // // FIXME(andypython): https://gitlab.redox-os.org/redox-os/relibc/-/merge_requests/570#note_35788 - tcb.dtv_mut().unwrap()[dtv_idx] = - tcb.tls_end.sub(tcb.tls_len).add(obj.tls_offset); + tcb.dtv_mut()[dtv_idx] = tcb.tls_end.sub(tcb.tls_len).add(obj.tls_offset); } } @@ -335,18 +617,22 @@ impl Linker { } } - self.relocate(&new_objects, &objects_data)?; - self.run_init(&new_objects); - - for obj in new_objects.into_iter() { - self.name_to_object_id_map.insert(obj.name.clone(), obj.id); - self.objects.insert(obj.id, obj); + // new_objects is already reversed. + for (i, obj) in new_objects.into_iter().enumerate() { + self.relocate(&obj, &objects_data[i], resolve)?; + self.run_init(&obj); + self.register_object(obj); } unsafe { _r_debug.state = RTLDState::RT_CONSISTENT }; _dl_debug_state(); - return Ok(()); + Ok(loaded_dso) + } + + fn register_object(&mut self, obj: Arc) { + self.name_to_object_id_map.insert(obj.name.clone(), obj.id); + self.objects.insert(obj.id, obj); } fn load_objects_recursive( @@ -355,24 +641,25 @@ impl Linker { parent_runpath: &Option, base_addr: Option, dlopened: bool, - new_objects: &mut Vec, + new_objects: &mut Vec>, objects_data: &mut Vec>, tcb_masters: &mut Vec, - ) -> Result<()> { - if let Some(obj) = { - if let Some(id) = self.name_to_object_id_map.get(name) { - self.objects.get_mut(id) - } else { - new_objects.iter_mut().find(|o| o.name == name) + // The object that caused this object to be loaded. + dependent: Option<&mut DSO>, + scope: ObjectScope, + ) -> Result> { + // fixme: double lookup slow + if let Some(id) = self.name_to_object_id_map.get(name) { + if let Some(obj) = self.objects.get_mut(id) { + return Ok(obj.clone()); } - } { - obj.use_count += 1; - return Ok(()); + } else if let Some(obj) = new_objects.iter_mut().find(|o| o.name == name) { + return Ok(obj.clone()); } let path = self.search_object(name, parent_runpath)?; let data = Linker::read_file(&path)?; - let (obj, tcb_master) = DSO::new( + let (mut obj, tcb_master) = DSO::new( &path, &data, base_addr, @@ -390,9 +677,6 @@ impl Linker { ); } - new_objects.push(obj); - objects_data.push(data); - self.next_object_id += 1; if let Some(master) = tcb_master { @@ -404,10 +688,9 @@ impl Linker { self.next_tls_module_id += 1; } - let (runpath, dependencies) = { - let parent = new_objects.last().unwrap(); - (parent.runpath.clone(), parent.dependencies.clone()) - }; + let runpath = obj.runpath.clone(); + let dependencies = obj.dependencies.clone(); + for dep_name in dependencies.iter() { self.load_objects_recursive( dep_name, @@ -417,10 +700,29 @@ impl Linker { new_objects, objects_data, tcb_masters, + Some(&mut obj), + scope, )?; } - return Ok(()); + let obj = Arc::new_cyclic(|sref| { + obj.scope.set_owner(sref.clone()); + obj + }); + + if let Some(dependent) = dependent { + match scope { + ObjectScope::Local => dependent.scope.add(&obj), + ObjectScope::Global => GLOBAL_SCOPE.lock().add(&obj), + } + } else if let ObjectScope::Global = scope { + GLOBAL_SCOPE.lock().add(&obj); + } + + objects_data.push(data); + new_objects.push(obj.clone()); + + Ok(obj) } fn search_object(&self, name: &str, parent_runpath: &Option) -> Result { @@ -471,205 +773,420 @@ impl Linker { file.read_to_end(&mut data) .map_err(|err| Error::Malformed(format!("failed to read '{}': {}", path, err)))?; - return Ok(data); + Ok(data) } - fn relocate(&self, new_objects: &Vec, objects_data: &Vec>) -> Result<()> { - let symbols_lookup_objects: Vec<&DSO> = - self.objects.values().chain(new_objects.iter()).collect(); + /// Perform lazy relocations. + fn lazy_relocate(&self, obj: &Arc, elf: &Elf, resolve: Resolve) -> Result<()> { + let Some(got) = obj.got() else { return Ok(()) }; + let object_base_addr = obj.mmap.as_ptr() as u64; - // Perform relocations - for i in (0..new_objects.len()).rev() { - let elf = Elf::parse(&objects_data[i])?; - let obj = &new_objects[i]; + unsafe { + got.add(1).write(Arc::as_ptr(obj) as usize); + got.add(2).write(__plt_resolve_trampoline as usize); + } - trace!("link {}", obj.name); + for rel in elf.pltrelocs.iter() { + let ptr = if is_pie_enabled(elf) { + (object_base_addr + rel.r_offset) as *mut u64 + } else { + rel.r_offset as *mut u64 + }; - let mmap = &obj.mmap; - let b = mmap.as_ptr() as usize; + match (rel.r_type, resolve) { + (reloc::R_X86_64_JUMP_SLOT, Resolve::Lazy) if is_pie_enabled(elf) => unsafe { + *ptr += object_base_addr; + }, - // Relocate - for rel in elf - .dynrelas - .iter() - .chain(elf.dynrels.iter()) - .chain(elf.pltrelocs.iter()) - { - trace!( - " rel {}: {:x?}", - reloc::r_to_str(rel.r_type, elf.header.e_machine), - rel - ); - let (symbol, t) = if rel.r_sym > 0 { + (reloc::R_X86_64_JUMP_SLOT, Resolve::Lazy) => { + // NOP. + } + + (reloc::R_X86_64_JUMP_SLOT, Resolve::Now) => { let sym = elf.dynsyms.get(rel.r_sym).ok_or(Error::Malformed(format!( "missing symbol for relocation {:?}", rel )))?; - let mut t = 0; let name = elf.dynstrtab - .get(sym.st_name) + .get_at(sym.st_name) .ok_or(Error::Malformed(format!( "missing name for symbol {:?}", sym - )))??; - let mut symbol = None; - let mut found = false; - let lookup_start = match rel.r_type { - reloc::R_X86_64_COPY => 1, - _ => 0, - }; - for lookup_id in lookup_start..symbols_lookup_objects.len() { - let lookup_obj = &symbols_lookup_objects[lookup_id]; - if let Some((s, strong)) = lookup_obj.get_sym(name) { - trace!( - "symbol {} from {} found in {} ({})", - name, - obj.name, - lookup_obj.name, - if strong { "strong" } else { "weak" } - ); - symbol = Some(s); - t = lookup_obj.tls_offset; - found = true; - // Stop looking if any strong symbol is found - if strong { - break; - } - } - } - // TODO: below doesn't work because of missing __preinit_array_{start,end} and __init_array_{start,end} symbols in dynamic linked programs - /* - if !found { - return Err(Error::Malformed(format!("missing symbol for name {}", name))); - } - */ - (symbol, t) - } else { - (None, 0) - }; + )))?; - let s = symbol - .as_ref() - .map(|sym| sym.as_ptr() as usize) - .unwrap_or(0); + let resolved = GLOBAL_SCOPE + .lock() + .get_sym(name) + .or_else(|| obj.scope.get_sym(name)) + .map(|(sym, _, _)| sym.as_ptr()) + .expect("unresolved symbol"); - let a = rel.r_addend.unwrap_or(0) as usize; + let addend = rel.r_addend.unwrap_or(0) as u64; - let ptr = if is_pie_enabled(&elf) { - (b + rel.r_offset as usize) as *mut u8 - } else { - rel.r_offset as *mut u8 - }; - let set_u64 = |value| { - trace!(" set_u64 {:#x}", value); unsafe { - *(ptr as *mut u64) = value; + *ptr = resolved as u64 + addend; } - }; + } - match rel.r_type { - reloc::R_X86_64_64 => { - set_u64((s + a) as u64); - } - reloc::R_X86_64_DTPMOD64 => { - set_u64(obj.tls_module_id as u64); - } - reloc::R_X86_64_DTPOFF64 => { - if s != 0 { - set_u64((s - b) as u64); - } else { - set_u64(s as u64); - } - } - reloc::R_X86_64_GLOB_DAT | reloc::R_X86_64_JUMP_SLOT => { + _ => todo!("unsupported relocation type {:?}", rel.r_type), + } + } + + Ok(()) + } + + fn relocate(&self, obj: &Arc, object_data: &[u8], resolve: Resolve) -> Result<()> { + // Perform static relocations. + let elf = Elf::parse(object_data)?; + + trace!("link {}", obj.name); + + let mmap = &obj.mmap; + let b = mmap.as_ptr() as usize; + + // Relocate + for rel in elf.dynrelas.iter().chain(elf.dynrels.iter()) { + trace!( + " rel {}: {:x?}", + reloc::r_to_str(rel.r_type, elf.header.e_machine), + rel + ); + + let symbol = if rel.r_sym > 0 { + let sym = elf.dynsyms.get(rel.r_sym).ok_or(Error::Malformed(format!( + "missing symbol for relocation {:?}", + rel + )))?; + + let name = elf + .dynstrtab + .get_at(sym.st_name) + .ok_or(Error::Malformed(format!( + "missing name for symbol {:?}", + sym + )))?; + + let symbol = GLOBAL_SCOPE + .lock() + .get_sym(name) + .or_else(|| obj.scope.get_sym(name)) + .map(|(sym, _, obj)| (sym, obj.tls_offset)); + + // TODO: below doesn't work because of missing __preinit_array_{start,end} and __init_array_{start,end} symbols in dynamic linked programs + // TODO: found := symbol.is_some() + /* + if !found { + return Err(Error::Malformed(format!("missing symbol for name {}", name))); + } + */ + symbol + } else { + None + }; + + let (s, t) = symbol + .as_ref() + .map(|(sym, t)| (sym.as_ptr() as usize, *t)) + .unwrap_or((0, 0)); + + let a = rel.r_addend.unwrap_or(0) as usize; + + let ptr = if is_pie_enabled(&elf) { + (b + rel.r_offset as usize) as *mut u8 + } else { + rel.r_offset as *mut u8 + }; + let set_u64 = |value| { + trace!( + " set_u64 r_type={} value={:#x} ptr={:p} base={:#x} mmap_len={:#x} a={:#x}", + rel.r_type, + value, + ptr, + b, + mmap.len(), + a + ); + unsafe { + *(ptr as *mut u64) = value; + } + }; + + match rel.r_type { + reloc::R_X86_64_64 => { + set_u64((s + a) as u64); + } + reloc::R_X86_64_DTPMOD64 => { + set_u64(obj.tls_module_id as u64); + } + reloc::R_X86_64_DTPOFF64 => { + if s != 0 { + set_u64((s - b) as u64); + } else { set_u64(s as u64); } - reloc::R_X86_64_RELATIVE => { - set_u64((b + a) as u64); - } - reloc::R_X86_64_TPOFF64 => { - if rel.r_sym > 0 { - let sym = symbol - .as_ref() - .expect("R_X86_64_TPOFF64 called without valid symbol"); - set_u64((sym.value + a).wrapping_sub(t) as u64); - } else { - set_u64(a.wrapping_sub(t) as u64); - } - } - reloc::R_X86_64_IRELATIVE => unsafe { - let f: unsafe extern "C" fn() -> u64 = transmute(b + a); - set_u64(f()); - }, - reloc::R_X86_64_COPY => unsafe { - let sym = symbol + } + reloc::R_X86_64_GLOB_DAT | reloc::R_X86_64_JUMP_SLOT => { + set_u64(s as u64); + } + reloc::R_X86_64_RELATIVE => { + set_u64((b + a) as u64); + } + reloc::R_X86_64_TPOFF64 => { + if rel.r_sym > 0 { + let (sym, _) = symbol .as_ref() - .expect("R_X86_64_COPY called without valid symbol"); - ptr::copy_nonoverlapping(sym.as_ptr() as *const u8, ptr, sym.size as usize); - }, - _ => { - panic!( - " {} unsupported", - reloc::r_to_str(rel.r_type, elf.header.e_machine) - ); + .expect("R_X86_64_TPOFF64 called without valid symbol"); + set_u64((sym.value + a).wrapping_sub(t) as u64); + } else { + set_u64(a.wrapping_sub(t) as u64); } } - } - - // Protect pages - for ph in elf - .program_headers - .iter() - .filter(|ph| ph.p_type == program_header::PT_LOAD) - { - let voff = ph.p_vaddr % ph.p_align; - let vaddr = (ph.p_vaddr - voff) as usize; - let vsize = ((ph.p_memsz + voff) as usize).next_multiple_of(ph.p_align as usize); - let mut prot = 0; - if ph.p_flags & program_header::PF_R == program_header::PF_R { - prot |= sys_mman::PROT_READ; + reloc::R_X86_64_IRELATIVE => unsafe { + let f: unsafe extern "C" fn() -> u64 = transmute(b + a); + set_u64(f()); + }, + reloc::R_X86_64_COPY => unsafe { + let (sym, _) = symbol + .as_ref() + .expect("R_X86_64_COPY called without valid symbol"); + ptr::copy_nonoverlapping(sym.as_ptr() as *const u8, ptr, sym.size); + }, + _ => { + panic!( + " {} unsupported", + reloc::r_to_str(rel.r_type, elf.header.e_machine) + ); } - - // W ^ X. If it is executable, do not allow it to be writable, even if requested - if ph.p_flags & program_header::PF_X == program_header::PF_X { - prot |= sys_mman::PROT_EXEC; - } else if ph.p_flags & program_header::PF_W == program_header::PF_W { - prot |= sys_mman::PROT_WRITE; - } - - let res = unsafe { - let ptr = if is_pie_enabled(&elf) { - mmap.as_ptr().add(vaddr) - } else { - vaddr as *const u8 - }; - trace!(" prot {:#x}, {:#x}: {:p}, {:#x}", vaddr, vsize, ptr, prot); - Sys::mprotect(ptr as *mut c_void, vsize, prot).map_err(|_| { - Error::Malformed(format!("failed to mprotect {}", obj.name)) - })?; - }; } } - return Ok(()); + self.lazy_relocate(obj, &elf, resolve)?; + + // Protect pages + for ph in elf + .program_headers + .iter() + .filter(|ph| ph.p_type == program_header::PT_LOAD) + { + let voff = ph.p_vaddr % ph.p_align; + let vaddr = (ph.p_vaddr - voff) as usize; + let vsize = ((ph.p_memsz + voff) as usize).next_multiple_of(ph.p_align as usize); + let mut prot = 0; + if ph.p_flags & program_header::PF_R == program_header::PF_R { + prot |= sys_mman::PROT_READ; + } + + // W ^ X. If it is executable, do not allow it to be writable, even if requested + if ph.p_flags & program_header::PF_X == program_header::PF_X { + prot |= sys_mman::PROT_EXEC; + } else if ph.p_flags & program_header::PF_W == program_header::PF_W { + prot |= sys_mman::PROT_WRITE; + } + + unsafe { + let ptr = if is_pie_enabled(&elf) { + mmap.as_ptr().add(vaddr) + } else { + vaddr as *const u8 + }; + trace!(" prot {:#x}, {:#x}: {:p}, {:#x}", vaddr, vsize, ptr, prot); + Sys::mprotect(ptr as *mut c_void, vsize, prot) + .map_err(|_| Error::Malformed(format!("failed to mprotect {}", obj.name)))?; + } + } + + Ok(()) } - fn run_init(&self, objects: &Vec) { + fn run_init(&self, obj: &DSO) { use crate::platform::{self, types::*}; - for obj in objects.iter().rev() { - if let Some((symbol, true)) = obj.get_sym("__relibc_init_environ") { - unsafe { - symbol - .as_ptr() - .cast::<*mut *mut c_char>() - .write(platform::environ); - } + if let Some((symbol, SymbolBinding::Global)) = obj.get_sym("__relibc_init_environ") { + unsafe { + symbol + .as_ptr() + .cast::<*mut *mut c_char>() + .write(platform::environ); } - - obj.run_init(); } + + obj.run_init(); } } + +// GOT[1] = object_id +// GOT[2] = __plt_resolve_trampoline +// +// The stubs in .plt will push the relocation index and the object pointer onto +// the stack and jump to [`__plt_resolve_trampoline`]. The trampoline will then +// call this function to resolve the symbol and update the respective GOT entry. +// The trampoline will then jump to the resolved symbol. +// +// FIXME(andypython): 32-bit +extern "C" fn __plt_resolve_inner(obj: *const DSO, relocation_index: c_uint) -> *mut c_void { + let resolve_sym = |name: &str, scopes: &[&Scope]| -> Option { + scopes + .iter() + .find_map(|scope| scope.get_sym(name)) + .map(|(sym, _, _)| sym) + }; + + let obj = unsafe { &*obj }; + let obj_base = obj.mmap.as_ptr() as usize; + let dynamic = obj.dynamic.as_ref().unwrap(); + let jmprel = dynamic.info.jmprel as usize; + + let rela = unsafe { + &*((obj_base + jmprel) as *const reloc::reloc64::Rela).add(relocation_index as usize) + }; + + assert_eq!( + reloc::reloc64::r_type(rela.r_info), + reloc::R_X86_64_JUMP_SLOT + ); + + let sym = obj + .dynsyms + .get(reloc::reloc64::r_sym(rela.r_info) as usize) + .expect("symbol not found"); + assert_ne!(sym.st_name, 0); + + let strtab_offset = dynamic + .dyns + .iter() + .find(|r#dyn| r#dyn.d_tag == DT_STRTAB) + .unwrap() + .d_val; + + let name = unsafe { + CStr::from_ptr( + (strtab_offset + sym.st_name as u64 + if obj.pie { obj_base as u64 } else { 0u64 }) + as *const c_char, + ) + }; + + let resolved = resolve_sym(name.to_str().unwrap(), &[&GLOBAL_SCOPE.lock(), &obj.scope]) + .expect(&format!("symbol '{}' not found", name.to_str().unwrap())) + .as_ptr(); + + trace!( + "@plt: {} -> *mut {:#x}", + name.to_string_lossy(), + resolved as usize + ); + + let ptr = if obj.pie { + (obj_base as u64 + rela.r_offset) as *mut u64 + } else { + rela.r_offset as *mut u64 + }; + + unsafe { *ptr = resolved as u64 } + resolved +} + +extern "C" { + fn __plt_resolve_trampoline() -> usize; +} + +#[cfg(target_arch = "x86_64")] +core::arch::global_asm!( + " +.global __plt_resolve_trampoline +.hidden __plt_resolve_trampoline +__plt_resolve_trampoline: + push rsi + push rdi + + mov rdi, qword ptr [rsp + 0x10] + mov rsi, qword ptr [rsp + 0x18] + + // stash the floating point argument registers + sub rsp, 128 + movdqu [rsp + 0x00], xmm0 + movdqu [rsp + 0x10], xmm1 + movdqu [rsp + 0x20], xmm2 + movdqu [rsp + 0x30], xmm3 + movdqu [rsp + 0x40], xmm4 + movdqu [rsp + 0x50], xmm5 + movdqu [rsp + 0x60], xmm6 + movdqu [rsp + 0x70], xmm7 + + push rax + push rcx + push rdx + push r8 + push r9 + push r10 + + push rbp + mov rbp, rsp + and rsp, 0xfffffffffffffff0 + call {__plt_resolve_inner} + mov r11, rax + mov rsp, rbp + pop rbp + + pop r10 + pop r9 + pop r8 + pop rdx + pop rcx + pop rax + + movdqu xmm7, [rsp + 0x70] + movdqu xmm6, [rsp + 0x60] + movdqu xmm5, [rsp + 0x50] + movdqu xmm4, [rsp + 0x40] + movdqu xmm3, [rsp + 0x30] + movdqu xmm2, [rsp + 0x20] + movdqu xmm1, [rsp + 0x10] + movdqu xmm0, [rsp + 0x00] + add rsp, 128 + + pop rdi + pop rsi + + add rsp, 0x10 + jmp r11 + + ud2 +.size __plt_resolve_trampoline, . - __plt_resolve_trampoline +", + __plt_resolve_inner = sym __plt_resolve_inner +); + +#[cfg(target_arch = "x86")] +core::arch::global_asm!( + " +.global __plt_resolve_trampoline +.hidden __plt_resolve_trampoline +__plt_resolve_trampoline: + ud2 +.size __plt_resolve_trampoline, . - __plt_resolve_trampoline + " +); + +#[cfg(target_arch = "aarch64")] +core::arch::global_asm!( + " +.global __plt_resolve_trampoline +.hidden __plt_resolve_trampoline +__plt_resolve_trampoline: + udf #0 +.size __plt_resolve_trampoline, . - __plt_resolve_trampoline + " +); + +#[cfg(target_arch = "riscv64")] +core::arch::global_asm!( + " +.global __plt_resolve_trampoline +.hidden __plt_resolve_trampoline +__plt_resolve_trampoline: + unimp +.size __plt_resolve_trampoline, . - __plt_resolve_trampoline + " +); diff --git a/src/ld_so/mod.rs b/src/ld_so/mod.rs index 2806f09f3f..73eca11249 100644 --- a/src/ld_so/mod.rs +++ b/src/ld_so/mod.rs @@ -1,5 +1,9 @@ //! Dynamic loading and linking. +// FIXME(andypython): remove this when #![allow(warnings, unused_variables)] is +// dropped from src/lib.rs. +#![warn(warnings, unused_variables)] + use core::{mem, ptr}; use goblin::elf::program_header::{self, program_header32, program_header64, ProgramHeader}; @@ -73,7 +77,7 @@ pub fn static_init(sp: &'static Stack) { let page_size = Sys::getpagesize(); let voff = ph.p_vaddr as usize % page_size; - let vaddr = ph.p_vaddr as usize - voff; + // let vaddr = ph.p_vaddr as usize - voff; let vsize = ((ph.p_memsz as usize + voff + page_size - 1) / page_size) * page_size; match ph.p_type { @@ -170,7 +174,7 @@ pub unsafe fn init(sp: &'static Stack) { pub unsafe fn fini() { if let Some(tcb) = Tcb::current() { - if tcb.linker_ptr != ptr::null_mut() { + if !tcb.linker_ptr.is_null() { let linker = (&*tcb.linker_ptr).lock(); linker.fini(); } diff --git a/src/ld_so/start.rs b/src/ld_so/start.rs index b3b1d181ee..661e4273dd 100644 --- a/src/ld_so/start.rs +++ b/src/ld_so/start.rs @@ -12,7 +12,6 @@ use generic_rt::ExpectTlsFree; use crate::{ c_str::CStr, header::unistd, - ld_so::tcb::Master, platform::{get_auxv, get_auxvs, types::c_char}, start::Stack, sync::mutex::Mutex, @@ -39,7 +38,6 @@ unsafe fn get_argv(mut ptr: *const usize) -> (Vec, *const usize) { _ => { eprintln!("ld.so: failed to parse argv[{}]", argv.len()); unistd::_exit(1); - loop {} } } ptr = ptr.add(1); @@ -88,16 +86,6 @@ unsafe fn adjust_stack(sp: &'static mut Stack) { if arg == 0 { break; } - if let Ok(arg_str) = CStr::from_ptr(arg as *const c_char).to_str() { - let mut parts = arg_str.splitn(2, '='); - if let Some(key) = parts.next() { - if let Some(value) = parts.next() { - if let "LD_LIBRARY_PATH" = key { - //library_path = value - } - } - } - } } // Move auxiliary vectors @@ -152,12 +140,7 @@ fn resolve_path_name( // TODO: Make unsafe #[no_mangle] -pub extern "C" fn relibc_ld_so_start( - sp: &'static mut Stack, - ld_entry: usize, - self_tls_start: usize, - self_tls_end: usize, -) -> usize { +pub extern "C" fn relibc_ld_so_start(sp: &'static mut Stack, ld_entry: usize) -> usize { // Setup TCB for ourselves. unsafe { let tcb = Tcb::new(0).expect_notls("ld.so: failed to allocate bootstrap TCB"); @@ -212,9 +195,6 @@ pub extern "C" fn relibc_ld_so_start( crate::platform::init(auxv.clone()); } - // Some variables that will be overridden by environment and auxiliary vectors - let ld_library_path = envs.get("LD_LIBRARY_PATH").map(|s| s.to_owned()); - let name_or_path = if is_manual { // ld.so is run directly by user and not via execve() or similar systemcall println!("argv: {:#?}", argv); @@ -224,7 +204,6 @@ pub extern "C" fn relibc_ld_so_start( if sp.argc < 2 { eprintln!("ld.so [executable] [arguments...]"); unistd::_exit(1); - loop {} } unsafe { adjust_stack(sp) }; argv[1].to_string() @@ -232,12 +211,11 @@ pub extern "C" fn relibc_ld_so_start( argv[0].to_string() }; - let (path, name) = match resolve_path_name(&name_or_path, &envs) { + 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); - loop {} } }; @@ -260,7 +238,6 @@ pub extern "C" fn relibc_ld_so_start( Err(err) => { eprintln!("ld.so: failed to link '{}': {}", path, err); unistd::_exit(1); - loop {} } }; if let Some(tcb) = unsafe { Tcb::current() } { diff --git a/src/ld_so/tcb.rs b/src/ld_so/tcb.rs index 8521842f41..48fd756a0e 100644 --- a/src/ld_so/tcb.rs +++ b/src/ld_so/tcb.rs @@ -1,6 +1,5 @@ use alloc::vec::Vec; use core::{ - arch::asm, cell::UnsafeCell, mem, ops::{Deref, DerefMut}, @@ -10,7 +9,6 @@ use core::{ use generic_rt::GenericTcb; use goblin::error::{Error, Result}; -use super::ExpectTlsFree; use crate::{ header::sys_mman, ld_so::linker::Linker, @@ -80,7 +78,7 @@ impl Tcb { /// `size` is the size of the TLS in bytes. pub unsafe fn new(size: usize) -> Result<&'static mut Self> { let page_size = Sys::getpagesize(); - let (abi_page, tls, tcb_page) = Self::os_new(size.next_multiple_of(page_size))?; + let (_abi_page, tls, tcb_page) = Self::os_new(size.next_multiple_of(page_size))?; let tcb_ptr = tcb_page.as_mut_ptr() as *mut Self; trace!("New TCB: {:p}", tcb_ptr); @@ -197,7 +195,7 @@ impl Tcb { // XXX: [`Vec::from_raw_parts`] cannot be used here as the masters were originally // allocated by the ld.so allocator and that would violate that function's invariants. let mut masters = self.masters().unwrap().to_vec(); - masters.extend(new_masters.into_iter()); + masters.extend(new_masters); self.masters_ptr = masters.as_mut_ptr(); self.masters_len = masters.len() * mem::size_of::(); @@ -212,8 +210,8 @@ impl Tcb { pub fn setup_dtv(&mut self, n: usize) { if self.dtv_ptr.is_null() { - let mut dtv = vec![ptr::null_mut(); n]; - let (ptr, len, cap) = dtv.into_raw_parts(); + let dtv = vec![ptr::null_mut(); n]; + let (ptr, len, _) = dtv.into_raw_parts(); self.dtv_ptr = ptr; self.dtv_len = len; @@ -222,7 +220,7 @@ impl Tcb { // // XXX: [`Vec::from_raw_parts`] cannot be used here as the DTV was originally allocated // by the ld.so allocator and that would violate that function's invariants. - let mut dtv = self.dtv_mut().unwrap().to_vec(); + let mut dtv = self.dtv_mut().to_vec(); dtv.resize(n, ptr::null_mut()); let (ptr, len, _) = dtv.into_raw_parts(); @@ -231,11 +229,11 @@ impl Tcb { } } - pub fn dtv_mut(&mut self) -> Option<&'static mut [*mut u8]> { + pub fn dtv_mut(&mut self) -> &'static mut [*mut u8] { if self.dtv_len != 0 { - Some(unsafe { slice::from_raw_parts_mut(self.dtv_ptr, self.dtv_len) }) + unsafe { slice::from_raw_parts_mut(self.dtv_ptr, self.dtv_len) } } else { - None + &mut [] } } @@ -269,7 +267,7 @@ impl Tcb { /// OS and architecture specific code to activate TLS - Linux x86_64 #[cfg(all(target_os = "linux", target_arch = "x86_64"))] - unsafe fn os_arch_activate(os: &(), tls_end: usize, _tls_len: usize) { + unsafe fn os_arch_activate(_os: &(), tls_end: usize, _tls_len: usize) { const ARCH_SET_FS: usize = 0x1002; syscall!(ARCH_PRCTL, ARCH_SET_FS, tls_end); } diff --git a/src/platform/mod.rs b/src/platform/mod.rs index 92e3e042ec..9879ad4af0 100644 --- a/src/platform/mod.rs +++ b/src/platform/mod.rs @@ -348,4 +348,4 @@ pub unsafe fn init(auxvs: Box<[[usize; 2]]>) { } } #[cfg(not(target_os = "redox"))] -pub fn init(auxvs: Box<[[usize; 2]]>) {} +pub unsafe fn init(auxvs: Box<[[usize; 2]]>) {} diff --git a/tests/Makefile b/tests/Makefile index 590fdd9eba..784c1682fc 100644 --- a/tests/Makefile +++ b/tests/Makefile @@ -159,7 +159,8 @@ EXPECT_NAMES=\ BUILD?=. DYNAMIC_ONLY_NAMES=\ - dlfcn + dlfcn \ + dlopen_scopes # Binaries that may generate varied output NAMES=\ @@ -323,10 +324,19 @@ $(BUILD)/bins_dynamic/%.so: %.c $(DEPS) mkdir -p "$$(dirname "$@")" $(CC) "$<" -o "$@" -shared -fpic $(FLAGS) $(DYNAMIC_FLAGS) +# foobar depends on foo +$(BUILD)/bins_dynamic/libfoobar.so: libfoobar.c $(BUILD)/bins_dynamic/libfoo.so $(DEPS) + mkdir -p "$$(dirname "$@")" + $(CC) "$<" -o "$@" -shared -fpic $(FLAGS) $(DYNAMIC_FLAGS) -L $(BUILD)/bins_dynamic -lfoo + $(BUILD)/bins_dynamic/dlfcn: dlfcn.c $(BUILD)/bins_dynamic/sharedlib.so $(DEPS) mkdir -p "$$(dirname "$@")" $(CC) "$<" -o "$@" $(FLAGS) $(DYNAMIC_FLAGS) +$(BUILD)/bins_dynamic/dlopen_scopes: dlopen_scopes.c $(BUILD)/bins_dynamic/libfoobar.so $(BUILD)/bins_dynamic/libfoo.so $(DEPS) + mkdir -p "$$(dirname "$@")" + $(CC) "$<" -o "$@" $(FLAGS) $(DYNAMIC_FLAGS) + $(BUILD)/bins_dynamic/%: %.c $(DEPS) mkdir -p "$$(dirname "$@")" $(CC) "$<" -o "$@" $(FLAGS) $(DYNAMIC_FLAGS) diff --git a/tests/dlfcn.c b/tests/dlfcn.c index bf1d7354d4..ed81a7e0e5 100644 --- a/tests/dlfcn.c +++ b/tests/dlfcn.c @@ -1,18 +1,16 @@ +#include +#include #include #include -#include +#define SHARED_LIB "sharedlib.so" -int add(int a, int b) -{ - return a + b; -} +int add(int a, int b) { return a + b; } -void test_dlopen_null() -{ - void* handle = dlopen(NULL, RTLD_LAZY); +void test_dlopen_null() { + void *handle = dlopen(NULL, RTLD_LAZY); if (!handle) { - printf("dlopen(NULL) failed\n"); + printf("dlopen(NULL) failed: %s\n", dlerror()); exit(1); } @@ -20,7 +18,7 @@ void test_dlopen_null() *(void **)(&f) = dlsym(handle, "add"); if (!f) { - printf("dlsym(handle, add) failed\n"); + printf("dlsym(handle, add) failed: %s\n", dlerror()); exit(2); } int a = 22; @@ -29,15 +27,14 @@ void test_dlopen_null() dlclose(handle); } -void test_dlopen_libc() -{ - void* handle = dlopen("libc.so.6", RTLD_LAZY); +void test_dlopen_libc() { + void *handle = dlopen("libc.so.6", RTLD_LAZY); if (!handle) { printf("dlopen(libc.so.6) failed\n"); exit(1); } - - int (*f)(const char*); + + int (*f)(const char *); *(void **)(&f) = dlsym(handle, "puts"); if (!f) { @@ -48,10 +45,8 @@ void test_dlopen_libc() dlclose(handle); } - -void test_dlsym_function() -{ - void* handle = dlopen("sharedlib.so", RTLD_LAZY); +void test_dlsym_function() { + void *handle = dlopen(SHARED_LIB, RTLD_LAZY); if (!handle) { printf("dlopen(sharedlib.so) failed\n"); exit(1); @@ -68,14 +63,13 @@ void test_dlsym_function() dlclose(handle); } -void test_dlsym_global_var() -{ - void* handle = dlopen("sharedlib.so", RTLD_LAZY); +void test_dlsym_global_var() { + void *handle = dlopen(SHARED_LIB, RTLD_LAZY); if (!handle) { printf("dlopen(sharedlib.so) failed\n"); exit(1); } - int* global_var = dlsym(handle, "global_var"); + int *global_var = dlsym(handle, "global_var"); if (!global_var) { printf("dlsym(handle, global_var) failed\n"); exit(2); @@ -84,14 +78,13 @@ void test_dlsym_global_var() dlclose(handle); } -void test_dlsym_tls_var() -{ - void* handle = dlopen("sharedlib.so", RTLD_LAZY); +void test_dlsym_tls_var() { + void *handle = dlopen(SHARED_LIB, RTLD_LAZY); if (!handle) { printf("dlopen(sharedlib.so) failed\n"); exit(1); } - int* tls_var = dlsym(handle, "tls_var"); + int *tls_var = dlsym(handle, "tls_var"); if (!tls_var) { printf("dlsym(handle, tls_var) failed\n"); exit(2); @@ -100,12 +93,30 @@ void test_dlsym_tls_var() dlclose(handle); } -int main() -{ +void test_dlunload(void) { + void *handle = dlopen(SHARED_LIB, RTLD_LAZY | RTLD_LOCAL); + void *handle2 = dlopen(SHARED_LIB, RTLD_LAZY | RTLD_LOCAL); + assert(handle == handle2 && handle); + assert(!dlclose(handle)); + void *handle3 = dlopen(SHARED_LIB, RTLD_LAZY | RTLD_NOLOAD); + assert(handle3 == handle2); + assert(!dlclose(handle3)); + assert(!dlclose(handle2)); + void *handle4 = dlopen(SHARED_LIB, RTLD_LAZY | RTLD_NOLOAD); + assert(handle4 == NULL); + + void *handle5 = dlopen(SHARED_LIB, RTLD_LAZY | RTLD_GLOBAL); + assert(handle5); + assert(!dlclose(handle5)); + void *handle6 = dlopen(SHARED_LIB, RTLD_LAZY | RTLD_NOLOAD); + assert(handle6 == NULL); +} + +int main() { test_dlopen_null(); test_dlopen_libc(); test_dlsym_function(); test_dlsym_global_var(); test_dlsym_tls_var(); + test_dlunload(); } - diff --git a/tests/dlopen_scopes.c b/tests/dlopen_scopes.c new file mode 100644 index 0000000000..3d8de7d74c --- /dev/null +++ b/tests/dlopen_scopes.c @@ -0,0 +1,38 @@ +#include +#include +#include +#include +#include + +int main(void) { + void *handle = dlopen("libfoobar.so", RTLD_LAZY | RTLD_LOCAL); + if (!handle) { + printf("dlopen(libfoobar.so): %s\n", dlerror()); + return EXIT_FAILURE; + } + + assert(dlsym(handle, "BAR") != NULL); + assert(dlsym(handle, "FOO") != NULL); + // not in the global scope + assert(dlsym(RTLD_DEFAULT, "BAR") == NULL); + assert(dlsym(RTLD_DEFAULT, "FOO") == NULL); + + void *self = dlopen(NULL, RTLD_LAZY | RTLD_LOCAL); + if (!self) { + printf("dlopen(NULL): %s\n", dlerror()); + return EXIT_FAILURE; + } + + assert(dlsym(self, "FOO") == NULL); + assert(dlsym(self, "BAR") == NULL); + + assert(dlclose(self) == 0); + + // Promote the library to the global scope. + assert(dlopen("libfoobar.so", /* RTLD_NOLOAD |*/ RTLD_NOW | RTLD_GLOBAL)); + + assert(dlsym(RTLD_DEFAULT, "FOO") != NULL); + assert(dlsym(RTLD_DEFAULT, "BAR") != NULL); + + return EXIT_SUCCESS; +} diff --git a/tests/expected/bins_dynamic/dlopen_scopes.stderr b/tests/expected/bins_dynamic/dlopen_scopes.stderr new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_dynamic/dlopen_scopes.stdout b/tests/expected/bins_dynamic/dlopen_scopes.stdout new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/libfoo.c b/tests/libfoo.c new file mode 100644 index 0000000000..b2106a4ec5 --- /dev/null +++ b/tests/libfoo.c @@ -0,0 +1,3 @@ +char *FOO = "foo"; + +void a() {} diff --git a/tests/libfoobar.c b/tests/libfoobar.c new file mode 100644 index 0000000000..67cef36df6 --- /dev/null +++ b/tests/libfoobar.c @@ -0,0 +1,4 @@ +char *BAR = "bar"; + +void a(); +void b() { a(); }