Add gc and shutdown callbacks to ZendMM custom handlers (#13432)

This commit is contained in:
Florian Engelhardt 2024-06-19 19:43:57 +02:00 committed by GitHub
parent a1ea464069
commit f4557b48a6
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 340 additions and 16 deletions

View File

@ -334,6 +334,8 @@ struct _zend_mm_heap {
void *(*_malloc)(size_t ZEND_FILE_LINE_DC ZEND_FILE_LINE_ORIG_DC);
void (*_free)(void* ZEND_FILE_LINE_DC ZEND_FILE_LINE_ORIG_DC);
void *(*_realloc)(void*, size_t ZEND_FILE_LINE_DC ZEND_FILE_LINE_ORIG_DC);
size_t (*_gc)(void);
void (*_shutdown)(bool full, bool silent);
} custom_heap;
HashTable *tracked_allocs;
#endif
@ -2119,6 +2121,10 @@ ZEND_API size_t zend_mm_gc(zend_mm_heap *heap)
#if ZEND_MM_CUSTOM
if (heap->use_custom_heap) {
size_t (*gc)(void) = heap->custom_heap._gc;
if (gc) {
return gc();
}
return 0;
}
#endif
@ -2421,10 +2427,10 @@ static void zend_mm_check_leaks(zend_mm_heap *heap)
#if ZEND_MM_CUSTOM
static void *tracked_malloc(size_t size ZEND_FILE_LINE_DC ZEND_FILE_LINE_ORIG_DC);
static void tracked_free_all(void);
static void tracked_free_all(zend_mm_heap *heap);
#endif
void zend_mm_shutdown(zend_mm_heap *heap, bool full, bool silent)
ZEND_API void zend_mm_shutdown(zend_mm_heap *heap, bool full, bool silent)
{
zend_mm_chunk *p;
zend_mm_huge_list *list;
@ -2433,7 +2439,7 @@ void zend_mm_shutdown(zend_mm_heap *heap, bool full, bool silent)
if (heap->use_custom_heap) {
if (heap->custom_heap._malloc == tracked_malloc) {
if (silent) {
tracked_free_all();
tracked_free_all(heap);
}
zend_hash_clean(heap->tracked_allocs);
if (full) {
@ -2445,9 +2451,16 @@ void zend_mm_shutdown(zend_mm_heap *heap, bool full, bool silent)
heap->size = 0;
}
void (*shutdown)(bool, bool) = heap->custom_heap._shutdown;
if (full) {
heap->custom_heap._free(heap ZEND_FILE_LINE_CC ZEND_FILE_LINE_EMPTY_CC);
}
if (shutdown) {
shutdown(full, silent);
}
return;
}
#endif
@ -3039,8 +3052,8 @@ static void *tracked_realloc(void *ptr, size_t new_size ZEND_FILE_LINE_DC ZEND_F
return ptr;
}
static void tracked_free_all(void) {
HashTable *tracked_allocs = AG(mm_heap)->tracked_allocs;
static void tracked_free_all(zend_mm_heap *heap) {
HashTable *tracked_allocs = heap->tracked_allocs;
zend_ulong h;
ZEND_HASH_FOREACH_NUM_KEY(tracked_allocs, h) {
void *ptr = (void *) (uintptr_t) (h << ZEND_MM_ALIGNMENT_LOG2);
@ -3138,6 +3151,18 @@ ZEND_API void zend_mm_set_custom_handlers(zend_mm_heap *heap,
void (*_free)(void* ZEND_FILE_LINE_DC ZEND_FILE_LINE_ORIG_DC),
void* (*_realloc)(void*, size_t ZEND_FILE_LINE_DC ZEND_FILE_LINE_ORIG_DC))
{
#if ZEND_MM_CUSTOM
zend_mm_set_custom_handlers_ex(heap, _malloc, _free, _realloc, NULL, NULL);
#endif
}
ZEND_API void zend_mm_set_custom_handlers_ex(zend_mm_heap *heap,
void* (*_malloc)(size_t ZEND_FILE_LINE_DC ZEND_FILE_LINE_ORIG_DC),
void (*_free)(void* ZEND_FILE_LINE_DC ZEND_FILE_LINE_ORIG_DC),
void* (*_realloc)(void*, size_t ZEND_FILE_LINE_DC ZEND_FILE_LINE_ORIG_DC),
size_t (*_gc)(void),
void (*_shutdown)(bool, bool))
{
#if ZEND_MM_CUSTOM
zend_mm_heap *_heap = (zend_mm_heap*)heap;
@ -3148,6 +3173,8 @@ ZEND_API void zend_mm_set_custom_handlers(zend_mm_heap *heap,
_heap->custom_heap._malloc = _malloc;
_heap->custom_heap._free = _free;
_heap->custom_heap._realloc = _realloc;
_heap->custom_heap._gc = _gc;
_heap->custom_heap._shutdown = _shutdown;
}
#endif
}
@ -3157,6 +3184,18 @@ ZEND_API void zend_mm_get_custom_handlers(zend_mm_heap *heap,
void (**_free)(void* ZEND_FILE_LINE_DC ZEND_FILE_LINE_ORIG_DC),
void* (**_realloc)(void*, size_t ZEND_FILE_LINE_DC ZEND_FILE_LINE_ORIG_DC))
{
#if ZEND_MM_CUSTOM
zend_mm_get_custom_handlers_ex(heap, _malloc, _free, _realloc, NULL, NULL);
#endif
}
ZEND_API void zend_mm_get_custom_handlers_ex(zend_mm_heap *heap,
void* (**_malloc)(size_t ZEND_FILE_LINE_DC ZEND_FILE_LINE_ORIG_DC),
void (**_free)(void* ZEND_FILE_LINE_DC ZEND_FILE_LINE_ORIG_DC),
void* (**_realloc)(void*, size_t ZEND_FILE_LINE_DC ZEND_FILE_LINE_ORIG_DC),
size_t (**_gc)(void),
void (**_shutdown)(bool, bool))
{
#if ZEND_MM_CUSTOM
zend_mm_heap *_heap = (zend_mm_heap*)heap;
@ -3164,15 +3203,29 @@ ZEND_API void zend_mm_get_custom_handlers(zend_mm_heap *heap,
*_malloc = _heap->custom_heap._malloc;
*_free = _heap->custom_heap._free;
*_realloc = _heap->custom_heap._realloc;
if (_gc != NULL) {
*_gc = _heap->custom_heap._gc;
}
if (_shutdown != NULL) {
*_shutdown = _heap->custom_heap._shutdown;
}
} else {
*_malloc = NULL;
*_free = NULL;
*_realloc = NULL;
if (_gc != NULL) {
*_gc = NULL;
}
if (_shutdown != NULL) {
*_shutdown = NULL;
}
}
#else
*_malloc = NULL;
*_free = NULL;
*_realloc = NULL;
*_gc = NULL;
*_shutdown = NULL;
#endif
}

View File

@ -277,10 +277,22 @@ ZEND_API void zend_mm_set_custom_handlers(zend_mm_heap *heap,
void* (*_malloc)(size_t ZEND_FILE_LINE_DC ZEND_FILE_LINE_ORIG_DC),
void (*_free)(void* ZEND_FILE_LINE_DC ZEND_FILE_LINE_ORIG_DC),
void* (*_realloc)(void*, size_t ZEND_FILE_LINE_DC ZEND_FILE_LINE_ORIG_DC));
ZEND_API void zend_mm_set_custom_handlers_ex(zend_mm_heap *heap,
void* (*_malloc)(size_t ZEND_FILE_LINE_DC ZEND_FILE_LINE_ORIG_DC),
void (*_free)(void* ZEND_FILE_LINE_DC ZEND_FILE_LINE_ORIG_DC),
void* (*_realloc)(void*, size_t ZEND_FILE_LINE_DC ZEND_FILE_LINE_ORIG_DC),
size_t (*_gc)(void),
void (*_shutdown)(bool, bool));
ZEND_API void zend_mm_get_custom_handlers(zend_mm_heap *heap,
void* (**_malloc)(size_t ZEND_FILE_LINE_DC ZEND_FILE_LINE_ORIG_DC),
void (**_free)(void* ZEND_FILE_LINE_DC ZEND_FILE_LINE_ORIG_DC),
void* (**_realloc)(void*, size_t ZEND_FILE_LINE_DC ZEND_FILE_LINE_ORIG_DC));
ZEND_API void zend_mm_get_custom_handlers_ex(zend_mm_heap *heap,
void* (**_malloc)(size_t ZEND_FILE_LINE_DC ZEND_FILE_LINE_ORIG_DC),
void (**_free)(void* ZEND_FILE_LINE_DC ZEND_FILE_LINE_ORIG_DC),
void* (**_realloc)(void*, size_t ZEND_FILE_LINE_DC ZEND_FILE_LINE_ORIG_DC),
size_t (**_gc)(void),
void (**_shutdown)(bool, bool));
typedef struct _zend_mm_storage zend_mm_storage;

View File

@ -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_custom_handlers.c, $ext_shared,, -DZEND_ENABLE_STATIC_TSRMLS_CACHE=1)
fi

View File

@ -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_custom_handlers.c", PHP_ZEND_TEST_SHARED, "/DZEND_ENABLE_STATIC_TSRMLS_CACHE=1");
ADD_FLAG("CFLAGS_ZEND_TEST", "/D PHP_ZEND_TEST_EXPORTS ");
}

View File

@ -62,6 +62,19 @@ ZEND_BEGIN_MODULE_GLOBALS(zend_test)
zend_long quantity_value;
zend_string *str_test;
zend_string *not_empty_str_test;
int zend_mm_custom_handlers_enabled;
// the previous heap that was found in ZendMM
zend_mm_heap* original_heap;
// the custom handlers that might have been found in the previous heap
void* (*custom_malloc)(size_t ZEND_FILE_LINE_DC ZEND_FILE_LINE_ORIG_DC);
void (*custom_free)(void* ZEND_FILE_LINE_DC ZEND_FILE_LINE_ORIG_DC);
void* (*custom_realloc)(void *, size_t ZEND_FILE_LINE_DC ZEND_FILE_LINE_ORIG_DC);
size_t (*custom_gc)(void);
void (*custom_shutdown)(bool, bool);
// this is our heap that we install our custom handlers on and inject into
// ZendMM
zend_mm_heap* observed_heap;
ZEND_END_MODULE_GLOBALS(zend_test)
extern ZEND_DECLARE_MODULE_GLOBALS(zend_test)

View File

@ -14,6 +14,7 @@
+----------------------------------------------------------------------+
*/
#include "zend_modules.h"
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
@ -35,6 +36,7 @@
#include "test_arginfo.h"
#include "zend_call_stack.h"
#include "zend_exceptions.h"
#include "zend_mm_custom_handlers.h"
// `php.h` sets `NDEBUG` when not `PHP_DEBUG` which will make `assert()` from
// assert.h a no-op. In order to have `assert()` working on NDEBUG builds, we
@ -1282,6 +1284,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_fiber_init();
zend_test_iterators_init();
zend_test_object_handlers_init();
@ -1310,6 +1313,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();
return SUCCESS;
}
@ -1327,6 +1331,7 @@ PHP_RSHUTDOWN_FUNCTION(zend_test)
zend_mm_set_heap(ZT_G(zend_orig_heap));
}
zend_test_mm_custom_handlers_rshutdown();
return SUCCESS;
}

View File

@ -0,0 +1,12 @@
--TEST--
ZendMM Custom Handlers: garbage collection
--EXTENSIONS--
zend_test
--FILE--
<?php
ini_set('zend_test.zend_mm_custom_handlers.enabled', 1);
$string = str_repeat('String', rand(1,100));
ini_set('zend_test.zend_mm_custom_handlers.enabled', 0);
?>
--EXPECTREGEX--
.*Allocated \d+ bytes at.*

View File

@ -0,0 +1,13 @@
--TEST--
ZendMM Custom Handlers: garbage collection
--EXTENSIONS--
zend_test
--FILE--
<?php
$string = str_repeat('String', rand(1,100));
ini_set('zend_test.zend_mm_custom_handlers.enabled', 1);
unset($string);
ini_set('zend_test.zend_mm_custom_handlers.enabled', 0);
?>
--EXPECTREGEX--
.*Freed memory at.*

View File

@ -0,0 +1,12 @@
--TEST--
ZendMM Custom Handlers: garbage collection
--EXTENSIONS--
zend_test
--FILE--
<?php
ini_set('zend_test.zend_mm_custom_handlers.enabled', 1);
gc_mem_caches();
ini_set('zend_test.zend_mm_custom_handlers.enabled', 0);
?>
--EXPECTREGEX--
.*ZendMM GC freed \d+ bytes.*

View File

@ -0,0 +1,180 @@
/*
+----------------------------------------------------------------------+
| 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 <florian@engelhardt.tc> |
+----------------------------------------------------------------------+
*/
#include "php.h"
#include "php_test.h"
#include "Zend/zend_alloc.h"
#include "zend_portability.h"
#include <stddef.h>
#include <stdio.h>
void* observe_malloc(size_t size ZEND_FILE_LINE_DC ZEND_FILE_LINE_ORIG_DC)
{
void *ptr = NULL;
if (ZT_G(custom_malloc)) {
zend_mm_set_heap(ZT_G(original_heap));
ptr = ZT_G(custom_malloc)(size ZEND_FILE_LINE_RELAY_CC ZEND_FILE_LINE_ORIG_RELAY_CC);
zend_mm_set_heap(ZT_G(observed_heap));
} else {
ptr = _zend_mm_alloc(ZT_G(original_heap), size ZEND_FILE_LINE_RELAY_CC ZEND_FILE_LINE_ORIG_RELAY_CC);
}
printf("Allocated %zu bytes at %p\n", size, ptr);
return ptr;
}
void observe_free(void* ptr ZEND_FILE_LINE_DC ZEND_FILE_LINE_ORIG_DC)
{
if (ZT_G(custom_free))
{
zend_mm_set_heap(ZT_G(original_heap));
ZT_G(custom_free)(ptr ZEND_FILE_LINE_RELAY_CC ZEND_FILE_LINE_ORIG_RELAY_CC);
zend_mm_set_heap(ZT_G(observed_heap));
} else {
_zend_mm_free(ZT_G(original_heap), ptr ZEND_FILE_LINE_RELAY_CC ZEND_FILE_LINE_ORIG_RELAY_CC);
}
printf("Freed memory at %p\n", ptr);
}
void* observe_realloc(void* ptr, size_t size ZEND_FILE_LINE_DC ZEND_FILE_LINE_ORIG_DC)
{
void * new_ptr;
if (ZT_G(custom_realloc))
{
zend_mm_set_heap(ZT_G(original_heap));
new_ptr = ZT_G(custom_realloc)(ptr, size ZEND_FILE_LINE_RELAY_CC ZEND_FILE_LINE_ORIG_RELAY_CC);
zend_mm_set_heap(ZT_G(observed_heap));
} else {
new_ptr = _zend_mm_realloc(ZT_G(original_heap), ptr, size ZEND_FILE_LINE_RELAY_CC ZEND_FILE_LINE_ORIG_RELAY_CC);
}
printf("Realloc of %zu bytes from %p to %p\n", size, ptr, new_ptr);
return new_ptr;
}
size_t observe_gc(void)
{
size_t size = 0;
if (ZT_G(custom_gc)) {
zend_mm_set_heap(ZT_G(original_heap));
size = ZT_G(custom_gc)();
zend_mm_set_heap(ZT_G(observed_heap));
} else {
size = zend_mm_gc(ZT_G(original_heap));
}
printf("ZendMM GC freed %zu bytes", size);
return size;
}
void observe_shutdown(bool full, bool silent)
{
if (ZT_G(custom_shutdown)) {
zend_mm_set_heap(ZT_G(original_heap));
ZT_G(custom_shutdown)(full, silent);
zend_mm_set_heap(ZT_G(observed_heap));
} else {
zend_mm_shutdown(ZT_G(original_heap), full, silent);
}
printf("Shutdown happened: full -> %d, silent -> %d\n", full, silent);
}
void zend_test_mm_custom_handlers_init(void)
{
if (ZT_G(observed_heap) != NULL) {
return;
}
ZT_G(original_heap) = zend_mm_get_heap();
if (zend_mm_is_custom_heap(ZT_G(original_heap))) {
zend_mm_get_custom_handlers_ex(
ZT_G(original_heap),
&ZT_G(custom_malloc),
&ZT_G(custom_free),
&ZT_G(custom_realloc),
&ZT_G(custom_gc),
&ZT_G(custom_shutdown)
);
}
printf("Prev handlers at %p, %p, %p, %p, %p\n", ZT_G(custom_malloc), ZT_G(custom_free), ZT_G(custom_realloc), ZT_G(custom_gc), ZT_G(custom_shutdown));
ZT_G(observed_heap) = zend_mm_startup();
zend_mm_set_custom_handlers_ex(
ZT_G(observed_heap),
observe_malloc,
observe_free,
observe_realloc,
observe_gc,
observe_shutdown
);
zend_mm_set_heap(ZT_G(observed_heap));
printf("Heap at %p installed in ZendMM (orig at %p)\n", ZT_G(observed_heap), ZT_G(original_heap));
}
void zend_test_mm_custom_handlers_shutdown(void)
{
if (ZT_G(observed_heap) == NULL) {
return;
}
zend_mm_set_heap(ZT_G(original_heap));
zend_mm_set_custom_handlers_ex(
ZT_G(observed_heap),
NULL,
NULL,
NULL,
NULL,
NULL
);
zend_mm_shutdown(ZT_G(observed_heap), true, true);
ZT_G(observed_heap) = NULL;
printf("Prev heap at %p restored in ZendMM\n", ZT_G(original_heap));
}
void zend_test_mm_custom_handlers_rshutdown(void)
{
zend_test_mm_custom_handlers_shutdown();
}
void zend_test_mm_custom_handlers_rinit(void)
{
if (ZT_G(zend_mm_custom_handlers_enabled)) {
zend_test_mm_custom_handlers_init();
}
}
static PHP_INI_MH(OnUpdateZendTestMMCustomHandlersEnabled)
{
if (new_value == NULL) {
return FAILURE;
}
int int_value = zend_ini_parse_bool(new_value);
if (int_value == 1) {
zend_test_mm_custom_handlers_init();
} else {
zend_test_mm_custom_handlers_shutdown();
}
return OnUpdateBool(entry, new_value, mh_arg1, mh_arg2, mh_arg3, stage);
}
PHP_INI_BEGIN()
STD_PHP_INI_BOOLEAN("zend_test.zend_mm_custom_handlers.enabled", "0", PHP_INI_USER, OnUpdateZendTestMMCustomHandlersEnabled, zend_mm_custom_handlers_enabled, zend_zend_test_globals, zend_test_globals)
PHP_INI_END()
void zend_test_mm_custom_handlers_minit(INIT_FUNC_ARGS)
{
if (type != MODULE_TEMPORARY) {
REGISTER_INI_ENTRIES();
} else {
(void)ini_entries;
}
}

View File

@ -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 <florian@engelhardt.tc> |
+----------------------------------------------------------------------+
*/
#ifndef ZEND_TEST_MM_CUSTOM_HANDLERS_H
#define ZEND_TEST_MM_CUSTOM_HANDLERS_H
void zend_test_mm_custom_handlers_minit(INIT_FUNC_ARGS);
void zend_test_mm_custom_handlers_rinit(void);
void zend_test_mm_custom_handlers_rshutdown(void);
#endif