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 <andypython@protonmail.com>
This commit is contained in:
@@ -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!(
|
||||
|
||||
+28
-11
@@ -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
|
||||
}
|
||||
|
||||
|
||||
+12
-10
@@ -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<dyn Fn(&mut Linker, usize)>,
|
||||
pub load_library: Box<dyn Fn(&mut Linker, Option<&str>, Resolve, ObjectScope) -> Result<usize>>,
|
||||
pub get_sym: Box<dyn Fn(&Linker, usize, &str) -> Option<*mut c_void>>,
|
||||
pub unload: Box<dyn Fn(&mut Linker, ObjectHandle)>,
|
||||
pub load_library:
|
||||
Box<dyn Fn(&mut Linker, Option<&str>, Resolve, ObjectScope, bool) -> Result<ObjectHandle>>,
|
||||
pub get_sym: Box<dyn Fn(&Linker, Option<ObjectHandle>, &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<usize> {
|
||||
linker.load_library(name, resolve, scope)
|
||||
noload: bool,
|
||||
) -> Result<ObjectHandle> {
|
||||
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<ObjectHandle>, name: &str) -> Option<*mut c_void> {
|
||||
linker.get_sym(handle, name)
|
||||
}
|
||||
|
||||
+33
-6
@@ -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<Dynamic>,
|
||||
pub dynsyms: Vec<goblin::elf::sym::Sym>,
|
||||
@@ -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<NonNull<usize>> {
|
||||
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 {
|
||||
|
||||
+276
-155
@@ -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<Scope> = Mutex::new(Scope::new());
|
||||
static GLOBAL_SCOPE: Mutex<Scope> = 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<Weak<DSO>>,
|
||||
pub enum Scope {
|
||||
Global {
|
||||
objs: Vec<Weak<DSO>>,
|
||||
},
|
||||
Local {
|
||||
owner: Option<Weak<DSO>>,
|
||||
objs: Vec<Arc<DSO>>,
|
||||
},
|
||||
}
|
||||
|
||||
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<DSO>) {
|
||||
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<DSO>) {
|
||||
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<DSO>)> {
|
||||
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<DSO>) {
|
||||
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<DSO>)> {
|
||||
let mut res = None;
|
||||
|
||||
let get_sym = |obj: Arc<DSO>| {
|
||||
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<DSO>) -> Self {
|
||||
Self(Arc::into_raw(obj))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn into_inner(&self) -> Arc<DSO> {
|
||||
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<Self> {
|
||||
NonNull::new(ptr as *mut DSO).map(|ptr| Self(ptr.as_ptr()))
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRef<DSO> 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<usize>) -> Result<usize> {
|
||||
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<usize> {
|
||||
noload: bool,
|
||||
) -> Result<ObjectHandle> {
|
||||
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<ObjectHandle>, 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<Arc<DSO>> {
|
||||
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::<u8>().sub(obj.tls_offset);
|
||||
tcb.dtv_mut()[dtv_idx] = tcb_ptr.cast::<u8>().sub(obj.tls_offset);
|
||||
} else {
|
||||
// FIMXE(andypython): Make it above the TP
|
||||
//
|
||||
@@ -452,8 +595,7 @@ impl Linker {
|
||||
// tcb_ptr.add(1).cast::<u8>().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<DSO>) {
|
||||
@@ -505,15 +647,14 @@ impl Linker {
|
||||
// The object that caused this object to be loaded.
|
||||
dependent: Option<&mut DSO>,
|
||||
scope: ObjectScope,
|
||||
) -> Result<()> {
|
||||
) -> Result<Arc<DSO>> {
|
||||
// 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<String>) -> Result<String> {
|
||||
@@ -637,79 +778,59 @@ impl Linker {
|
||||
|
||||
/// Perform lazy relocations.
|
||||
fn lazy_relocate(&self, obj: &Arc<DSO>, 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),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+2
-2
@@ -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();
|
||||
}
|
||||
|
||||
+4
-4
@@ -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 []
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+41
-30
@@ -1,18 +1,16 @@
|
||||
#include <assert.h>
|
||||
#include <dlfcn.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <dlfcn.h>
|
||||
|
||||
#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();
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
char *FOO = "foo";
|
||||
char *BAZ = "baz";
|
||||
|
||||
void a() {}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
char *BAR = "bar";
|
||||
extern char *BAZ;
|
||||
|
||||
void a();
|
||||
void b() { a(); }
|
||||
|
||||
Reference in New Issue
Block a user