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 26, 2024
1 parent 0f8f179 commit b0876fd
Show file tree
Hide file tree
Showing 7 changed files with 140 additions and 51 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}, [{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.
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
46 changes: 25 additions & 21 deletions src/relocate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand Down Expand Up @@ -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
Expand All @@ -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)
Expand All @@ -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`.

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

Expand Down Expand Up @@ -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()) }
}
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 b0876fd

Please sign in to comment.