From 500848bc7698f1d92c939622a872ca0b3d18103b Mon Sep 17 00:00:00 2001 From: Anhad Singh Date: Tue, 17 Dec 2024 21:29:33 +1100 Subject: [PATCH 01/15] fix(ld.so): remove unreachable `loop {}` _exit() is noreturn Signed-off-by: Anhad Singh --- src/ld_so/start.rs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/ld_so/start.rs b/src/ld_so/start.rs index b3b1d181ee..59b1ddeb93 100644 --- a/src/ld_so/start.rs +++ b/src/ld_so/start.rs @@ -39,7 +39,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); @@ -224,7 +223,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 +230,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 +257,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() } { From eb82f9afebe19ce5a647b343f78f87d0e2207eca Mon Sep 17 00:00:00 2001 From: Anhad Singh Date: Tue, 17 Dec 2024 21:34:46 +1100 Subject: [PATCH 02/15] misc(lint): unused variables and imports Signed-off-by: Anhad Singh --- src/ld_so/dso.rs | 4 ++-- src/ld_so/linker.rs | 2 +- src/ld_so/start.rs | 8 +------- src/ld_so/tcb.rs | 10 ++++------ 4 files changed, 8 insertions(+), 16 deletions(-) diff --git a/src/ld_so/dso.rs b/src/ld_so/dso.rs index 62d20aae81..2942b5b936 100644 --- a/src/ld_so/dso.rs +++ b/src/ld_so/dso.rs @@ -4,8 +4,8 @@ use super::{ 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, diff --git a/src/ld_so/linker.rs b/src/ld_so/linker.rs index 8d0c91f1ad..7a223d9b39 100644 --- a/src/ld_so/linker.rs +++ b/src/ld_so/linker.rs @@ -31,7 +31,7 @@ use super::{ debug::{RTLDState, _dl_debug_state, _r_debug}, dso::{is_pie_enabled, DSO}, tcb::{Master, Tcb}, - ExpectTlsFree, PATH_SEP, + PATH_SEP, }; /// Same as [`crate::fs::File`], but does not touch [`crate::platform::ERRNO`] as the dynamic diff --git a/src/ld_so/start.rs b/src/ld_so/start.rs index 59b1ddeb93..a10bb8e696 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, @@ -207,12 +206,7 @@ pub extern "C" fn relibc_ld_so_start( } // TODO: Fix memory leak, although minimal. - unsafe { - 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()); + crate::platform::init(auxv.clone()); let name_or_path = if is_manual { // ld.so is run directly by user and not via execve() or similar systemcall diff --git a/src/ld_so/tcb.rs b/src/ld_so/tcb.rs index 8521842f41..4dac13494c 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); @@ -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; @@ -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); } From cc5e0ceebc0a163811ca60bedad02bee6178697d Mon Sep 17 00:00:00 2001 From: Anhad Singh Date: Tue, 17 Dec 2024 21:35:11 +1100 Subject: [PATCH 03/15] fix(ld.so): function signature No longer expects a self TLS. Signed-off-by: Anhad Singh --- src/ld_so/start.rs | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/ld_so/start.rs b/src/ld_so/start.rs index a10bb8e696..c133a3deaa 100644 --- a/src/ld_so/start.rs +++ b/src/ld_so/start.rs @@ -150,12 +150,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"); From 959982b8a4a398f661729d52bea5ae5854f37c98 Mon Sep 17 00:00:00 2001 From: Anhad Singh Date: Tue, 17 Dec 2024 21:38:47 +1100 Subject: [PATCH 04/15] fix(ld.so): use `Strtab::get_at()` > use of deprecated method `goblin::strtab::Strtab::<'a>::get`: Bad performance, use get_at() instead Signed-off-by: Anhad Singh --- src/ld_so/dso.rs | 8 ++++---- src/ld_so/linker.rs | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/ld_so/dso.rs b/src/ld_so/dso.rs index 2942b5b936..8f619e3957 100644 --- a/src/ld_so/dso.rs +++ b/src/ld_so/dso.rs @@ -147,8 +147,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))); } @@ -361,8 +361,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, diff --git a/src/ld_so/linker.rs b/src/ld_so/linker.rs index 7a223d9b39..b8d2156eb5 100644 --- a/src/ld_so/linker.rs +++ b/src/ld_so/linker.rs @@ -509,11 +509,11 @@ impl Linker { 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 { From 30970e111ae12579872efceb11b0dddd1f1fa4d3 Mon Sep 17 00:00:00 2001 From: Anhad Singh Date: Wed, 18 Dec 2024 01:17:34 +1100 Subject: [PATCH 05/15] feat(ld.so): enable warnings just for ld.so Signed-off-by: Anhad Singh --- src/ld_so/mod.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/ld_so/mod.rs b/src/ld_so/mod.rs index 2806f09f3f..334793e19e 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}; From 5ae1b8e974a2b1d42f981f6954a6a2d43d657097 Mon Sep 17 00:00:00 2001 From: Anhad Singh Date: Thu, 19 Dec 2024 00:29:06 +1100 Subject: [PATCH 06/15] feat(ld.so): lazy binding Signed-off-by: Anhad Singh --- src/header/dlfcn/mod.rs | 13 +- src/ld_so/callbacks.rs | 8 +- src/ld_so/dso.rs | 43 ++++- src/ld_so/linker.rs | 338 ++++++++++++++++++++++++++++++++++------ 4 files changed, 341 insertions(+), 61 deletions(-) diff --git a/src/header/dlfcn/mod.rs b/src/header/dlfcn/mod.rs index b12f834b42..c62fcacd0f 100644 --- a/src/header/dlfcn/mod.rs +++ b/src/header/dlfcn/mod.rs @@ -7,7 +7,11 @@ use core::{ sync::atomic::{AtomicUsize, Ordering}, }; -use crate::{c_str::CStr, ld_so::tcb::Tcb, platform::types::*}; +use crate::{ + c_str::CStr, + ld_so::{linker::Resolve, tcb::Tcb}, + platform::types::*, +}; pub const RTLD_LAZY: c_int = 0x0001; pub const RTLD_NOW: c_int = 0x0002; @@ -42,6 +46,11 @@ 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 filename = if cfilename.is_null() { None @@ -69,7 +78,7 @@ 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) { + let id = match (cbs.load_library)(&mut linker, filename, resolve) { Err(err) => { ERROR.store(ERROR_NOT_SUPPORTED.as_ptr() as usize, Ordering::SeqCst); return ptr::null_mut(); diff --git a/src/ld_so/callbacks.rs b/src/ld_so/callbacks.rs index 1d7d7a05f4..f2b5b3e51e 100644 --- a/src/ld_so/callbacks.rs +++ b/src/ld_so/callbacks.rs @@ -1,11 +1,11 @@ -use super::linker::Linker; +use super::linker::{Linker, 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 load_library: Box, Resolve) -> Result>, pub get_sym: Box Option<*mut c_void>>, } @@ -23,8 +23,8 @@ fn unload(linker: &mut Linker, lib_id: usize) { linker.unload(lib_id) } -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) -> Result { + linker.load_library(name, resolve) } fn get_sym(linker: &Linker, lib_id: usize, name: &str) -> Option<*mut c_void> { diff --git a/src/ld_so/dso.rs b/src/ld_so/dso.rs index 8f619e3957..56a33d01bc 100644 --- a/src/ld_so/dso.rs +++ b/src/ld_so/dso.rs @@ -1,3 +1,5 @@ +//! See . + use super::{ debug::{RTLDDebug, _r_debug}, linker::Symbol, @@ -33,10 +35,32 @@ use goblin::elf64::{ sym, }; use goblin::{ - elf::Elf, + elf::{ + 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 { @@ -57,6 +81,9 @@ pub struct DSO { pub tls_module_id: usize, pub tls_offset: usize, pub use_count: usize, + + pub dynamic: Option, + pub dynsyms: Vec, } impl DSO { @@ -106,15 +133,19 @@ impl DSO { 0 }, tls_offset: tls_offset, + + dynamic: elf.dynamic.map(|dynamic| dynamic), + dynsyms: elf.dynsyms.iter().collect(), }; - return Ok((dso, tcb_master)); + + Ok((dso, tcb_master)) } - pub fn get_sym(&self, name: &str) -> Option<(Symbol, bool)> { + 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 } @@ -404,7 +435,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 diff --git a/src/ld_so/linker.rs b/src/ld_so/linker.rs index b8d2156eb5..34711d5450 100644 --- a/src/ld_so/linker.rs +++ b/src/ld_so/linker.rs @@ -6,7 +6,12 @@ use alloc::{ }; use core::{cell::RefCell, mem::transmute, ptr}; use goblin::{ - elf::{program_header, reloc, sym::STT_TLS, Elf}, + elf::{ + dynamic::{DT_PLTGOT, DT_STRTAB}, + program_header, reloc, + sym::STT_TLS, + Elf, + }, error::{Error, Result}, }; @@ -19,8 +24,9 @@ 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, }, }; @@ -68,6 +74,16 @@ impl Symbol { } } +#[derive(Debug, Default, Copy, Clone, PartialEq)] +pub enum Resolve { + /// Resolve all undefined symbols immediately. + Now, + /// Perform lazy binding (i.e. symbols will be resolved when they are first + /// used). + #[default] + Lazy, +} + bitflags::bitflags! { #[derive(Debug, Default)] pub struct DebugFlags: u32 { @@ -141,53 +157,50 @@ 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); + self.load_object(path, &None, base_addr, false, Resolve::default())?; + // TODO(andypython): make self.load_object() return a reference to the + // loaded object, thereby remove the ugly unwrap(). + Ok(self.objects.get(&root_id).unwrap().entry_point) } - pub fn load_library(&mut self, name: Option<&str>) -> Result { + pub fn load_library(&mut self, name: Option<&str>, resolve: Resolve) -> Result { 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); + Ok(*id) } else { let parent_runpath = &self .objects .get(&root_id) .and_then(|parent| parent.runpath.clone()); let lib_id = self.next_object_id; - self.load_object(name, parent_runpath, None, true)?; + self.load_object(name, parent_runpath, None, true, resolve)?; - return Ok(lib_id); + Ok(lib_id) } } - None => return Ok(root_id), + + None => 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; - } - } + self.objects.get(&lib_id).and_then(|obj| { + obj.get_sym(name).map(|(s, _binding)| { + if s.sym_type != STT_TLS { + s.as_ptr() + } else { + let mut tls_index = dl_tls_index { + ti_module: obj.tls_module_id as u64, + ti_offset: s.value as u64, + }; + + unsafe { __tls_get_addr(&mut tls_index) } + } + }) + }) } pub fn unload(&mut self, lib_id: usize) { @@ -219,6 +232,7 @@ impl Linker { runpath: &Option, base_addr: Option, dlopened: bool, + resolve: Resolve, ) -> Result<()> { unsafe { _r_debug.state = RTLDState::RT_ADD }; _dl_debug_state(); @@ -335,7 +349,7 @@ impl Linker { } } - self.relocate(&new_objects, &objects_data)?; + self.relocate(&new_objects, &objects_data, resolve)?; self.run_init(&new_objects); for obj in new_objects.into_iter() { @@ -420,7 +434,7 @@ impl Linker { )?; } - return Ok(()); + Ok(()) } fn search_object(&self, name: &str, parent_runpath: &Option) -> Result { @@ -474,7 +488,102 @@ impl Linker { return Ok(data); } - fn relocate(&self, new_objects: &Vec, objects_data: &Vec>) -> Result<()> { + fn resolve_sym<'a>(name: &str, objs: impl Iterator) -> Option { + let mut res = None; + + for dso in objs { + if let Some((sym, binding)) = dso.get_sym(name) { + if binding.is_global() { + return Some(sym); + } + + res = Some(sym); + } + } + + res + } + + /// Perform lazy relocations. + fn lazy_relocate<'a>( + &self, + obj: &DSO, + new_objects: &[DSO], + elf: &Elf, + resolve: Resolve, + ) -> Result<()> { + let object_base_addr = obj.mmap.as_ptr() as u64; + + if let Some(dynamic) = elf.dynamic.as_ref() { + // Global Offset Table + 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) + } { + (object_base_addr + ptr) as *mut usize + } else { + assert_eq!(dynamic.info.jmprel, 0); + return Ok(()); + }; + + unsafe { + got.add(1).write(obj.id); + got.add(2).write(__plt_resolve_trampoline as usize); + } + + for rel in elf.pltrelocs.iter() { + match (rel.r_type, resolve) { + (reloc::R_X86_64_JUMP_SLOT, Resolve::Lazy) => unsafe { + *((object_base_addr + rel.r_offset) as *mut u64) += object_base_addr; + }, + + (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 name = + elf.dynstrtab + .get_at(sym.st_name) + .ok_or(Error::Malformed(format!( + "missing name for symbol {:?}", + sym + )))?; + + // FIXME(andypython): warn on unresolved symbols? + let resolved = Linker::resolve_sym( + name, + self.objects.values().chain(new_objects.iter()), + ) + .map(|sym| sym.as_ptr()) + .unwrap_or(ptr::null_mut()); + + let addend = rel.r_addend.unwrap_or(0) as u64; + + unsafe { + *((object_base_addr + rel.r_offset) as *mut u64) = + resolved as u64 + addend; + } + } + + _ => todo!("unsupported relocation type {:?}", rel.r_type), + } + } + } + + Ok(()) + } + + fn relocate( + &self, + new_objects: &Vec, + objects_data: &Vec>, + resolve: Resolve, + ) -> Result<()> { let symbols_lookup_objects: Vec<&DSO> = self.objects.values().chain(new_objects.iter()).collect(); @@ -489,12 +598,7 @@ impl Linker { let b = mmap.as_ptr() as usize; // Relocate - for rel in elf - .dynrelas - .iter() - .chain(elf.dynrels.iter()) - .chain(elf.pltrelocs.iter()) - { + for rel in elf.dynrelas.iter().chain(elf.dynrels.iter()) { trace!( " rel {}: {:x?}", reloc::r_to_str(rel.r_type, elf.header.e_machine), @@ -520,21 +624,20 @@ impl Linker { 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) { + for lookup_obj in symbols_lookup_objects.iter().skip(lookup_start) { + if let Some((s, binding)) = lookup_obj.get_sym(name) { trace!( - "symbol {} from {} found in {} ({})", + "symbol {} from {} found in {} ({:?})", name, obj.name, lookup_obj.name, - if strong { "strong" } else { "weak" } + binding ); symbol = Some(s); t = lookup_obj.tls_offset; found = true; // Stop looking if any strong symbol is found - if strong { + if binding.is_global() { break; } } @@ -607,7 +710,7 @@ impl Linker { 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); + ptr::copy_nonoverlapping(sym.as_ptr() as *const u8, ptr, sym.size); }, _ => { panic!( @@ -639,7 +742,7 @@ impl Linker { prot |= sys_mman::PROT_WRITE; } - let res = unsafe { + unsafe { let ptr = if is_pie_enabled(&elf) { mmap.as_ptr().add(vaddr) } else { @@ -649,18 +752,22 @@ impl Linker { Sys::mprotect(ptr as *mut c_void, vsize, prot).map_err(|_| { Error::Malformed(format!("failed to mprotect {}", obj.name)) })?; - }; + } } } - return Ok(()); + for (i, obj) in new_objects.iter().enumerate() { + self.lazy_relocate(obj, new_objects, &Elf::parse(&objects_data[i])?, resolve)?; + } + + Ok(()) } fn run_init(&self, objects: &Vec) { use crate::platform::{self, types::*}; for obj in objects.iter().rev() { - if let Some((symbol, true)) = obj.get_sym("__relibc_init_environ") { + if let Some((symbol, SymbolBinding::Global)) = obj.get_sym("__relibc_init_environ") { unsafe { symbol .as_ptr() @@ -673,3 +780,136 @@ impl Linker { } } } + +// GOT[1] = object_id +// GOT[2] = __plt_resolve_trampoline +// +// The stubs in .plt will push the relocation index and the object ID 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(object_id: usize, relocation_index: c_uint) -> *mut c_void { + let tcb = unsafe { Tcb::current() }.unwrap(); + assert!(!tcb.linker_ptr.is_null()); + + let linker = unsafe { &*tcb.linker_ptr }.lock(); + + let obj = linker.objects.get(&object_id).unwrap(); + 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 + obj_base as u64) as *const c_char) + }; + + let resolved = Linker::resolve_sym(name.to_str().unwrap(), linker.objects.values()) + .expect("symbol not found") + .as_ptr(); + + unsafe { + println!( + "@plt: {} -> *mut {:#x}", + name.to_string_lossy(), + resolved as usize + ); + + *((obj_base as u64 + rela.r_offset) as *mut u64) = 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 +); From c6fd0517ace5eb50481b138a85143ceed8d265a0 Mon Sep 17 00:00:00 2001 From: Anhad Singh Date: Thu, 19 Dec 2024 00:38:02 +1100 Subject: [PATCH 07/15] feat(ld.so: config): handle `LD_BIND_NOW` Signed-off-by: Anhad Singh --- src/ld_so/linker.rs | 34 +++++++++++++++++++++++++++++----- 1 file changed, 29 insertions(+), 5 deletions(-) diff --git a/src/ld_so/linker.rs b/src/ld_so/linker.rs index 34711d5450..a8c79a851a 100644 --- a/src/ld_so/linker.rs +++ b/src/ld_so/linker.rs @@ -98,6 +98,8 @@ bitflags::bitflags! { pub struct Config { debug_flags: DebugFlags, library_path: Option, + /// Resolve symbols at program startup. + bind_now: bool, } impl Config { @@ -121,11 +123,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(), } } } @@ -157,7 +161,17 @@ impl Linker { } pub fn load_program(&mut self, path: &str, base_addr: Option) -> Result { - self.load_object(path, &None, base_addr, false, Resolve::default())?; + self.load_object( + path, + &None, + base_addr, + false, + if self.config.bind_now { + Resolve::Now + } else { + Resolve::default() + }, + )?; // TODO(andypython): make self.load_object() return a reference to the // loaded object, thereby remove the ugly unwrap(). Ok(self.objects.get(&root_id).unwrap().entry_point) @@ -176,7 +190,17 @@ impl Linker { .get(&root_id) .and_then(|parent| parent.runpath.clone()); let lib_id = self.next_object_id; - self.load_object(name, parent_runpath, None, true, resolve)?; + self.load_object( + name, + parent_runpath, + None, + true, + if self.config.bind_now { + Resolve::Now + } else { + resolve + }, + )?; Ok(lib_id) } From 0a0ecb018f49b12577cad7440ac63452ef95c824 Mon Sep 17 00:00:00 2001 From: Anhad Singh Date: Thu, 19 Dec 2024 00:41:26 +1100 Subject: [PATCH 08/15] fix(ld.so: adjust_stack): remove dead code Does not do anything. Signed-off-by: Anhad Singh --- src/ld_so/start.rs | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/src/ld_so/start.rs b/src/ld_so/start.rs index c133a3deaa..257572169b 100644 --- a/src/ld_so/start.rs +++ b/src/ld_so/start.rs @@ -86,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 From e3587017f60bc064a3b4813009f328c5e2e97d05 Mon Sep 17 00:00:00 2001 From: Anhad Singh Date: Thu, 19 Dec 2024 00:53:08 +1100 Subject: [PATCH 09/15] fix(ld.so): stub plt stub for non-x86_64 Signed-off-by: Anhad Singh --- src/ld_so/linker.rs | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/src/ld_so/linker.rs b/src/ld_so/linker.rs index a8c79a851a..eeb8f6f873 100644 --- a/src/ld_so/linker.rs +++ b/src/ld_so/linker.rs @@ -937,3 +937,36 @@ __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 + " +); From 13d819ac896eeeb99731f5db4b56f24b9e113882 Mon Sep 17 00:00:00 2001 From: Anhad Singh Date: Thu, 19 Dec 2024 01:37:02 +1100 Subject: [PATCH 10/15] misc(ld.so: __plt_resolve_inner): println -> trace Signed-off-by: Anhad Singh --- src/ld_so/linker.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ld_so/linker.rs b/src/ld_so/linker.rs index eeb8f6f873..5bb8f3726a 100644 --- a/src/ld_so/linker.rs +++ b/src/ld_so/linker.rs @@ -855,7 +855,7 @@ extern "C" fn __plt_resolve_inner(object_id: usize, relocation_index: c_uint) -> .as_ptr(); unsafe { - println!( + trace!( "@plt: {} -> *mut {:#x}", name.to_string_lossy(), resolved as usize From 1db68ab9787b96b0425f3f60712ae5dfa7a9fbd3 Mon Sep 17 00:00:00 2001 From: Anhad Singh Date: Fri, 20 Dec 2024 02:33:09 +1100 Subject: [PATCH 11/15] feat(ld.so): scopes Signed-off-by: Anhad Singh --- src/header/dlfcn/mod.rs | 20 +- src/ld_so/callbacks.rs | 13 +- src/ld_so/dso.rs | 34 +- src/ld_so/linker.rs | 600 ++++++++++-------- src/ld_so/tcb.rs | 2 +- tests/Makefile | 12 +- tests/dlopen_scopes.c | 43 ++ .../bins_dynamic/dlopen_scopes.stderr | 0 .../bins_dynamic/dlopen_scopes.stdout | 0 tests/libfoo.c | 4 + tests/libfoobar.c | 5 + 11 files changed, 443 insertions(+), 290 deletions(-) create mode 100644 tests/dlopen_scopes.c create mode 100644 tests/expected/bins_dynamic/dlopen_scopes.stderr create mode 100644 tests/expected/bins_dynamic/dlopen_scopes.stdout create mode 100644 tests/libfoo.c create mode 100644 tests/libfoobar.c diff --git a/src/header/dlfcn/mod.rs b/src/header/dlfcn/mod.rs index c62fcacd0f..d8bd4f06fd 100644 --- a/src/header/dlfcn/mod.rs +++ b/src/header/dlfcn/mod.rs @@ -9,13 +9,17 @@ use core::{ use crate::{ c_str::CStr, - ld_so::{linker::Resolve, tcb::Tcb}, + ld_so::{ + linker::{ObjectScope, Resolve}, + tcb::Tcb, + }, platform::types::*, }; -pub const RTLD_LAZY: c_int = 0x0001; -pub const RTLD_NOW: c_int = 0x0002; -pub const RTLD_GLOBAL: c_int = 0x0100; +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; static ERROR_NOT_SUPPORTED: CStr = c_str!("dlfcn not supported"); @@ -52,6 +56,12 @@ pub unsafe extern "C" fn dlopen(cfilename: *const c_char, flags: c_int) -> *mut Resolve::Lazy }; + let scope = if flags & RTLD_GLOBAL == RTLD_GLOBAL { + ObjectScope::Global + } else { + ObjectScope::Local + }; + let filename = if cfilename.is_null() { None } else { @@ -78,7 +88,7 @@ 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, resolve) { + let id = match (cbs.load_library)(&mut linker, filename, resolve, scope) { Err(err) => { ERROR.store(ERROR_NOT_SUPPORTED.as_ptr() as usize, Ordering::SeqCst); return ptr::null_mut(); diff --git a/src/ld_so/callbacks.rs b/src/ld_so/callbacks.rs index f2b5b3e51e..e9d804bcdc 100644 --- a/src/ld_so/callbacks.rs +++ b/src/ld_so/callbacks.rs @@ -1,11 +1,11 @@ -use super::linker::{Linker, Resolve}; +use super::linker::{Linker, 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, Resolve) -> Result>, + pub load_library: Box, Resolve, ObjectScope) -> Result>, pub get_sym: Box Option<*mut c_void>>, } @@ -23,8 +23,13 @@ fn unload(linker: &mut Linker, lib_id: usize) { linker.unload(lib_id) } -fn load_library(linker: &mut Linker, name: Option<&str>, resolve: Resolve) -> Result { - linker.load_library(name, resolve) +fn load_library( + linker: &mut Linker, + name: Option<&str>, + resolve: Resolve, + scope: ObjectScope, +) -> Result { + linker.load_library(name, resolve, scope) } fn get_sym(linker: &Linker, lib_id: usize, name: &str) -> Option<*mut c_void> { diff --git a/src/ld_so/dso.rs b/src/ld_so/dso.rs index 56a33d01bc..f11bdde25d 100644 --- a/src/ld_so/dso.rs +++ b/src/ld_so/dso.rs @@ -2,7 +2,7 @@ use super::{ debug::{RTLDDebug, _r_debug}, - linker::Symbol, + linker::{Scope, Symbol}, tcb::Master, }; use crate::{ @@ -17,6 +17,7 @@ use alloc::{ use core::{ mem::{size_of, transmute}, ptr, slice, + sync::atomic::AtomicUsize, }; #[cfg(target_pointer_width = "32")] use goblin::elf32::{ @@ -62,7 +63,6 @@ impl SymbolBinding { } /// 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, @@ -80,10 +80,12 @@ pub struct DSO { pub fini_array: (usize, usize), pub tls_module_id: usize, pub tls_offset: usize, - pub use_count: usize, + pub use_count: AtomicUsize, pub dynamic: Option, pub dynsyms: Vec, + + pub scope: Scope, } impl DSO { @@ -115,27 +117,29 @@ 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, + use_count: AtomicUsize::new(1), + 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, dynamic: elf.dynamic.map(|dynamic| dynamic), dynsyms: elf.dynsyms.iter().collect(), + + scope: Scope::new(), }; Ok((dso, tcb_master)) @@ -453,7 +457,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 5bb8f3726a..55d39e5cb3 100644 --- a/src/ld_so/linker.rs +++ b/src/ld_so/linker.rs @@ -2,9 +2,10 @@ 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, sync::atomic::Ordering}; use goblin::{ elf::{ dynamic::{DT_PLTGOT, DT_STRTAB}, @@ -29,6 +30,7 @@ use crate::{ types::{c_char, c_int, c_uint, c_void}, Pal, Sys, }, + sync::Mutex, }; use super::{ @@ -40,6 +42,9 @@ use super::{ PATH_SEP, }; +// TODO: rwlock? +static GLOBAL_SCOPE: Mutex = Mutex::new(Scope::new()); + /// Same as [`crate::fs::File`], but does not touch [`crate::platform::ERRNO`] as the dynamic /// linker does not have thread-local storage. struct File { @@ -84,13 +89,65 @@ pub enum Resolve { Lazy, } +#[derive(Debug, Copy, Clone, PartialEq)] +pub enum ObjectScope { + Global, + Local, +} + +pub struct Scope { + objs: Vec>, +} + +impl Scope { + #[inline] + pub const fn new() -> Self { + Self { objs: Vec::new() } + } + + pub fn add(&mut self, obj: Weak) { + for dso in self.objs.iter() { + if dso.ptr_eq(&obj) { + // Already in scope. + return; + } + } + + self.objs.push(obj); + } + + pub fn get_sym(&self, name: &str) -> Option<(Symbol, SymbolBinding, Arc)> { + let mut res = None; + + for obj in self.objs.iter() { + let obj = obj.upgrade().expect("in scope object was dropped"); + if let Some((sym, binding)) = obj.get_sym(name) { + if binding.is_global() { + return Some((sym, binding, obj)); + } + + res = Some((sym, binding, obj)); + } + } + + res + } + + pub fn move_into(&self, other: &mut Self) { + // FIXME: move not copy? as afiak u cannot downgrade from a global to a local scope. + other.objs.extend(self.objs.iter().cloned()); + } +} + 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; } } @@ -112,6 +169,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() @@ -140,18 +198,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(), @@ -171,23 +229,45 @@ impl Linker { } else { Resolve::default() }, + ObjectScope::Global, )?; // TODO(andypython): make self.load_object() return a reference to the // loaded object, thereby remove the ugly unwrap(). - Ok(self.objects.get(&root_id).unwrap().entry_point) + Ok(self.objects.get(&ROOT_ID).unwrap().entry_point) } - pub fn load_library(&mut self, name: Option<&str>, resolve: Resolve) -> Result { + pub fn load_library( + &mut self, + name: Option<&str>, + resolve: Resolve, + scope: ObjectScope, + ) -> Result { 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; + obj.use_count.fetch_add(1, Ordering::SeqCst); + + // We may be upgrading the object from a local scope to the + // global scope. + // + // TODO(andypython): Only do it if RTLD_NOLOAD. Otherwise, + // we should reload rather than just promoting. + 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(); + global_scope.add(Arc::downgrade(obj)); + obj.scope.move_into(&mut global_scope); + } + Ok(*id) } else { 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( @@ -200,37 +280,48 @@ impl Linker { } else { resolve }, + scope, )?; Ok(lib_id) } } - None => Ok(root_id), + None => Ok(ROOT_ID), } } pub fn get_sym(&self, lib_id: usize, name: &str) -> Option<*mut c_void> { - self.objects.get(&lib_id).and_then(|obj| { - obj.get_sym(name).map(|(s, _binding)| { - if s.sym_type != STT_TLS { - s.as_ptr() - } else { - let mut tls_index = dl_tls_index { - ti_module: obj.tls_module_id as u64, - ti_offset: s.value as u64, - }; + let guard; - unsafe { __tls_get_addr(&mut tls_index) } - } - }) + if lib_id == 0 { + guard = GLOBAL_SCOPE.lock(); + &*guard + } else { + &self.objects.get(&lib_id)?.scope + } + .get_sym(name) + .map(|(symbol, _, obj)| { + if symbol.sym_type != STT_TLS { + symbol.as_ptr() + } else { + let mut tls_index = dl_tls_index { + // TODO(andypython): instead of an ID, why don't we pass a + // pointer to the object? Then we won't need to look it up. + // This is what we already do for __plt_resolve_trampoline. + 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, lib_id: usize) { if let Some(obj) = self.objects.get_mut(&lib_id) { if obj.dlopened { - if obj.use_count == 1 { + if obj.use_count.load(Ordering::SeqCst) == 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()); @@ -238,7 +329,7 @@ impl Linker { self.name_to_object_id_map.remove(&obj.name); drop(obj); } else { - obj.use_count -= 1; + obj.use_count.fetch_sub(1, Ordering::SeqCst); } } } @@ -257,6 +348,7 @@ impl Linker { base_addr: Option, dlopened: bool, resolve: Resolve, + scope: ObjectScope, ) -> Result<()> { unsafe { _r_debug.state = RTLDState::RT_ADD }; _dl_debug_state(); @@ -272,6 +364,8 @@ impl Linker { &mut new_objects, &mut objects_data, &mut tcb_masters, + None, + scope, )?; unsafe { @@ -373,18 +467,22 @@ impl Linker { } } - self.relocate(&new_objects, &objects_data, resolve)?; - 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(()) + } + + 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( @@ -393,24 +491,26 @@ 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, + // The object that caused this object to be loaded. + dependent: Option<&mut DSO>, + scope: ObjectScope, ) -> 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) + if let Some(id) = self.name_to_object_id_map.get(name) { + if let Some(obj) = self.objects.get_mut(id) { + obj.use_count.fetch_add(1, Ordering::SeqCst); + return Ok(()); } - } { - obj.use_count += 1; + } else if let Some(obj) = new_objects.iter_mut().find(|o| o.name == name) { + obj.use_count.fetch_add(1, Ordering::SeqCst); return Ok(()); } 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, @@ -428,9 +528,6 @@ impl Linker { ); } - new_objects.push(obj); - objects_data.push(data); - self.next_object_id += 1; if let Some(master) = tcb_master { @@ -442,10 +539,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, @@ -455,9 +551,28 @@ impl Linker { new_objects, objects_data, tcb_masters, + Some(&mut obj), + scope, )?; } + let obj = Arc::new_cyclic(|sref| { + obj.scope.add(sref.clone()); + obj + }); + + if let Some(dependent) = dependent { + match scope { + ObjectScope::Local => dependent.scope.add(Arc::downgrade(&obj)), + ObjectScope::Global => GLOBAL_SCOPE.lock().add(Arc::downgrade(&obj)), + } + } else if let ObjectScope::Global = scope { + GLOBAL_SCOPE.lock().add(Arc::downgrade(&obj)); + } + + objects_data.push(data); + new_objects.push(obj); + Ok(()) } @@ -509,36 +624,14 @@ impl Linker { file.read_to_end(&mut data) .map_err(|err| Error::Malformed(format!("failed to read '{}': {}", path, err)))?; - return Ok(data); - } - - fn resolve_sym<'a>(name: &str, objs: impl Iterator) -> Option { - let mut res = None; - - for dso in objs { - if let Some((sym, binding)) = dso.get_sym(name) { - if binding.is_global() { - return Some(sym); - } - - res = Some(sym); - } - } - - res + Ok(data) } /// Perform lazy relocations. - fn lazy_relocate<'a>( - &self, - obj: &DSO, - new_objects: &[DSO], - elf: &Elf, - resolve: Resolve, - ) -> Result<()> { - let object_base_addr = obj.mmap.as_ptr() as u64; + fn lazy_relocate(&self, obj: &Arc, elf: &Elf, resolve: Resolve) -> Result<()> { + if let Some(dynamic) = obj.dynamic.as_ref() { + let object_base_addr = obj.mmap.as_ptr() as u64; - if let Some(dynamic) = elf.dynamic.as_ref() { // Global Offset Table let got = if let Some(ptr) = { dynamic @@ -554,7 +647,8 @@ impl Linker { }; unsafe { - got.add(1).write(obj.id); + let obj = Arc::into_raw(obj.clone()); + got.add(1).write(obj as usize); got.add(2).write(__plt_resolve_trampoline as usize); } @@ -578,13 +672,12 @@ impl Linker { sym )))?; - // FIXME(andypython): warn on unresolved symbols? - let resolved = Linker::resolve_sym( - name, - self.objects.values().chain(new_objects.iter()), - ) - .map(|sym| sym.as_ptr()) - .unwrap_or(ptr::null_mut()); + let resolved = GLOBAL_SCOPE + .lock() + .get_sym(name) + .or_else(|| obj.scope.get_sym(name)) + .map(|(sym, _, _)| sym.as_ptr()) + .expect("unresolved symbol"); let addend = rel.r_addend.unwrap_or(0) as u64; @@ -602,224 +695,203 @@ impl Linker { Ok(()) } - fn relocate( - &self, - new_objects: &Vec, - objects_data: &Vec>, - resolve: Resolve, - ) -> Result<()> { - let symbols_lookup_objects: Vec<&DSO> = - self.objects.values().chain(new_objects.iter()).collect(); - + fn relocate(&self, obj: &Arc, object_data: &[u8], resolve: Resolve) -> Result<()> { // Perform relocations - for i in (0..new_objects.len()).rev() { - let elf = Elf::parse(&objects_data[i])?; - let obj = &new_objects[i]; + let elf = Elf::parse(object_data)?; - trace!("link {}", obj.name); + trace!("link {}", obj.name); - let mmap = &obj.mmap; - let b = mmap.as_ptr() as usize; + 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), + // 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 (symbol, t) = 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 mut t = 0; - let name = - elf.dynstrtab - .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_obj in symbols_lookup_objects.iter().skip(lookup_start) { - if let Some((s, binding)) = lookup_obj.get_sym(name) { - trace!( - "symbol {} from {} found in {} ({:?})", - name, - obj.name, - lookup_obj.name, - binding - ); - symbol = Some(s); - t = lookup_obj.tls_offset; - found = true; - // Stop looking if any strong symbol is found - if binding.is_global() { - 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 symbol = GLOBAL_SCOPE + .lock() + .get_sym(name) + .or_else(|| obj.scope.get_sym(name)) + .map(|(sym, _, obj)| (sym, obj.tls_offset)); - let s = symbol - .as_ref() - .map(|sym| sym.as_ptr() as usize) - .unwrap_or(0); + // 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 a = rel.r_addend.unwrap_or(0) as usize; + let (s, t) = symbol + .as_ref() + .map(|(sym, t)| (sym.as_ptr() as usize, *t)) + .unwrap_or((0, 0)); - 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; - } - }; + let a = rel.r_addend.unwrap_or(0) as usize; - 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 => { + 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); - }, - _ => { - 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; - } - - // 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)) - })?; + 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) + ); } } } - for (i, obj) in new_objects.iter().enumerate() { - self.lazy_relocate(obj, new_objects, &Elf::parse(&objects_data[i])?, resolve)?; + 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, SymbolBinding::Global)) = 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 ID 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. +// 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(object_id: usize, relocation_index: c_uint) -> *mut c_void { - let tcb = unsafe { Tcb::current() }.unwrap(); - assert!(!tcb.linker_ptr.is_null()); +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 linker = unsafe { &*tcb.linker_ptr }.lock(); - - let obj = linker.objects.get(&object_id).unwrap(); + 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; @@ -850,8 +922,8 @@ extern "C" fn __plt_resolve_inner(object_id: usize, relocation_index: c_uint) -> CStr::from_ptr((strtab_offset + sym.st_name as u64 + obj_base as u64) as *const c_char) }; - let resolved = Linker::resolve_sym(name.to_str().unwrap(), linker.objects.values()) - .expect("symbol not found") + let resolved = resolve_sym(name.to_str().unwrap(), &[&GLOBAL_SCOPE.lock(), &obj.scope]) + .expect(&format!("symbol '{}' not found", name.to_str().unwrap())) .as_ptr(); unsafe { diff --git a/src/ld_so/tcb.rs b/src/ld_so/tcb.rs index 4dac13494c..ef3c17ea45 100644 --- a/src/ld_so/tcb.rs +++ b/src/ld_so/tcb.rs @@ -195,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::(); 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/dlopen_scopes.c b/tests/dlopen_scopes.c new file mode 100644 index 0000000000..d639bfba53 --- /dev/null +++ b/tests/dlopen_scopes.c @@ -0,0 +1,43 @@ +#include +#include +#include +#include +#include + +// FIXME(andpython): should be defined in dlfcn.h +#ifndef RTLD_DEFAULT +#define RTLD_DEFAULT NULL +#endif + +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..3bc4cbd8bc --- /dev/null +++ b/tests/libfoo.c @@ -0,0 +1,4 @@ +char *FOO = "foo"; +char *BAZ = "baz"; + +void a() {} diff --git a/tests/libfoobar.c b/tests/libfoobar.c new file mode 100644 index 0000000000..c862ee0bf6 --- /dev/null +++ b/tests/libfoobar.c @@ -0,0 +1,5 @@ +char *BAR = "bar"; +extern char *BAZ; + +void a(); +void b() { a(); } From 023a005688a57e00264556af4a6037320ad3a5d4 Mon Sep 17 00:00:00 2001 From: Anhad Singh Date: Fri, 20 Dec 2024 14:55:45 +1100 Subject: [PATCH 12/15] fix(ld.so): non-pie lazy Signed-off-by: Anhad Singh --- src/ld_so/dso.rs | 3 +++ src/ld_so/linker.rs | 51 +++++++++++++++++++++++++++++++-------------- src/ld_so/start.rs | 10 +++++---- src/platform/mod.rs | 2 +- 4 files changed, 45 insertions(+), 21 deletions(-) diff --git a/src/ld_so/dso.rs b/src/ld_so/dso.rs index f11bdde25d..f8159fe3c7 100644 --- a/src/ld_so/dso.rs +++ b/src/ld_so/dso.rs @@ -86,6 +86,8 @@ pub struct DSO { pub dynsyms: Vec, pub scope: Scope, + /// Position Independent Executable. + pub pie: bool, } impl DSO { @@ -136,6 +138,7 @@ impl DSO { }, tls_offset, + pie: is_pie_enabled(&elf), dynamic: elf.dynamic.map(|dynamic| dynamic), dynsyms: elf.dynsyms.iter().collect(), diff --git a/src/ld_so/linker.rs b/src/ld_so/linker.rs index 55d39e5cb3..9105271df9 100644 --- a/src/ld_so/linker.rs +++ b/src/ld_so/linker.rs @@ -629,7 +629,7 @@ impl Linker { /// Perform lazy relocations. fn lazy_relocate(&self, obj: &Arc, elf: &Elf, resolve: Resolve) -> Result<()> { - if let Some(dynamic) = obj.dynamic.as_ref() { + if let Some(dynamic) = elf.dynamic.as_ref() { let object_base_addr = obj.mmap.as_ptr() as u64; // Global Offset Table @@ -640,7 +640,11 @@ impl Linker { .find(|r#dyn| r#dyn.d_tag == DT_PLTGOT) .map(|r#dyn| r#dyn.d_val) } { - (object_base_addr + ptr) as *mut usize + if is_pie_enabled(&elf) { + (object_base_addr + ptr) as *mut usize + } else { + ptr as *mut usize + } } else { assert_eq!(dynamic.info.jmprel, 0); return Ok(()); @@ -653,11 +657,21 @@ impl Linker { } 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 + }; + match (rel.r_type, resolve) { - (reloc::R_X86_64_JUMP_SLOT, Resolve::Lazy) => unsafe { - *((object_base_addr + rel.r_offset) as *mut u64) += object_base_addr; + (reloc::R_X86_64_JUMP_SLOT, Resolve::Lazy) if is_pie_enabled(elf) => unsafe { + *ptr += object_base_addr; }, + (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 {:?}", @@ -682,8 +696,7 @@ impl Linker { let addend = rel.r_addend.unwrap_or(0) as u64; unsafe { - *((object_base_addr + rel.r_offset) as *mut u64) = - resolved as u64 + addend; + *ptr = resolved as u64 + addend; } } @@ -696,7 +709,7 @@ impl Linker { } fn relocate(&self, obj: &Arc, object_data: &[u8], resolve: Resolve) -> Result<()> { - // Perform relocations + // Perform static relocations. let elf = Elf::parse(object_data)?; trace!("link {}", obj.name); @@ -919,23 +932,29 @@ extern "C" fn __plt_resolve_inner(obj: *const DSO, relocation_index: c_uint) -> .d_val; let name = unsafe { - CStr::from_ptr((strtab_offset + sym.st_name as u64 + obj_base as u64) as *const c_char) + 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(); - unsafe { - trace!( - "@plt: {} -> *mut {:#x}", - name.to_string_lossy(), - resolved as usize - ); + trace!( + "@plt: {} -> *mut {:#x}", + name.to_string_lossy(), + resolved as usize + ); - *((obj_base as u64 + rela.r_offset) as *mut u64) = resolved as u64; - } + 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 } diff --git a/src/ld_so/start.rs b/src/ld_so/start.rs index 257572169b..e8aab25eaf 100644 --- a/src/ld_so/start.rs +++ b/src/ld_so/start.rs @@ -191,7 +191,9 @@ pub extern "C" fn relibc_ld_so_start(sp: &'static mut Stack, ld_entry: usize) -> } // TODO: Fix memory leak, although minimal. - crate::platform::init(auxv.clone()); + unsafe { + crate::platform::init(auxv.clone()); + } let name_or_path = if is_manual { // ld.so is run directly by user and not via execve() or similar systemcall @@ -242,8 +244,8 @@ pub extern "C" fn relibc_ld_so_start(sp: &'static mut Stack, ld_entry: usize) -> tcb.linker_ptr = Box::into_raw(Box::new(Mutex::new(linker))); tcb.mspace = ALLOCATOR.get(); } - if is_manual { - eprintln!("ld.so: entry '{}': {:#x}", path, entry); - } + // if is_manual { + eprintln!("ld.so: entry '{}': {:#x}", path, entry); + // } entry } 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]]>) {} From 1612c78774c3da81665c54ead85b76f4d3b34903 Mon Sep 17 00:00:00 2001 From: Anhad Singh Date: Fri, 20 Dec 2024 14:56:01 +1100 Subject: [PATCH 13/15] fix(ld.so): disable lazy binding on non-x86_64 Signed-off-by: Anhad Singh --- src/ld_so/linker.rs | 10 +++++++++- src/ld_so/start.rs | 6 +++--- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/src/ld_so/linker.rs b/src/ld_so/linker.rs index 9105271df9..e19a3bcf70 100644 --- a/src/ld_so/linker.rs +++ b/src/ld_so/linker.rs @@ -82,10 +82,11 @@ 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). - #[default] + #[cfg_attr(target_arch = "x86_64", default)] Lazy, } @@ -350,6 +351,13 @@ impl Linker { 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(); diff --git a/src/ld_so/start.rs b/src/ld_so/start.rs index e8aab25eaf..661e4273dd 100644 --- a/src/ld_so/start.rs +++ b/src/ld_so/start.rs @@ -244,8 +244,8 @@ pub extern "C" fn relibc_ld_so_start(sp: &'static mut Stack, ld_entry: usize) -> tcb.linker_ptr = Box::into_raw(Box::new(Mutex::new(linker))); tcb.mspace = ALLOCATOR.get(); } - // if is_manual { - eprintln!("ld.so: entry '{}': {:#x}", path, entry); - // } + if is_manual { + eprintln!("ld.so: entry '{}': {:#x}", path, entry); + } entry } From 2be4316aeced8a6a884afcccfebab6a65106b4d7 Mon Sep 17 00:00:00 2001 From: Anhad Singh Date: Wed, 25 Dec 2024 16:08:03 +1100 Subject: [PATCH 14/15] feat(dlfcn): return DSO as the handle Fine because: > The value of this handle should not be interpreted in any way by the > caller. From: https://pubs.opengroup.org/onlinepubs/009696599/functions/dlopen.html * Add tests for unload * Implement NOLOAD so we can test that the loaded object has been destroyed. * Minor cleanup. Signed-off-by: Anhad Singh --- src/header/dl-tls/mod.rs | 8 +- src/header/dlfcn/mod.rs | 39 +++- src/ld_so/callbacks.rs | 22 +- src/ld_so/dso.rs | 39 +++- src/ld_so/linker.rs | 431 +++++++++++++++++++++++++-------------- src/ld_so/mod.rs | 4 +- src/ld_so/tcb.rs | 8 +- tests/dlfcn.c | 71 ++++--- tests/libfoo.c | 1 - tests/libfoobar.c | 1 - 10 files changed, 400 insertions(+), 224 deletions(-) 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 d8bd4f06fd..324b3ba303 100644 --- a/src/header/dlfcn/mod.rs +++ b/src/header/dlfcn/mod.rs @@ -3,14 +3,17 @@ #![deny(unsafe_op_in_unsafe_fn)] use core::{ - ptr, str, + ptr::{self, NonNull}, + str, sync::atomic::{AtomicUsize, Ordering}, }; +use alloc::sync::Arc; + use crate::{ c_str::CStr, ld_so::{ - linker::{ObjectScope, Resolve}, + linker::{ObjectHandle, ObjectScope, Resolve}, tcb::Tcb, }, platform::types::*, @@ -18,7 +21,10 @@ use crate::{ pub const RTLD_LAZY: c_int = 1 << 0; pub const RTLD_NOW: c_int = 1 << 1; +// FIXME(andypython): +// #ifdef _GNU_SOURCE pub const RTLD_NOLOAD: c_int = 1 << 2; +// #endif pub const RTLD_GLOBAL: c_int = 1 << 8; pub const RTLD_LOCAL: c_int = 0x0000; @@ -62,6 +68,8 @@ pub unsafe extern "C" fn dlopen(cfilename: *const c_char, flags: c_int) -> *mut ObjectScope::Local }; + let noload = flags & RTLD_NOLOAD == RTLD_NOLOAD; + let filename = if cfilename.is_null() { None } else { @@ -88,19 +96,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, resolve, scope) { - 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(); @@ -108,6 +116,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 => { @@ -124,7 +135,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); @@ -147,10 +158,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 e9d804bcdc..64dc41dc08 100644 --- a/src/ld_so/callbacks.rs +++ b/src/ld_so/callbacks.rs @@ -1,12 +1,13 @@ -use super::linker::{Linker, ObjectScope, Resolve}; +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, Resolve, ObjectScope) -> 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,8 +20,8 @@ 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( @@ -28,10 +29,11 @@ fn load_library( name: Option<&str>, resolve: Resolve, scope: ObjectScope, -) -> Result { - linker.load_library(name, resolve, scope) + 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 f8159fe3c7..c2cc36790a 100644 --- a/src/ld_so/dso.rs +++ b/src/ld_so/dso.rs @@ -16,8 +16,8 @@ use alloc::{ }; use core::{ mem::{size_of, transmute}, - ptr, slice, - sync::atomic::AtomicUsize, + ptr::{self, NonNull}, + slice, }; #[cfg(target_pointer_width = "32")] use goblin::elf32::{ @@ -37,6 +37,7 @@ use goblin::elf64::{ }; use goblin::{ elf::{ + dynamic::DT_PLTGOT, sym::{STB_GLOBAL, STB_WEAK}, Dynamic, Elf, }, @@ -80,7 +81,6 @@ pub struct DSO { pub fini_array: (usize, usize), pub tls_module_id: usize, pub tls_offset: usize, - pub use_count: AtomicUsize, pub dynamic: Option, pub dynsyms: Vec, @@ -121,7 +121,6 @@ impl DSO { let dso = DSO { name, id, - use_count: AtomicUsize::new(1), dlopened, entry_point, runpath: DSO::get_runpath(&path, &elf)?, @@ -142,12 +141,40 @@ impl DSO { dynamic: elf.dynamic.map(|dynamic| dynamic), dynsyms: elf.dynsyms.iter().collect(), - scope: Scope::new(), + scope: Scope::local(), }; Ok((dso, tcb_master)) } + /// 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, SymbolBinding::Global)) @@ -287,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 { diff --git a/src/ld_so/linker.rs b/src/ld_so/linker.rs index e19a3bcf70..1ac0b59e92 100644 --- a/src/ld_so/linker.rs +++ b/src/ld_so/linker.rs @@ -5,14 +5,13 @@ use alloc::{ sync::{Arc, Weak}, vec::Vec, }; -use core::{cell::RefCell, mem::transmute, ptr, sync::atomic::Ordering}; +use core::{ + cell::RefCell, + mem::transmute, + ptr::{self, NonNull}, +}; use goblin::{ - elf::{ - dynamic::{DT_PLTGOT, DT_STRTAB}, - program_header, reloc, - sym::STT_TLS, - Elf, - }, + elf::{dynamic::DT_STRTAB, program_header, reloc, sym::STT_TLS, Elf}, error::{Error, Result}, }; @@ -42,8 +41,15 @@ use super::{ 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::new()); +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. @@ -96,47 +102,143 @@ pub enum ObjectScope { Local, } -pub struct Scope { - objs: Vec>, +pub enum Scope { + Global { + objs: Vec>, + }, + Local { + owner: Option>, + objs: Vec>, + }, } impl Scope { #[inline] - pub const fn new() -> Self { - Self { objs: Vec::new() } + const fn global() -> Self { + Self::Global { objs: Vec::new() } } - pub fn add(&mut self, obj: Weak) { - for dso in self.objs.iter() { - if dso.ptr_eq(&obj) { - // Already in scope. - return; + #[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); } } - - self.objs.push(obj); } - pub fn get_sym(&self, name: &str) -> Option<(Symbol, SymbolBinding, Arc)> { - let mut res = None; - - for obj in self.objs.iter() { - let obj = obj.upgrade().expect("in scope object was dropped"); - if let Some((sym, binding)) = obj.get_sym(name) { - if binding.is_global() { - return Some((sym, binding, 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; + } } - res = Some((sym, binding, obj)); + objs.push(target); + } + + Self::Local { objs, .. } => { + for obj in objs.iter() { + if Arc::ptr_eq(obj, &target) { + return; + } + } + + objs.push(target.clone()); } } - - res } - pub fn move_into(&self, other: &mut Self) { + 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. - other.objs.extend(self.objs.iter().cloned()); + 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 } } } @@ -220,7 +322,7 @@ impl Linker { } pub fn load_program(&mut self, path: &str, base_addr: Option) -> Result { - self.load_object( + let dso = self.load_object( path, &None, base_addr, @@ -232,9 +334,7 @@ impl Linker { }, ObjectScope::Global, )?; - // TODO(andypython): make self.load_object() return a reference to the - // loaded object, thereby remove the ugly unwrap(). - Ok(self.objects.get(&ROOT_ID).unwrap().entry_point) + Ok(dso.entry_point) } pub fn load_library( @@ -242,36 +342,47 @@ impl Linker { name: Option<&str>, resolve: Resolve, scope: ObjectScope, - ) -> Result { + 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.fetch_add(1, Ordering::SeqCst); // We may be upgrading the object from a local scope to the // global scope. - // - // TODO(andypython): Only do it if RTLD_NOLOAD. Otherwise, - // we should reload rather than just promoting. 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(); - global_scope.add(Arc::downgrade(obj)); obj.scope.move_into(&mut global_scope); } - Ok(*id) - } else { + Ok(ObjectHandle::new(obj.clone())) + } else if !noload { let parent_runpath = &self .objects .get(&ROOT_ID) .and_then(|parent| parent.runpath.clone()); - let lib_id = self.next_object_id; - self.load_object( + + Ok(ObjectHandle::new(self.load_object( name, parent_runpath, None, @@ -282,24 +393,34 @@ impl Linker { resolve }, scope, - )?; - - Ok(lib_id) + )?)) + } else { + // FIXME: LoadError? + // Err(Error::Malformed(format!( + // "object '{}' has not yet been loaded", + // name + // ))) + Ok(ObjectHandle(ptr::null())) } } - None => Ok(ROOT_ID), + None => Ok(ObjectHandle::new( + self.objects + .get(&ROOT_ID) + .expect("root object missing") + .clone(), + )), } } - pub fn get_sym(&self, lib_id: usize, name: &str) -> Option<*mut c_void> { + pub fn get_sym(&self, handle: Option, name: &str) -> Option<*mut c_void> { let guard; - if lib_id == 0 { + if let Some(handle) = handle.as_ref() { + &handle.as_ref().scope + } else { guard = GLOBAL_SCOPE.lock(); &*guard - } else { - &self.objects.get(&lib_id)?.scope } .get_sym(name) .map(|(symbol, _, obj)| { @@ -307,9 +428,6 @@ impl Linker { symbol.as_ptr() } else { let mut tls_index = dl_tls_index { - // TODO(andypython): instead of an ID, why don't we pass a - // pointer to the object? Then we won't need to look it up. - // This is what we already do for __plt_resolve_trampoline. ti_module: obj.tls_module_id as u64, ti_offset: symbol.value as u64, }; @@ -319,21 +437,46 @@ impl Linker { }) } - 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.load(Ordering::SeqCst) == 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); - } else { - obj.use_count.fetch_sub(1, Ordering::SeqCst); - } - } + 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) { @@ -350,7 +493,7 @@ impl Linker { dlopened: bool, resolve: Resolve, scope: ObjectScope, - ) -> Result<()> { + ) -> Result> { let resolve = if cfg!(target_arch = "x86_64") { resolve } else { @@ -364,7 +507,7 @@ impl Linker { 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, @@ -444,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 // @@ -452,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); } } @@ -485,7 +627,7 @@ impl Linker { unsafe { _r_debug.state = RTLDState::RT_CONSISTENT }; _dl_debug_state(); - Ok(()) + Ok(loaded_dso) } fn register_object(&mut self, obj: Arc) { @@ -505,15 +647,14 @@ impl Linker { // The object that caused this object to be loaded. dependent: Option<&mut DSO>, scope: ObjectScope, - ) -> Result<()> { + ) -> 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) { - obj.use_count.fetch_add(1, Ordering::SeqCst); - return Ok(()); + return Ok(obj.clone()); } } else if let Some(obj) = new_objects.iter_mut().find(|o| o.name == name) { - obj.use_count.fetch_add(1, Ordering::SeqCst); - return Ok(()); + return Ok(obj.clone()); } let path = self.search_object(name, parent_runpath)?; @@ -565,23 +706,23 @@ impl Linker { } let obj = Arc::new_cyclic(|sref| { - obj.scope.add(sref.clone()); + obj.scope.set_owner(sref.clone()); obj }); if let Some(dependent) = dependent { match scope { - ObjectScope::Local => dependent.scope.add(Arc::downgrade(&obj)), - ObjectScope::Global => GLOBAL_SCOPE.lock().add(Arc::downgrade(&obj)), + ObjectScope::Local => dependent.scope.add(&obj), + ObjectScope::Global => GLOBAL_SCOPE.lock().add(&obj), } } else if let ObjectScope::Global = scope { - GLOBAL_SCOPE.lock().add(Arc::downgrade(&obj)); + GLOBAL_SCOPE.lock().add(&obj); } objects_data.push(data); - new_objects.push(obj); + new_objects.push(obj.clone()); - Ok(()) + Ok(obj) } fn search_object(&self, name: &str, parent_runpath: &Option) -> Result { @@ -637,79 +778,59 @@ impl Linker { /// Perform lazy relocations. fn lazy_relocate(&self, obj: &Arc, elf: &Elf, resolve: Resolve) -> Result<()> { - if let Some(dynamic) = elf.dynamic.as_ref() { - let object_base_addr = obj.mmap.as_ptr() as u64; + let Some(got) = obj.got() else { return Ok(()) }; + let object_base_addr = obj.mmap.as_ptr() as u64; - // Global Offset Table - 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 is_pie_enabled(&elf) { - (object_base_addr + ptr) as *mut usize - } else { - ptr as *mut usize - } + unsafe { + got.add(1).write(Arc::as_ptr(obj) as usize); + got.add(2).write(__plt_resolve_trampoline as usize); + } + + for rel in elf.pltrelocs.iter() { + let ptr = if is_pie_enabled(elf) { + (object_base_addr + rel.r_offset) as *mut u64 } else { - assert_eq!(dynamic.info.jmprel, 0); - return Ok(()); + rel.r_offset as *mut u64 }; - unsafe { - let obj = Arc::into_raw(obj.clone()); - got.add(1).write(obj as usize); - got.add(2).write(__plt_resolve_trampoline 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; + }, - 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 - }; - - match (rel.r_type, resolve) { - (reloc::R_X86_64_JUMP_SLOT, Resolve::Lazy) if is_pie_enabled(elf) => unsafe { - *ptr += object_base_addr; - }, - - (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 name = - elf.dynstrtab - .get_at(sym.st_name) - .ok_or(Error::Malformed(format!( - "missing name for symbol {:?}", - sym - )))?; - - let resolved = GLOBAL_SCOPE - .lock() - .get_sym(name) - .or_else(|| obj.scope.get_sym(name)) - .map(|(sym, _, _)| sym.as_ptr()) - .expect("unresolved symbol"); - - let addend = rel.r_addend.unwrap_or(0) as u64; - - unsafe { - *ptr = resolved as u64 + addend; - } - } - - _ => todo!("unsupported relocation type {:?}", rel.r_type), + (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 name = + elf.dynstrtab + .get_at(sym.st_name) + .ok_or(Error::Malformed(format!( + "missing name for symbol {:?}", + sym + )))?; + + let resolved = GLOBAL_SCOPE + .lock() + .get_sym(name) + .or_else(|| obj.scope.get_sym(name)) + .map(|(sym, _, _)| sym.as_ptr()) + .expect("unresolved symbol"); + + let addend = rel.r_addend.unwrap_or(0) as u64; + + unsafe { + *ptr = resolved as u64 + addend; + } + } + + _ => todo!("unsupported relocation type {:?}", rel.r_type), } } diff --git a/src/ld_so/mod.rs b/src/ld_so/mod.rs index 334793e19e..73eca11249 100644 --- a/src/ld_so/mod.rs +++ b/src/ld_so/mod.rs @@ -77,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 { @@ -174,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/tcb.rs b/src/ld_so/tcb.rs index ef3c17ea45..48fd756a0e 100644 --- a/src/ld_so/tcb.rs +++ b/src/ld_so/tcb.rs @@ -220,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(); @@ -229,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 [] } } 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/libfoo.c b/tests/libfoo.c index 3bc4cbd8bc..b2106a4ec5 100644 --- a/tests/libfoo.c +++ b/tests/libfoo.c @@ -1,4 +1,3 @@ char *FOO = "foo"; -char *BAZ = "baz"; void a() {} diff --git a/tests/libfoobar.c b/tests/libfoobar.c index c862ee0bf6..67cef36df6 100644 --- a/tests/libfoobar.c +++ b/tests/libfoobar.c @@ -1,5 +1,4 @@ char *BAR = "bar"; -extern char *BAZ; void a(); void b() { a(); } From 1ffd7187ef8ddfd3ae4975d718a27fb1ed9c863b Mon Sep 17 00:00:00 2001 From: Anhad Singh Date: Wed, 25 Dec 2024 19:27:31 +1100 Subject: [PATCH 15/15] feat(dlfcn): define RTLD_DEFAULT Signed-off-by: Anhad Singh --- src/header/dlfcn/mod.rs | 5 ++-- tests/dlopen_scopes.c | 51 +++++++++++++++++++---------------------- 2 files changed, 25 insertions(+), 31 deletions(-) diff --git a/src/header/dlfcn/mod.rs b/src/header/dlfcn/mod.rs index 324b3ba303..e1230c7dcd 100644 --- a/src/header/dlfcn/mod.rs +++ b/src/header/dlfcn/mod.rs @@ -21,13 +21,12 @@ use crate::{ pub const RTLD_LAZY: c_int = 1 << 0; pub const RTLD_NOW: c_int = 1 << 1; -// FIXME(andypython): -// #ifdef _GNU_SOURCE pub const RTLD_NOLOAD: c_int = 1 << 2; -// #endif 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] diff --git a/tests/dlopen_scopes.c b/tests/dlopen_scopes.c index d639bfba53..3d8de7d74c 100644 --- a/tests/dlopen_scopes.c +++ b/tests/dlopen_scopes.c @@ -4,40 +4,35 @@ #include #include -// FIXME(andpython): should be defined in dlfcn.h -#ifndef RTLD_DEFAULT -#define RTLD_DEFAULT NULL -#endif - int main(void) { - void *handle = dlopen("libfoobar.so", RTLD_LAZY | RTLD_LOCAL); - if (!handle) { - printf("dlopen(libfoobar.so): %s\n", dlerror()); - return EXIT_FAILURE; - } + 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); + 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; - } + 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(dlsym(self, "FOO") == NULL); + assert(dlsym(self, "BAR") == NULL); - assert(dlclose(self) == 0); + assert(dlclose(self) == 0); - // Promote the library to the global scope. - assert(dlopen("libfoobar.so", /* RTLD_NOLOAD |*/ RTLD_NOW | RTLD_GLOBAL)); + // 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); + assert(dlsym(RTLD_DEFAULT, "FOO") != NULL); + assert(dlsym(RTLD_DEFAULT, "BAR") != NULL); - return EXIT_SUCCESS; + return EXIT_SUCCESS; }