aae27c5e0c
Signed-off-by: Anhad Singh <andypython@protonmail.com>
1031 lines
29 KiB
Rust
1031 lines
29 KiB
Rust
use alloc::{
|
|
collections::BTreeMap,
|
|
rc::Rc,
|
|
string::{String, ToString},
|
|
sync::{Arc, Weak},
|
|
vec::Vec,
|
|
};
|
|
use object::{
|
|
elf,
|
|
read::elf::{Rela as _, Sym},
|
|
NativeEndian,
|
|
};
|
|
|
|
use core::{
|
|
cell::RefCell,
|
|
ptr::{self, NonNull},
|
|
};
|
|
|
|
use crate::{
|
|
c_str::{CStr, CString},
|
|
error::Errno,
|
|
header::{
|
|
dl_tls::{__tls_get_addr, dl_tls_index},
|
|
fcntl, sys_mman,
|
|
unistd::F_OK,
|
|
},
|
|
ld_so::dso::SymbolBinding,
|
|
platform::{
|
|
types::{c_int, c_uint, c_void},
|
|
Pal, Sys,
|
|
},
|
|
sync::rwlock::RwLock,
|
|
};
|
|
|
|
use super::{
|
|
access::accessible,
|
|
callbacks::LinkerCallbacks,
|
|
debug::{RTLDState, _dl_debug_state, _r_debug},
|
|
dso::{ProgramHeader, Rela, DSO},
|
|
tcb::{Master, Tcb},
|
|
PATH_SEP,
|
|
};
|
|
|
|
#[derive(Debug, Copy, Clone)]
|
|
pub enum DlError {
|
|
/// Failed to locate the requested DSO.
|
|
NotFound,
|
|
/// The DSO is malformed somehow.
|
|
Malformed,
|
|
/// Invalid DSO handle.
|
|
InvalidHandle,
|
|
/// Out of memory.
|
|
Oom,
|
|
}
|
|
|
|
impl DlError {
|
|
/// Returns a human-readable, null-terminated C string describing the error.
|
|
pub fn repr(&self) -> CStr<'static> {
|
|
match self {
|
|
DlError::NotFound => c_str!(
|
|
"Failed to locate the requested DSO. Set `LD_DEBUG=all` for more information."
|
|
),
|
|
|
|
DlError::Malformed => {
|
|
c_str!("The DSO is malformed somehow. Set `LD_DEBUG=all` for more information.")
|
|
}
|
|
|
|
DlError::InvalidHandle => {
|
|
c_str!("Invalid DSO handle. Set `LD_DEBUG=all` for more information.")
|
|
}
|
|
|
|
DlError::Oom => c_str!("Out of memory."),
|
|
}
|
|
}
|
|
}
|
|
|
|
pub type Result<T> = core::result::Result<T, DlError>;
|
|
|
|
pub(super) static GLOBAL_SCOPE: RwLock<Scope> = RwLock::new(Scope::global());
|
|
|
|
struct MmapFile {
|
|
fd: i32,
|
|
ptr: *mut c_void,
|
|
size: usize,
|
|
}
|
|
|
|
impl MmapFile {
|
|
fn open(path: CStr, oflag: c_int) -> core::result::Result<Self, Errno> {
|
|
let fd = Sys::open(path, oflag, 0 /* mode */)?;
|
|
let mut stat = crate::header::sys_stat::stat::default();
|
|
unsafe {
|
|
Sys::fstat(fd, &mut stat)?;
|
|
}
|
|
|
|
let size = stat.st_size as usize;
|
|
let ptr = unsafe {
|
|
Sys::mmap(
|
|
ptr::null_mut(),
|
|
size,
|
|
sys_mman::PROT_READ,
|
|
sys_mman::MAP_PRIVATE,
|
|
fd,
|
|
0,
|
|
)
|
|
}?;
|
|
|
|
Ok(Self { fd, ptr, size })
|
|
}
|
|
|
|
fn data(&self) -> &[u8] {
|
|
unsafe { core::slice::from_raw_parts(self.ptr.cast::<u8>(), self.size) }
|
|
}
|
|
}
|
|
|
|
impl Drop for MmapFile {
|
|
fn drop(&mut self) {
|
|
unsafe {
|
|
Sys::munmap(self.ptr, self.size).unwrap();
|
|
Sys::close(self.fd).unwrap();
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, Copy, Debug)]
|
|
pub struct Symbol {
|
|
pub value: usize,
|
|
pub base: usize,
|
|
pub size: usize,
|
|
pub sym_type: u8,
|
|
}
|
|
|
|
impl Symbol {
|
|
pub fn as_ptr(self) -> *mut c_void {
|
|
(self.base + self.value) as *mut c_void
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Default, Copy, Clone, PartialEq)]
|
|
pub enum Resolve {
|
|
/// Resolve all undefined symbols immediately.
|
|
#[cfg_attr(not(target_arch = "x86_64"), default)]
|
|
Now,
|
|
/// Perform lazy binding (i.e. symbols will be resolved when they are first
|
|
/// used).
|
|
#[cfg_attr(target_arch = "x86_64", default)]
|
|
Lazy,
|
|
}
|
|
|
|
#[derive(Debug, Copy, Clone, PartialEq)]
|
|
pub enum ObjectScope {
|
|
Global,
|
|
Local,
|
|
}
|
|
|
|
pub enum Scope {
|
|
Global {
|
|
objs: Vec<Weak<DSO>>,
|
|
},
|
|
Local {
|
|
owner: Option<Weak<DSO>>,
|
|
objs: Vec<Arc<DSO>>,
|
|
},
|
|
}
|
|
|
|
impl Scope {
|
|
#[inline]
|
|
const fn global() -> Self {
|
|
Self::Global { objs: Vec::new() }
|
|
}
|
|
|
|
#[inline]
|
|
pub(super) const fn local() -> Self {
|
|
Self::Local {
|
|
owner: None,
|
|
objs: Vec::new(),
|
|
}
|
|
}
|
|
|
|
fn set_owner(&mut self, obj: Weak<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);
|
|
}
|
|
}
|
|
}
|
|
|
|
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;
|
|
}
|
|
}
|
|
|
|
objs.push(target);
|
|
}
|
|
|
|
Self::Local { objs, .. } => {
|
|
for obj in objs.iter() {
|
|
if Arc::ptr_eq(obj, target) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
objs.push(target.clone());
|
|
}
|
|
}
|
|
}
|
|
|
|
pub(super) 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(res)
|
|
}
|
|
|
|
fn move_into(&self, other: &mut Self) {
|
|
// FIXME: move not copy? as afiak u cannot downgrade from a global to a local scope.
|
|
match (self, other) {
|
|
(Self::Local { owner, objs }, Self::Global { objs: other_objs }) => {
|
|
let owner = owner.as_ref().expect("local scope without owner");
|
|
other_objs.push(owner.clone());
|
|
other_objs.extend(objs.iter().map(Arc::downgrade));
|
|
}
|
|
|
|
_ => 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 }
|
|
}
|
|
}
|
|
|
|
bitflags::bitflags! {
|
|
#[derive(Debug, Default)]
|
|
pub struct DebugFlags: u32 {
|
|
/// Display what objects and where they are being loaded.
|
|
const LOAD = 1 << 1;
|
|
/// Display library search paths.
|
|
const SEARCH = 1 << 2;
|
|
/// Display scope information.
|
|
const SCOPES = 1 << 3;
|
|
}
|
|
}
|
|
|
|
#[derive(Default)]
|
|
pub struct Config {
|
|
debug_flags: DebugFlags,
|
|
library_path: Option<String>,
|
|
/// Resolve symbols at program startup.
|
|
bind_now: bool,
|
|
}
|
|
|
|
impl Config {
|
|
pub fn from_env(env: &BTreeMap<String, String>) -> Self {
|
|
let debug_flags = env
|
|
.get("LD_DEBUG")
|
|
.map(|value| {
|
|
let mut flags = DebugFlags::empty();
|
|
for opt in value.split(',') {
|
|
flags |= match opt {
|
|
"load" => DebugFlags::LOAD,
|
|
"search" => DebugFlags::SEARCH,
|
|
"scopes" => DebugFlags::SCOPES,
|
|
"all" => DebugFlags::all(),
|
|
_ => {
|
|
eprintln!("[ld.so]: unknown debug flag '{}'", opt);
|
|
DebugFlags::empty()
|
|
}
|
|
};
|
|
}
|
|
|
|
flags
|
|
})
|
|
.unwrap_or(DebugFlags::empty());
|
|
|
|
Self {
|
|
debug_flags,
|
|
library_path: env.get("LD_LIBRARY_PATH").cloned(),
|
|
bind_now: env
|
|
.get("LD_BIND_NOW")
|
|
.map(|value| !value.is_empty())
|
|
.unwrap_or_default(),
|
|
}
|
|
}
|
|
}
|
|
|
|
pub struct Linker {
|
|
config: Config,
|
|
|
|
next_object_id: usize,
|
|
next_tls_module_id: usize,
|
|
tls_size: usize,
|
|
objects: BTreeMap<usize, Arc<DSO>>,
|
|
name_to_object_id_map: BTreeMap<String, usize>,
|
|
pub cbs: Rc<RefCell<LinkerCallbacks>>,
|
|
}
|
|
|
|
const ROOT_ID: usize = 1;
|
|
|
|
impl Linker {
|
|
pub fn new(config: Config) -> Self {
|
|
Self {
|
|
config,
|
|
next_object_id: ROOT_ID,
|
|
next_tls_module_id: 1,
|
|
tls_size: 0,
|
|
objects: BTreeMap::new(),
|
|
name_to_object_id_map: BTreeMap::new(),
|
|
cbs: Rc::new(RefCell::new(LinkerCallbacks::new())),
|
|
}
|
|
}
|
|
|
|
pub fn load_program(&mut self, path: &str, base_addr: Option<usize>) -> Result<usize> {
|
|
let dso = self.load_object(
|
|
path,
|
|
&None,
|
|
base_addr,
|
|
false,
|
|
if self.config.bind_now {
|
|
Resolve::Now
|
|
} else {
|
|
Resolve::default()
|
|
},
|
|
ObjectScope::Global,
|
|
)?;
|
|
Ok(dso.entry_point)
|
|
}
|
|
|
|
pub fn load_library(
|
|
&mut self,
|
|
name: Option<&str>,
|
|
resolve: Resolve,
|
|
scope: ObjectScope,
|
|
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();
|
|
|
|
// We may be upgrading the object from a local scope to the
|
|
// global scope.
|
|
if scope == ObjectScope::Global {
|
|
if self.config.debug_flags.contains(DebugFlags::SCOPES) {
|
|
eprintln!("[ld.so]: moving {} into the global scope", obj.name);
|
|
}
|
|
|
|
let mut global_scope = GLOBAL_SCOPE.write();
|
|
obj.scope.move_into(&mut global_scope);
|
|
}
|
|
|
|
Ok(ObjectHandle::new(obj.clone()))
|
|
} else if !noload {
|
|
let parent_runpath = &self
|
|
.objects
|
|
.get(&ROOT_ID)
|
|
.and_then(|parent| parent.runpath().map(|path| path.to_string()));
|
|
|
|
Ok(ObjectHandle::new(self.load_object(
|
|
name,
|
|
parent_runpath,
|
|
None,
|
|
true,
|
|
if self.config.bind_now {
|
|
Resolve::Now
|
|
} else {
|
|
resolve
|
|
},
|
|
scope,
|
|
)?))
|
|
} else {
|
|
// FIXME: LoadError?
|
|
// Err(Error::Malformed(format!(
|
|
// "object '{}' has not yet been loaded",
|
|
// name
|
|
// )))
|
|
Ok(ObjectHandle(ptr::null()))
|
|
}
|
|
}
|
|
|
|
None => Ok(ObjectHandle::new(
|
|
self.objects
|
|
.get(&ROOT_ID)
|
|
.expect("root object missing")
|
|
.clone(),
|
|
)),
|
|
}
|
|
}
|
|
|
|
pub fn get_sym(&self, handle: Option<ObjectHandle>, name: &str) -> Option<*mut c_void> {
|
|
let guard;
|
|
|
|
if let Some(handle) = handle.as_ref() {
|
|
&handle.as_ref().scope
|
|
} else {
|
|
guard = GLOBAL_SCOPE.read();
|
|
&guard
|
|
}
|
|
.get_sym(name)
|
|
.map(|(symbol, _, obj)| {
|
|
if symbol.sym_type != elf::STT_TLS {
|
|
symbol.as_ptr()
|
|
} else {
|
|
let mut tls_index = dl_tls_index {
|
|
ti_module: obj.tls_module_id as u64,
|
|
ti_offset: symbol.value as u64,
|
|
};
|
|
|
|
unsafe { __tls_get_addr(&mut tls_index) }
|
|
}
|
|
})
|
|
}
|
|
|
|
pub fn unload(&mut self, handle: ObjectHandle) {
|
|
let obj = handle.into_inner();
|
|
if !obj.dlopened {
|
|
return;
|
|
}
|
|
|
|
trace!(
|
|
"[ld.so] unloading {} (sc={}, wc={})",
|
|
obj.name,
|
|
Arc::strong_count(&obj),
|
|
Arc::weak_count(&obj)
|
|
);
|
|
|
|
// One for the reference we have and the other for the one in the
|
|
// objects map.
|
|
if Arc::strong_count(&obj) == 2 {
|
|
// Remove from the global scope.
|
|
match *GLOBAL_SCOPE.write() {
|
|
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() {
|
|
self.unload(ObjectHandle::new(
|
|
self.objects
|
|
.get(self.name_to_object_id_map.get(*dep).unwrap())
|
|
.unwrap()
|
|
.clone(),
|
|
));
|
|
}
|
|
self.name_to_object_id_map.remove(&obj.name);
|
|
assert!(Arc::strong_count(&obj) == 1);
|
|
drop(obj);
|
|
}
|
|
|
|
// obj is dropped here.
|
|
}
|
|
|
|
pub fn fini(&self) {
|
|
for obj in self.objects.values() {
|
|
obj.run_fini();
|
|
}
|
|
}
|
|
|
|
fn load_object(
|
|
&mut self,
|
|
path: &str,
|
|
runpath: &Option<String>,
|
|
base_addr: Option<usize>,
|
|
dlopened: bool,
|
|
resolve: Resolve,
|
|
scope: ObjectScope,
|
|
) -> Result<Arc<DSO>> {
|
|
let resolve = if cfg!(target_arch = "x86_64") {
|
|
resolve
|
|
} else {
|
|
// Lazy binding is not currently supported on non-x86_64 architectures.
|
|
Resolve::Now
|
|
};
|
|
|
|
unsafe { _r_debug.state = RTLDState::RT_ADD };
|
|
_dl_debug_state();
|
|
|
|
let mut new_objects = Vec::new();
|
|
let mut objects_data = Vec::new();
|
|
let mut tcb_masters = Vec::new();
|
|
let loaded_dso = self.load_objects_recursive(
|
|
path,
|
|
runpath,
|
|
base_addr,
|
|
dlopened,
|
|
&mut new_objects,
|
|
&mut objects_data,
|
|
&mut tcb_masters,
|
|
None,
|
|
scope,
|
|
)?;
|
|
|
|
unsafe {
|
|
if !dlopened {
|
|
#[cfg(target_os = "redox")]
|
|
let (tcb, old_tcb) = {
|
|
use redox_rt::signal::tmp_disable_signals;
|
|
|
|
let old_tcb = Tcb::current().expect("failed to get bootstrap TCB");
|
|
let new_tcb = Tcb::new(self.tls_size)?; // This actually allocates TCB, TLS and ABI page.
|
|
|
|
// Stash
|
|
let new_tls_end = new_tcb.generic.tls_end;
|
|
let new_tls_len = new_tcb.generic.tls_len;
|
|
let new_tcb_ptr = new_tcb.generic.tcb_ptr;
|
|
let new_tcb_len = new_tcb.generic.tcb_len;
|
|
|
|
// Unmap just the TCB page.
|
|
Sys::munmap(new_tcb as *mut Tcb as *mut c_void, syscall::PAGE_SIZE).unwrap();
|
|
|
|
let new_addr = ptr::addr_of!(*new_tcb) as usize;
|
|
|
|
assert_eq!(
|
|
syscall::syscall5(
|
|
syscall::SYS_MREMAP,
|
|
old_tcb as *mut Tcb as usize,
|
|
syscall::PAGE_SIZE,
|
|
new_addr,
|
|
syscall::PAGE_SIZE,
|
|
(syscall::MremapFlags::FIXED | syscall::MremapFlags::KEEP_OLD).bits()
|
|
| (syscall::MapFlags::PROT_READ | syscall::MapFlags::PROT_WRITE)
|
|
.bits(),
|
|
)
|
|
.expect("mremap: failed to alias TCB"),
|
|
new_addr,
|
|
);
|
|
// XXX: New TCB is now at the same physical address as the old TCB.
|
|
|
|
let _guard = tmp_disable_signals();
|
|
// Restore
|
|
new_tcb.generic.tls_end = new_tls_end;
|
|
new_tcb.generic.tls_len = new_tls_len;
|
|
new_tcb.generic.tcb_ptr = new_tcb_ptr;
|
|
new_tcb.generic.tcb_len = new_tcb_len;
|
|
|
|
drop(_guard);
|
|
(new_tcb, old_tcb as *mut Tcb as *mut c_void)
|
|
};
|
|
|
|
#[cfg(not(target_os = "redox"))]
|
|
let tcb = Tcb::new(self.tls_size)?;
|
|
|
|
// We are now loading the main program or its dependencies. The TLS for all initially
|
|
// loaded objects reside in the static TLS block. Depending on the architecture, the
|
|
// static TLS block is either placed before the TP or after the TP.
|
|
let tcb_ptr = tcb as *mut Tcb;
|
|
|
|
// Setup the DTVs.
|
|
tcb.setup_dtv(tcb_masters.len());
|
|
|
|
for obj in new_objects.iter() {
|
|
if obj.tls_module_id == 0 {
|
|
// No TLS for this object.
|
|
continue;
|
|
}
|
|
|
|
let dtv_idx = obj.tls_module_id - 1;
|
|
|
|
if cfg!(any(target_arch = "x86", target_arch = "x86_64")) {
|
|
// Below the TP
|
|
tcb.dtv_mut()[dtv_idx] = tcb_ptr.cast::<u8>().sub(obj.tls_offset);
|
|
} else {
|
|
// FIMXE(andypython): Make it above the TP
|
|
//
|
|
// tcb.dtv_mut().unwrap()[obj.tls_module_id - 1] =
|
|
// 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()[dtv_idx] = tcb.tls_end.sub(tcb.tls_len).add(obj.tls_offset);
|
|
}
|
|
}
|
|
|
|
tcb.append_masters(tcb_masters);
|
|
// Copy the master data into the static TLS block.
|
|
tcb.copy_masters().map_err(|_| DlError::Malformed)?;
|
|
tcb.activate();
|
|
|
|
#[cfg(target_os = "redox")]
|
|
{
|
|
// Unmap the old TCB.
|
|
Sys::munmap(old_tcb, syscall::PAGE_SIZE).unwrap();
|
|
}
|
|
} else {
|
|
let tcb = Tcb::current().expect("failed to get current tcb");
|
|
|
|
// TLS variables for dlopen'ed objects are lazily allocated in `__tls_get_addr`.
|
|
tcb.append_masters(tcb_masters);
|
|
}
|
|
}
|
|
|
|
// new_objects is already reversed.
|
|
for (i, obj) in new_objects.into_iter().enumerate() {
|
|
obj.relocate(&objects_data[i], resolve).unwrap();
|
|
self.run_init(&obj);
|
|
self.register_object(obj);
|
|
}
|
|
|
|
unsafe { _r_debug.state = RTLDState::RT_CONSISTENT };
|
|
_dl_debug_state();
|
|
|
|
Ok(loaded_dso)
|
|
}
|
|
|
|
fn register_object(&mut self, obj: Arc<DSO>) {
|
|
self.name_to_object_id_map.insert(obj.name.clone(), obj.id);
|
|
self.objects.insert(obj.id, obj);
|
|
}
|
|
|
|
fn load_objects_recursive<'a>(
|
|
&mut self,
|
|
name: &str,
|
|
parent_runpath: &Option<String>,
|
|
base_addr: Option<usize>,
|
|
dlopened: bool,
|
|
new_objects: &mut Vec<Arc<DSO>>,
|
|
objects_data: &mut Vec<Vec<ProgramHeader>>,
|
|
tcb_masters: &mut Vec<Master>,
|
|
// The object that caused this object to be loaded.
|
|
dependent: Option<&mut DSO>,
|
|
scope: ObjectScope,
|
|
) -> 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) {
|
|
return Ok(obj.clone());
|
|
}
|
|
} else if let Some(obj) = new_objects.iter_mut().find(|o| o.name == name) {
|
|
return Ok(obj.clone());
|
|
}
|
|
|
|
let debug = self.config.debug_flags.contains(DebugFlags::LOAD);
|
|
|
|
let path = self.search_object(name, parent_runpath)?;
|
|
let file = self.read_file(&path)?;
|
|
let data = file.data();
|
|
let (mut obj, tcb_master, elf) = DSO::new(
|
|
&path,
|
|
data,
|
|
base_addr,
|
|
dlopened,
|
|
self.next_object_id,
|
|
self.next_tls_module_id,
|
|
self.tls_size,
|
|
)
|
|
.map_err(|err| {
|
|
if debug {
|
|
eprintln!("[ld.so]: failed to load '{}': {}", name, err)
|
|
}
|
|
|
|
DlError::Malformed
|
|
})?;
|
|
|
|
if debug {
|
|
eprintln!(
|
|
"[ld.so]: loading object: {} at {:#x}",
|
|
name,
|
|
obj.mmap.as_ptr() as usize
|
|
);
|
|
}
|
|
|
|
self.next_object_id += 1;
|
|
|
|
if let Some(master) = tcb_master {
|
|
if !dlopened {
|
|
self.tls_size += master.offset; // => aligned ph.p_memsz
|
|
}
|
|
|
|
tcb_masters.push(master);
|
|
self.next_tls_module_id += 1;
|
|
}
|
|
|
|
let runpath = obj.runpath().map(|rpath| rpath.to_string());
|
|
let dependencies = obj
|
|
.dependencies()
|
|
.iter()
|
|
.map(|dep| dep.to_string())
|
|
.collect::<Vec<_>>();
|
|
|
|
for dep_name in dependencies.iter() {
|
|
self.load_objects_recursive(
|
|
dep_name,
|
|
&runpath,
|
|
None,
|
|
dlopened,
|
|
new_objects,
|
|
objects_data,
|
|
tcb_masters,
|
|
Some(&mut obj),
|
|
scope,
|
|
)?;
|
|
}
|
|
|
|
let obj = Arc::new_cyclic(|sref| {
|
|
obj.scope.set_owner(sref.clone());
|
|
obj
|
|
});
|
|
|
|
if let Some(dependent) = dependent {
|
|
match scope {
|
|
ObjectScope::Local => dependent.scope.add(&obj),
|
|
ObjectScope::Global => GLOBAL_SCOPE.write().add(&obj),
|
|
}
|
|
} else if let ObjectScope::Global = scope {
|
|
GLOBAL_SCOPE.write().add(&obj);
|
|
}
|
|
|
|
objects_data.push(elf);
|
|
new_objects.push(obj.clone());
|
|
|
|
Ok(obj)
|
|
}
|
|
|
|
fn search_object(&self, name: &str, parent_runpath: &Option<String>) -> Result<String> {
|
|
let debug = self.config.debug_flags.contains(DebugFlags::SEARCH);
|
|
if debug {
|
|
eprintln!("[ld.so]: looking for '{}'", name);
|
|
}
|
|
|
|
let mut full_path = name.to_string();
|
|
if accessible(&full_path, F_OK).is_ok() {
|
|
if debug {
|
|
eprintln!("[ld.so]: found at '{}'!", full_path);
|
|
}
|
|
return Ok(full_path);
|
|
} else {
|
|
let mut search_paths = Vec::new();
|
|
if let Some(runpath) = parent_runpath {
|
|
search_paths.extend(runpath.split(PATH_SEP));
|
|
}
|
|
if let Some(ld_path) = self.config.library_path.as_ref() {
|
|
search_paths.extend(ld_path.split(PATH_SEP));
|
|
}
|
|
search_paths.push("/lib");
|
|
for part in search_paths.iter() {
|
|
full_path = format!("{}/{}", part, name);
|
|
if debug {
|
|
eprintln!("[ld.so]: trying path '{}'", full_path);
|
|
}
|
|
if accessible(&full_path, F_OK).is_ok() {
|
|
if debug {
|
|
eprintln!("[ld.so]: found at '{}'!", full_path);
|
|
}
|
|
return Ok(full_path);
|
|
}
|
|
}
|
|
}
|
|
|
|
if debug {
|
|
eprintln!("[ld.so]: failed to locate '{}'", name);
|
|
}
|
|
|
|
Err(DlError::NotFound)
|
|
}
|
|
|
|
fn read_file(&self, path: &str) -> Result<MmapFile> {
|
|
let debug = self.config.debug_flags.contains(DebugFlags::SEARCH);
|
|
|
|
let path_c = CString::new(path).map_err(|err| {
|
|
if debug {
|
|
eprintln!("[ld.so]: invalid path '{}': {}", path, err)
|
|
}
|
|
|
|
DlError::NotFound
|
|
})?;
|
|
|
|
let flags = fcntl::O_RDONLY | fcntl::O_CLOEXEC;
|
|
let file = MmapFile::open(CStr::borrow(&path_c), flags).map_err(|err| {
|
|
if debug {
|
|
eprintln!("[ld.so]: failed to open '{}': {}", path, err)
|
|
}
|
|
|
|
DlError::NotFound
|
|
})?;
|
|
|
|
Ok(file)
|
|
}
|
|
|
|
fn run_init(&self, obj: &DSO) {
|
|
use crate::platform::{self, types::*};
|
|
|
|
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();
|
|
}
|
|
}
|
|
|
|
// GOT[1] = object_id
|
|
// GOT[2] = __plt_resolve_trampoline
|
|
//
|
|
// The stubs in .plt will push the relocation index and the object pointer onto
|
|
// the stack and jump to [`__plt_resolve_trampoline`]. The trampoline will then
|
|
// call this function to resolve the symbol and update the respective GOT entry.
|
|
// The trampoline will then jump to the resolved symbol.
|
|
//
|
|
// FIXME(andypython): 32-bit
|
|
extern "C" fn __plt_resolve_inner(obj: *const DSO, relocation_index: c_uint) -> *mut c_void {
|
|
let resolve_sym = |name: &str, scopes: &[&Scope]| -> Option<Symbol> {
|
|
scopes
|
|
.iter()
|
|
.find_map(|scope| scope.get_sym(name))
|
|
.map(|(sym, _, _)| sym)
|
|
};
|
|
|
|
let obj = unsafe { &*obj };
|
|
let obj_base = obj.mmap.as_ptr() as usize;
|
|
let jmprel = obj.dynamic.jmprel;
|
|
|
|
let rela = unsafe { &*(jmprel as *const Rela).add(relocation_index as usize) };
|
|
assert_eq!(rela.r_type(NativeEndian, false), elf::R_X86_64_JUMP_SLOT);
|
|
|
|
let sym = obj
|
|
.dynamic
|
|
.symbol(rela.symbol(NativeEndian, false).unwrap())
|
|
.expect("symbol not found");
|
|
assert_ne!(sym.st_name(NativeEndian), 0);
|
|
|
|
let name = core::str::from_utf8(
|
|
obj.dynamic
|
|
.dynstrtab
|
|
.get(sym.st_name(NativeEndian))
|
|
.unwrap(),
|
|
)
|
|
.expect("non utf8 symbol name");
|
|
|
|
let resolved = resolve_sym(name, &[&GLOBAL_SCOPE.read(), &obj.scope])
|
|
.unwrap_or_else(|| panic!("symbol '{name}' not found"))
|
|
.as_ptr();
|
|
|
|
let ptr = if obj.pie {
|
|
(obj_base as u64 + rela.r_offset(NativeEndian)) as *mut u64
|
|
} else {
|
|
rela.r_offset(NativeEndian) as *mut u64
|
|
};
|
|
|
|
trace!("@plt: {} -> *mut {:p}", name, ptr);
|
|
|
|
unsafe { *ptr = resolved as u64 }
|
|
resolved
|
|
}
|
|
|
|
extern "C" {
|
|
pub(super) fn __plt_resolve_trampoline() -> usize;
|
|
}
|
|
|
|
#[cfg(target_arch = "x86_64")]
|
|
core::arch::global_asm!(
|
|
"
|
|
.global __plt_resolve_trampoline
|
|
.hidden __plt_resolve_trampoline
|
|
__plt_resolve_trampoline:
|
|
push rsi
|
|
push rdi
|
|
|
|
mov rdi, qword ptr [rsp + 0x10]
|
|
mov rsi, qword ptr [rsp + 0x18]
|
|
|
|
// stash the floating point argument registers
|
|
sub rsp, 128
|
|
movdqu [rsp + 0x00], xmm0
|
|
movdqu [rsp + 0x10], xmm1
|
|
movdqu [rsp + 0x20], xmm2
|
|
movdqu [rsp + 0x30], xmm3
|
|
movdqu [rsp + 0x40], xmm4
|
|
movdqu [rsp + 0x50], xmm5
|
|
movdqu [rsp + 0x60], xmm6
|
|
movdqu [rsp + 0x70], xmm7
|
|
|
|
push rax
|
|
push rcx
|
|
push rdx
|
|
push r8
|
|
push r9
|
|
push r10
|
|
|
|
push rbp
|
|
mov rbp, rsp
|
|
and rsp, 0xfffffffffffffff0
|
|
call {__plt_resolve_inner}
|
|
mov r11, rax
|
|
mov rsp, rbp
|
|
pop rbp
|
|
|
|
pop r10
|
|
pop r9
|
|
pop r8
|
|
pop rdx
|
|
pop rcx
|
|
pop rax
|
|
|
|
movdqu xmm7, [rsp + 0x70]
|
|
movdqu xmm6, [rsp + 0x60]
|
|
movdqu xmm5, [rsp + 0x50]
|
|
movdqu xmm4, [rsp + 0x40]
|
|
movdqu xmm3, [rsp + 0x30]
|
|
movdqu xmm2, [rsp + 0x20]
|
|
movdqu xmm1, [rsp + 0x10]
|
|
movdqu xmm0, [rsp + 0x00]
|
|
add rsp, 128
|
|
|
|
pop rdi
|
|
pop rsi
|
|
|
|
add rsp, 0x10
|
|
jmp r11
|
|
|
|
ud2
|
|
.size __plt_resolve_trampoline, . - __plt_resolve_trampoline
|
|
",
|
|
__plt_resolve_inner = sym __plt_resolve_inner
|
|
);
|
|
|
|
#[cfg(target_arch = "x86")]
|
|
core::arch::global_asm!(
|
|
"
|
|
.global __plt_resolve_trampoline
|
|
.hidden __plt_resolve_trampoline
|
|
__plt_resolve_trampoline:
|
|
ud2
|
|
.size __plt_resolve_trampoline, . - __plt_resolve_trampoline
|
|
"
|
|
);
|
|
|
|
#[cfg(target_arch = "aarch64")]
|
|
core::arch::global_asm!(
|
|
"
|
|
.global __plt_resolve_trampoline
|
|
.hidden __plt_resolve_trampoline
|
|
__plt_resolve_trampoline:
|
|
udf #0
|
|
.size __plt_resolve_trampoline, . - __plt_resolve_trampoline
|
|
"
|
|
);
|
|
|
|
#[cfg(target_arch = "riscv64")]
|
|
core::arch::global_asm!(
|
|
"
|
|
.global __plt_resolve_trampoline
|
|
.hidden __plt_resolve_trampoline
|
|
__plt_resolve_trampoline:
|
|
unimp
|
|
.size __plt_resolve_trampoline, . - __plt_resolve_trampoline
|
|
"
|
|
);
|