diff --git a/arch/sandbox/cpu/Makefile b/arch/sandbox/cpu/Makefile index ef9a01c5d7c..212cf14fe37 100644 --- a/arch/sandbox/cpu/Makefile +++ b/arch/sandbox/cpu/Makefile @@ -15,9 +15,14 @@ extra-y := start.o extra-$(CONFIG_SANDBOX_SDL) += sdl.o obj-$(CONFIG_XPL_BUILD) += spl.o obj-$(CONFIG_ETH_SANDBOX_RAW) += eth-raw-os.o +obj-$(CONFIG_BACKTRACE) += backtrace.o # Compile these files with system headers -CFLAGS_USE_SYSHDRS := eth-raw-os.o fuzz.o main.o os.o sdl.o tty.o +CFLAGS_USE_SYSHDRS := backtrace.o eth-raw-os.o fuzz.o main.o os.o sdl.o tty.o + +# backtrace.c needs libbacktrace header from GCC +LIBBT_INC := $(dir $(shell $(CC) -print-file-name=include/backtrace.h)) +CFLAGS_backtrace.o += -isystem $(LIBBT_INC) # sdl.c fails to build with -fshort-wchar using musl cmd_cc_sdl.o = $(CC) $(filter-out -nostdinc -fshort-wchar, \ diff --git a/arch/sandbox/cpu/backtrace.c b/arch/sandbox/cpu/backtrace.c new file mode 100644 index 00000000000..1f5a14ed541 --- /dev/null +++ b/arch/sandbox/cpu/backtrace.c @@ -0,0 +1,122 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * OS-level backtrace support for sandbox + * + * Copyright 2025 Canonical Ltd + * Written by Simon Glass + */ + +#define _GNU_SOURCE + +#include +#include +#include +#include +#include +#include + +#include + +/* libbacktrace state - created once and cached */ +static struct backtrace_state *bt_state; + +/* Context for collecting symbol info */ +struct bt_sym_ctx { + char *buf; + size_t size; + int found; +}; + +uint os_backtrace(void **buffer, uint size, uint skip) +{ + void *tmp[size + skip]; + uint count; + int nptrs; + + nptrs = backtrace(tmp, size + skip); + if ((int)skip >= nptrs) + return 0; + + count = nptrs - skip; + memcpy(buffer, tmp + skip, count * sizeof(*buffer)); + + return count; +} + +static void bt_error_callback(void *data, const char *msg, int errnum) +{ + /* Silently ignore errors - we'll fall back to addresses */ +} + +static struct backtrace_state *get_bt_state(void) +{ + if (!bt_state) + bt_state = backtrace_create_state(NULL, 0, bt_error_callback, + NULL); + + return bt_state; +} + +static int bt_full_callback(void *data, uintptr_t pc, const char *fname, + int lineno, const char *func) +{ + struct bt_sym_ctx *ctx = data; + + if (func) { + if (fname && lineno) + snprintf(ctx->buf, ctx->size, "%s() at %s:%d", func, + fname, lineno); + else if (fname) + snprintf(ctx->buf, ctx->size, "%s() at %s", func, + fname); + else + snprintf(ctx->buf, ctx->size, "%s()", func); + ctx->found = 1; + } + + return 0; /* continue to get innermost frame for inlined functions */ +} + +char **os_backtrace_symbols(void *const *buffer, uint count) +{ + struct backtrace_state *state; + char *str_storage; + char **strings; + uint i; + + state = get_bt_state(); + + /* Allocate array of string pointers plus space for strings */ + strings = malloc(count * sizeof(char *) + count * 256); + if (!strings) + return NULL; + + /* String storage starts after the pointer array */ + str_storage = (char *)(strings + count); + + for (i = 0; i < count; i++) { + struct bt_sym_ctx ctx; + + strings[i] = str_storage + i * 256; + ctx.buf = strings[i]; + ctx.size = 256; + ctx.found = 0; + + if (state) { + backtrace_pcinfo(state, (uintptr_t)buffer[i], + bt_full_callback, bt_error_callback, + &ctx); + } + + /* Fall back to address if no symbol found */ + if (!ctx.found) + snprintf(strings[i], 256, "%p", buffer[i]); + } + + return strings; +} + +void os_backtrace_symbols_free(char **strings) +{ + free(strings); +} diff --git a/arch/sandbox/lib/Makefile b/arch/sandbox/lib/Makefile index edb650c48da..563a5c33156 100644 --- a/arch/sandbox/lib/Makefile +++ b/arch/sandbox/lib/Makefile @@ -5,6 +5,7 @@ # (C) Copyright 2002-2006 # Wolfgang Denk, DENX Software Engineering, wd@denx.de. +obj-$(CONFIG_BACKTRACE) += backtrace.o obj-y += fdt_fixup.o interrupts.o obj-$(CONFIG_PCI) += pci_io.o obj-$(CONFIG_BOOT) += bootm.o diff --git a/arch/sandbox/lib/backtrace.c b/arch/sandbox/lib/backtrace.c new file mode 100644 index 00000000000..073eb945622 --- /dev/null +++ b/arch/sandbox/lib/backtrace.c @@ -0,0 +1,71 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Backtrace support for sandbox + * + * Copyright 2025 Canonical Ltd + * Written by Simon Glass + */ + +#include +#include +#include +#include + +int backtrace_init(struct backtrace_ctx *ctx, uint skip) +{ + uint i; + + for (i = 0; i < BACKTRACE_MAX; i++) + ctx->syms[i] = NULL; + /* +1 to skip this function */ + ctx->count = os_backtrace(ctx->addrs, BACKTRACE_MAX, skip + 1); + + return ctx->count; +} + +int backtrace_get_syms(struct backtrace_ctx *ctx, char *buf, int size) +{ + char **raw_syms; + size_t total_len; + char *p; + uint i; + + raw_syms = os_backtrace_symbols(ctx->addrs, ctx->count); + if (!raw_syms) + return -ENOMEM; + + /* Calculate total buffer size needed */ + total_len = 0; + for (i = 0; i < ctx->count; i++) { + if (raw_syms[i]) + total_len += strlen(raw_syms[i]) + 1; + else + total_len += 1; /* empty string */ + } + + if ((size_t)size < total_len) { + os_backtrace_symbols_free(raw_syms); + return -ENOSPC; + } + + /* Copy strings into buffer */ + p = buf; + for (i = 0; i < ctx->count; i++) { + ctx->syms[i] = p; + if (raw_syms[i]) { + strcpy(p, raw_syms[i]); + p += strlen(raw_syms[i]) + 1; + } else { + *p++ = '\0'; + } + } + + os_backtrace_symbols_free(raw_syms); + + return 0; +} + +void backtrace_uninit(struct backtrace_ctx *ctx) +{ + /* Nothing to free - caller owns the buffer */ +} diff --git a/configs/sandbox_defconfig b/configs/sandbox_defconfig index 006c6916af6..55f8ddcc952 100644 --- a/configs/sandbox_defconfig +++ b/configs/sandbox_defconfig @@ -368,6 +368,7 @@ CONFIG_TPM=y CONFIG_ERRNO_STR=y CONFIG_GETOPT=y CONFIG_ARGON2=y +CONFIG_BACKTRACE=y CONFIG_TEST_FDTDEC=y CONFIG_UNIT_TEST=y CONFIG_UT_TIME=y diff --git a/examples/rust/build.rs b/examples/rust/build.rs index 9c713ba574c..6be6a21d714 100644 --- a/examples/rust/build.rs +++ b/examples/rust/build.rs @@ -65,6 +65,9 @@ fn main() { // Fallback to just SDL2 if sdl2-config is not available println!("cargo:rustc-link-arg=-lSDL2"); } + + // Link with libbacktrace for backtrace support on sandbox + println!("cargo:rustc-link-arg=-lbacktrace"); } // For dynamic linking, link required system libraries normally diff --git a/examples/ulib/config.mk b/examples/ulib/config.mk index 993f0923b4f..5e2ffa5a4fe 100644 --- a/examples/ulib/config.mk +++ b/examples/ulib/config.mk @@ -44,4 +44,4 @@ SHARED_LDFLAGS := -L$(UBOOT_BUILD) -lu-boot -Wl,-rpath,$(UBOOT_BUILD) STATIC_LDFLAGS := -Wl,-T,$(LIB_STATIC_LDS) \ -Wl,--whole-archive $(UBOOT_BUILD)/libu-boot.a \ -Wl,--no-whole-archive \ - -lpthread -ldl $(PLATFORM_LIBS) -Wl,-z,noexecstack + -lpthread -ldl -lbacktrace $(PLATFORM_LIBS) -Wl,-z,noexecstack diff --git a/include/os.h b/include/os.h index 1b2243d46d4..ab4710fc265 100644 --- a/include/os.h +++ b/include/os.h @@ -576,6 +576,40 @@ int os_setup_signal_handlers(void); */ void os_signal_action(int sig, unsigned long pc); +/** + * os_backtrace() - get backtrace addresses + * + * Collect backtrace addresses into a caller-supplied buffer. + * + * @buffer: array to fill with return addresses + * @size: maximum number of entries in buffer + * @skip: number of stack frames to skip (0 to include os_backtrace itself) + * Return: number of addresses collected + */ +uint os_backtrace(void **buffer, uint size, uint skip); + +/** + * os_backtrace_symbols() - convert addresses to symbol strings + * + * Convert backtrace addresses to human-readable symbol strings. The returned + * array and strings are allocated with malloc() and must be freed with + * os_backtrace_symbols_free(). + * + * @buffer: array of addresses from os_backtrace() + * @count: number of addresses in buffer + * Return: array of symbol strings, or NULL on error + */ +char **os_backtrace_symbols(void *const *buffer, uint count); + +/** + * os_backtrace_symbols_free() - free symbol strings + * + * Free the array returned by os_backtrace_symbols(). + * + * @strings: array to free (may be NULL) + */ +void os_backtrace_symbols_free(char **strings); + /** * os_get_time_offset() - get time offset *