Files
RedBear-OS/src/ld_so/dso.rs
T
2026-05-07 00:05:07 +02:00

1312 lines
44 KiB
Rust

//! See:
//! * <https://refspecs.linuxfoundation.org/elf/elf.pdf>
//! * <https://www.akkadia.org/drepper/dsohowto.pdf>
use object::{
NativeEndian, Object, StringTable, SymbolIndex, elf,
read::elf::{
Dyn as _, GnuHashTable, HashTable as SysVHashTable, ProgramHeader as _, Rel as _,
Rela as _, Sym as _, Version, VersionTable,
},
};
#[cfg(any(target_arch = "aarch64", target_arch = "x86_64"))]
use super::tcb::Tcb;
use super::{
debug::{_r_debug, RTLDDebug},
linker::{__plt_resolve_trampoline, GLOBAL_SCOPE, Resolve, Scope, Symbol},
tcb::Master,
};
use crate::{
header::{dl_tls::__tls_get_addr, sys_mman},
platform::{Pal, Sys, types::c_void},
};
use alloc::{
boxed::Box,
string::{String, ToString},
sync::Arc,
vec::Vec,
};
#[cfg(any(target_arch = "aarch64", target_arch = "x86_64"))]
use core::mem::offset_of;
use core::{
ffi::c_char,
mem::size_of,
ptr::{self, NonNull},
slice,
sync::atomic::{AtomicBool, Ordering},
};
pub const CHAR_BITS: usize = c_char::BITS as usize;
pub type Relr = usize;
#[cfg(target_pointer_width = "32")]
mod shim {
use object::{NativeEndian, elf::*, read::elf::ElfFile32};
pub type Dyn = Dyn32<NativeEndian>;
pub type Rel = Rel32<NativeEndian>;
pub type Rela = Rela32<NativeEndian>;
pub type Sym = Sym32<NativeEndian>;
pub type FileHeader = FileHeader32<NativeEndian>;
pub type ProgramHeader = ProgramHeader32<NativeEndian>;
pub type ElfFile<'a> = ElfFile32<'a, NativeEndian>;
}
#[cfg(target_pointer_width = "64")]
mod shim {
use object::{NativeEndian, elf::*, read::elf::ElfFile64};
pub type Dyn = Dyn64<NativeEndian>;
pub type Rel = Rel64<NativeEndian>;
pub type Rela = Rela64<NativeEndian>;
pub type Sym = Sym64<NativeEndian>;
pub type FileHeader = FileHeader64<NativeEndian>;
pub type ProgramHeader = ProgramHeader64<NativeEndian>;
pub type ElfFile<'a> = ElfFile64<'a, NativeEndian>;
}
pub use shim::*;
// TODO: missing from the `object` crate
pub const DT_RELRSZ: u32 = 35;
pub const DT_RELR: u32 = 36;
pub const DT_RELRENT: u32 = 37;
/// Undefined Symbol Index
pub const STN_UNDEF: SymbolIndex = SymbolIndex(0);
enum HashTable<'a> {
Gnu(GnuHashTable<'a, FileHeader>),
Sysv(SysVHashTable<'a, FileHeader>),
}
impl<'a> HashTable<'a> {
/// Use the hash table to find the symbol table entry with the given name, hash, and version.
#[inline]
pub fn find(
&self,
name: &str,
version: Option<&Version<'_>>,
symbols: &'a [Sym],
strings: StringTable<'a>,
versions: &VersionTable<'a, FileHeader>,
) -> Option<(SymbolIndex, &'a Sym)> {
let name = name.as_bytes();
match self {
Self::Gnu(hash_table) => {
let hash = elf::gnu_hash(name);
hash_table.find(
NativeEndian,
name,
hash,
version,
symbols,
strings,
versions,
)
}
Self::Sysv(hash_table) => {
let hash = elf::hash(name);
hash_table.find(
NativeEndian,
name,
hash,
version,
symbols,
strings,
versions,
)
}
}
}
fn symbol_table_length(&self) -> usize {
match self {
Self::Gnu(hash_table) => hash_table
.symbol_table_length(NativeEndian)
.expect("empty GNU symbol hash table")
as usize,
Self::Sysv(hash_table) => hash_table.symbol_table_length() as usize,
}
}
}
type InitFn = unsafe extern "C" fn();
pub(super) struct Dynamic<'data> {
runpath: Option<String>,
got: Option<NonNull<usize>>,
needed: Vec<&'data str>,
pub(super) jmprel: usize,
hash_table: HashTable<'data>,
pub(super) dynstrtab: StringTable<'data>,
soname: Option<&'data str>,
init_array: &'data [unsafe extern "C" fn()],
fini_array: &'data [unsafe extern "C" fn()],
rela: &'data [Rela],
relr: &'data [Relr],
rel: &'data [Rel],
symbols: &'data [Sym],
explicit_addend: bool,
pltrelsz: usize,
}
impl<'data> Dynamic<'data> {
pub fn symbol(&self, index: SymbolIndex) -> Option<&'data Sym> {
// Symbol table entry for index 0 is reserved.
assert!(index != SymbolIndex(0));
self.symbols.get(index.0)
}
fn symbol_name(&self, index: SymbolIndex) -> Option<&'data str> {
let sym = self.symbol(index)?;
let name = sym.name(NativeEndian, self.dynstrtab).ok()?;
Some(core::str::from_utf8(name).expect("non UTF-8 ELF symbol name"))
}
fn static_relocations(&self) -> impl Iterator<Item = Relocation> + '_ {
self.rela
.iter()
.map(Relocation::from)
.chain(self.rel.iter().map(Relocation::from))
}
}
unsafe impl Send for Dynamic<'_> {}
unsafe impl Sync for Dynamic<'_> {}
#[derive(Debug)]
pub(super) struct Relocation {
pub(super) offset: usize,
pub(super) addend: Option<usize>,
pub(super) sym: SymbolIndex,
pub(super) kind: RelocationKind,
}
#[cfg(target_pointer_width = "32")]
impl From<&Rela> for Relocation {
fn from(reloc: &Rela) -> Self {
Self {
offset: reloc.r_offset(NativeEndian) as usize,
addend: Some(reloc.r_addend(NativeEndian) as usize),
sym: SymbolIndex(reloc.r_sym(NativeEndian) as usize),
kind: RelocationKind::new(reloc.r_type(NativeEndian)),
}
}
}
#[cfg(target_pointer_width = "64")]
impl From<&Rela> for Relocation {
fn from(reloc: &Rela) -> Self {
let is_mips64el = cfg!(all(target_arch = "mips64", target_endian = "little"));
Self {
offset: reloc.r_offset(NativeEndian) as usize,
addend: Some(reloc.r_addend(NativeEndian) as usize),
sym: SymbolIndex(reloc.r_sym(NativeEndian, is_mips64el) as usize),
kind: RelocationKind::new(reloc.r_type(NativeEndian, is_mips64el)),
}
}
}
impl From<&Rel> for Relocation {
fn from(reloc: &Rel) -> Self {
Self {
offset: reloc.r_offset(NativeEndian) as usize,
addend: None,
sym: SymbolIndex(reloc.r_sym(NativeEndian) as usize),
kind: RelocationKind::new(reloc.r_type(NativeEndian)),
}
}
}
// This is matched up to REL_* constants used by musl for ease of comparison
#[allow(dead_code)]
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum RelocationKind {
COPY,
DTPMOD,
DTPOFF,
GOT,
IRELATIVE,
OFFSET,
PLT,
RELATIVE,
SYMBOLIC,
TLSDESC,
TPOFF,
UNKNOWN(u32),
}
impl RelocationKind {
#[cfg(target_arch = "aarch64")]
pub fn new(kind: u32) -> Self {
//WARNING: Only use R_AARCH64_* constants here!
match kind {
elf::R_AARCH64_COPY => Self::COPY,
elf::R_AARCH64_TLS_DTPMOD => Self::DTPMOD,
elf::R_AARCH64_TLS_DTPREL => Self::DTPOFF,
elf::R_AARCH64_GLOB_DAT => Self::GOT,
elf::R_AARCH64_IRELATIVE => Self::IRELATIVE,
elf::R_AARCH64_JUMP_SLOT => Self::PLT,
elf::R_AARCH64_RELATIVE => Self::RELATIVE,
elf::R_AARCH64_ABS64 => Self::SYMBOLIC,
elf::R_AARCH64_TLSDESC => Self::TLSDESC,
elf::R_AARCH64_TLS_TPREL => Self::TPOFF,
_ => Self::UNKNOWN(kind),
}
}
#[cfg(target_arch = "riscv64")]
pub fn new(kind: u32) -> Self {
//WARNING: Only use R_RISCV_* constants here!
match kind {
elf::R_RISCV_COPY => Self::COPY,
elf::R_RISCV_TLS_DTPMOD64 => Self::DTPMOD,
elf::R_RISCV_TLS_DTPREL64 => Self::DTPOFF,
elf::R_RISCV_IRELATIVE => Self::IRELATIVE,
elf::R_RISCV_JUMP_SLOT => Self::PLT,
elf::R_RISCV_RELATIVE => Self::RELATIVE,
elf::R_RISCV_64 => Self::SYMBOLIC,
//TODO: not defined, should be 12: elf::R_RISCV_TLSDESC => Self::TLSDESC,
elf::R_RISCV_TLS_TPREL64 => Self::TPOFF,
_ => Self::UNKNOWN(kind),
}
}
#[cfg(target_arch = "x86")]
pub fn new(kind: u32) -> Self {
//WARNING: Only use R_386_* constants here!
match kind {
elf::R_386_COPY => Self::COPY,
elf::R_386_TLS_DTPMOD32 => Self::DTPMOD,
elf::R_386_TLS_DTPOFF32 => Self::DTPOFF,
elf::R_386_GLOB_DAT => Self::GOT,
elf::R_386_IRELATIVE => Self::IRELATIVE,
elf::R_386_JMP_SLOT => Self::PLT,
elf::R_386_PC32 => Self::OFFSET,
elf::R_386_RELATIVE => Self::RELATIVE,
elf::R_386_32 => Self::SYMBOLIC,
elf::R_386_TLS_DESC => Self::TLSDESC,
elf::R_386_TLS_TPOFF => Self::TPOFF,
_ => Self::UNKNOWN(kind),
}
}
#[cfg(target_arch = "x86_64")]
pub fn new(kind: u32) -> Self {
//WARNING: Only use R_X86_64_* constants here!
match kind {
elf::R_X86_64_COPY => Self::COPY,
elf::R_X86_64_DTPMOD64 => Self::DTPMOD,
elf::R_X86_64_DTPOFF64 => Self::DTPOFF,
elf::R_X86_64_GLOB_DAT => Self::GOT,
elf::R_X86_64_IRELATIVE => Self::IRELATIVE,
elf::R_X86_64_JUMP_SLOT => Self::PLT,
elf::R_X86_64_RELATIVE => Self::RELATIVE,
elf::R_X86_64_64 => Self::SYMBOLIC,
elf::R_X86_64_TLSDESC => Self::TLSDESC,
elf::R_X86_64_TPOFF64 => Self::TPOFF,
_ => Self::UNKNOWN(kind),
}
}
}
#[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 = elf::STB_GLOBAL,
/// Weak symbols resemble global symbols, but their definitions have lower
/// precedence.
Weak = elf::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.
pub struct DSO {
pub name: String,
pub id: usize,
pub dlopened: bool,
pub entry_point: usize,
/// Loaded library in-memory data
pub mmap: &'static [u8],
pub tls_module_id: usize,
pub tls_offset: usize,
pub(super) dynamic: Dynamic<'static>,
pub scope: spin::Once<Scope>,
/// Position Independent Executable.
pub pie: bool,
/// Whether this DSO *and* its dependencies have been successfully loaded.
is_ready: AtomicBool,
}
impl DSO {
pub fn new(
path: &str,
data: &[u8],
base_addr: Option<usize>,
dlopened: bool,
id: usize,
tls_module_id: usize,
tls_offset: usize,
) -> Result<(DSO, Option<Master>, Vec<ProgramHeader>), String> {
let elf = ElfFile::parse(data).map_err(|err| err.to_string())?;
let (mmap, tcb_master, dynamic) =
DSO::mmap_and_copy(path, &elf, data, base_addr, tls_offset)?;
let name = match dynamic.soname {
Some(soname) => soname.to_string(),
_ => basename(path),
};
let tls_offset = match tcb_master {
Some(ref master) => master.offset,
_ => 0,
};
let entry_point = if is_pie_enabled(&elf) {
mmap.as_ptr() as usize + elf.entry() as usize
} else {
elf.entry() as usize
};
let dso = DSO {
name,
id,
dlopened,
entry_point,
mmap,
tls_module_id: if tcb_master.is_some() {
tls_module_id
} else {
0
},
tls_offset,
pie: is_pie_enabled(&elf),
dynamic,
scope: spin::Once::new(),
is_ready: AtomicBool::new(false),
};
Ok((dso, tcb_master, elf.elf_program_headers().to_vec()))
}
#[inline]
pub fn mark_ready(&self) {
self.is_ready.store(true, Ordering::SeqCst);
}
#[inline]
pub fn scope(&self) -> &Scope {
self.scope.get().expect("scope not initialized")
}
/// Global Offset Table
#[inline]
pub fn got(&self) -> Option<NonNull<usize>> {
self.dynamic.got
}
#[inline]
pub fn runpath(&self) -> Option<&String> {
self.dynamic.runpath.as_ref()
}
#[inline]
pub fn dependencies(&self) -> &[&str] {
&self.dynamic.needed
}
pub fn get_sym<'a>(&self, name: &'a str) -> Option<(Symbol<'a>, SymbolBinding)> {
let (_, sym) = self.dynamic.hash_table.find(
name,
None,
self.dynamic.symbols,
self.dynamic.dynstrtab,
&VersionTable::default(),
)?;
if sym.st_shndx(NativeEndian) == elf::SHN_UNDEF {
return None;
}
Some((
Symbol {
name,
base: if self.pie {
self.mmap.as_ptr() as usize
} else {
0
},
value: sym.st_value(NativeEndian) as usize,
size: sym.st_size(NativeEndian) as usize,
sym_type: sym.st_type(),
},
// TODO(andypython): move this into [`Symbol`]
match sym.st_bind() {
elf::STB_GLOBAL => SymbolBinding::Global,
elf::STB_WEAK => SymbolBinding::Weak,
bind => unreachable!("get_sym bind {bind}"),
},
))
}
pub fn run_init(&self) {
for f in self.dynamic.init_array {
unsafe { f() }
}
}
pub fn run_fini(&self) {
for f in self.dynamic.fini_array.iter().rev() {
unsafe { f() }
}
}
fn mmap_and_copy<'a>(
path: &str,
elf: &ElfFile<'a>,
data: &'a [u8],
base_addr: Option<usize>,
tls_offset: usize,
) -> Result<(&'static [u8], Option<Master>, Dynamic<'static>), String> {
let endian = elf.endian();
log::trace!("# {}", path);
// data for struct LinkMap
let mut l_ld = 0;
// Calculate virtual memory bounds
let bounds = {
let mut bounds_opt: Option<(usize, usize)> = None;
for ph in elf.elf_program_headers() {
let voff = ph.p_vaddr(endian) % ph.p_align(endian);
let vaddr = (ph.p_vaddr(endian) - voff) as usize;
let vsize = ((ph.p_memsz(endian) + voff) as usize)
.next_multiple_of(ph.p_align(endian) as usize);
match ph.p_type(endian) {
elf::PT_DYNAMIC => {
l_ld = ph.p_vaddr(endian);
}
elf::PT_LOAD => {
log::trace!(" load {:#x}, {:#x}: {:x?}", vaddr, vsize, ph);
if let Some(ref mut bounds) = bounds_opt {
if vaddr < bounds.0 {
bounds.0 = vaddr;
}
if vaddr + vsize > bounds.1 {
bounds.1 = vaddr + vsize;
}
} else {
bounds_opt = Some((vaddr, vaddr + vsize));
}
}
_ => (),
}
}
bounds_opt.ok_or_else(|| "Unable to find PT_LOAD section".to_string())?
};
log::trace!(" bounds {:#x}, {:#x}", bounds.0, bounds.1);
// Allocate memory
let mmap = unsafe {
if let Some(addr) = base_addr {
let size = if is_pie_enabled(elf) {
bounds.1
} else {
bounds.1 - bounds.0
};
_r_debug
.lock()
.insert_first(addr + bounds.0, path, addr + l_ld as usize);
slice::from_raw_parts_mut((addr + bounds.0) as *mut u8, size)
} else {
let (start, end) = bounds;
let size = end - start;
let mut flags = sys_mman::MAP_ANONYMOUS | sys_mman::MAP_PRIVATE;
if start != 0 {
flags |= sys_mman::MAP_FIXED_NOREPLACE;
}
log::trace!(" mmap({:#x}, {:x}, {:x})", start, size, flags);
let ptr = Sys::mmap(
start as *mut c_void,
size,
//TODO: Make it possible to not specify PROT_EXEC on Redox
sys_mman::PROT_READ | sys_mman::PROT_WRITE,
flags,
-1,
0,
)
.map_err(|e| format!("failed to map {}. errno: {}", path, e.0))?;
if !(start as *mut c_void).is_null() {
assert_eq!(
ptr, start as *mut c_void,
"mmap must always map on the destination we requested"
);
}
log::trace!(" = {:p}", ptr);
_r_debug
.lock()
.insert(ptr as usize, path, ptr as usize + l_ld as usize);
slice::from_raw_parts_mut(ptr.cast::<u8>(), size)
}
};
let skip_load_segment_copy = base_addr.is_some();
let mut tcb_master = None;
// Copy data
let mut dynamic = None;
for ph in elf.elf_program_headers() {
match ph.p_type(endian) {
elf::PT_LOAD => {
if skip_load_segment_copy {
continue;
}
let obj_data = {
let (offset, size) = ph.file_range(endian);
let offset = offset as usize;
let range = offset..(offset + size as usize);
match data.get(range.clone()) {
Some(some) => some,
None => return Err(format!("failed to read {:x?}", range)),
}
};
let mmap_data = {
let range = if is_pie_enabled(elf) {
let addr = ph.p_vaddr(endian) as usize;
addr..addr + obj_data.len()
} else {
let addr = ph.p_vaddr(endian) as usize - mmap.as_ptr() as usize;
addr..addr + obj_data.len()
};
match mmap.get_mut(range.clone()) {
Some(some) => some,
None => {
return Err(format!("failed to write {:x?}", range));
}
}
};
let _voff = ph.p_vaddr(endian) % ph.p_align(endian);
let _vsize = ((ph.p_memsz(endian) + _voff) as usize)
.next_multiple_of(ph.p_align(endian) as usize);
log::trace!(
" copy {:#x}, {:#x}: {:#x}, {:#x}",
ph.p_vaddr(endian) - _voff,
_vsize,
_voff,
obj_data.len()
);
mmap_data.copy_from_slice(obj_data);
}
elf::PT_TLS => {
let ptr = unsafe {
if is_pie_enabled(elf) {
mmap.as_ptr().add(ph.p_vaddr(endian) as usize)
} else {
ph.p_vaddr(endian) as *const u8
}
};
tcb_master = Some(Master {
ptr,
image_size: ph.p_filesz(endian) as usize,
segment_size: ph.p_memsz(endian) as usize,
offset: tls_offset + ph.p_memsz(endian) as usize,
});
log::trace!(" tcb master {:x?}", tcb_master);
}
elf::PT_DYNAMIC => {
let entries = ph
.dynamic(endian, data)
.map_err(|err| err.to_string())?
.ok_or_else(|| "Unable to parse PT_DYNAMIC section".to_string())?;
dynamic = Some((ph, entries));
}
_ => (),
}
}
let dynamic = dynamic.ok_or_else(|| "Unable to find PT_DYNAMIC section".to_string())?;
let (parsed_dynamic, debug) = Self::parse_dynamic(path, mmap, is_pie_enabled(elf), dynamic)
.map_err(|e| e.to_string())?;
if let Some(i) = debug {
// FIXME: cleanup
let (ph, _) = dynamic;
let vaddr = ph.p_vaddr(endian) as usize;
let bytes: [u8; size_of::<Dyn>() / 2] =
((&raw const _r_debug).cast::<*const RTLDDebug>() as usize).to_ne_bytes();
let start = if is_pie_enabled(elf) {
vaddr + i * size_of::<Dyn>() + size_of::<Dyn>() / 2
} else {
vaddr + i * size_of::<Dyn>() + size_of::<Dyn>() / 2
- mmap.as_ptr().cast_mut() as usize
};
unsafe {
ptr::copy_nonoverlapping(
bytes.as_ptr(),
mmap.as_ptr().cast_mut().add(start),
bytes.len(),
);
}
}
Ok((mmap, tcb_master, parsed_dynamic))
}
fn parse_dynamic<'a>(
path: &str,
mmap: &'a [u8],
is_pie: bool,
(_, entries): (&ProgramHeader, &[Dyn]),
) -> object::Result<(Dynamic<'a>, Option<usize>)> {
let mut runpath = None;
let mut got = None;
let mut needed = vec![];
let mut jmprel = None;
let mut soname = None;
let mut hash_table = None;
let mut explicit_addend = None;
let mut pltrelsz = None;
let mut debug = None;
let mut symtab_ptr = None;
let (mut rel_ptr, mut rel_len) = (None, None);
let (mut relr_ptr, mut relr_len) = (None, None);
let (mut strtab_offset, mut strtab_size) = (None, None);
let (mut init_array_ptr, mut init_array_len) = (None, None);
let (mut fini_array_ptr, mut fini_array_len) = (None, None);
let (mut rela_offset, mut rela_len) = (None, None);
for (i, entry) in entries.iter().enumerate() {
let val = entry.d_val(NativeEndian);
let relative_idx = val as usize - if is_pie { 0 } else { mmap.as_ptr() as usize };
let ptr = (val as usize + if is_pie { mmap.as_ptr() as usize } else { 0 }) as *const u8;
let tag = entry.d_tag(NativeEndian) as u32;
match tag {
elf::DT_DEBUG => debug = Some(i),
// {Gnu,SysV}HashTable::parse()
//
// > The header does not contain a length field, and so all of
// > `data` will be used as the hash table values. It does not
// > matter if this is longer than needed...
elf::DT_GNU_HASH => {
let value = GnuHashTable::parse(NativeEndian, &mmap[relative_idx..])?;
hash_table = Some(HashTable::Gnu(value));
}
// XXX: Both GNU_HASH and HASH may be present, we give priority
// to GNU_HASH as it is significantly faster.
elf::DT_HASH if hash_table.is_none() => {
let value = SysVHashTable::parse(NativeEndian, &mmap[relative_idx..])?;
hash_table = Some(HashTable::Sysv(value));
}
elf::DT_PLTGOT => {
got = Some(NonNull::new(ptr as *mut usize).expect("DT_PLTGOT is NULL"));
}
elf::DT_NEEDED => needed.push(entry),
elf::DT_JMPREL => jmprel = Some(ptr as usize),
elf::DT_RUNPATH => runpath = Some(entry), // FIXME(andypython): rpath
elf::DT_STRTAB => strtab_offset = Some(relative_idx),
elf::DT_STRSZ => strtab_size = Some(val),
elf::DT_SONAME => soname = Some(entry),
elf::DT_RELA => rela_offset = Some(ptr.cast::<Rela>()),
elf::DT_RELASZ => rela_len = Some(val as usize / size_of::<Rela>()),
elf::DT_RELAENT => {
assert_eq!(val, size_of::<Rela>() as _)
}
elf::DT_REL => rel_ptr = Some(ptr.cast::<Rel>()),
elf::DT_RELSZ => rel_len = Some(val as usize / size_of::<Rel>()),
elf::DT_RELENT => {
assert_eq!(val, size_of::<Rel>() as _)
}
DT_RELR => relr_ptr = Some(ptr.cast::<Relr>()),
DT_RELRSZ => relr_len = Some(val as usize / size_of::<Relr>()),
DT_RELRENT => {
assert_eq!(val, size_of::<Relr>() as _)
}
elf::DT_PLTREL => {
let val = val as u32;
if val == elf::DT_RELA {
explicit_addend = Some(true);
} else {
assert_eq!(val, elf::DT_REL);
explicit_addend = Some(false);
}
}
elf::DT_PLTRELSZ => pltrelsz = Some(val as usize),
elf::DT_INIT_ARRAY if val != 0 => init_array_ptr = Some(ptr.cast::<InitFn>()),
elf::DT_INIT_ARRAYSZ => init_array_len = Some(val as usize / size_of::<InitFn>()),
elf::DT_FINI_ARRAY if val != 0 => fini_array_ptr = Some(ptr.cast::<InitFn>()),
elf::DT_FINI_ARRAYSZ => fini_array_len = Some(val as usize / size_of::<InitFn>()),
elf::DT_SYMTAB => symtab_ptr = Some(ptr.cast::<Sym>()),
elf::DT_SYMENT => {
assert_eq!(val as usize, size_of::<Sym>());
}
_ => {}
}
}
let strtab_offset = strtab_offset.expect("mandatory DT_STRTAB not present");
let strtab_size = strtab_size.expect("mandatory DT_STRSZ not present");
#[expect(clippy::unnecessary_cast, reason="needed on i586")]
let dynstrtab = StringTable::new(
mmap,
strtab_offset as u64,
strtab_offset as u64 + strtab_size as u64,
);
let get_str = |entry: &Dyn| {
entry
.string(NativeEndian, dynstrtab)
.map(|bytes| core::str::from_utf8(bytes).expect("non utf-8 elf symbol name"))
};
unsafe fn get_array<'a, T>(ptr: Option<*const T>, len: Option<usize>) -> &'a [T] {
if let Some(ptr) = ptr {
let len = len.expect("dynamic entry was present without it's corresponding size");
unsafe { core::slice::from_raw_parts(ptr, len) }
} else {
assert!(len.is_none());
&[]
}
}
let needed = needed
.into_iter()
.map(get_str)
.collect::<object::Result<Vec<_>>>()?;
let base = dirname(path);
let runpath = runpath
.map(get_str)
.transpose()?
.map(|value| value.replace("$ORIGIN", &base));
let soname = soname.map(get_str).transpose()?;
let jmprel = jmprel.unwrap_or_default();
let hash_table = hash_table.expect("either DT_GNU_HASH and/or DT_HASH mut be present");
let init_array = unsafe { get_array(init_array_ptr, init_array_len) };
let fini_array = unsafe { get_array(fini_array_ptr, fini_array_len) };
let rela = unsafe { get_array(rela_offset, rela_len) };
let relr = unsafe { get_array(relr_ptr, relr_len) };
let rel = unsafe { get_array(rel_ptr, rel_len) };
Ok((
Dynamic {
symbols: unsafe { get_array(symtab_ptr, Some(hash_table.symbol_table_length())) },
runpath,
got,
needed,
jmprel,
soname,
hash_table,
dynstrtab,
init_array,
fini_array,
rela,
rel,
relr,
explicit_addend: explicit_addend.unwrap_or_default(),
pltrelsz: pltrelsz.unwrap_or_default(),
},
debug,
))
}
/// `TLSDESC` relocation being an extension to the original TLS ABI spec can
/// be present in either `.rela.plt` (handled in [`Self::static_relocate`])
/// or `.rela.dyn` (handled in [`Self::lazy_relocate`]) due to the lack of a
/// standard unfortunately.
///
/// # Panics
///
/// Panics if `reloc.kind` is not `RelocationKind::TLSDESC`.
fn do_tlsdesc_reloc(&self, reloc: Relocation, ptr: *mut usize, global_scope: &Scope) {
assert!(reloc.kind == RelocationKind::TLSDESC);
let (sym, tls_module_id, tls_offset) = if reloc.sym != SymbolIndex(0) {
let sym_name = self.dynamic.symbol_name(reloc.sym).unwrap();
let (sym, _, obj) = resolve_sym(sym_name, &[global_scope, self.scope()]).unwrap();
(sym.value, obj.tls_module_id, obj.tls_offset)
} else {
(0, self.tls_module_id, self.tls_offset)
};
let resolver = unsafe { &mut *ptr };
let descriptor = unsafe { &mut *ptr.add(1) };
if self.dlopened {
#[cfg(not(any(target_arch = "x86_64", target_arch = "aarch64")))]
{
unimplemented!("`TLSDESC` relocations are not yet implemented for riscv64 and x86");
}
let mut tls_index = crate::header::dl_tls::dl_tls_index {
ti_module: tls_module_id,
ti_offset: reloc.addend.unwrap_or_default(),
};
// Ensure the DTV entry is initialised.
unsafe { __tls_get_addr(&raw mut tls_index) };
*resolver = __tlsdesc_dynamic as *const () as usize;
*descriptor = Box::into_raw(Box::new(TlsDescriptor {
module_id: tls_module_id - 1,
addend: sym + reloc.addend.unwrap_or_default(),
})) as usize;
} else {
*resolver = __tlsdesc_static as *const () as usize;
*descriptor = sym + tls_offset + reloc.addend.unwrap_or_default();
}
}
fn static_relocate(&self, global_scope: &Scope, reloc: Relocation) -> object::Result<()> {
let b = self.mmap.as_ptr() as usize;
let (sym, my_sym) = if reloc.sym != STN_UNDEF {
let name = self.dynamic.symbol_name(reloc.sym).unwrap();
let lookup_scopes = [global_scope, self.scope()];
let sym = if matches!(reloc.kind, RelocationKind::COPY) {
lookup_scopes
.iter()
.find_map(|scope| scope._get_sym(name, 1))
} else {
resolve_sym(name, &lookup_scopes)
}
.map(|(sym, _, obj)| (sym, obj));
(sym, self.dynamic.symbol(reloc.sym))
} else {
(None, None)
};
let (s, tls_obj) = sym
.as_ref()
.map(|(sym, obj)| (sym.as_ptr() as usize, obj.as_ref()))
// (1) According to the System V gABI (Chapter 4, "Relocation"): if
// the symbol index (`reloc.sym`) is undefined, the symbol value
// (`s`) is defined as 0.
//
// (2) According to Drepper's ELF Handling For Thread-Local Storage
// (Section 4.2, "Local Dynamic TLS Model"): a relocation with
// undefined symbol index implies a reference to the current module
// itself. Hence we resolve the object to `self`.
.unwrap_or((0, self));
let ptr = if self.pie {
(b + reloc.offset) as *mut u8
} else {
reloc.offset as *mut u8
};
let p = ptr as usize;
let a = match reloc.addend {
Some(some) => some,
None => match reloc.kind {
RelocationKind::COPY | RelocationKind::GOT | RelocationKind::PLT => 0,
_ => unsafe { *ptr.cast::<usize>() },
},
};
// TODO: support different sizes?
let set_usize = |value| unsafe {
*ptr.cast::<usize>() = value;
};
match reloc.kind {
RelocationKind::DTPMOD => set_usize(tls_obj.tls_module_id),
// TODO: Subtract DTP_OFFSET, which is 0x800 on riscv64, 0 on x86?
RelocationKind::DTPOFF => {
if reloc.sym.0 > 0 {
let (sym, _) = sym
.as_ref()
.expect("RelocationKind::DTPOFF called without valid symbol");
set_usize(sym.value + a);
} else {
set_usize(a);
}
}
RelocationKind::GOT => set_usize(s),
RelocationKind::OFFSET => set_usize((s + a).wrapping_sub(p)),
RelocationKind::RELATIVE => set_usize(b + a),
RelocationKind::SYMBOLIC => set_usize(s + a),
RelocationKind::TPOFF => {
assert!(
!tls_obj.dlopened,
"The {{local/initial}}-exec access model is used for symbol '{}' in '{}', which requires a static TLS block. However, the definition in '{}' resides in the dynamic TLS block because the object was loaded via dlopen(2).",
reloc.sym, self.name, tls_obj.name
);
if reloc.sym.0 > 0 {
let (sym, _) = sym
.as_ref()
.expect("RelocationKind::TPOFF called without valid symbol");
set_usize((sym.value + a).wrapping_sub(tls_obj.tls_offset));
} else {
set_usize(a.wrapping_sub(tls_obj.tls_offset));
}
}
RelocationKind::IRELATIVE => unsafe {
let f: unsafe extern "C" fn() -> usize = core::mem::transmute(b + a);
set_usize(f());
},
RelocationKind::COPY => unsafe {
let (sym, obj) = sym
.as_ref()
.expect("RelocationKind::COPY called without valid symbol");
let my_sym = my_sym.expect("RelocationKind::COPY called without valid symbol");
assert!(
sym.size == my_sym.st_size(NativeEndian) as usize,
"RelocationKind::COPY failed: I was trying to use the symbol {} from {} for {} but they had different sizes. Please consider relinking.",
sym.name,
obj.name,
self.name
);
// SAFETY: Both the source and destination have the same size.
ptr::copy_nonoverlapping(sym.as_ptr() as *const u8, ptr, sym.size);
},
RelocationKind::TLSDESC => {
self.do_tlsdesc_reloc(reloc, ptr.cast::<usize>(), global_scope)
}
_ => unimplemented!("relocation type {:?}", reloc.kind),
}
Ok(())
}
fn lazy_relocate(&self, global_scope: &Scope, resolve: Resolve) -> object::Result<()> {
let Some(got) = self.got() else {
assert_eq!(self.dynamic.jmprel, 0);
return Ok(());
};
let object_base_addr = self.mmap.as_ptr() as usize;
let jmprel = self.dynamic.jmprel;
let pltrelsz = self.dynamic.pltrelsz;
unsafe {
got.add(1).write(core::ptr::addr_of!(*self) as usize);
got.add(2)
.write(__plt_resolve_trampoline as *const () as usize);
}
let relsz = if self.dynamic.explicit_addend {
size_of::<Rela>()
} else {
size_of::<Rel>()
};
for addr in (jmprel..(jmprel + pltrelsz)).step_by(relsz) {
let reloc: Relocation = if self.dynamic.explicit_addend {
unsafe { &*(addr as *const Rela) }.into()
} else {
unsafe { &*(addr as *const Rel) }.into()
};
let ptr = if self.pie {
(object_base_addr + reloc.offset) as *mut usize
} else {
reloc.offset as *mut usize
};
match (reloc.kind, resolve) {
(RelocationKind::PLT, Resolve::Lazy) if self.pie => unsafe {
*ptr += object_base_addr;
},
(RelocationKind::PLT, Resolve::Lazy) => {
// NOP.
}
(RelocationKind::PLT, Resolve::Now) => {
let name = self.dynamic.symbol_name(reloc.sym).unwrap();
let resolved = resolve_sym(name, &[global_scope, self.scope()])
.map(|(sym, _, _)| sym.as_ptr() as usize)
.unwrap_or_else(|| {
panic!(
"unresolved symbol: {name} for soname {:?}",
self.dynamic.soname
)
});
unsafe {
*ptr = resolved + reloc.addend.unwrap_or(0);
}
}
(RelocationKind::TLSDESC, Resolve::Now) => {
self.do_tlsdesc_reloc(reloc, ptr, global_scope);
}
(RelocationKind::TLSDESC, Resolve::Lazy) => {
unreachable!("TLSDESC cannot be lazily resolved")
}
_ => {
unimplemented!(
"relocation type {:?} with resolve {:?}",
reloc.kind,
resolve
)
}
}
}
Ok(())
}
pub fn relocate(&self, ph: &[ProgramHeader], resolve: Resolve) -> object::Result<()> {
let global_scope = GLOBAL_SCOPE.read();
let base = self.mmap.as_ptr();
unsafe {
apply_relr(base, self.dynamic.relr);
}
self.dynamic
.static_relocations()
.try_for_each(|reloc| self.static_relocate(&global_scope, reloc))?;
self.lazy_relocate(&global_scope, resolve)?;
// Protect pages
for ph in ph
.iter()
.filter(|ph| ph.p_type(NativeEndian) == elf::PT_LOAD)
{
let voff = ph.p_vaddr(NativeEndian) % ph.p_align(NativeEndian);
let vaddr = (ph.p_vaddr(NativeEndian) - voff) as usize;
let vsize = ((ph.p_memsz(NativeEndian) + voff) as usize)
.next_multiple_of(ph.p_align(NativeEndian) as usize);
let mut prot = 0;
if ph.p_flags(NativeEndian) & elf::PF_R == elf::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(NativeEndian) & elf::PF_X == elf::PF_X {
prot |= sys_mman::PROT_EXEC;
} else if ph.p_flags(NativeEndian) & elf::PF_W == elf::PF_W {
prot |= sys_mman::PROT_WRITE;
}
unsafe {
let ptr = if self.pie {
self.mmap.as_ptr().add(vaddr)
} else {
vaddr as *const u8
};
log::trace!(" prot {:#x}, {:#x}: {:p}, {:#x}", vaddr, vsize, ptr, prot);
Sys::mprotect(ptr as *mut c_void, vsize, prot).expect("[ld.so]: mprotect failed");
}
}
Ok(())
}
}
impl Drop for DSO {
fn drop(&mut self) {
if self.is_ready.load(Ordering::SeqCst) {
// `run_fini` should not be called if we are being prematurely
// dropped (e.g. failed to satisfy dependencies).
self.run_fini();
}
unsafe { Sys::munmap(self.mmap.as_ptr() as *mut c_void, self.mmap.len()).unwrap() };
}
}
fn is_pie_enabled(elf: &ElfFile) -> bool {
elf.elf_header().e_type.get(elf.endian()) == elf::ET_DYN
}
fn basename(path: &str) -> String {
path.split("/").last().unwrap_or(path).to_string()
}
fn dirname(path: &str) -> String {
let mut parts: Vec<&str> = path.split("/").collect();
parts.truncate(parts.len() - 1);
parts.join("/")
}
pub fn resolve_sym<'a>(
name: &'a str,
scopes: &[&'a Scope],
) -> Option<(Symbol<'a>, SymbolBinding, Arc<DSO>)> {
scopes.iter().find_map(|scope| scope.get_sym(name))
}
#[repr(C)]
struct TlsDescriptor {
module_id: usize,
addend: usize,
}
#[cfg(target_arch = "x86_64")]
#[unsafe(naked)]
unsafe extern "C" fn __tlsdesc_static() {
core::arch::naked_asm!("mov rax, [rax + 8]", "ret")
}
#[cfg(target_arch = "x86")]
#[unsafe(naked)]
unsafe extern "C" fn __tlsdesc_static() {
core::arch::naked_asm!("mov eax, [eax + 4]", "ret")
}
#[cfg(target_arch = "aarch64")]
#[unsafe(naked)]
unsafe extern "C" fn __tlsdesc_static() {
core::arch::naked_asm!("ldr x0, [x0, #8]", "ret")
}
#[cfg(target_arch = "riscv64")]
#[unsafe(naked)]
unsafe extern "C" fn __tlsdesc_static() {
core::arch::naked_asm!("ld a0, 8(a0)", "ret");
}
unsafe extern "C" {
fn __tlsdesc_dynamic();
}
#[cfg(target_arch = "x86_64")]
core::arch::global_asm!(
"
.global __tlsdesc_dynamic
.hidden __tlsdesc_dynamic
__tlsdesc_dynamic:
push rbx
push rcx
mov rax, [rax + 8] // TLS descriptor
mov rbx, [rax + {TLS_DESCRIPTOR_MODULE_ID_OFF}] // tls_descriptor.module_id
mov rcx, [rax + {TLS_DESCRIPTOR_ADDEND_OFF}] // tls_descriptor.addend
mov rax, qword ptr fs:[{DTV_PTR_OFF}] // tcb.dtv_ptr
// tcb.dtv_ptr[tls_descriptor.module_id] + tls_descriptor.addend
mov rax, [rax + rbx * 8]
add rax, rcx
sub rax, qword ptr fs:[{TCB_SELF_PTR_OFF}]
pop rcx
pop rbx
ret
",
TLS_DESCRIPTOR_MODULE_ID_OFF = const offset_of!(TlsDescriptor, module_id),
TLS_DESCRIPTOR_ADDEND_OFF = const offset_of!(TlsDescriptor, addend),
DTV_PTR_OFF = const offset_of!(Tcb, dtv_ptr),
TCB_SELF_PTR_OFF = const offset_of!(Tcb, generic.tcb_ptr),
);
#[cfg(target_arch = "x86")]
core::arch::global_asm!(
"
.global __tlsdesc_dynamic
.hidden __tlsdesc_dynamic
__tlsdesc_dynamic:
ud2
"
);
#[cfg(target_arch = "aarch64")]
core::arch::global_asm!(
"
.global __tlsdesc_dynamic
.hidden __tlsdesc_dynamic
__tlsdesc_dynamic:
stp x1, x2, [sp, #-16]!
ldr x0, [x0, #8] // TLS descriptor
// x0 := tls_descriptor.module_id
// x1 := tls_descriptor.addend
ldp x0, x1, [x0]
mrs x2, tpidr_el0 // ABI ptr
ldr x2, [x2] // TCB ptr
sub x1, x1, x2 // tls_descriptor.addend -= tcb
ldr x2, [x2, {DTV_PTR_OFF}] // tcb.dtv_ptr
ldr x2, [x2, x0, lsl #3] // tcb.dtv_ptr[tls_descriptor.module_id]
add x0, x2, x1 // tcb.dtv_ptr[tls_descriptor.module_id] + tls_descriptor.addend
ldp x1, x2, [sp], #16
ret
",
DTV_PTR_OFF = const offset_of!(Tcb, dtv_ptr),
);
#[cfg(target_arch = "riscv64")]
core::arch::global_asm!(
"
.global __tlsdesc_dynamic
.hidden __tlsdesc_dynamic
__tlsdesc_dynamic:
unimp
"
);
/// Applies [`DT_RELR`] relative relocations.
pub unsafe fn apply_relr(base: *const u8, relr: &[Relr]) {
let mut addr = ptr::null_mut();
for &entry in relr {
if entry & 1 == 0 {
// An even entry sets up `addr` for subsequent odd entries.
unsafe {
addr = base.add(entry) as *mut usize;
*addr += base as usize;
addr = addr.add(1);
}
} else {
// An odd entry indicates a bitmap describing at maximum 63
// (for 64-bit) or 31 (for 32-bit) locations following `addr`.
// Odd entries can be chained.
let mut entry = entry >> 1;
let mut i = 0;
while entry != 0 {
if entry & 1 != 0 {
unsafe {
*addr.add(i) += base as usize;
}
}
entry >>= 1;
i += 1;
}
addr = unsafe { addr.add(CHAR_BITS * size_of::<Relr>() - 1) };
}
}
}