From aae27c5e0c874b6c8e1f2d3dc47afcc04616677d Mon Sep 17 00:00:00 2001 From: Anhad Singh Date: Tue, 7 Jan 2025 18:23:32 +1100 Subject: [PATCH] feat(ld.so): switch to object crate Signed-off-by: Anhad Singh --- Cargo.lock | 1 + Cargo.toml | 2 +- Makefile | 32 ++- src/ld_so/dso.rs | 648 +++++++++++++++++++++++++++++++------------- src/ld_so/linker.rs | 382 ++++++-------------------- src/ld_so/mod.rs | 57 ++-- src/ld_so/start.rs | 7 +- src/ld_so/tcb.rs | 17 +- src/lib.rs | 2 +- 9 files changed, 611 insertions(+), 537 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e6c4544d72..cf469f7684 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -277,6 +277,7 @@ dependencies = [ [[package]] name = "object" version = "0.36.7" +source = "git+https://gitlab.redox-os.org/andypython/object#7270e3f0d06e5ef4c2b80abc6166d31f4ddf4fea" dependencies = [ "memchr", ] diff --git a/Cargo.toml b/Cargo.toml index b43aaeace9..d1f47b558c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -49,7 +49,7 @@ generic-rt = { path = "generic-rt" } chrono-tz = {version = "0.10", default-features = false} chrono = {version = "0.4", default-features = false, features = ["alloc"]} libm = "0.2" -object = { version = "0.36.7", path = "../object", default-features = false, features = ["elf", "read_core"] } +object = { version = "0.36.7", git = "https://gitlab.redox-os.org/andypython/object", default-features = false, features = ["elf", "read_core"] } [dependencies.dlmalloc] path = "dlmalloc-rs" diff --git a/Makefile b/Makefile index ce16fe650e..1edf530c9b 100644 --- a/Makefile +++ b/Makefile @@ -15,6 +15,8 @@ CARGOFLAGS+=--target=$(TARGET) TARGET_HEADERS?=$(BUILD)/include export CFLAGS=-I$(TARGET_HEADERS) +PROFILE?=release + HEADERS_UNPARSED=$(shell find src/header -mindepth 1 -maxdepth 1 -type d -not -name "_*" -printf "%f\n") HEADERS_DEPS=$(shell find src/header -type f \( -name "cbindgen.toml" -o -name "*.rs" \)) #HEADERS=$(patsubst %,%.h,$(subst _,/,$(HEADERS_UNPARSED))) @@ -42,7 +44,7 @@ ifeq ($(TARGET),x86_64-unknown-linux-gnu) export LD=x86_64-linux-gnu-ld export AR=x86_64-linux-gnu-ar export NM=x86_64-linux-gnu-nm - export OBJCOPY=x86_64-linux-gnu-objcopy + export OBJCOPY=objcopy export CPPFLAGS= endif @@ -118,23 +120,23 @@ install-headers: headers libs cp -rv "$(TARGET_HEADERS)"/* "$(DESTDIR)/include" libs: \ - $(BUILD)/release/libc.a \ - $(BUILD)/release/libc.so \ - $(BUILD)/release/crt0.o \ - $(BUILD)/release/crti.o \ - $(BUILD)/release/crtn.o \ - $(BUILD)/release/ld_so + $(BUILD)/$(PROFILE)/libc.a \ + $(BUILD)/$(PROFILE)/libc.so \ + $(BUILD)/$(PROFILE)/crt0.o \ + $(BUILD)/$(PROFILE)/crti.o \ + $(BUILD)/$(PROFILE)/crtn.o \ + $(BUILD)/$(PROFILE)/ld_so install-libs: headers libs mkdir -pv "$(DESTDIR)/lib" - cp -v "$(BUILD)/release/libc.a" "$(DESTDIR)/lib" - cp -v "$(BUILD)/release/libc.so" "$(DESTDIR)/lib" + cp -v "$(BUILD)/$(PROFILE)/libc.a" "$(DESTDIR)/lib" + cp -v "$(BUILD)/$(PROFILE)/libc.so" "$(DESTDIR)/lib" ln -vnfs libc.so "$(DESTDIR)/lib/libc.so.6" - cp -v "$(BUILD)/release/crt0.o" "$(DESTDIR)/lib" + cp -v "$(BUILD)/$(PROFILE)/crt0.o" "$(DESTDIR)/lib" ln -vnfs crt0.o "$(DESTDIR)/lib/crt1.o" - cp -v "$(BUILD)/release/crti.o" "$(DESTDIR)/lib" - cp -v "$(BUILD)/release/crtn.o" "$(DESTDIR)/lib" - cp -v "$(BUILD)/release/ld_so" "$(DESTDIR)/lib/ld64.so.1" + cp -v "$(BUILD)/$(PROFILE)/crti.o" "$(DESTDIR)/lib" + cp -v "$(BUILD)/$(PROFILE)/crtn.o" "$(DESTDIR)/lib" + cp -v "$(BUILD)/$(PROFILE)/ld_so" "$(DESTDIR)/lib/ld64.so.1" cp -v "$(BUILD)/openlibm/libopenlibm.a" "$(DESTDIR)/lib/libm.a" # Empty libraries for dl, pthread, and rt $(AR) -rcs "$(DESTDIR)/lib/libdl.a" @@ -181,7 +183,7 @@ $(BUILD)/debug/libc.so: $(BUILD)/debug/librelibc.a $(BUILD)/openlibm/libopenlibm $(CC) -nostdlib -shared -Wl,--allow-multiple-definition -Wl,--whole-archive $^ -Wl,--no-whole-archive -Wl,-soname,libc.so.6 -lgcc -o $@ $(BUILD)/debug/librelibc.a: $(SRC) - $(CARGO) rustc $(CARGOFLAGS) -- --emit link=$@ $(RUSTCFLAGS) + $(CARGO) rustc $(CARGOFLAGS) -- --emit link=$@ -g -C debug-assertions=no $(RUSTCFLAGS) ./renamesyms.sh "$@" "$(BUILD)/debug/deps/" touch $@ @@ -198,7 +200,7 @@ $(BUILD)/debug/crtn.o: $(SRC) touch $@ $(BUILD)/debug/ld_so.o: $(SRC) - $(CARGO) rustc --manifest-path ld_so/Cargo.toml $(CARGOFLAGS) -- --emit obj=$@ -C panic=abort $(RUSTCFLAGS) + $(CARGO) rustc --manifest-path ld_so/Cargo.toml $(CARGOFLAGS) -- --emit obj=$@ -C panic=abort -g -C debug-assertions=no $(RUSTCFLAGS) touch $@ $(BUILD)/debug/ld_so: $(BUILD)/debug/ld_so.o $(BUILD)/debug/crti.o $(BUILD)/debug/libc.a $(BUILD)/debug/crtn.o diff --git a/src/ld_so/dso.rs b/src/ld_so/dso.rs index 63187ce2db..95b330170d 100644 --- a/src/ld_so/dso.rs +++ b/src/ld_so/dso.rs @@ -3,18 +3,17 @@ //! * use object::{ - elf::{self, Dyn64, FileHeader64}, + elf::{self, Sym64}, read::elf::{ - Dyn as _, ElfFile64, FileHeader, GnuHashTable, HashTable as SysVHashTable, ProgramHeader, - Sym, SymbolTable, Version, VersionTable, + Dyn as _, GnuHashTable, HashTable as SysVHashTable, ProgramHeader as _, Rel as _, + Rela as _, Sym as _, Version, VersionTable, }, - Endianness, NativeEndian, Object, ObjectSection, ReadRef, SectionKind, StringTable, - SymbolIndex, + Endianness, NativeEndian, Object, StringTable, SymbolIndex, }; use super::{ - debug::_r_debug, - linker::{Scope, Symbol}, + debug::{RTLDDebug, _r_debug}, + linker::{Resolve, Scope, Symbol, __plt_resolve_trampoline, GLOBAL_SCOPE}, tcb::Master, }; use crate::{ @@ -27,69 +26,152 @@ use alloc::{ vec::Vec, }; use core::{ - mem::transmute, + mem::size_of, ptr::{self, NonNull}, slice, }; -#[cfg(target_pointer_width = "32")] -use goblin::elf32::{ - header::ET_DYN, - program_header, - section_header::{SHT_FINI_ARRAY, SHT_INIT_ARRAY}, -}; + #[cfg(target_pointer_width = "64")] -use goblin::elf64::{ - header::ET_DYN, - program_header, - section_header::{SHT_FINI_ARRAY, SHT_INIT_ARRAY}, -}; -use goblin::error::{Error, Result}; - -type Dyn = Dyn64; - -enum HashTable<'a, Elf: FileHeader> { - Gnu(GnuHashTable<'a, Elf>), - Sysv(SysVHashTable<'a, Elf>), +mod shim { + use object::{elf::*, read::elf::ElfFile64, NativeEndian}; + pub type Dyn = Dyn64; + pub type Rel = Rel64; + pub type Rela = Rela64; + pub type Sym = Sym64; + pub type FileHeader = FileHeader64; + pub type ProgramHeader = ProgramHeader64; + pub type ElfFile<'a> = ElfFile64<'a, NativeEndian>; } -impl<'a, Elf> HashTable<'a, Elf> -where - Elf: FileHeader, -{ +#[cfg(target_pointer_width = "64")] +pub use shim::*; + +#[cfg(target_pointer_width = "32")] +type Dyn = Dyn32; + +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>( + pub fn find( &self, - endian: Elf::Endian, name: &str, version: Option<&Version<'_>>, - symbols: &SymbolTable<'a, Elf, R>, - versions: &VersionTable<'a, Elf>, - ) -> Option<(SymbolIndex, &'a Elf::Sym)> { + 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(endian, name, hash, version, symbols, versions) + hash_table.find( + NativeEndian, + name, + hash, + version, + symbols, + strings, + versions, + ) } Self::Sysv(hash_table) => { let hash = elf::hash(name); - hash_table.find(endian, name, hash, version, symbols, versions) + 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, + } + } } -pub(super) struct Dynamic<'a> { +type InitFn = unsafe extern "C" fn(); + +pub(super) struct Dynamic<'data> { runpath: Option, got: Option>, - needed: Vec<&'a str>, + needed: Vec<&'data str>, pub(super) jmprel: usize, - hash_table: HashTable<'a, FileHeader64>, - pub(super) dynstrtab: StringTable<'a>, - soname: Option<&'a str>, + 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], + 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")) + } +} + +unsafe impl Send for Dynamic<'_> {} +unsafe impl Sync for Dynamic<'_> {} + +#[derive(Debug)] +struct Relocation { + offset: usize, + addend: usize, + sym: SymbolIndex, + kind: u32, +} + +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: reloc.r_addend(NativeEndian) as usize, + sym: SymbolIndex(reloc.r_sym(NativeEndian, is_mips64el) as usize), + kind: 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: 0, + sym: SymbolIndex(reloc.r_sym(NativeEndian) as usize), + kind: reloc.r_type(NativeEndian), + } + } } #[derive(Debug, PartialEq)] @@ -121,48 +203,30 @@ pub struct DSO { pub mmap: &'static [u8], pub global_syms: BTreeMap, pub weak_syms: BTreeMap, - /// .init_array addr and len - pub init_array: (usize, usize), - /// .fini_array addr and len - pub fini_array: (usize, usize), pub tls_module_id: usize, pub tls_offset: usize, pub(super) dynamic: Dynamic<'static>, - pub(super) symbols: SymbolTable<'static, FileHeader64>, pub scope: Scope, /// Position Independent Executable. pub pie: bool, } -unsafe impl Send for DSO {} -unsafe impl Sync for DSO {} - impl DSO { - pub fn new( + pub fn new<'a>( path: &str, - data: &'static [u8], + data: &'a [u8], base_addr: Option, dlopened: bool, id: usize, tls_module_id: usize, tls_offset: usize, - ) -> Result<(DSO, Option)> { - let elf = ElfFile64::::parse(data).unwrap(); + ) -> object::Result<(DSO, Option, Vec)> { + let elf = ElfFile::parse(data).unwrap(); let (mmap, tcb_master, dynamic) = DSO::mmap_and_copy(path, &elf, data, base_addr, tls_offset).unwrap(); - // dynamic.hash_table = HashTable::Gnu( - // elf.elf_section_table() - // .gnu_hash(Endianness::default(), data) - // .unwrap() - // .unwrap() - // .0, - // ); - - let (init_array, fini_array) = DSO::init_fini_arrays(&elf, mmap.as_ptr() as usize); - let name = match dynamic.soname { Some(soname) => soname.to_string(), _ => basename(path), @@ -176,9 +240,7 @@ impl DSO { } else { elf.entry() as usize }; - let endian = Endianness::default(); - let sections = elf.elf_header().sections(endian, data).unwrap(); - let symbols = sections.symbols(endian, data, elf::SHT_DYNSYM).unwrap(); + let dso = DSO { name, id, @@ -187,8 +249,6 @@ impl DSO { mmap, global_syms: BTreeMap::new(), weak_syms: BTreeMap::new(), - init_array, - fini_array, tls_module_id: if tcb_master.is_some() { tls_module_id } else { @@ -197,12 +257,11 @@ impl DSO { tls_offset, pie: is_pie_enabled(&elf), - symbols, dynamic, scope: Scope::local(), }; - Ok((dso, tcb_master)) + Ok((dso, tcb_master, elf.elf_program_headers().to_vec())) } /// Global Offset Table @@ -222,15 +281,18 @@ impl DSO { } pub fn get_sym(&self, name: &str) -> Option<(Symbol, SymbolBinding)> { - let endian = Endianness::default(); - let (_index, sym) = self.dynamic.hash_table.find( - endian, + let (_, sym) = self.dynamic.hash_table.find( name, None, - &self.symbols, + &self.dynamic.symbols, + self.dynamic.dynstrtab, &VersionTable::default(), )?; + if sym.st_shndx(NativeEndian) == elf::SHN_UNDEF { + return None; + } + Some(( Symbol { base: if self.pie { @@ -238,8 +300,8 @@ impl DSO { } else { 0 }, - value: sym.st_value(endian) as usize, - size: sym.st_size(endian) as usize, + 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`] @@ -252,36 +314,24 @@ impl DSO { } pub fn run_init(&self) { - unsafe { - let (addr, size) = self.init_array; - for i in (0..size).step_by(8) { - let func = transmute::>(addr + i); - if let Some(f) = *func { - f(); - } - } + for f in self.dynamic.init_array { + unsafe { f() } } } pub fn run_fini(&self) { - unsafe { - let (addr, size) = self.fini_array; - for i in (0..size).step_by(8).rev() { - let func = transmute::>(addr + i); - if let Some(f) = *func { - f(); - } - } + for f in self.dynamic.fini_array.iter().rev() { + unsafe { f() } } } fn mmap_and_copy<'a>( path: &str, - elf: &ElfFile64<'a>, + elf: &ElfFile<'a>, data: &'a [u8], base_addr: Option, tls_offset: usize, - ) -> object::Result<(&'static [u8], Option, Dynamic<'a>)> { + ) -> object::Result<(&'static [u8], Option, Dynamic<'static>)> { let endian = elf.endian(); trace!("# {}", path); // data for struct LinkMap @@ -296,10 +346,10 @@ impl DSO { .next_multiple_of(ph.p_align(endian) as usize); match ph.p_type(endian) { - program_header::PT_DYNAMIC => { + elf::PT_DYNAMIC => { l_ld = ph.p_vaddr(endian); } - program_header::PT_LOAD => { + elf::PT_LOAD => { trace!(" load {:#x}, {:#x}: {:x?}", vaddr, vsize, ph); if let Some(ref mut bounds) = bounds_opt { if vaddr < bounds.0 { @@ -316,9 +366,7 @@ impl DSO { } } bounds_opt - .ok_or(Error::Malformed( - "Unable to find PT_LOAD section".to_string(), - )) + .ok_or("Unable to find PT_LOAD section".to_string()) .unwrap() }; trace!(" bounds {:#x}, {:#x}", bounds.0, bounds.1); @@ -349,7 +397,7 @@ impl DSO { -1, 0, ) - .map_err(|e| Error::Malformed(format!("failed to map {}. errno: {}", path, e.0))) + .map_err(|e| format!("failed to map {}. errno: {}", path, e.0)) .unwrap(); if !(start as *mut c_void).is_null() { @@ -368,12 +416,6 @@ impl DSO { let skip_load_segment_copy = base_addr.is_some(); let mut tcb_master = None; - let base = if is_pie_enabled(elf) { - mmap.as_ptr() as usize - } else { - 0 - }; - // Copy data let mut dynamic = None; for ph in elf.elf_program_headers() { @@ -383,7 +425,7 @@ impl DSO { .next_multiple_of(ph.p_align(endian) as usize); match ph.p_type(endian) { - program_header::PT_LOAD => { + elf::PT_LOAD => { if skip_load_segment_copy { continue; } @@ -393,13 +435,7 @@ impl DSO { let range = offset..(offset + size as usize); match data.get(range.clone()) { Some(some) => some, - None => { - return Err(Error::Malformed(format!( - "failed to read {:x?}", - range - ))) - .unwrap() - } + None => return Err(format!("failed to read {:x?}", range)).unwrap(), } }; @@ -414,11 +450,7 @@ impl DSO { match mmap.get_mut(range.clone()) { Some(some) => some, None => { - return Err(Error::Malformed(format!( - "failed to write {:x?}", - range - ))) - .unwrap(); + return Err(format!("failed to write {:x?}", range)).unwrap(); } } }; @@ -431,7 +463,7 @@ impl DSO { ); mmap_data.copy_from_slice(obj_data); } - program_header::PT_TLS => { + elf::PT_TLS => { let ptr = unsafe { if is_pie_enabled(elf) { mmap.as_ptr().add(ph.p_vaddr(endian) as usize) @@ -447,77 +479,131 @@ impl DSO { trace!(" tcb master {:x?}", tcb_master); } - program_header::PT_DYNAMIC => { - dynamic = Some(ph.dynamic(endian, data).unwrap().unwrap()) - } + elf::PT_DYNAMIC => dynamic = Some((ph, ph.dynamic(endian, data).unwrap().unwrap())), _ => (), } } - Ok(( - mmap, - tcb_master, - Self::parse_dynamic(path, base, endian, mmap, dynamic.unwrap())?, - )) + let (parsed_dynamic, debug) = + Self::parse_dynamic(path, mmap, is_pie_enabled(elf), dynamic.unwrap())?; + + if let Some(i) = debug { + // FIXME: cleanup + let (ph, _) = dynamic.unwrap(); + let vaddr = ph.p_vaddr(endian) as usize; + let bytes: [u8; size_of::() / 2] = + unsafe { core::mem::transmute((&_r_debug) as *const RTLDDebug as usize) }; + let start = if is_pie_enabled(elf) { + vaddr + i * size_of::() + size_of::() / 2 + } else { + vaddr + i * size_of::() + size_of::() / 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, - base: usize, - endian: Endianness, - mmap: &'static [u8], - entries: &'a [Dyn64], - ) -> object::Result> { + mmap: &'a [u8], + is_pie: bool, + (_, entries): (&ProgramHeader, &[Dyn]), + ) -> object::Result<(Dynamic<'a>, Option)> { 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_size) = (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_size) = (None, None); for (i, entry) in entries.iter().enumerate() { - let val = entry.d_val(endian); - let tag = entry.d_tag(endian) as u32; + let val = entry.d_val(NativeEndian); + let relative_idx = val - if is_pie { 0 } else { mmap.as_ptr() as u64 }; + let ptr = (val + if is_pie { mmap.as_ptr() as u64 } else { 0 }) as *const u8; + let tag = entry.d_tag(NativeEndian) as u32; match tag { - elf::DT_DEBUG => { - // let vaddr = ph.p_vaddr(endian) as usize; - // let bytes: [u8; size_of::() / 2] = - // unsafe { transmute((&_r_debug) as *const RTLDDebug as usize) }; - // let start = if is_pie_enabled(elf) { - // vaddr + i * size_of::() + size_of::() / 2 - // } else { - // vaddr + i * size_of::() + size_of::() / 2 - // - mmap.as_mut_ptr() as usize - // }; - // mmap[start..start + size_of::() / 2].clone_from_slice(&bytes); - } + 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(endian, &mmap[val as usize..])?; + let value = GnuHashTable::parse(NativeEndian, &mmap[relative_idx as usize..])?; 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() => { - hash_table = Some(HashTable::Sysv(SysVHashTable::parse( - endian, - &mmap[val as usize..], - )?)); + let value = SysVHashTable::parse(NativeEndian, &mmap[relative_idx as usize..])?; + hash_table = Some(HashTable::Sysv(value)); } elf::DT_PLTGOT => { - let ptr = NonNull::new(val as *mut usize).expect("DT_PLTGOT is NULL"); - got = Some(unsafe { ptr.byte_add(base) }); + got = Some(NonNull::new(ptr as *mut usize).expect("DT_PLTGOT is NULL")); } elf::DT_NEEDED => needed.push(entry), - elf::DT_JMPREL => jmprel = Some(val as usize), - elf::DT_RUNPATH => runpath = Some(entry), - elf::DT_STRTAB => strtab_offset = Some(val), + 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::()), + elf::DT_RELASZ => rela_size = Some(val as usize / size_of::()), + elf::DT_RELAENT => { + assert_eq!(val, size_of::>() as u64) + } + + elf::DT_REL => rel_ptr = Some(ptr.cast::()), + elf::DT_RELSZ => rel_size = Some(val as usize / size_of::()), + elf::DT_RELENT => { + assert_eq!(val, size_of::>() as u64) + } + + 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::()), + elf::DT_INIT_ARRAYSZ => init_array_len = Some(val as usize / size_of::()), + + elf::DT_FINI_ARRAY if val != 0 => fini_array_ptr = Some(ptr.cast::()), + elf::DT_FINI_ARRAYSZ => fini_array_len = Some(val as usize / size_of::()), + + elf::DT_SYMTAB => symtab_ptr = Some(ptr as *const Sym), + elf::DT_SYMENT => { + assert_eq!(val, size_of::>() as u64); + } + _ => {} } } @@ -525,14 +611,24 @@ 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 dynstrtab = StringTable::new(mmap, strtab_offset, strtab_offset + strtab_size); + let dynstrtab = StringTable::new(&*mmap, strtab_offset, strtab_offset + strtab_size); - let get_str = |entry: &Dyn64| { + let get_str = |entry: &Dyn| { entry - .string(endian, dynstrtab) + .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) -> &'a [T] { + if let Some(ptr) = ptr { + let len = len.expect("dynamic entry was present without it's corresponding size"); + core::slice::from_raw_parts(ptr, len) + } else { + assert!(len.is_none()); + &[] + } + } + let needed = needed .into_iter() .map(get_str) @@ -550,39 +646,215 @@ impl DSO { let jmprel = jmprel.unwrap_or_default(); let hash_table = hash_table.expect("either DT_GNU_HASH and/or DT_HASH mut be present"); - Ok(Dynamic { - runpath, - got, - needed, - jmprel, - soname, - hash_table, - dynstrtab, - }) + 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_size) }; + let rel = unsafe { get_array(rel_ptr, rel_size) }; + + 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, + explicit_addend: explicit_addend.unwrap_or_default(), + pltrelsz: pltrelsz.unwrap_or_default(), + }, + debug, + )) } - fn init_fini_arrays(elf: &ElfFile64, mmap_addr: usize) -> ((usize, usize), (usize, usize)) { - let mut init_array: (usize, usize) = (0, 0); - let mut fini_array: (usize, usize) = (0, 0); - for section in elf.sections().filter(|s| { - matches!( - s.kind(), - SectionKind::Elf(SHT_INIT_ARRAY) | SectionKind::Elf(SHT_FINI_ARRAY) - ) - }) { - let addr = if is_pie_enabled(elf) { - mmap_addr + section.address() as usize + fn static_relocate(&self, reloc: Relocation) -> object::Result<()> { + let b = self.mmap.as_ptr() as usize; + + let sym = if reloc.sym.0 > 0 { + let name = self.dynamic.symbol_name(reloc.sym).unwrap(); + + GLOBAL_SCOPE + .read() + .get_sym(name) + .or_else(|| self.scope.get_sym(name)) + .map(|(sym, _, obj)| (sym, obj.tls_offset)) + } else { + None + }; + + let (s, t) = sym + .as_ref() + .map(|(sym, t)| (sym.as_ptr() as usize, *t)) + .unwrap_or((0, 0)); + + let a = reloc.addend; + let ptr = if self.pie { + (b + reloc.offset) as *mut u8 + } else { + reloc.offset as *mut u8 + }; + + let set_u64 = |value| unsafe { + *(ptr as *mut u64) = value; + }; + + match reloc.kind { + elf::R_X86_64_64 => set_u64((s + a) as u64), + elf::R_X86_64_DTPMOD64 => set_u64(self.tls_module_id as u64), + elf::R_X86_64_DTPOFF64 => { + if s != 0 { + set_u64((s - b) as u64); + } else { + set_u64(s as u64); + } + } + elf::R_X86_64_GLOB_DAT => set_u64(s as u64), + elf::R_X86_64_RELATIVE => set_u64((b + a) as u64), + elf::R_X86_64_TPOFF64 => { + if reloc.sym.0 > 0 { + let (sym, _) = sym + .as_ref() + .expect("R_X86_64_TPOFF64 called without valid symbol"); + set_u64((sym.value + a).wrapping_sub(t) as u64); + } else { + set_u64(a.wrapping_sub(t) as u64); + } + } + elf::R_X86_64_IRELATIVE => unsafe { + let f: unsafe extern "C" fn() -> u64 = core::mem::transmute(b + a); + set_u64(f()); + }, + elf::R_X86_64_COPY => unsafe { + let (sym, _) = sym + .as_ref() + .expect("R_X86_64_COPY called without valid symbol"); + ptr::copy_nonoverlapping(sym.as_ptr() as *const u8, ptr, sym.size); + }, + _ => unimplemented!("relocation type {:#x}", reloc.kind), + } + + Ok(()) + } + + fn lazy_relocate(&self, 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 usize); + } + + let relsz = if self.dynamic.explicit_addend { + size_of::() + } else { + size_of::() + }; + + for addr in (jmprel..(jmprel + pltrelsz)).step_by(relsz) { + let reloc: Relocation = if self.dynamic.explicit_addend { + unsafe { &*(addr as *const Rela) }.into() } else { - section.address() as usize + unsafe { &*(addr as *const Rel) }.into() }; - if let SectionKind::Elf(SHT_INIT_ARRAY) = section.kind() { - init_array = (addr, section.size() as usize); + + let ptr = if self.pie { + (object_base_addr + reloc.offset) as *mut usize } else { - fini_array = (addr, section.size() as usize); + reloc.offset as *mut usize + }; + + match (reloc.kind, resolve) { + (elf::R_X86_64_JUMP_SLOT, Resolve::Lazy) if self.pie => unsafe { + *ptr += object_base_addr; + }, + + (elf::R_X86_64_JUMP_SLOT, Resolve::Lazy) => { + // NOP. + } + + (elf::R_X86_64_JUMP_SLOT, Resolve::Now) => { + let name = self.dynamic.symbol_name(reloc.sym).unwrap(); + + let resolved = GLOBAL_SCOPE + .read() + .get_sym(name) + .or_else(|| self.scope.get_sym(name)) + .map(|(sym, _, _)| sym.as_ptr() as usize) + .expect("unresolved symbol"); + + unsafe { + *ptr = resolved + reloc.addend; + } + } + + _ => { + unimplemented!("relocation type {:#x}", reloc.kind) + } } } - (init_array, fini_array) + Ok(()) + } + + pub fn relocate(&self, ph: &[ProgramHeader], resolve: Resolve) -> object::Result<()> { + self.dynamic + .rela + .iter() + .try_for_each(|reloc| self.static_relocate(reloc.into()))?; + + self.dynamic + .rel + .iter() + .try_for_each(|reloc| self.static_relocate(reloc.into()))?; + + self.lazy_relocate(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 + }; + 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(()) } } @@ -593,8 +865,8 @@ impl Drop for DSO { } } -pub fn is_pie_enabled(elf: &ElfFile64) -> bool { - elf.elf_header().e_type.get(elf.endian()) == ET_DYN +pub fn is_pie_enabled(elf: &ElfFile) -> bool { + elf.elf_header().e_type.get(elf.endian()) == elf::ET_DYN } fn basename(path: &str) -> String { diff --git a/src/ld_so/linker.rs b/src/ld_so/linker.rs index cb18c01294..6f26cd3dcc 100644 --- a/src/ld_so/linker.rs +++ b/src/ld_so/linker.rs @@ -6,19 +6,16 @@ use alloc::{ vec::Vec, }; use object::{ - elf::Rela64, - read::elf::{Rela, Sym}, - Endianness, + elf, + read::elf::{Rela as _, Sym}, + NativeEndian, }; use core::{ cell::RefCell, - mem::transmute, ptr::{self, NonNull}, }; -use goblin::elf::{program_header, reloc, sym::STT_TLS, Elf}; - use crate::{ c_str::{CStr, CString}, error::Errno, @@ -27,7 +24,6 @@ use crate::{ fcntl, sys_mman, unistd::F_OK, }, - io, ld_so::dso::SymbolBinding, platform::{ types::{c_int, c_uint, c_void}, @@ -40,7 +36,7 @@ use super::{ access::accessible, callbacks::LinkerCallbacks, debug::{RTLDState, _dl_debug_state, _r_debug}, - dso::DSO, + dso::{ProgramHeader, Rela, DSO}, tcb::{Master, Tcb}, PATH_SEP, }; @@ -53,6 +49,8 @@ pub enum DlError { Malformed, /// Invalid DSO handle. InvalidHandle, + /// Out of memory. + Oom, } impl DlError { @@ -70,31 +68,56 @@ impl DlError { DlError::InvalidHandle => { c_str!("Invalid DSO handle. Set `LD_DEBUG=all` for more information.") } + + DlError::Oom => c_str!("Out of memory."), } } } pub type Result = core::result::Result; -static GLOBAL_SCOPE: RwLock = RwLock::new(Scope::global()); +pub(super) static GLOBAL_SCOPE: RwLock = RwLock::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. -struct File { - fd: c_int, +struct MmapFile { + fd: i32, + ptr: *mut c_void, + size: usize, } -impl File { - pub fn open(path: CStr, oflag: c_int) -> core::result::Result { - Ok(Self { - fd: Sys::open(path, oflag, 0)?, - }) +impl MmapFile { + fn open(path: CStr, oflag: c_int) -> core::result::Result { + 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::(), self.size) } } } -impl io::Read for File { - fn read(&mut self, buf: &mut [u8]) -> core::result::Result { - Sys::read(self.fd, buf).map_err(|err| io::Error::from_raw_os_error(err.0)) +impl Drop for MmapFile { + fn drop(&mut self) { + unsafe { + Sys::munmap(self.ptr, self.size).unwrap(); + Sys::close(self.fd).unwrap(); + } } } @@ -188,7 +211,7 @@ impl Scope { } } - fn get_sym(&self, name: &str) -> Option<(Symbol, SymbolBinding, Arc)> { + pub(super) fn get_sym(&self, name: &str) -> Option<(Symbol, SymbolBinding, Arc)> { let mut res = None; let get_sym = |obj: Arc| { @@ -452,7 +475,7 @@ impl Linker { } .get_sym(name) .map(|(symbol, _, obj)| { - if symbol.sym_type != STT_TLS { + if symbol.sym_type != elf::STT_TLS { symbol.as_ptr() } else { let mut tls_index = dl_tls_index { @@ -554,7 +577,7 @@ impl Linker { 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).map_err(|_| DlError::Malformed)?; // This actually allocates TCB, TLS and ABI page. + 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; @@ -595,7 +618,7 @@ impl Linker { }; #[cfg(not(target_os = "redox"))] - let tcb = Tcb::new(self.tls_size).map_err(|_| DlError::Malformed)?; + 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 @@ -647,7 +670,7 @@ impl Linker { // new_objects is already reversed. for (i, obj) in new_objects.into_iter().enumerate() { - self.relocate(&obj, objects_data[i], resolve)?; + obj.relocate(&objects_data[i], resolve).unwrap(); self.run_init(&obj); self.register_object(obj); } @@ -663,14 +686,14 @@ impl Linker { self.objects.insert(obj.id, obj); } - fn load_objects_recursive( + fn load_objects_recursive<'a>( &mut self, name: &str, parent_runpath: &Option, base_addr: Option, dlopened: bool, new_objects: &mut Vec>, - objects_data: &mut Vec<&[u8]>, + objects_data: &mut Vec>, tcb_masters: &mut Vec, // The object that caused this object to be loaded. dependent: Option<&mut DSO>, @@ -688,10 +711,11 @@ impl Linker { let debug = self.config.debug_flags.contains(DebugFlags::LOAD); let path = self.search_object(name, parent_runpath)?; - let data = self.read_file(&path)?; - let (mut obj, tcb_master) = DSO::new( + let file = self.read_file(&path)?; + let data = file.data(); + let (mut obj, tcb_master, elf) = DSO::new( &path, - &data, + data, base_addr, dlopened, self.next_object_id, @@ -760,7 +784,7 @@ impl Linker { GLOBAL_SCOPE.write().add(&obj); } - objects_data.push(data); + objects_data.push(elf); new_objects.push(obj.clone()); Ok(obj) @@ -808,7 +832,7 @@ impl Linker { Err(DlError::NotFound) } - fn read_file(&self, path: &str) -> Result<&'static [u8]> { + fn read_file(&self, path: &str) -> Result { let debug = self.config.debug_flags.contains(DebugFlags::SEARCH); let path_c = CString::new(path).map_err(|err| { @@ -820,7 +844,7 @@ impl Linker { })?; let flags = fcntl::O_RDONLY | fcntl::O_CLOEXEC; - let file = File::open(CStr::borrow(&path_c), flags).map_err(|err| { + let file = MmapFile::open(CStr::borrow(&path_c), flags).map_err(|err| { if debug { eprintln!("[ld.so]: failed to open '{}': {}", path, err) } @@ -828,261 +852,7 @@ impl Linker { DlError::NotFound })?; - let mut stat = crate::header::sys_stat::stat::default(); - unsafe { - Sys::fstat(file.fd, &mut stat).unwrap(); - } - - let mmap_ptr = unsafe { - Sys::mmap( - ptr::null_mut(), - stat.st_size as usize, - sys_mman::PROT_READ, - sys_mman::MAP_PRIVATE, - file.fd, - 0, - ) - } - .unwrap(); - - Ok(unsafe { core::slice::from_raw_parts(mmap_ptr as *const u8, stat.st_size as usize) }) - } - - /// Perform lazy relocations. - fn lazy_relocate(&self, obj: &Arc, elf: &Elf, resolve: Resolve) -> Result<()> { - let Some(got) = obj.got() else { return Ok(()) }; - let object_base_addr = obj.mmap.as_ptr() as u64; - let debug = self.config.debug_flags.contains(DebugFlags::LOAD); - - 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 obj.pie { - (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 obj.pie => 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_else(|| { - if debug { - eprintln!("missing symbol for relocation {:?}", rel) - } - - DlError::Malformed - })?; - - let name = elf.dynstrtab.get_at(sym.st_name).ok_or_else(|| { - if debug { - eprintln!("missing name for symbol {:?}", sym) - } - - DlError::Malformed - })?; - - let resolved = GLOBAL_SCOPE - .read() - .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), - } - } - - Ok(()) - } - - fn relocate(&self, obj: &Arc, object_data: &[u8], resolve: Resolve) -> Result<()> { - // Perform static relocations. - let elf = Elf::parse(object_data).or(Err(DlError::Malformed))?; - let debug = self.config.debug_flags.contains(DebugFlags::LOAD); // FIXME - - trace!("link {}", obj.name); - - let mmap = &obj.mmap; - let b = mmap.as_ptr() as usize; - - // Relocate - for rel in elf.dynrelas.iter().chain(elf.dynrels.iter()) { - trace!( - " rel {}: {:x?}", - reloc::r_to_str(rel.r_type, elf.header.e_machine), - rel - ); - - let symbol = if rel.r_sym > 0 { - let sym = elf.dynsyms.get(rel.r_sym).ok_or_else(|| { - if debug { - eprintln!("[ld.so]: missing symbol for relocation {:?}", rel) - } - - DlError::Malformed - })?; - - let name = elf.dynstrtab.get_at(sym.st_name).ok_or_else(|| { - if debug { - eprintln!("[ld.so]: missing name for symbol {:?}", sym) - } - - DlError::Malformed - })?; - - let symbol = GLOBAL_SCOPE - .read() - .get_sym(name) - .or_else(|| obj.scope.get_sym(name)) - .map(|(sym, _, obj)| (sym, obj.tls_offset)); - - // TODO: below doesn't work because of missing __preinit_array_{start,end} and __init_array_{start,end} symbols in dynamic linked programs - // TODO: found := symbol.is_some() - /* - if !found { - return Err(Error::Malformed(format!("missing symbol for name {}", name))); - } - */ - symbol - } else { - None - }; - - let (s, t) = symbol - .as_ref() - .map(|(sym, t)| (sym.as_ptr() as usize, *t)) - .unwrap_or((0, 0)); - - let a = rel.r_addend.unwrap_or(0) as usize; - - let ptr = if obj.pie { - (b + rel.r_offset as usize) as *mut u8 - } else { - rel.r_offset as *mut u8 - }; - let set_u64 = |value| { - trace!( - " set_u64 r_type={} value={:#x} ptr={:p} base={:#x} mmap_len={:#x} a={:#x}", - rel.r_type, - value, - ptr, - b, - mmap.len(), - a - ); - unsafe { - *(ptr as *mut u64) = value; - } - }; - - match rel.r_type { - reloc::R_X86_64_64 => { - set_u64((s + a) as u64); - } - reloc::R_X86_64_DTPMOD64 => { - set_u64(obj.tls_module_id as u64); - } - reloc::R_X86_64_DTPOFF64 => { - if s != 0 { - set_u64((s - b) as u64); - } else { - set_u64(s as u64); - } - } - reloc::R_X86_64_GLOB_DAT | reloc::R_X86_64_JUMP_SLOT => { - set_u64(s as u64); - } - reloc::R_X86_64_RELATIVE => { - set_u64((b + a) as u64); - } - reloc::R_X86_64_TPOFF64 => { - if rel.r_sym > 0 { - let (sym, _) = symbol - .as_ref() - .expect("R_X86_64_TPOFF64 called without valid symbol"); - set_u64((sym.value + a).wrapping_sub(t) as u64); - } else { - set_u64(a.wrapping_sub(t) as u64); - } - } - reloc::R_X86_64_IRELATIVE => unsafe { - let f: unsafe extern "C" fn() -> u64 = transmute(b + a); - set_u64(f()); - }, - reloc::R_X86_64_COPY => unsafe { - let (sym, _) = symbol - .as_ref() - .expect("R_X86_64_COPY called without valid symbol"); - ptr::copy_nonoverlapping(sym.as_ptr() as *const u8, ptr, sym.size); - }, - _ => { - panic!( - " {} unsupported", - reloc::r_to_str(rel.r_type, elf.header.e_machine) - ); - } - } - } - - self.lazy_relocate(obj, &elf, resolve)?; - - // Protect pages - for ph in elf - .program_headers - .iter() - .filter(|ph| ph.p_type == program_header::PT_LOAD) - { - let voff = ph.p_vaddr % ph.p_align; - let vaddr = (ph.p_vaddr - voff) as usize; - let vsize = ((ph.p_memsz + voff) as usize).next_multiple_of(ph.p_align as usize); - let mut prot = 0; - if ph.p_flags & program_header::PF_R == program_header::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 & program_header::PF_X == program_header::PF_X { - prot |= sys_mman::PROT_EXEC; - } else if ph.p_flags & program_header::PF_W == program_header::PF_W { - prot |= sys_mman::PROT_WRITE; - } - - unsafe { - let ptr = if obj.pie { - mmap.as_ptr().add(vaddr) - } else { - vaddr as *const u8 - }; - trace!(" prot {:#x}, {:#x}: {:p}, {:#x}", vaddr, vsize, ptr, prot); - Sys::mprotect(ptr as *mut c_void, vsize, prot).map_err(|err| { - if debug { - eprintln!("[ld.so]: failed to mprotect: {}", err) - } - - DlError::Malformed - })?; - } - } - - Ok(()) + Ok(file) } fn run_init(&self, obj: &DSO) { @@ -1122,41 +892,41 @@ extern "C" fn __plt_resolve_inner(obj: *const DSO, relocation_index: c_uint) -> let obj_base = obj.mmap.as_ptr() as usize; let jmprel = obj.dynamic.jmprel; - let rela = unsafe { - &*((obj_base + jmprel) as *const Rela64).add(relocation_index as usize) - }; - - let endian = Endianness::default(); - - assert_eq!(rela.r_type(endian, false), reloc::R_X86_64_JUMP_SLOT); + 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 - .symbols - .symbol(rela.symbol(endian, false).unwrap()) + .dynamic + .symbol(rela.symbol(NativeEndian, false).unwrap()) .expect("symbol not found"); - assert_ne!(sym.st_name(endian), 0); + assert_ne!(sym.st_name(NativeEndian), 0); - let name = core::str::from_utf8(obj.dynamic.dynstrtab.get(sym.st_name(endian)).unwrap()) - .expect("non utf8 symbol name"); + 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(); - trace!("@plt: {} -> *mut {:#x}", name, resolved as usize); - let ptr = if obj.pie { - (obj_base as u64 + rela.r_offset(endian)) as *mut u64 + (obj_base as u64 + rela.r_offset(NativeEndian)) as *mut u64 } else { - rela.r_offset(endian) as *mut u64 + rela.r_offset(NativeEndian) as *mut u64 }; + trace!("@plt: {} -> *mut {:p}", name, ptr); + unsafe { *ptr = resolved as u64 } resolved } extern "C" { - fn __plt_resolve_trampoline() -> usize; + pub(super) fn __plt_resolve_trampoline() -> usize; } #[cfg(target_arch = "x86_64")] diff --git a/src/ld_so/mod.rs b/src/ld_so/mod.rs index 8544f13feb..eb3bb8c369 100644 --- a/src/ld_so/mod.rs +++ b/src/ld_so/mod.rs @@ -5,7 +5,11 @@ #![warn(warnings, unused_variables)] use core::{mem, ptr}; -use goblin::elf::program_header::{self, program_header32, program_header64, ProgramHeader}; +use object::{ + elf::{self, ProgramHeader32, ProgramHeader64}, + read::elf::ProgramHeader, + Endianness, +}; use self::tcb::{Master, Tcb}; use crate::{ @@ -38,6 +42,9 @@ static mut STATIC_TCB_MASTER: Master = Master { #[inline(never)] pub fn static_init(sp: &'static Stack) { + const SIZEOF_PHDR64: usize = mem::size_of::>(); + const SIZEOF_PHDR32: usize = mem::size_of::>(); + let mut phdr_opt = None; let mut phent_opt = None; let mut phnum_opt = None; @@ -65,31 +72,47 @@ pub fn static_init(sp: &'static Stack) { for i in 0..phnum { let ph_addr = phdr + phent * i; - let ph: ProgramHeader = match phent { - program_header32::SIZEOF_PHDR => { - unsafe { *(ph_addr as *const program_header32::ProgramHeader) }.into() - } - program_header64::SIZEOF_PHDR => { - unsafe { *(ph_addr as *const program_header64::ProgramHeader) }.into() - } + let endian = Endianness::default(); + let (p_align, p_filesz, p_memsz, p_type, p_vaddr) = match phent { + SIZEOF_PHDR64 => unsafe { + let ph = &*(ph_addr as *const ProgramHeader64); + ( + ph.p_align(endian) as usize, + ph.p_filesz(endian) as usize, + ph.p_memsz(endian) as usize, + ph.p_type(endian), + ph.p_vaddr(endian) as usize, + ) + }, + + SIZEOF_PHDR32 => unsafe { + let ph = &*(ph_addr as *const ProgramHeader32); + ( + ph.p_align(endian) as usize, + ph.p_filesz(endian) as usize, + ph.p_memsz(endian) as usize, + ph.p_type(endian), + ph.p_vaddr(endian) as usize, + ) + }, _ => panic_notls(format_args!("unknown AT_PHENT size {}", phent)), }; let page_size = Sys::getpagesize(); - let voff = ph.p_vaddr as usize % page_size; + let voff = p_vaddr % page_size; // let vaddr = ph.p_vaddr as usize - voff; - let vsize = ((ph.p_memsz as usize + voff + page_size - 1) / page_size) * page_size; + let vsize = ((p_memsz + voff + page_size - 1) / page_size) * page_size; - if ph.p_type == program_header::PT_TLS { - let valign = if ph.p_align > 0 { - ((ph.p_memsz + (ph.p_align - 1)) / ph.p_align) * ph.p_align + if p_type == elf::PT_TLS { + let valign = if p_align > 0 { + ((p_memsz + (p_align - 1)) / p_align) * p_align } else { - ph.p_memsz - } as usize; + p_memsz + }; unsafe { - STATIC_TCB_MASTER.ptr = ph.p_vaddr as usize as *const u8; - STATIC_TCB_MASTER.len = ph.p_filesz as usize; + STATIC_TCB_MASTER.ptr = p_vaddr as *const u8; + STATIC_TCB_MASTER.len = p_filesz; STATIC_TCB_MASTER.offset = valign; let tcb = Tcb::new(vsize).expect_notls("failed to allocate TCB"); diff --git a/src/ld_so/start.rs b/src/ld_so/start.rs index 19fdbdcfd1..5f40a6ef4e 100644 --- a/src/ld_so/start.rs +++ b/src/ld_so/start.rs @@ -26,7 +26,12 @@ use super::{ PATH_SEP, }; use crate::header::sys_auxv::{AT_ENTRY, AT_PHDR}; -use goblin::elf::header::header64::SIZEOF_EHDR; + +#[cfg(target_pointer_width = "32")] +pub const SIZEOF_EHDR: usize = 52; + +#[cfg(target_pointer_width = "64")] +pub const SIZEOF_EHDR: usize = 64; unsafe fn get_argv(mut ptr: *const usize) -> (Vec, *const usize) { //traverse the stack and collect argument vector diff --git a/src/ld_so/tcb.rs b/src/ld_so/tcb.rs index 2fc7205788..773dab48dc 100644 --- a/src/ld_so/tcb.rs +++ b/src/ld_so/tcb.rs @@ -1,4 +1,4 @@ -use alloc::{string::ToString, vec::Vec}; +use alloc::vec::Vec; use core::{ cell::UnsafeCell, mem, @@ -7,7 +7,6 @@ use core::{ sync::atomic::AtomicBool, }; use generic_rt::GenericTcb; -use goblin::error::{Error, Result}; use crate::{ header::sys_mman, @@ -17,6 +16,8 @@ use crate::{ sync::{mutex::Mutex, waitval::Waitval}, }; +use super::linker::DlError; + #[repr(C)] #[derive(Debug, Clone)] pub struct Master { @@ -76,7 +77,7 @@ impl Tcb { /// Create a new TCB /// /// `size` is the size of the TLS in bytes. - pub unsafe fn new(size: usize) -> Result<&'static mut Self> { + pub unsafe fn new(size: usize) -> Result<&'static mut Self, DlError> { let page_size = Sys::getpagesize(); let (_abi_page, tls, tcb_page) = Self::os_new(size.next_multiple_of(page_size))?; @@ -145,7 +146,7 @@ impl Tcb { } /// Copy data from masters - pub unsafe fn copy_masters(&mut self) -> Result<()> { + pub unsafe fn copy_masters(&mut self) -> Result<(), DlError> { //TODO: Complain if masters or tls exist without the other if let Some(tls) = self.tls() { if let Some(masters) = self.masters() { @@ -175,7 +176,7 @@ impl Tcb { ); tls_data.copy_from_slice(data); } else { - return Err(Error::Malformed(format!("failed to copy tls master {}", i))); + return Err(DlError::Malformed); } } self.num_copied_masters = masters.len(); @@ -238,7 +239,7 @@ impl Tcb { } /// Mapping with correct flags for TCB and TLS - unsafe fn map(size: usize) -> Result<&'static mut [u8]> { + unsafe fn map(size: usize) -> Result<&'static mut [u8], DlError> { let ptr = Sys::mmap( ptr::null_mut(), size, @@ -247,7 +248,7 @@ impl Tcb { -1, 0, ) - .map_err(|_| Error::Malformed("failed to map tls".to_string()))?; + .map_err(|_| DlError::Oom)?; ptr::write_bytes(ptr as *mut u8, 0, size); Ok(slice::from_raw_parts_mut(ptr as *mut u8, size)) @@ -257,7 +258,7 @@ impl Tcb { #[cfg(any(target_os = "linux", target_os = "redox"))] unsafe fn os_new( size: usize, - ) -> Result<(&'static mut [u8], &'static mut [u8], &'static mut [u8])> { + ) -> Result<(&'static mut [u8], &'static mut [u8], &'static mut [u8]), DlError> { let page_size = Sys::getpagesize(); let abi_tls_tcb = Self::map(page_size + size + page_size)?; let (abi, tls_tcb) = abi_tls_tcb.split_at_mut(page_size); diff --git a/src/lib.rs b/src/lib.rs index 76fd2b7818..f1d0025e31 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -23,6 +23,7 @@ #![feature(lang_items)] #![feature(let_chains)] #![feature(linkage)] +#![feature(naked_functions)] #![feature(pointer_is_aligned_to)] #![feature(ptr_as_uninit)] #![feature(slice_as_chunks)] @@ -44,7 +45,6 @@ #[macro_use] extern crate alloc; extern crate cbitset; -extern crate goblin; extern crate memchr; extern crate posix_regex; extern crate rand;