diff --git a/Makefile b/Makefile index 3c10c9d893..2bb04c48c1 100644 --- a/Makefile +++ b/Makefile @@ -133,6 +133,10 @@ $(BUILD)/$(PROFILE)/libc.so: $(BUILD)/$(PROFILE)/librelibc.a $(BUILD)/openlibm/l $(LINKFLAGS) \ -o $@ +$(BUILD)/$(PROFILE)/ld_so: $(BUILD)/$(PROFILE)/ld_so.o $(BUILD)/$(PROFILE)/crti.o $(BUILD)/$(PROFILE)/libc.a $(BUILD)/$(PROFILE)/crtn.o + # TODO: merge ld.so with libc.so: --dynamic-list=dynamic-list-file + $(LD) --shared -Bsymbolic --no-relax -T ld_so/ld_script/$(TARGET).ld --allow-multiple-definition --gc-sections $^ -o $@ + # Debug targets $(BUILD)/debug/libc.a: $(BUILD)/debug/librelibc.a $(BUILD)/openlibm/libopenlibm.a @@ -145,7 +149,7 @@ $(BUILD)/debug/libc.a: $(BUILD)/debug/librelibc.a $(BUILD)/openlibm/libopenlibm. $(AR) -M < "$@.mri" $(BUILD)/debug/librelibc.a: $(SRC) - $(CARGO) rustc $(CARGOFLAGS) -- --emit link=$@ -g -C debug-assertions=no $(RUSTCFLAGS) + $(CARGO) rustc $(CARGOFLAGS) -- --emit link=$@ -g -C debug-assertions=no -C opt-level=2 $(RUSTCFLAGS) ./renamesyms.sh "$@" "$(BUILD)/debug/deps/" ./stripcore.sh "$@" touch $@ @@ -166,9 +170,6 @@ $(BUILD)/debug/ld_so.o: $(SRC) $(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 - $(LD) --no-relax -T ld_so/ld_script/$(TARGET).ld --allow-multiple-definition --gc-sections $^ -o $@ - # Release targets $(BUILD)/release/libc.a: $(BUILD)/release/librelibc.a $(BUILD)/openlibm/libopenlibm.a @@ -204,9 +205,6 @@ $(BUILD)/release/ld_so.o: $(SRC) $(CARGO) rustc --release --manifest-path ld_so/Cargo.toml $(CARGOFLAGS) -- --emit obj=$@ -C panic=abort $(RUSTCFLAGS) touch $@ -$(BUILD)/release/ld_so: $(BUILD)/release/ld_so.o $(BUILD)/release/crti.o $(BUILD)/release/libc.a $(BUILD)/release/crtn.o - $(LD) --no-relax -T ld_so/ld_script/$(TARGET).ld --allow-multiple-definition --gc-sections $^ -o $@ - # Other targets $(BUILD)/openlibm: openlibm diff --git a/ld_so/src/lib.rs b/ld_so/src/lib.rs index 6c7d4736a9..ee7ab3b6cf 100644 --- a/ld_so/src/lib.rs +++ b/ld_so/src/lib.rs @@ -7,14 +7,18 @@ use core::arch::global_asm; #[cfg(target_arch = "aarch64")] global_asm!( " +.weak _DYNAMIC +.hidden _DYNAMIC + .global _start _start: mov x28, sp // align stack to 16 bytes and sp, x28, #0xfffffffffffffff0 - adr x1, _start mov x0, x28 - // ld_so_start(stack=x0, ld_entry=x1) + adrp x1, _DYNAMIC + add x1, x1, #:lo12:_DYNAMIC + // ld_so_start(stack=x0, dynamic=x1) bl relibc_ld_so_start // restore original stack, clear registers, and jump to the new start function mov sp, x28 @@ -69,16 +73,18 @@ _start: #[cfg(target_arch = "x86_64")] global_asm!( " +.weak _DYNAMIC +.hidden _DYNAMIC + .globl _start _start: - lea rsi, [rip + _start] - # Save original stack and align stack to 16 bytes mov rbp, rsp and rsp, 0xfffffffffffffff0 - # Call ld_so_start(stack=rdi, ld_entry=rsi) + # Call ld_so_start(stack=rdi, dynamic=rsi) mov rdi, rbp + lea rsi, [rip + _DYNAMIC] call relibc_ld_so_start # Restore original stack, clear registers, and jump to new start function diff --git a/src/ld_so/dso.rs b/src/ld_so/dso.rs index 5e94d0f636..55c6c559f1 100644 --- a/src/ld_so/dso.rs +++ b/src/ld_so/dso.rs @@ -64,6 +64,11 @@ mod shim { 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); @@ -662,10 +667,6 @@ impl DSO { is_pie: bool, (_, entries): (&ProgramHeader, &[Dyn]), ) -> object::Result<(Dynamic<'a>, Option)> { - const DT_RELRSZ: u32 = 35; - const DT_RELR: u32 = 36; - const DT_RELRENT: u32 = 37; - let mut runpath = None; let mut got = None; let mut needed = vec![]; @@ -1078,34 +1079,8 @@ impl DSO { let global_scope = GLOBAL_SCOPE.read(); let base = self.mmap.as_ptr(); - // Apply DT_RELR relative relocations. - let mut addr = ptr::null_mut(); - for &entry in self.dynamic.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::() - 1) }; - } + unsafe { + apply_relr(base, self.dynamic.relr); } self.dynamic @@ -1295,3 +1270,35 @@ __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::() - 1) }; + } + } +} diff --git a/src/ld_so/start.rs b/src/ld_so/start.rs index 4d237ecb92..ac37af04ea 100644 --- a/src/ld_so/start.rs +++ b/src/ld_so/start.rs @@ -9,15 +9,19 @@ use alloc::{ string::{String, ToString}, vec::Vec, }; -use object::{NativeEndian, elf::PT_PHDR, read::elf::ProgramHeader as _}; +use object::{ + NativeEndian, + elf::{self, PT_PHDR}, + read::elf::{Dyn as _, ProgramHeader as _, Rela as _}, +}; use crate::{ c_str::CStr, header::{ - elf::{AT_ENTRY, AT_PHDR, AT_PHENT, AT_PHNUM}, + elf::{AT_BASE, AT_PHDR, AT_PHENT, AT_PHNUM, PT_DYNAMIC}, unistd, }, - ld_so::dso::ProgramHeader, + ld_so::dso::{DT_RELR, DT_RELRENT, DT_RELRSZ, Dyn, ProgramHeader, Rela, Relr, apply_relr}, platform::{auxv_iter, get_auxvs, types::c_char}, start::Stack, sync::mutex::Mutex, @@ -153,35 +157,111 @@ fn resolve_path_name( } #[unsafe(no_mangle)] -pub unsafe extern "C" fn relibc_ld_so_start(sp: &'static mut Stack, ld_entry: usize) -> usize { +pub unsafe extern "C" fn relibc_ld_so_start(sp: &'static mut Stack, dynamic: *const Dyn) -> usize { let mut at_phdr = None; let mut at_phnum = None; let mut at_phent = None; - let mut at_entry = None; + let mut at_base = None; for [kind, value] in unsafe { auxv_iter(sp.auxv().cast::()) } { match kind { AT_PHDR => at_phdr = Some(value as *const ProgramHeader), AT_PHNUM => at_phnum = Some(value), AT_PHENT => at_phent = Some(value), - AT_ENTRY => at_entry = Some(value), + AT_BASE => at_base = Some(value), _ => {} } } - let at_entry = at_entry.expect("`AT_ENTRY` must be present"); - let (is_manual, base_addr) = if at_entry == ld_entry { - (true, None) + let at_phdr = at_phdr.unwrap(); + let at_phnum = at_phnum.expect("`AT_PHNUM` must be present if `AT_PHDR` is"); + let at_phent = at_phent.expect("`AT_PHENT` must be present if `AT_PHDR` is"); + assert!(!at_phdr.is_null() && at_phnum != 0 && at_phent == size_of::()); + let phdrs = unsafe { slice::from_raw_parts(at_phdr, at_phnum) }; + + // [`AT_BASE`] is the base address at which the dynamic linker was loaded in memory. On Linux, + // this entry is always present. If the dynamic linker was not loaded (i.e. run as a command), + // its value is 0. On Redox, it is only present if the dynamic linker is loaded. + let at_base = at_base.unwrap_or_default(); + + let (is_manual, self_base) = if at_base != 0 { + (false, at_base) } else { + // Dynamic linker was run as a command. + let ph = phdrs + .iter() + .find(|ph| ph.p_type(NativeEndian) == PT_DYNAMIC as u32) + .unwrap(); + (true, unsafe { + dynamic.byte_sub(ph.p_vaddr(NativeEndian) as usize) as usize + }) + }; + + let mut i = dynamic; + let mut rela_ptr = None; + let mut rela_len = None; + let mut relr_ptr = None; + let mut relr_len = None; + loop { + let entry = unsafe { &*i }; + let val = entry.d_val(NativeEndian); + let ptr = val as *const u8; + match entry.d_tag(NativeEndian) as u32 { + elf::DT_NULL => break, + elf::DT_RELA => rela_ptr = Some(ptr.cast::()), + elf::DT_RELASZ => rela_len = Some(val as usize / size_of::()), + elf::DT_RELAENT => { + assert_eq!(val as usize, size_of::(),); + } + DT_RELR => relr_ptr = Some(ptr.cast::()), + DT_RELRSZ => relr_len = Some(val as usize / size_of::()), + DT_RELRENT => { + assert_eq!(val as usize, size_of::()); + } + _ => {} + } + i = unsafe { i.add(1) }; + } + + unsafe fn get_array<'a, T>( + ptr: Option<*const T>, + len: Option, + base_addr: 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.byte_add(base_addr), len) } + } else { + assert!(len.is_none()); + &[] + } + } + + for rela in unsafe { get_array(rela_ptr, rela_len, self_base) } { + let offset = rela.r_offset(NativeEndian); + let addend = rela.r_addend(NativeEndian) as isize; + let reloc_ptr = (offset + self_base as u64) as *mut usize; + match rela.r_type(NativeEndian, false) as u32 { + elf::R_X86_64_RELATIVE => { + unsafe { reloc_ptr.write((self_base as isize + addend) as usize) }; + } + _ => {} + } + } + + unsafe { + let relr = get_array(relr_ptr, relr_len, self_base); + apply_relr(self_base as *const u8, relr); + } + + println!( + "[ld.so]: relocated self at {self_base:#x} (DT_RELASZ={rela_len:?}, DT_RELRSZ={relr_len:?})" + ); + + let mut base_addr = None; + if is_manual && cfg!(target_os = "linux") { // if we are not running in manual mode, then the main // program is already loaded by the kernel and we want // to use it. on redox, we treat it the same. - let at_phdr = at_phdr.unwrap(); - let at_phnum = at_phnum.expect("`AT_PHNUM` must be present if `AT_PHDR` is"); - let at_phent = at_phent.expect("`AT_PHENT` must be present if `AT_PHDR` is"); - assert!(!at_phdr.is_null() && at_phnum != 0 && at_phent == size_of::()); - let phdrs = unsafe { slice::from_raw_parts(at_phdr, at_phnum) }; - - let mut base_addr = None; for ph in phdrs.iter() { if ph.p_type(NativeEndian) == PT_PHDR { assert!(base_addr.is_none(), "`PT_PHDR` cannot occur more than once"); @@ -193,15 +273,7 @@ pub unsafe extern "C" fn relibc_ld_so_start(sp: &'static mut Stack, ld_entry: us } as usize); } } - - ( - false, - #[cfg(target_os = "redox")] - None, - #[cfg(target_os = "linux")] - Some(base_addr.expect("`PT_PHDR` must be present for executables")), - ) - }; + } // Setup TCB for ourselves. unsafe { @@ -279,7 +351,7 @@ pub unsafe extern "C" fn relibc_ld_so_start(sp: &'static mut Stack, ld_entry: us } // we might need global lock for this kind of stuff - _r_debug.lock().r_ldbase = ld_entry; + _r_debug.lock().r_ldbase = self_base; // TODO: Fix memory leak, although minimal. #[cfg(target_os = "redox")]