Skip to content

Commit 122c56f

Browse files
Auto merge of #146019 - joboet:better-dlsym, r=<try>
std: optimize `dlsym!` macro and add a test for it try-job: test-various try-job: dist-various-*
2 parents 68baa87 + 492e08b commit 122c56f

File tree

3 files changed

+137
-51
lines changed

3 files changed

+137
-51
lines changed

library/std/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -350,6 +350,7 @@
350350
#![feature(float_gamma)]
351351
#![feature(float_minimum_maximum)]
352352
#![feature(fmt_internals)]
353+
#![feature(fn_ptr_trait)]
353354
#![feature(generic_atomic)]
354355
#![feature(hasher_prefixfree_extras)]
355356
#![feature(hashmap_internals)]

library/std/src/sys/pal/unix/weak.rs

Lines changed: 94 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,14 @@
2222
#![allow(dead_code, unused_macros)]
2323
#![forbid(unsafe_op_in_unsafe_fn)]
2424

25-
use crate::ffi::CStr;
26-
use crate::marker::PhantomData;
27-
use crate::sync::atomic::{self, Atomic, AtomicPtr, Ordering};
25+
use crate::ffi::{CStr, c_char, c_void};
26+
use crate::marker::{FnPtr, PhantomData};
27+
use crate::sync::atomic::{Atomic, AtomicPtr, Ordering};
2828
use crate::{mem, ptr};
2929

30+
#[cfg(test)]
31+
mod tests;
32+
3033
// We can use true weak linkage on ELF targets.
3134
#[cfg(all(unix, not(target_vendor = "apple")))]
3235
pub(crate) macro weak {
@@ -62,9 +65,14 @@ impl<F: Copy> ExternWeak<F> {
6265
}
6366
}
6467

68+
#[cfg(any(
69+
target_vendor = "apple",
70+
all(target_os = "linux", target_env = "gnu"),
71+
target_os = "freebsd",
72+
))]
6573
pub(crate) macro dlsym {
6674
(fn $name:ident($($param:ident : $t:ty),* $(,)?) -> $ret:ty;) => (
67-
dlsym!(
75+
dlsym!(
6876
#[link_name = stringify!($name)]
6977
fn $name($($param : $t),*) -> $ret;
7078
);
@@ -73,84 +81,119 @@ pub(crate) macro dlsym {
7381
#[link_name = $sym:expr]
7482
fn $name:ident($($param:ident : $t:ty),* $(,)?) -> $ret:ty;
7583
) => (
76-
static DLSYM: DlsymWeak<unsafe extern "C" fn($($t),*) -> $ret> =
77-
DlsymWeak::new(concat!($sym, '\0'));
84+
static DLSYM: DlsymWeak<unsafe extern "C" fn($($t),*) -> $ret> = {
85+
let Ok(name) = CStr::from_bytes_with_nul(concat!($sym, '\0').as_bytes()) else {
86+
panic!("symbol name may not contain NUL")
87+
};
88+
89+
// SAFETY: Whoever calls the function pointer returned by `get()`
90+
// is responsible for ensuring that the signature is correct. Just
91+
// like with extern blocks, this is syntactically enforced by making
92+
// the function pointer be unsafe.
93+
unsafe { DlsymWeak::new(name) }
94+
};
95+
7896
let $name = &DLSYM;
7997
)
8098
}
99+
100+
#[cfg(any(
101+
target_vendor = "apple",
102+
all(target_os = "linux", target_env = "gnu"),
103+
target_os = "freebsd",
104+
))]
81105
pub(crate) struct DlsymWeak<F> {
82-
name: &'static str,
106+
/// A pointer to the nul-terminated name of the symbol.
107+
// Use a pointer instead of `&'static CStr` to save space.
108+
name: *const c_char,
83109
func: Atomic<*mut libc::c_void>,
84110
_marker: PhantomData<F>,
85111
}
86112

