diff --git a/Zend/zend_alloc.c b/Zend/zend_alloc.c index 942a8b8e1309..8f05bca25405 100644 --- a/Zend/zend_alloc.c +++ b/Zend/zend_alloc.c @@ -230,6 +230,26 @@ 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_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. @@ -262,9 +282,10 @@ static bool zend_mm_use_huge_pages = false; * 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; @@ -315,6 +336,7 @@ struct _zend_mm_heap { bool check_freelists_on_shutdown; } debug; }; + zend_mm_observer *observers; #endif #if ZEND_DEBUG pid_t pid; @@ -2085,6 +2107,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; @@ -2108,12 +2131,21 @@ ZEND_API size_t zend_mm_gc(zend_mm_heap *heap) size_t collected = 0; #if ZEND_MM_CUSTOM - if (heap->use_custom_heap) { + if (heap->use_custom_heap & ~ZEND_MM_CUSTOM_HEAP_OBSERVED) { size_t (*gc)(void) = heap->custom_heap._gc; if (gc) { - return gc(); + collected = gc(); } - return 0; + if (heap->use_custom_heap & ZEND_MM_CUSTOM_HEAP_OBSERVED) { + zend_mm_observer *current = heap->observers; + while (current != NULL) { + if (current->gc != NULL) { + current->gc(collected); + } + current = current->next; + } + } + return collected; } #endif @@ -2219,7 +2251,20 @@ ZEND_API size_t zend_mm_gc(zend_mm_heap *heap) } } while (chunk != heap->main_chunk); - return collected * ZEND_MM_PAGE_SIZE; + size_t collected_bytes = collected * ZEND_MM_PAGE_SIZE; +#if ZEND_MM_CUSTOM + if (heap->use_custom_heap & ZEND_MM_CUSTOM_HEAP_OBSERVED) { + zend_mm_observer *current = heap->observers; + while (current != NULL) { + if (current->gc != NULL) { + current->gc(collected_bytes); + } + current = current->next; + } + } +#endif + + return collected_bytes; } #if ZEND_DEBUG @@ -2435,7 +2480,21 @@ ZEND_API 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) { + if (heap->use_custom_heap & ZEND_MM_CUSTOM_HEAP_OBSERVED) { + zend_mm_observer *current = heap->observers; + while (current != NULL) { + zend_mm_observer *next = current->next; + if (current->shutdown != NULL) { + current->shutdown(full, silent); + } + pefree(current, 1); + current = next; + } + heap->observers = NULL; + heap->use_custom_heap &= ~ZEND_MM_CUSTOM_HEAP_OBSERVED; + } + + if (heap->use_custom_heap & ~ZEND_MM_CUSTOM_HEAP_OBSERVED) { if (heap->custom_heap._malloc == tracked_malloc) { if (silent) { tracked_free_all(heap); @@ -2588,7 +2647,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._malloc == tracked_malloc) { zend_ulong h = ((uintptr_t) ptr) >> ZEND_MM_ALIGNMENT_LOG2; zval *size_zv = zend_hash_index_find(heap->tracked_allocs, h); @@ -2623,7 +2682,7 @@ 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 true; #endif @@ -2632,7 +2691,7 @@ ZEND_API bool is_zend_mm(void) 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._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); @@ -2664,33 +2723,103 @@ ZEND_API bool is_zend_ptr(const void *ptr) } block = block->next; } - return 0; } +#if ZEND_MM_CUSTOM +static ZEND_COLD void* ZEND_FASTCALL zend_mm_alloc_custom(zend_mm_heap *heap, int use_custom_heap, size_t size ZEND_FILE_LINE_DC ZEND_FILE_LINE_ORIG_DC) +{ + void *ptr; + + ZEND_ASSERT(use_custom_heap & ~ZEND_MM_CUSTOM_HEAP_OBSERVED); + ptr = heap->custom_heap._malloc(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 zend_mm_free_custom(zend_mm_heap *heap, int use_custom_heap, void *ptr ZEND_FILE_LINE_DC ZEND_FILE_LINE_ORIG_DC) +{ + ZEND_ASSERT(use_custom_heap & ~ZEND_MM_CUSTOM_HEAP_OBSERVED); + HANDLE_OBSERVERS(free, ptr) + heap->custom_heap._free(ptr ZEND_FILE_LINE_RELAY_CC ZEND_FILE_LINE_ORIG_RELAY_CC); +} +#endif + #if !ZEND_DEBUG && defined(HAVE_BUILTIN_CONSTANT_P) #undef _emalloc #if ZEND_MM_CUSTOM -# define ZEND_MM_CUSTOM_ALLOCATOR(size) do { \ - if (UNEXPECTED(AG(mm_heap)->use_custom_heap)) { \ - return AG(mm_heap)->custom_heap._malloc(size ZEND_FILE_LINE_RELAY_CC ZEND_FILE_LINE_ORIG_RELAY_CC); \ +# define ZEND_MM_OBSERVED_BIN_ALLOCATOR(_size, _num, _min_size) do { \ + zend_mm_heap *heap = AG(mm_heap); \ + int use_custom_heap = heap->use_custom_heap; \ + if (UNEXPECTED(use_custom_heap)) { \ + void *ptr; \ + if (use_custom_heap & ~ZEND_MM_CUSTOM_HEAP_OBSERVED) { \ + return zend_mm_alloc_custom(heap, use_custom_heap, _size ZEND_FILE_LINE_RELAY_CC ZEND_FILE_LINE_ORIG_RELAY_CC); \ + } \ + if (_size < _min_size) { \ + ptr = zend_mm_alloc_small(heap, ZEND_MM_SMALL_SIZE_TO_BIN(_min_size) ZEND_FILE_LINE_RELAY_CC ZEND_FILE_LINE_ORIG_RELAY_CC); \ + } else { \ + ptr = zend_mm_alloc_small(heap, _num ZEND_FILE_LINE_RELAY_CC ZEND_FILE_LINE_ORIG_RELAY_CC); \ + } \ + HANDLE_OBSERVERS(malloc, _size, ptr) \ + return ptr; \ + } \ + } while (0) +# define ZEND_MM_OBSERVED_ALLOCATOR(size, allocation) do { \ + zend_mm_heap *heap = AG(mm_heap); \ + int use_custom_heap = heap->use_custom_heap; \ + if (UNEXPECTED(use_custom_heap)) { \ + void *ptr; \ + if (use_custom_heap & ~ZEND_MM_CUSTOM_HEAP_OBSERVED) { \ + return zend_mm_alloc_custom(heap, use_custom_heap, size ZEND_FILE_LINE_RELAY_CC ZEND_FILE_LINE_ORIG_RELAY_CC); \ + } \ + ptr = (allocation); \ + HANDLE_OBSERVERS(malloc, size, ptr) \ + return ptr; \ + } \ + } while (0) +# define ZEND_MM_OBSERVED_DEALLOCATOR(ptr, deallocation) do { \ + zend_mm_heap *heap = AG(mm_heap); \ + int use_custom_heap = heap->use_custom_heap; \ + if (UNEXPECTED(use_custom_heap)) { \ + if (use_custom_heap & ~ZEND_MM_CUSTOM_HEAP_OBSERVED) { \ + zend_mm_free_custom(heap, use_custom_heap, ptr ZEND_FILE_LINE_RELAY_CC ZEND_FILE_LINE_ORIG_RELAY_CC); \ + return; \ + } \ + HANDLE_OBSERVERS(free, ptr) \ + deallocation; \ + return; \ } \ } while (0) -# define ZEND_MM_CUSTOM_DEALLOCATOR(ptr) do { \ - if (UNEXPECTED(AG(mm_heap)->use_custom_heap)) { \ - AG(mm_heap)->custom_heap._free(ptr ZEND_FILE_LINE_RELAY_CC ZEND_FILE_LINE_ORIG_RELAY_CC); \ +# define ZEND_MM_OBSERVED_BIN_DEALLOCATOR(ptr, _size, _min_size, deallocation) do { \ + zend_mm_heap *heap = AG(mm_heap); \ + int use_custom_heap = heap->use_custom_heap; \ + if (UNEXPECTED(use_custom_heap)) { \ + if (use_custom_heap & ~ZEND_MM_CUSTOM_HEAP_OBSERVED) { \ + zend_mm_free_custom(heap, use_custom_heap, ptr ZEND_FILE_LINE_RELAY_CC ZEND_FILE_LINE_ORIG_RELAY_CC); \ + return; \ + } \ + if (_size < _min_size) { \ + _efree_ ## _min_size(ptr); \ + return; \ + } \ + HANDLE_OBSERVERS(free, ptr) \ + deallocation; \ return; \ } \ } while (0) #else -# define ZEND_MM_CUSTOM_ALLOCATOR(size) -# define ZEND_MM_CUSTOM_DEALLOCATOR(ptr) +# define ZEND_MM_OBSERVED_BIN_ALLOCATOR(_size, _num, _min_size) +# define ZEND_MM_OBSERVED_ALLOCATOR(size, allocation) +# define ZEND_MM_OBSERVED_DEALLOCATOR(ptr, deallocation) +# define ZEND_MM_OBSERVED_BIN_DEALLOCATOR(ptr, _size, _min_size, deallocation) #endif # define _ZEND_BIN_ALLOCATOR(_num, _size, _elements, _pages, _min_size, y) \ ZEND_API void* ZEND_FASTCALL _emalloc_ ## _size(void) { \ - ZEND_MM_CUSTOM_ALLOCATOR(_size); \ + ZEND_MM_OBSERVED_BIN_ALLOCATOR(_size, _num, _min_size); \ if (_size < _min_size) { \ return _emalloc_ ## _min_size(); \ } \ @@ -2701,20 +2830,28 @@ ZEND_MM_BINS_INFO(_ZEND_BIN_ALLOCATOR, ZEND_MM_MIN_USEABLE_BIN_SIZE, y) ZEND_API void* ZEND_FASTCALL _emalloc_large(size_t size ZEND_FILE_LINE_DC ZEND_FILE_LINE_ORIG_DC) { - ZEND_MM_CUSTOM_ALLOCATOR(size); + ZEND_MM_OBSERVED_ALLOCATOR(size, zend_mm_alloc_large_ex(heap, size ZEND_FILE_LINE_RELAY_CC ZEND_FILE_LINE_ORIG_RELAY_CC)); return zend_mm_alloc_large_ex(AG(mm_heap), size ZEND_FILE_LINE_RELAY_CC ZEND_FILE_LINE_ORIG_RELAY_CC); } ZEND_API void* ZEND_FASTCALL _emalloc_huge(size_t size) { - ZEND_MM_CUSTOM_ALLOCATOR(size); + ZEND_MM_OBSERVED_ALLOCATOR(size, zend_mm_alloc_huge(heap, size)); return zend_mm_alloc_huge(AG(mm_heap), size); } #if ZEND_DEBUG # define _ZEND_BIN_FREE(_num, _size, _elements, _pages, _min_size, y) \ ZEND_API void ZEND_FASTCALL _efree_ ## _size(void *ptr) { \ - ZEND_MM_CUSTOM_DEALLOCATOR(ptr); \ + ZEND_MM_OBSERVED_BIN_DEALLOCATOR(ptr, _size, _min_size, { \ + size_t page_offset = ZEND_MM_ALIGNED_OFFSET(ptr, ZEND_MM_CHUNK_SIZE); \ + zend_mm_chunk *chunk = (zend_mm_chunk*)ZEND_MM_ALIGNED_BASE(ptr, ZEND_MM_CHUNK_SIZE); \ + int page_num = page_offset / ZEND_MM_PAGE_SIZE; \ + ZEND_MM_CHECK(chunk->heap == heap, "zend_mm_heap corrupted"); \ + ZEND_ASSERT(chunk->map[page_num] & ZEND_MM_IS_SRUN); \ + ZEND_ASSERT(ZEND_MM_SRUN_BIN_NUM(chunk->map[page_num]) == _num); \ + zend_mm_free_small(heap, ptr, _num); \ + }); \ if (_size < _min_size) { \ _efree_ ## _min_size(ptr); \ return; \ @@ -2732,7 +2869,11 @@ ZEND_API void* ZEND_FASTCALL _emalloc_huge(size_t size) #else # define _ZEND_BIN_FREE(_num, _size, _elements, _pages, _min_size, y) \ ZEND_API void ZEND_FASTCALL _efree_ ## _size(void *ptr) { \ - ZEND_MM_CUSTOM_DEALLOCATOR(ptr); \ + ZEND_MM_OBSERVED_BIN_DEALLOCATOR(ptr, _size, _min_size, { \ + zend_mm_chunk *chunk = (zend_mm_chunk*)ZEND_MM_ALIGNED_BASE(ptr, ZEND_MM_CHUNK_SIZE); \ + ZEND_MM_CHECK(chunk->heap == heap, "zend_mm_heap corrupted"); \ + zend_mm_free_small(heap, ptr, _num); \ + }); \ if (_size < _min_size) { \ _efree_ ## _min_size(ptr); \ return; \ @@ -2749,7 +2890,17 @@ ZEND_MM_BINS_INFO(_ZEND_BIN_FREE, ZEND_MM_MIN_USEABLE_BIN_SIZE, y) ZEND_API void ZEND_FASTCALL _efree_large(void *ptr, size_t size) { - ZEND_MM_CUSTOM_DEALLOCATOR(ptr); + ZEND_MM_OBSERVED_DEALLOCATOR(ptr, { + size_t page_offset = ZEND_MM_ALIGNED_OFFSET(ptr, ZEND_MM_CHUNK_SIZE); + zend_mm_chunk *chunk = (zend_mm_chunk*)ZEND_MM_ALIGNED_BASE(ptr, ZEND_MM_CHUNK_SIZE); + int page_num = page_offset / ZEND_MM_PAGE_SIZE; + uint32_t pages_count = ZEND_MM_ALIGNED_SIZE_EX(size, ZEND_MM_PAGE_SIZE) / ZEND_MM_PAGE_SIZE; + + ZEND_MM_CHECK(chunk->heap == heap && ZEND_MM_ALIGNED_OFFSET(page_offset, ZEND_MM_PAGE_SIZE) == 0, "zend_mm_heap corrupted"); + ZEND_ASSERT(chunk->map[page_num] & ZEND_MM_IS_LRUN); + ZEND_ASSERT(ZEND_MM_LRUN_PAGES(chunk->map[page_num]) == pages_count); + zend_mm_free_large(heap, chunk, page_num, pages_count); + }); { size_t page_offset = ZEND_MM_ALIGNED_OFFSET(ptr, ZEND_MM_CHUNK_SIZE); zend_mm_chunk *chunk = (zend_mm_chunk*)ZEND_MM_ALIGNED_BASE(ptr, ZEND_MM_CHUNK_SIZE); @@ -2766,7 +2917,7 @@ ZEND_API void ZEND_FASTCALL _efree_large(void *ptr, size_t size) ZEND_API void ZEND_FASTCALL _efree_huge(void *ptr, size_t size) { - ZEND_MM_CUSTOM_DEALLOCATOR(ptr); + ZEND_MM_OBSERVED_DEALLOCATOR(ptr, zend_mm_free_huge(heap, ptr)); zend_mm_free_huge(AG(mm_heap), ptr); } #endif @@ -2774,8 +2925,18 @@ ZEND_API void ZEND_FASTCALL _efree_huge(void *ptr, size_t size) ZEND_API void* ZEND_FASTCALL _emalloc(size_t size ZEND_FILE_LINE_DC ZEND_FILE_LINE_ORIG_DC) { #if ZEND_MM_CUSTOM - if (UNEXPECTED(AG(mm_heap)->use_custom_heap)) { - return AG(mm_heap)->custom_heap._malloc(size 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; + + if (UNEXPECTED(use_custom_heap)) { + void *ptr; + + if (use_custom_heap & ~ZEND_MM_CUSTOM_HEAP_OBSERVED) { + return zend_mm_alloc_custom(heap, use_custom_heap, size ZEND_FILE_LINE_RELAY_CC ZEND_FILE_LINE_ORIG_RELAY_CC); + } + ptr = zend_mm_alloc_heap(heap, size ZEND_FILE_LINE_RELAY_CC ZEND_FILE_LINE_ORIG_RELAY_CC); + HANDLE_OBSERVERS(malloc, size, ptr) + return ptr; } #endif return zend_mm_alloc_heap(AG(mm_heap), size ZEND_FILE_LINE_RELAY_CC ZEND_FILE_LINE_ORIG_RELAY_CC); @@ -2784,8 +2945,16 @@ ZEND_API void* ZEND_FASTCALL _emalloc(size_t size ZEND_FILE_LINE_DC ZEND_FILE_LI ZEND_API void ZEND_FASTCALL _efree(void *ptr ZEND_FILE_LINE_DC ZEND_FILE_LINE_ORIG_DC) { #if ZEND_MM_CUSTOM - if (UNEXPECTED(AG(mm_heap)->use_custom_heap)) { - AG(mm_heap)->custom_heap._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; + + if (UNEXPECTED(use_custom_heap)) { + if (use_custom_heap & ~ZEND_MM_CUSTOM_HEAP_OBSERVED) { + zend_mm_free_custom(heap, use_custom_heap, ptr ZEND_FILE_LINE_RELAY_CC ZEND_FILE_LINE_ORIG_RELAY_CC); + return; + } + HANDLE_OBSERVERS(free, ptr) + zend_mm_free_heap(heap, ptr ZEND_FILE_LINE_RELAY_CC ZEND_FILE_LINE_ORIG_RELAY_CC); return; } #endif @@ -2795,8 +2964,19 @@ ZEND_API void ZEND_FASTCALL _efree(void *ptr ZEND_FILE_LINE_DC ZEND_FILE_LINE_OR ZEND_API void* ZEND_FASTCALL _erealloc(void *ptr, size_t size ZEND_FILE_LINE_DC ZEND_FILE_LINE_ORIG_DC) { #if ZEND_MM_CUSTOM - if (UNEXPECTED(AG(mm_heap)->use_custom_heap)) { - return AG(mm_heap)->custom_heap._realloc(ptr, size 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; + + if (UNEXPECTED(use_custom_heap)) { + void *new_ptr; + + if (use_custom_heap & ~ZEND_MM_CUSTOM_HEAP_OBSERVED) { + new_ptr = heap->custom_heap._realloc(ptr, size ZEND_FILE_LINE_RELAY_CC ZEND_FILE_LINE_ORIG_RELAY_CC); + } else { + new_ptr = zend_mm_realloc_heap(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 return zend_mm_realloc_heap(AG(mm_heap), ptr, size, 0, size ZEND_FILE_LINE_RELAY_CC ZEND_FILE_LINE_ORIG_RELAY_CC); @@ -2805,8 +2985,19 @@ ZEND_API void* ZEND_FASTCALL _erealloc(void *ptr, size_t size ZEND_FILE_LINE_DC ZEND_API void* ZEND_FASTCALL _erealloc2(void *ptr, size_t size, size_t copy_size ZEND_FILE_LINE_DC ZEND_FILE_LINE_ORIG_DC) { #if ZEND_MM_CUSTOM - if (UNEXPECTED(AG(mm_heap)->use_custom_heap)) { - return AG(mm_heap)->custom_heap._realloc(ptr, size 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; + + if (UNEXPECTED(use_custom_heap)) { + void *new_ptr; + + if (use_custom_heap & ~ZEND_MM_CUSTOM_HEAP_OBSERVED) { + new_ptr = heap->custom_heap._realloc(ptr, size ZEND_FILE_LINE_RELAY_CC ZEND_FILE_LINE_ORIG_RELAY_CC); + } else { + new_ptr = zend_mm_realloc_heap(heap, ptr, size, 1, copy_size ZEND_FILE_LINE_RELAY_CC ZEND_FILE_LINE_ORIG_RELAY_CC); + } + HANDLE_OBSERVERS(realloc, ptr, size, new_ptr) + return new_ptr; } #endif return zend_mm_realloc_heap(AG(mm_heap), ptr, size, 1, copy_size ZEND_FILE_LINE_RELAY_CC ZEND_FILE_LINE_ORIG_RELAY_CC); @@ -3358,9 +3549,9 @@ 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 } @@ -3383,6 +3574,7 @@ ZEND_API void zend_mm_set_custom_handlers_ex(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; @@ -3394,6 +3586,8 @@ ZEND_API void zend_mm_set_custom_handlers_ex(zend_mm_heap *heap, _heap->custom_heap._gc = _gc; _heap->custom_heap._shutdown = _shutdown; } + + _heap->use_custom_heap |= observed; #endif } @@ -3417,7 +3611,7 @@ ZEND_API void zend_mm_get_custom_handlers_ex(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._malloc; *_free = _heap->custom_heap._free; *_realloc = _heap->custom_heap._realloc; @@ -3447,6 +3641,90 @@ ZEND_API void zend_mm_get_custom_handlers_ex(zend_mm_heap *heap, #endif } +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 == NULL) { + heap = AG(mm_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; + + if (heap->observers == NULL) { + heap->observers = node; + } else { + zend_mm_observer *current = heap->observers; + while (current->next != NULL) { + current = current->next; + } + current->next = node; + } + + heap->use_custom_heap |= ZEND_MM_CUSTOM_HEAP_OBSERVED; + return node; +#else + return NULL; +#endif +} + +ZEND_API bool zend_mm_is_observed(zend_mm_heap *heap) +{ +#if ZEND_MM_CUSTOM + if (heap == NULL) { + heap = AG(mm_heap); + } + return (heap->use_custom_heap & ZEND_MM_CUSTOM_HEAP_OBSERVED) != 0; +#else + return false; +#endif +} + +ZEND_API bool zend_mm_observer_unregister(zend_mm_heap *heap, zend_mm_observer *observer) +{ +#if ZEND_MM_CUSTOM + if (heap == NULL) { + heap = AG(mm_heap); + } + if (heap->observers == NULL) { + return false; + } + + zend_mm_observer *prev = NULL; + zend_mm_observer *current = heap->observers; + + while (current != NULL) { + if (current == observer) { + if (prev == NULL) { + heap->observers = current->next; + } else { + prev->next = current->next; + } + pefree(current, 1); + + if (heap->observers == NULL) { + heap->use_custom_heap &= ~ZEND_MM_CUSTOM_HEAP_OBSERVED; + } + return true; + } + prev = current; + current = current->next; + } +#endif + return false; +} + ZEND_API zend_mm_storage *zend_mm_get_storage(zend_mm_heap *heap) { #if ZEND_MM_STORAGE @@ -3511,6 +3789,7 @@ ZEND_API zend_mm_heap *zend_mm_startup_ex(const zend_mm_handlers *handlers, void #endif #if ZEND_MM_CUSTOM heap->use_custom_heap = 0; + heap->observers = NULL; #endif heap->storage = &tmp_storage; heap->huge_list = NULL; diff --git a/Zend/zend_alloc.h b/Zend/zend_alloc.h index ff51c4fe8652..62c020729272 100644 --- a/Zend/zend_alloc.h +++ b/Zend/zend_alloc.h @@ -268,9 +268,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, @@ -294,6 +295,19 @@ ZEND_API void zend_mm_get_custom_handlers_ex(zend_mm_heap *heap, size_t (**_gc)(void), void (**_shutdown)(bool, bool)); +typedef struct _zend_mm_observer zend_mm_observer; + +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); + typedef struct _zend_mm_storage zend_mm_storage; typedef void* (*zend_mm_chunk_alloc_t)(zend_mm_storage *storage, size_t size, size_t alignment); diff --git a/ext/zend_test/config.m4 b/ext/zend_test/config.m4 index f49221b9fb64..c88fa87877c7 100644 --- a/ext/zend_test/config.m4 +++ b/ext/zend_test/config.m4 @@ -11,6 +11,7 @@ if test "$PHP_ZEND_TEST" != "no"; then observer.c test.c zend_mm_custom_handlers.c + zend_mm_observer.c ]), [$ext_shared],, [-DZEND_ENABLE_STATIC_TSRMLS_CACHE=1]) diff --git a/ext/zend_test/config.w32 b/ext/zend_test/config.w32 index 1e9ae1545dcb..5835c093a5fc 100644 --- a/ext/zend_test/config.w32 +++ b/ext/zend_test/config.w32 @@ -3,5 +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 zend_mm_custom_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_custom_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 e3cf4b4284ba..81cd66ee2b24 100644 --- a/ext/zend_test/php_test.h +++ b/ext/zend_test/php_test.h @@ -14,6 +14,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 @@ -71,6 +72,8 @@ ZEND_BEGIN_MODULE_GLOBALS(zend_test) // this is our heap that we install our custom handlers on and inject into // ZendMM zend_mm_heap* observed_heap; + 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 96f8db83c116..a74a5be32650 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" @@ -1602,6 +1603,7 @@ PHP_MINIT_FUNCTION(zend_test) zend_test_observer_init(INIT_FUNC_ARGS_PASSTHRU); zend_test_mm_custom_handlers_minit(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(); @@ -1632,6 +1634,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_custom_handlers_rinit(); + zend_test_mm_observer_rinit(); return SUCCESS; } @@ -1647,6 +1650,7 @@ PHP_RSHUTDOWN_FUNCTION(zend_test) } zend_test_mm_custom_handlers_rshutdown(); + zend_test_mm_observer_rshutdown(); return SUCCESS; } 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 000000000000..1d0a9ec39c49 --- /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 000000000000..7d828984afcd --- /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_lifecycle_01.phpt b/ext/zend_test/tests/zend_mm_observer_lifecycle_01.phpt new file mode 100644 index 000000000000..9c1a6f2b6a66 --- /dev/null +++ b/ext/zend_test/tests/zend_mm_observer_lifecycle_01.phpt @@ -0,0 +1,13 @@ +--TEST-- +ZendMM Observer: Observe lifecycle (RINIT -> RSHUTDOWN) +--EXTENSIONS-- +zend_test +--INI-- +zend_test.zend_mm_observer.enabled=1 +opcache.enable=0 +--FILE-- + +--EXPECTREGEX-- +.*ZendMM Observer enabled.* 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 000000000000..1ae00b3ab94d --- /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 000000000000..46ce15a8bc3d --- /dev/null +++ b/ext/zend_test/tests/zend_mm_observer_realloc_01.phpt @@ -0,0 +1,26 @@ +--TEST-- +ZendMM Observer: Observe realloc +--EXTENSIONS-- +zend_test +--INI-- +zend_test.zend_mm_observer.enabled=0 +opcache.enable=0 +--FILE-- + +--EXPECTREGEX-- +.*ZendMM Observer enabled.* +.*realloc \S+ of size \d+ \(block: \d+, former \S+\).* +.*ZendMM Observer disabled.* 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 000000000000..50a0f8020243 --- /dev/null +++ b/ext/zend_test/tests/zend_mm_observer_shutdown_01.phpt @@ -0,0 +1,14 @@ +--TEST-- +ZendMM Observer: Observe shutdown +--EXTENSIONS-- +zend_test +--ENV-- +ZEND_TEST_MM_OBSERVER_SHUTDOWN_TEST=1 +--INI-- +zend_test.zend_mm_observer.enabled=1 +--FILE-- + +--EXPECTREGEX-- +.*shutdown in progress: full\(\d\), silent\(\d\).* diff --git a/ext/zend_test/zend_mm_observer.c b/ext/zend_test/zend_mm_observer.c new file mode 100644 index 000000000000..774f6d769b3d --- /dev/null +++ b/ext/zend_test/zend_mm_observer.c @@ -0,0 +1,143 @@ +/* + +----------------------------------------------------------------------+ + | 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); + + /* Only toggle observer during runtime (ini_set during active request). + * RINIT/RSHUTDOWN handle initialization and cleanup. */ + if (stage == PHP_INI_STAGE_RUNTIME) { + 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 + ); + printf("ZendMM Observer enabled\n"); + fflush(stdout); + } + } else { + if (ZT_G(observer) != NULL) { + zend_mm_observer_unregister(zend_mm_get_heap(), ZT_G(observer)); + ZT_G(observer) = NULL; + printf("ZendMM Observer disabled\n"); + fflush(stdout); + } + } + } + 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) +{ + char *env = getenv("ZEND_TEST_MM_OBSERVER_SHUTDOWN_TEST"); + if (env != NULL) { + return; + } + 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 000000000000..1276d6087121 --- /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