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:
Anhad Singh
2024-12-25 16:08:03 +11:00
parent 1612c78774
commit 2be4316aec
10 changed files with 400 additions and 224 deletions
+4 -4
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -1,4 +1,3 @@
char *FOO = "foo";
char *BAZ = "baz";
void a() {}
-1
View File
@@ -1,5 +1,4 @@
char *BAR = "bar";
extern char *BAZ;
void a();
void b() { a(); }