Replace the dynamic allocation in os_backtrace_symbols() with a static buffer embedded in struct backtrace_ctx. This avoids malloc recursion when backtrace is called from within dlmalloc (e.g., for the upcoming mcheck caller-tracking). The API gets a complete rework as part of this: - Combine addrs[] and syms[] arrays into struct backtrace_frame with addr and sym fields - Store the strings in a unified buffer, with pointers from an array - Change os_backtrace_symbols() to take ctx pointer and fill sym_buf - Remove os_backtrace_symbols_free() as nothing needs freeing - Rename BACKTRACE_MAX to BACKTRACE_MAX_FRAMES Co-developed-by: Claude <noreply@anthropic.com> Signed-off-by: Simon Glass <simon.glass@canonical.com>
120 lines
2.5 KiB
C
120 lines
2.5 KiB
C
// SPDX-License-Identifier: GPL-2.0+
|
|
/*
|
|
* OS-level backtrace support for sandbox
|
|
*
|
|
* Copyright 2025 Canonical Ltd
|
|
* Written by Simon Glass <simon.glass@canonical.com>
|
|
*/
|
|
|
|
#define _GNU_SOURCE
|
|
|
|
#include <backtrace.h>
|
|
#include <execinfo.h>
|
|
#include <stdint.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
#include <os.h>
|
|
/* For BACKTRACE_MAX_FRAMES - include U-Boot's header after system headers */
|
|
#include "../../../include/backtrace.h"
|
|
|
|
/* 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 */
|
|
}
|
|
|
|
void os_backtrace_symbols(struct backtrace_ctx *ctx)
|
|
{
|
|
char *end = ctx->sym_buf + BACKTRACE_SYM_BUFSZ;
|
|
struct backtrace_state *state;
|
|
char *p = ctx->sym_buf;
|
|
int remaining, i;
|
|
|
|
state = get_bt_state();
|
|
|
|
for (i = 0; i < ctx->count; i++) {
|
|
struct backtrace_frame *frame = &ctx->frame[i];
|
|
struct bt_sym_ctx sym_ctx;
|
|
|
|
remaining = end - p;
|
|
if (remaining <= 1) {
|
|
/* No more space, leave remaining syms as NULL */
|
|
frame->sym = NULL;
|
|
continue;
|
|
}
|
|
|
|
frame->sym = p;
|
|
sym_ctx.buf = p;
|
|
sym_ctx.size = remaining;
|
|
sym_ctx.found = 0;
|
|
|
|
if (state) {
|
|
backtrace_pcinfo(state, (uintptr_t)frame->addr,
|
|
bt_full_callback, bt_error_callback,
|
|
&sym_ctx);
|
|
}
|
|
|
|
/* Fall back to address if no symbol found */
|
|
if (!sym_ctx.found)
|
|
snprintf(p, remaining, "%p", frame->addr);
|
|
|
|
p += strlen(p) + 1;
|
|
}
|
|
}
|