* [PATCH v2 0/4] talloc and memory leak debugging
@ 2025-11-07 14:41 Sascha Hauer
2025-11-07 14:41 ` [PATCH v2 1/4] lib: add talloc for overlaying a tree onto allocations Sascha Hauer
` (4 more replies)
0 siblings, 5 replies; 6+ messages in thread
From: Sascha Hauer @ 2025-11-07 14:41 UTC (permalink / raw)
To: BAREBOX; +Cc: Ahmad Fatoum
This picks up the talloc series previously sent by Ahmad. I also added
the memory leak debugging patch tooling for sandbox.
This series includes my review comments to v1.
Changes since v1:
- pass originally allocated pointer to malloc_usable_size()
- align talloc allocations to 8 bytes
- Use __lsan_do_recoverable_leak_check() instead of
__lsan_do_leak_check() to keep barebox running after memory leaks have
been detected
Signed-off-by: Sascha Hauer <s.hauer@pengutronix.de>
---
Ahmad Fatoum (4):
lib: add talloc for overlaying a tree onto allocations
test: self: add talloc selftest
hush: fix memory leaks
sandbox: add memory leak debugging tooling around LeakSanitizer
arch/sandbox/Makefile | 2 +-
arch/sandbox/os/libc_malloc.c | 9 +
commands/Kconfig | 9 +
commands/Makefile | 1 +
commands/checkleak.c | 52 ++++++
common/Kconfig.debug | 6 +
common/hush.c | 41 +++--
include/malloc.h | 6 +
include/talloc.h | 43 +++++
include/xfuncs.h | 6 +
lib/Makefile | 1 +
lib/talloc.c | 415 ++++++++++++++++++++++++++++++++++++++++++
lib/xfuncs.c | 20 ++
test/self/Kconfig | 6 +
test/self/Makefile | 1 +
test/self/talloc.c | 153 ++++++++++++++++
16 files changed, 754 insertions(+), 17 deletions(-)
---
base-commit: 594850b2de53300ae25c103e4ec88b2dac418106
change-id: 20251107-talloc-68c86806dc53
Best regards,
--
Sascha Hauer <s.hauer@pengutronix.de>
^ permalink raw reply [flat|nested] 6+ messages in thread
* [PATCH v2 1/4] lib: add talloc for overlaying a tree onto allocations
2025-11-07 14:41 [PATCH v2 0/4] talloc and memory leak debugging Sascha Hauer
@ 2025-11-07 14:41 ` Sascha Hauer
2025-11-07 14:41 ` [PATCH v2 2/4] test: self: add talloc selftest Sascha Hauer
` (3 subsequent siblings)
4 siblings, 0 replies; 6+ messages in thread
From: Sascha Hauer @ 2025-11-07 14:41 UTC (permalink / raw)
To: BAREBOX; +Cc: Ahmad Fatoum
From: Ahmad Fatoum <a.fatoum@barebox.org>
Talloc is the hierarchical allocator used by Samba, which keeps all
allocated buffers in a tree, where each child may not live longer than
its parent. When a parent is talloc_free'd, all its children are as
well. This can simplify memory management a great deal.
In barebox, this can also be used to implement devm_.
The implementation here is based on MIT-licensed:
https://github.com/esneider/talloc
But the API was adapted to the Samba3 talloc API, as described here:
https://linux.die.net/man/3/talloc
https://talloc.samba.org/talloc/doc/html/index.html
Support for destructors is planned, but not implemented yet.
Also, the TLSF allocator has two spare bits in each block's size:
One bit could be used to mark talloc buffers to prevent use of talloc_
family functions with non-talloc allocated buffers.
Signed-off-by: Ahmad Fatoum <a.fatoum@barebox.org>
Link: https://lore.barebox.org/20251027075438.2480311-1-a.fatoum@barebox.org
Signed-off-by: Sascha Hauer <s.hauer@pengutronix.de>
---
include/talloc.h | 43 ++++++
include/xfuncs.h | 6 +
lib/Makefile | 1 +
lib/talloc.c | 415 +++++++++++++++++++++++++++++++++++++++++++++++++++++++
lib/xfuncs.c | 20 +++
5 files changed, 485 insertions(+)
diff --git a/include/talloc.h b/include/talloc.h
new file mode 100644
index 0000000000000000000000000000000000000000..79a175bd5441cd9a34a3155fcb9119b8e20fb41e
--- /dev/null
+++ b/include/talloc.h
@@ -0,0 +1,43 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/**
+ * Talloc is a replacement for the standard memory allocation routines that
+ * provides structure aware allocations.
+ *
+ * @author Dario Sneidermanis
+ */
+
+#ifndef __TALLOC_H__
+#define __TALLOC_H__
+
+#include <linux/types.h>
+#include <linux/compiler_types.h>
+
+struct talloc {
+ struct talloc *child;
+ struct talloc *next;
+ union {
+ struct talloc *prev;
+ struct talloc *parent; /* Valid only when is_first(mem) */
+ };
+} __aligned(8); /* align to 8 byte to maintain malloc alignment */
+
+void mem_set_parent(struct talloc *child, struct talloc *parent);
+
+void *talloc_new(const void *parent);
+size_t talloc_usable_size(void *mem);
+void *talloc_size(const void *parent, size_t size);
+void *talloc_zero_size(const void *parent, size_t size);
+void *talloc_realloc_size(const void *parent, void *mem, size_t size);
+void talloc_free(void *mem);
+
+char *talloc_strdup(const void *parent, const char *str);
+const char *talloc_strdup_const(const void *parent, const char *str);
+void talloc_free_const(const void *ptr);
+
+void *talloc_parent(const void *mem);
+void talloc_steal(const void *parent, void *mem);
+void talloc_steal_children(const void *parent, void *mem);
+
+size_t talloc_total_blocks(const void *mem);
+
+#endif /* __TALLOC_H__ */
diff --git a/include/xfuncs.h b/include/xfuncs.h
index 2cc2048bd58338c4b341860dc0cb09eefaa7936c..d567f7416f39a827b6a4875c9c348a5d7985a2d8 100644
--- a/include/xfuncs.h
+++ b/include/xfuncs.h
@@ -26,6 +26,9 @@ wchar_t *xstrdup_wchar(const wchar_t *src);
wchar_t *xstrdup_char_to_wchar(const char *src);
char *xstrdup_wchar_to_char(const wchar_t *src);
+void *xtalloc_size(const void *parent, size_t size) __xalloc_size(2);
+void *xtalloc_zero_size(const void *parent, size_t size) __xalloc_size(2);
+
#else
static inline void *xmalloc(size_t size) { BUG(); }
@@ -43,6 +46,9 @@ static inline __printf(2, 3) char *xrasprintf(char *str, const char *fmt, ...) {
static inline wchar_t *xstrdup_wchar(const wchar_t *src) { BUG(); }
static inline wchar_t *xstrdup_char_to_wchar(const char *src) { BUG(); }
static inline char *xstrdup_wchar_to_char(const wchar_t *src) { BUG(); }
+
+static inline void *xtalloc_size(const void *paent, size_t size) { BUG(); }
+static inline void *xtalloc_zero_size(const void *paent, size_t size) { BUG(); }
#endif
#endif /* __XFUNCS_H */
diff --git a/lib/Makefile b/lib/Makefile
index e9f152b21dad3f521cb9dbbf88d6012fdee19c3c..9ab4cad0359c0e8e5ad0c0785d8f45afc9c1d00e 100644
--- a/lib/Makefile
+++ b/lib/Makefile
@@ -12,6 +12,7 @@ obj-$(CONFIG_VERSION_CMP) += strverscmp.o
obj-y += strtox.o
obj-y += kstrtox.o
obj-y += vsprintf.o
+obj-y += talloc.o
obj-$(CONFIG_KASAN) += kasan/
obj-pbl-$(CONFIG_STACKPROTECTOR) += stackprot.o
pbl-$(CONFIG_PBL_CONSOLE) += vsprintf.o
diff --git a/lib/talloc.c b/lib/talloc.c
new file mode 100644
index 0000000000000000000000000000000000000000..a1a7f8f0adc86fead4fa8a5465632ca061ef3ed3
--- /dev/null
+++ b/lib/talloc.c
@@ -0,0 +1,415 @@
+// SPDX-License-Identifier: GPL-2.0-only OR MIT
+/*
+ * Talloc is a replacement for the standard memory allocation routines that
+ * provides structure aware allocations.
+ *
+ * @author Dario Sneidermanis
+ *
+ * Each chunk of talloc'ed memory has a header of the following form:
+ *
+ * +---------+---------+---------+--------···
+ * | first | next | prev | memory
+ * | child | sibling | sibling | chunk
+ * +---------+---------+---------+--------···
+ *
+ * Thus, a talloc hierarchy tree would look like this:
+ *
+ * NULL <-- chunk --> NULL
+ * ^
+ * |
+ * +-> chunk <--> chunk <--> chunk --> NULL
+ * | | ^
+ * v v |
+ * NULL NULL +-> chunk <--> chunk --> NULL
+ * | |
+ * v v
+ * NULL NULL
+ */
+
+#include <stdlib.h>
+#include <string.h>
+#include <linux/bug.h>
+#include <linux/export.h>
+#include <linux/container_of.h>
+#include <asm/sections.h>
+#include "talloc.h"
+
+// TODO difference between set_parent and steal?
+// TODO use a bit from TLSF for extra safety to differentiate talloc
+// TODO allow leaf nodes (that have no children to be destructors!!)
+#define is_root(hdr) (!(hdr)->prev)
+#define is_first(hdr) ((hdr)->prev->next != (hdr))
+
+static inline void *hdr2mem(struct talloc *hdr)
+{
+ return hdr ? &hdr[1] : NULL;
+}
+
+static inline struct talloc *mem2hdr(const void *mem)
+{
+ return mem ? (void *)mem - sizeof(struct talloc) : NULL;
+}
+
+/**
+ * talloc_ctx_init() - Initialize a raw chunk of memory.
+ *
+ * @hdr: pointer to freshly allocated memory chunk.
+ * @parent: pointer to previously talloc'ed memory chunk from which this
+ * chunk depends, or NULL.
+ *
+ * Return: pointer to the allocated memory chunk, or NULL if there was an error.
+ */
+static void *talloc_ctx_init(struct talloc *hdr, const void *parent)
+{
+ void *mem;
+
+ if (!hdr)
+ return NULL;
+
+ memset(hdr, 0, sizeof(*hdr));
+
+ mem = hdr2mem(hdr);
+ talloc_steal(parent, mem);
+ return mem;
+}
+
+/**
+ * talloc_usable_size() - Report the size of the tallocation
+ *
+ * @mem: pointer to previously talloc'ed memory chunk.
+ *
+ * Return: size of tallocation
+ */
+size_t talloc_usable_size(void *mem)
+{
+ struct talloc *hdr = mem2hdr(mem);
+
+ return malloc_usable_size(hdr) - sizeof(struct talloc);
+}
+EXPORT_SYMBOL(talloc_usable_size);
+
+/**
+ * talloc_new() - Allocate a talloc object without user data
+ *
+ * @parent: pointer to previously talloc'ed memory chunk from which this
+ * chunk depends, or NULL.
+ *
+ * Return: pointer to the allocated object, or NULL if there was an error.
+ */
+void *talloc_new(const void *parent)
+{
+ return talloc_ctx_init(malloc(sizeof(struct talloc)), parent);
+}
+EXPORT_SYMBOL(talloc_new);
+
+/**
+ * talloc_size() - Allocate a (contiguous) memory chunk.
+ *
+ * @parent: pointer to previously talloc'ed memory chunk from which this
+ * chunk depends, or NULL.
+ * @size: amount of memory requested (in bytes).
+ *
+ * Return: pointer to the allocated memory chunk, or NULL if there was an error.
+ */
+void *talloc_size(const void *parent, size_t size)
+{
+ if (!size)
+ return ZERO_SIZE_PTR;
+
+ return talloc_ctx_init(malloc(size + sizeof(struct talloc)), parent);
+}
+EXPORT_SYMBOL(talloc_size);
+
+/**
+ * talloc_strdup() - Duplicate a string
+ *
+ * @parent: pointer to previously talloc'ed memory chunk from which this
+ * chunk depends, or NULL.
+ * @str: string to duplicate
+ *
+ * Return: pointer to the duplicated string, or NULL if there was an error.
+ */
+char *talloc_strdup(const void *parent, const char *str)
+{
+ size_t len = strlen(str) + 1;
+ void *usr;
+
+ usr = talloc_size(parent, len);
+ if (!usr)
+ return NULL;
+
+ return memcpy(usr, str, len);
+}
+EXPORT_SYMBOL(talloc_strdup);
+
+/**
+ * talloc_strdup_const() - Duplicate a string if not read-only
+ *
+ * @parent: pointer to previously talloc'ed memory chunk from which this
+ * chunk depends, or NULL.
+ * @size: amount of memory requested (in bytes).
+ *
+ * Return: pointer to the allocated memory chunk, or NULL if there was an error.
+ */
+const char *talloc_strdup_const(const void *parent, const char *str)
+{
+ if (is_barebox_rodata((ulong)str))
+ return str;
+
+ return talloc_strdup(parent, str);
+}
+EXPORT_SYMBOL(talloc_strdup_const);
+
+/**
+ * tzalloc() - Allocate a zeroed (contiguous) memory chunk.
+ *
+ * @parent: pointer to previously talloc'ed memory chunk from which this
+ * chunk depends, or NULL.
+ * @size: amount of memory requested (in bytes).
+ *
+ * Return: pointer to the allocated memory chunk, or NULL if there was an error.
+ */
+void *talloc_zero_size(const void *parent, size_t size)
+{
+ if (!size)
+ return ZERO_SIZE_PTR;
+
+ return talloc_ctx_init(calloc(1, size + sizeof(struct talloc)), parent);
+}
+EXPORT_SYMBOL(talloc_zero_size);
+
+/**
+ * trealloc() - Modify the size of a talloc'ed memory chunk.
+ *
+ * @parent: parent to set if mem is NULL.
+ * @mem: pointer to previously talloc'ed memory chunk.
+ * @size: amount of memory requested (in bytes).
+ *
+ * Return: pointer to the allocated memory chunk, or NULL if there was an error.
+ */
+void *talloc_realloc_size(const void *parent, void *mem, size_t size)
+{
+ struct talloc *oldhdr, *hdr;
+ void *newmem;
+
+ if (!size) {
+ talloc_free(mem);
+ return ZERO_SIZE_PTR;
+ }
+ if (ZERO_OR_NULL_PTR(mem))
+ mem = NULL;
+
+ oldhdr = mem2hdr(mem);
+
+ newmem = realloc(oldhdr, size + sizeof(*oldhdr));
+ if (!oldhdr || !newmem)
+ return talloc_ctx_init(newmem, parent);
+
+ if (oldhdr == newmem)
+ return mem;
+
+ /* Buffer start address changed, update all references. */
+ hdr = newmem;
+ mem = hdr2mem(hdr);
+
+ if (hdr->child)
+ hdr->child->parent = hdr;
+
+ if (!is_root(hdr)) {
+ if (hdr->next)
+ hdr->next->prev = hdr;
+
+ if (hdr->prev->next == oldhdr)
+ hdr->prev->next = hdr;
+ if (hdr->parent->child == oldhdr)
+ hdr->parent->child = hdr;
+ }
+
+ return mem;
+}
+EXPORT_SYMBOL(talloc_realloc_size);
+
+/**
+ * __tfree() - Deallocate all the descendants of given parent recursively.
+ *
+ * @hdr: pointer to previously talloc'ed memory chunk.
+ */
+static void __tfree(struct talloc *hdr)
+{
+ if (ZERO_OR_NULL_PTR(hdr))
+ return;
+
+ /* Fail if the tree hierarchy has cycles. */
+
+ ASSERT(hdr->prev);
+ hdr->prev = NULL;
+
+ __tfree(hdr->child);
+ __tfree(hdr->next);
+ free(hdr);
+}
+
+/**
+ * talloc_free() - Deallocate a talloc'ed memory chunk and all the chunks depending on it.
+ *
+ * @mem: pointer to previously talloc'ed memory chunk.
+ */
+void talloc_free(void *mem)
+{
+ struct talloc *hdr = mem2hdr(mem);
+
+ if (ZERO_OR_NULL_PTR(mem))
+ return;
+
+ talloc_steal(NULL, mem);
+
+ __tfree(hdr->child);
+
+ free(hdr);
+}
+EXPORT_SYMBOL(talloc_free);
+
+/**
+ * talloc_free_const() - call talloc_free, unless read/only memory
+ *
+ * @mem: pointer to previously talloc'ed memory chunk.
+ */
+void talloc_free_const(const void *mem)
+{
+ if (is_barebox_rodata((ulong)mem))
+ return;
+
+ talloc_free((void *)mem);
+}
+EXPORT_SYMBOL(talloc_free_const);
+
+/**
+ * talloc_parent() - Get the parent of a talloc'ed memory chunk
+ *
+ * @mem: pointer to previously talloc'ed memory chunk.
+ *
+ * Return: pointer to the parent memory chunk it depends on (could be NULL).
+ */
+void *talloc_parent(const void *mem)
+{
+ struct talloc *hdr = mem2hdr(mem);
+
+ if (ZERO_OR_NULL_PTR(hdr) || is_root(hdr))
+ return NULL;
+
+ while (!is_first(hdr))
+ hdr = hdr->prev;
+
+ return hdr2mem(hdr->parent);
+}
+EXPORT_SYMBOL(talloc_parent);
+
+void mem_set_parent(struct talloc *hdr, struct talloc *parent_hdr)
+{
+ if (ZERO_OR_NULL_PTR(hdr))
+ return;
+
+ if (!is_root(hdr)) {
+ /* Remove node from old tree. */
+ if (hdr->next)
+ hdr->next->prev = hdr->prev;
+
+ if (!is_first(hdr))
+ hdr->prev->next = hdr->next;
+ else
+ hdr->parent->child = hdr->next;
+ }
+
+ hdr->next = hdr->prev = NULL;
+
+ if (parent_hdr) {
+ /* Insert node into new tree. */
+ if (parent_hdr->child) {
+ hdr->next = parent_hdr->child;
+ parent_hdr->child->prev = hdr;
+ }
+
+ hdr->parent = parent_hdr;
+ parent_hdr->child = hdr;
+ }
+}
+EXPORT_SYMBOL(mem_set_parent);
+
+/**
+ * talloc_steal() - Reparent talloc'ed memory chunk
+ *
+ * @parent: pointer to previously talloc'ed memory chunk from which this
+ * chunk depends, or NULL.
+ * @mem: pointer to previously talloc'ed memory chunk.
+ *
+ * Change the parent of a talloc'ed memory chunk. This will affect the
+ * dependencies of the entire subtree rooted at the given chunk.
+ */
+void talloc_steal(const void *parent, void *mem)
+{
+ struct talloc *hdr = mem2hdr(mem);
+ struct talloc *parent_hdr = mem2hdr(parent);
+
+ mem_set_parent(hdr, parent_hdr);
+}
+EXPORT_SYMBOL(talloc_steal);
+
+/**
+ * talloc_steal_children() - Remove a talloc'ed memory chunk from the dependency tree
+ *
+ * @parent: pointer to previously talloc'ed memory chunk from which this
+ * chunk's children will depend, or NULL.
+ * @mem: pointer to previously talloc'ed memory chunk.
+ *
+ * Removing a talloc'ed memory chunk from the dependency tree takes care of its
+ * children (they will depend on the specified parent).
+ */
+void talloc_steal_children(const void *parent, void *mem)
+{
+ struct talloc *hdr = mem2hdr(mem);
+ struct talloc *parent_hdr = mem2hdr(parent);
+
+ if (ZERO_OR_NULL_PTR(hdr))
+ return;
+
+ talloc_steal(NULL, mem);
+
+ if (!hdr->child)
+ return;
+
+ if (parent_hdr) {
+ /* Insert mem children in front of the list of parent children. */
+ if (parent_hdr->child) {
+ struct talloc *last = hdr->child;
+
+ while (last->next)
+ last = last->next;
+
+ parent_hdr->child->prev = last;
+ last->next = parent_hdr->child;
+ }
+
+ parent_hdr->child = hdr->child;
+ }
+
+ hdr->child->parent = parent_hdr;
+ hdr->child = NULL;
+}
+EXPORT_SYMBOL(talloc_steal_children);
+
+size_t talloc_total_blocks(const void *mem)
+{
+ struct talloc *hdr = mem2hdr(mem);
+ size_t nblocks = 0;
+
+ if (ZERO_OR_NULL_PTR(hdr))
+ return 0;
+
+ nblocks++;
+
+ for (hdr = hdr->child; hdr; hdr = hdr->next)
+ nblocks++;
+
+ return nblocks;
+}
+EXPORT_SYMBOL(talloc_total_blocks);
diff --git a/lib/xfuncs.c b/lib/xfuncs.c
index 4c694513050ee3fc2f9cac4e3249a2ff5d3672d9..c34605072411535cce18b158515f963fd1ce15c9 100644
--- a/lib/xfuncs.c
+++ b/lib/xfuncs.c
@@ -19,6 +19,7 @@
#include <common.h>
#include <malloc.h>
+#include <talloc.h>
#include <module.h>
#include <wchar.h>
@@ -44,6 +45,17 @@ void *xmalloc(size_t size)
}
EXPORT_SYMBOL(xmalloc);
+void *xtalloc_size(const void *parent, size_t size)
+{
+ void *p = NULL;
+
+ if (!(p = talloc_size(parent, size)))
+ enomem_panic(size);
+
+ return p;
+}
+EXPORT_SYMBOL(xtalloc_size);
+
void *xrealloc(void *ptr, size_t size)
{
void *p = NULL;
@@ -63,6 +75,14 @@ void *xzalloc(size_t size)
}
EXPORT_SYMBOL(xzalloc);
+void *xtalloc_zero_size(const void *parent, size_t size)
+{
+ void *ptr = xtalloc_size(parent, size);
+ memset(ptr, 0, size);
+ return ptr;
+}
+EXPORT_SYMBOL(xtalloc_zero_size);
+
char *xstrdup(const char *s)
{
char *p;
--
2.47.3
^ permalink raw reply [flat|nested] 6+ messages in thread
* [PATCH v2 2/4] test: self: add talloc selftest
2025-11-07 14:41 [PATCH v2 0/4] talloc and memory leak debugging Sascha Hauer
2025-11-07 14:41 ` [PATCH v2 1/4] lib: add talloc for overlaying a tree onto allocations Sascha Hauer
@ 2025-11-07 14:41 ` Sascha Hauer
2025-11-07 14:41 ` [PATCH v2 3/4] hush: fix memory leaks Sascha Hauer
` (2 subsequent siblings)
4 siblings, 0 replies; 6+ messages in thread
From: Sascha Hauer @ 2025-11-07 14:41 UTC (permalink / raw)
To: BAREBOX; +Cc: Ahmad Fatoum
From: Ahmad Fatoum <a.fatoum@barebox.org>
This self test exercises the hierarchical properties of talloc.
Signed-off-by: Ahmad Fatoum <a.fatoum@barebox.org>
Link: https://lore.barebox.org/20251027075438.2480311-2-a.fatoum@barebox.org
Signed-off-by: Sascha Hauer <s.hauer@pengutronix.de>
---
test/self/Kconfig | 6 +++
test/self/Makefile | 1 +
test/self/talloc.c | 153 +++++++++++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 160 insertions(+)
diff --git a/test/self/Kconfig b/test/self/Kconfig
index f46253afc034486833da5696e9df76294f2504d3..936b12072e60fc8d946ecd488af20eae8e8aa950 100644
--- a/test/self/Kconfig
+++ b/test/self/Kconfig
@@ -48,6 +48,7 @@ config SELFTEST_ENABLE_ALL
select SELFTEST_IDR
select SELFTEST_TLV
select SELFTEST_DM
+ select SELFTEST_TALLOC
help
Selects all self-tests compatible with current configuration
@@ -61,6 +62,11 @@ config SELFTEST_MALLOC
help
Tests barebox memory allocator
+config SELFTEST_TALLOC
+ bool "talloc() selftest"
+ help
+ Tests barebox talloc allocator
+
config SELFTEST_PRINTF
bool "printf selftest"
help
diff --git a/test/self/Makefile b/test/self/Makefile
index 124607b375d84a59a1e650eb2d39ac533e4a3ff1..0bd947928a50f1fb0620bb9cc8c2e0d7c846237f 100644
--- a/test/self/Makefile
+++ b/test/self/Makefile
@@ -3,6 +3,7 @@
obj-$(CONFIG_SELFTEST) += core.o
obj-$(CONFIG_SELFTEST_RANGE) += range.o
obj-$(CONFIG_SELFTEST_MALLOC) += malloc.o
+obj-$(CONFIG_SELFTEST_TALLOC) += talloc.o
obj-$(CONFIG_SELFTEST_PRINTF) += printf.o
CFLAGS_printf.o += -Wno-format-security -Wno-format
obj-$(CONFIG_SELFTEST_PROGRESS_NOTIFIER) += progress-notifier.o
diff --git a/test/self/talloc.c b/test/self/talloc.c
new file mode 100644
index 0000000000000000000000000000000000000000..1322269af498eef3b64cfe4226b82d39bbd853b8
--- /dev/null
+++ b/test/self/talloc.c
@@ -0,0 +1,153 @@
+// SPDX-License-Identifier: GPL-2.0-only
+
+#define pr_fmt(fmt) "talloc: " fmt
+
+#include <common.h>
+#include <stdlib.h>
+#include <bselftest.h>
+#include <talloc.h>
+#include <linux/sizes.h>
+#include <linux/bitops.h>
+
+BSELFTEST_GLOBALS();
+
+static bool __selftest_check(bool cond, const char *func, int line)
+{
+ total_tests ++;
+ if (cond)
+ return true;
+
+ pr_err("assertion failure at %s:%d\n", func, line);
+
+ failed_tests++;
+ return false;
+}
+#define selftest_check(cond) __selftest_check((cond), __func__, __LINE__)
+
+#define STRESS_ALLOCS 1024
+#define STRESS_ITERS 5000
+
+static void talloc_basic_tests(void)
+{
+ void *root, *a, *b, *c;
+ void *tmp;
+
+ root = talloc_size(NULL, 0);
+ selftest_check(root == ZERO_SIZE_PTR);
+
+ root = talloc_new(NULL);
+ if (!selftest_check(!ZERO_OR_NULL_PTR(root)))
+ return;
+
+ a = talloc_size(root, 128);
+ selftest_check(a);
+
+ selftest_check(talloc_parent(a) == root);
+
+ b = talloc_zero_size(root, 256);
+ selftest_check(b && memchr_inv(b, 0x00, 256) == NULL);
+
+ c = talloc_strdup(root, "barebox talloc test");
+ selftest_check(c);
+
+ tmp = talloc_realloc_size(talloc_parent(a), a, 512);
+ if (selftest_check(tmp))
+ a = tmp;
+
+ selftest_check(talloc_parent(a) == root);
+
+ selftest_check(talloc_total_blocks(root) == 4);
+
+ /* Free child and verify parent is intact */
+ talloc_free(a);
+ selftest_check(talloc_total_blocks(root) == 3);
+
+ talloc_free(b);
+ selftest_check(talloc_total_blocks(root) == 2);
+
+ talloc_free(c);
+ selftest_check(talloc_total_blocks(root) == 1);
+
+ talloc_free(root);
+}
+
+static void talloc_hierarchy_tests(void)
+{
+ void *root = talloc_new(NULL);
+ void *lvl1, *lvl2, *lvl3;
+
+ lvl1 = talloc_new(root);
+ lvl2 = talloc_new(lvl1);
+ lvl3 = talloc_size(lvl2, 64);
+
+ selftest_check(lvl1 && lvl2 && lvl3);
+
+ selftest_check(talloc_parent(lvl3) == lvl2);
+
+ talloc_free(lvl1); /* should free all children */
+ selftest_check(talloc_total_blocks(root) == 1);
+
+ talloc_free(root);
+}
+
+static void talloc_stress_tests(void)
+{
+ void *root = talloc_new(NULL);
+ void *ptrs[STRESS_ALLOCS];
+
+ memset(ptrs, 0, sizeof(ptrs));
+
+ for (int iter = 0; iter < STRESS_ITERS; iter++) {
+ int idx = prandom_u32_max(STRESS_ALLOCS);
+ if (ptrs[idx]) {
+ talloc_free(ptrs[idx]);
+ ptrs[idx] = NULL;
+ } else {
+ size_t sz = prandom_u32_max(4096) + 1;
+ ptrs[idx] = talloc_size(root, sz);
+ if (selftest_check(ptrs[idx]) &&
+ prandom_u32_max(8) == 0) {
+ /* Occasionally realloc to a different size */
+ size_t new_sz = prandom_u32_max(8192) + 1;
+ void *tmp = talloc_realloc_size(NULL,
+ ptrs[idx], new_sz);
+ if (selftest_check(tmp))
+ ptrs[idx] = tmp;
+ }
+ }
+ }
+
+ for (int i = 0; i < STRESS_ALLOCS; i++) {
+ if (ptrs[i])
+ talloc_free(ptrs[i]);
+ }
+ talloc_free(root);
+}
+
+static void talloc_corner_cases(void)
+{
+ void *root = talloc_new(NULL);
+ void *large;
+
+ talloc_free(NULL);
+
+ void *realloc_null = talloc_realloc_size(NULL, NULL, 128);
+ selftest_check(realloc_null);
+ talloc_free(realloc_null);
+
+ /* Large alloc test (may fail gracefully if memory-constrained) */
+ large = talloc_size(root, 1024 * 1024 * 10);
+ if (selftest_check(large))
+ talloc_free(large);
+
+ talloc_free(root);
+}
+
+static void test_talloc(void)
+{
+ talloc_basic_tests();
+ talloc_hierarchy_tests();
+ talloc_stress_tests();
+ talloc_corner_cases();
+}
+bselftest(core, test_talloc);
--
2.47.3
^ permalink raw reply [flat|nested] 6+ messages in thread
* [PATCH v2 3/4] hush: fix memory leaks
2025-11-07 14:41 [PATCH v2 0/4] talloc and memory leak debugging Sascha Hauer
2025-11-07 14:41 ` [PATCH v2 1/4] lib: add talloc for overlaying a tree onto allocations Sascha Hauer
2025-11-07 14:41 ` [PATCH v2 2/4] test: self: add talloc selftest Sascha Hauer
@ 2025-11-07 14:41 ` Sascha Hauer
2025-11-07 14:41 ` [PATCH v2 4/4] sandbox: add memory leak debugging tooling around LeakSanitizer Sascha Hauer
2025-11-10 8:12 ` [PATCH v2 0/4] talloc and memory leak debugging Sascha Hauer
4 siblings, 0 replies; 6+ messages in thread
From: Sascha Hauer @ 2025-11-07 14:41 UTC (permalink / raw)
To: BAREBOX; +Cc: Ahmad Fatoum
From: Ahmad Fatoum <a.fatoum@barebox.org>
The memory leaks in hush have been bothered me for years and every time
I look into it, I give up after a while, because the code is convoluted.
Talloc shines here as we can just allocate a "zero-size" talloc object
as parent for a context and associate all allocations from that context
with it.
Then when that context is free'd all children will be freed as well.
Signed-off-by: Ahmad Fatoum <a.fatoum@barebox.org>
Link: https://lore.barebox.org/20251027075438.2480311-3-a.fatoum@barebox.org
Signed-off-by: Sascha Hauer <s.hauer@pengutronix.de>
---
common/hush.c | 41 +++++++++++++++++++++++++----------------
1 file changed, 25 insertions(+), 16 deletions(-)
diff --git a/common/hush.c b/common/hush.c
index ec3c0cd91320706afa2306c1ab224ecbbc0474e2..2e0cc4229d3575dcf045f68ef8db3e943c41eedb 100644
--- a/common/hush.c
+++ b/common/hush.c
@@ -97,6 +97,7 @@
#define pr_fmt(fmt) "hush: " fmt
+#include <talloc.h>
#include <malloc.h> /* malloc, free, realloc*/
#include <xfuncs.h>
#include <linux/ctype.h> /* isalpha, isdigit */
@@ -180,6 +181,7 @@ struct option {
/* This holds pointers to the various results of parsing */
struct p_context {
+ const void *scope;
struct child_prog *child;
struct pipe *list_head;
struct pipe *pipe;
@@ -308,7 +310,7 @@ static int run_pipe_real(struct p_context *ctx, struct pipe *pi);
/* variable assignment: */
static int is_assignment(const char *s);
/* data structure manipulation: */
-static void initialize_context(struct p_context *ctx);
+static void initialize_context(struct p_context *ctx, bool newscope);
static void release_context(struct p_context *ctx);
static int done_word(o_string *dest, struct p_context *ctx);
static int done_command(struct p_context *ctx);
@@ -435,7 +437,7 @@ static char *getprompt(void)
if (prompt_command) {
unsigned int lr = last_return_code;
- initialize_context(&ctx);
+ initialize_context(&ctx, false);
parse_string_outer(&ctx, prompt_command, FLAG_PARSE_SEMICOLON);
release_context(&ctx);
@@ -837,7 +839,7 @@ static int run_pipe_real(struct p_context *ctx, struct pipe *pi)
char * str = NULL;
struct p_context ctx1;
- initialize_context(&ctx1);
+ initialize_context(&ctx1, true);
str = make_string((child->argv + i));
rcode = parse_string_outer(&ctx1, str, FLAG_EXIT_FROM_LOOP | FLAG_REPARSING);
@@ -1062,7 +1064,8 @@ static int free_pipe(struct pipe *pi, int indent)
}
}
- free(pi->progs); /* children are an array, they get freed all at once */
+ /* children are an array, they get freed all at once */
+ talloc_free(pi->progs);
pi->progs = NULL;
return ret_code;
@@ -1079,7 +1082,7 @@ static int free_pipe_list(struct pipe *head, int indent)
final_printf("%s pipe followup code %d\n", indenter(indent), pi->followup);
next = pi->next;
pi->next = NULL;
- free(pi);
+ talloc_free(pi);
}
return rcode;
}
@@ -1182,16 +1185,18 @@ static int is_assignment(const char *s)
}
-static struct pipe *new_pipe(void)
+static struct pipe *new_pipe(struct p_context *ctx)
{
- return xzalloc(sizeof(struct pipe));
+ return xtalloc_zero_size(ctx->scope, sizeof(struct pipe));
}
-static void initialize_context(struct p_context *ctx)
+static void initialize_context(struct p_context *ctx, bool newscope)
{
+ if (newscope)
+ ctx->scope = talloc_new(NULL);
ctx->pipe = NULL;
ctx->child = NULL;
- ctx->list_head = new_pipe();
+ ctx->list_head = new_pipe(ctx);
ctx->pipe = ctx->list_head;
ctx->w = RES_NONE;
ctx->stack = NULL;
@@ -1211,6 +1216,7 @@ static void release_context(struct p_context *ctx)
free(opt);
}
#endif
+ talloc_free((void *)ctx->scope);
}
/* normal return is 0
@@ -1269,7 +1275,7 @@ static int reserved_word(o_string *dest, struct p_context *ctx)
return 1;
}
*new = *ctx; /* physical copy */
- initialize_context(ctx);
+ initialize_context(ctx, false);
ctx->stack = new;
} else if (ctx->w == RES_NONE || !(ctx->old_flag & (1 << r->code))) {
syntax_unexpected_token(r->literal);
@@ -1366,7 +1372,10 @@ static int done_command(struct p_context *ctx)
} else {
hush_debug("%s: initializing\n", __func__);
}
- pi->progs = xrealloc(pi->progs, sizeof(*pi->progs) * (pi->num_progs + 1));
+ pi->progs = talloc_realloc_size(ctx->scope, pi->progs,
+ sizeof(*pi->progs) * (pi->num_progs + 1));
+ if (!pi->progs)
+ panic("enomem");
prog = pi->progs + pi->num_progs;
prog->glob_result.gl_pathv = NULL;
@@ -1392,7 +1401,7 @@ static int done_pipe(struct p_context *ctx, pipe_style type)
ctx->pipe->followup = type;
ctx->pipe->r_mode = ctx->w;
- new_p = new_pipe();
+ new_p = new_pipe(ctx);
ctx->pipe->next = new_p;
ctx->pipe = new_p;
@@ -1705,7 +1714,7 @@ char *shell_expand(char *str)
o.quote = 1;
- initialize_context(&ctx);
+ initialize_context(&ctx, false);
parse_string(&o, &ctx, str);
@@ -1732,7 +1741,7 @@ static int parse_stream_outer(struct p_context *ctx, struct in_str *inp, int fla
do {
ctx->type = flag;
- initialize_context(ctx);
+ initialize_context(ctx, false);
update_ifs_map();
if (!(flag & FLAG_PARSE_SEMICOLON) || (flag & FLAG_REPARSING))
@@ -1921,7 +1930,7 @@ int run_command(const char *cmd)
if (!IS_ALLOWED(SCONFIG_SHELL))
return -EPERM;
- initialize_context(&ctx);
+ initialize_context(&ctx, true);
ret = parse_string_outer(&ctx, cmd, FLAG_PARSE_SEMICOLON);
release_context(&ctx);
@@ -1949,7 +1958,7 @@ static int source_script(const char *path, int argc, char *argv[])
char *script;
int ret;
- initialize_context(&ctx);
+ initialize_context(&ctx, true);
ctx.global_argc = argc;
ctx.global_argv = argv;
--
2.47.3
^ permalink raw reply [flat|nested] 6+ messages in thread
* [PATCH v2 4/4] sandbox: add memory leak debugging tooling around LeakSanitizer
2025-11-07 14:41 [PATCH v2 0/4] talloc and memory leak debugging Sascha Hauer
` (2 preceding siblings ...)
2025-11-07 14:41 ` [PATCH v2 3/4] hush: fix memory leaks Sascha Hauer
@ 2025-11-07 14:41 ` Sascha Hauer
2025-11-10 8:12 ` [PATCH v2 0/4] talloc and memory leak debugging Sascha Hauer
4 siblings, 0 replies; 6+ messages in thread
From: Sascha Hauer @ 2025-11-07 14:41 UTC (permalink / raw)
To: BAREBOX; +Cc: Ahmad Fatoum
From: Ahmad Fatoum <a.fatoum@barebox.org>
When enabled, this allows calling barebox_memleak_check() or running the
checkleak command to instruct LeakSanitizer to sweep the memory and find
unreferenced allocations.
LeakSanitizier is also enabled along AddressSanitizer and runs on AMD64
Linux automatically on exit already.
Signed-off-by: Ahmad Fatoum <a.fatoum@barebox.org>
Link: https://lore.barebox.org/20251027074446.2474869-1-a.fatoum@barebox.org
Signed-off-by: Sascha Hauer <s.hauer@pengutronix.de>
---
arch/sandbox/Makefile | 2 +-
arch/sandbox/os/libc_malloc.c | 9 ++++++++
commands/Kconfig | 9 ++++++++
commands/Makefile | 1 +
commands/checkleak.c | 52 +++++++++++++++++++++++++++++++++++++++++++
common/Kconfig.debug | 6 +++++
include/malloc.h | 6 +++++
7 files changed, 84 insertions(+), 1 deletion(-)
diff --git a/arch/sandbox/Makefile b/arch/sandbox/Makefile
index 0318c8dd145294f21e88e9496d2a60ea0a94f436..be6412a23f8ae19f83d9102536e6cf8c53e75c87 100644
--- a/arch/sandbox/Makefile
+++ b/arch/sandbox/Makefile
@@ -36,7 +36,7 @@ TEXT_BASE = $(CONFIG_TEXT_BASE)
SANDBOX_PBL2PROPER_GLUE_SYMS := \
putchar errno setjmp longjmp \
- malloc_stats memalign malloc free realloc calloc brk sbrk
+ malloc_stats memalign malloc free realloc calloc memleak_check brk sbrk
KBUILD_CFLAGS += $(foreach s,$(SANDBOX_PBL2PROPER_GLUE_SYMS),-D$(s)=barebox_$(s))
diff --git a/arch/sandbox/os/libc_malloc.c b/arch/sandbox/os/libc_malloc.c
index bb4fb1c9ead4a4c6aacedb06349ad96a2463b133..24d2da3cb2c1af2dd91c4e7e8abe3f8ed3a43c57 100644
--- a/arch/sandbox/os/libc_malloc.c
+++ b/arch/sandbox/os/libc_malloc.c
@@ -98,3 +98,12 @@ void *barebox_calloc(size_t n, size_t elem_size)
return mem;
}
+
+#ifdef CONFIG_DEBUG_MEMLEAK
+void barebox_memleak_check(void)
+{
+ void __lsan_do_recoverable_leak_check(void);
+
+ __lsan_do_recoverable_leak_check();
+}
+#endif
diff --git a/commands/Kconfig b/commands/Kconfig
index 78b1e69dd38ef98ffd6a1863c3600fc79173e6cf..c7c03a65477b4f083c256dc55053aab8c8ad2741 100644
--- a/commands/Kconfig
+++ b/commands/Kconfig
@@ -210,6 +210,15 @@ config CMD_MEMINFO
system bytes = 282616
in use bytes = 274752
+config CMD_CHECKLEAK
+ tristate
+ prompt "checkleak"
+ depends on DEBUG_MEMLEAK
+ default y
+ help
+ List memory leaks encountered since the last time
+ the command ran.
+
config CMD_ARM_MMUINFO
bool "mmuinfo command"
depends on CPU_V7 || CPU_V8
diff --git a/commands/Makefile b/commands/Makefile
index 858e0c257eba9bb3cf9bea1d2446be59bdd3a92e..8fffac8fd4428d275f86c5ad9898fed96c5b18ac 100644
--- a/commands/Makefile
+++ b/commands/Makefile
@@ -47,6 +47,7 @@ obj-$(CONFIG_CMD_TRUNCATE) += truncate.o
obj-$(CONFIG_CMD_SYNC) += sync.o
obj-$(CONFIG_CMD_FLASH) += flash.o
obj-$(CONFIG_CMD_MEMINFO) += meminfo.o
+obj-$(CONFIG_CMD_CHECKLEAK) += checkleak.o
obj-$(CONFIG_CMD_TIMEOUT) += timeout.o
obj-$(CONFIG_CMD_READLINE) += readline.o
obj-$(CONFIG_CMD_SETENV) += setenv.o
diff --git a/commands/checkleak.c b/commands/checkleak.c
new file mode 100644
index 0000000000000000000000000000000000000000..4254ff51d95dfcaeb8ce7e5aa2961017a2d86c4f
--- /dev/null
+++ b/commands/checkleak.c
@@ -0,0 +1,52 @@
+// SPDX-License-Identifier: GPL-2.0-only
+
+#include <command.h>
+#include <malloc.h>
+#include <abort.h>
+#include <getopt.h>
+#include <linux/kstrtox.h>
+
+static int do_checkleak(int argc, char *argv[])
+{
+ unsigned int count;
+ int opt;
+
+ while ((opt = getopt(argc, argv, "l:")) > 0) {
+ switch(opt) {
+ case 'l':
+ if (kstrtouint(optarg, 0, &count))
+ return COMMAND_ERROR;
+ (void)malloc(count);
+ break;
+ default:
+ return COMMAND_ERROR_USAGE;
+ }
+ }
+
+ argv += optind;
+ argc -= optind;
+
+ if (argc)
+ return COMMAND_ERROR_USAGE;
+
+ memleak_check();
+
+ return 0;
+}
+
+BAREBOX_CMD_HELP_START(checkleak)
+BAREBOX_CMD_HELP_TEXT("list memory leaks encountered since the last time")
+BAREBOX_CMD_HELP_TEXT("the command ran.")
+BAREBOX_CMD_HELP_TEXT("")
+BAREBOX_CMD_HELP_TEXT("Options:")
+BAREBOX_CMD_HELP_OPT ("-l COUNT", "force leak of COUNT bytes")
+BAREBOX_CMD_HELP_END
+
+BAREBOX_CMD_START(checkleak)
+ .cmd = do_checkleak,
+ BAREBOX_CMD_DESC("check for memory leaks")
+ BAREBOX_CMD_OPTS("[-l]")
+ BAREBOX_CMD_GROUP(CMD_GRP_MISC)
+ BAREBOX_CMD_HELP(cmd_checkleak_help)
+BAREBOX_CMD_END
+
diff --git a/common/Kconfig.debug b/common/Kconfig.debug
index 9c70555eb83caf5a69ee1c495300d3314fa4b176..2de885ebb3f8278a844249486350d98e8c36e18e 100644
--- a/common/Kconfig.debug
+++ b/common/Kconfig.debug
@@ -124,6 +124,12 @@ config PRINTF_FULL
source "lib/Kconfig.ubsan"
source "lib/kasan/Kconfig"
+config DEBUG_MEMLEAK
+ bool "barebox memory leak detector"
+ depends on MALLOC_LIBC && ASAN
+ help
+ Say Y here if you want to enable LeakSanitizer.
+
config COMPILE_TEST
bool "compile-test drivers of other platforms"
default n
diff --git a/include/malloc.h b/include/malloc.h
index 81ab0f457b01c54de08dd6ef8d5d3d7b0f94d7bb..31a2ff1b3d8e5daad123698de552f4217ca1d476 100644
--- a/include/malloc.h
+++ b/include/malloc.h
@@ -88,4 +88,10 @@ static inline bool want_init_on_free(void)
return IS_ENABLED(CONFIG_INIT_ON_FREE_DEFAULT_ON);
}
+#ifdef CONFIG_DEBUG_MEMLEAK
+void memleak_check(void);
+#else
+static inline void memleak_check(void) {}
+#endif
+
#endif /* __MALLOC_H */
--
2.47.3
^ permalink raw reply [flat|nested] 6+ messages in thread
* Re: [PATCH v2 0/4] talloc and memory leak debugging
2025-11-07 14:41 [PATCH v2 0/4] talloc and memory leak debugging Sascha Hauer
` (3 preceding siblings ...)
2025-11-07 14:41 ` [PATCH v2 4/4] sandbox: add memory leak debugging tooling around LeakSanitizer Sascha Hauer
@ 2025-11-10 8:12 ` Sascha Hauer
4 siblings, 0 replies; 6+ messages in thread
From: Sascha Hauer @ 2025-11-10 8:12 UTC (permalink / raw)
To: BAREBOX, Sascha Hauer; +Cc: Ahmad Fatoum
On Fri, 07 Nov 2025 15:41:08 +0100, Sascha Hauer wrote:
> This picks up the talloc series previously sent by Ahmad. I also added
> the memory leak debugging patch tooling for sandbox.
>
> This series includes my review comments to v1.
>
> Changes since v1:
> - pass originally allocated pointer to malloc_usable_size()
> - align talloc allocations to 8 bytes
> - Use __lsan_do_recoverable_leak_check() instead of
> __lsan_do_leak_check() to keep barebox running after memory leaks have
> been detected
>
> [...]
Applied, thanks!
[1/4] lib: add talloc for overlaying a tree onto allocations
https://git.pengutronix.de/cgit/barebox/commit/?id=9c35b3c2ac7e (link may not be stable)
[2/4] test: self: add talloc selftest
https://git.pengutronix.de/cgit/barebox/commit/?id=2e65ba56f3ee (link may not be stable)
[3/4] hush: fix memory leaks
https://git.pengutronix.de/cgit/barebox/commit/?id=f86d68979fa6 (link may not be stable)
[4/4] sandbox: add memory leak debugging tooling around LeakSanitizer
https://git.pengutronix.de/cgit/barebox/commit/?id=bfa34bbddb8a (link may not be stable)
Best regards,
--
Sascha Hauer <s.hauer@pengutronix.de>
^ permalink raw reply [flat|nested] 6+ messages in thread
end of thread, other threads:[~2025-11-10 8:12 UTC | newest]
Thread overview: 6+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2025-11-07 14:41 [PATCH v2 0/4] talloc and memory leak debugging Sascha Hauer
2025-11-07 14:41 ` [PATCH v2 1/4] lib: add talloc for overlaying a tree onto allocations Sascha Hauer
2025-11-07 14:41 ` [PATCH v2 2/4] test: self: add talloc selftest Sascha Hauer
2025-11-07 14:41 ` [PATCH v2 3/4] hush: fix memory leaks Sascha Hauer
2025-11-07 14:41 ` [PATCH v2 4/4] sandbox: add memory leak debugging tooling around LeakSanitizer Sascha Hauer
2025-11-10 8:12 ` [PATCH v2 0/4] talloc and memory leak debugging Sascha Hauer
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox