feat(libc.so.6): merge with ld.so

Signed-off-by: Anhad Singh <andypython@protonmail.com>
This commit is contained in:
Anhad Singh
2026-07-14 17:22:43 +10:00
parent e09e8bac0b
commit 3f20b58d38
3 changed files with 299 additions and 83 deletions
+196 -65
View File
@@ -3,7 +3,7 @@
//! * <https://www.akkadia.org/drepper/dsohowto.pdf>
use object::{
NativeEndian, Object, StringTable, SymbolIndex, elf,
NativeEndian, Object, StringTable, SymbolIndex, U32, elf,
read::elf::{
Dyn as _, GnuHashTable, HashTable as SysVHashTable, ProgramHeader as _, Rel as _,
Rela as _, Sym as _, Version, VersionTable,
@@ -42,25 +42,27 @@ 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>;
use object::{NativeEndian, read::elf::ElfFile32};
pub type Dyn = elf::Dyn32<NativeEndian>;
pub type Rel = elf::Rel32<NativeEndian>;
pub type Rela = elf::Rela32<NativeEndian>;
pub type Sym = elf::Sym32<NativeEndian>;
pub type FileHeader = elf::FileHeader32<NativeEndian>;
pub type ProgramHeader = elf::ProgramHeader32<NativeEndian>;
pub type GnuHashHeader = elf::GnuHashHeader<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>;
use object::{NativeEndian, elf, read::elf::ElfFile64};
pub type Dyn = elf::Dyn64<NativeEndian>;
pub type Rel = elf::Rel64<NativeEndian>;
pub type Rela = elf::Rela64<NativeEndian>;
pub type Sym = elf::Sym64<NativeEndian>;
pub type FileHeader = elf::FileHeader64<NativeEndian>;
pub type ProgramHeader = elf::ProgramHeader64<NativeEndian>;
pub type GnuHashHeader = elf::GnuHashHeader<NativeEndian>;
pub type ElfFile<'a> = ElfFile64<'a, NativeEndian>;
}
@@ -176,7 +178,7 @@ impl<'data> Dynamic<'data> {
unsafe impl Send for Dynamic<'_> {}
unsafe impl Sync for Dynamic<'_> {}
#[derive(Debug)]
#[derive(Debug, Copy, Clone)]
pub(super) struct Relocation {
pub(super) offset: usize,
pub(super) addend: Option<usize>,
@@ -331,14 +333,35 @@ impl SymbolBinding {
}
}
pub struct MemoryMapHandle {
ptr: *const u8,
size: usize,
}
impl MemoryMapHandle {
pub fn as_ptr(&self) -> *const u8 {
self.ptr
}
pub fn size(&self) -> usize {
self.size
}
}
impl Drop for MemoryMapHandle {
fn drop(&mut self) {
unsafe { Sys::munmap(self.ptr as *mut c_void, self.size).unwrap() };
}
}
/// 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 base: *const u8,
pub mmap: Option<MemoryMapHandle>,
pub tls_module_id: usize,
pub tls_offset: usize,
@@ -349,10 +372,56 @@ pub struct DSO {
pub pie: bool,
/// Whether this DSO *and* its dependencies have been successfully loaded.
is_ready: AtomicBool,
pub is_ready: AtomicBool,
/// Is this DSO for ld.so?
is_me: bool,
}
impl DSO {
pub fn from_raw(
base: *const u8,
dyns: &[Dyn],
phdrs: &[ProgramHeader],
id: usize,
tls_module_id: usize,
tls_offset: usize,
) -> (Self, Master) {
// FIXME: What should `path` be set to?
// FIXME: this function and `parse_dynamic` should be unsafe.
let (dynamic, _debug) = Self::parse_dynamic("", base, true, dyns).unwrap();
let tcb_master = phdrs
.iter()
.find(|ph| ph.p_type(NativeEndian) == elf::PT_TLS)
.map(|ph| Master {
ptr: unsafe { base.byte_add(ph.p_vaddr(NativeEndian) as usize) },
image_size: ph.p_filesz(NativeEndian) as usize,
segment_size: ph.p_memsz(NativeEndian) as usize,
offset: tls_offset + ph.p_memsz(NativeEndian) as usize,
})
.unwrap();
(
Self {
name: String::from("libc.so.6"),
id,
tls_offset: tcb_master.offset,
tls_module_id,
dlopened: false,
entry_point: 0,
base,
mmap: None,
dynamic,
scope: spin::Once::new(),
pie: true,
is_ready: AtomicBool::new(false),
is_me: true,
},
tcb_master,
)
}
pub fn new(
path: &str,
data: &[u8],
@@ -385,7 +454,10 @@ impl DSO {
id,
dlopened,
entry_point,
mmap,
base: mmap.as_ptr(),
mmap: Some(mmap),
tls_module_id: if tcb_master.is_some() {
tls_module_id
} else {
@@ -397,6 +469,7 @@ impl DSO {
dynamic,
scope: spin::Once::new(),
is_ready: AtomicBool::new(false),
is_me: false,
};
Ok((dso, tcb_master, elf.elf_program_headers().to_vec()))
@@ -444,11 +517,7 @@ impl DSO {
Some((
Symbol {
name,
base: if self.pie {
self.mmap.as_ptr() as usize
} else {
0
},
base: if self.pie { self.base as usize } else { 0 },
value: sym.st_value(NativeEndian) as usize,
size: sym.st_size(NativeEndian) as usize,
sym_type: sym.st_type(),
@@ -480,7 +549,7 @@ impl DSO {
data: &'a [u8],
base_addr: Option<usize>,
tls_offset: usize,
) -> Result<(&'static [u8], Option<Master>, Dynamic<'static>), String> {
) -> Result<(MemoryMapHandle, Option<Master>, Dynamic<'static>), String> {
let endian = elf.endian();
log::trace!("# {}", path);
// data for struct LinkMap
@@ -640,8 +709,9 @@ impl DSO {
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())?;
let (parsed_dynamic, debug) =
Self::parse_dynamic(path, mmap.as_ptr(), is_pie_enabled(elf), dynamic.1)
.map_err(|e| e.to_string())?;
if let Some(i) = debug {
// FIXME: cleanup
@@ -664,14 +734,21 @@ impl DSO {
}
}
Ok((mmap, tcb_master, parsed_dynamic))
Ok((
MemoryMapHandle {
ptr: mmap.as_ptr(),
size: mmap.len(),
},
tcb_master,
parsed_dynamic,
))
}
fn parse_dynamic<'a>(
path: &str,
mmap: &'a [u8],
mmap: *const u8,
is_pie: bool,
(_, entries): (&ProgramHeader, &[Dyn]),
entries: &[Dyn],
) -> object::Result<(Dynamic<'a>, Option<usize>)> {
let mut runpath = None;
let mut got = None;
@@ -692,28 +769,66 @@ impl DSO {
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 relative_idx = val as usize - if is_pie { 0 } else { mmap as usize };
let ptr = (val as usize + if is_pie { mmap 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));
let header = unsafe { ptr.cast::<GnuHashHeader>().as_ref() }.unwrap();
let bloom_count = header.bloom_count.get(NativeEndian) as usize;
let bucket_count = header.bucket_count.get(NativeEndian) as usize;
let symbol_base = header.symbol_base.get(NativeEndian);
let bloom_size = bloom_count * size_of::<usize>();
let mut ptr = unsafe { ptr.byte_add(size_of::<GnuHashHeader>()) };
let bloom_filters = unsafe { slice::from_raw_parts(ptr, bloom_size) };
unsafe { ptr = ptr.byte_add(bloom_size) };
let buckets = unsafe {
slice::from_raw_parts(ptr.cast::<U32<NativeEndian>>(), bucket_count)
};
unsafe { ptr = ptr.byte_add(bucket_count * size_of::<u32>()) };
let mut max_symbol = 0;
for bucket in buckets {
let bucket = bucket.get(NativeEndian);
if max_symbol < bucket {
max_symbol = bucket;
}
}
assert_ne!(max_symbol, 0);
let chains_ptr = ptr.cast::<u32>();
let mut i = max_symbol - symbol_base;
while unsafe { chains_ptr.add(i as usize).read() & 1 == 0 } {
i += 1;
}
let values = unsafe {
slice::from_raw_parts(ptr.cast::<U32<NativeEndian>>(), i as usize + 1)
};
let table = GnuHashTable {
symbol_base,
bloom_shift: header.bloom_shift.get(NativeEndian),
bloom_filters,
buckets,
values,
};
hash_table = Some(HashTable::Gnu(table));
}
// 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));
// FIXME: BEFORE MR FIX THIS
// todo!();
}
elf::DT_PLTGOT => {
@@ -771,14 +886,16 @@ impl DSO {
}
}
let strtab_offset = strtab_offset.expect("mandatory DT_STRTAB not present");
let strtab_size = strtab_size.expect("mandatory DT_STRSZ not present");
let hash_table = hash_table.expect("either DT_GNU_HASH and/or DT_HASH mut be present");
let strtab_offset = strtab_offset.expect("DT_STRTAB must be present");
let strtab_size = strtab_size.expect("DT_STRSZ must be 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,
unsafe { slice::from_raw_parts(mmap.byte_add(strtab_offset), strtab_size as usize) },
0,
strtab_size as u64,
);
let get_str = |entry: &Dyn| {
@@ -812,7 +929,6 @@ impl DSO {
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) };
@@ -889,8 +1005,6 @@ impl DSO {
}
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();
@@ -923,7 +1037,7 @@ impl DSO {
.unwrap_or((0, self));
let ptr = if self.pie {
(b + reloc.offset) as *mut u8
unsafe { self.base.byte_add(reloc.offset).cast_mut() }
} else {
reloc.offset as *mut u8
};
@@ -956,7 +1070,7 @@ impl DSO {
}
RelocationKind::GOT => set_usize(s),
RelocationKind::OFFSET => set_usize((s + a).wrapping_sub(p)),
RelocationKind::RELATIVE => set_usize(b + a),
RelocationKind::RELATIVE => set_usize(self.base as usize + a),
RelocationKind::SYMBOLIC => set_usize(s + a),
RelocationKind::TPOFF => {
assert!(
@@ -974,7 +1088,8 @@ impl DSO {
}
}
RelocationKind::IRELATIVE => unsafe {
let f: unsafe extern "C" fn() -> usize = core::mem::transmute(b + a);
let f: unsafe extern "C" fn() -> usize =
core::mem::transmute(self.base as usize + a);
set_usize(f());
},
RelocationKind::COPY => unsafe {
@@ -1007,7 +1122,6 @@ impl DSO {
return Ok(());
};
let object_base_addr = self.mmap.as_ptr() as usize;
let jmprel = self.dynamic.jmprel;
let pltrelsz = self.dynamic.pltrelsz;
@@ -1031,14 +1145,14 @@ impl DSO {
};
let ptr = if self.pie {
(object_base_addr + reloc.offset) as *mut usize
(self.base as usize + 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;
*ptr += self.base as usize;
},
(RelocationKind::PLT, Resolve::Lazy) => {
@@ -1082,20 +1196,31 @@ impl DSO {
Ok(())
}
pub fn relocate(&self, ph: &[ProgramHeader], resolve: Resolve) -> object::Result<()> {
pub fn relocate(&self, ph: Option<&[ProgramHeader]>, resolve: Resolve) -> object::Result<()> {
let global_scope = GLOBAL_SCOPE.read();
let base = self.mmap.as_ptr();
unsafe {
apply_relr(base, self.dynamic.relr);
if !self.is_me {
unsafe {
apply_relr(self.base, self.dynamic.relr);
}
}
self.dynamic
.static_relocations()
.try_for_each(|reloc| self.static_relocate(&global_scope, reloc))?;
for reloc in self.dynamic.static_relocations() {
if reloc.kind == RelocationKind::RELATIVE && self.is_me {
continue;
}
self.static_relocate(&global_scope, reloc)?;
}
if self.is_me {
// TODO: assert that ld.so have no lazy relocations.
return Ok(());
}
self.lazy_relocate(&global_scope, resolve)?;
let ph = ph.unwrap();
// Protect pages
for ph in ph
.iter()
@@ -1120,7 +1245,7 @@ impl DSO {
unsafe {
let ptr = if self.pie {
self.mmap.as_ptr().add(vaddr)
self.base.add(vaddr)
} else {
vaddr as *const u8
};
@@ -1135,12 +1260,14 @@ impl DSO {
impl Drop for DSO {
fn drop(&mut self) {
if self.is_me {
return;
}
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() };
}
}
@@ -1309,3 +1436,7 @@ pub unsafe fn apply_relr(base: *const u8, relr: &[Relr]) {
}
}
}
// TODO: Add safety comment.
unsafe impl Send for DSO {}
unsafe impl Sync for DSO {}
+64 -8
View File
@@ -26,7 +26,7 @@ use crate::{
fcntl, sys_mman,
unistd::F_OK,
},
ld_so::dso::SymbolBinding,
ld_so::dso::{Dyn, SymbolBinding},
out::Out,
platform::{
Pal, Sys,
@@ -417,9 +417,17 @@ impl Config {
}
}
pub struct Me {
pub base: *const u8,
pub phdrs: &'static [ProgramHeader],
pub dyns: &'static [Dyn],
}
pub struct Linker {
config: Config,
me: Me,
next_object_id: usize,
next_tls_module_id: usize,
tls_size: usize,
@@ -431,8 +439,9 @@ pub struct Linker {
const ROOT_ID: usize = 1;
impl Linker {
pub fn new(config: Config) -> Self {
pub fn new(me: Me, config: Config) -> Self {
Self {
me,
config,
next_object_id: ROOT_ID,
next_tls_module_id: 1,
@@ -642,7 +651,11 @@ impl Linker {
)?;
for (i, obj) in new_objects.iter().enumerate() {
obj.relocate(&objects_data[i], resolve).unwrap();
obj.relocate(
objects_data[i].as_ref().map(|phdrs| phdrs.as_slice()),
resolve,
)
.unwrap();
}
unsafe {
@@ -788,7 +801,7 @@ impl Linker {
base_addr: Option<usize>,
dlopened: bool,
new_objects: &mut Vec<Arc<DSO>>,
objects_data: &mut Vec<Vec<ProgramHeader>>,
objects_data: &mut Vec<Option<Vec<ProgramHeader>>>,
tcb_masters: &mut Vec<Master>,
// Scope of the object that caused this object to be loaded.
dependent_scope: Option<&mut Scope>,
@@ -821,6 +834,49 @@ impl Linker {
let debug = self.config.debug_flags.contains(DebugFlags::LOAD);
if name == "libc.so.6" || name == "libc.so" {
if debug {
println!(
"[ld.so]: loading libc.so.6 (aka. ld.so) at {:#?}",
self.me.base
);
}
let (me, master) = DSO::from_raw(
self.me.base,
self.me.dyns,
self.me.phdrs,
self.next_object_id,
self.next_tls_module_id,
self.tls_size.next_multiple_of(16),
);
self.tls_size = master.offset;
self.next_tls_module_id += 1;
self.next_object_id += 1;
let obj = Arc::new(me);
tcb_masters.push(master);
objects_data.push(None);
new_objects.push(obj.clone());
if let Some(dependent_scope) = dependent_scope {
match scope_kind {
ScopeKind::Local => dependent_scope.add(&obj),
ScopeKind::Global => GLOBAL_SCOPE.write().add(&obj),
}
} else if let ScopeKind::Global = scope_kind {
GLOBAL_SCOPE.write().add(&obj);
}
let mut scope = Scope::local();
scope.set_owner(Arc::downgrade(&obj));
obj.scope.call_once(|| scope);
return Ok(obj);
}
let path = self.search_object(name, parent_runpath)?;
let file = self.read_file(&path)?;
let data = file.data();
@@ -846,8 +902,8 @@ impl Linker {
eprintln!(
"[ld.so]: loading object: {} at {:#x}:{:#x} (pie: {})",
name,
obj.mmap.as_ptr() as usize,
obj.mmap.as_ptr() as usize + obj.mmap.len(),
obj.mmap.as_ref().unwrap().as_ptr() as usize,
obj.mmap.as_ref().unwrap().as_ptr() as usize + obj.mmap.as_ref().unwrap().size(),
obj.pie,
);
}
@@ -896,7 +952,7 @@ impl Linker {
)?;
}
objects_data.push(elf);
objects_data.push(Some(elf));
new_objects.push(obj.clone());
scope.set_owner(Arc::downgrade(&obj));
@@ -1011,7 +1067,7 @@ impl Linker {
#[cfg(target_pointer_width = "64")]
extern "C" fn __plt_resolve_inner(obj: *const DSO, relocation_index: c_uint) -> *mut c_void {
let obj = unsafe { &*obj };
let obj_base = obj.mmap.as_ptr() as usize;
let obj_base = obj.base as usize;
let jmprel = obj.dynamic.jmprel;
let rela = unsafe { &*(jmprel as *const Rela).add(relocation_index as usize) };
+39 -10
View File
@@ -12,7 +12,7 @@ use alloc::{
use object::{
NativeEndian,
elf::{self, PT_DYNAMIC, PT_PHDR},
read::elf::{Dyn as _, ProgramHeader as _},
read::elf::{Dyn as _, FileHeader as _, ProgramHeader as _},
};
use crate::{
@@ -23,10 +23,10 @@ use crate::{
},
ld_so::{
dso::{
DT_RELR, DT_RELRENT, DT_RELRSZ, Dyn, ProgramHeader, Rel, Rela, Relocation,
DT_RELR, DT_RELRENT, DT_RELRSZ, Dyn, FileHeader, ProgramHeader, Rel, Rela, Relocation,
RelocationKind, Relr, apply_relr,
},
linker::DebugFlags,
linker::{DebugFlags, Me},
},
platform::{auxv_iter, get_auxvs, types::c_char},
start::Stack,
@@ -211,7 +211,7 @@ pub unsafe extern "C" fn relibc_ld_so_start(
let is_manual = at_entry == ld_entry; // Whether the dynamic linker was invoked as a command.
let mut i = dynamic;
let mut i = 0;
let mut rela_ptr = None;
let mut rela_len = None;
let mut relr_ptr = None;
@@ -219,7 +219,7 @@ pub unsafe extern "C" fn relibc_ld_so_start(
let mut rel_ptr = None;
let mut rel_len = None;
loop {
let entry = unsafe { &*i };
let entry = unsafe { &*dynamic.add(i) };
let val = entry.d_val(NativeEndian);
let ptr = val as *const u8;
match entry.d_tag(NativeEndian) as u32 {
@@ -241,9 +241,11 @@ pub unsafe extern "C" fn relibc_ld_so_start(
}
_ => {}
}
i = unsafe { i.add(1) };
i += 1;
}
let dyns = unsafe { slice::from_raw_parts(dynamic, i) };
unsafe fn get_array<'a, T>(
ptr: Option<*const T>,
len: Option<usize>,
@@ -251,7 +253,7 @@ pub unsafe extern "C" fn relibc_ld_so_start(
) -> &'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.byte_add(base_addr), len) }
unsafe { slice::from_raw_parts(ptr.byte_add(base_addr), len) }
} else {
&[]
}
@@ -298,7 +300,23 @@ pub unsafe extern "C" fn relibc_ld_so_start(
}
}
stage2(sp, self_base, is_manual, base_addr)
unsafe extern "C" {
safe static __ehdr_start: FileHeader;
}
let ph_off = __ehdr_start.e_phoff(NativeEndian) as usize;
let ph_num = __ehdr_start.e_phnum(NativeEndian) as usize;
let my_phdrs = unsafe {
slice::from_raw_parts(
core::ptr::addr_of!(__ehdr_start)
.byte_add(ph_off)
.cast::<ProgramHeader>(),
ph_num,
)
};
stage2(sp, self_base, is_manual, base_addr, dyns, my_phdrs)
}
fn stage2(
@@ -306,6 +324,8 @@ fn stage2(
self_base: usize,
is_manual: bool,
base_addr: Option<usize>,
my_dyns: &'static [Dyn],
my_phdrs: &'static [ProgramHeader],
) -> usize {
// Setup TCB for ourselves.
unsafe {
@@ -394,7 +414,9 @@ fn stage2(
crate::platform::environ = crate::platform::OUR_ENVIRON.unsafe_mut().as_mut_ptr();
crate::platform::logger::init();
if let Err(_) = crate::platform::logger::init() {
log::error!("Logger has already been initialised");
}
}
// we might need global lock for this kind of stuff
@@ -435,7 +457,14 @@ fn stage2(
}
}
let mut linker = Linker::new(config);
let mut linker = Linker::new(
Me {
base: self_base as *const u8,
phdrs: my_phdrs,
dyns: my_dyns,
},
config,
);
let entry = match linker.load_program(&path, base_addr) {
Ok(entry) => entry,
Err(err) => {