std/sys/pal/unix/weak/dlsym.rs
1use crate::ffi::{CStr, c_char, c_void};
2use crate::marker::PhantomData;
3use crate::ops::FnPtr;
4use crate::sync::atomic::{Atomic, AtomicPtr, Ordering};
5use crate::{mem, ptr};
6
7#[cfg(test)]
8#[path = "./tests.rs"]
9mod tests;
10
11pub(crate) macro weak {
12 (fn $name:ident($($param:ident : $t:ty),* $(,)?) -> $ret:ty;) => (
13 static DLSYM: DlsymWeak<unsafe extern "C" fn($($t),*) -> $ret> = {
14 let Ok(name) = CStr::from_bytes_with_nul(concat!(stringify!($name), '\0').as_bytes()) else {
15 panic!("symbol name may not contain NUL")
16 };
17
18 // SAFETY: Whoever calls the function pointer returned by `get()`
19 // is responsible for ensuring that the signature is correct. Just
20 // like with extern blocks, this is syntactically enforced by making
21 // the function pointer be unsafe.
22 unsafe { DlsymWeak::new(name) }
23 };
24
25 let $name = &DLSYM;
26 )
27}
28
29pub(crate) struct DlsymWeak<F> {
30 /// A pointer to the nul-terminated name of the symbol.
31 // Use a pointer instead of `&'static CStr` to save space.
32 name: *const c_char,
33 func: Atomic<*mut libc::c_void>,
34 _marker: PhantomData<F>,
35}
36
37impl<F: FnPtr> DlsymWeak<F> {
38 /// # Safety
39 ///
40 /// If the signature of `F` does not match the signature of the symbol (if
41 /// it exists), calling the function pointer returned by `get()` is
42 /// undefined behaviour.
43 pub const unsafe fn new(name: &'static CStr) -> Self {
44 DlsymWeak {
45 name: name.as_ptr(),
46 func: AtomicPtr::new(ptr::without_provenance_mut(1)),
47 _marker: PhantomData,
48 }
49 }
50
51 #[inline]
52 pub fn get(&self) -> Option<F> {
53 // The caller is presumably going to read through this value
54 // (by calling the function we've dlsymed). This means we'd
55 // need to have loaded it with at least C11's consume
56 // ordering in order to be guaranteed that the data we read
57 // from the pointer isn't from before the pointer was
58 // stored. Rust has no equivalent to memory_order_consume,
59 // so we use an acquire load (sorry, ARM).
60 //
61 // Now, in practice this likely isn't needed even on CPUs
62 // where relaxed and consume mean different things. The
63 // symbols we're loading are probably present (or not) at
64 // init, and even if they aren't the runtime dynamic loader
65 // is extremely likely have sufficient barriers internally
66 // (possibly implicitly, for example the ones provided by
67 // invoking `mprotect`).
68 //
69 // That said, none of that's *guaranteed*, so we use acquire.
70 match self.func.load(Ordering::Acquire) {
71 func if func.addr() == 1 => self.initialize(),
72 func if func.is_null() => None,
73 // SAFETY:
74 // `func` is not null and `F` implements `FnPtr`, thus this
75 // transmutation is well-defined. It is the responsibility of the
76 // creator of this `DlsymWeak` to ensure that calling the resulting
77 // function pointer does not result in undefined behaviour (though
78 // the `weak!` macro delegates this responsibility to the caller
79 // of the function by using `unsafe` function pointers).
80 // FIXME: use `transmute` once it stops complaining about generics.
81 func => Some(unsafe { mem::transmute_copy::<*mut c_void, F>(&func) }),
82 }
83 }
84
85 // Cold because it should only happen during first-time initialization.
86 #[cold]
87 fn initialize(&self) -> Option<F> {
88 // SAFETY: `self.name` was created from a `&'static CStr` and is
89 // therefore a valid C string pointer.
90 let val = unsafe { libc::dlsym(libc::RTLD_DEFAULT, self.name) };
91 // This synchronizes with the acquire load in `get`.
92 self.func.store(val, Ordering::Release);
93
94 if val.is_null() {
95 None
96 } else {
97 // SAFETY: see the comment in `get`.
98 // FIXME: use `transmute` once it stops complaining about generics.
99 Some(unsafe { mem::transmute_copy::<*mut libc::c_void, F>(&val) })
100 }
101 }
102}
103
104unsafe impl<F> Send for DlsymWeak<F> {}
105unsafe impl<F> Sync for DlsymWeak<F> {}