feat(ld_so/dso): TLSDESC

The dynamic linker now works on aarch64. You can also try it on Linux by
setting `TEST_RUNNER` to `qemu-aarch64` when running `make test
TARGET=aarch64-unknown-linux-gnu`

Co-authored-by: @willnode
Signed-off-by: Anhad Singh <andypython@protonmail.com>
This commit is contained in:
Anhad Singh
2025-12-09 22:28:39 +11:00
parent 979de28ebc
commit 8572c855fc
2 changed files with 144 additions and 9 deletions
+2 -1
View File
@@ -1,3 +1,4 @@
{
"cmake.ignoreCMakeListsMissing": true
"cmake.ignoreCMakeListsMissing": true,
"editor.formatOnSave": true
}
+142 -8
View File
@@ -13,20 +13,21 @@ use object::{
use super::{
debug::{_r_debug, RTLDDebug},
linker::{__plt_resolve_trampoline, GLOBAL_SCOPE, Resolve, Scope, Symbol},
tcb::Master,
tcb::{Master, Tcb},
};
use crate::{
header::sys_mman,
header::{dl_tls::__tls_get_addr, sys_mman},
platform::{Pal, Sys, types::c_void},
};
use alloc::{
boxed::Box,
string::{String, ToString},
sync::Arc,
vec::Vec,
};
use core::{
ffi::c_char,
mem::size_of,
mem::{offset_of, size_of},
ptr::{self, NonNull},
slice,
};
@@ -208,7 +209,7 @@ impl From<&Rel> for Relocation {
// This is matched up to REL_* constants used by musl for ease of comparison
#[allow(dead_code)]
#[derive(Clone, Copy, Debug)]
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum RelocationKind {
COPY,
DTPMOD,
@@ -817,6 +818,47 @@ impl DSO {
))
}
/// `TLSDESC` relocation being an extension to the original TLS ABI spec can
/// be present in either `.rela.plt` (handled in [`Self::static_relocate`])
/// or `.rela.dyn` (handled in [`Self::lazy_relocate`]) due to the lack of a
/// standard unfortunately.
///
/// # Panics
///
/// Panics if `reloc.kind` is not `RelocationKind::TLSDESC`.
fn do_tlsdesc_reloc(&self, reloc: Relocation, ptr: *mut usize, global_scope: &Scope) {
assert!(reloc.kind == RelocationKind::TLSDESC);
let (sym, tls_module_id, tls_offset) = if reloc.sym != SymbolIndex(0) {
let sym_name = self.dynamic.symbol_name(reloc.sym).unwrap();
let (sym, _, obj) = resolve_sym(sym_name, &[global_scope, self.scope()]).unwrap();
(sym.value, obj.tls_module_id, obj.tls_offset)
} else {
(0, self.tls_module_id, self.tls_offset)
};
let resolver = unsafe { &mut *ptr };
let descriptor = unsafe { &mut *ptr.add(1) };
if self.dlopened {
let mut tls_index = crate::header::dl_tls::dl_tls_index {
ti_module: tls_module_id,
ti_offset: reloc.addend.unwrap_or_default(),
};
// Ensure the DTV entry is initialised.
unsafe { __tls_get_addr(&mut tls_index) };
*resolver = __tlsdesc_dynamic as *const () as usize;
*descriptor = Box::into_raw(Box::new(TlsDescriptor {
module_id: tls_module_id - 1,
addend: sym + reloc.addend.unwrap_or_default(),
})) as usize;
} else {
*resolver = __tlsdesc_static as *const () as usize;
*descriptor = sym + tls_offset + reloc.addend.unwrap_or_default();
}
}
fn static_relocate(&self, global_scope: &Scope, reloc: Relocation) -> object::Result<()> {
let b = self.mmap.as_ptr() as usize;
@@ -841,7 +883,7 @@ impl DSO {
let (s, t, tls_id) = sym
.as_ref()
.map(|(sym, obj)| (sym.as_ptr() as usize, obj.tls_offset, obj.tls_module_id))
//TODO: is self.tls_module_id the right fallback?
// TODO: is self.tls_module_id the right fallback?
.unwrap_or((0, 0, self.tls_module_id));
let ptr = if self.pie {
@@ -858,14 +900,14 @@ impl DSO {
},
};
//TODO: support different sizes?
// TODO: support different sizes?
let set_usize = |value| unsafe {
*(ptr as *mut usize) = value;
};
match reloc.kind {
RelocationKind::DTPMOD => set_usize(tls_id),
//TODO: Subtract DTP_OFFSET, which is 0x800 on riscv64, 0 on x86?
// TODO: Subtract DTP_OFFSET, which is 0x800 on riscv64, 0 on x86?
RelocationKind::DTPOFF => {
if reloc.sym.0 > 0 {
let (sym, _) = sym
@@ -909,6 +951,9 @@ impl DSO {
// SAFETY: Both the source and destination have the same size.
ptr::copy_nonoverlapping(sym.as_ptr() as *const u8, ptr, sym.size);
},
RelocationKind::TLSDESC => {
self.do_tlsdesc_reloc(reloc, ptr.cast::<usize>(), global_scope)
}
_ => unimplemented!("relocation type {:?}", reloc.kind),
}
@@ -927,7 +972,8 @@ impl DSO {
unsafe {
got.add(1).write(core::ptr::addr_of!(*self) as usize);
got.add(2).write(__plt_resolve_trampoline as usize);
got.add(2)
.write(__plt_resolve_trampoline as *const () as usize);
}
let relsz = if self.dynamic.explicit_addend {
@@ -975,6 +1021,13 @@ impl DSO {
}
}
(RelocationKind::TLSDESC, Resolve::Now) => {
self.do_tlsdesc_reloc(reloc, ptr, global_scope);
}
(RelocationKind::TLSDESC, Resolve::Lazy) => {
unreachable!("TLSDESC cannot be lazily resolved")
}
_ => {
unimplemented!(
"relocation type {:?} with resolve {:?}",
@@ -1092,3 +1145,84 @@ pub fn resolve_sym<'a>(
) -> Option<(Symbol<'a>, SymbolBinding, Arc<DSO>)> {
scopes.iter().find_map(|scope| scope.get_sym(name))
}
#[repr(C)]
struct TlsDescriptor {
module_id: usize,
addend: usize,
}
#[cfg(target_arch = "aarch64")]
#[unsafe(naked)]
unsafe extern "C" fn __tlsdesc_static() {
core::arch::naked_asm!("ldr x0, [x0, #8]", "ret")
}
#[cfg(target_arch = "x86_64")]
#[unsafe(naked)]
unsafe extern "C" fn __tlsdesc_static() {
core::arch::naked_asm!("mov rax, [rax + 8]", "ret")
}
unsafe extern "C" {
fn __tlsdesc_dynamic();
}
#[cfg(target_arch = "x86_64")]
core::arch::global_asm!(
"
.global __tlsdesc_dynamic
.hidden __tlsdesc_dynamic
__tlsdesc_dynamic:
push rbx
push rcx
mov rax, [rax + 8] // TLS descriptor
mov rbx, [rax + {TLS_DESCRIPTOR_MODULE_ID_OFF}] // tls_descriptor.module_id
mov rcx, [rax + {TLS_DESCRIPTOR_ADDEND_OFF}] // tls_descriptor.addend
mov rax, qword ptr fs:[{DTV_PTR_OFF}] // tcb.dtv_ptr
// tcb.dtv_ptr[tls_descriptor.module_id] + tls_descriptor.addend
mov rax, [rax + rbx * 8]
add rax, rcx
sub rax, qword ptr fs:[{TCB_SELF_PTR_OFF}]
pop rcx
pop rbx
ret
",
TLS_DESCRIPTOR_MODULE_ID_OFF = const offset_of!(TlsDescriptor, module_id),
TLS_DESCRIPTOR_ADDEND_OFF = const offset_of!(TlsDescriptor, addend),
DTV_PTR_OFF = const offset_of!(Tcb, dtv_ptr),
TCB_SELF_PTR_OFF = const offset_of!(Tcb, generic.tcb_ptr),
);
#[cfg(target_arch = "aarch64")]
core::arch::global_asm!(
"
.global __tlsdesc_dynamic
.hidden __tlsdesc_dynamic
__tlsdesc_dynamic:
stp x1, x2, [sp, #-16]!
ldr x0, [x0, #8] // TLS descriptor
// x0 := tls_descriptor.module_id
// x1 := tls_descriptor.addend
ldp x0, x1, [x0]
mrs x2, tpidr_el0 // ABI ptr
ldr x2, [x2] // TCB ptr
sub x1, x1, x2 // tls_descriptor.addend -= tcb
ldr x2, [x2, {DTV_PTR_OFF}] // tcb.dtv_ptr
ldr x2, [x2, x0, lsl #3] // tcb.dtv_ptr[tls_descriptor.module_id]
add x0, x2, x1 // tcb.dtv_ptr[tls_descriptor.module_id] + tls_descriptor.addend
ldp x1, x2, [sp], #16
ret
",
DTV_PTR_OFF = const offset_of!(Tcb, dtv_ptr),
);