From patchwork Mon Mar 16 18:30:32 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Simon Glass X-Patchwork-Id: 2020 Return-Path: X-Original-To: u-boot-concept@u-boot.org Delivered-To: u-boot-concept@u-boot.org DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1773685890; bh=E1EBwnSZFMur53S/tUomr3J0qIRXfRfMSJplSqSNpQ8=; h=From:To:Date:In-Reply-To:References:CC:Subject:List-Id: List-Archive:List-Help:List-Owner:List-Post:List-Subscribe: List-Unsubscribe:From; b=LMB7ArpDBYMg9H29SAvo8ZhUt1QQIy9gR5lt3f1xXubhXFuHH7V/z7C8+G1GJ1hG1 LYny+MEXa83sZ3Tde7TPQxvjurKALtb2St8NZRg1Ju6Kybzm6NOVvQl2wmWgq2DSIf vglWKGQqqdr+nY2LWK5rneSmtLBGeT7r1+S6cJIHijV3co8yHXC4ewY07C2eLT6dth TMUHCqGRb9JHoN199AFzaGxY8Kht1zdM2G74K/SMG7s+19SB3AwYZY9QcxvRJzGzC6 Ffk2v1zdgCKjOJ5PFjWTfCfWdJ2RKct5S+gHlPrD416XQnBeWwOrdCv46AgMONhhk3 GL1FAmXAE9Isw== Received: from localhost (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id 06EBD6A081 for ; Mon, 16 Mar 2026 12:31:30 -0600 (MDT) X-Virus-Scanned: Debian amavis at Received: from mail.u-boot.org ([127.0.0.1]) by localhost (mail.u-boot.org [127.0.0.1]) (amavis, port 10024) with ESMTP id grD0Uwi1eiuM for ; Mon, 16 Mar 2026 12:31:29 -0600 (MDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1773685887; bh=E1EBwnSZFMur53S/tUomr3J0qIRXfRfMSJplSqSNpQ8=; h=From:To:Date:In-Reply-To:References:CC:Subject:List-Id: List-Archive:List-Help:List-Owner:List-Post:List-Subscribe: List-Unsubscribe:From; b=emslsM5mf2DPw7kdbj8tO169REQ23Psy8grE/YBMqrGWen9r3WyBbginrjdlG0z1N S6Cy8lECFRYvFgOa7Hw/ovTMrme8d0x9+IkM39Pzc5eIsVaH2lnklHPtpeCNwF8gow BIKECRHB7gOtAFPGoCl5WNNsiGM4ClrHj9rxXISXqFlSZ5aJkyUd+3e3an1c26ef7W Y2Hy4r68CBaW5H8MPYkrO8F73XxXejOBV1tflDxtDhB3YbLhrByKshDaIO5kPpw83H 0Bu/uOF8/tiPipX+WZd2vRe8CLTu5100o9s1HLbAs50Ix0rcLP18MiGpkGpw+MIGxF EXSksZ6CMSkNw== Received: from mail.u-boot.org (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id A117F6A0B1 for ; Mon, 16 Mar 2026 12:31:27 -0600 (MDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1773685880; bh=DFNCvghN99hxGJtJm8SY/c+kW68EK0Nqk0AeEopvFaA=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=E3M4X8T+wRqJa6yjJ9nTx7krfDwBEAATrGKnPImlm1jZkmWM2X/uaMMUhjU7bdoMq F7X/cvSH8fYjaXrc6kwbuL0gcgKbPZzEwP9YkV0LerejQCbxYmiiyo+Rvfhqh32Ue/ B1xU2+Zw7kORrxguI6wfbElJbMj9JWqkNyc+AvQnpOowmRi3EK+q9If4daAu6CkvnY CAz/zjLc6MEae9XdodPccp16CQ/lHdyLWD1gs7DspHb32ENTfwXO/I9sFXo6J9HjAE Pf876iAAFHI/3l232bq9FtlDubb287iCr5ON+MDuVEZ4Pz4QwXdq0+Oos5QaIq9G+W n4E139vWVIxCg== Received: from localhost (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id 751A26A09C; Mon, 16 Mar 2026 12:31:20 -0600 (MDT) X-Virus-Scanned: Debian amavis at Received: from mail.u-boot.org ([127.0.0.1]) by localhost (mail.u-boot.org [127.0.0.1]) (amavis, port 10026) with ESMTP id d2rl3-zghZcx; Mon, 16 Mar 2026 12:31:20 -0600 (MDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1773685879; bh=fTdqaMkB/ZjNsWLP93lWzDAbGd9djz7MrDFH0DOPBbc=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=jTeEc55rCoDNHTQD2eYoBO42oaBjwKmNc1WZdq/2NRDeoSlsA8HQTV7p5otskVP9V Kp8Y1GoJyCMpIZw+OUR7joAVE50N0vNz8AT3sffeGinyS2YGIPqvx8IRib/4iCdsjZ BfXkwb3AsL5cp/73cDb0qfpkrhRC2fNEkE1lsNBE0pGIUPCAAZ0xO6BQW0y/gaDzb3 u/bOSRkc85k7udXS/0rHgzD12Uao3nH+iXSYMZ9+L1GRwkpeJ5WKCvQoXYM7sK/k9c qC4d+moC+2p5d2Xp3QAEOSwKnLfq3SJgMOUT0pGRJskt5Voy/hKcbuLKiQjJ7ExkzF PlTR5KAuQUkrw== Received: from u-boot.org (unknown [73.34.74.121]) by mail.u-boot.org (Postfix) with ESMTPSA id 35BA16A081; Mon, 16 Mar 2026 12:31:19 -0600 (MDT) From: Simon Glass To: U-Boot Concept Date: Mon, 16 Mar 2026 12:30:32 -0600 Message-ID: <20260316183050.3855921-10-sjg@u-boot.org> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20260316183050.3855921-1-sjg@u-boot.org> References: <20260316183050.3855921-1-sjg@u-boot.org> MIME-Version: 1.0 Message-ID-Hash: UPLCSGBMFEEJU65GHFD6QCWJRGKBXH4K X-Message-ID-Hash: UPLCSGBMFEEJU65GHFD6QCWJRGKBXH4K X-MailFrom: sjg@u-boot.org X-Mailman-Rule-Misses: dmarc-mitigation; no-senders; approved; loop; banned-address; emergency; member-moderation; nonmember-moderation; administrivia; implicit-dest; max-recipients; max-size; news-moderation; no-subject; digests; suspicious-header CC: Simon Glass X-Mailman-Version: 3.3.10 Precedence: list Subject: [Concept] [PATCH 09/17] cmd: malloc: Add leak subcommand List-Id: Discussion and patches related to U-Boot Concept Archived-At: List-Archive: List-Help: List-Owner: List-Post: List-Subscribe: List-Unsubscribe: From: Simon Glass Add 'malloc leak' subcommands for interactive heap-leak detection at the U-Boot command line: malloc leak start - snapshot current heap allocations malloc leak - count new allocations (keeps the snapshot) malloc leak end - print and count new allocations, free snapshot Each leaked allocation is printed with its address, size and caller backtrace (when mcheck is enabled). Guarded by CONFIG_CMD_MALLOC_LEAK which depends on CONFIG_SANDBOX since the snapshot uses os_malloc() for storage. Signed-off-by: Simon Glass --- cmd/Kconfig | 10 +++++++ cmd/malloc.c | 65 ++++++++++++++++++++++++++++++++++++++++++ doc/develop/malloc.rst | 32 +++++++++++++++++++-- test/cmd/malloc.c | 52 +++++++++++++++++++++++++++++++++ 4 files changed, 157 insertions(+), 2 deletions(-) diff --git a/cmd/Kconfig b/cmd/Kconfig index 4af032233b9..f9d6ea46566 100644 --- a/cmd/Kconfig +++ b/cmd/Kconfig @@ -3139,6 +3139,16 @@ config CMD_MALLOC about memory allocation, such as total memory allocated and currently in use. +config CMD_MALLOC_LEAK + bool "malloc leak - Detect heap leaks" + depends on CMD_MALLOC && SANDBOX + default y + help + This adds the 'malloc leak' subcommand which snapshots the heap + and later reports any new allocations. Use 'malloc leak start' + before an operation and 'malloc leak end' afterwards to see + leaked allocations with their caller backtraces. + config CMD_MALLOC_LOG bool "malloc log - Log malloc traffic" depends on CMD_MALLOC && MCHECK_LOG diff --git a/cmd/malloc.c b/cmd/malloc.c index e7362a39bd2..6b019f4c056 100644 --- a/cmd/malloc.c +++ b/cmd/malloc.c @@ -62,6 +62,69 @@ static int __maybe_unused do_malloc_log(struct cmd_tbl *cmdtp, int flag, return 0; } +static struct malloc_leak_snap leak_snap; + +static int __maybe_unused do_malloc_leak(struct cmd_tbl *cmdtp, int flag, + int argc, char *const argv[]) +{ + if (argc < 2) { + int leaks; + + if (!leak_snap.addr) { + printf("No snapshot taken, use 'malloc leak start'\n"); + return CMD_RET_FAILURE; + } + + leaks = malloc_leak_check_count(&leak_snap); + if (leaks > 0) + printf("%d new allocs\n", leaks); + else + printf("No leaks\n"); + + return 0; + } + + if (!strcmp(argv[1], "start")) { + int ret; + + if (leak_snap.addr) + malloc_leak_check_free(&leak_snap); + ret = malloc_leak_check_start(&leak_snap); + if (ret) + return CMD_RET_FAILURE; + printf("Heap snapshot: %d allocs\n", leak_snap.count); + } else if (!strcmp(argv[1], "end")) { + int leaks; + + if (!leak_snap.addr) { + printf("No snapshot taken, use 'malloc leak start'\n"); + return CMD_RET_FAILURE; + } + + leaks = malloc_leak_check_end(&leak_snap); + if (leaks > 0) + printf("%d leaked allocs\n", leaks); + else + printf("No leaks\n"); + } else { + return CMD_RET_USAGE; + } + + return 0; +} + +#if CONFIG_IS_ENABLED(CMD_MALLOC_LEAK) +#define MALLOC_LEAK_HELP \ + "malloc leak [start|end] - detect heap leaks\n" \ + " start - snapshot current heap allocations\n" \ + " end - show allocations not in the snapshot\n" \ + " (none) - count new allocations without freeing snapshot\n" +#define MALLOC_LEAK_SUBCMD , U_BOOT_SUBCMD_MKENT(leak, 3, 1, do_malloc_leak) +#else +#define MALLOC_LEAK_HELP +#define MALLOC_LEAK_SUBCMD +#endif + #if CONFIG_IS_ENABLED(CMD_MALLOC_LOG) #define MALLOC_LOG_HELP \ "malloc log [start|stop|dump] - log malloc traffic\n" \ @@ -77,9 +140,11 @@ static int __maybe_unused do_malloc_log(struct cmd_tbl *cmdtp, int flag, U_BOOT_LONGHELP(malloc, "info - display malloc statistics\n" "malloc dump - dump heap chunks (address, size, status)\n" + MALLOC_LEAK_HELP 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) + MALLOC_LEAK_SUBCMD MALLOC_LOG_SUBCMD); diff --git a/doc/develop/malloc.rst b/doc/develop/malloc.rst index 6bb17dca7b1..ecaf8af3c3e 100644 --- a/doc/develop/malloc.rst +++ b/doc/develop/malloc.rst @@ -527,12 +527,40 @@ This makes it easy to scan an entire test suite for leaks:: $ um t --leak-check -V dm +**Interactive leak checking with the malloc command** + +The ``malloc leak`` command provides interactive leak detection at the +U-Boot command line. Take a snapshot before an operation and check +afterwards:: + + => malloc leak start + Heap snapshot: 974 allocs + => setenv foo bar + => malloc leak end + 14a2a9a0 90 sandbox_strdup:353 <-hsearch_r:403 <-env_do_env_set:130 + 14a2aa30 90 sandbox_strdup:353 <-hsearch_r:403 <-env_do_env_set:130 + 2 leaked allocs + +Use ``malloc leak`` (without arguments) to check the count without +releasing the snapshot, so you can continue testing:: + + => malloc leak start + Heap snapshot: 974 allocs + => + => malloc leak + No leaks + => + => malloc leak + 3 new allocs + => malloc leak end + ... + **Practical workflow** 1. Run ``um t --leak-check -V dm`` (or another suite) to find leaky tests 2. Use the caller backtrace in the ``-L`` output to find the allocation site -3. If more detail is needed, add ``malloc_dump_to_file()`` calls or enable - ``malloc_log_start()`` to trace all allocations during the operation +3. If more detail is needed, use ``malloc leak start`` / ``malloc leak end`` + interactively, or enable ``malloc_log_start()`` to trace all allocations 4. Fix the leak and verify the test passes **Dumping heap state on exit** diff --git a/test/cmd/malloc.c b/test/cmd/malloc.c index 75e8afdec63..95809845dd7 100644 --- a/test/cmd/malloc.c +++ b/test/cmd/malloc.c @@ -54,6 +54,58 @@ static int cmd_test_malloc_dump(struct unit_test_state *uts) } CMD_TEST(cmd_test_malloc_dump, UTF_CONSOLE); +/* Test 'malloc leak' command using the C API directly */ +static int cmd_test_malloc_leak(struct unit_test_state *uts) +{ + struct malloc_leak_snap snap = {}; + ulong chunk_addr; + size_t chunk_sz; + void *ptr; + int ret; + + /* Take a snapshot, then check with no leaks */ + ut_assertok(malloc_leak_check_start(&snap)); + ut_assert(snap.count > 0); + ut_asserteq(0, malloc_leak_check_count(&snap)); + + /* Allocate something and check it is detected */ + ptr = malloc(0x42); + ut_assertnonnull(ptr); + ut_asserteq(1, malloc_leak_check_count(&snap)); + + /* Verify freeing clears the leak */ + free(ptr); + ut_asserteq(0, malloc_leak_check_count(&snap)); + + /* Re-allocate so end has something to print */ + ptr = malloc(0x42); + ut_assertnonnull(ptr); + + /* End should print the leaked allocation and free the snapshot */ + ret = malloc_leak_check_end(&snap); + ut_asserteq(1, ret); + + /* + * Check the output line shows the correct chunk address and chunk + * size. The chunk address is the malloc pointer minus the mcheck + * header. The caller name is only available with mcheck. + */ + chunk_addr = (ulong)ptr - malloc_mcheck_hdr_size(); + chunk_sz = malloc_chunk_size(ptr); + if (IS_ENABLED(CONFIG_MCHECK_HEAP_PROTECTION)) + ut_assert_nextlinen(" %lx %zx %s:", chunk_addr, chunk_sz, + __func__); + else + ut_assert_nextlinen(" %lx %zx ", chunk_addr, chunk_sz); + ut_assert_console_end(); + ut_assertnull(snap.addr); + + free(ptr); + + return 0; +} +CMD_TEST(cmd_test_malloc_leak, UTF_CONSOLE); + #if CONFIG_IS_ENABLED(MCHECK_LOG) /* Test 'malloc log' command */ static int cmd_test_malloc_log(struct unit_test_state *uts)