diff --git a/profiling/README.md b/profiling/README.md index dd62d3e75b8..3fb38424d1e 100644 --- a/profiling/README.md +++ b/profiling/README.md @@ -4,8 +4,9 @@ The profiler is implemented in Rust. To see the currently required Rust version, refer to the [rust-toolchain](rust-toolchain) file. The profiler -requires PHP 7.1+, and does not support debug nor ZTS builds. There are bits of -ZTS support in the build system and profiler, but it's not complete. +requires PHP 7.1+, and does not support debug builds. There are bits of ZTS +support in the build system and profiler, but it's not complete, consider the +current state of ZTS support beta. ## Time Profiling diff --git a/profiling/src/allocation.rs b/profiling/src/allocation.rs index 7adb6d9e4bc..a40d8e417fc 100644 --- a/profiling/src/allocation.rs +++ b/profiling/src/allocation.rs @@ -1,46 +1,30 @@ -use crate::bindings as zend; -use crate::PROFILER; -use crate::PROFILER_NAME; -use crate::REQUEST_LOCALS; +use crate::bindings::{ + self as zend, datadog_php_install_handler, datadog_php_zif_handler, + ddog_php_prof_copy_long_into_zval, +}; +use crate::{PROFILER, PROFILER_NAME, REQUEST_LOCALS}; use lazy_static::lazy_static; use libc::{c_char, c_int, c_void, size_t}; use log::{debug, error, trace, warn}; use rand::rngs::ThreadRng; -use std::cell::RefCell; -use std::ffi::CStr; -use std::sync::atomic::AtomicU64; -use std::sync::atomic::Ordering::SeqCst; - use rand_distr::{Distribution, Poisson}; - -use crate::bindings::{ - datadog_php_install_handler, datadog_php_zif_handler, ddog_php_prof_copy_long_into_zval, -}; +use std::cell::{RefCell, UnsafeCell}; +use std::sync::atomic::{AtomicU64, Ordering::SeqCst}; +use std::{ffi, ptr}; static mut GC_MEM_CACHES_HANDLER: zend::InternalFunctionHandler = None; -/// The engine's previous custom allocation function, if there is one. -static mut PREV_CUSTOM_MM_ALLOC: Option = None; - -/// The engine's previous custom reallocation function, if there is one. -static mut PREV_CUSTOM_MM_REALLOC: Option = None; - -/// The engine's previous custom free function, if there is one. -static mut PREV_CUSTOM_MM_FREE: Option = None; - -/// The heap installed in ZendMM at the time we install our custom handlers -static mut HEAP: Option<*mut zend::_zend_mm_heap> = None; - /// take a sample every 4096 KiB pub const ALLOCATION_PROFILING_INTERVAL: f64 = 1024.0 * 4096.0; -/// this will store the count of allocations (including reallocations) during a profiling period. -/// This will overflow when doing more then u64::MAX allocations, which seems big enough to ignore. +/// This will store the count of allocations (including reallocations) during +/// a profiling period. This will overflow when doing more than u64::MAX +/// allocations, which seems big enough to ignore. pub static ALLOCATION_PROFILING_COUNT: AtomicU64 = AtomicU64::new(0); -/// this will store the accumulated size of all allocations in bytes during the profiling period. -/// This will overflow when allocating more then 18 exabyte of memory (u64::MAX) which might not -/// happen, so we can ignore this +/// This will store the accumulated size of all allocations in bytes during the +/// profiling period. This will overflow when allocating more than 18 exabyte +/// of memory (u64::MAX) which might not happen, so we can ignore this. pub static ALLOCATION_PROFILING_SIZE: AtomicU64 = AtomicU64::new(0); pub struct AllocationProfilingStats { @@ -50,6 +34,39 @@ pub struct AllocationProfilingStats { rng: ThreadRng, } +type ZendHeapPrepareFn = unsafe fn(heap: *mut zend::_zend_mm_heap) -> c_int; +type ZendHeapRestoreFn = unsafe fn(heap: *mut zend::_zend_mm_heap, custom_heap: c_int); + +struct ZendMMState { + /// The heap installed in ZendMM at the time we install our custom + /// handlers, this is also the heap our custom handlers are installed in. + /// We need this in case there is no custom handlers installed prior to us, + /// in order to forward our allocation calls to this heap. + heap: Option<*mut zend::zend_mm_heap>, + /// The engine's previous custom allocation function, if there is one. + prev_custom_mm_alloc: Option, + /// The engine's previous custom reallocation function, if there is one. + prev_custom_mm_realloc: Option, + /// The engine's previous custom free function, if there is one. + prev_custom_mm_free: Option, + prepare_restore_zend_heap: (ZendHeapPrepareFn, ZendHeapRestoreFn), + /// Safety: this function pointer is only allowed to point to + /// `alloc_prof_prev_alloc()` when at the same time the + /// `ZEND_MM_STATE.prev_custom_mm_alloc` is initialised to a valid function + /// pointer, otherwise there will be dragons. + alloc: unsafe fn(size_t) -> *mut c_void, + /// Safety: this function pointer is only allowed to point to + /// `alloc_prof_prev_realloc()` when at the same time the + /// `ZEND_MM_STATE.prev_custom_mm_realloc` is initialised to a valid + /// function pointer, otherwise there will be dragons. + realloc: unsafe fn(*mut c_void, size_t) -> *mut c_void, + /// Safety: this function pointer is only allowed to point to + /// `alloc_prof_prev_free()` when at the same time the + /// `ZEND_MM_STATE.prev_custom_mm_free` is initialised to a valid function + /// pointer, otherwise there will be dragons. + free: unsafe fn(*mut c_void), +} + impl AllocationProfilingStats { fn new() -> AllocationProfilingStats { // Safety: this will only error if lambda <= 0 @@ -90,13 +107,39 @@ impl AllocationProfilingStats { } thread_local! { - static ALLOCATION_PROFILING_STATS: RefCell = RefCell::new(AllocationProfilingStats::new()); + static ALLOCATION_PROFILING_STATS: RefCell = + RefCell::new(AllocationProfilingStats::new()); + + /// Using an `UnsafeCell` here should be okay. There might not be any + /// synchronisation issues, as it is used in as thread local and only + /// mutated in RINIT and RSHUTDOWN. + static ZEND_MM_STATE: UnsafeCell = const { + UnsafeCell::new(ZendMMState { + heap: None, + prev_custom_mm_alloc: None, + prev_custom_mm_realloc: None, + prev_custom_mm_free: None, + prepare_restore_zend_heap: (prepare_zend_heap, restore_zend_heap), + alloc: alloc_prof_orig_alloc, + realloc: alloc_prof_orig_realloc, + free: alloc_prof_orig_free, + }) + }; +} + +macro_rules! tls_zend_mm_state { + ($x:ident) => { + ZEND_MM_STATE.with(|cell| { + let zend_mm_state = cell.get(); + (*zend_mm_state).$x + }) + }; } const NEEDS_RUN_TIME_CHECK_FOR_ENABLED_JIT: bool = zend::PHP_VERSION_ID >= 80000 && zend::PHP_VERSION_ID < 80300; -fn allocation_profiling_needs_disabled_for_jit(version: u32) -> bool { +fn alloc_prof_needs_disabled_for_jit(version: u32) -> bool { // see https://github.com/php/php-src/pull/11380 (80000..80121).contains(&version) || (80200..80208).contains(&version) } @@ -105,16 +148,13 @@ lazy_static! { static ref JIT_ENABLED: bool = unsafe { zend::ddog_php_jit_enabled() }; } -pub fn allocation_profiling_minit() { - #[cfg(php_zts)] - return; - +pub fn alloc_prof_minit() { unsafe { zend::ddog_php_opcache_init_handle() }; } pub fn first_rinit_should_disable_due_to_jit() -> bool { if NEEDS_RUN_TIME_CHECK_FOR_ENABLED_JIT - && allocation_profiling_needs_disabled_for_jit(unsafe { crate::PHP_VERSION_ID }) + && alloc_prof_needs_disabled_for_jit(unsafe { crate::PHP_VERSION_ID }) && *JIT_ENABLED { error!("Memory allocation profiling will be disabled as long as JIT is active. To enable allocation profiling disable JIT or upgrade PHP to at least version 8.1.21 or 8.2.8. See https://github.com/DataDog/dd-trace-php/pull/2088"); @@ -124,10 +164,7 @@ pub fn first_rinit_should_disable_due_to_jit() -> bool { } } -pub fn allocation_profiling_rinit() { - #[cfg(php_zts)] - return; - +pub fn alloc_prof_rinit() { let allocation_profiling: bool = REQUEST_LOCALS.with(|cell| { match cell.try_borrow() { Ok(locals) => { @@ -145,62 +182,69 @@ pub fn allocation_profiling_rinit() { return; } - unsafe { - HEAP = Some(zend::zend_mm_get_heap()); - } + ZEND_MM_STATE.with(|cell| { + let zend_mm_state = cell.get(); - if !is_zend_mm() { - // Neighboring custom memory handlers found - debug!("Found another extension using the ZendMM custom handler hook"); - unsafe { - zend::zend_mm_get_custom_handlers( - // Safety: `unwrap()` is safe here, as `HEAP` is initialized just above - HEAP.unwrap(), - &mut PREV_CUSTOM_MM_ALLOC, - &mut PREV_CUSTOM_MM_FREE, - &mut PREV_CUSTOM_MM_REALLOC, - ); - ALLOCATION_PROFILING_ALLOC = allocation_profiling_prev_alloc; - ALLOCATION_PROFILING_FREE = allocation_profiling_prev_free; - ALLOCATION_PROFILING_REALLOC = allocation_profiling_prev_realloc; - ALLOCATION_PROFILING_PREPARE_ZEND_HEAP = prepare_zend_heap_none; - ALLOCATION_PROFILING_RESTORE_ZEND_HEAP = restore_zend_heap_none; + // Safety: `zend_mm_get_heap()` always returns a non-null pointer to a valid heap structure + let heap = unsafe { Some(zend::zend_mm_get_heap()).unwrap() }; + + unsafe { ptr::addr_of_mut!((*zend_mm_state).heap).write(Some(heap)) }; + + if !is_zend_mm() { + // Neighboring custom memory handlers found + debug!("Found another extension using the ZendMM custom handler hook"); + unsafe { + zend::zend_mm_get_custom_handlers( + heap, + ptr::addr_of_mut!((*zend_mm_state).prev_custom_mm_alloc), + ptr::addr_of_mut!((*zend_mm_state).prev_custom_mm_free), + ptr::addr_of_mut!((*zend_mm_state).prev_custom_mm_realloc), + ); + ptr::addr_of_mut!((*zend_mm_state).alloc).write(alloc_prof_prev_alloc); + ptr::addr_of_mut!((*zend_mm_state).free).write(alloc_prof_prev_free); + ptr::addr_of_mut!((*zend_mm_state).realloc).write(alloc_prof_prev_realloc); + ptr::addr_of_mut!((*zend_mm_state).prepare_restore_zend_heap) + .write((prepare_zend_heap_none, restore_zend_heap_none)); + } + } else { + unsafe { + ptr::addr_of_mut!((*zend_mm_state).alloc).write(alloc_prof_orig_alloc); + ptr::addr_of_mut!((*zend_mm_state).free).write(alloc_prof_orig_free); + ptr::addr_of_mut!((*zend_mm_state).realloc).write(alloc_prof_orig_realloc); + ptr::addr_of_mut!((*zend_mm_state).prepare_restore_zend_heap) + .write((prepare_zend_heap, restore_zend_heap)); + + // Reset previous handlers to None. There might be a chaotic neighbor that + // registered custom handlers in an earlier request, but it doesn't do so for this + // request. In that case we would restore the neighbouring extensions custom + // handlers to the ZendMM in RSHUTDOWN which would lead to a crash! + ptr::addr_of_mut!((*zend_mm_state).prev_custom_mm_alloc).write(None); + ptr::addr_of_mut!((*zend_mm_state).prev_custom_mm_free).write(None); + ptr::addr_of_mut!((*zend_mm_state).prev_custom_mm_realloc).write(None); + } } - } else { + + // install our custom handler to ZendMM unsafe { - ALLOCATION_PROFILING_ALLOC = allocation_profiling_orig_alloc; - ALLOCATION_PROFILING_FREE = allocation_profiling_orig_free; - ALLOCATION_PROFILING_REALLOC = allocation_profiling_orig_realloc; - ALLOCATION_PROFILING_PREPARE_ZEND_HEAP = prepare_zend_heap; - ALLOCATION_PROFILING_RESTORE_ZEND_HEAP = restore_zend_heap; + zend::ddog_php_prof_zend_mm_set_custom_handlers( + heap, + Some(alloc_prof_malloc), + Some(alloc_prof_free), + Some(alloc_prof_realloc), + ); } - } - - // install our custom handler to ZendMM - unsafe { - zend::ddog_php_prof_zend_mm_set_custom_handlers( - // Safety: `unwrap()` is safe here, as `HEAP` is initialized just above - HEAP.unwrap(), - Some(alloc_profiling_malloc), - Some(alloc_profiling_free), - Some(alloc_profiling_realloc), - ); - } + }); - // `is_zend_mm()` should be `false` now, as we installed our custom handlers + // `is_zend_mm()` should be false now, as we installed our custom handlers if is_zend_mm() { // Can't proceed with it being disabled, because that's a system-wide // setting, not per-request. panic!("Memory allocation profiling could not be enabled. Please feel free to fill an issue stating the PHP version and installed modules. Most likely the reason is your PHP binary was compiled with `ZEND_MM_CUSTOM` being disabled."); - } else { - trace!("Memory allocation profiling enabled.") } + trace!("Memory allocation profiling enabled.") } -pub fn allocation_profiling_rshutdown() { - #[cfg(php_zts)] - return; - +pub fn alloc_prof_rshutdown() { let allocation_profiling = REQUEST_LOCALS.with(|cell| { cell.try_borrow() .map(|locals| locals.system_settings().profiling_allocation_enabled) @@ -212,78 +256,74 @@ pub fn allocation_profiling_rshutdown() { } // If `is_zend_mm()` is true, the custom handlers have been reset to `None` - // already. This is unexpected, therefore we will not touch the ZendMM handlers - // anymore as resetting to prev handlers might result in segfaults and other - // undefined behaviour. + // already. This is unexpected, therefore we will not touch the ZendMM + // handlers anymore as resetting to prev handlers might result in segfaults + // and other undefined behaviour. if is_zend_mm() { return; } - let mut custom_mm_malloc: Option = None; - let mut custom_mm_free: Option = None; - let mut custom_mm_realloc: Option = None; - unsafe { - zend::zend_mm_get_custom_handlers( - // Safety: `unwrap()` is safe here, as `HEAP` is initialized in `RINIT` - HEAP.unwrap(), - &mut custom_mm_malloc, - &mut custom_mm_free, - &mut custom_mm_realloc, - ); - } - if custom_mm_free != Some(alloc_profiling_free) - || custom_mm_malloc != Some(alloc_profiling_malloc) - || custom_mm_realloc != Some(alloc_profiling_realloc) - { - // Custom handlers are installed, but it's not us. Someone, somewhere might have - // function pointers to our custom handlers. Best bet to avoid segfaults is to not - // touch custom handlers in ZendMM and make sure our extension will not be - // `dlclose()`-ed so the pointers stay valid - let zend_extension = - unsafe { zend::zend_get_extension(PROFILER_NAME.as_ptr() as *const c_char) }; - if !zend_extension.is_null() { - // Safety: Checked for null pointer above. - unsafe { - (*zend_extension).handle = std::ptr::null_mut(); - } - } - 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."); - } else { - // This is the happy path (restore previously installed custom handlers)! + ZEND_MM_STATE.with(|cell| { + let zend_mm_state = cell.get(); + let mut custom_mm_malloc: Option = None; + let mut custom_mm_free: Option = None; + let mut custom_mm_realloc: Option = None; + // Safety: `unwrap()` is safe here, as `heap` is initialized in `RINIT` + let heap = unsafe { (*zend_mm_state).heap.unwrap() }; unsafe { - zend::ddog_php_prof_zend_mm_set_custom_handlers( - // Safety: `unwrap()` is safe here, as `HEAP` is initialized in `RINIT` - HEAP.unwrap(), - PREV_CUSTOM_MM_ALLOC, - PREV_CUSTOM_MM_FREE, - PREV_CUSTOM_MM_REALLOC, + zend::zend_mm_get_custom_handlers( + heap, + &mut custom_mm_malloc, + &mut custom_mm_free, + &mut custom_mm_realloc, ); } - trace!("Memory allocation profiling shutdown gracefully."); - } - unsafe { - HEAP = None; - } + if custom_mm_free != Some(alloc_prof_free) + || custom_mm_malloc != Some(alloc_prof_malloc) + || custom_mm_realloc != Some(alloc_prof_realloc) + { + // Custom handlers are installed, but it's not us. Someone, somewhere might have + // function pointers to our custom handlers. Best bet to avoid segfaults is to not + // touch custom handlers in ZendMM and make sure our extension will not be + // `dlclose()`-ed so the pointers stay valid + let zend_extension = + unsafe { zend::zend_get_extension(PROFILER_NAME.as_ptr() as *const c_char) }; + if !zend_extension.is_null() { + // Safety: Checked for null pointer above. + unsafe { ptr::addr_of_mut!((*zend_extension).handle).write(ptr::null_mut()) }; + } + 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."); + } else { + // This is the happy path. Restore previously installed custom handlers or + // NULL-pointers to the ZendMM. In case all pointers are NULL, the ZendMM will reset + // the `use_custom_heap` flag to `None`, in case we restore a neighbouring extension + // custom handlers, ZendMM will call those for future allocations. In either way, we + // have unregistered and we'll not receive any allocation calls anymore. + unsafe { + zend::ddog_php_prof_zend_mm_set_custom_handlers( + heap, + (*zend_mm_state).prev_custom_mm_alloc, + (*zend_mm_state).prev_custom_mm_free, + (*zend_mm_state).prev_custom_mm_realloc, + ); + } + trace!("Memory allocation profiling shutdown gracefully."); + } + unsafe { ptr::addr_of_mut!((*zend_mm_state).heap).write(None) }; + }); } -pub fn allocation_profiling_startup() { - #[cfg(php_zts)] - return; - +pub fn alloc_prof_startup() { unsafe { let handle = datadog_php_zif_handler::new( - CStr::from_bytes_with_nul_unchecked(b"gc_mem_caches\0"), + ffi::CStr::from_bytes_with_nul_unchecked(b"gc_mem_caches\0"), &mut GC_MEM_CACHES_HANDLER, - Some(alloc_profiling_gc_mem_caches), + Some(alloc_prof_gc_mem_caches), ); datadog_php_install_handler(handle); } } -static mut ALLOCATION_PROFILING_PREPARE_ZEND_HEAP: unsafe fn( - heap: *mut zend::_zend_mm_heap, -) -> c_int = prepare_zend_heap; - /// Overrides the ZendMM heap's `use_custom_heap` flag with the default `ZEND_MM_CUSTOM_HEAP_NONE` /// (currently a `u32: 0`). This needs to be done, as the `zend_mm_gc()` and `zend_mm_shutdown()` /// functions alter behaviour in case custom handlers are installed. @@ -293,8 +333,8 @@ static mut ALLOCATION_PROFILING_PREPARE_ZEND_HEAP: unsafe fn( /// is the first element and thus the first 4 bytes. /// Take care and call `restore_zend_heap()` afterwards! unsafe fn prepare_zend_heap(heap: *mut zend::_zend_mm_heap) -> c_int { - let custom_heap: c_int = std::ptr::read(heap as *const c_int); - std::ptr::write(heap as *mut c_int, zend::ZEND_MM_CUSTOM_HEAP_NONE as c_int); + let custom_heap: c_int = ptr::read(heap as *const c_int); + ptr::write(heap as *mut c_int, zend::ZEND_MM_CUSTOM_HEAP_NONE as c_int); custom_heap } @@ -302,14 +342,9 @@ fn prepare_zend_heap_none(_heap: *mut zend::_zend_mm_heap) -> c_int { 0 } -static mut ALLOCATION_PROFILING_RESTORE_ZEND_HEAP: unsafe fn( - heap: *mut zend::_zend_mm_heap, - custom_heap: c_int, -) = restore_zend_heap; - /// Restore the ZendMM heap's `use_custom_heap` flag, see `prepare_zend_heap` for details unsafe fn restore_zend_heap(heap: *mut zend::_zend_mm_heap, custom_heap: c_int) { - std::ptr::write(heap as *mut c_int, custom_heap); + ptr::write(heap as *mut c_int, custom_heap); } fn restore_zend_heap_none(_heap: *mut zend::_zend_mm_heap, _custom_heap: c_int) {} @@ -317,7 +352,7 @@ fn restore_zend_heap_none(_heap: *mut zend::_zend_mm_heap, _custom_heap: c_int) /// The PHP userland function `gc_mem_caches()` directly calls the `zend_mm_gc()` function which /// does nothing in case custom handlers are installed on the heap used. So we prepare the heap for /// this operation, call the original function and restore the heap again -unsafe extern "C" fn alloc_profiling_gc_mem_caches( +unsafe extern "C" fn alloc_prof_gc_mem_caches( execute_data: *mut zend::zend_execute_data, return_value: *mut zend::zval, ) { @@ -331,9 +366,10 @@ unsafe extern "C" fn alloc_profiling_gc_mem_caches( if let Some(func) = GC_MEM_CACHES_HANDLER { if allocation_profiling { let heap = zend::zend_mm_get_heap(); - let custom_heap = ALLOCATION_PROFILING_PREPARE_ZEND_HEAP(heap); + let (prepare, restore) = tls_zend_mm_state!(prepare_restore_zend_heap); + let custom_heap = prepare(heap); func(execute_data, return_value); - ALLOCATION_PROFILING_RESTORE_ZEND_HEAP(heap, custom_heap); + restore(heap, custom_heap); } else { func(execute_data, return_value); } @@ -342,11 +378,11 @@ unsafe extern "C" fn alloc_profiling_gc_mem_caches( } } -unsafe extern "C" fn alloc_profiling_malloc(len: size_t) -> *mut c_void { +unsafe extern "C" fn alloc_prof_malloc(len: size_t) -> *mut c_void { ALLOCATION_PROFILING_COUNT.fetch_add(1, SeqCst); ALLOCATION_PROFILING_SIZE.fetch_add(len as u64, SeqCst); - let ptr: *mut c_void = ALLOCATION_PROFILING_ALLOC(len); + let ptr = tls_zend_mm_state!(alloc)(len); // during startup, minit, rinit, ... current_execute_data is null // we are only interested in allocations during userland operations @@ -362,47 +398,49 @@ unsafe extern "C" fn alloc_profiling_malloc(len: size_t) -> *mut c_void { ptr } -static mut ALLOCATION_PROFILING_ALLOC: unsafe fn(size_t) -> *mut c_void = - allocation_profiling_orig_alloc; - -unsafe fn allocation_profiling_prev_alloc(len: size_t) -> *mut c_void { - let prev = PREV_CUSTOM_MM_ALLOC.unwrap(); - prev(len) +unsafe fn alloc_prof_prev_alloc(len: size_t) -> *mut c_void { + // Safety: `ALLOCATION_PROFILING_ALLOC` will be initialised in + // `alloc_prof_rinit()` and only point to this function when + // `prev_custom_mm_alloc` is also initialised + let alloc = tls_zend_mm_state!(prev_custom_mm_alloc).unwrap(); + alloc(len) } -unsafe fn allocation_profiling_orig_alloc(len: size_t) -> *mut c_void { +unsafe fn alloc_prof_orig_alloc(len: size_t) -> *mut c_void { let heap = zend::zend_mm_get_heap(); - let custom_heap = ALLOCATION_PROFILING_PREPARE_ZEND_HEAP(heap); + let (prepare, restore) = tls_zend_mm_state!(prepare_restore_zend_heap); + let custom_heap = prepare(heap); let ptr: *mut c_void = zend::_zend_mm_alloc(heap, len); - ALLOCATION_PROFILING_RESTORE_ZEND_HEAP(heap, custom_heap); + restore(heap, custom_heap); ptr } -// The reason this function exists is because when calling `zend_mm_set_custom_handlers()` you need -// to pass a pointer to a `free()` function as well, otherwise your custom handlers won't be -// installed. We can not just point to the original `zend::_zend_mm_free()` as the function -// definitions differ. -unsafe extern "C" fn alloc_profiling_free(ptr: *mut c_void) { - ALLOCATION_PROFILING_FREE(ptr); +/// This function exists because when calling `zend_mm_set_custom_handlers()`, +/// you need to pass a pointer to a `free()` function as well, otherwise your +/// custom handlers won't be installed. We can not just point to the original +/// `zend::_zend_mm_free()` as the function definitions differ. +unsafe extern "C" fn alloc_prof_free(ptr: *mut c_void) { + tls_zend_mm_state!(free)(ptr); } -static mut ALLOCATION_PROFILING_FREE: unsafe fn(*mut c_void) = allocation_profiling_orig_free; - -unsafe fn allocation_profiling_prev_free(ptr: *mut c_void) { - let prev = PREV_CUSTOM_MM_FREE.unwrap(); - prev(ptr); +unsafe fn alloc_prof_prev_free(ptr: *mut c_void) { + // Safety: `ALLOCATION_PROFILING_FREE` will be initialised in + // `alloc_prof_free()` and only point to this function when + // `prev_custom_mm_free` is also initialised + let free = tls_zend_mm_state!(prev_custom_mm_free).unwrap(); + free(ptr) } -unsafe fn allocation_profiling_orig_free(ptr: *mut c_void) { +unsafe fn alloc_prof_orig_free(ptr: *mut c_void) { let heap = zend::zend_mm_get_heap(); zend::_zend_mm_free(heap, ptr); } -unsafe extern "C" fn alloc_profiling_realloc(prev_ptr: *mut c_void, len: size_t) -> *mut c_void { +unsafe extern "C" fn alloc_prof_realloc(prev_ptr: *mut c_void, len: size_t) -> *mut c_void { ALLOCATION_PROFILING_COUNT.fetch_add(1, SeqCst); ALLOCATION_PROFILING_SIZE.fetch_add(len as u64, SeqCst); - let ptr: *mut c_void = ALLOCATION_PROFILING_REALLOC(prev_ptr, len); + let ptr = tls_zend_mm_state!(realloc)(prev_ptr, len); // during startup, minit, rinit, ... current_execute_data is null // we are only interested in allocations during userland operations @@ -418,19 +456,20 @@ unsafe extern "C" fn alloc_profiling_realloc(prev_ptr: *mut c_void, len: size_t) ptr } -static mut ALLOCATION_PROFILING_REALLOC: unsafe fn(*mut c_void, size_t) -> *mut c_void = - allocation_profiling_orig_realloc; - -unsafe fn allocation_profiling_prev_realloc(prev_ptr: *mut c_void, len: size_t) -> *mut c_void { - let prev = PREV_CUSTOM_MM_REALLOC.unwrap(); - prev(prev_ptr, len) +unsafe fn alloc_prof_prev_realloc(prev_ptr: *mut c_void, len: size_t) -> *mut c_void { + // Safety: `ALLOCATION_PROFILING_REALLOC` will be initialised in + // `alloc_prof_realloc()` and only point to this function when + // `prev_custom_mm_realloc` is also initialised + let realloc = tls_zend_mm_state!(prev_custom_mm_realloc).unwrap(); + realloc(prev_ptr, len) } -unsafe fn allocation_profiling_orig_realloc(prev_ptr: *mut c_void, len: size_t) -> *mut c_void { +unsafe fn alloc_prof_orig_realloc(prev_ptr: *mut c_void, len: size_t) -> *mut c_void { let heap = zend::zend_mm_get_heap(); - let custom_heap = ALLOCATION_PROFILING_PREPARE_ZEND_HEAP(heap); + let (prepare, restore) = tls_zend_mm_state!(prepare_restore_zend_heap); + let custom_heap = prepare(heap); let ptr: *mut c_void = zend::_zend_mm_realloc(heap, prev_ptr, len); - ALLOCATION_PROFILING_RESTORE_ZEND_HEAP(heap, custom_heap); + restore(heap, custom_heap); ptr } @@ -439,12 +478,12 @@ unsafe fn allocation_profiling_orig_realloc(prev_ptr: *mut c_void, len: size_t) /// installed. Upstream returns a `c_bool` as of PHP 8.0. PHP 7 returns a `c_int` fn is_zend_mm() -> bool { #[cfg(php7)] - { - unsafe { zend::is_zend_mm() == 1 } + unsafe { + zend::is_zend_mm() == 1 } #[cfg(php8)] - { - unsafe { zend::is_zend_mm() } + unsafe { + zend::is_zend_mm() } } @@ -455,18 +494,18 @@ mod tests { #[test] fn check_versions_that_allocation_profiling_needs_disabled_with_active_jit() { // versions that need disabled allocation profiling with active jit - assert!(allocation_profiling_needs_disabled_for_jit(80000)); - assert!(allocation_profiling_needs_disabled_for_jit(80100)); - assert!(allocation_profiling_needs_disabled_for_jit(80120)); - assert!(allocation_profiling_needs_disabled_for_jit(80200)); - assert!(allocation_profiling_needs_disabled_for_jit(80207)); + assert!(alloc_prof_needs_disabled_for_jit(80000)); + assert!(alloc_prof_needs_disabled_for_jit(80100)); + assert!(alloc_prof_needs_disabled_for_jit(80120)); + assert!(alloc_prof_needs_disabled_for_jit(80200)); + assert!(alloc_prof_needs_disabled_for_jit(80207)); // versions that DO NOT need disabled allocation profiling with active jit - assert!(!allocation_profiling_needs_disabled_for_jit(70421)); - assert!(!allocation_profiling_needs_disabled_for_jit(80121)); - assert!(!allocation_profiling_needs_disabled_for_jit(80122)); - assert!(!allocation_profiling_needs_disabled_for_jit(80208)); - assert!(!allocation_profiling_needs_disabled_for_jit(80209)); - assert!(!allocation_profiling_needs_disabled_for_jit(80300)); + assert!(!alloc_prof_needs_disabled_for_jit(70421)); + assert!(!alloc_prof_needs_disabled_for_jit(80121)); + assert!(!alloc_prof_needs_disabled_for_jit(80122)); + assert!(!alloc_prof_needs_disabled_for_jit(80208)); + assert!(!alloc_prof_needs_disabled_for_jit(80209)); + assert!(!alloc_prof_needs_disabled_for_jit(80300)); } } diff --git a/profiling/src/lib.rs b/profiling/src/lib.rs index 881564310bf..b664e0a9e3f 100644 --- a/profiling/src/lib.rs +++ b/profiling/src/lib.rs @@ -282,7 +282,7 @@ extern "C" fn minit(_type: c_int, module_number: c_int) -> ZendResult { unsafe { zend::zend_register_extension(&extension, handle) }; #[cfg(feature = "allocation_profiling")] - allocation::allocation_profiling_minit(); + allocation::alloc_prof_minit(); #[cfg(feature = "timeline")] timeline::timeline_minit(); @@ -369,8 +369,8 @@ thread_local! { /// The tags for this thread/request. These get sent to other threads, /// which is why they are Arc. However, they are wrapped in a RefCell /// because the values _can_ change from request to request depending on - /// the on the values sent in the SAPI for env, service, version, etc. - /// They get reset at the end of the request. + /// the values sent in the SAPI for env, service, version, etc. They get + /// reset at the end of the request. static TAGS: RefCell>> = RefCell::new(Arc::new(Vec::new())); } @@ -541,7 +541,7 @@ extern "C" fn rinit(_type: c_int, _module_number: c_int) -> ZendResult { } #[cfg(feature = "allocation_profiling")] - allocation::allocation_profiling_rinit(); + allocation::alloc_prof_rinit(); // SAFETY: called after config is initialized. #[cfg(feature = "timeline")] @@ -599,7 +599,7 @@ extern "C" fn rshutdown(_type: c_int, _module_number: c_int) -> ZendResult { }); #[cfg(feature = "allocation_profiling")] - allocation::allocation_profiling_rshutdown(); + allocation::alloc_prof_rshutdown(); ZendResult::Success } @@ -843,7 +843,7 @@ extern "C" fn startup(extension: *mut ZendExtension) -> ZendResult { } #[cfg(feature = "allocation_profiling")] - allocation::allocation_profiling_startup(); + allocation::alloc_prof_startup(); ZendResult::Success } diff --git a/profiling/src/pcntl.rs b/profiling/src/pcntl.rs index a11b16b2e3f..1a740b75247 100644 --- a/profiling/src/pcntl.rs +++ b/profiling/src/pcntl.rs @@ -1,4 +1,4 @@ -use crate::allocation::allocation_profiling_rshutdown; +use crate::allocation::alloc_prof_rshutdown; use crate::bindings::{ datadog_php_install_handler, datadog_php_zif_handler, zend_execute_data, zend_long, zval, InternalFunctionHandler, @@ -94,7 +94,7 @@ fn stop_and_forget_profiling(maybe_profiler: &mut Option) { swap(&mut *maybe_profiler, &mut old_profiler); forget(old_profiler); - allocation_profiling_rshutdown(); + alloc_prof_rshutdown(); // Reset some global state to prevent further profiling and to not handle // any pending interrupts. diff --git a/profiling/tests/phpt/jit_01.phpt b/profiling/tests/phpt/jit_01.phpt index 3ead1a21ea2..60fce7135b1 100644 --- a/profiling/tests/phpt/jit_01.phpt +++ b/profiling/tests/phpt/jit_01.phpt @@ -15,11 +15,6 @@ if (!extension_loaded('datadog-profiling')) $arch = php_uname('m'); if (PHP_VERSION_ID < 80100 && in_array($arch, ['aarch64', 'arm64'])) echo "skip: JIT not available on aarch64 on PHP 8.0", PHP_EOL; - -// TODO: remove once ZTS support for allocation profiling is done -if (PHP_ZTS) { - echo "skip: not support on ZTS builds at the moment"; -} ?> --INI-- datadog.profiling.enabled=yes diff --git a/profiling/tests/phpt/jit_02.phpt b/profiling/tests/phpt/jit_02.phpt index 84e9a5bdb78..c23ae845812 100644 --- a/profiling/tests/phpt/jit_02.phpt +++ b/profiling/tests/phpt/jit_02.phpt @@ -14,12 +14,6 @@ if (!extension_loaded('datadog-profiling')) $arch = php_uname('m'); if (PHP_VERSION_ID < 80100 && in_array($arch, ['aarch64', 'arm64'])) echo "skip: JIT not available on aarch64 on PHP 8.0", PHP_EOL; - -// TODO: remove once ZTS support for allocation profiling is done -if (PHP_ZTS) { - echo "skip: not support on ZTS builds at the moment"; -} - ?> --INI-- datadog.profiling.enabled=yes diff --git a/profiling/tests/phpt/phpinfo_02.phpt b/profiling/tests/phpt/phpinfo_02.phpt index afab305f1d7..9a9058ead8e 100644 --- a/profiling/tests/phpt/phpinfo_02.phpt +++ b/profiling/tests/phpt/phpinfo_02.phpt @@ -14,11 +14,6 @@ if (!extension_loaded('datadog-profiling')) $arch = php_uname('m'); if (PHP_VERSION_ID < 80100 && in_array($arch, ['aarch64', 'arm64'])) echo "skip: JIT not available on aarch64 on PHP 8.0", PHP_EOL; - -// TODO: remove once ZTS support for allocation profiling is done -if (PHP_ZTS) { - echo "skip: not support on ZTS builds at the moment"; -} ?> --ENV-- DD_PROFILING_ENABLED=yes