dynlink/arch/
x86_64.rs

1use elf::{endian::NativeEndian, string_table::StringTable, symbol::SymbolTable};
2use petgraph::graph::NodeIndex;
3use tracing::error;
4
5use crate::{
6    context::{
7        relocate::{EitherRel, RelocCache},
8        Context,
9    },
10    library::Library,
11    symbol::LookupFlags,
12    tls::{TlsRegion, TlsVariant},
13    DynlinkError, DynlinkErrorKind,
14};
15
16pub(crate) const MINIMUM_TLS_ALIGNMENT: usize = 32;
17
18pub use elf::abi::{
19    R_X86_64_64 as REL_SYMBOLIC, R_X86_64_DTPMOD64 as REL_DTPMOD, R_X86_64_DTPOFF64 as REL_DTPOFF,
20    R_X86_64_GLOB_DAT as REL_GOT, R_X86_64_JUMP_SLOT as REL_PLT, R_X86_64_RELATIVE as REL_RELATIVE,
21    R_X86_64_TPOFF64 as REL_TPOFF, STB_WEAK,
22};
23
24#[repr(C)]
25pub struct Tcb<T> {
26    pub self_ptr: *const Tcb<T>,
27    pub dtv_len: usize,
28    pub dtv: *const usize,
29    pub runtime_data: T,
30}
31
32/// Return the TLS variant defined by the arch-specific ABI.
33pub fn get_tls_variant() -> TlsVariant {
34    TlsVariant::Variant2
35}
36
37/// Get a pointer to the current thread control block, using the thread pointer.
38///
39/// # Safety
40/// The TCB must actually contain runtime data of type T, and be initialized.
41pub unsafe fn get_current_thread_control_block<T>() -> *mut Tcb<T> {
42    let mut val: usize;
43    core::arch::asm!("mov {}, fs:0", out(reg) val);
44    val as *mut _
45}
46
47impl TlsRegion {
48    /// Get a pointer to the thread control block for this TLS region.
49    ///
50    /// # Safety
51    /// The TCB must actually contain runtime data of type T, and be initialized.
52    pub unsafe fn get_thread_control_block<T>(&self) -> *mut Tcb<T> {
53        self.get_thread_pointer_value() as *mut _
54    }
55}
56
57impl Context {
58    pub(crate) fn do_reloc(
59        &self,
60        lib: &Library,
61        rel: EitherRel,
62        strings: &StringTable,
63        syms: &SymbolTable<NativeEndian>,
64        deps_list: &[NodeIndex],
65        reloc_cache: &mut RelocCache<'_>,
66    ) -> Result<(), DynlinkError> {
67        let base = lib.base_addr() as u64;
68        let target: *mut u64 = lib.laddr_mut(rel.offset());
69        let addend = rel.addend(target);
70        let mut is_weak = false;
71        // Lookup a symbol if the relocation's symbol index is non-zero.
72        let symbol = if rel.sym() != 0 {
73            let sym = syms.get(rel.sym() as usize)?;
74            let flags = if sym.st_bind() == STB_WEAK {
75                is_weak = true;
76                LookupFlags::ALLOW_WEAK
77            } else {
78                LookupFlags::ALLOW_WEAK
79            };
80            let _start = std::time::Instant::now();
81            let r = strings
82                .get(sym.st_name as usize)
83                .map(|name| {
84                    let sym = match reloc_cache.find(name, lib.comp_id) {
85                        Some(sym) => {
86                            tracing::trace!("found {} in cache", name);
87                            Ok(sym.clone())
88                        }
89                        None => {
90                            let sym = self.lookup_symbol(lib.id(), name, flags, deps_list);
91                            if let Ok(ref sym) = sym {
92                                reloc_cache.insert(name, lib.comp_id, unsafe {
93                                    std::mem::transmute(sym.clone())
94                                });
95                            }
96                            sym
97                        }
98                    };
99
100                    (name, sym)
101                })
102                .ok();
103            tracing::trace!(
104                "lookup {:?} cost {}us",
105                r.as_ref().map(|r| r.0),
106                _start.elapsed().as_micros()
107            );
108            r
109        } else {
110            None
111        };
112        let sn = symbol.as_ref().map(|s| s.0).unwrap_or_default();
113
114        // Helper for logging errors.
115        let open_sym = || {
116            if let Some((name, sym)) = symbol {
117                if let Ok(sym) = sym {
118                    Result::<_, DynlinkError>::Ok(sym)
119                } else if is_weak {
120                    Result::<_, DynlinkError>::Ok(crate::symbol::RelocatedSymbol::new_zero(lib))
121                } else {
122                    error!("{}: needed symbol {} not found", lib, name);
123                    Err(DynlinkErrorKind::SymbolLookupFail {
124                        symname: name.into(),
125                        sourcelib: lib.name.as_str().into(),
126                    }
127                    .into())
128                }
129            } else {
130                error!("{}: invalid relocation, no symbol data", lib);
131                Err(DynlinkErrorKind::MissingSection {
132                    name: "symbol data".into(),
133                }
134                .into())
135            }
136        };
137
138        // This is where the magic happens.
139        match rel.r_type() {
140            REL_RELATIVE => unsafe { *target = base.wrapping_add_signed(addend) },
141            REL_SYMBOLIC => unsafe {
142                *target = open_sym()?.reloc_value().wrapping_add_signed(addend)
143            },
144            REL_PLT | REL_GOT => unsafe {
145                let x = open_sym()?.reloc_value();
146                *target = x;
147            },
148            REL_DTPMOD => {
149                // See the TLS module for understanding where the TLS ID is coming from.
150                let id = if rel.sym() == 0 {
151                    lib.tls_id
152                        .as_ref()
153                        .ok_or_else(|| DynlinkErrorKind::NoTLSInfo {
154                            library: lib.name.as_str().into(),
155                        })?
156                        .tls_id()
157                } else {
158                    let other_lib = open_sym()?.lib;
159                    other_lib
160                        .tls_id
161                        .as_ref()
162                        .ok_or_else(|| DynlinkErrorKind::NoTLSInfo {
163                            library: other_lib.name.as_str().into(),
164                        })?
165                        .tls_id()
166                };
167                unsafe { *target = id }
168            }
169            REL_DTPOFF => {
170                let val = open_sym().map(|sym| sym.raw_value()).unwrap_or(0);
171                unsafe { *target = val.wrapping_add_signed(addend) }
172            }
173            REL_TPOFF => {
174                let sym = open_sym()?;
175                if let Some(tls) = sym.lib.tls_id {
176                    unsafe {
177                        *target = sym
178                            .raw_value()
179                            .wrapping_sub(tls.offset() as u64)
180                            .wrapping_add_signed(addend)
181                    }
182                } else {
183                    error!(
184                        "{}: TPOFF relocations require a PT_TLS segment (sym {})",
185                        lib, sn
186                    );
187                    Err(DynlinkErrorKind::NoTLSInfo {
188                        library: lib.name.as_str().into(),
189                    })?
190                }
191            }
192            _ => {
193                error!("{}: unsupported relocation: {}", lib, rel.r_type());
194                Result::<_, DynlinkError>::Err(
195                    DynlinkErrorKind::UnsupportedReloc {
196                        library: lib.name.as_str().into(),
197                        reloc: rel.r_type().to_string().into(),
198                    }
199                    .into(),
200                )?
201            }
202        }
203        tracing::trace!("set reloc {} to {:x}", rel.r_type(), unsafe { *target });
204
205        Ok(())
206    }
207}