|
6 | 6 | // option. This file may not be copied, modified, or distributed
|
7 | 7 | // except according to those terms.
|
8 | 8 | #![allow(dead_code)]
|
9 |
| -use crate::{util::LazyUsize, Error}; |
10 |
| -use core::{num::NonZeroU32, ptr::NonNull}; |
| 9 | +use crate::Error; |
| 10 | +use core::{ |
| 11 | + num::NonZeroU32, |
| 12 | + ptr::NonNull, |
| 13 | + sync::atomic::{fence, AtomicPtr, Ordering}, |
| 14 | +}; |
| 15 | +use libc::c_void; |
11 | 16 |
|
12 | 17 | cfg_if! {
|
13 | 18 | if #[cfg(any(target_os = "netbsd", target_os = "openbsd", target_os = "android"))] {
|
@@ -76,29 +81,57 @@ pub fn sys_fill_exact(
|
76 | 81 |
|
77 | 82 | // A "weak" binding to a C function that may or may not be present at runtime.
|
78 | 83 | // Used for supporting newer OS features while still building on older systems.
|
79 |
| -// F must be a function pointer of type `unsafe extern "C" fn`. Based off of the |
80 |
| -// weak! macro in libstd. |
| 84 | +// Based off of the DlsymWeak struct in libstd: |
| 85 | +// https://github.com/rust-lang/rust/blob/1.61.0/library/std/src/sys/unix/weak.rs#L84 |
| 86 | +// except that the caller must manually cast self.ptr() to a function pointer. |
81 | 87 | pub struct Weak {
|
82 | 88 | name: &'static str,
|
83 |
| - addr: LazyUsize, |
| 89 | + addr: AtomicPtr<c_void>, |
84 | 90 | }
|
85 | 91 |
|
86 | 92 | impl Weak {
|
| 93 | + // A non-null pointer value which indicates we are uninitialized. This |
| 94 | + // constant should ideally not be a valid address of a function pointer. |
| 95 | + // However, if by chance libc::dlsym does return UNINIT, there will not |
| 96 | + // be undefined behavior. libc::dlsym will just be called each time ptr() |
| 97 | + // is called. This would be inefficient, but correct. |
| 98 | + // TODO: Replace with core::ptr::invalid_mut(1) when that is stable. |
| 99 | + const UNINIT: *mut c_void = 1 as *mut c_void; |
| 100 | + |
87 | 101 | // Construct a binding to a C function with a given name. This function is
|
88 | 102 | // unsafe because `name` _must_ be null terminated.
|
89 | 103 | pub const unsafe fn new(name: &'static str) -> Self {
|
90 | 104 | Self {
|
91 | 105 | name,
|
92 |
| - addr: LazyUsize::new(), |
| 106 | + addr: AtomicPtr::new(Self::UNINIT), |
93 | 107 | }
|
94 | 108 | }
|
95 | 109 |
|
96 |
| - // Return a function pointer if present at runtime. Otherwise, return null. |
97 |
| - pub fn ptr(&self) -> Option<NonNull<libc::c_void>> { |
98 |
| - let addr = self.addr.unsync_init(|| unsafe { |
99 |
| - libc::dlsym(libc::RTLD_DEFAULT, self.name.as_ptr() as *const _) as usize |
100 |
| - }); |
101 |
| - NonNull::new(addr as *mut _) |
| 110 | + // Return the address of a function if present at runtime. Otherwise, |
| 111 | + // return None. Multiple callers can call ptr() concurrently. It will |
| 112 | + // always return _some_ value returned by libc::dlsym. However, the |
| 113 | + // dlsym function may be called multiple times. |
| 114 | + pub fn ptr(&self) -> Option<NonNull<c_void>> { |
| 115 | + // Despite having only a single atomic variable (self.addr), we still |
| 116 | + // cannot always use Ordering::Relaxed, as we need to make sure a |
| 117 | + // successful call to dlsym() is "ordered before" any data read through |
| 118 | + // the returned pointer (which occurs when the function is called). |
| 119 | + // Our implementation mirrors that of the one in libstd, meaning that |
| 120 | + // the use of non-Relaxed operations is probably unnecessary. |
| 121 | + match self.addr.load(Ordering::Relaxed) { |
| 122 | + Self::UNINIT => { |
| 123 | + let symbol = self.name.as_ptr() as *const _; |
| 124 | + let addr = unsafe { libc::dlsym(libc::RTLD_DEFAULT, symbol) }; |
| 125 | + // Synchronizes with the Acquire fence below |
| 126 | + self.addr.store(addr, Ordering::Release); |
| 127 | + NonNull::new(addr) |
| 128 | + } |
| 129 | + addr => { |
| 130 | + let func = NonNull::new(addr)?; |
| 131 | + fence(Ordering::Acquire); |
| 132 | + Some(func) |
| 133 | + } |
| 134 | + } |
102 | 135 | }
|
103 | 136 | }
|
104 | 137 |
|
|
0 commit comments