|
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::{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,48 @@ 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 (src/sys/unix/weak.rs), except |
| 85 | +// that the caller must cast self.ptr() to a function pointer. |
81 | 86 | pub struct Weak {
|
82 | 87 | name: &'static str,
|
83 |
| - addr: LazyUsize, |
| 88 | + addr: AtomicPtr<c_void>, |
84 | 89 | }
|
85 | 90 |
|
86 | 91 | impl Weak {
|
| 92 | + // A non-null pointer value which indicates we are uninitialized. This |
| 93 | + // constant should ideally not be a valid address of a function pointer. |
| 94 | + // However, if by chance libc::dlsym does return UNINIT, there will not |
| 95 | + // be undefined behavior. libc::dlsym will just be called each time ptr() |
| 96 | + // is called. This would be inefficient, but correct. |
| 97 | + // TODO: Replace with core::ptr::invalid_mut(1) when that is stable. |
| 98 | + const UNINIT: *mut c_void = 1 as *mut c_void; |
| 99 | + |
87 | 100 | // Construct a binding to a C function with a given name. This function is
|
88 | 101 | // unsafe because `name` _must_ be null terminated.
|
89 | 102 | pub const unsafe fn new(name: &'static str) -> Self {
|
90 | 103 | Self {
|
91 | 104 | name,
|
92 |
| - addr: LazyUsize::new(), |
| 105 | + addr: AtomicPtr::new(Self::UNINIT), |
93 | 106 | }
|
94 | 107 | }
|
95 | 108 |
|
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 _) |
| 109 | + // Return the address of a function if present at runtime. Otherwise, |
| 110 | + // return null. Multiple callers can call ptr() concurrently. It will |
| 111 | + // always return _some_ value returned by libc::dlsym. However, the |
| 112 | + // dlsym function may be called multiple times. |
| 113 | + pub fn ptr(&self) -> Option<NonNull<c_void>> { |
| 114 | + // Despite having only a single atomic variable (self.addr), we still |
| 115 | + // need a "consume" ordering as we will generally be reading though |
| 116 | + // this value (by calling the function we dlsym'ed). Rust lacks this |
| 117 | + // ordering, so we have to go with the next strongest: Acquire/Release. |
| 118 | + // As noted in libstd, this might be unnecessary. |
| 119 | + let mut addr = self.addr.load(Ordering::Acquire); |
| 120 | + if addr == Self::UNINIT { |
| 121 | + let symbol = self.name.as_ptr() as *const _; |
| 122 | + addr = unsafe { libc::dlsym(libc::RTLD_DEFAULT, symbol) }; |
| 123 | + self.addr.store(addr, Ordering::Release); |
| 124 | + } |
| 125 | + NonNull::new(addr) |
102 | 126 | }
|
103 | 127 | }
|
104 | 128 |
|
|
0 commit comments