@@ -61,6 +61,8 @@ mod elf;
6161#[ cfg( all( target_os = "linux" , target_pointer_width = "64" ) ) ]
6262mod hooks;
6363
64+ #[ cfg( all( target_os = "linux" , target_pointer_width = "64" ) ) ]
65+ use std:: sync:: atomic:: { AtomicBool , Ordering } ;
6466#[ cfg( all( target_os = "linux" , target_pointer_width = "64" ) ) ]
6567use std:: sync:: { Mutex , MutexGuard , TryLockError } ;
6668
@@ -73,10 +75,29 @@ use elf::SymbolOverrides;
7375#[ cfg( all( target_os = "linux" , target_pointer_width = "64" ) ) ]
7476static GLOBAL_OVERRIDES : Mutex < Option < SymbolOverrides > > = Mutex :: new ( None ) ;
7577
78+ /// Set when a `dlopen` hook loses the `try_lock` race in
79+ /// `update_heap_overrides`: its newly-loaded library wasn't scanned, so the
80+ /// thread holding the lock must re-scan before releasing. Prevents a
81+ /// concurrent `dlopen` from being missed until the next unrelated scan.
82+ #[ cfg( all( target_os = "linux" , target_pointer_width = "64" ) ) ]
83+ static RESCAN_PENDING : AtomicBool = AtomicBool :: new ( false ) ;
84+
85+ /// Run `update_overrides` while the lock is held until no rescan is pending.
86+ /// Called by the lock holder after its own scan so a `dlopen` that raced the
87+ /// lock still gets picked up. `update_overrides` is cheap when `dlpi_adds`
88+ /// hasn't changed, so a spurious drain is nearly free.
89+ #[ cfg( all( target_os = "linux" , target_pointer_width = "64" ) ) ]
90+ fn drain_pending_rescans ( so : & mut SymbolOverrides ) {
91+ while RESCAN_PENDING . swap ( false , Ordering :: AcqRel ) {
92+ so. update_overrides ( ) ;
93+ }
94+ }
95+
7696#[ cfg( all( target_os = "linux" , target_pointer_width = "64" ) ) ]
7797fn lock_global_overrides ( ) -> MutexGuard < ' static , Option < SymbolOverrides > > {
7898 GLOBAL_OVERRIDES
7999 . lock ( )
100+ // Recover from poison: hot path never takes this lock, registry re-applies idempotently.
80101 . unwrap_or_else ( |poisoned| poisoned. into_inner ( ) )
81102}
82103
@@ -111,6 +132,7 @@ pub fn install_heap_overrides() -> bool {
111132 }
112133 let so = guard. as_mut ( ) . unwrap ( ) ;
113134 so. apply_overrides ( ) ;
135+ drain_pending_rescans ( so) ;
114136 // Heuristic: at least one ORIG slot resolved.
115137 any_orig_resolved ( )
116138}
@@ -131,11 +153,18 @@ pub fn update_heap_overrides() {
131153 // outer apply_overrides, which already walks every library.
132154 let mut guard = match GLOBAL_OVERRIDES . try_lock ( ) {
133155 Ok ( guard) => guard,
156+ // Recover from poison: same rationale as lock_global_overrides.
134157 Err ( TryLockError :: Poisoned ( poisoned) ) => poisoned. into_inner ( ) ,
135- Err ( TryLockError :: WouldBlock ) => return ,
158+ // Lock held by another install/update: flag a rescan so that thread
159+ // picks up the library we just loaded before it releases the lock.
160+ Err ( TryLockError :: WouldBlock ) => {
161+ RESCAN_PENDING . store ( true , Ordering :: Release ) ;
162+ return ;
163+ }
136164 } ;
137165 if let Some ( so) = guard. as_mut ( ) {
138166 so. update_overrides ( ) ;
167+ drain_pending_rescans ( so) ;
139168 }
140169}
141170
@@ -179,55 +208,31 @@ pub fn test_hook_hits() -> u64 {
179208#[ cfg( all( target_os = "linux" , target_pointer_width = "64" ) ) ]
180209fn register_all ( so : & mut SymbolOverrides ) {
181210 use hooks:: * ;
182- use std:: sync:: atomic:: AtomicUsize ;
183211
184212 // Register one entry per supported symbol. The install path stores
185213 // via `store(Release)` and hooks read via `load(Acquire)`; both go
186214 // through the typed atomic to avoid racing plain writes against
187215 // atomic loads.
188- fn reg ( so : & mut SymbolOverrides , name : & str , hook_addr : usize , slot : & ' static AtomicUsize ) {
189- so. register ( name, hook_addr, slot) ;
190- }
191-
192- reg (
193- so,
194- "malloc" ,
195- gotter_malloc as * const ( ) as usize ,
196- & ORIG_MALLOC ,
197- ) ;
198- reg ( so, "free" , gotter_free as * const ( ) as usize , & ORIG_FREE ) ;
199- reg (
200- so,
201- "calloc" ,
202- gotter_calloc as * const ( ) as usize ,
203- & ORIG_CALLOC ,
204- ) ;
205- reg (
206- so,
216+ so. register ( "malloc" , gotter_malloc as * const ( ) as usize , & ORIG_MALLOC ) ;
217+ so. register ( "free" , gotter_free as * const ( ) as usize , & ORIG_FREE ) ;
218+ so. register ( "calloc" , gotter_calloc as * const ( ) as usize , & ORIG_CALLOC ) ;
219+ so. register (
207220 "realloc" ,
208221 gotter_realloc as * const ( ) as usize ,
209222 & ORIG_REALLOC ,
210223 ) ;
211- reg (
212- so,
224+ so. register (
213225 "posix_memalign" ,
214226 gotter_posix_memalign as * const ( ) as usize ,
215227 & ORIG_POSIX_MEMALIGN ,
216228 ) ;
217- reg (
218- so,
229+ so. register (
219230 "aligned_alloc" ,
220231 gotter_aligned_alloc as * const ( ) as usize ,
221232 & ORIG_ALIGNED_ALLOC ,
222233 ) ;
223- reg (
224- so,
225- "dlopen" ,
226- gotter_dlopen as * const ( ) as usize ,
227- & ORIG_DLOPEN ,
228- ) ;
229- reg (
230- so,
234+ so. register ( "dlopen" , gotter_dlopen as * const ( ) as usize , & ORIG_DLOPEN ) ;
235+ so. register (
231236 "pthread_create" ,
232237 gotter_pthread_create as * const ( ) as usize ,
233238 & ORIG_PTHREAD_CREATE ,
0 commit comments