From d6145356a457d347c714c4fde0a8f91fcc261756 Mon Sep 17 00:00:00 2001 From: SG Date: Thu, 4 Apr 2024 23:58:54 +0300 Subject: [PATCH] Furi: heap walker --- applications/services/cli/cli_commands.c | 44 +++++++- furi/core/memmgr_heap.c | 131 +++++++++++++++++++++-- furi/core/memmgr_heap.h | 12 ++- targets/f7/api_symbols.csv | 4 +- 4 files changed, 178 insertions(+), 13 deletions(-) diff --git a/applications/services/cli/cli_commands.c b/applications/services/cli/cli_commands.c index 43f1c01c4..9105e40e8 100644 --- a/applications/services/cli/cli_commands.c +++ b/applications/services/cli/cli_commands.c @@ -429,12 +429,54 @@ void cli_command_free(Cli* cli, FuriString* args, void* context) { printf("Maximum pool block: %zu\r\n", memmgr_pool_get_max_block()); } +typedef struct { + void* addr; + size_t size; +} FreeBlockInfo; + +#define FREE_BLOCK_INFO_MAX 128 + +typedef struct { + FreeBlockInfo free_blocks[FREE_BLOCK_INFO_MAX]; + size_t free_blocks_count; +} FreeBlockContext; + +static bool free_block_walker(void* pointer, size_t size, bool used, void* context) { + FreeBlockContext* free_blocks = (FreeBlockContext*)context; + if(!used) { + if(free_blocks->free_blocks_count < FREE_BLOCK_INFO_MAX) { + free_blocks->free_blocks[free_blocks->free_blocks_count].addr = pointer; + free_blocks->free_blocks[free_blocks->free_blocks_count].size = size; + free_blocks->free_blocks_count++; + } else { + return false; + } + } + return true; +} + void cli_command_free_blocks(Cli* cli, FuriString* args, void* context) { UNUSED(cli); UNUSED(args); UNUSED(context); - memmgr_heap_printf_free_blocks(); + FreeBlockContext* free_blocks = malloc(sizeof(FreeBlockContext)); + free_blocks->free_blocks_count = 0; + + memmgr_heap_walk_blocks(free_block_walker, free_blocks); + + for(size_t i = 0; i < free_blocks->free_blocks_count; i++) { + printf( + "A %p S %zu\r\n", + (void*)free_blocks->free_blocks[i].addr, + free_blocks->free_blocks[i].size); + } + + if(free_blocks->free_blocks_count == FREE_BLOCK_INFO_MAX) { + printf("... and more\r\n"); + } + + free(free_blocks); } void cli_command_i2c(Cli* cli, FuriString* args, void* context) { diff --git a/furi/core/memmgr_heap.c b/furi/core/memmgr_heap.c index 65e1cae1d..748ab845b 100644 --- a/furi/core/memmgr_heap.c +++ b/furi/core/memmgr_heap.c @@ -11,6 +11,23 @@ static tlsf_t tlsf = NULL; static size_t heap_used = 0; static size_t heap_max_used = 0; +// Furi heap extension +#include + +// Allocation tracking types +DICT_DEF2(MemmgrHeapAllocDict, uint32_t, uint32_t) //-V1048 + +DICT_DEF2( //-V1048 + MemmgrHeapThreadDict, + uint32_t, + M_DEFAULT_OPLIST, + MemmgrHeapAllocDict_t, + DICT_OPLIST(MemmgrHeapAllocDict)) + +// Thread allocation tracing storage +static MemmgrHeapThreadDict_t memmgr_heap_thread_dict = {0}; +static volatile uint32_t memmgr_heap_thread_trace_depth = 0; + static inline void memmgr_lock(void) { vTaskSuspendAll(); } @@ -23,24 +40,70 @@ static inline size_t memmgr_get_heap_size(void) { return (size_t)&__heap_end__ - (size_t)&__heap_start__; } +// Initialize tracing storage on start +void memmgr_heap_init(void) { + MemmgrHeapThreadDict_init(memmgr_heap_thread_dict); +} + void memmgr_heap_enable_thread_trace(FuriThreadId thread_id) { - UNUSED(thread_id); + memmgr_lock(); + { + memmgr_heap_thread_trace_depth++; + furi_check(MemmgrHeapThreadDict_get(memmgr_heap_thread_dict, (uint32_t)thread_id) == NULL); + MemmgrHeapAllocDict_t alloc_dict; + MemmgrHeapAllocDict_init(alloc_dict); + MemmgrHeapThreadDict_set_at(memmgr_heap_thread_dict, (uint32_t)thread_id, alloc_dict); + MemmgrHeapAllocDict_clear(alloc_dict); + memmgr_heap_thread_trace_depth--; + } + memmgr_unlock(); } void memmgr_heap_disable_thread_trace(FuriThreadId thread_id) { - UNUSED(thread_id); + memmgr_lock(); + { + memmgr_heap_thread_trace_depth++; + furi_check(MemmgrHeapThreadDict_erase(memmgr_heap_thread_dict, (uint32_t)thread_id)); + memmgr_heap_thread_trace_depth--; + } + memmgr_unlock(); } size_t memmgr_heap_get_thread_memory(FuriThreadId thread_id) { - UNUSED(thread_id); - return 0; + size_t leftovers = MEMMGR_HEAP_UNKNOWN; + vTaskSuspendAll(); + { + memmgr_heap_thread_trace_depth++; + MemmgrHeapAllocDict_t* alloc_dict = + MemmgrHeapThreadDict_get(memmgr_heap_thread_dict, (uint32_t)thread_id); + if(alloc_dict) { + leftovers = 0; + MemmgrHeapAllocDict_it_t alloc_dict_it; + for(MemmgrHeapAllocDict_it(alloc_dict_it, *alloc_dict); + !MemmgrHeapAllocDict_end_p(alloc_dict_it); + MemmgrHeapAllocDict_next(alloc_dict_it)) { + MemmgrHeapAllocDict_itref_t* data = MemmgrHeapAllocDict_ref(alloc_dict_it); + if(data->key != 0) { + block_header_t* block = block_from_ptr((uint8_t*)data->key); + if(!block_is_free(block)) { + // TODO: with tlsf we know the size of the block, so we don't need to store it on the dict + leftovers += data->value; + } + } + } + } + memmgr_heap_thread_trace_depth--; + } + (void)xTaskResumeAll(); + return leftovers; } static bool tlsf_walker_max_free(void* ptr, size_t size, int used, void* user) { UNUSED(ptr); + bool free = !used; size_t* max_free_block_size = (size_t*)user; - if(!used && size > *max_free_block_size) { + if(free && size > *max_free_block_size) { *max_free_block_size = size; } @@ -60,7 +123,52 @@ size_t memmgr_heap_get_max_free_block(void) { return max_free_block_size; } -void memmgr_heap_printf_free_blocks(void) { +typedef struct { + BlockWalker walker; + void* context; +} BlockWalkerWrapper; + +static bool tlsf_walker_wrapper(void* ptr, size_t size, int used, void* user) { + BlockWalkerWrapper* wrapper = (BlockWalkerWrapper*)user; + return wrapper->walker(ptr, size, used, wrapper->context); +} + +void memmgr_heap_walk_blocks(BlockWalker walker, void* context) { + memmgr_lock(); + + BlockWalkerWrapper wrapper = {walker, context}; + pool_t pool = tlsf_get_pool(tlsf); + tlsf_walk_pool(pool, tlsf_walker_wrapper, &wrapper); + + memmgr_unlock(); +} + +static inline void memmgr_heap_trace_malloc(void* pointer, size_t size) { + FuriThreadId thread_id = furi_thread_get_current_id(); + if(thread_id && memmgr_heap_thread_trace_depth == 0) { + memmgr_heap_thread_trace_depth++; + MemmgrHeapAllocDict_t* alloc_dict = + MemmgrHeapThreadDict_get(memmgr_heap_thread_dict, (uint32_t)thread_id); + if(alloc_dict) { + MemmgrHeapAllocDict_set_at(*alloc_dict, (uint32_t)pointer, (uint32_t)size); + } + memmgr_heap_thread_trace_depth--; + } +} + +static inline void memmgr_heap_trace_free(void* pointer) { + FuriThreadId thread_id = furi_thread_get_current_id(); + if(thread_id && memmgr_heap_thread_trace_depth == 0) { + memmgr_heap_thread_trace_depth++; + MemmgrHeapAllocDict_t* alloc_dict = + MemmgrHeapThreadDict_get(memmgr_heap_thread_dict, (uint32_t)thread_id); + if(alloc_dict) { + // In some cases thread may want to release memory that was not allocated by it + const bool res = MemmgrHeapAllocDict_erase(*alloc_dict, (uint32_t)pointer); + UNUSED(res); + } + memmgr_heap_thread_trace_depth--; + } } void* pvPortMalloc(size_t xSize) { @@ -75,6 +183,7 @@ void* pvPortMalloc(size_t xSize) { if(tlsf == NULL) { size_t pool_size = (size_t)&__heap_end__ - (size_t)&__heap_start__; tlsf = tlsf_create_with_pool((void*)&__heap_start__, pool_size, pool_size); + memmgr_heap_init(); } // allocate block @@ -93,6 +202,9 @@ void* pvPortMalloc(size_t xSize) { // clear block content memset(data, 0, xSize); + // trace allocation + memmgr_heap_trace_malloc(data, xSize); + memmgr_unlock(); return data; @@ -119,12 +231,15 @@ void vPortFree(void* pv) { // free tlsf_free(tlsf, pv); + // trace free + memmgr_heap_trace_free(pv); + memmgr_unlock(); } } size_t xPortGetFreeHeapSize(void) { - return memmgr_get_heap_size() - heap_used; + return memmgr_get_heap_size() - heap_used - tlsf_size(tlsf); } size_t xPortGetTotalHeapSize(void) { @@ -132,5 +247,5 @@ size_t xPortGetTotalHeapSize(void) { } size_t xPortGetMinimumEverFreeHeapSize(void) { - return memmgr_get_heap_size() - heap_max_used; + return memmgr_get_heap_size() - heap_max_used - tlsf_size(tlsf); } \ No newline at end of file diff --git a/furi/core/memmgr_heap.h b/furi/core/memmgr_heap.h index 7d889f152..2f61deb64 100644 --- a/furi/core/memmgr_heap.h +++ b/furi/core/memmgr_heap.h @@ -40,9 +40,17 @@ size_t memmgr_heap_get_thread_memory(FuriThreadId thread_id); */ size_t memmgr_heap_get_max_free_block(void); -/** Print the address and size of all free blocks to stdout +typedef bool (*BlockWalker)(void* pointer, size_t size, bool used, void* context); + +/** + * @brief Walk through all heap blocks + * @warning This function will lock memory manager and may cause deadlocks if any malloc/free is called inside the callback. + * Also, printf and furi_log contains malloc calls, so do not use them. + * + * @param walker + * @param context */ -void memmgr_heap_printf_free_blocks(void); +void memmgr_heap_walk_blocks(BlockWalker walker, void* context); #ifdef __cplusplus } diff --git a/targets/f7/api_symbols.csv b/targets/f7/api_symbols.csv index 6e65b9471..1d0cf168d 100644 --- a/targets/f7/api_symbols.csv +++ b/targets/f7/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,60.4,, +Version,+,61.0,, Header,+,applications/drivers/subghz/cc1101_ext/cc1101_ext_interconnect.h,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, @@ -2360,7 +2360,7 @@ Function,+,memmgr_heap_disable_thread_trace,void,FuriThreadId Function,+,memmgr_heap_enable_thread_trace,void,FuriThreadId Function,+,memmgr_heap_get_max_free_block,size_t, Function,+,memmgr_heap_get_thread_memory,size_t,FuriThreadId -Function,+,memmgr_heap_printf_free_blocks,void, +Function,+,memmgr_heap_walk_blocks,void,"BlockWalker, void*" Function,-,memmgr_pool_get_free,size_t, Function,-,memmgr_pool_get_max_block,size_t, Function,+,memmove,void*,"void*, const void*, size_t"