[Concept,29/35] malloc: Add malloc dump command to walk the heap

Message ID 20251210000737.180797-30-sjg@u-boot.org
State New
Headers
Series malloc: Add heap debugging commands and mcheck caller tracking |

Commit Message

Simon Glass Dec. 10, 2025, 12:07 a.m. UTC
  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(-)
  

Patch

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);