[Concept,16/24] cmd: malloc: Add a command to show the malloc log

Message ID 20260103011908.149445-17-sjg@u-boot.org
State New
Headers
Series Malloc debugging and test/py improvements |

Commit Message

Simon Glass Jan. 3, 2026, 1:18 a.m. UTC
  From: Simon Glass <simon.glass@canonical.com>

Add a command interface for the malloc-traffic log:
- malloc log start: Start recording allocations
- malloc log stop: Stop recording
- malloc log: Dump the recorded entries

Example output:

  => malloc log
  Malloc log: 29 entries (max 524288, total 29)
   Seq  Type                   Ptr      Size  Caller
  ----  --------  ----------------  --------  ------
     0  free              16a016e0         0  free_pipe_list:2001
                <-parse_stream_outer:3208 <-parse_file_outer:3300
     1  alloc             16a01b90        20  hush_file_init:3277
                <-parse_file_outer:3295 <-run_pipe_real:1986

Co-developed-by: Claude <noreply@anthropic.com>
Signed-off-by: Simon Glass <simon.glass@canonical.com>
---

 cmd/Kconfig              | 10 +++++++++
 cmd/malloc.c             | 41 ++++++++++++++++++++++++++++++++--
 doc/develop/malloc.rst   |  3 ++-
 doc/usage/cmd/malloc.rst | 30 ++++++++++++++++++++++++-
 test/cmd/malloc.c        | 48 ++++++++++++++++++++++++++++++++++++++++
 5 files changed, 128 insertions(+), 4 deletions(-)
  

Patch

diff --git a/cmd/Kconfig b/cmd/Kconfig
index d602f430ab6..072ff879cd8 100644
--- a/cmd/Kconfig
+++ b/cmd/Kconfig
@@ -3049,6 +3049,16 @@  config CMD_MALLOC
 	  about memory allocation, such as total memory allocated and
 	  currently in use.
 
+config CMD_MALLOC_LOG
+	bool "malloc log - Log malloc traffic"
+	depends on CMD_MALLOC && MCHECK_LOG
+	default y
+	help
+	  This adds the 'malloc log' subcommand which records all
+	  malloc/free/realloc calls with their addresses, sizes, and caller
+	  information. Use 'malloc log start' to begin recording and
+	  'malloc log' to display the recorded entries.
+
 config CMD_MOUSE
 	bool "mouse - Show mouse input"
 	default y if MOUSE
diff --git a/cmd/malloc.c b/cmd/malloc.c
index 9c7dfbfc0c3..361750c45dd 100644
--- a/cmd/malloc.c
+++ b/cmd/malloc.c
@@ -38,10 +38,47 @@  static int do_malloc_dump(struct cmd_tbl *cmdtp, int flag, int argc,
 	return 0;
 }
 
+static int __maybe_unused do_malloc_log(struct cmd_tbl *cmdtp, int flag,
+					int argc, char *const argv[])
+{
+	if (argc < 2) {
+		malloc_log_dump();
+		return 0;
+	}
+
+	if (!strcmp(argv[1], "start")) {
+		malloc_log_start();
+		printf("Malloc logging started\n");
+	} else if (!strcmp(argv[1], "stop")) {
+		malloc_log_stop();
+		printf("Malloc logging stopped\n");
+	} else if (!strcmp(argv[1], "dump")) {
+		malloc_log_dump();
+	} else {
+		return CMD_RET_USAGE;
+	}
+
+	return 0;
+}
+
+#if CONFIG_IS_ENABLED(CMD_MALLOC_LOG)
+#define MALLOC_LOG_HELP	\
+	"malloc log [start|stop|dump] - log malloc traffic\n" \
+	"    start - start recording malloc/free calls\n" \
+	"    stop  - stop recording\n" \
+	"    dump  - print the log (or just 'malloc log')\n"
+#define MALLOC_LOG_SUBCMD , U_BOOT_SUBCMD_MKENT(log, 3, 1, do_malloc_log)
+#else
+#define MALLOC_LOG_HELP
+#define MALLOC_LOG_SUBCMD
+#endif
+
 U_BOOT_LONGHELP(malloc,
 	"info - display malloc statistics\n"
-	"malloc dump - dump heap chunks (address, size, status)\n");
+	"malloc dump - dump heap chunks (address, size, status)\n"
+	MALLOC_LOG_HELP);
 
 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(dump, 1, 1, do_malloc_dump));
+	U_BOOT_SUBCMD_MKENT(dump, 1, 1, do_malloc_dump)
+	MALLOC_LOG_SUBCMD);
diff --git a/doc/develop/malloc.rst b/doc/develop/malloc.rst
index 92180af055a..b9ab884d419 100644
--- a/doc/develop/malloc.rst
+++ b/doc/develop/malloc.rst
@@ -390,7 +390,8 @@  Malloc-Traffic Log
 
 On sandbox, when mcheck is enabled, a malloc-traffic log can record all
 malloc/free/realloc calls. This is useful for debugging allocation patterns
-and finding where allocations without caller info originate.
+and finding where allocations without caller info originate. See
+:doc:`../usage/cmd/malloc` for usage.
 
 The log buffer is allocated from host memory using ``os_malloc()``, so it
 does not affect U-Boot's heap. The size is controlled by
