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(+) 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); -- 2.43.0