[Concept,13/35] backtrace: Add backtrace_str() for condensed backtrace output

Message ID 20251210000737.180797-14-sjg@u-boot.org
State New
Headers
Series malloc: Add heap debugging commands and mcheck caller tracking |

Commit Message

Simon Glass Dec. 10, 2025, 12:07 a.m. UTC
  From: Simon Glass <simon.glass@canonical.com>

Add a new function backtrace_str() that returns a condensed backtrace
string containing function names and line numbers separated by " <-".

For example: "func_a:123 <-func_b:456 <-func_c:789"

This is useful for logging and debugging where a compact representation
of the call stack is needed. The depth is controlled by the new
CONFIG_BACKTRACE_DEPTH option (default 3).

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

 include/backtrace.h  |  36 +++++++++++++
 lib/Kconfig          |   9 ++++
 lib/backtrace.c      | 122 +++++++++++++++++++++++++++++++++++++++++++
 test/lib/backtrace.c |  36 +++++++++++++
 4 files changed, 203 insertions(+)
  

Patch

diff --git a/include/backtrace.h b/include/backtrace.h
index 7bb2ba68bec..7d9d8c1bb88 100644
--- a/include/backtrace.h
+++ b/include/backtrace.h
@@ -94,4 +94,40 @@  void backtrace_uninit(struct backtrace_ctx *ctx);
  */
 int backtrace_show(void);
 
+/**
+ * backtrace_strf() - get a condensed backtrace string into a buffer
+ *
+ * Return a string containing the last CONFIG_BACKTRACE_SUMMARY_FRAMES function names
+ * and line number, separated by ``<-``.
+ *
+ * For example: ``func_a:12 <-func_b:34 <-func_c:56``
+ *
+ * @skip: number of stack frames to skip (0 to include backtrace_strf itself)
+ * @buf: buffer to write the string to
+ * @size: size of buffer
+ * Return: pointer to buf, or NULL on error
+ */
+char *backtrace_strf(unsigned int skip, char *buf, int size);
+
+/**
+ * backtrace_str() - get a condensed backtrace string
+ *
+ * Return a string containing the last CONFIG_BACKTRACE_SUMMARY_FRAMES function names
+ * and line number, separated by ``<-``. The string is statically allocated and
+ * will be overwritten on the next call.
+ *
+ * For example: ``func_a:12 <-func_b:34 <-func_c:56``
+ *
+ * @skip: number of stack frames to skip (0 to include backtrace_str itself)
+ * Return: pointer to static string, or NULL on error
+ */
+#if CONFIG_IS_ENABLED(BACKTRACE)
+const char *backtrace_str(unsigned int skip);
+#else
+static inline const char *backtrace_str(unsigned int skip)
+{
+	return NULL;
+}
+#endif
+
 #endif /* __BACKTRACE_H */
diff --git a/lib/Kconfig b/lib/Kconfig
index 79a75f98446..9c7eb27c392 100644
--- a/lib/Kconfig
+++ b/lib/Kconfig
@@ -36,6 +36,15 @@  config BACKTRACE
 	  stack. This is currently only available on sandbox. The backtrace
 	  command can be used to print the backtrace.
 
+config BACKTRACE_SUMMARY_FRAMES
+	int "Number of frames in condensed backtrace"
+	depends on BACKTRACE
+	default 3
+	help
+	  Number of stack frames to include in the condensed backtrace
+	  string returned by backtrace_str(). This affects BSS usage
+	  since space must be allocated for the string.
+
 config LIB_FORMAT_SIZE
 	bool
 	default y
diff --git a/lib/backtrace.c b/lib/backtrace.c
index b01a08af8ba..b657e8336ab 100644
--- a/lib/backtrace.c
+++ b/lib/backtrace.c
@@ -7,6 +7,7 @@ 
  */
 
 #include <backtrace.h>
+#include <stdbool.h>
 #include <stdio.h>
 #include <string.h>
 
@@ -54,3 +55,124 @@  int backtrace_show(void)
 
 	return 0;
 }
