diff --git a/.github/workflows/prof_correctness.yml b/.github/workflows/prof_correctness.yml index de76304a0c..4e468cae26 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 911fdf05f9..55d9c0786e 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 1935519dbf..4157f4bfe2 100644 --- a/profiling/src/allocation/allocation_ge84.rs +++ b/profiling/src/allocation/allocation_ge84.rs @@ -1,9 +1,9 @@ use crate::allocation::{collect_allocation, ALLOCATION_PROFILING_STATS}; -use crate::bindings::{self as zend}; +use crate::bindings as zend; use crate::{RefCellExt, 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; @@ -12,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. @@ -53,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, @@ -139,85 +129,87 @@ macro_rules! tls_zend_mm_state_set { }; } +const NEEDS_RUN_TIME_CHECK_FOR_ENABLED_JIT: bool = + zend::PHP_VERSION_ID >= 80000 && zend::PHP_VERSION_ID < 80300 || zend::PHP_VERSION_ID >= 80400; + +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) + || (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"); + if zend::PHP_VERSION_ID >= 80400 { + 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"); + } else { + 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"); + } true } else { false } } -/// 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 + // 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), @@ -225,58 +217,11 @@ 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() { @@ -289,28 +234,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()` 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 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( @@ -328,28 +272,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 +355,38 @@ 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); + let heap = zend::zend_mm_get_heap(); + let ptr: *mut c_void = zend::_zend_mm_alloc(heap, len); 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 +/// 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); + let heap = zend::zend_mm_get_heap(); + zend::_zend_mm_free(heap, ptr); } unsafe extern "C" fn alloc_prof_realloc(prev_ptr: *mut c_void, len: size_t) -> *mut c_void { @@ -447,23 +413,17 @@ 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) + let heap = zend::zend_mm_get_heap(); + let ptr: *mut c_void = zend::_zend_mm_realloc(heap, prev_ptr, len); + ptr } unsafe extern "C" fn alloc_prof_gc() -> size_t { @@ -471,51 +431,73 @@ 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!()) + let gc = tls_zend_mm_state_get!(prev_custom_mm_gc); + if let Some(gc) = gc { + return gc(); + } + 0 } unsafe fn alloc_prof_orig_gc() -> size_t { - zend::zend_mm_gc(tls_zend_mm_state_get!(prev_heap)) + let heap = tls_zend_mm_state_get!(heap).unwrap(); + 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!()) + // 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 + let shutdown = tls_zend_mm_state_get!(prev_custom_mm_shutdown).unwrap(); + 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) + 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); } /// safe wrapper for `zend::is_zend_mm()`. /// `true` means the internal ZendMM is being used, `false` means that a custom memory manager is -/// installed +/// installed. Upstream returns a `c_bool` as of PHP 8.0. PHP 7 returns a `c_int` fn is_zend_mm() -> bool { - unsafe { zend::is_zend_mm() } + #[cfg(php7)] + unsafe { + zend::is_zend_mm() == 1 + } + #[cfg(php8)] + unsafe { + zend::is_zend_mm() + } +} + +#[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(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!(!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/allocation/mod.rs b/profiling/src/allocation/mod.rs index 3506d29ae6..65c76b3930 100644 --- a/profiling/src/allocation/mod.rs +++ b/profiling/src/allocation/mod.rs @@ -98,8 +98,8 @@ pub fn alloc_prof_ginit() { } pub fn alloc_prof_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(); } #[cfg(not(php_zend_mm_set_custom_handlers_ex))]