@@ -7,6 +7,7 @@ use libc::{c_char, c_int, c_void, size_t};
77use log:: { debug, error, trace, warn} ;
88use rand:: rngs:: ThreadRng ;
99use std:: cell:: RefCell ;
10+ use std:: cell:: UnsafeCell ;
1011use std:: ffi:: CStr ;
1112use std:: sync:: atomic:: AtomicU64 ;
1213use std:: sync:: atomic:: Ordering :: SeqCst ;
@@ -19,18 +20,6 @@ use crate::bindings::{
1920
2021static mut GC_MEM_CACHES_HANDLER : zend:: InternalFunctionHandler = None ;
2122
22- /// The engine's previous custom allocation function, if there is one.
23- static mut PREV_CUSTOM_MM_ALLOC : Option < zend:: VmMmCustomAllocFn > = None ;
24-
25- /// The engine's previous custom reallocation function, if there is one.
26- static mut PREV_CUSTOM_MM_REALLOC : Option < zend:: VmMmCustomReallocFn > = None ;
27-
28- /// The engine's previous custom free function, if there is one.
29- static mut PREV_CUSTOM_MM_FREE : Option < zend:: VmMmCustomFreeFn > = None ;
30-
31- /// The heap installed in ZendMM at the time we install our custom handlers
32- static mut HEAP : Option < * mut zend:: _zend_mm_heap > = None ;
33-
3423/// take a sample every 4096 KiB
3524pub const ALLOCATION_PROFILING_INTERVAL : f64 = 1024.0 * 4096.0 ;
3625
@@ -50,6 +39,17 @@ pub struct AllocationProfilingStats {
5039 rng : ThreadRng ,
5140}
5241
42+ struct ZendMMState {
43+ /// The heap installed in ZendMM at the time we install our custom handlers
44+ heap : Option < * mut zend:: zend_mm_heap > ,
45+ /// The engine's previous custom allocation function, if there is one.
46+ prev_custom_mm_alloc : Option < zend:: VmMmCustomAllocFn > ,
47+ /// The engine's previous custom reallocation function, if there is one.
48+ prev_custom_mm_realloc : Option < zend:: VmMmCustomReallocFn > ,
49+ /// The engine's previous custom free function, if there is one.
50+ prev_custom_mm_free : Option < zend:: VmMmCustomFreeFn > ,
51+ }
52+
5353impl AllocationProfilingStats {
5454 fn new ( ) -> AllocationProfilingStats {
5555 // Safety: this will only error if lambda <= 0
@@ -91,6 +91,18 @@ impl AllocationProfilingStats {
9191
9292thread_local ! {
9393 static ALLOCATION_PROFILING_STATS : RefCell <AllocationProfilingStats > = RefCell :: new( AllocationProfilingStats :: new( ) ) ;
94+ /// Using an `UnsafeCell` here should be okay. There might not be any synchronisation issues,
95+ /// as it is used in as thread local and only mutated in RINIT and RSHUTDOWN.
96+ static ZEND_MM_STATE : UnsafeCell <ZendMMState > = const {
97+ UnsafeCell :: new(
98+ ZendMMState {
99+ heap: None ,
100+ prev_custom_mm_alloc: None ,
101+ prev_custom_mm_realloc: None ,
102+ prev_custom_mm_free: None
103+ }
104+ )
105+ } ;
94106}
95107
96108const NEEDS_RUN_TIME_CHECK_FOR_ENABLED_JIT : bool =
@@ -106,9 +118,6 @@ lazy_static! {
106118}
107119
108120pub fn allocation_profiling_minit ( ) {
109- #[ cfg( php_zts) ]
110- return ;
111-
112121 unsafe { zend:: ddog_php_opcache_init_handle ( ) } ;
113122}
114123
@@ -125,9 +134,6 @@ pub fn first_rinit_should_disable_due_to_jit() -> bool {
125134}
126135
127136pub fn allocation_profiling_rinit ( ) {
128- #[ cfg( php_zts) ]
129- return ;
130-
131137 let allocation_profiling: bool = REQUEST_LOCALS . with ( |cell| {
132138 match cell. try_borrow ( ) {
133139 Ok ( locals) => {
@@ -145,47 +151,50 @@ pub fn allocation_profiling_rinit() {
145151 return ;
146152 }
147153
148- unsafe {
149- HEAP = Some ( zend:: zend_mm_get_heap ( ) ) ;
150- }
151-
152- if !is_zend_mm ( ) {
153- // Neighboring custom memory handlers found
154- debug ! ( "Found another extension using the ZendMM custom handler hook" ) ;
154+ ZEND_MM_STATE . with ( |cell| {
155+ let zend_mm_state = cell. get ( ) ;
155156 unsafe {
156- zend:: zend_mm_get_custom_handlers (
157- // Safety: `unwrap()` is safe here, as `HEAP` is initialized just above
158- HEAP . unwrap ( ) ,
159- & mut PREV_CUSTOM_MM_ALLOC ,
160- & mut PREV_CUSTOM_MM_FREE ,
161- & mut PREV_CUSTOM_MM_REALLOC ,
162- ) ;
163- ALLOCATION_PROFILING_ALLOC = allocation_profiling_prev_alloc;
164- ALLOCATION_PROFILING_FREE = allocation_profiling_prev_free;
165- ALLOCATION_PROFILING_REALLOC = allocation_profiling_prev_realloc;
166- ALLOCATION_PROFILING_PREPARE_ZEND_HEAP = prepare_zend_heap_none;
167- ALLOCATION_PROFILING_RESTORE_ZEND_HEAP = restore_zend_heap_none;
157+ ( * zend_mm_state) . heap = Some ( zend:: zend_mm_get_heap ( ) ) ;
168158 }
169- } else {
170- unsafe {
171- ALLOCATION_PROFILING_ALLOC = allocation_profiling_orig_alloc;
172- ALLOCATION_PROFILING_FREE = allocation_profiling_orig_free;
173- ALLOCATION_PROFILING_REALLOC = allocation_profiling_orig_realloc;
174- ALLOCATION_PROFILING_PREPARE_ZEND_HEAP = prepare_zend_heap;
175- ALLOCATION_PROFILING_RESTORE_ZEND_HEAP = restore_zend_heap;
159+
160+ if !is_zend_mm ( ) {
161+ // Neighboring custom memory handlers found
162+ debug ! ( "Found another extension using the ZendMM custom handler hook" ) ;
163+ unsafe {
164+ zend:: zend_mm_get_custom_handlers (
165+ // Safety: `unwrap()` is safe here, as `heap` is initialized just above
166+ ( * zend_mm_state) . heap . unwrap ( ) ,
167+ & mut ( * zend_mm_state) . prev_custom_mm_alloc ,
168+ & mut ( * zend_mm_state) . prev_custom_mm_free ,
169+ & mut ( * zend_mm_state) . prev_custom_mm_realloc ,
170+ ) ;
171+ ALLOCATION_PROFILING_ALLOC = allocation_profiling_prev_alloc;
172+ ALLOCATION_PROFILING_FREE = allocation_profiling_prev_free;
173+ ALLOCATION_PROFILING_REALLOC = allocation_profiling_prev_realloc;
174+ ALLOCATION_PROFILING_PREPARE_ZEND_HEAP = prepare_zend_heap_none;
175+ ALLOCATION_PROFILING_RESTORE_ZEND_HEAP = restore_zend_heap_none;
176+ }
177+ } else {
178+ unsafe {
179+ ALLOCATION_PROFILING_ALLOC = allocation_profiling_orig_alloc;
180+ ALLOCATION_PROFILING_FREE = allocation_profiling_orig_free;
181+ ALLOCATION_PROFILING_REALLOC = allocation_profiling_orig_realloc;
182+ ALLOCATION_PROFILING_PREPARE_ZEND_HEAP = prepare_zend_heap;
183+ ALLOCATION_PROFILING_RESTORE_ZEND_HEAP = restore_zend_heap;
184+ }
176185 }
177- }
178186
179- // install our custom handler to ZendMM
180- unsafe {
181- zend:: ddog_php_prof_zend_mm_set_custom_handlers (
182- // Safety: `unwrap()` is safe here, as `HEAP` is initialized just above
183- HEAP . unwrap ( ) ,
184- Some ( alloc_profiling_malloc) ,
185- Some ( alloc_profiling_free) ,
186- Some ( alloc_profiling_realloc) ,
187- ) ;
188- }
187+ // install our custom handler to ZendMM
188+ unsafe {
189+ zend:: ddog_php_prof_zend_mm_set_custom_handlers (
190+ // Safety: `unwrap()` is safe here, as `heap` is initialized just above
191+ ( * zend_mm_state) . heap . unwrap ( ) ,
192+ Some ( alloc_profiling_malloc) ,
193+ Some ( alloc_profiling_free) ,
194+ Some ( alloc_profiling_realloc) ,
195+ ) ;
196+ }
197+ } ) ;
189198
190199 // `is_zend_mm()` should be `false` now, as we installed our custom handlers
191200 if is_zend_mm ( ) {
@@ -198,9 +207,6 @@ pub fn allocation_profiling_rinit() {
198207}
199208
200209pub fn allocation_profiling_rshutdown ( ) {
201- #[ cfg( php_zts) ]
202- return ;
203-
204210 let allocation_profiling = REQUEST_LOCALS . with ( |cell| {
205211 cell. try_borrow ( )
206212 . map ( |locals| locals. system_settings ( ) . profiling_allocation_enabled )
@@ -219,57 +225,57 @@ pub fn allocation_profiling_rshutdown() {
219225 return ;
220226 }
221227
222- let mut custom_mm_malloc: Option < zend:: VmMmCustomAllocFn > = None ;
223- let mut custom_mm_free: Option < zend:: VmMmCustomFreeFn > = None ;
224- let mut custom_mm_realloc: Option < zend:: VmMmCustomReallocFn > = None ;
225- unsafe {
226- zend:: zend_mm_get_custom_handlers (
227- // Safety: `unwrap()` is safe here, as `HEAP` is initialized in `RINIT`
228- HEAP . unwrap ( ) ,
229- & mut custom_mm_malloc,
230- & mut custom_mm_free,
231- & mut custom_mm_realloc,
232- ) ;
233- }
234- if custom_mm_free != Some ( alloc_profiling_free)
235- || custom_mm_malloc != Some ( alloc_profiling_malloc)
236- || custom_mm_realloc != Some ( alloc_profiling_realloc)
237- {
238- // Custom handlers are installed, but it's not us. Someone, somewhere might have
239- // function pointers to our custom handlers. Best bet to avoid segfaults is to not
240- // touch custom handlers in ZendMM and make sure our extension will not be
241- // `dlclose()`-ed so the pointers stay valid
242- let zend_extension =
243- unsafe { zend:: zend_get_extension ( PROFILER_NAME . as_ptr ( ) as * const c_char ) } ;
244- if !zend_extension. is_null ( ) {
245- // Safety: Checked for null pointer above.
228+ ZEND_MM_STATE . with ( |cell| {
229+ let zend_mm_state = cell. get ( ) ;
230+ let mut custom_mm_malloc: Option < zend:: VmMmCustomAllocFn > = None ;
231+ let mut custom_mm_free: Option < zend:: VmMmCustomFreeFn > = None ;
232+ let mut custom_mm_realloc: Option < zend:: VmMmCustomReallocFn > = None ;
233+ unsafe {
234+ zend:: zend_mm_get_custom_handlers (
235+ // Safety: `unwrap()` is safe here, as `heap` is initialized in `RINIT`
236+ ( * zend_mm_state) . heap . unwrap ( ) ,
237+ & mut custom_mm_malloc,
238+ & mut custom_mm_free,
239+ & mut custom_mm_realloc,
240+ ) ;
241+ }
242+ if custom_mm_free != Some ( alloc_profiling_free)
243+ || custom_mm_malloc != Some ( alloc_profiling_malloc)
244+ || custom_mm_realloc != Some ( alloc_profiling_realloc)
245+ {
246+ // Custom handlers are installed, but it's not us. Someone, somewhere might have
247+ // function pointers to our custom handlers. Best bet to avoid segfaults is to not
248+ // touch custom handlers in ZendMM and make sure our extension will not be
249+ // `dlclose()`-ed so the pointers stay valid
250+ let zend_extension =
251+ unsafe { zend:: zend_get_extension ( PROFILER_NAME . as_ptr ( ) as * const c_char ) } ;
252+ if !zend_extension. is_null ( ) {
253+ // Safety: Checked for null pointer above.
254+ unsafe {
255+ ( * zend_extension) . handle = std:: ptr:: null_mut ( ) ;
256+ }
257+ }
258+ warn ! ( "Found another extension using the custom heap which is unexpected at this point, so the extension handle was `null`'ed to avoid being `dlclose()`'ed." ) ;
259+ } else {
260+ // This is the happy path (restore previously installed custom handlers)!
246261 unsafe {
247- ( * zend_extension) . handle = std:: ptr:: null_mut ( ) ;
262+ zend:: ddog_php_prof_zend_mm_set_custom_handlers (
263+ // Safety: `unwrap()` is safe here, as `heap` is initialized in `RINIT`
264+ ( * zend_mm_state) . heap . unwrap ( ) ,
265+ ( * zend_mm_state) . prev_custom_mm_alloc ,
266+ ( * zend_mm_state) . prev_custom_mm_free ,
267+ ( * zend_mm_state) . prev_custom_mm_realloc ,
268+ ) ;
248269 }
270+ trace ! ( "Memory allocation profiling shutdown gracefully." ) ;
249271 }
250- warn ! ( "Found another extension using the custom heap which is unexpected at this point, so the extension handle was `null`'ed to avoid being `dlclose()`'ed." ) ;
251- } else {
252- // This is the happy path (restore previously installed custom handlers)!
253272 unsafe {
254- zend:: ddog_php_prof_zend_mm_set_custom_handlers (
255- // Safety: `unwrap()` is safe here, as `HEAP` is initialized in `RINIT`
256- HEAP . unwrap ( ) ,
257- PREV_CUSTOM_MM_ALLOC ,
258- PREV_CUSTOM_MM_FREE ,
259- PREV_CUSTOM_MM_REALLOC ,
260- ) ;
273+ ( * zend_mm_state) . heap = None ;
261274 }
262- trace ! ( "Memory allocation profiling shutdown gracefully." ) ;
263- }
264- unsafe {
265- HEAP = None ;
266- }
275+ } ) ;
267276}
268277
269278pub fn allocation_profiling_startup ( ) {
270- #[ cfg( php_zts) ]
271- return ;
272-
273279 unsafe {
274280 let handle = datadog_php_zif_handler:: new (
275281 CStr :: from_bytes_with_nul_unchecked ( b"gc_mem_caches\0 " ) ,
@@ -366,8 +372,11 @@ static mut ALLOCATION_PROFILING_ALLOC: unsafe fn(size_t) -> *mut c_void =
366372 allocation_profiling_orig_alloc;
367373
368374unsafe fn allocation_profiling_prev_alloc ( len : size_t ) -> * mut c_void {
369- let prev = PREV_CUSTOM_MM_ALLOC . unwrap ( ) ;
370- prev ( len)
375+ ZEND_MM_STATE . with ( |cell| {
376+ let zend_mm_state = cell. get ( ) ;
377+ let prev = ( * zend_mm_state) . prev_custom_mm_alloc . unwrap ( ) ;
378+ prev ( len)
379+ } )
371380}
372381
373382unsafe fn allocation_profiling_orig_alloc ( len : size_t ) -> * mut c_void {
@@ -389,8 +398,11 @@ unsafe extern "C" fn alloc_profiling_free(ptr: *mut c_void) {
389398static mut ALLOCATION_PROFILING_FREE : unsafe fn ( * mut c_void ) = allocation_profiling_orig_free;
390399
391400unsafe fn allocation_profiling_prev_free ( ptr : * mut c_void ) {
392- let prev = PREV_CUSTOM_MM_FREE . unwrap ( ) ;
393- prev ( ptr) ;
401+ ZEND_MM_STATE . with ( |cell| {
402+ let zend_mm_state = cell. get ( ) ;
403+ let prev = ( * zend_mm_state) . prev_custom_mm_free . unwrap ( ) ;
404+ prev ( ptr)
405+ } )
394406}
395407
396408unsafe fn allocation_profiling_orig_free ( ptr : * mut c_void ) {
@@ -422,8 +434,11 @@ static mut ALLOCATION_PROFILING_REALLOC: unsafe fn(*mut c_void, size_t) -> *mut
422434 allocation_profiling_orig_realloc;
423435
424436unsafe fn allocation_profiling_prev_realloc ( prev_ptr : * mut c_void , len : size_t ) -> * mut c_void {
425- let prev = PREV_CUSTOM_MM_REALLOC . unwrap ( ) ;
426- prev ( prev_ptr, len)
437+ ZEND_MM_STATE . with ( |cell| {
438+ let zend_mm_state = cell. get ( ) ;
439+ let prev = ( * zend_mm_state) . prev_custom_mm_realloc . unwrap ( ) ;
440+ prev ( prev_ptr, len)
441+ } )
427442}
428443
429444unsafe fn allocation_profiling_orig_realloc ( prev_ptr : * mut c_void , len : size_t ) -> * mut c_void {
0 commit comments