22
22
#![ allow( dead_code, unused_macros) ]
23
23
#![ forbid( unsafe_op_in_unsafe_fn) ]
24
24
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 } ;
28
28
use crate :: { mem, ptr} ;
29
29
30
+ // We currently only test `dlsym!`, but that doesn't work on all platforms, so
31
+ // we gate the tests to only the platforms where it is actually used.
32
+ //
33
+ // FIXME(joboet): add more tests, reorganise the whole module and get rid of
34
+ // `#[allow(dead_code, unused_macros)]`.
35
+ #[ cfg( any(
36
+ target_vendor = "apple" ,
37
+ all( target_os = "linux" , target_env = "gnu" ) ,
38
+ target_os = "freebsd" ,
39
+ ) ) ]
40
+ #[ cfg( test) ]
41
+ mod tests;
42
+
30
43
// We can use true weak linkage on ELF targets.
31
44
#[ cfg( all( unix, not( target_vendor = "apple" ) ) ) ]
32
45
pub ( crate ) macro weak {
@@ -64,7 +77,7 @@ impl<F: Copy> ExternWeak<F> {
64
77
65
78
pub ( crate ) macro dlsym {
66
79
( fn $name: ident( $( $param: ident : $t: ty) , * $( , ) ?) -> $ret: ty; ) => (
67
- dlsym ! (
80
+ dlsym ! (
68
81
#[ link_name = stringify!( $name) ]
69
82
fn $name( $( $param : $t) , * ) -> $ret;
70
83
) ;
@@ -73,84 +86,99 @@ pub(crate) macro dlsym {
73
86
#[ link_name = $sym: expr]
74
87
fn $name: ident( $( $param: ident : $t: ty) , * $( , ) ?) -> $ret: ty ;
75
88
) => (
76
- static DLSYM : DlsymWeak < unsafe extern "C" fn ( $( $t) , * ) -> $ret> =
77
- DlsymWeak :: new ( concat ! ( $sym, '\0' ) ) ;
89
+ static DLSYM : DlsymWeak < unsafe extern "C" fn ( $( $t) , * ) -> $ret> = {
90
+ let Ok ( name ) = CStr :: from_bytes_with_nul ( concat ! ( $sym, '\0' ) . as_bytes( ) ) else {
91
+ panic ! ( "symbol name may not contain NUL" )
92
+ } ;
93
+
94
+ // SAFETY: Whoever calls the function pointer returned by `get()`
95
+ // is responsible for ensuring that the signature is correct. Just
96
+ // like with extern blocks, this is syntactically enforced by making
97
+ // the function pointer be unsafe.
98
+ unsafe { DlsymWeak :: new ( name ) }
99
+ } ;
100
+
78
101
let $name = & DLSYM ;
79
102
)
80
103
}
104
+
81
105
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 ,
83
109
func : Atomic < * mut libc:: c_void > ,
84
110
_marker : PhantomData < F > ,
85
111
}
86
112
87
- impl < F > DlsymWeak < F > {
88
- pub ( crate ) const fn new ( name : & ' static str ) -> Self {
113
+ impl < F : FnPtr > DlsymWeak < F > {
114
+ /// # Safety
115
+ ///
116
+ /// If the signature of `F` does not match the signature of the symbol (if
117
+ /// it exists), calling the function pointer returned by `get()` is
118
+ /// undefined behaviour.
119
+ pub ( crate ) const unsafe fn new ( name : & ' static CStr ) -> Self {
89
120
DlsymWeak {
90
- name,
121
+ name : name . as_ptr ( ) ,
91
122
func : AtomicPtr :: new ( ptr:: without_provenance_mut ( 1 ) ) ,
92
123
_marker : PhantomData ,
93
124
}
94
125
}
95
126
96
127
#[ inline]
97
128
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
- }
129
+ // The caller is presumably going to read through this value
130
+ // (by calling the function we've dlsymed). This means we'd
131
+ // need to have loaded it with at least C11's consume
132
+ // ordering in order to be guaranteed that the data we read
133
+ // from the pointer isn't from before the pointer was
134
+ // stored. Rust has no equivalent to memory_order_consume ,
135
+ // so we use an acquire load (sorry, ARM).
136
+ //
137
+ // Now, in practice this likely isn't needed even on CPUs
138
+ // where relaxed and consume mean different things. The
139
+ // symbols we're loading are probably present (or not) at
140
+ // init, and even if they aren't the runtime dynamic loader
141
+ // is extremely likely have sufficient barriers internally
142
+ // (possibly implicitly, for example the ones provided by
143
+ // invoking `mprotect` ).
144
+ //
145
+ // That said, none of that's *guaranteed*, so we use acquire.
146
+ match self . func . load ( Ordering :: Acquire ) {
147
+ func if func . addr ( ) == 1 => self . initialize ( ) ,
148
+ func if func . is_null ( ) => None ,
149
+ // SAFETY:
150
+ // `func` is not null and `F` implements `FnPtr`, thus this
151
+ // transmutation is well-defined. It is the responsibility of the
152
+ // creator of this `DlsymWeak` to ensure that calling the resulting
153
+ // function pointer does not result in undefined behaviour (though
154
+ // the `dlsym!` macro delegates this responsibility to the caller
155
+ // of the function by using `unsafe` function pointers).
156
+ // FIXME: use `transmute` once it stops complaining about generics.
157
+ func => Some ( unsafe { mem :: transmute_copy :: < * mut c_void , F > ( & func ) } ) ,
127
158
}
128
159
}
129
160
130
161
// Cold because it should only happen during first-time initialization.
131
162
#[ 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`.
163
+ fn initialize ( & self ) -> Option < F > {
164
+ // SAFETY: `self.name` was created from a `&'static CStr` and is
165
+ // therefore a valid C string pointer.
166
+ let val = unsafe { libc :: dlsym ( libc :: RTLD_DEFAULT , self . name ) } ;
167
+ // This synchronizes with the acquire load in `get`.
137
168
self . func . store ( val, Ordering :: Release ) ;
138
169
139
170
if val. is_null ( ) {
140
171
None
141
172
} else {
173
+ // SAFETY: see the comment in `get`.
174
+ // FIXME: use `transmute` once it stops complaining about generics.
142
175
Some ( unsafe { mem:: transmute_copy :: < * mut libc:: c_void , F > ( & val) } )
143
176
}
144
177
}
145
178
}
146
179
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
- }
180
+ unsafe impl < F > Send for DlsymWeak < F > { }
181
+ unsafe impl < F > Sync for DlsymWeak < F > { }
154
182
155
183
#[ cfg( not( any( target_os = "linux" , target_os = "android" ) ) ) ]
156
184
pub ( crate ) macro syscall {
0 commit comments