From: Simon Glass <simon.glass@canonical.com> Add a new 'malloc dump' command that walks the dlmalloc heap from start to end, printing each chunk's address, size (in hex), and status (used/free/top). This is useful for debugging memory allocation issues. When CONFIG_MCHECK_HEAP_PROTECTION is enabled, the caller string is also shown if available. Example output: Heap dump: 18a1d000 - 1ea1f000 Address Size Status ---------------------------------- 18a1d000 10 (chunk header) 18a1d010 90 used 18adfc30 60 <free> 18adff90 5f3f030 top 1ea1f000 end ---------------------------------- Used: c2ef0 bytes in 931 chunks Free: 5f3f0c0 bytes in 2 chunks + top Expand the console-record size to handle this command. Co-developed-by: Claude <noreply@anthropic.com> Signed-off-by: Simon Glass <simon.glass@canonical.com> --- cmd/malloc.c | 14 ++++++-- common/Kconfig | 1 + common/dlmalloc.c | 71 +++++++++++++++++++++++++++++++++++++++- doc/usage/cmd/malloc.rst | 22 +++++++++++++ include/malloc.h | 8 +++++ test/cmd/malloc.c | 20 +++++++++++ 6 files changed, 133 insertions(+), 3 deletions(-) diff --git a/cmd/malloc.c b/cmd/malloc.c index 3750a16158b..9c7dfbfc0c3 100644 --- a/cmd/malloc.c +++ b/cmd/malloc.c @@ -30,8 +30,18 @@ static int do_malloc_info(struct cmd_tbl *cmdtp, int flag, int argc, return 0; } +static int do_malloc_dump(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + malloc_dump(); + + return 0; +} + U_BOOT_LONGHELP(malloc, - "info - display malloc statistics\n"); + "info - display malloc statistics\n" + "malloc dump - dump heap chunks (address, size, status)\n"); U_BOOT_CMD_WITH_SUBCMDS(malloc, "malloc information", malloc_help_text, - U_BOOT_SUBCMD_MKENT(info, 1, 1, do_malloc_info)); + U_BOOT_SUBCMD_MKENT(info, 1, 1, do_malloc_info), + U_BOOT_SUBCMD_MKENT(dump, 1, 1, do_malloc_dump)); diff --git a/common/Kconfig b/common/Kconfig index 3bd11f44c51..0aa32145710 100644 --- a/common/Kconfig +++ b/common/Kconfig @@ -26,6 +26,7 @@ config CONSOLE_RECORD_INIT_F config CONSOLE_RECORD_OUT_SIZE hex "Output buffer size" depends on CONSOLE_RECORD + default 0x2000 if CMD_MALLOC && UNIT_TEST default 0x1000 if UNIT_TEST default 0x400 help diff --git a/common/dlmalloc.c b/common/dlmalloc.c index 420bed1c480..b40963604e4 100644 --- a/common/dlmalloc.c +++ b/common/dlmalloc.c @@ -635,7 +635,7 @@ DECLARE_GLOBAL_DATA_PTR; #if CONFIG_IS_ENABLED(MCHECK_HEAP_PROTECTION) || CONFIG_IS_ENABLED(MALLOC_DEBUG) #define STATIC_IF_MCHECK static -#ifdef CONFIG_MCHECK_HEAP_PROTECTION +#if CONFIG_IS_ENABLED(MCHECK_HEAP_PROTECTION) #undef MALLOC_COPY #undef MALLOC_ZERO static inline void MALLOC_ZERO(void *p, size_t sz) { memset(p, 0, sz); } @@ -7021,6 +7021,75 @@ void malloc_disable_testing(void) malloc_testing = false; } +void malloc_dump(void) +{ + mchunkptr q; + msegmentptr s; + size_t used = 0, free_space = 0; + int used_count = 0, free_count = 0; + + if (!is_initialized(gm)) { + printf("dlmalloc not initialized\n"); + return; + } + + printf("Heap dump: %lx - %lx\n", mem_malloc_start, mem_malloc_end); + printf("%12s %10s %s\n", "Address", "Size", "Status"); + printf("----------------------------------\n"); + + s = &gm->seg; + while (s != 0) { + q = align_as_chunk(s->base); + + /* Show chunk header before first allocation */ + printf("%12lx %10zx (chunk header)\n", (ulong)s->base, + (size_t)((char *)chunk2mem(q) - (char *)s->base)); + + while (segment_holds(s, q) && + q != gm->top && q->head != FENCEPOST_HEAD) { + size_t sz = chunksize(q); + void *mem = chunk2mem(q); + + if (is_inuse(q)) { +#if CONFIG_IS_ENABLED(MCHECK_HEAP_PROTECTION) + struct mcheck_hdr *hdr = (struct mcheck_hdr *)mem; + + if (hdr->caller[0]) + printf("%12lx %10zx %s\n", + (ulong)mem, sz, hdr->caller); + else + printf("%12lx %10zx\n", + (ulong)mem, sz); +#else + printf("%12lx %10zx\n", + (ulong)mem, sz); +#endif + used += sz; + used_count++; + } else { + printf("%12lx %10zx <free>\n", + (ulong)mem, sz); + free_space += sz; + free_count++; + } + q = next_chunk(q); + } + s = s->next; + } + + /* Print top chunk (wilderness) */ + if (gm->top && gm->topsize > 0) { + printf("%12lx %10zx top\n", + (ulong)chunk2mem(gm->top), gm->topsize); + free_space += gm->topsize; + } + + printf("%12lx %10s end\n", mem_malloc_end, ""); + printf("----------------------------------\n"); + printf("Used: %zx bytes in %d chunks\n", used, used_count); + printf("Free: %zx bytes in %d chunks + top\n", free_space, free_count); +} + int initf_malloc(void) { #if CONFIG_IS_ENABLED(SYS_MALLOC_F) diff --git a/doc/usage/cmd/malloc.rst b/doc/usage/cmd/malloc.rst index 7b08b358258..fac9bb29aac 100644 --- a/doc/usage/cmd/malloc.rst +++ b/doc/usage/cmd/malloc.rst @@ -12,6 +12,7 @@ Synopsis :: malloc info + malloc dump Description ----------- @@ -23,6 +24,13 @@ info amount currently in use, and call counts for malloc(), free(), and realloc(). +dump + Walks the heap and prints each chunk's address, size (in hex), and status. + In-use chunks show no status label, while free chunks show ``<free>``. + Special entries show ``(chunk header)``, ``top``, or ``end``. This is useful + for debugging memory allocation issues. When CONFIG_MCHECK_HEAP_PROTECTION + is enabled, the caller string is also shown if available. + The total heap size is set by ``CONFIG_SYS_MALLOC_LEN``. Example @@ -37,6 +45,20 @@ Example free count = 567 realloc count = 89 + => malloc dump + Heap dump: 19a0e000 - 1fa10000 + Address Size Status + ---------------------------------- + 19a0e000 10 (chunk header) + 19a0e010 a0 + 19a0e0b0 6070 + 19adfc30 60 <free> + 19adff90 5f3f030 top + 1fa10000 end + ---------------------------------- + Used: c2ef0 bytes in 931 chunks + Free: 5f3f0c0 bytes in 2 chunks + top + Configuration ------------- diff --git a/include/malloc.h b/include/malloc.h index 0d8a2a43f2a..a4d588936ec 100644 --- a/include/malloc.h +++ b/include/malloc.h @@ -704,6 +704,14 @@ void malloc_enable_testing(int max_allocs); */ void malloc_disable_testing(void); +/** + * malloc_dump() - Print a dump of all heap chunks + * + * Walks the dlmalloc heap from start to end, printing each chunk's + * address, size, and status (used/free/top). + */ +void malloc_dump(void); + /** * mem_malloc_init() - Initialize the malloc() heap * diff --git a/test/cmd/malloc.c b/test/cmd/malloc.c index f9d41a36cad..3c1a44bcacf 100644 --- a/test/cmd/malloc.c +++ b/test/cmd/malloc.c @@ -32,3 +32,23 @@ static int cmd_test_malloc_info(struct unit_test_state *uts) return 0; } CMD_TEST(cmd_test_malloc_info, UTF_CONSOLE); + +/* Test 'malloc dump' command */ +static int cmd_test_malloc_dump(struct unit_test_state *uts) +{ + /* this takes a long time to dump, with truetype enabled, so skip it */ + return -EAGAIN; + + ut_assertok(run_command("malloc dump", 0)); + ut_assert_nextlinen("Heap dump: "); + ut_assert_nextline("%12s %10s %s", "Address", "Size", "Status"); + ut_assert_nextline("----------------------------------"); + ut_assert_nextline("%12lx %10x (chunk header)", mem_malloc_start, 0x10); + ut_assert_skip_to_line("----------------------------------"); + ut_assert_nextlinen("Used: "); + ut_assert_nextlinen("Free: "); + ut_assert_console_end(); + + return 0; +} +CMD_TEST(cmd_test_malloc_dump, UTF_CONSOLE); -- 2.43.0