87-
impl<F> DlsymWeak<F> {
88-
pub(crate) const fn new(name: &'static str) -> Self {
113+
#[cfg(any(
114+
target_vendor = "apple",
115+
all(target_os = "linux", target_env = "gnu"),
116+
target_os = "freebsd",
117+
))]
118+
impl<F: FnPtr> DlsymWeak<F> {
119+
/// # Safety
120+
///
121+
/// If the signature of `F` does not match the signature of the symbol (if
122+
/// it exists), calling the function pointer returned by `get()` is
123+
/// undefined behaviour.
124+
pub(crate) const unsafe fn new(name: &'static CStr) -> Self {
89125
DlsymWeak {
90-
name,
126+
name: name.as_ptr(),
91127
func: AtomicPtr::new(ptr::without_provenance_mut(1)),
92128
_marker: PhantomData,
93129
}
94130
}
95131

96132
#[inline]
97133
pub(crate) fn get(&self) -> Option<F> {
98-
unsafe {
99-
// Relaxed is fine here because we fence before reading through the
100-
// pointer (see the comment below).
101-
match self.func.load(Ordering::Relaxed) {
102-
func if func.addr() == 1 => self.initialize(),
103-
func if func.is_null() => None,
104-
func => {
105-
let func = mem::transmute_copy::<*mut libc::c_void, F>(&func);
106-
// The caller is presumably going to read through this value
107-
// (by calling the function we've dlsymed). This means we'd
108-
// need to have loaded it with at least C11's consume
109-
// ordering in order to be guaranteed that the data we read
110-
// from the pointer isn't from before the pointer was
111-
// stored. Rust has no equivalent to memory_order_consume,
112-
// so we use an acquire fence (sorry, ARM).
113-
//
114-
// Now, in practice this likely isn't needed even on CPUs
115-
// where relaxed and consume mean different things. The
116-
// symbols we're loading are probably present (or not) at
117-
// init, and even if they aren't the runtime dynamic loader
118-
// is extremely likely have sufficient barriers internally
119-
// (possibly implicitly, for example the ones provided by
120-
// invoking `mprotect`).
121-
//
122-
// That said, none of that's *guaranteed*, and so we fence.
123-
atomic::fence(Ordering::Acquire);
124-
Some(func)
125-
}
126-
}
134+
// The caller is presumably going to read through this value
135+
// (by calling the function we've dlsymed). This means we'd
136+
// need to have loaded it with at least C11's consume
137+
// ordering in order to be guaranteed that the data we read
138+
// from the pointer isn't from before the pointer was
139+
// stored. Rust has no equivalent to memory_order_consume,
140+
// so we use an acquire load (sorry, ARM).
141+
//
142+
// Now, in practice this likely isn't needed even on CPUs
143+
// where relaxed and consume mean different things. The
144+
// symbols we're loading are probably present (or not) at
145+
// init, and even if they aren't the runtime dynamic loader
146+
// is extremely likely have sufficient barriers internally
147+
// (possibly implicitly, for example the ones provided by
148+
// invoking `mprotect`).
149+
//
150+
// That said, none of that's *guaranteed*, so we use acquire.
151+
match self.func.load(Ordering::Acquire) {
152+
func if func.addr() == 1 => self.initialize(),
153+
func if func.is_null() => None,
154+
// SAFETY:
155+
// `func` is not null and `F` implements `FnPtr`, thus this
156+
// transmutation is well-defined. It is the responsibility of the
157+
// creator of this `DlsymWeak` to ensure that calling the resulting
158+
// function pointer does not result in undefined behaviour (though
159+
// the `dlsym!` macro delegates this responsibility to the caller
160+
// of the function by using `unsafe` function pointers).
161+
// FIXME: use `transmute` once it stops complaining about generics.
162+
func => Some(unsafe { mem::transmute_copy::<*mut c_void, F>(&func) }),
127163
}
128164
}
129165

130166
// Cold because it should only happen during first-time initialization.
131167
#[cold]
132-
unsafe fn initialize(&self) -> Option<F> {
133-
assert_eq!(size_of::<F>(), size_of::<*mut libc::c_void>());
134-
135-
let val = unsafe { fetch(self.name) };
136-
// This synchronizes with the acquire fence in `get`.
168+
fn initialize(&self) -> Option<F> {
169+
// SAFETY: `self.name` was created from a `&'static CStr` and is
170+
// therefore a valid C string pointer.
171+
let val = unsafe { libc::dlsym(libc::RTLD_DEFAULT, self.name) };
172+
// This synchronizes with the acquire load in `get`.
137173
self.func.store(val, Ordering::Release);
138174

139175
if val.is_null() {
140176
None
141177
} else {
178+
// SAFETY: see the comment in `get`.
179+
// FIXME: use `transmute` once it stops complaining about generics.
142180
Some(unsafe { mem::transmute_copy::<*mut libc::c_void, F>(&val) })
143181
}
144182
}
145183
}
146184

147-
unsafe fn fetch(name: &str) -> *mut libc::c_void {
148-
let name = match CStr::from_bytes_with_nul(name.as_bytes()) {
149-
Ok(cstr) => cstr,
150-
Err(..) => return ptr::null_mut(),
151-
};
152-
unsafe { libc::dlsym(libc::RTLD_DEFAULT, name.as_ptr()) }
153-
}
185+
#[cfg(any(
186+
target_vendor = "apple",
187+
all(target_os = "linux", target_env = "gnu"),
188+
target_os = "freebsd",
189+
))]
190+
unsafe impl<F> Send for DlsymWeak<F> {}
191+
#[cfg(any(
192+
target_vendor = "apple",
193+
all(target_os = "linux", target_env = "gnu"),
194+
target_os = "freebsd",
195+
))]
196+
unsafe impl<F> Sync for DlsymWeak<F> {}
154197

155198
#[cfg(not(any(target_os = "linux", target_os = "android")))]
156199
pub(crate) macro syscall {
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
use super::*;
2+
3+
#[test]
4+
#[cfg(any(
5+
target_vendor = "apple",
6+
all(target_os = "linux", target_env = "gnu"),
7+
target_os = "freebsd",
8+
))]
9+
fn dlsym_existing() {
10+
const TEST_STRING: &'static CStr = c"Ferris!";
11+
12+
// Try to find a symbol that definitely exists.
13+
dlsym! {
14+
fn strlen(cs: *const c_char) -> usize;
15+
}
16+
17+
dlsym! {
18+
#[link_name = "strlen"]
19+
fn custom_name(cs: *const c_char) -> usize;
20+
}
21+
22+
let strlen = strlen.get().unwrap();
23+
assert_eq!(unsafe { strlen(TEST_STRING.as_ptr()) }, TEST_STRING.count_bytes());
24+
25+
let custom_name = custom_name.get().unwrap();
26+
assert_eq!(unsafe { custom_name(TEST_STRING.as_ptr()) }, TEST_STRING.count_bytes());
27+
}
28+
29+
#[test]
30+
#[cfg(any(
31+
target_vendor = "apple",
32+
all(target_os = "linux", target_env = "gnu"),
33+
target_os = "freebsd",
34+
))]
35+
fn dlsym_missing() {
36+
// Try to find a symbol that definitely does not exist.
37+
dlsym! {
38+
fn test_symbol_that_does_not_exist() -> i32;
39+
}
40+
41+
assert!(test_symbol_that_does_not_exist.get().is_none());
42+
}

0 commit comments

Comments
 (0)