diff --git a/doc/usage/cmd/malloc.rst b/doc/usage/cmd/malloc.rst
index 3693034b41e..03f0669658b 100644
--- a/doc/usage/cmd/malloc.rst
+++ b/doc/usage/cmd/malloc.rst
@@ -13,6 +13,7 @@  Synopsis
 
     malloc info
     malloc dump
+    malloc log [start|stop]
 
 Description
 -----------
@@ -31,6 +32,13 @@  dump
     for debugging memory allocation issues. When CONFIG_MCHECK_HEAP_PROTECTION
     is enabled, the caller string is also shown if available.
 
+log
+    Controls the malloc traffic log. With no argument, dumps the recorded log
+    entries. Use ``start`` to begin recording malloc/free/realloc calls, and
+    ``stop`` to stop recording. Each entry shows the operation type, pointer
+    address, size, and caller backtrace. This is useful for tracking down
+    memory leaks or understanding allocation patterns.
+
 The total heap size is set by ``CONFIG_SYS_MALLOC_LEN``.
 
 Example
@@ -71,11 +79,31 @@  With CONFIG_MCHECK_HEAP_PROTECTION enabled, the caller backtrace is shown::
         18a3b840          90  used  of_alias_scan:911 <-board_init_
         ...
 
+With CONFIG_CMD_MALLOC_LOG enabled, the log subcommand is available::
+
+    => malloc log start
+    Malloc logging started
+    => ... do some operations ...
+    => malloc log stop
+    Malloc logging stopped
+    => malloc log
+    Malloc log: 5 entries (max 524288, total 5)
+     Seq  Type                   Ptr      Size  Caller
+    ----  --------  ----------------  --------  ------
+       0  alloc             16a01b90        20  hush_file_init:3277
+                  <-parse_file_outer:3295 <-run_pipe_real:1986
+       1  alloc             16a01bc0       100  xmalloc:107 <-xzalloc:117
+                  <-new_pipe:1498 <-run_list_real:1702
+       2  free              16a01bc0         0  free_pipe_list:2001
+                  <-parse_stream_outer:3208 <-parse_file_outer:3300
+       ...
+
 Configuration
 -------------
 
 The malloc command is enabled by CONFIG_CMD_MALLOC which depends on
-CONFIG_MALLOC_DEBUG.
+CONFIG_MALLOC_DEBUG. The log subcommand is enabled by CONFIG_CMD_MALLOC_LOG
+which additionally requires CONFIG_MCHECK_LOG.
 
 Return value
 ------------
diff --git a/test/cmd/malloc.c b/test/cmd/malloc.c
index 3c1a44bcacf..75e8afdec63 100644
--- a/test/cmd/malloc.c
+++ b/test/cmd/malloc.c
@@ -7,6 +7,7 @@ 
  */
 
 #include <malloc.h>
+#include <mapmem.h>
 #include <dm/test.h>
 #include <test/cmd.h>
 #include <test/ut.h>
@@ -52,3 +53,50 @@  static int cmd_test_malloc_dump(struct unit_test_state *uts)
 	return 0;
 }
 CMD_TEST(cmd_test_malloc_dump, UTF_CONSOLE);
+
+#if CONFIG_IS_ENABLED(MCHECK_LOG)
+/* Test 'malloc log' command */
+static int cmd_test_malloc_log(struct unit_test_state *uts)
+{
+	struct mlog_info info;
+	void *ptr, *ptr2;
+	int seq;
+
+	ut_assertok(run_command("malloc log start", 0));
+	ut_assert_nextline("Malloc logging started");
+	ut_assert_console_end();
+
+	/* Get current log position so we know our sequence numbers */
+	ut_assertok(malloc_log_info(&info));
+	seq = info.total_count;
+
+	/* Do allocations with distinctive sizes we can search for */
+	ptr = malloc(12345);
+	ut_assertnonnull(ptr);
+	ptr2 = realloc(ptr, 23456);
+	ut_assertnonnull(ptr2);
+	free(ptr2);
+
+	ut_assertok(run_command("malloc log stop", 0));
+	ut_assert_nextline("Malloc logging stopped");
+	ut_assert_console_end();
+
+	/* Dump the log and find our allocations by sequence number and size */
+	ut_assertok(run_command("malloc log", 0));
+	ut_assert_nextlinen("Malloc log: ");
+	ut_assert_nextline("%4s  %-8s  %10s  %8s  %s",
+			   "Seq", "Type", "Address", "Size", "Caller");
+	ut_assert_nextline("----  --------  ----------  --------  ------");
+	/* 12345 = 0x3039, 23456 = 0x5ba0 */
+	ut_assert_skip_to_linen("%4d  alloc     %10lx      3039", seq,
+				(ulong)map_to_sysmem(ptr));
+	ut_assert_skip_to_linen("%4d  realloc   %10lx      5ba0", seq + 1,
+				(ulong)map_to_sysmem(ptr2));
+	ut_assert_skip_to_linen("%4d  free      %10lx         0", seq + 2,
+				(ulong)map_to_sysmem(ptr2));
+	console_record_reset_enable();	/* discard remaining output */
+
+	return 0;
+}
+CMD_TEST(cmd_test_malloc_log, UTF_CONSOLE);
+#endif /* MCHECK_LOG */