@@ -7,6 +7,7 @@ use libc::{c_char, c_int, c_void, size_t};
7
7
use log:: { debug, error, trace, warn} ;
8
8
use rand:: rngs:: ThreadRng ;
9
9
use std:: cell:: RefCell ;
10
+ use std:: cell:: UnsafeCell ;
10
11
use std:: ffi:: CStr ;
11
12
use std:: sync:: atomic:: AtomicU64 ;
12
13
use std:: sync:: atomic:: Ordering :: SeqCst ;
@@ -19,18 +20,6 @@ use crate::bindings::{
19
20
20
21
static mut GC_MEM_CACHES_HANDLER : zend:: InternalFunctionHandler = None ;
21
22
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
-
34
23
/// take a sample every 4096 KiB
35
24
pub const ALLOCATION_PROFILING_INTERVAL : f64 = 1024.0 * 4096.0 ;
36
25
@@ -50,6 +39,17 @@ pub struct AllocationProfilingStats {
50
39
rng : ThreadRng ,
51
40
}
52
41
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
+
53
53
impl AllocationProfilingStats {
54
54
fn new ( ) -> AllocationProfilingStats {
55
55
// Safety: this will only error if lambda <= 0
@@ -91,6 +91,18 @@ impl AllocationProfilingStats {
91
91
92
92
thread_local ! {
93
93
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
+ } ;
94
106
}
95
107
96
108
const NEEDS_RUN_TIME_CHECK_FOR_ENABLED_JIT : bool =
@@ -106,9 +118,6 @@ lazy_static! {
106
118
}
107
119
108
120
pub fn allocation_profiling_minit ( ) {
109
- #[ cfg( php_zts) ]
110
- return ;
111
-
112
121
unsafe { zend:: ddog_php_opcache_init_handle ( ) } ;
113
122
}
114
123
@@ -125,9 +134,6 @@ pub fn first_rinit_should_disable_due_to_jit() -> bool {
125
134
}
126
135
127
136
pub fn allocation_profiling_rinit ( ) {
128
- #[ cfg( php_zts) ]
129
- return ;
130
-
131
137
let allocation_profiling: bool = REQUEST_LOCALS . with ( |cell| {
132
138
match cell. try_borrow ( ) {
133
139
Ok ( locals) => {
@@ -145,47 +151,50 @@ pub fn allocation_profiling_rinit() {
145
151
return ;
146
152
}
147
153
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 ( ) ;
155
156
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 ( ) ) ;
168
158
}
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
+ }
176
185
}
177
- }
178
186
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
+ } ) ;
189
198
190
199
// `is_zend_mm()` should be `false` now, as we installed our custom handlers
191
200
if is_zend_mm ( ) {
@@ -198,9 +207,6 @@ pub fn allocation_profiling_rinit() {
198
207
}
199
208
200
209
pub fn allocation_profiling_rshutdown ( ) {
201
- #[ cfg( php_zts) ]
202
- return ;
203
-
204
210
let allocation_profiling = REQUEST_LOCALS . with ( |cell| {
205
211
cell. try_borrow ( )
206
212
. map ( |locals| locals. system_settings ( ) . profiling_allocation_enabled )
@@ -219,57 +225,57 @@ pub fn allocation_profiling_rshutdown() {
219
225
return ;
220
226
}
221
227
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)!
246
261
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
+ ) ;
248
269
}
270
+ trace ! ( "Memory allocation profiling shutdown gracefully." ) ;
249
271
}
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)!
253
272
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 ;
261
274
}
262
- trace ! ( "Memory allocation profiling shutdown gracefully." ) ;
263
- }
264
- unsafe {
265
- HEAP = None ;
266
- }
275
+ } ) ;
267
276
}
268
277
269
278
pub fn allocation_profiling_startup ( ) {
270
- #[ cfg( php_zts) ]
271
- return ;
272
-
273
279
unsafe {
274
280
let handle = datadog_php_zif_handler:: new (
275
281
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 =
366
372
allocation_profiling_orig_alloc;
367
373
368
374
unsafe 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
+ } )
371
380
}
372
381
373
382
unsafe 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) {
389
398
static mut ALLOCATION_PROFILING_FREE : unsafe fn ( * mut c_void ) = allocation_profiling_orig_free;
390
399
391
400
unsafe 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
+ } )
394
406
}
395
407
396
408
unsafe 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
422
434
allocation_profiling_orig_realloc;
423
435
424
436
unsafe 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
+ } )
427
442
}
428
443
429
444
unsafe fn allocation_profiling_orig_realloc ( prev_ptr : * mut c_void , len : size_t ) -> * mut c_void {
0 commit comments