From: Simon Glass <simon.glass@canonical.com> When CONFIG_MCHECK_HEAP_PROTECTION is enabled, use backtrace_str() to capture the caller information for each malloc/calloc/realloc/memalign call. This information is stored in the mcheck header and can be viewed with 'malloc dump'. Add a flag to disable the backtrace when the stack is corrupted, since the backtrace code tries to walks the invalid stack frames and will crash. Note: A few allocations made during libbacktrace initialisation may not have caller info since they occur during the first backtrace call. Example output showing caller info: 18a1d010 90 used log_init:453 <-board_init_r:774 18a1d0a0 6060 used membuf_new:420 <-console_record 18a3b840 90 used of_alias_scan:911 <-board_init_ Fix up the backtrace test to avoid recursion. Co-developed-by: Claude <noreply@anthropic.com> Signed-off-by: Simon Glass <simon.glass@canonical.com> --- common/dlmalloc.c | 35 +++++++++++++++++++++++++++++++---- doc/usage/cmd/malloc.rst | 12 ++++++++++++ include/malloc.h | 15 +++++++++++++++ test/lib/backtrace.c | 7 ++++--- 4 files changed, 62 insertions(+), 7 deletions(-) diff --git a/common/dlmalloc.c b/common/dlmalloc.c index 14515e423cc..c294b355ff2 100644 --- a/common/dlmalloc.c +++ b/common/dlmalloc.c @@ -5953,8 +5953,35 @@ size_t dlmalloc_usable_size(const void* mem) { } #if CONFIG_IS_ENABLED(MCHECK_HEAP_PROTECTION) +#include <backtrace.h> #include "mcheck_core.inc.h" +/* Guard against recursive backtrace calls during malloc */ +static bool in_backtrace __section(".data"); + +/* + * Flag to disable backtrace collection when the stack is known to be corrupt. + * Set via malloc_backtrace_skip() before calling panic(). + */ +static bool mcheck_skip_backtrace __section(".data"); + +void malloc_backtrace_skip(bool skip) +{ + mcheck_skip_backtrace = skip; +} + +static const char *mcheck_caller(void) +{ + const char *caller = NULL; + + if (!in_backtrace && !mcheck_skip_backtrace) { + in_backtrace = true; + caller = backtrace_str(2); + in_backtrace = false; + } + return caller; +} + void *dlmalloc(size_t bytes) { mcheck_pedantic_prehook(); @@ -5963,7 +5990,7 @@ void *dlmalloc(size_t bytes) if (!p) return p; - return mcheck_alloc_posthook(p, bytes, NULL); + return mcheck_alloc_posthook(p, bytes, mcheck_caller()); } void dlfree(void *mem) { dlfree_impl(mcheck_free_prehook(mem)); } @@ -5988,7 +6015,7 @@ void *dlrealloc(void *oldmem, size_t bytes) p = dlrealloc_impl(p, newsz); if (!p) return p; - return mcheck_alloc_noclean_posthook(p, bytes, NULL); + return mcheck_alloc_noclean_posthook(p, bytes, mcheck_caller()); } void *dlmemalign(size_t alignment, size_t bytes) @@ -5999,7 +6026,7 @@ void *dlmemalign(size_t alignment, size_t bytes) if (!p) return p; - return mcheck_memalign_posthook(alignment, p, bytes, NULL); + return mcheck_memalign_posthook(alignment, p, bytes, mcheck_caller()); } /* dlpvalloc, dlvalloc redirect to dlmemalign, so they need no wrapping */ @@ -6013,7 +6040,7 @@ void *dlcalloc(size_t n, size_t elem_size) if (!p) return p; - return mcheck_alloc_noclean_posthook(p, n * elem_size, NULL); + return mcheck_alloc_noclean_posthook(p, n * elem_size, mcheck_caller()); } /* mcheck API */ diff --git a/doc/usage/cmd/malloc.rst b/doc/usage/cmd/malloc.rst index fac9bb29aac..3693034b41e 100644 --- a/doc/usage/cmd/malloc.rst +++ b/doc/usage/cmd/malloc.rst @@ -59,6 +59,18 @@ Example Used: c2ef0 bytes in 931 chunks Free: 5f3f0c0 bytes in 2 chunks + top +With CONFIG_MCHECK_HEAP_PROTECTION enabled, the caller backtrace is shown:: + + => malloc dump + Heap dump: 18a1d000 - 1ea1f000 + Address Size Status + ---------------------------------- + 18a1d000 10 (chunk header) + 18a1d010 90 used log_init:453 <-board_init_r:774 + 18a1d0a0 6060 used membuf_new:420 <-console_record + 18a3b840 90 used of_alias_scan:911 <-board_init_ + ... + Configuration ------------- diff --git a/include/malloc.h b/include/malloc.h index a4d588936ec..3327bdcb44f 100644 --- a/include/malloc.h +++ b/include/malloc.h @@ -712,6 +712,21 @@ void malloc_disable_testing(void); */ void malloc_dump(void); +/** + * malloc_backtrace_skip() - Control backtrace collection in malloc + * + * When the stack is corrupted (e.g., by a stack overflow), collecting + * a backtrace during malloc can crash. Use this function to disable + * backtrace collection before corrupting the stack. + * + * @skip: true to skip backtrace collection, false to enable it + */ +#if CONFIG_IS_ENABLED(MCHECK_HEAP_PROTECTION) +void malloc_backtrace_skip(bool skip); +#else +static inline void malloc_backtrace_skip(bool skip) {} +#endif + /** * mem_malloc_init() - Initialize the malloc() heap * diff --git a/test/lib/backtrace.c b/test/lib/backtrace.c index 3f20b7854bf..155e5b13af8 100644 --- a/test/lib/backtrace.c +++ b/test/lib/backtrace.c @@ -68,16 +68,17 @@ static int lib_test_backtrace_str(struct unit_test_state *uts) line); ut_asserteq_regex(pattern, str); - /* Test backtrace_str() */ + /* Test backtrace_str() - copy result before printf since it may recurse */ line = __LINE__ + 1; cstr = backtrace_str(0); ut_assertnonnull(cstr); + strlcpy(buf, cstr, sizeof(buf)); - printf("backtrace_str: %s\n", cstr); + printf("backtrace_str: %s\n", buf); snprintf(pattern, sizeof(pattern), "lib_test_backtrace_str:%d <-ut_run_test:\\d+ <-ut_run_test_live_flat:\\d+", line); - ut_asserteq_regex(pattern, cstr); + ut_asserteq_regex(pattern, buf); return 0; } -- 2.43.0