diff --git a/.github/workflows/prof_correctness.yml b/.github/workflows/prof_correctness.yml index de76304a0c2..4e468cae263 100644 --- a/.github/workflows/prof_correctness.yml +++ b/.github/workflows/prof_correctness.yml @@ -102,6 +102,10 @@ jobs: export DD_PROFILING_OUTPUT_PPROF=$PWD/profiling/tests/correctness/allocations_1byte/test.pprof export DD_PROFILING_ALLOCATION_SAMPLING_DISTANCE=1 php -d extension=$PWD/target/profiler-release/libdatadog_php_profiling.so profiling/tests/correctness/allocations.php + mkdir -p profiling/tests/correctness/allocations_1byte_no_zend_alloc/ + export DD_PROFILING_OUTPUT_PPROF=$PWD/profiling/tests/correctness/allocations_1byte_no_zend_alloc/test.pprof + export DD_PROFILING_ALLOCATION_SAMPLING_DISTANCE=1 + USE_ZEND_ALLOC=0 php -d extension=$PWD/target/profiler-release/libdatadog_php_profiling.so profiling/tests/correctness/allocations.php unset DD_PROFILING_ALLOCATION_SAMPLING_DISTANCE - name: Run ZTS tests @@ -131,6 +135,12 @@ jobs: expected_json: profiling/tests/correctness/allocations.json pprof_path: profiling/tests/correctness/allocations_1byte/ + - name: Check profiler correctness for allocations with 1 byte sampling distance and `USE_ZEND_ALLOC=0` + uses: Datadog/prof-correctness/analyze@main + with: + expected_json: profiling/tests/correctness/allocations.json + pprof_path: profiling/tests/correctness/allocations_1byte_no_zend_alloc/ + - name: Check profiler correctness for time uses: Datadog/prof-correctness/analyze@main with: diff --git a/profiling/build.rs b/profiling/build.rs index 911fdf05f91..55d9c0786e6 100644 --- a/profiling/build.rs +++ b/profiling/build.rs @@ -400,10 +400,7 @@ fn cfg_php_feature_flags(vernum: u64) { if vernum >= 80400 { println!("cargo:rustc-cfg=php_frameless"); println!("cargo:rustc-cfg=php_opcache_restart_hook"); - // Commenting the following line temporary disables the new hooking mechanism for - // allocation profiling until we solved the intefereing with - // `memory_get_usage()`/`memory_get_peak_usage()` - // println!("cargo:rustc-cfg=php_zend_mm_set_custom_handlers_ex"); + println!("cargo:rustc-cfg=php_zend_mm_set_custom_handlers_ex"); } } diff --git a/profiling/src/allocation/allocation_ge84.rs b/profiling/src/allocation/allocation_ge84.rs index 4554738ffcd..1b5cf53b19e 100644 --- a/profiling/src/allocation/allocation_ge84.rs +++ b/profiling/src/allocation/allocation_ge84.rs @@ -1,11 +1,9 @@ -use crate::allocation::{ - allocation_profiling_stats_mut, allocation_profiling_stats_should_collect, collect_allocation, -}; -use crate::bindings::{self as zend}; -use crate::{RefCellExt, PROFILER_NAME}; +use crate::allocation::{allocation_profiling_stats_should_collect, collect_allocation}; +use crate::bindings as zend; +use crate::PROFILER_NAME; use core::{cell::Cell, ptr}; use lazy_static::lazy_static; -use libc::{c_char, c_void, size_t}; +use libc::{c_char, c_int, c_void, size_t}; use log::{debug, error, trace, warn}; use std::sync::atomic::Ordering::Relaxed; @@ -14,10 +12,11 @@ use crate::allocation::{ALLOCATION_PROFILING_COUNT, ALLOCATION_PROFILING_SIZE}; #[derive(Copy, Clone)] struct ZendMMState { - /// The heap we create and set as the current heap in ZendMM - heap: *mut zend::zend_mm_heap, - /// The heap installed in ZendMM at the time we install our custom handlers - prev_heap: *mut zend::zend_mm_heap, + /// 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. @@ -55,29 +54,18 @@ struct ZendMMState { shutdown: unsafe fn(bool, bool), } -#[track_caller] -fn initialization_panic() -> ! { - panic!("Allocation profiler was not initialised properly. Please fill an issue stating the PHP version and the backtrace from this panic."); -} - unsafe fn alloc_prof_panic_gc() -> size_t { - initialization_panic(); + super::initialization_panic(); } unsafe fn alloc_prof_panic_shutdown(_full: bool, _silent: bool) { - initialization_panic(); + super::initialization_panic(); } impl ZendMMState { const fn new() -> ZendMMState { ZendMMState { - // Safety: Using `ptr::null_mut()` might seem dangerous but actually it is okay in this - // case. The `heap` and `prev_heap` fields will be initialized in the first call to - // RINIT and only used after that. By using this "trick" we can get rid of all - // `unwrap()` calls when using the `heap` or `prev_heap` field. Alternatively we could - // use `unwrap_unchecked()` for the same performance characteristics. - heap: ptr::null_mut(), - prev_heap: ptr::null_mut(), + heap: None, prev_custom_mm_alloc: None, prev_custom_mm_realloc: None, prev_custom_mm_free: None, @@ -141,14 +129,26 @@ macro_rules! tls_zend_mm_state_set { }; } +const NEEDS_RUN_TIME_CHECK_FOR_ENABLED_JIT: bool = + zend::PHP_VERSION_ID >= 80400 && zend::PHP_VERSION_ID < 80500; + +fn alloc_prof_needs_disabled_for_jit(version: u32) -> bool { + // see https://github.com/php/php-src/pull/11380 + (80400..80407).contains(&version) +} + lazy_static! { static ref JIT_ENABLED: bool = unsafe { zend::ddog_php_jit_enabled() }; } +pub fn alloc_prof_ginit() { + unsafe { zend::ddog_php_opcache_init_handle() }; +} + pub fn first_rinit_should_disable_due_to_jit() -> bool { - if *JIT_ENABLED - && zend::PHP_VERSION_ID >= 80400 - && (80400..80406).contains(&crate::RUNTIME_PHP_VERSION_ID.load(Relaxed)) + if NEEDS_RUN_TIME_CHECK_FOR_ENABLED_JIT + && alloc_prof_needs_disabled_for_jit(crate::RUNTIME_PHP_VERSION_ID.load(Relaxed)) + && *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.4.7. See https://github.com/DataDog/dd-trace-php/pull/3199"); true @@ -157,69 +157,53 @@ pub fn first_rinit_should_disable_due_to_jit() -> bool { } } -/// This initializes the thread locale variable `ZEND_MM_STATE` with respect to the currently -/// installed `zend_mm_heap` in ZendMM. It guarantees compliance with the safety guarantees -/// described in the `ZendMMState` structure, specifically for `ZendMMState::alloc`, -/// `ZendMMState::realloc`, `ZendMMState::free`, `ZendMMState::gc` and `ZendMMState::shutdown`. -/// This function may panic if called out of order! -pub fn alloc_prof_ginit() { - unsafe { zend::ddog_php_opcache_init_handle() }; +pub fn alloc_prof_rinit() { let zend_mm_state_init = |mut zend_mm_state: ZendMMState| -> ZendMMState { - // Only need to create an observed heap once per thread. When we have it, we can just - // install the observed heap via `zend::zend_mm_set_heap()` - if !zend_mm_state.heap.is_null() { - // This can only happen if either MINIT or GINIT is being called out of order. - panic!("MINIT/GINIT was called with an already initialized allocation profiler. Most likely the SAPI did this without going through MSHUTDOWN/GSHUTDOWN before."); - } - // Safety: `zend_mm_get_heap()` always returns a non-null pointer to a valid heap structure - let prev_heap = unsafe { zend::zend_mm_get_heap() }; - zend_mm_state.prev_heap = prev_heap; + let heap = unsafe { zend::zend_mm_get_heap() }; + + zend_mm_state.heap = Some(heap); - if !is_zend_mm() { - // Neighboring custom memory handlers found in the currently used ZendMM heap + if unsafe { !zend::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_ex( - prev_heap, + 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.prev_custom_mm_gc), ptr::addr_of_mut!(zend_mm_state.prev_custom_mm_shutdown), ); - zend_mm_state.alloc = alloc_prof_prev_alloc; - zend_mm_state.free = alloc_prof_prev_free; - zend_mm_state.realloc = alloc_prof_prev_realloc; - // `gc` handler can be NULL - zend_mm_state.gc = if zend_mm_state.prev_custom_mm_gc.is_none() { - alloc_prof_orig_gc - } else { - alloc_prof_prev_gc - }; - // `shutdown` handler can be NULL - zend_mm_state.shutdown = if zend_mm_state.prev_custom_mm_shutdown.is_none() { - alloc_prof_orig_shutdown - } else { - alloc_prof_prev_shutdown - } } + zend_mm_state.alloc = alloc_prof_prev_alloc; + zend_mm_state.free = alloc_prof_prev_free; + zend_mm_state.realloc = alloc_prof_prev_realloc; + zend_mm_state.gc = alloc_prof_prev_gc; + zend_mm_state.shutdown = alloc_prof_prev_shutdown; } else { zend_mm_state.alloc = alloc_prof_orig_alloc; zend_mm_state.free = alloc_prof_orig_free; zend_mm_state.realloc = alloc_prof_orig_realloc; zend_mm_state.gc = alloc_prof_orig_gc; zend_mm_state.shutdown = alloc_prof_orig_shutdown; - } - // Create a new (to be observed) heap and prepare custom handlers - let heap = unsafe { zend::zend_mm_startup() }; - zend_mm_state.heap = 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! + zend_mm_state.prev_custom_mm_alloc = None; + zend_mm_state.prev_custom_mm_free = None; + zend_mm_state.prev_custom_mm_realloc = None; + zend_mm_state.prev_custom_mm_gc = None; + zend_mm_state.prev_custom_mm_shutdown = None; + } // install our custom handler to ZendMM unsafe { zend::zend_mm_set_custom_handlers_ex( - zend_mm_state.heap, + heap, Some(alloc_prof_malloc), Some(alloc_prof_free), Some(alloc_prof_realloc), @@ -227,61 +211,14 @@ pub fn alloc_prof_ginit() { Some(alloc_prof_shutdown), ); } - debug!("New observed heap created"); zend_mm_state }; let mm_state = tls_zend_mm_state_copy!(); tls_zend_mm_state_set!(zend_mm_state_init(mm_state)); -} - -/// This resets the thread locale variable `ZEND_MM_STATE` and frees allocated memory. It -/// guarantees compliance with the safety guarantees described in the `ZendMMState` structure, -/// specifically for `ZendMMState::alloc`, `ZendMMState::realloc`, `ZendMMState::free`, -/// `ZendMMState::gc` and `ZendMMState::shutdown`. -pub fn alloc_prof_gshutdown() { - let zend_mm_state_shutdown = |mut zend_mm_state: ZendMMState| -> ZendMMState { - unsafe { - // Remove custom handlers to allow for ZendMM internal shutdown - zend::zend_mm_set_custom_handlers_ex(zend_mm_state.heap, None, None, None, None, None); - - // Reset ZEND_MM_STATE to defaults, now that the pointer are not know to the observed - // heap anymore. - zend_mm_state.alloc = alloc_prof_orig_alloc; - zend_mm_state.free = alloc_prof_orig_free; - zend_mm_state.realloc = alloc_prof_orig_realloc; - zend_mm_state.gc = alloc_prof_orig_gc; - zend_mm_state.shutdown = alloc_prof_orig_shutdown; - zend_mm_state.prev_custom_mm_alloc = None; - zend_mm_state.prev_custom_mm_free = None; - zend_mm_state.prev_custom_mm_realloc = None; - zend_mm_state.prev_custom_mm_gc = None; - zend_mm_state.prev_custom_mm_shutdown = None; - - // This shutdown call will free the observed heap we created in `alloc_prof_custom_heap_init` - zend::zend_mm_shutdown(zend_mm_state.heap, true, true); - - // Now that the heap is gone, we need to NULL the pointer - zend_mm_state.heap = ptr::null_mut(); - zend_mm_state.prev_heap = ptr::null_mut(); - } - trace!("Observed heap was freed and `zend_mm_state` reset"); - zend_mm_state - }; - - let mm_state = tls_zend_mm_state_copy!(); - tls_zend_mm_state_set!(zend_mm_state_shutdown(mm_state)); -} - -pub fn alloc_prof_rinit() { - let heap = tls_zend_mm_state_get!(heap); - // Install our observed heap into ZendMM - // Safety: `heap` got initialized in `MINIT` and is guaranteed to be a - // non-null pointer to a valid `zend::zend_mm_heap` struct. - unsafe { zend::zend_mm_set_heap(heap) }; // `is_zend_mm()` should be false now, as we installed our custom handlers - if is_zend_mm() { + if unsafe { zend::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."); @@ -291,28 +228,27 @@ pub fn alloc_prof_rinit() { #[allow(unknown_lints, unpredictable_function_pointer_comparisons)] pub fn alloc_prof_rshutdown() { - // If `is_zend_mm()` is true, the custom handlers have been reset to `None` or our observed - // heap has been uninstalled. 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() { + // If `is_zend_mm()` is true, the custom handlers have already been reset + // to `None`. This is unexpected, therefore we will not touch the ZendMM + // handlers anymore as resetting to prev handlers might result in segfaults + // and other undefined behavior. + if unsafe { zend::is_zend_mm() } { return; } - let zend_mm_state_shutdown = |zend_mm_state: ZendMMState| { - // Do a sanity check and see if something played with our heap + let zend_mm_state_shutdown = |mut zend_mm_state: ZendMMState| -> ZendMMState { let mut custom_mm_malloc: Option = None; let mut custom_mm_free: Option = None; let mut custom_mm_realloc: Option = None; let mut custom_mm_gc: Option = None; let mut custom_mm_shutdown: Option = None; - let heap = zend_mm_state.heap; - - // The heap ptr can be null if a fork happens outside the request. - if heap.is_null() { - return; - } + // SAFETY: UnsafeCell::get() ensures non-null, and the object should + // be valid for reads during rshutdown. + let Some(heap) = zend_mm_state.heap else { + // The heap can be None if a fork happens outside the request. + return zend_mm_state; + }; unsafe { zend::zend_mm_get_custom_handlers_ex( @@ -330,28 +266,63 @@ pub fn alloc_prof_rshutdown() { || custom_mm_gc != Some(alloc_prof_gc) || custom_mm_shutdown != Some(alloc_prof_shutdown) { - // Custom handlers are installed, but it's not us. Someone, - // somewhere might have function pointers to our custom handlers. - // The 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. + // 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 a null pointer above. + // 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 the previous heap. + // 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::zend_mm_set_heap(zend_mm_state.prev_heap); + zend::zend_mm_set_custom_handlers_ex( + heap, + zend_mm_state.prev_custom_mm_alloc, + zend_mm_state.prev_custom_mm_free, + zend_mm_state.prev_custom_mm_realloc, + zend_mm_state.prev_custom_mm_gc, + zend_mm_state.prev_custom_mm_shutdown, + ); } trace!("Memory allocation profiling shutdown gracefully."); } + zend_mm_state.heap = None; + zend_mm_state }; - zend_mm_state_shutdown(tls_zend_mm_state_copy!()); + let mm_state = tls_zend_mm_state_copy!(); + tls_zend_mm_state_set!(zend_mm_state_shutdown(mm_state)); +} + +/// 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 behavior +/// in case custom handlers are installed. +/// +/// - `zend_mm_gc()` will not do anything anymore. +/// - `zend_mm_shutdown()` won't clean up chunks anymore (leaks memory) +/// +/// The `_zend_mm_heap`-struct itself is private, but we are lucky, as the +/// `use_custom_heap` flag is the first element and thus the first 4 bytes. +/// Take care and call `restore_zend_heap()` afterward! +unsafe fn prepare_zend_heap(heap: *mut zend::_zend_mm_heap) -> 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 +} + +/// 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) { + ptr::write(heap as *mut c_int, custom_heap); } unsafe extern "C" fn alloc_prof_malloc(len: size_t) -> *mut c_void { @@ -376,51 +347,41 @@ unsafe extern "C" fn alloc_prof_malloc(len: size_t) -> *mut c_void { } unsafe fn alloc_prof_prev_alloc(len: size_t) -> *mut c_void { - let alloc = |zend_mm_state: ZendMMState| { - // Safety: `ZEND_MM_STATE.prev_heap` got initialised in `alloc_prof_rinit()` - zend::zend_mm_set_heap(zend_mm_state.prev_heap); - // Safety: `ZEND_MM_STATE.prev_custom_mm_alloc` will be initialised in - // `alloc_prof_rinit()` and only point to this function when - // `prev_custom_mm_alloc` is also initialised - let ptr = zend_mm_state.prev_custom_mm_alloc.unwrap_unchecked()(len); - // Safety: `ZEND_MM_STATE.heap` got initialised in `alloc_prof_rinit()` - zend::zend_mm_set_heap(zend_mm_state.heap); - ptr - }; - - alloc(tls_zend_mm_state_copy!()) + // Safety: `ZEND_MM_STATE.prev_custom_mm_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_get!(prev_custom_mm_alloc).unwrap(); + alloc(len) } unsafe fn alloc_prof_orig_alloc(len: size_t) -> *mut c_void { - let ptr: *mut c_void = zend::_zend_mm_alloc(tls_zend_mm_state_get!(prev_heap), len); - ptr + // Safety: `ZEND_MM_STATE.heap` will be initialised in `alloc_prof_rinit()` and custom ZendMM + // handlers are only installed and pointing to this function if initialization was succesful. + let heap = tls_zend_mm_state_get!(heap).unwrap_unchecked(); + zend::_zend_mm_alloc(heap, len) } /// 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 +/// custom handlers won't be installed. We cannot 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_get!(free)(ptr); } unsafe fn alloc_prof_prev_free(ptr: *mut c_void) { - let free = |zend_mm_state: ZendMMState| { - // Safety: `ZEND_MM_STATE.prev_heap` got initialised in `alloc_prof_rinit()` - zend::zend_mm_set_heap(zend_mm_state.prev_heap); - // Safety: `ZEND_MM_STATE.prev_custom_mm_free` will be initialised in - // `alloc_prof_rinit()` and only point to this function when - // `prev_custom_mm_free` is also initialised - (zend_mm_state.prev_custom_mm_free.unwrap_unchecked())(ptr); - // Safety: `ZEND_MM_STATE.heap` got initialised in `alloc_prof_rinit()` - zend::zend_mm_set_heap(zend_mm_state.heap); - }; - - free(tls_zend_mm_state_copy!()) + // Safety: `ZEND_MM_STATE.prev_custom_mm_free` will be initialised in + // `alloc_prof_rinit()` and only point to this function when + // `prev_custom_mm_free` is also initialised + let free = tls_zend_mm_state_get!(prev_custom_mm_free).unwrap(); + free(ptr) } unsafe fn alloc_prof_orig_free(ptr: *mut c_void) { - zend::_zend_mm_free(tls_zend_mm_state_get!(prev_heap), ptr); + // Safety: `ZEND_MM_STATE.heap` will be initialised in `alloc_prof_rinit()` and custom ZendMM + // handlers are only installed and pointing to this function if initialization was succesful. + let heap = tls_zend_mm_state_get!(heap).unwrap_unchecked(); + zend::_zend_mm_free(heap, ptr); } unsafe extern "C" fn alloc_prof_realloc(prev_ptr: *mut c_void, len: size_t) -> *mut c_void { @@ -445,23 +406,18 @@ unsafe extern "C" fn alloc_prof_realloc(prev_ptr: *mut c_void, len: size_t) -> * } unsafe fn alloc_prof_prev_realloc(prev_ptr: *mut c_void, len: size_t) -> *mut c_void { - let realloc = |zend_mm_state: ZendMMState| { - // Safety: `ZEND_MM_STATE.prev_heap` got initialised in `alloc_prof_rinit()` - zend::zend_mm_set_heap(zend_mm_state.prev_heap); - // Safety: `ZEND_MM_STATE.prev_custom_mm_realloc` will be initialised in - // `alloc_prof_rinit()` and only point to this function when - // `prev_custom_mm_realloc` is also initialised - let ptr = zend_mm_state.prev_custom_mm_realloc.unwrap_unchecked()(prev_ptr, len); - // Safety: `ZEND_MM_STATE.heap` got initialised in `alloc_prof_rinit()` - zend::zend_mm_set_heap(zend_mm_state.heap); - ptr - }; - - realloc(tls_zend_mm_state_copy!()) + // Safety: `ZEND_MM_STATE.prev_custom_mm_realloc` will be initialised in + // `alloc_prof_rinit()` and only point to this function when + // `prev_custom_mm_realloc` is also initialised + let realloc = tls_zend_mm_state_get!(prev_custom_mm_realloc).unwrap(); + realloc(prev_ptr, len) } unsafe fn alloc_prof_orig_realloc(prev_ptr: *mut c_void, len: size_t) -> *mut c_void { - zend::_zend_mm_realloc(tls_zend_mm_state_get!(prev_heap), prev_ptr, len) + // Safety: `ZEND_MM_STATE.heap` will be initialised in `alloc_prof_rinit()` and custom ZendMM + // handlers are only installed and pointing to this function if initialization was succesful. + let heap = tls_zend_mm_state_get!(heap).unwrap_unchecked(); + zend::_zend_mm_realloc(heap, prev_ptr, len) } unsafe extern "C" fn alloc_prof_gc() -> size_t { @@ -469,51 +425,53 @@ unsafe extern "C" fn alloc_prof_gc() -> size_t { } unsafe fn alloc_prof_prev_gc() -> size_t { - let gc = |zend_mm_state: ZendMMState| { - // Safety: `ZEND_MM_STATE.prev_heap` got initialised in `alloc_prof_rinit()` - zend::zend_mm_set_heap(zend_mm_state.prev_heap); - // Safety: `ZEND_MM_STATE.prev_custom_mm_gc` will be initialised in - // `alloc_prof_rinit()` and only point to this function when - // `prev_custom_mm_gc` is also initialised - let freed = zend_mm_state.prev_custom_mm_gc.unwrap_unchecked()(); - // Safety: `ZEND_MM_STATE.heap` got initialised in `alloc_prof_rinit()` - zend::zend_mm_set_heap(zend_mm_state.heap); - freed - }; - - gc(tls_zend_mm_state_copy!()) + match tls_zend_mm_state_get!(prev_custom_mm_gc) { + Some(gc) => gc(), + None => 0, + } } unsafe fn alloc_prof_orig_gc() -> size_t { - zend::zend_mm_gc(tls_zend_mm_state_get!(prev_heap)) + // Safety: `ZEND_MM_STATE.heap` will be initialised in `alloc_prof_rinit()` and custom ZendMM + // handlers are only installed and pointing to this function if initialization was succesful. + let heap = tls_zend_mm_state_get!(heap).unwrap_unchecked(); + let custom_heap = prepare_zend_heap(heap); + let size = zend::zend_mm_gc(heap); + restore_zend_heap(heap, custom_heap); + size } unsafe extern "C" fn alloc_prof_shutdown(full: bool, silent: bool) { - tls_zend_mm_state_get!(shutdown)(full, silent); + tls_zend_mm_state_get!(shutdown)(full, silent) } unsafe fn alloc_prof_prev_shutdown(full: bool, silent: bool) { - let shutdown = |zend_mm_state: ZendMMState| { - // Safety: `ZEND_MM_STATE.prev_heap` got initialised in `alloc_prof_rinit()` - zend::zend_mm_set_heap(zend_mm_state.prev_heap); - // Safety: `ZEND_MM_STATE.prev_custom_mm_shutdown` will be initialised in - // `alloc_prof_rinit()` and only point to this function when - // `prev_custom_mm_shutdown` is also initialised - zend_mm_state.prev_custom_mm_shutdown.unwrap_unchecked()(full, silent); - // Safety: `ZEND_MM_STATE.heap` got initialised in `alloc_prof_rinit()` - zend::zend_mm_set_heap(zend_mm_state.heap); - }; - - shutdown(tls_zend_mm_state_copy!()) + if let Some(shutdown) = tls_zend_mm_state_get!(prev_custom_mm_shutdown) { + shutdown(full, silent) + } } unsafe fn alloc_prof_orig_shutdown(full: bool, silent: bool) { - zend::zend_mm_shutdown(tls_zend_mm_state_get!(prev_heap), full, silent) -} - -/// safe wrapper for `zend::is_zend_mm()`. -/// `true` means the internal ZendMM is being used, `false` means that a custom memory manager is -/// installed -fn is_zend_mm() -> bool { - unsafe { zend::is_zend_mm() } + // Safety: `ZEND_MM_STATE.heap` will be initialised in `alloc_prof_rinit()` and custom ZendMM + // handlers are only installed and pointing to this function if initialization was succesful. + let heap = tls_zend_mm_state_get!(heap).unwrap_unchecked(); + let custom_heap = prepare_zend_heap(heap); + zend::zend_mm_shutdown(heap, full, silent); + restore_zend_heap(heap, custom_heap); +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn check_versions_that_allocation_profiling_needs_disabled_with_active_jit() { + // versions that need disabled allocation profiling with active jit + assert!(alloc_prof_needs_disabled_for_jit(80400)); + assert!(alloc_prof_needs_disabled_for_jit(80406)); + + // versions that DO NOT need disabled allocation profiling with active jit + assert!(!alloc_prof_needs_disabled_for_jit(80407)); + assert!(!alloc_prof_needs_disabled_for_jit(80501)); + } } diff --git a/profiling/src/allocation/profiling_stats.rs b/profiling/src/allocation/profiling_stats.rs index c2d3fa4135a..f23eae54e2d 100644 --- a/profiling/src/allocation/profiling_stats.rs +++ b/profiling/src/allocation/profiling_stats.rs @@ -128,8 +128,8 @@ pub unsafe fn ginit() { /// /// Must be called once per PHP thread gshutdown. pub unsafe fn gshutdown() { - #[cfg(php_zend_mm_set_custom_handlers_ex)] - allocation_ge84::alloc_prof_gshutdown(); + // #[cfg(php_zend_mm_set_custom_handlers_ex)] + // allocation_ge84::alloc_prof_gshutdown(); // SAFETY: // 1. During gshutdown, there will not be any other borrows.