diff --git a/src/arch/aarch64.rs b/src/arch/aarch64.rs index 1f07237..aadda9e 100644 --- a/src/arch/aarch64.rs +++ b/src/arch/aarch64.rs @@ -3,7 +3,7 @@ use core::arch::asm; #[cfg(all(feature = "experimental-relocate", feature = "origin-start"))] #[cfg(relocation_model = "pic")] -use linux_raw_sys::elf::Elf_Dyn; +use linux_raw_sys::elf::{Elf_Dyn, Elf_Ehdr}; #[cfg(feature = "origin-signal")] use linux_raw_sys::general::__NR_rt_sigreturn; #[cfg(all(feature = "experimental-relocate", feature = "origin-start"))] @@ -49,9 +49,24 @@ pub(super) fn dynamic_table_addr() -> *const Elf_Dyn { asm!( ".weak _DYNAMIC", ".hidden _DYNAMIC", - "adrp x0, _DYNAMIC", - "add x0, x0, #:lo12:_DYNAMIC", - out("x0") addr, + "adrp {0}, _DYNAMIC", + "add {0}, {0}, :lo12:_DYNAMIC", + out(reg) addr + ); + } + addr +} + +/// Compute the dynamic address of `__ehdr_start`. +#[cfg(all(feature = "experimental-relocate", feature = "origin-start"))] +#[cfg(relocation_model = "pic")] +pub(super) fn ehdr_addr() -> *const Elf_Ehdr { + let addr: *const Elf_Ehdr; + unsafe { + asm!( + "adrp {0}, __ehdr_start", + "add {0}, {0}, :lo12:__ehdr_start", + out(reg) addr ); } addr diff --git a/src/arch/arm.rs b/src/arch/arm.rs index 2527e26..3560669 100644 --- a/src/arch/arm.rs +++ b/src/arch/arm.rs @@ -3,7 +3,7 @@ use core::arch::asm; #[cfg(all(feature = "experimental-relocate", feature = "origin-start"))] #[cfg(relocation_model = "pic")] -use linux_raw_sys::elf::Elf_Dyn; +use linux_raw_sys::elf::{Elf_Dyn, Elf_Ehdr}; #[cfg(all(feature = "experimental-relocate", feature = "origin-start"))] #[cfg(relocation_model = "pic")] use linux_raw_sys::general::{__NR_mprotect, PROT_READ}; @@ -49,13 +49,36 @@ pub(super) fn dynamic_table_addr() -> *const Elf_Dyn { asm!( ".weak _DYNAMIC", ".hidden _DYNAMIC", - "ldr r0, 2f", - "1: add r0, pc, r0", - "b 3f", - ".align 2", - "2: .word _DYNAMIC-(1b+8)", - "3:", - out("r0") addr, + "ldr {0}, 1f", + "0:", + "add {0}, pc, {0}", + "b 2f", + ".p2align 2", + "1:", + ".word _DYNAMIC-(0b+8)", + "2:", + out(reg) addr + ); + } + addr +} + +/// Compute the dynamic address of `__ehdr_start`. +#[cfg(all(feature = "experimental-relocate", feature = "origin-start"))] +#[cfg(relocation_model = "pic")] +pub(super) fn ehdr_addr() -> *const Elf_Ehdr { + let addr; + unsafe { + asm!( + "ldr {0}, 1f", + "0:", + "add {0}, pc, {0}", + "b 2f", + ".p2align 2", + "1:", + ".word __ehdr_start-(0b+8)", + "2:", + out(reg) addr ); } addr diff --git a/src/arch/riscv64.rs b/src/arch/riscv64.rs index 8a333b7..51a7c62 100644 --- a/src/arch/riscv64.rs +++ b/src/arch/riscv64.rs @@ -7,7 +7,7 @@ use core::arch::asm; #[cfg(all(feature = "experimental-relocate", feature = "origin-start"))] #[cfg(relocation_model = "pic")] -use linux_raw_sys::elf::Elf_Dyn; +use linux_raw_sys::elf::{Elf_Dyn, Elf_Ehdr}; #[cfg(all(feature = "experimental-relocate", feature = "origin-start"))] #[cfg(relocation_model = "pic")] use linux_raw_sys::general::{__NR_mprotect, PROT_READ}; @@ -52,13 +52,26 @@ pub(super) fn dynamic_table_addr() -> *const Elf_Dyn { asm!( ".weak _DYNAMIC", ".hidden _DYNAMIC", - "lla a0, _DYNAMIC", - out("a0") addr, + "lla {}, _DYNAMIC", + out(reg) addr ); } addr } +/// Compute the dynamic address of `__ehdr_start`. +#[cfg(all(feature = "experimental-relocate", feature = "origin-start"))] +#[cfg(relocation_model = "pic")] +pub(super) fn ehdr_addr() -> *const Elf_Ehdr { + let addr: *const Elf_Ehdr; + unsafe { + asm!( + "lla {}, __ehdr_start", + out(reg) addr) + }; + addr +} + /// Perform a single load operation, outside the Rust memory model. /// /// This function conceptually casts `ptr` to a `*const *mut c_void` and loads diff --git a/src/arch/x86.rs b/src/arch/x86.rs index 0d688cd..c65b4c7 100644 --- a/src/arch/x86.rs +++ b/src/arch/x86.rs @@ -3,7 +3,7 @@ use core::arch::asm; #[cfg(all(feature = "experimental-relocate", feature = "origin-start"))] #[cfg(relocation_model = "pic")] -use linux_raw_sys::elf::Elf_Dyn; +use linux_raw_sys::elf::{Elf_Dyn, Elf_Ehdr}; #[cfg(all(feature = "experimental-relocate", feature = "origin-start"))] #[cfg(relocation_model = "pic")] use linux_raw_sys::general::{__NR_mprotect, PROT_READ}; @@ -49,23 +49,44 @@ pub(super) unsafe extern "C" fn _start() -> ! { #[cfg(all(feature = "experimental-relocate", feature = "origin-start"))] #[cfg(relocation_model = "pic")] pub(super) fn dynamic_table_addr() -> *const Elf_Dyn { - panic!("acting as dynamic linker not yet supported on 32-bit x86"); - // FIXME somehow get LLVM to accept `add dword ptr [esp], _DYNAMIC-1b` or - // some other way to emit an `R_386_PC32` relocation against `_DYNAMIC`. - /* let addr; unsafe { asm!( ".weak _DYNAMIC", ".hidden _DYNAMIC", - "call 1f", - "1: add dword ptr [esp], _DYNAMIC-1b", - "pop eax", - out("eax") addr, - ); + "call 0f", + ".cfi_adjust_cfa_offset 4", + "0:", + "pop {0}", + ".cfi_adjust_cfa_offset -4", + "1:", + "add {0}, offset _GLOBAL_OFFSET_TABLE_+(1b-0b)", + "lea {0}, [{0} + _DYNAMIC@GOTOFF]", + out(reg) addr + ) + } + addr +} + +/// Compute the dynamic address of `__ehdr_start`. +#[cfg(all(feature = "experimental-relocate", feature = "origin-start"))] +#[cfg(relocation_model = "pic")] +pub(super) fn ehdr_addr() -> *const Elf_Ehdr { + let addr; + unsafe { + asm!( + "call 0f", + ".cfi_adjust_cfa_offset 4", + "0:", + "pop {0}", + ".cfi_adjust_cfa_offset -4", + "1:", + "add {0}, offset _GLOBAL_OFFSET_TABLE_+(1b-0b)", + "lea {0}, [{0} + __ehdr_start@GOTOFF]", + out(reg) addr + ) } addr - */ } /// Perform a single load operation, outside the Rust memory model. diff --git a/src/arch/x86_64.rs b/src/arch/x86_64.rs index 307f9fc..e2ed39b 100644 --- a/src/arch/x86_64.rs +++ b/src/arch/x86_64.rs @@ -3,7 +3,7 @@ use core::arch::asm; #[cfg(all(feature = "experimental-relocate", feature = "origin-start"))] #[cfg(relocation_model = "pic")] -use linux_raw_sys::elf::Elf_Dyn; +use linux_raw_sys::elf::{Elf_Dyn, Elf_Ehdr}; #[cfg(feature = "origin-signal")] use linux_raw_sys::general::__NR_rt_sigreturn; #[cfg(all(feature = "experimental-relocate", feature = "origin-start"))] @@ -49,8 +49,22 @@ pub(super) fn dynamic_table_addr() -> *const Elf_Dyn { asm!( ".weak _DYNAMIC", ".hidden _DYNAMIC", - "lea rax, [rip + _DYNAMIC]", - out("rax") addr, + "lea {}, [rip + _DYNAMIC]", + out(reg) addr + ); + } + addr +} + +/// Compute the dynamic address of `__ehdr_start`. +#[cfg(all(feature = "experimental-relocate", feature = "origin-start"))] +#[cfg(relocation_model = "pic")] +pub(super) fn ehdr_addr() -> *const Elf_Ehdr { + let addr: *const Elf_Ehdr; + unsafe { + asm!( + "lea {}, [rip + __ehdr_start]", + out(reg) addr ); } addr diff --git a/src/relocate.rs b/src/relocate.rs index b3a6abe..dfd7e00 100644 --- a/src/relocate.rs +++ b/src/relocate.rs @@ -27,7 +27,7 @@ #![allow(clippy::cmp_null)] use crate::arch::{ - dynamic_table_addr, relocation_load, relocation_mprotect_readonly, relocation_store, + dynamic_table_addr, ehdr_addr, relocation_load, relocation_mprotect_readonly, relocation_store, }; use core::ffi::c_void; use core::ptr::{null, null_mut, with_exposed_provenance}; @@ -81,12 +81,12 @@ pub(super) unsafe fn relocate(envp: *mut *mut u8) { // There are four cases to consider here: // - // 1. Static executable + // 1. Static executable. // This is the trivial case. No relocations necessary. - // 2. Static pie executable + // 2. Static pie executable. // We need to relocate ourself. `AT_PHDR` points to our own program // headers and `AT_ENTRY` to our own entry point. - // 3. Dynamic pie executable with external dynamic linker + // 3. Dynamic pie executable with external dynamic linker. // We don't need to relocate ourself as the dynamic linker has already // done this. `AT_PHDR` points to our own program headers and `AT_ENTRY` // to our own entry point. `AT_BASE` contains the relocation offset of @@ -96,19 +96,20 @@ pub(super) unsafe fn relocate(envp: *mut *mut u8) { // program headers and `AT_ENTRY` doesn't point to our own entry point. // `AT_BASE` contains our own relocation offset. - // Compute the static address of `_start`. - let static_start = compute_static_start(); - - if static_start == auxv_entry { - // This is case 1) or case 3). If AT_BASE doesn't exist, then we are + if load_static_start() == auxv_entry { + // This is case 1) or case 3). If `AT_BASE` doesn't exist, then we are // already loaded at our static address despite the lack of any dynamic - // linker. As such it would be case 1). If AT_BASE exists, we have + // linker. As such it would be case 1). If `AT_BASE` does exist, we have // already been relocated by the dynamic linker, which is case 3). // In either case there is no need to do any relocations. return; } let offset = if auxv_base == 0 { + // Obtain the static address of `_start`, which is recorded in the + // entry field of the ELF header. + let static_start = (*ehdr_addr()).e_entry; + // This is case 2) as without dynamic linker `AT_BASE` doesn't exist // and we have already excluded case 1) above. auxv_entry.wrapping_sub(static_start) @@ -119,7 +120,7 @@ pub(super) unsafe fn relocate(envp: *mut *mut u8) { auxv_base }; - // This is case 2) or 4). We need to do all R_RELATIVE relocations. + // This is case 2) or 4). We need to do all `R_RELATIVE` relocations. // There should be no other kind of relocation because we are either a // static PIE binary or a dynamic linker compiled with `-Bsymbolic`. @@ -228,7 +229,7 @@ pub(super) unsafe fn relocate(envp: *mut *mut u8) { // entry point when AT_BASE is not zero and thus a dynamic linker is in // use. In this case the assertion would fail. if auxv_base == 0 { - assert_eq!(compute_static_start(), auxv_entry); + assert_eq!(load_static_start(), auxv_entry); } } @@ -288,21 +289,24 @@ unsafe fn compute_auxp(envp: *mut *mut u8) -> *const Elf_auxv_t { auxp.add(1).cast() } -/// Compute the static address of `_start`. +/// Load the address of `_start` from static memory. +/// +/// This function contains a static variable initialized with the address of +/// the `_start` function. This requires an `R_RELATIVE` relocation, because +/// it requires an absolute address exist in memory, rather than being able +/// to use PC-relative addressing. This allows us to tell whether relocation +/// has already been done or whether we need to do it. /// /// This returns a `usize` because we don't dereference the address; we just /// use it for address computation. -fn compute_static_start() -> usize { - // This relies on the sneaky fact that we initialize a static variable with - // the address of `_start`, and if we haven't performed relocations yet, - // we'll be able to see the static address. Also, the program *just* - // started so there are no other threads yet, so loading from static memory - // without synchronization is fine. But we still use `asm` to do everything - // we can to protect this code because the optimizer won't have any idea - // what we're up to. +fn load_static_start() -> usize { + // Initialize a static variable with the address of `_start`. struct StaticStart(*const c_void); unsafe impl Sync for StaticStart {} static STATIC_START: StaticStart = StaticStart(crate::arch::_start as *const c_void); + + // Use `relocation_load` to do the load because the optimizer won't have + // any idea what we're up to. let static_start_addr: *const *const c_void = &STATIC_START.0; unsafe { relocation_load(static_start_addr.addr()) } } diff --git a/tests/example_crates.rs b/tests/example_crates.rs index 375a6bf..0431b4f 100644 --- a/tests/example_crates.rs +++ b/tests/example_crates.rs @@ -136,7 +136,6 @@ fn example_crate_origin_start_crt_static_relocation_static_relocate() { /// Act as dynamic linker, run `relocate`. #[test] -#[cfg_attr(target_arch = "x86", ignore)] // FIXME make it work on x86 fn example_crate_origin_start_dynamic_linker() { use assert_cmd::Command;