From 7632a411e3b839e6a438aa1367ec3d6f310d3d90 Mon Sep 17 00:00:00 2001 From: Nikita Popov Date: Fri, 28 Jun 2019 10:19:18 +0200 Subject: [PATCH] Add tracked arena allocator Available under -DZEND_TRACK_ARENA_ALLOC. This will use the system allocator combined with arena checkpointing & release semantics and allows analyzing arena usage under asan/valgrind. I've sacrificed the duplicate arena implementation in mysqlnd, as the integration with mysqlnd alloc is not worth the code duplication to me. --- Zend/zend_arena.h | 104 +++++++++++++++++++++++++++++- ext/mysqlnd/mysqlnd_block_alloc.c | 96 +++++---------------------- 2 files changed, 117 insertions(+), 83 deletions(-) diff --git a/Zend/zend_arena.h b/Zend/zend_arena.h index a469616592..6ecc1fc85f 100644 --- a/Zend/zend_arena.h +++ b/Zend/zend_arena.h @@ -21,6 +21,8 @@ #include "zend.h" +#ifndef ZEND_TRACK_ARENA_ALLOC + typedef struct _zend_arena zend_arena; struct _zend_arena { @@ -48,8 +50,6 @@ static zend_always_inline void zend_arena_destroy(zend_arena *arena) } while (arena); } -#define ZEND_ARENA_ALIGNMENT 8U - static zend_always_inline void* zend_arena_alloc(zend_arena **arena_ptr, size_t size) { zend_arena *arena = *arena_ptr; @@ -121,4 +121,104 @@ static zend_always_inline zend_bool zend_arena_contains(zend_arena *arena, void return 0; } +#else + +/* Use normal allocations and keep track of them for mass-freeing. + * This is intended for use with asan/valgrind. */ + +typedef struct _zend_arena zend_arena; + +struct _zend_arena { + void **ptr; + void **end; + struct _zend_arena *prev; + void *ptrs[0]; +}; + +#define ZEND_TRACKED_ARENA_SIZE 1000 + +static zend_always_inline zend_arena *zend_arena_create(size_t _size) +{ + zend_arena *arena = (zend_arena*) emalloc( + sizeof(zend_arena) + sizeof(void *) * ZEND_TRACKED_ARENA_SIZE); + arena->ptr = &arena->ptrs[0]; + arena->end = &arena->ptrs[ZEND_TRACKED_ARENA_SIZE]; + arena->prev = NULL; + return arena; +} + +static zend_always_inline void zend_arena_destroy(zend_arena *arena) +{ + do { + zend_arena *prev = arena->prev; + void **ptr; + for (ptr = arena->ptrs; ptr < arena->ptr; ptr++) { + efree(*ptr); + } + efree(arena); + arena = prev; + } while (arena); +} + +static zend_always_inline void *zend_arena_alloc(zend_arena **arena_ptr, size_t size) +{ + zend_arena *arena = *arena_ptr; + if (arena->ptr == arena->end) { + *arena_ptr = zend_arena_create(0); + (*arena_ptr)->prev = arena; + arena = *arena_ptr; + } + + return *arena->ptr++ = emalloc(size); +} + +static zend_always_inline void* zend_arena_calloc(zend_arena **arena_ptr, size_t count, size_t unit_size) +{ + int overflow; + size_t size; + void *ret; + + size = zend_safe_address(unit_size, count, 0, &overflow); + if (UNEXPECTED(overflow)) { + zend_error(E_ERROR, "Possible integer overflow in zend_arena_calloc() (%zu * %zu)", unit_size, count); + } + ret = zend_arena_alloc(arena_ptr, size); + memset(ret, 0, size); + return ret; +} + +static zend_always_inline void* zend_arena_checkpoint(zend_arena *arena) +{ + return arena->ptr; +} + +static zend_always_inline void zend_arena_release(zend_arena **arena_ptr, void *checkpoint) +{ + while (1) { + zend_arena *arena = *arena_ptr; + zend_arena *prev = arena->prev; + while (1) { + if (arena->ptr == (void **) checkpoint) { + return; + } + if (arena->ptr == arena->ptrs) { + break; + } + arena->ptr--; + efree(*arena->ptr); + } + efree(arena); + *arena_ptr = prev; + ZEND_ASSERT(*arena_ptr); + } +} + +static zend_always_inline zend_bool zend_arena_contains(zend_arena *arena, void *ptr) +{ + /* TODO: Dummy */ + return 1; +} + +#endif + #endif /* _ZEND_ARENA_H_ */ diff --git a/ext/mysqlnd/mysqlnd_block_alloc.c b/ext/mysqlnd/mysqlnd_block_alloc.c index bce28a0330..63a48f4faa 100644 --- a/ext/mysqlnd/mysqlnd_block_alloc.c +++ b/ext/mysqlnd/mysqlnd_block_alloc.c @@ -24,83 +24,13 @@ #include "mysqlnd_debug.h" #include "mysqlnd_priv.h" - -/* {{{ mysqlnd_arena_create */ -static zend_always_inline zend_arena* mysqlnd_arena_create(size_t size) -{ - zend_arena *arena = (zend_arena*)mnd_emalloc(size); - - arena->ptr = (char*) arena + ZEND_MM_ALIGNED_SIZE(sizeof(zend_arena)); - arena->end = (char*) arena + size; - arena->prev = NULL; - return arena; -} -/* }}} */ - -/* {{{ mysqlnd_arena_destroy */ -static zend_always_inline void mysqlnd_arena_destroy(zend_arena *arena) -{ - do { - zend_arena *prev = arena->prev; - mnd_efree(arena); - arena = prev; - } while (arena); -} -/* }}} */ - -/* {{{ mysqlnd_arena_alloc */ -static zend_always_inline void* mysqlnd_arena_alloc(zend_arena **arena_ptr, size_t size) -{ - zend_arena *arena = *arena_ptr; - char *ptr = arena->ptr; - - size = ZEND_MM_ALIGNED_SIZE(size); - - if (EXPECTED(size <= (size_t)(arena->end - ptr))) { - arena->ptr = ptr + size; - } else { - size_t arena_size = - UNEXPECTED((size + ZEND_MM_ALIGNED_SIZE(sizeof(zend_arena))) > (size_t)(arena->end - (char*) arena)) ? - (size + ZEND_MM_ALIGNED_SIZE(sizeof(zend_arena))) : - (size_t)(arena->end - (char*) arena); - zend_arena *new_arena = (zend_arena*)mnd_emalloc(arena_size); - - ptr = (char*) new_arena + ZEND_MM_ALIGNED_SIZE(sizeof(zend_arena)); - new_arena->ptr = (char*) new_arena + ZEND_MM_ALIGNED_SIZE(sizeof(zend_arena)) + size; - new_arena->end = (char*) new_arena + arena_size; - new_arena->prev = arena; - *arena_ptr = new_arena; - } - - return (void*) ptr; -} -/* }}} */ - -static zend_always_inline void* mysqlnd_arena_checkpoint(zend_arena *arena) -{ - return arena->ptr; -} - -static zend_always_inline void mysqlnd_arena_release(zend_arena **arena_ptr, void *checkpoint) -{ - zend_arena *arena = *arena_ptr; - - while (UNEXPECTED((char*)checkpoint > arena->end) || - UNEXPECTED((char*)checkpoint <= (char*)arena)) { - zend_arena *prev = arena->prev; - mnd_efree(arena); - *arena_ptr = arena = prev; - } - ZEND_ASSERT((char*)checkpoint > (char*)arena && (char*)checkpoint <= arena->end); - arena->ptr = (char*)checkpoint; -} - /* {{{ mysqlnd_mempool_free_chunk */ static void mysqlnd_mempool_free_chunk(MYSQLND_MEMORY_POOL * pool, void * ptr) { DBG_ENTER("mysqlnd_mempool_free_chunk"); /* Try to back-off and guess if this is the last block allocated */ +#ifndef ZEND_TRACK_ARENA_ALLOC if (ptr == pool->last) { /* This was the last allocation. Lucky us, we can free @@ -109,6 +39,7 @@ mysqlnd_mempool_free_chunk(MYSQLND_MEMORY_POOL * pool, void * ptr) pool->arena->ptr = (char*)ptr; pool->last = NULL; } +#endif DBG_VOID_RETURN; } /* }}} */ @@ -120,6 +51,7 @@ mysqlnd_mempool_resize_chunk(MYSQLND_MEMORY_POOL * pool, void * ptr, size_t old_ { DBG_ENTER("mysqlnd_mempool_resize_chunk"); +#ifndef ZEND_TRACK_ARENA_ALLOC /* Try to back-off and guess if this is the last block allocated */ if (ptr == pool->last && (ZEND_MM_ALIGNED_SIZE(size) <= ((char*)pool->arena->end - (char*)ptr))) { @@ -128,11 +60,13 @@ mysqlnd_mempool_resize_chunk(MYSQLND_MEMORY_POOL * pool, void * ptr, size_t old_ a bit of memory from the pool. Next time we will return from the same ptr. */ pool->arena->ptr = (char*)ptr + ZEND_MM_ALIGNED_SIZE(size); - } else { - void *new_ptr = mysqlnd_arena_alloc(&pool->arena, size); - memcpy(new_ptr, ptr, MIN(old_size, size)); - pool->last = ptr = new_ptr; + DBG_RETURN(ptr); } +#endif + + void *new_ptr = zend_arena_alloc(&pool->arena, size); + memcpy(new_ptr, ptr, MIN(old_size, size)); + pool->last = ptr = new_ptr; DBG_RETURN(ptr); } /* }}} */ @@ -145,7 +79,7 @@ mysqlnd_mempool_get_chunk(MYSQLND_MEMORY_POOL * pool, size_t size) void *ptr = NULL; DBG_ENTER("mysqlnd_mempool_get_chunk"); - ptr = mysqlnd_arena_alloc(&pool->arena, size); + ptr = zend_arena_alloc(&pool->arena, size); pool->last = ptr; DBG_RETURN(ptr); @@ -161,8 +95,8 @@ mysqlnd_mempool_create(size_t arena_size) MYSQLND_MEMORY_POOL * ret; DBG_ENTER("mysqlnd_mempool_create"); - arena = mysqlnd_arena_create(MAX(arena_size, sizeof(zend_arena))); - ret = mysqlnd_arena_alloc(&arena, sizeof(MYSQLND_MEMORY_POOL)); + arena = zend_arena_create(MAX(arena_size, sizeof(zend_arena))); + ret = zend_arena_alloc(&arena, sizeof(MYSQLND_MEMORY_POOL)); ret->arena = arena; ret->last = NULL; ret->checkpoint = NULL; @@ -180,7 +114,7 @@ mysqlnd_mempool_destroy(MYSQLND_MEMORY_POOL * pool) { DBG_ENTER("mysqlnd_mempool_destroy"); /* mnd_free will reference LOCK_access and might crash, depending on the caller...*/ - mysqlnd_arena_destroy(pool->arena); + zend_arena_destroy(pool->arena); DBG_VOID_RETURN; } /* }}} */ @@ -190,7 +124,7 @@ PHPAPI void mysqlnd_mempool_save_state(MYSQLND_MEMORY_POOL * pool) { DBG_ENTER("mysqlnd_mempool_save_state"); - pool->checkpoint = mysqlnd_arena_checkpoint(pool->arena); + pool->checkpoint = zend_arena_checkpoint(pool->arena); DBG_VOID_RETURN; } /* }}} */ @@ -201,7 +135,7 @@ mysqlnd_mempool_restore_state(MYSQLND_MEMORY_POOL * pool) { DBG_ENTER("mysqlnd_mempool_restore_state"); if (pool->checkpoint) { - mysqlnd_arena_release(&pool->arena, pool->checkpoint); + zend_arena_release(&pool->arena, pool->checkpoint); pool->last = NULL; pool->checkpoint = NULL; } -- 2.40.0