From e3dc5d0617991833be0d36b65f1c13828f9ee8fd Mon Sep 17 00:00:00 2001 From: Anhad Singh Date: Sat, 18 Jan 2025 21:20:10 +1100 Subject: [PATCH 1/4] chore(dso): remove unused members Signed-off-by: Anhad Singh --- src/ld_so/dso.rs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/ld_so/dso.rs b/src/ld_so/dso.rs index e401cf5d36..98ff082bba 100644 --- a/src/ld_so/dso.rs +++ b/src/ld_so/dso.rs @@ -235,8 +235,6 @@ pub struct DSO { pub entry_point: usize, /// Loaded library in-memory data pub mmap: &'static [u8], - pub global_syms: BTreeMap, - pub weak_syms: BTreeMap, pub tls_module_id: usize, pub tls_offset: usize, @@ -281,8 +279,6 @@ impl DSO { dlopened, entry_point, mmap, - global_syms: BTreeMap::new(), - weak_syms: BTreeMap::new(), tls_module_id: if tcb_master.is_some() { tls_module_id } else { From 4a082206641ef17a6e3c3e433ed4d522419d6372 Mon Sep 17 00:00:00 2001 From: Anhad Singh Date: Sat, 18 Jan 2025 23:51:53 +1100 Subject: [PATCH 2/4] fix(ld.so): copy relocations Signed-off-by: Anhad Singh --- Cargo.lock | 28 ++++++++- Cargo.toml | 1 + src/header/dlfcn/mod.rs | 6 +- src/ld_so/callbacks.rs | 6 +- src/ld_so/dso.rs | 56 ++++++++++++----- src/ld_so/linker.rs | 136 +++++++++++++++++++++++++++------------- 6 files changed, 169 insertions(+), 64 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index cf469f7684..20bcce1f98 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,6 +1,6 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. -version = 3 +version = 4 [[package]] name = "aho-corasick" @@ -243,6 +243,16 @@ dependencies = [ "libc", ] +[[package]] +name = "lock_api" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" +dependencies = [ + "autocfg", + "scopeguard", +] + [[package]] name = "log" version = "0.4.22" @@ -511,6 +521,7 @@ dependencies = [ "scrypt", "sha-crypt", "sha2", + "spin", "unicode-width", ] @@ -529,6 +540,12 @@ version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "010e18bd3bfd1d45a7e666b236c78720df0d9a7698ebaa9c1c559961eb60a38b" +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + [[package]] name = "scroll" version = "0.11.0" @@ -594,6 +611,15 @@ version = "0.3.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d" +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" +dependencies = [ + "lock_api", +] + [[package]] name = "subtle" version = "2.6.1" diff --git a/Cargo.toml b/Cargo.toml index d1f47b558c..7b0bab04c6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -50,6 +50,7 @@ chrono-tz = {version = "0.10", default-features = false} chrono = {version = "0.4", default-features = false, features = ["alloc"]} libm = "0.2" object = { version = "0.36.7", git = "https://gitlab.redox-os.org/andypython/object", default-features = false, features = ["elf", "read_core"] } +spin = "0.9.8" [dependencies.dlmalloc] path = "dlmalloc-rs" diff --git a/src/header/dlfcn/mod.rs b/src/header/dlfcn/mod.rs index 1c23f6ec85..5b49dda958 100644 --- a/src/header/dlfcn/mod.rs +++ b/src/header/dlfcn/mod.rs @@ -13,7 +13,7 @@ use core::{ use crate::{ c_str::CStr, ld_so::{ - linker::{DlError, ObjectHandle, ObjectScope, Resolve}, + linker::{DlError, ObjectHandle, Resolve, ScopeKind}, tcb::Tcb, }, platform::types::*, @@ -67,9 +67,9 @@ pub unsafe extern "C" fn dlopen(cfilename: *const c_char, flags: c_int) -> *mut }; let scope = if flags & RTLD_GLOBAL == RTLD_GLOBAL { - ObjectScope::Global + ScopeKind::Global } else { - ObjectScope::Local + ScopeKind::Local }; let noload = flags & RTLD_NOLOAD == RTLD_NOLOAD; diff --git a/src/ld_so/callbacks.rs b/src/ld_so/callbacks.rs index c853f7ab36..19cdc4c4fb 100644 --- a/src/ld_so/callbacks.rs +++ b/src/ld_so/callbacks.rs @@ -1,11 +1,11 @@ -use super::linker::{Linker, ObjectHandle, ObjectScope, Resolve, Result}; +use super::linker::{Linker, ObjectHandle, Resolve, Result, ScopeKind}; use crate::platform::types::c_void; use alloc::boxed::Box; pub struct LinkerCallbacks { pub unload: Box, pub load_library: - Box, Resolve, ObjectScope, bool) -> Result>, + Box, Resolve, ScopeKind, bool) -> Result>, pub get_sym: Box, &str) -> Option<*mut c_void>>, } @@ -27,7 +27,7 @@ fn load_library( linker: &mut Linker, name: Option<&str>, resolve: Resolve, - scope: ObjectScope, + scope: ScopeKind, noload: bool, ) -> Result { linker.load_library(name, resolve, scope, noload) diff --git a/src/ld_so/dso.rs b/src/ld_so/dso.rs index 98ff082bba..8a949367a3 100644 --- a/src/ld_so/dso.rs +++ b/src/ld_so/dso.rs @@ -21,7 +21,6 @@ use crate::{ platform::{types::c_void, Pal, Sys}, }; use alloc::{ - collections::BTreeMap, string::{String, ToString}, sync::Arc, vec::Vec, @@ -240,7 +239,7 @@ pub struct DSO { pub(super) dynamic: Dynamic<'static>, - pub scope: Scope, + pub scope: spin::Once, /// Position Independent Executable. pub pie: bool, } @@ -288,12 +287,17 @@ impl DSO { pie: is_pie_enabled(&elf), dynamic, - scope: Scope::local(), + scope: spin::Once::new(), }; Ok((dso, tcb_master, elf.elf_program_headers().to_vec())) } + #[inline] + pub fn scope(&self) -> &Scope { + self.scope.get().expect("scope not initialized") + } + /// Global Offset Table #[inline] pub fn got(&self) -> Option> { @@ -310,7 +314,7 @@ impl DSO { &self.dynamic.needed } - pub fn get_sym(&self, name: &str) -> Option<(Symbol, SymbolBinding)> { + pub fn get_sym<'a>(&self, name: &'a str) -> Option<(Symbol<'a>, SymbolBinding)> { let (_, sym) = self.dynamic.hash_table.find( name, None, @@ -325,6 +329,7 @@ impl DSO { Some(( Symbol { + name, base: if self.pie { self.mmap.as_ptr() as usize } else { @@ -654,7 +659,7 @@ impl DSO { let dynstrtab = StringTable::new( &*mmap, strtab_offset as u64, - strtab_offset as u64 + strtab_size as u64, + strtab_offset as u64 + strtab_size, ); let get_str = |entry: &Dyn| { @@ -721,18 +726,27 @@ impl DSO { fn static_relocate(&self, global_scope: &Scope, reloc: Relocation) -> object::Result<()> { let b = self.mmap.as_ptr() as usize; - let sym = if reloc.sym.0 > 0 { + let (sym, my_sym) = if reloc.sym.0 > 0 { let name = self.dynamic.symbol_name(reloc.sym).unwrap(); - resolve_sym(name, &[global_scope, &self.scope]) - .map(|(sym, _, obj)| (sym, obj.tls_offset)) + let lookup_scopes = [global_scope, self.scope()]; + let sym = if reloc.kind == elf::R_X86_64_COPY { + lookup_scopes + .iter() + .find_map(|scope| scope._get_sym(name, 1)) + } else { + resolve_sym(name, &lookup_scopes) + } + .map(|(sym, _, obj)| (sym, obj)); + + (sym, self.dynamic.symbol(reloc.sym)) } else { - None + (None, None) }; let (s, t) = sym .as_ref() - .map(|(sym, t)| (sym.as_ptr() as usize, *t)) + .map(|(sym, obj)| (sym.as_ptr() as usize, obj.tls_offset)) .unwrap_or((0, 0)); let a = reloc.addend; @@ -773,9 +787,18 @@ impl DSO { set_u64(f()); }, elf::R_X86_64_COPY => unsafe { - let (sym, _) = sym + let (sym, obj) = sym .as_ref() .expect("R_X86_64_COPY called without valid symbol"); + let my_sym = my_sym.expect("R_X86_64_COPY called without valid symbol"); + assert!( + sym.size == my_sym.st_size(NativeEndian) as usize, + "R_X86_64_COPY failed: I was trying to use the symbol {} from {} for {} but they had different sizes. Please consider relinking.", + sym.name, + obj.name, + self.name + ); + // SAFETY: Both the source and destination have the same size. ptr::copy_nonoverlapping(sym.as_ptr() as *const u8, ptr, sym.size); }, _ => unimplemented!("relocation type {:#x}", reloc.kind), @@ -830,9 +853,9 @@ impl DSO { (elf::R_X86_64_JUMP_SLOT, Resolve::Now) => { let name = self.dynamic.symbol_name(reloc.sym).unwrap(); - let resolved = resolve_sym(name, &[global_scope, &self.scope]) + let resolved = resolve_sym(name, &[global_scope, self.scope()]) .map(|(sym, _, _)| sym.as_ptr() as usize) - .expect("unresolved symbol"); + .unwrap_or_else(|| panic!("unresolved symbol: {name}")); unsafe { *ptr = resolved + reloc.addend; @@ -858,7 +881,7 @@ impl DSO { if entry & 1 == 0 { // An even entry sets up `addr` for subsequent odd entries. unsafe { - addr = base.add(entry as usize) as *mut usize; + addr = base.add(entry) as *mut usize; *addr += base as usize; addr = addr.add(1); } @@ -946,6 +969,9 @@ fn dirname(path: &str) -> String { parts.join("/") } -pub fn resolve_sym(name: &str, scopes: &[&Scope]) -> Option<(Symbol, SymbolBinding, Arc)> { +pub fn resolve_sym<'a>( + name: &'a str, + scopes: &[&'a Scope], +) -> Option<(Symbol<'a>, SymbolBinding, Arc)> { scopes.iter().find_map(|scope| scope.get_sym(name)) } diff --git a/src/ld_so/linker.rs b/src/ld_so/linker.rs index 391d7b6006..7077bbea63 100644 --- a/src/ld_so/linker.rs +++ b/src/ld_so/linker.rs @@ -121,16 +121,17 @@ impl Drop for MmapFile { } } -#[derive(Clone, Copy, Debug)] -pub struct Symbol { +#[derive(Clone, Debug)] +pub struct Symbol<'a> { + pub name: &'a str, pub value: usize, pub base: usize, pub size: usize, pub sym_type: u8, } -impl Symbol { - pub fn as_ptr(self) -> *mut c_void { +impl Symbol<'_> { + pub fn as_ptr(&self) -> *mut c_void { (self.base + self.value) as *mut c_void } } @@ -147,15 +148,16 @@ pub enum Resolve { } #[derive(Debug, Copy, Clone, PartialEq)] -pub enum ObjectScope { +pub enum ScopeKind { Global, Local, } pub enum Scope { - Global { - objs: Vec>, - }, + /// The global scope initially contains the main program and all of its + /// dependencies. Additional objects will be added to this scope via + /// `dlopen(2)` if the `RTLD_GLOBAL` flag is set. + Global { objs: Vec> }, Local { owner: Option>, objs: Vec>, @@ -169,7 +171,7 @@ impl Scope { } #[inline] - pub(super) const fn local() -> Self { + const fn local() -> Self { Self::Local { owner: None, objs: Vec::new(), @@ -211,7 +213,18 @@ impl Scope { } } - pub(super) fn get_sym(&self, name: &str) -> Option<(Symbol, SymbolBinding, Arc)> { + pub(super) fn get_sym<'a>( + &self, + name: &'a str, + ) -> Option<(Symbol<'a>, SymbolBinding, Arc)> { + self._get_sym(name, 0) + } + + pub(super) fn _get_sym<'a>( + &self, + name: &'a str, + skip: usize, + ) -> Option<(Symbol<'a>, SymbolBinding, Arc)> { let mut res = None; let get_sym = |obj: Arc| { @@ -227,7 +240,7 @@ impl Scope { }; match self { - Self::Global { objs } => objs.iter().map(|o| o.upgrade().unwrap()).find_map(get_sym), + Self::Global { objs } => objs.iter().skip(skip).map(|o| o.upgrade().unwrap()).find_map(get_sym), Self::Local { owner, objs } => { let owner = owner .as_ref() @@ -237,16 +250,17 @@ impl Scope { core::iter::once(owner) .chain(objs.iter().cloned()) + .skip(skip) .find_map(get_sym) } } .or(res) } - fn move_into(&self, other: &mut Self) { - // FIXME: move not copy? as afiak u cannot downgrade from a global to a local scope. + fn copy_into(&self, other: &mut Self) { match (self, other) { (Self::Local { owner, objs }, Self::Global { objs: other_objs }) => { + // FIXME: may have duplicates let owner = owner.as_ref().expect("local scope without owner"); other_objs.push(owner.clone()); other_objs.extend(objs.iter().map(Arc::downgrade)); @@ -255,6 +269,28 @@ impl Scope { _ => unreachable!(), } } + + fn debug(&self) { + match self { + Self::Global { objs } => { + println!( + "[@global] {:?}", + objs.iter() + .map(|x| x.upgrade().unwrap().name.clone()) + .collect::>() + ); + } + + Self::Local { owner, objs } => { + let owner = owner.as_ref().unwrap().upgrade().unwrap(); + println!( + "[{}] {:?}", + owner.name, + objs.iter().map(|x| x.name.clone()).collect::>() + ) + } + } + } } // Used by dlfcn.h @@ -383,7 +419,7 @@ impl Linker { } else { Resolve::default() }, - ObjectScope::Global, + ScopeKind::Global, )?; Ok(dso.entry_point) } @@ -392,7 +428,7 @@ impl Linker { &mut self, name: Option<&str>, resolve: Resolve, - scope: ObjectScope, + scope: ScopeKind, noload: bool, ) -> Result { trace!( @@ -417,13 +453,13 @@ impl Linker { // We may be upgrading the object from a local scope to the // global scope. - if scope == ObjectScope::Global { + if scope == ScopeKind::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.write(); - obj.scope.move_into(&mut global_scope); + obj.scope().copy_into(&mut global_scope); } Ok(ObjectHandle::new(obj.clone())) @@ -466,7 +502,7 @@ impl Linker { let guard; if let Some(handle) = handle.as_ref() { - &handle.as_ref().scope + handle.as_ref().scope() } else { guard = GLOBAL_SCOPE.read(); &guard @@ -541,7 +577,7 @@ impl Linker { base_addr: Option, dlopened: bool, resolve: Resolve, - scope: ObjectScope, + scope: ScopeKind, ) -> Result> { let resolve = if cfg!(target_arch = "x86_64") { resolve @@ -666,9 +702,11 @@ impl Linker { } } - // new_objects is already reversed. - for (i, obj) in new_objects.into_iter().enumerate() { + for (i, obj) in new_objects.iter().enumerate() { obj.relocate(&objects_data[i], resolve).unwrap(); + } + + for obj in new_objects.into_iter() { self.run_init(&obj); self.register_object(obj); } @@ -684,6 +722,19 @@ impl Linker { self.objects.insert(obj.id, obj); } + /// Loads the specified object and all of its dependencies. + /// + /// `new_objects` contains any new objects that were loaded. Order is + /// reverse of how the scope is populated. + /// + /// The scope is populated such that the loaded objects are in breadth-first + /// order. This means that first the requested object is added to the scope, + /// and then its dependencies are added in the order of their respective + /// `DT_NEEDED` entries in the requested object. This is done recursively + /// until all dependencies have been loaded. + /// + /// If a dependency has already been loaded, it is *not* added to the scope + /// nor to `new_objects`. fn load_objects_recursive<'a>( &mut self, name: &str, @@ -693,9 +744,9 @@ impl Linker { new_objects: &mut Vec>, objects_data: &mut Vec>, tcb_masters: &mut Vec, - // The object that caused this object to be loaded. - dependent: Option<&mut DSO>, - scope: ObjectScope, + // Scope of the object that caused this object to be loaded. + dependent_scope: Option<&mut Scope>, + scope_kind: ScopeKind, ) -> Result> { // fixme: double lookup slow if let Some(id) = self.name_to_object_id_map.get(name) { @@ -711,7 +762,7 @@ impl Linker { let path = self.search_object(name, parent_runpath)?; let file = self.read_file(&path)?; let data = file.data(); - let (mut obj, tcb_master, elf) = DSO::new( + let (obj, tcb_master, elf) = DSO::new( &path, data, base_addr, @@ -754,6 +805,18 @@ impl Linker { .map(|dep| dep.to_string()) .collect::>(); + let obj = Arc::new(obj); + let mut scope = Scope::local(); + + if let Some(dependent_scope) = dependent_scope { + match scope_kind { + ScopeKind::Local => dependent_scope.add(&obj), + ScopeKind::Global => GLOBAL_SCOPE.write().add(&obj), + } + } else if let ScopeKind::Global = scope_kind { + GLOBAL_SCOPE.write().add(&obj); + } + for dep_name in dependencies.iter() { self.load_objects_recursive( dep_name, @@ -763,28 +826,17 @@ impl Linker { new_objects, objects_data, tcb_masters, - Some(&mut obj), - scope, + Some(&mut scope), + scope_kind, )?; } - 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.write().add(&obj), - } - } else if let ObjectScope::Global = scope { - GLOBAL_SCOPE.write().add(&obj); - } - objects_data.push(elf); new_objects.push(obj.clone()); + scope.set_owner(Arc::downgrade(&obj)); + obj.scope.call_once(|| scope); + Ok(obj) } @@ -901,7 +953,7 @@ extern "C" fn __plt_resolve_inner(obj: *const DSO, relocation_index: c_uint) -> ) .expect("non utf8 symbol name"); - let resolved = resolve_sym(name, &[&GLOBAL_SCOPE.read(), &obj.scope]) + let resolved = resolve_sym(name, &[&GLOBAL_SCOPE.read(), obj.scope()]) .map(|(sym, _, _)| sym) .unwrap_or_else(|| panic!("symbol '{name}' not found")) .as_ptr(); From 99449094d4ae7b0c98772e45209a91b47c9b305b Mon Sep 17 00:00:00 2001 From: Anhad Singh Date: Sun, 19 Jan 2025 00:03:07 +1100 Subject: [PATCH 3/4] chore: formatting Signed-off-by: Anhad Singh --- src/ld_so/linker.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/ld_so/linker.rs b/src/ld_so/linker.rs index 7077bbea63..f57caf101f 100644 --- a/src/ld_so/linker.rs +++ b/src/ld_so/linker.rs @@ -240,7 +240,11 @@ impl Scope { }; match self { - Self::Global { objs } => objs.iter().skip(skip).map(|o| o.upgrade().unwrap()).find_map(get_sym), + Self::Global { objs } => objs + .iter() + .skip(skip) + .map(|o| o.upgrade().unwrap()) + .find_map(get_sym), Self::Local { owner, objs } => { let owner = owner .as_ref() From d3073fdad402b81ee831fe845928166b495a88c3 Mon Sep 17 00:00:00 2001 From: Anhad Singh Date: Sun, 19 Jan 2025 00:11:51 +1100 Subject: [PATCH 4/4] feat(ld.so): more debug info for scopes When scopes is set for `LD_DEBUG`, more debug information is displayed now, Signed-off-by: Anhad Singh --- src/ld_so/linker.rs | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/src/ld_so/linker.rs b/src/ld_so/linker.rs index f57caf101f..96eb0263b3 100644 --- a/src/ld_so/linker.rs +++ b/src/ld_so/linker.rs @@ -453,7 +453,7 @@ impl Linker { match name { Some(name) => { if let Some(id) = self.name_to_object_id_map.get(name) { - let obj = self.objects.get_mut(id).unwrap(); + let obj = self.objects.get(id).unwrap(); // We may be upgrading the object from a local scope to the // global scope. @@ -464,6 +464,7 @@ impl Linker { let mut global_scope = GLOBAL_SCOPE.write(); obj.scope().copy_into(&mut global_scope); + self.scope_debug(); } Ok(ObjectHandle::new(obj.clone())) @@ -923,6 +924,17 @@ impl Linker { obj.run_init(); } + + fn scope_debug(&self) { + if self.config.debug_flags.contains(DebugFlags::SCOPES) { + println!("[ld.so]: =========== SCOPES =========="); + GLOBAL_SCOPE.read().debug(); + for obj in self.objects.values() { + obj.scope().debug(); + } + println!("[ld.so]: =============================="); + } + } } // GOT[1] = object_id