Skip to content

Commit

Permalink
Fix relocation to work on the latest Rust nightly.
Browse files Browse the repository at this point in the history
The trick of using a static variable initialized with an address to
obtain the pre-relocated address no longer appears to work on Rust
nightly, so replace it with code that reads the static start address
out of the `e_start` field of the executable's ELF header. This is
less "sneaky", so it should be more robust against compiler changes.

And, fix the 32-bit x86 relocation code and re-enable all the tests
on x86.
  • Loading branch information
sunfishcode committed May 25, 2024
1 parent 0f8f179 commit ee15b06
Show file tree
Hide file tree
Showing 7 changed files with 132 additions and 43 deletions.
23 changes: 19 additions & 4 deletions src/arch/aarch64.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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"))]
Expand Down Expand Up @@ -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
Expand Down
39 changes: 31 additions & 8 deletions src/arch/arm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand Down Expand Up @@ -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
Expand Down
19 changes: 16 additions & 3 deletions src/arch/riscv64.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand Down Expand Up @@ -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
Expand Down
43 changes: 32 additions & 11 deletions src/arch/x86.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand Down Expand Up @@ -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}, [eax + _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}, [eax + __ehdr_start@GOTOFF]",
out(reg) addr
)
}
addr
*/
}

/// Perform a single load operation, outside the Rust memory model.
Expand Down
20 changes: 17 additions & 3 deletions src/arch/x86_64.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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"))]
Expand Down Expand Up @@ -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
Expand Down
30 changes: 17 additions & 13 deletions src/relocate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,8 @@
#![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};
use linux_raw_sys::elf::*;
use linux_raw_sys::general::{AT_BASE, AT_ENTRY, AT_NULL, AT_PAGESZ};
Expand Down Expand Up @@ -96,10 +95,7 @@ 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 {
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
Expand All @@ -109,6 +105,10 @@ pub(super) unsafe fn relocate(envp: *mut *mut u8) {
}

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)
Expand Down Expand Up @@ -228,7 +228,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);
}
}

Expand Down Expand Up @@ -288,21 +288,25 @@ 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 ensures that we don't do a PC-relative load. An absolute address in
/// static memory must be relocated, if the executable needs relocation, so
/// we can use this to determine if we need to perform relocation.
///
/// This returns a `usize` because we don't dereference the address; we just
/// use it for address computation.
fn compute_static_start() -> usize {
fn load_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*
// we'll be able to see the pre-relocated 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.
struct StaticStart(*const c_void);
struct StaticStart(*const core::ffi::c_void);
unsafe impl Sync for StaticStart {}
static STATIC_START: StaticStart = StaticStart(crate::arch::_start as *const c_void);
let static_start_addr: *const *const c_void = &STATIC_START.0;
static STATIC_START: StaticStart = StaticStart(crate::arch::_start as *const core::ffi::c_void);
let static_start_addr: *const *const core::ffi::c_void = &STATIC_START.0;
unsafe { relocation_load(static_start_addr.addr()) }
}
1 change: 0 additions & 1 deletion tests/example_crates.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down

0 comments on commit ee15b06

Please sign in to comment.