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 } ;
2828use 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" ) ) ) ]
3235pub ( 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+ ) ) ]
6573pub ( 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+ ) ) ]
81105pub ( 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" ) ) ) ]
156199pub( crate ) macro syscall {
0 commit comments