[Concept,04/33] malloc: Expose the largest-contiguous free region

Message ID 20260416023021.626949-5-sjg@u-boot.org
State New
Headers
Series Fix memory leaks and test pollution in sandbox tests |

Commit Message

Simon Glass April 16, 2026, 2:29 a.m. UTC
  From: Simon Glass <sjg@chromium.org>

Heap-pressure tests like common_test_malloc_very_large() assume almost
the whole heap is available as one contiguous free chunk. Even a single,
stray, leaked allocation in the middle of the area breaks that
assumption and fails the test, which makes the tests flakily depend on
every earlier test running with a perfect cleanup.

Add malloc_largest_free() which walks all segments (and the top and dv
chunks) and returns the biggest free chunk size, minus the per-chunk
dlmalloc overhead. Report it from 'malloc info' so the value is easy to
see while diagnosing fragmentation, and check for the new line in
cmd_test_malloc_info()

Signed-off-by: Simon Glass <sjg@chromium.org>
---

 cmd/malloc.c      |  1 +
 common/dlmalloc.c | 29 +++++++++++++++++++++++++++++
 include/malloc.h  | 13 +++++++++++++
 test/cmd/malloc.c |  1 +
 4 files changed, 44 insertions(+)
  

Patch

diff --git a/cmd/malloc.c b/cmd/malloc.c
index 7de421c0ae8..94d0f9427d6 100644
--- a/cmd/malloc.c
+++ b/cmd/malloc.c
@@ -24,6 +24,7 @@  static int do_malloc_info(struct cmd_tbl *cmdtp, int flag, int argc,
 
 	printf("total bytes   = %s\n", format_size(buf, info.total_bytes));
 	printf("in use bytes  = %s\n", format_size(buf, info.in_use_bytes));
+	printf("largest free  = %s\n", format_size(buf, malloc_largest_free()));
 	printf("malloc count  = %lu\n", info.malloc_count);
 	printf("free count    = %lu\n", info.free_count);
 	printf("realloc count = %lu\n", info.realloc_count);
diff --git a/common/dlmalloc.c b/common/dlmalloc.c
index 895aa215228..6d0bdc5b64e 100644
--- a/common/dlmalloc.c
+++ b/common/dlmalloc.c
@@ -7900,6 +7900,35 @@  void malloc_leak_check_free(struct malloc_leak_snap *snap)
 	snap->count = 0;
 }
 
+size_t malloc_largest_free(void)
+{
+	size_t largest = 0;
+	mstate m = gm;
+	msegmentptr s;
+
+	if (m->topsize > largest)
+		largest = m->topsize;
+	if (m->dvsize > largest)
+		largest = m->dvsize;
+
+	for (s = &m->seg; s != 0; s = s->next) {
+		mchunkptr q = align_as_chunk(s->base);
+
+		while (segment_holds(s, q) && q != m->top &&
+		       q->head != FENCEPOST_HEAD) {
+			if (!is_inuse(q)) {
+				size_t sz = chunksize(q);
+
+				if (sz > largest)
+					largest = sz;
+			}
+			q = next_chunk(q);
+		}
+	}
+
+	return largest > CHUNK_OVERHEAD ? largest - CHUNK_OVERHEAD : 0;
+}
+
 int initf_malloc(void)
 {
 #if CONFIG_IS_ENABLED(SYS_MALLOC_F)
diff --git a/include/malloc.h b/include/malloc.h
index 787ec999f5e..599440246b4 100644
--- a/include/malloc.h
+++ b/include/malloc.h
@@ -892,6 +892,19 @@  static inline size_t malloc_mcheck_count(void) { return 0; }
  */
 size_t malloc_chunk_size(void *ptr);
 
+/**
+ * malloc_largest_free() - Return the size of the largest free chunk
+ *
+ * Walks the heap and returns the largest contiguous free region that
+ * malloc() could currently hand out, minus the per-chunk dlmalloc
+ * overhead. Any mcheck header/canary overhead still comes off the top
+ * of the caller's request. Useful for tests that want to allocate "as
+ * much as possible" without tripping over fragmentation.
+ *
+ * Return: approximate largest request bytes malloc() would satisfy
+ */
+size_t malloc_largest_free(void);
+
 /**
  * malloc_mcheck_hdr_size() - Return the size of the mcheck header
  *
diff --git a/test/cmd/malloc.c b/test/cmd/malloc.c
index 812f7530b79..e84033b3ce9 100644
--- a/test/cmd/malloc.c
+++ b/test/cmd/malloc.c
@@ -25,6 +25,7 @@  static int cmd_test_malloc_info(struct unit_test_state *uts)
 	ut_assertok(run_command("malloc info", 0));
 	ut_assert_nextlinen("total bytes   = ");
 	ut_assert_nextlinen("in use bytes  = ");
+	ut_assert_nextlinen("largest free  = ");
 	ut_assert_nextlinen("malloc count  = ");
 	ut_assert_nextlinen("free count    = ");
 	ut_assert_nextlinen("realloc count = ");