+
+/**
+ * extract_func_info() - extract function name and line number from a symbol
+ *
+ * Parse a backtrace symbol string and extract function name with line number.
+ * The format is typically "func_name() at /path/to/file.c:line" or similar.
+ *
+ * @sym: symbol string from backtrace
+ * @buf: buffer to write "func_name:line" to
+ * @size: size of buffer
+ * Return: pointer to buf, or NULL if extraction failed
+ */
+static char *extract_func_info(const char *sym, char *buf, int size)
+{
+	const char *start, *end, *colon;
+	int len;
+
+	if (!sym)
+		return NULL;
+
+	/*
+	 * Skip leading whitespace and any address prefix (e.g. "0x12345678 ")
+	 * Look for the function name which ends at '+' or '(' or ' '
+	 */
+	start = sym;
+	while (*start == ' ')
+		start++;
+
+	/* Skip hex address if present */
+	if (start[0] == '0' && start[1] == 'x') {
+		while (*start && *start != ' ')
+			start++;
+		while (*start == ' ')
+			start++;
+	}
+
+	/* Find end of function name */
+	end = start;
+	while (*end && *end != '+' && *end != '(' && *end != ' ')
+		end++;
+
+	len = end - start;
+	if (len <= 0 || len >= size)
+		return NULL;
+
+	memcpy(buf, start, len);
+
+	/* Look for line number after last colon (file:line format) */
+	colon = strrchr(sym, ':');
+	if (colon && colon[1] >= '0' && colon[1] <= '9') {
+		buf[len++] = ':';
+		colon++;
+		/* Copy digits */
+		while (*colon >= '0' && *colon <= '9' && len < size - 1)
+			buf[len++] = *colon++;
+	}
+	buf[len] = '\0';
+
+	return buf;
+}
+
+char *backtrace_strf(unsigned int skip, char *buf, int size)
+{
+	static struct backtrace_ctx ctx;
+	int remaining = size;
+	bool first = true;
+	char func[64];
+	char *p = buf;
+	uint i, count;
+	int ret, len;
+
+	/* skip + 1 to skip backtrace_strf() */
+	ret = backtrace_init(&ctx, skip + 1);
+	if (ret < 0)
+		return NULL;
+
+	ret = backtrace_get_syms(&ctx, NULL, 0);
+	if (ret) {
+		backtrace_uninit(&ctx);
+		return NULL;
+	}
+
+	count = ctx.count;
+	if (count > CONFIG_BACKTRACE_SUMMARY_FRAMES)
+		count = CONFIG_BACKTRACE_SUMMARY_FRAMES;
+
+	for (i = 0; i < count; i++) {
+		if (!extract_func_info(ctx.frame[i].sym, func, sizeof(func)))
+			continue;
+
+		if (!first) {
+			if (remaining < 4)
+				break;
+			*p++ = ' ';
+			*p++ = '<';
+			*p++ = '-';
+			remaining -= 3;
+		}
+		first = false;
+
+		len = strlen(func);
+		if (len >= remaining)
+			break;
+		memcpy(p, func, len);
+		p += len;
+		remaining -= len;
+	}
+	*p = '\0';
+
+	backtrace_uninit(&ctx);
+
+	return buf;
+}
+
+const char *backtrace_str(unsigned int skip)
+{
+	static char result[CONFIG_BACKTRACE_SUMMARY_FRAMES * 64];
+
+	/* skip + 1 to account for this wrapper function */
+	return backtrace_strf(skip + 1, result, sizeof(result));
+}
diff --git a/test/lib/backtrace.c b/test/lib/backtrace.c
index 11f0d43ca7e..3f20b7854bf 100644
--- a/test/lib/backtrace.c
+++ b/test/lib/backtrace.c
@@ -46,3 +46,39 @@  static int lib_test_backtrace(struct unit_test_state *uts)
 	return 0;
 }
 LIB_TEST(lib_test_backtrace, 0);
+
+/* Test backtrace_strf() and backtrace_str() */
+static int lib_test_backtrace_str(struct unit_test_state *uts)
+{
+	char pattern[128];
+	char buf[256];
+	const char *cstr;
+	char *str;
+	int line;
+
+	/* Test backtrace_strf() with skip=1 to skip backtrace_strf() itself */
+	line = __LINE__ + 1;
+	str = backtrace_strf(1, buf, sizeof(buf));
+	ut_assertnonnull(str);
+	ut_asserteq_ptr(buf, str);
+
+	printf("backtrace_strf: %s\n", str);
+	snprintf(pattern, sizeof(pattern),
+		 "lib_test_backtrace_str:%d <-ut_run_test:\\d+ <-ut_run_test_live_flat:\\d+",
+		 line);
+	ut_asserteq_regex(pattern, str);
+
+	/* Test backtrace_str() */
+	line = __LINE__ + 1;
+	cstr = backtrace_str(0);
+	ut_assertnonnull(cstr);
+
+	printf("backtrace_str: %s\n", cstr);
+	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);
+
+	return 0;
+}
+LIB_TEST(lib_test_backtrace_str, 0);