diff --git a/Zend/zend_alloc.c b/Zend/zend_alloc.c index d76c4da023ed2..f16bf1edfb979 100644 --- a/Zend/zend_alloc.c +++ b/Zend/zend_alloc.c @@ -202,6 +202,37 @@ typedef struct _zend_mm_huge_list zend_mm_huge_list; static bool zend_mm_use_huge_pages = false; +struct _zend_mm_observer { + void (*malloc)(size_t len, void *ptr ZEND_FILE_LINE_DC ZEND_FILE_LINE_ORIG_DC); + void (*free)(void *ptr ZEND_FILE_LINE_DC ZEND_FILE_LINE_ORIG_DC); + void (*realloc)(void *old_ptr, size_t len, void *new_ptr ZEND_FILE_LINE_DC ZEND_FILE_LINE_ORIG_DC); + void (*gc)(size_t len); + void (*shutdown)(bool full, bool silent); + zend_mm_observer *next; +}; + +#define HANDLE_NO_DEBUG_OBSERVERS(observer_function, ...) \ + if (use_custom_heap & ZEND_MM_CUSTOM_HEAP_OBSERVED) { \ + zend_mm_observer *current = heap->observers; \ + while (current != NULL) { \ + if (current->observer_function != NULL) { \ + current->observer_function(__VA_ARGS__); \ + } \ + current = current->next; \ + } \ + } + +#define HANDLE_OBSERVERS(observer_function, ...) \ + if (use_custom_heap & ZEND_MM_CUSTOM_HEAP_OBSERVED) { \ + zend_mm_observer *current = heap->observers; \ + while (current != NULL) { \ + if (current->observer_function != NULL) { \ + current->observer_function(__VA_ARGS__ ZEND_FILE_LINE_RELAY_CC ZEND_FILE_LINE_ORIG_RELAY_CC); \ + } \ + current = current->next; \ + } \ + } + /* * Memory is retrieved from OS by chunks of fixed size 2MB. * Inside chunk it's managed by pages of fixed size 4096B. @@ -233,10 +264,9 @@ static bool zend_mm_use_huge_pages = false; * (5 bits) bin number (e.g. 0 for sizes 0-2, 1 for 3-4, * 2 for 5-8, 3 for 9-16 etc) see zend_alloc_sizes.h */ - struct _zend_mm_heap { #if ZEND_MM_CUSTOM - int use_custom_heap; + int use_custom_heap; /* bitflag */ #endif #if ZEND_MM_STORAGE zend_mm_storage *storage; @@ -281,6 +311,7 @@ struct _zend_mm_heap { } debug; } custom_heap; HashTable *tracked_allocs; + zend_mm_observer *observers; #endif }; @@ -1947,6 +1978,7 @@ static zend_mm_heap *zend_mm_init(void) #endif #if ZEND_MM_CUSTOM heap->use_custom_heap = ZEND_MM_CUSTOM_HEAP_NONE; + heap->observers = NULL; #endif #if ZEND_MM_STORAGE heap->storage = NULL; @@ -1967,7 +1999,9 @@ ZEND_API size_t zend_mm_gc(zend_mm_heap *heap) size_t collected = 0; #if ZEND_MM_CUSTOM - if (heap->use_custom_heap) { + int use_custom_heap = heap->use_custom_heap; + if (use_custom_heap & ~ZEND_MM_CUSTOM_HEAP_OBSERVED) { + HANDLE_NO_DEBUG_OBSERVERS(gc ,0); return 0; } #endif @@ -2066,6 +2100,10 @@ ZEND_API size_t zend_mm_gc(zend_mm_heap *heap) } } while (chunk != heap->main_chunk); +#if ZEND_MM_CUSTOM + HANDLE_NO_DEBUG_OBSERVERS(gc, collected * ZEND_MM_PAGE_SIZE); +#endif + return collected * ZEND_MM_PAGE_SIZE; } @@ -2271,7 +2309,12 @@ void zend_mm_shutdown(zend_mm_heap *heap, bool full, bool silent) zend_mm_huge_list *list; #if ZEND_MM_CUSTOM - if (heap->use_custom_heap) { + int use_custom_heap = heap->use_custom_heap; + + HANDLE_NO_DEBUG_OBSERVERS(shutdown, full, silent) + zend_mm_observers_shutdown(heap); + + if (use_custom_heap & ~ZEND_MM_CUSTOM_HEAP_OBSERVED) { if (heap->custom_heap.std._malloc == tracked_malloc) { if (silent) { tracked_free_all(); @@ -2287,7 +2330,7 @@ void zend_mm_shutdown(zend_mm_heap *heap, bool full, bool silent) } if (full) { - if (ZEND_DEBUG && heap->use_custom_heap == ZEND_MM_CUSTOM_HEAP_DEBUG) { + if (ZEND_DEBUG && use_custom_heap & ZEND_MM_CUSTOM_HEAP_DEBUG) { heap->custom_heap.debug._free(heap ZEND_FILE_LINE_CC ZEND_FILE_LINE_EMPTY_CC); } else { heap->custom_heap.std._free(heap); @@ -2411,7 +2454,7 @@ void* ZEND_FASTCALL _zend_mm_realloc2(zend_mm_heap *heap, void *ptr, size_t size ZEND_API size_t ZEND_FASTCALL _zend_mm_block_size(zend_mm_heap *heap, void *ptr ZEND_FILE_LINE_DC ZEND_FILE_LINE_ORIG_DC) { #if ZEND_MM_CUSTOM - if (UNEXPECTED(heap->use_custom_heap)) { + if (UNEXPECTED(heap->use_custom_heap & ~ZEND_MM_CUSTOM_HEAP_OBSERVED)) { if (heap->custom_heap.std._malloc == tracked_malloc) { zend_ulong h = ((uintptr_t) ptr) >> ZEND_MM_ALIGNMENT_LOG2; zval *size_zv = zend_hash_index_find(heap->tracked_allocs, h); @@ -2445,16 +2488,16 @@ static zend_alloc_globals alloc_globals; ZEND_API bool is_zend_mm(void) { #if ZEND_MM_CUSTOM - return !AG(mm_heap)->use_custom_heap; + return !(AG(mm_heap)->use_custom_heap & ~ZEND_MM_CUSTOM_HEAP_OBSERVED); #else - return 1; + return true; #endif } ZEND_API bool is_zend_ptr(const void *ptr) { #if ZEND_MM_CUSTOM - if (AG(mm_heap)->use_custom_heap) { + if (AG(mm_heap)->use_custom_heap & ~ZEND_MM_CUSTOM_HEAP_OBSERVED) { if (AG(mm_heap)->custom_heap.std._malloc == tracked_malloc) { zend_ulong h = ((uintptr_t) ptr) >> ZEND_MM_ALIGNMENT_LOG2; zval *size_zv = zend_hash_index_find(AG(mm_heap)->tracked_allocs, h); @@ -2496,29 +2539,53 @@ ZEND_API bool is_zend_ptr(const void *ptr) static ZEND_COLD void* ZEND_FASTCALL _malloc_custom(size_t size ZEND_FILE_LINE_DC ZEND_FILE_LINE_ORIG_DC) { - if (ZEND_DEBUG && AG(mm_heap)->use_custom_heap == ZEND_MM_CUSTOM_HEAP_DEBUG) { - return AG(mm_heap)->custom_heap.debug._malloc(size ZEND_FILE_LINE_RELAY_CC ZEND_FILE_LINE_ORIG_RELAY_CC); + void *ptr; + zend_mm_heap *heap = AG(mm_heap); + int use_custom_heap = heap->use_custom_heap; + if (ZEND_DEBUG && use_custom_heap & ZEND_MM_CUSTOM_HEAP_DEBUG) { + ptr = heap->custom_heap.debug._malloc(size ZEND_FILE_LINE_RELAY_CC ZEND_FILE_LINE_ORIG_RELAY_CC); + } else if (use_custom_heap & ZEND_MM_CUSTOM_HEAP_STD) { + ptr = heap->custom_heap.std._malloc(size); } else { - return AG(mm_heap)->custom_heap.std._malloc(size); + // no custom memory manager, only observer present + ptr = zend_mm_alloc_heap(AG(mm_heap), size ZEND_FILE_LINE_RELAY_CC ZEND_FILE_LINE_ORIG_RELAY_CC); } + HANDLE_OBSERVERS(malloc, size, ptr) + return ptr; } static ZEND_COLD void ZEND_FASTCALL _efree_custom(void *ptr ZEND_FILE_LINE_DC ZEND_FILE_LINE_ORIG_DC) { - if (ZEND_DEBUG && AG(mm_heap)->use_custom_heap == ZEND_MM_CUSTOM_HEAP_DEBUG) { - AG(mm_heap)->custom_heap.debug._free(ptr ZEND_FILE_LINE_RELAY_CC ZEND_FILE_LINE_ORIG_RELAY_CC); + zend_mm_heap *heap = AG(mm_heap); + int use_custom_heap = heap->use_custom_heap; + + HANDLE_OBSERVERS(free, ptr) + + if (ZEND_DEBUG && use_custom_heap & ZEND_MM_CUSTOM_HEAP_DEBUG) { + heap->custom_heap.debug._free(ptr ZEND_FILE_LINE_RELAY_CC ZEND_FILE_LINE_ORIG_RELAY_CC); + } else if (use_custom_heap & ZEND_MM_CUSTOM_HEAP_STD) { + heap->custom_heap.std._free(ptr); } else { - AG(mm_heap)->custom_heap.std._free(ptr); + // no custom memory manager, only observer present + zend_mm_free_heap(AG(mm_heap), ptr ZEND_FILE_LINE_RELAY_CC ZEND_FILE_LINE_ORIG_RELAY_CC); } } static ZEND_COLD void* ZEND_FASTCALL _realloc_custom(void *ptr, size_t size ZEND_FILE_LINE_DC ZEND_FILE_LINE_ORIG_DC) { - if (ZEND_DEBUG && AG(mm_heap)->use_custom_heap == ZEND_MM_CUSTOM_HEAP_DEBUG) { - return AG(mm_heap)->custom_heap.debug._realloc(ptr, size ZEND_FILE_LINE_RELAY_CC ZEND_FILE_LINE_ORIG_RELAY_CC); + void *new_ptr; + zend_mm_heap *heap = AG(mm_heap); + int use_custom_heap = heap->use_custom_heap; + if (ZEND_DEBUG && use_custom_heap & ZEND_MM_CUSTOM_HEAP_DEBUG) { + new_ptr = heap->custom_heap.debug._realloc(ptr, size ZEND_FILE_LINE_RELAY_CC ZEND_FILE_LINE_ORIG_RELAY_CC); + } else if (use_custom_heap & ZEND_MM_CUSTOM_HEAP_STD) { + new_ptr = heap->custom_heap.std._realloc(ptr, size); } else { - return AG(mm_heap)->custom_heap.std._realloc(ptr, size); + // no custom memory manager, only observer present + new_ptr = zend_mm_realloc_heap(AG(mm_heap), ptr, size, 0, size ZEND_FILE_LINE_RELAY_CC ZEND_FILE_LINE_ORIG_RELAY_CC); } + HANDLE_OBSERVERS(realloc, ptr, size, new_ptr) + return new_ptr; } #endif @@ -2983,9 +3050,21 @@ ZEND_API zend_mm_heap *zend_mm_get_heap(void) ZEND_API bool zend_mm_is_custom_heap(zend_mm_heap *new_heap) { #if ZEND_MM_CUSTOM - return AG(mm_heap)->use_custom_heap; + return (AG(mm_heap)->use_custom_heap & ~ZEND_MM_CUSTOM_HEAP_OBSERVED) != 0; #else - return 0; + return false; +#endif +} + +ZEND_API bool zend_mm_is_observed(zend_mm_heap *heap) +{ +#if ZEND_MM_CUSTOM + if (!heap && !(heap = AG(mm_heap))) { + return false; + } + return (heap->use_custom_heap & ZEND_MM_CUSTOM_HEAP_OBSERVED) != 0; +#else + return false; #endif } @@ -2996,6 +3075,7 @@ ZEND_API void zend_mm_set_custom_handlers(zend_mm_heap *heap, { #if ZEND_MM_CUSTOM zend_mm_heap *_heap = (zend_mm_heap*)heap; + int observed = _heap->use_custom_heap & ZEND_MM_CUSTOM_HEAP_OBSERVED; if (!_malloc && !_free && !_realloc) { _heap->use_custom_heap = ZEND_MM_CUSTOM_HEAP_NONE; @@ -3005,6 +3085,8 @@ ZEND_API void zend_mm_set_custom_handlers(zend_mm_heap *heap, _heap->custom_heap.std._free = _free; _heap->custom_heap.std._realloc = _realloc; } + + _heap->use_custom_heap |= observed; #endif } @@ -3016,7 +3098,7 @@ ZEND_API void zend_mm_get_custom_handlers(zend_mm_heap *heap, #if ZEND_MM_CUSTOM zend_mm_heap *_heap = (zend_mm_heap*)heap; - if (heap->use_custom_heap) { + if (heap->use_custom_heap & ~ZEND_MM_CUSTOM_HEAP_OBSERVED) { *_malloc = _heap->custom_heap.std._malloc; *_free = _heap->custom_heap.std._free; *_realloc = _heap->custom_heap.std._realloc; @@ -3032,6 +3114,121 @@ ZEND_API void zend_mm_get_custom_handlers(zend_mm_heap *heap, #endif } +/* + * Use this function to register observers to the ZendMM. + * This function operates on the thread local heap and is meant to be called + * during the request lifetime. + */ +ZEND_API zend_mm_observer* zend_mm_observer_register( + zend_mm_heap* heap, + void (*malloc)(size_t len, void *ptr ZEND_FILE_LINE_DC ZEND_FILE_LINE_ORIG_DC), + void (*free)(void *ptr ZEND_FILE_LINE_DC ZEND_FILE_LINE_ORIG_DC), + void (*realloc)(void *old_ptr, size_t len, void *new_ptr ZEND_FILE_LINE_DC ZEND_FILE_LINE_ORIG_DC), + void (*gc)(size_t len), + void (*shutdown)(bool full, bool silent) +) { +#if ZEND_MM_CUSTOM + if (!heap) { + heap = AG(mm_heap); + } + ZEND_ASSERT(heap); + + zend_mm_observer *node = pemalloc(sizeof(zend_mm_observer), 1); + node->malloc = malloc; + node->free = free; + node->realloc = realloc; + node->gc = gc; + node->shutdown = shutdown; + node->next = NULL; + + // set bitflag for observers being around + heap->use_custom_heap |= ZEND_MM_CUSTOM_HEAP_OBSERVED; + + if (heap->observers == NULL) { + heap->observers = node; + return node; + } + zend_mm_observer *current = heap->observers; + while (current->next != NULL) { + current = current->next; + } + current->next = node; + + return node; +#else + return NULL; +#endif +} + +/* + * Use this function to unregister your observer at any given time. In case your + * observer was the last one to be unregistered, this will also reset the + * `heap->use_custom_heap` bitflag. + */ +ZEND_API bool zend_mm_observer_unregister(zend_mm_heap *heap, zend_mm_observer *observer) +{ +#if ZEND_MM_CUSTOM + if (!heap) { + heap = AG(mm_heap); + } + ZEND_ASSERT(heap); + + zend_mm_observer *current = heap->observers, *prev = NULL; + + // find the given observer in the list of observers + while (current != NULL && current != observer) { + prev = current; + current = current->next; + } + + // did not find observer or NULL was given + if (current == NULL) { + return false; + } + + if (prev == NULL) { + // current observer is the first in the list + heap->observers = current->next; + if (!heap->observers) { + // and current was also the last in the list, so no more observers + // left + heap->use_custom_heap &= ~ZEND_MM_CUSTOM_HEAP_OBSERVED; + } + } else { + prev->next = current->next; + } + + pefree(observer, 1); + return true; +#else + return false; +#endif +} + +/* + * This function will shutdown the observers for the current heap and free + * memory, it is called in `zend_mm_shutdown()` + */ +void zend_mm_observers_shutdown(zend_mm_heap *heap) +{ +#if ZEND_MM_CUSTOM + if (!heap) { + heap = AG(mm_heap); + } + ZEND_ASSERT(heap); + zend_mm_observer *current = heap->observers; + zend_mm_observer *next = NULL; + while (current != NULL) { + next = current->next; + pefree(current, 1); + current = next; + } + heap->observers = NULL; + heap->use_custom_heap &= ~ZEND_MM_CUSTOM_HEAP_OBSERVED; + return; +#endif +} + #if ZEND_DEBUG ZEND_API void zend_mm_set_custom_debug_handlers(zend_mm_heap *heap, void* (*_malloc)(size_t ZEND_FILE_LINE_DC ZEND_FILE_LINE_ORIG_DC), @@ -3041,7 +3238,7 @@ ZEND_API void zend_mm_set_custom_debug_handlers(zend_mm_heap *heap, #if ZEND_MM_CUSTOM zend_mm_heap *_heap = (zend_mm_heap*)heap; - _heap->use_custom_heap = ZEND_MM_CUSTOM_HEAP_DEBUG; + _heap->use_custom_heap = ZEND_MM_CUSTOM_HEAP_DEBUG | (_heap->use_custom_heap & ZEND_MM_CUSTOM_HEAP_OBSERVED); _heap->custom_heap.debug._malloc = _malloc; _heap->custom_heap.debug._free = _free; _heap->custom_heap.debug._realloc = _realloc; diff --git a/Zend/zend_alloc.h b/Zend/zend_alloc.h index eb80bfb14b018..759a80b29973a 100644 --- a/Zend/zend_alloc.h +++ b/Zend/zend_alloc.h @@ -266,9 +266,10 @@ ZEND_API zend_mm_heap *zend_mm_get_heap(void); ZEND_API size_t zend_mm_gc(zend_mm_heap *heap); -#define ZEND_MM_CUSTOM_HEAP_NONE 0 -#define ZEND_MM_CUSTOM_HEAP_STD 1 -#define ZEND_MM_CUSTOM_HEAP_DEBUG 2 +#define ZEND_MM_CUSTOM_HEAP_NONE 0 +#define ZEND_MM_CUSTOM_HEAP_STD 1 +#define ZEND_MM_CUSTOM_HEAP_DEBUG 2 +#define ZEND_MM_CUSTOM_HEAP_OBSERVED 4 ZEND_API bool zend_mm_is_custom_heap(zend_mm_heap *new_heap); ZEND_API void zend_mm_set_custom_handlers(zend_mm_heap *heap, @@ -280,6 +281,21 @@ ZEND_API void zend_mm_get_custom_handlers(zend_mm_heap *heap, void (**_free)(void*), void* (**_realloc)(void*, size_t)); +typedef struct _zend_mm_observer zend_mm_observer; + +// thread local +ZEND_API bool zend_mm_is_observed(zend_mm_heap *heap); +ZEND_API zend_mm_observer* zend_mm_observer_register( + zend_mm_heap *heap, + void (*malloc)(size_t len, void *ptr ZEND_FILE_LINE_DC ZEND_FILE_LINE_ORIG_DC), + void (*free)(void *ptr ZEND_FILE_LINE_DC ZEND_FILE_LINE_ORIG_DC), + void (*realloc)(void *old_ptr, size_t len, void *new_ptr ZEND_FILE_LINE_DC ZEND_FILE_LINE_ORIG_DC), + void (*gc)(size_t len), + void (*shutdown)(bool full, bool silent) +); +ZEND_API bool zend_mm_observer_unregister(zend_mm_heap *heap, zend_mm_observer *observer); +void zend_mm_observers_shutdown(zend_mm_heap *heap); + #if ZEND_DEBUG ZEND_API void zend_mm_set_custom_debug_handlers(zend_mm_heap *heap, void* (*_malloc)(size_t ZEND_FILE_LINE_DC ZEND_FILE_LINE_ORIG_DC), diff --git a/ext/zend_test/config.m4 b/ext/zend_test/config.m4 index 4b15db47750cb..6bef2b0577d1d 100644 --- a/ext/zend_test/config.m4 +++ b/ext/zend_test/config.m4 @@ -4,5 +4,5 @@ PHP_ARG_ENABLE([zend-test], [Enable zend_test extension])]) if test "$PHP_ZEND_TEST" != "no"; then - PHP_NEW_EXTENSION(zend_test, test.c observer.c fiber.c iterators.c object_handlers.c, $ext_shared,, -DZEND_ENABLE_STATIC_TSRMLS_CACHE=1) + PHP_NEW_EXTENSION(zend_test, test.c observer.c fiber.c iterators.c object_handlers.c zend_mm_observer.c, $ext_shared,, -DZEND_ENABLE_STATIC_TSRMLS_CACHE=1) fi diff --git a/ext/zend_test/config.w32 b/ext/zend_test/config.w32 index e066fdeb243ea..565df8023c8fc 100644 --- a/ext/zend_test/config.w32 +++ b/ext/zend_test/config.w32 @@ -3,6 +3,6 @@ ARG_ENABLE("zend-test", "enable zend_test extension", "no"); if (PHP_ZEND_TEST != "no") { - EXTENSION("zend_test", "test.c observer.c fiber.c iterators.c object_handlers.c", PHP_ZEND_TEST_SHARED, "/DZEND_ENABLE_STATIC_TSRMLS_CACHE=1"); + EXTENSION("zend_test", "test.c observer.c fiber.c iterators.c object_handlers.c zend_mm_observer.c", PHP_ZEND_TEST_SHARED, "/DZEND_ENABLE_STATIC_TSRMLS_CACHE=1"); ADD_FLAG("CFLAGS_ZEND_TEST", "/D PHP_ZEND_TEST_EXPORTS "); } diff --git a/ext/zend_test/php_test.h b/ext/zend_test/php_test.h index 89570d1155cea..14f60cbbe7605 100644 --- a/ext/zend_test/php_test.h +++ b/ext/zend_test/php_test.h @@ -18,6 +18,7 @@ #define PHP_TEST_H #include "fiber.h" +#include "Zend/zend_alloc.h" extern zend_module_entry zend_test_module_entry; #define phpext_zend_test_ptr &zend_test_module_entry @@ -62,6 +63,8 @@ ZEND_BEGIN_MODULE_GLOBALS(zend_test) zend_long quantity_value; zend_string *str_test; zend_string *not_empty_str_test; + int zend_mm_observer_enabled; + zend_mm_observer *observer; ZEND_END_MODULE_GLOBALS(zend_test) extern ZEND_DECLARE_MODULE_GLOBALS(zend_test) diff --git a/ext/zend_test/test.c b/ext/zend_test/test.c index ba91328596926..fa943b0ed8e4d 100644 --- a/ext/zend_test/test.c +++ b/ext/zend_test/test.c @@ -23,6 +23,7 @@ #include "ext/standard/info.h" #include "php_test.h" #include "observer.h" +#include "zend_mm_observer.h" #include "fiber.h" #include "iterators.h" #include "object_handlers.h" @@ -1189,6 +1190,7 @@ PHP_MINIT_FUNCTION(zend_test) } zend_test_observer_init(INIT_FUNC_ARGS_PASSTHRU); + zend_test_mm_observer_minit(INIT_FUNC_ARGS_PASSTHRU); zend_test_fiber_init(); zend_test_iterators_init(); zend_test_object_handlers_init(); @@ -1217,6 +1219,7 @@ PHP_RINIT_FUNCTION(zend_test) { zend_hash_init(&ZT_G(global_weakmap), 8, NULL, ZVAL_PTR_DTOR, 0); ZT_G(observer_nesting_depth) = 0; + zend_test_mm_observer_rinit(); return SUCCESS; } @@ -1234,6 +1237,7 @@ PHP_RSHUTDOWN_FUNCTION(zend_test) zend_mm_set_heap(ZT_G(zend_orig_heap)); } + zend_test_mm_observer_rshutdown(); return SUCCESS; } diff --git a/ext/zend_test/tests/zend_mm_observer_all_01.phpt b/ext/zend_test/tests/zend_mm_observer_all_01.phpt new file mode 100644 index 0000000000000..07f2619d06e00 --- /dev/null +++ b/ext/zend_test/tests/zend_mm_observer_all_01.phpt @@ -0,0 +1,14 @@ +--TEST-- +ZendMM Observer: Observe all +--EXTENSIONS-- +zend_test +--INI-- +zend_test.zend_mm_observer.enabled=1 +--FILE-- + +--EXPECTREGEX-- +.*ZendMM Observer enabled.* +.*done\..* +.*ZendMM Observer disabled.* diff --git a/ext/zend_test/tests/zend_mm_observer_free_01.phpt b/ext/zend_test/tests/zend_mm_observer_free_01.phpt new file mode 100644 index 0000000000000..1d0a9ec39c491 --- /dev/null +++ b/ext/zend_test/tests/zend_mm_observer_free_01.phpt @@ -0,0 +1,16 @@ +--TEST-- +ZendMM Observer: Observe free +--EXTENSIONS-- +zend_test +--INI-- +zend_test.zend_mm_observer.enabled=0 +opcache.enable=0 +--FILE-- + +--EXPECTREGEX-- +.*freed \S+ of size \d+.* diff --git a/ext/zend_test/tests/zend_mm_observer_gc_01.phpt b/ext/zend_test/tests/zend_mm_observer_gc_01.phpt new file mode 100644 index 0000000000000..7d828984afcd8 --- /dev/null +++ b/ext/zend_test/tests/zend_mm_observer_gc_01.phpt @@ -0,0 +1,15 @@ +--TEST-- +ZendMM Observer: Observe garbage collection +--EXTENSIONS-- +zend_test +--INI-- +zend_test.zend_mm_observer.enabled=0 +opcache.enable=0 +--FILE-- + +--EXPECTREGEX-- +.*garbage collection ended with \d+ bytes collected.* diff --git a/ext/zend_test/tests/zend_mm_observer_malloc_01.phpt b/ext/zend_test/tests/zend_mm_observer_malloc_01.phpt new file mode 100644 index 0000000000000..1ae00b3ab94d5 --- /dev/null +++ b/ext/zend_test/tests/zend_mm_observer_malloc_01.phpt @@ -0,0 +1,15 @@ +--TEST-- +ZendMM Observer: Observe malloc +--EXTENSIONS-- +zend_test +--INI-- +zend_test.zend_mm_observer.enabled=0 +opcache.enable=0 +--FILE-- + +--EXPECTREGEX-- +.*malloc \S+ of size \d+ \(block: \d+\).* diff --git a/ext/zend_test/tests/zend_mm_observer_realloc_01.phpt b/ext/zend_test/tests/zend_mm_observer_realloc_01.phpt new file mode 100644 index 0000000000000..a1f7e002812b5 --- /dev/null +++ b/ext/zend_test/tests/zend_mm_observer_realloc_01.phpt @@ -0,0 +1,24 @@ +--TEST-- +ZendMM Observer: Observe realloc +--EXTENSIONS-- +zend_test +--INI-- +zend_test.zend_mm_observer.enabled=0 +opcache.enable=0 +--FILE-- + +--EXPECTREGEX-- +.*realloc \S+ of size \d+ \(block: \d+, former \S+\) diff --git a/ext/zend_test/tests/zend_mm_observer_shutdown_01.phpt b/ext/zend_test/tests/zend_mm_observer_shutdown_01.phpt new file mode 100644 index 0000000000000..664a52af68849 --- /dev/null +++ b/ext/zend_test/tests/zend_mm_observer_shutdown_01.phpt @@ -0,0 +1,12 @@ +--TEST-- +ZendMM Observer: Observe shutdown +--EXTENSIONS-- +zend_test +--INI-- +zend_test.zend_mm_observer.enabled=1 +--FILE-- + +--EXPECTREGEX-- +.*shutdown in progress: full\(0\), silent\(1\).* diff --git a/ext/zend_test/zend_mm_observer.c b/ext/zend_test/zend_mm_observer.c new file mode 100644 index 0000000000000..7ea7ef7ce2b24 --- /dev/null +++ b/ext/zend_test/zend_mm_observer.c @@ -0,0 +1,131 @@ +/* + +----------------------------------------------------------------------+ + | Copyright (c) The PHP Group | + +----------------------------------------------------------------------+ + | This source file is subject to version 3.01 of the PHP license, | + | that is bundled with this package in the file LICENSE, and is | + | available through the world-wide-web at the following url: | + | https://www.php.net/license/3_01.txt | + | If you did not receive a copy of the PHP license and are unable to | + | obtain it through the world-wide-web, please send a note to | + | license@php.net so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ + | Author: Florian Engelhardt | + +----------------------------------------------------------------------+ +*/ + +#include "php.h" +#include "php_test.h" +#include "Zend/zend_alloc.h" +#include "ext/standard/info.h" +#include "ext/standard/php_var.h" +#include "zend_mm_observer.h" + +static void zend_mm_test_observer_malloc(size_t len, void *ptr ZEND_FILE_LINE_DC ZEND_FILE_LINE_ORIG_DC) +{ + size_t block_len = 0; + if (is_zend_ptr(ptr)) { + block_len = zend_mm_block_size(zend_mm_get_heap(), ptr); + } + printf("malloc %p of size %zu (block: %zu)\n", ptr, len, block_len); + fflush(stdout); +} + +static void zend_mm_test_observer_free(void *ptr ZEND_FILE_LINE_DC ZEND_FILE_LINE_ORIG_DC) +{ + size_t block_len = 0; + if (is_zend_ptr(ptr)) { + block_len = zend_mm_block_size(zend_mm_get_heap(), ptr); + } + printf("freed %p of size %zu\n", ptr, block_len); + fflush(stdout); +} + +static void zend_mm_test_observer_realloc(void *ptr, size_t len, void *newptr ZEND_FILE_LINE_DC ZEND_FILE_LINE_ORIG_DC) +{ + size_t block_len = 0; + if (is_zend_ptr(ptr)) { + block_len = zend_mm_block_size(zend_mm_get_heap(), ptr); + } + printf("realloc %p of size %zu (block: %zu, former %p)\n", newptr, len, block_len, ptr); + fflush(stdout); +} + +static void zend_mm_test_observer_gc(size_t len) +{ + printf("garbage collection ended with %zu bytes collected\n", len); + fflush(stdout); +} + +static void zend_mm_test_observer_shutdown(bool full, bool silent) +{ + printf("shutdown in progress: full(%d), silent(%d)\n", full, silent); + fflush(stdout); +} + +static PHP_INI_MH(OnUpdateZendTestMMObserverEnabled) +{ + if (new_value == NULL) { + return FAILURE; + } + + int int_value = zend_ini_parse_bool(new_value); + + if (int_value == 1) { + if (ZT_G(observer) == NULL) { + ZT_G(observer) = zend_mm_observer_register( + zend_mm_get_heap(), + zend_mm_test_observer_malloc, + zend_mm_test_observer_free, + zend_mm_test_observer_realloc, + zend_mm_test_observer_gc, + zend_mm_test_observer_shutdown + ); + } + } else { + if (ZT_G(observer) != NULL) { + zend_mm_observer_unregister(zend_mm_get_heap(), ZT_G(observer)); + ZT_G(observer) = NULL; + } + } + return OnUpdateBool(entry, new_value, mh_arg1, mh_arg2, mh_arg3, stage); +} + +PHP_INI_BEGIN() + STD_PHP_INI_BOOLEAN("zend_test.zend_mm_observer.enabled", "0", PHP_INI_ALL, OnUpdateZendTestMMObserverEnabled, zend_mm_observer_enabled, zend_zend_test_globals, zend_test_globals) +PHP_INI_END() + +void zend_test_mm_observer_minit(INIT_FUNC_ARGS) +{ + if (type != MODULE_TEMPORARY) { + REGISTER_INI_ENTRIES(); + } else { + (void)ini_entries; + } +} + +void zend_test_mm_observer_rinit(void) +{ + if (ZT_G(zend_mm_observer_enabled)) { + ZT_G(observer) = zend_mm_observer_register( + zend_mm_get_heap(), + zend_mm_test_observer_malloc, + zend_mm_test_observer_free, + zend_mm_test_observer_realloc, + zend_mm_test_observer_gc, + zend_mm_test_observer_shutdown + ); + printf("ZendMM Observer enabled\n"); + fflush(stdout); + } +} + +void zend_test_mm_observer_rshutdown(void) +{ + if (ZT_G(observer)) { + zend_mm_observer_unregister(zend_mm_get_heap(), ZT_G(observer)); + printf("ZendMM Observer disabled\n"); + fflush(stdout); + } + ZT_G(observer) = NULL; +} diff --git a/ext/zend_test/zend_mm_observer.h b/ext/zend_test/zend_mm_observer.h new file mode 100644 index 0000000000000..1276d6087121f --- /dev/null +++ b/ext/zend_test/zend_mm_observer.h @@ -0,0 +1,24 @@ +/* + +----------------------------------------------------------------------+ + | Copyright (c) The PHP Group | + +----------------------------------------------------------------------+ + | This source file is subject to version 3.01 of the PHP license, | + | that is bundled with this package in the file LICENSE, and is | + | available through the world-wide-web at the following url: | + | https://www.php.net/license/3_01.txt | + | If you did not receive a copy of the PHP license and are unable to | + | obtain it through the world-wide-web, please send a note to | + | license@php.net so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ + | Author: Florian Engelhardt | + +----------------------------------------------------------------------+ +*/ + +#ifndef ZEND_TEST_MM_OBSERVER_H +#define ZEND_TEST_MM_OBSERVER_H + +void zend_test_mm_observer_minit(INIT_FUNC_ARGS); +void zend_test_mm_observer_rinit(void); +void zend_test_mm_observer_rshutdown(void); + +#endif