Skip to content

Commit

Permalink
Add support for collecting frame information via callbacks.
Browse files Browse the repository at this point in the history
In addition to the previous API where the caller provided a buffer and a size,
it's now possible to instead proviced a callback and a context. This allows
the caller to use their own data structures without having to allocate an
additional set of objects to pass to backtrace.

The old API is still intact but has internally been rewritten to also use the
callback mechanisms. Note that this does increase the stack strain a bit as
each frame has to go through the callback.

Additionally, this also adds support for traversing a frame provided by the
user, with or without callbacks).

Closes red-rocket-computing#10
  • Loading branch information
denravonska committed May 23, 2023
1 parent a438f64 commit b112095
Show file tree
Hide file tree
Showing 3 changed files with 118 additions and 25 deletions.
28 changes: 23 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,23 +17,36 @@ to the name of the function (if the GCC -mpoke-function-name option is used).

Backtrace provides the following API:

```C
typedef struct backtrace
{
void *function; /* Address of the current address */
void *address; /* Calling site address
void *address; /* Calling site address */
const char *name;
} backtrace_t;

/* Unwind the stack from the current address, filling in provided
backtrace bufffer, limited by size of the buffer, return a count
of the valid frames */
of the valid frames */
int backtrace_unwind(backtrace_t *backtrace, int size)

/* Unwind the stack from the current address, calling the specified callback
for each frame. Stack traversion will stop on stack end or when the callback
returns anything but UNWIND_CONTINUE. */
int backtrace_unwind_cb(unwind_trace_fn callback, void *context);

/* Same as backtrace_unwind but iterates over a user specified frame */
int backtrace_unwind_from_frame(backtrace_t *buffer, int size, backtrace_frame_t *frame);

/* Same as backtrace_unwind_cb but iterates over a user specified frame */
int backtrace_unwind_from_frame_cb(unwind_trace_fn callback, void *context, backtrace_frame_t *frame);

/* Return a pointer to the function name at the specified address or
"unknown" if the function name string is not present. The address
can be the entry point for the function or any address within the
function's scope. */
"unknown" if the function name string is not present. The address
can be the entry point for the function or any address within the
function's scope. */
const char *backtrace_function_name(uint32_t pc);
```C

ARM estimates the overhead for the unwind tables at 2.6% to 3.6%
(see /doc/IHI0038B_ehabi.pdf). Using the -mpoke-function-name option increases
Expand Down Expand Up @@ -61,16 +74,20 @@ Dependencies

Backtrace requires a compatible/working ARM cross compiler supporting the following built-ins:

```C
__builtin_return_address /* Get the return address for the current function */
__builtin_frame_address /* Get the current frame pointer */
```

and is known to work with recent version of gcc
(see https://launchpad.net/gcc-arm-embedded).

In addition your linker scripts should export the following symbols:

```C
__exidx_start /* Pointing to the start of the unwind index table */
__exidx_end /* Pointing to the end of the unwind index table */
```

Building
--------
Expand Down Expand Up @@ -150,6 +167,7 @@ Using The Unwinder
/* All good */
return 0;
}
```
To compile and link this sample using a recent GCC ARM Embedded compiler:
Expand Down
82 changes: 70 additions & 12 deletions backtrace/backtrace.c
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,46 @@
#include <stdlib.h>
#include <string.h>

int _backtrace_unwind(unwind_trace_fn callback, void *context, backtrace_frame_t *frame);
unwind_code_t default_unwind_callback(backtrace_t *trace, void *context);

/* These symbols point to the unwind index and should be provide by the linker script */
extern const unwind_index_t __exidx_start[];
extern const unwind_index_t __exidx_end[];

/* Default unwind context */
typedef struct
{
backtrace_t *buffer;
backtrace_t *buffer_end;
} default_unwind_context_t;

/* Default unwind handler */
unwind_code_t default_unwind_callback(backtrace_t *trace, void *context)
{
default_unwind_context_t *ctx = (default_unwind_context_t*) context;

if(ctx->buffer == ctx->buffer_end)
return UNWIND_ABORT;

ctx->buffer->address = trace->address;
ctx->buffer->function = trace->function;
ctx->buffer->name = trace->name;
ctx->buffer++;

return ctx->buffer != ctx->buffer_end ? UNWIND_CONTINUE : UNWIND_DONE;
}

static unwind_code_t collect_backtrace(unwind_trace_fn callback, void *context, backtrace_t *trace, int *count)
{
unwind_code_t res = callback(trace, context);

if(res == UNWIND_CONTINUE || res == UNWIND_DONE)
(*count)++;

return res;
}

/* This prevents the linking of libgcc unwinder code */
void __aeabi_unwind_cpp_pr0(void);
void __aeabi_unwind_cpp_pr1(void);
Expand Down Expand Up @@ -341,24 +381,26 @@ static int unwind_frame(backtrace_frame_t *frame)
return 1;
}

int _backtrace_unwind(backtrace_t *buffer, int size, backtrace_frame_t *frame)
int _backtrace_unwind(unwind_trace_fn callback, void *context, backtrace_frame_t *frame)
{
int count = 0;

/* Initialize the backtrace frame buffer */
memset(buffer, 0, sizeof(backtrace_t) * size);
backtrace_t backtrace;

/* Unwind all frames */
do {
memset(&backtrace, 0, sizeof(backtrace_t));

if (frame->pc == 0) {
/* Reached __exidx_end. */
buffer[count++].name = "<reached end of unwind table>";
backtrace.name = "<reached end of unwind table>";
collect_backtrace(callback, context, &backtrace, &count);
break;
}

if (frame->pc == 0x00000001) {
/* Reached .cantunwind instruction. */
buffer[count++].name = "<reached .cantunwind>";
backtrace.name = "<reached .cantunwind>";
collect_backtrace(callback, context, &backtrace, &count);
break;
}

Expand All @@ -369,14 +411,14 @@ int _backtrace_unwind(backtrace_t *buffer, int size, backtrace_frame_t *frame)
frame->pc &= 0xfffffffeU;

/* Generate the backtrace information */
buffer[count].address = (void *)frame->pc;
buffer[count].function = (void *)prel31_to_addr(&index->addr_offset);
buffer[count].name = unwind_get_function_name(buffer[count].function);
backtrace.address = (void *)frame->pc;
backtrace.function = (void *)prel31_to_addr(&index->addr_offset);
backtrace.name = unwind_get_function_name(backtrace.function);

/* Next backtrace frame */
++count;
if(collect_backtrace(callback, context, &backtrace, &count) != UNWIND_CONTINUE)
break;

} while (unwind_frame(frame) && count < size);
} while (unwind_frame(frame));

/* All done */
return count;
Expand All @@ -390,3 +432,19 @@ const char *backtrace_function_name(uint32_t pc)

return unwind_get_function_name((void *)prel31_to_addr(&index->addr_offset));
}

int backtrace_unwind_from_frame(backtrace_t *buffer, int size, backtrace_frame_t *frame)
{
default_unwind_context_t context =
{
.buffer = buffer,
.buffer_end = buffer + size
};

return _backtrace_unwind(default_unwind_callback, &context, frame);
}

int backtrace_unwind_from_frame_cb(unwind_trace_fn callback, void *context, backtrace_frame_t *frame)
{
return _backtrace_unwind(callback, context, frame);
}
33 changes: 25 additions & 8 deletions include/backtrace.h
Original file line number Diff line number Diff line change
Expand Up @@ -40,17 +40,23 @@ typedef struct unwind_index
uint32_t insn;
} unwind_index_t;

/* These symbols point to the unwind index and should be provide by the linker script */
extern const unwind_index_t __exidx_start[];
extern const unwind_index_t __exidx_end[];
typedef enum
{
UNWIND_CONTINUE,
UNWIND_DONE,
UNWIND_ABORT
} unwind_code_t;

typedef unwind_code_t (*unwind_trace_fn)(backtrace_t *trace, void *context);

int _backtrace_unwind(backtrace_t *buffer, int size, backtrace_frame_t *frame);
const char *backtrace_function_name(uint32_t pc);
int backtrace_unwind_from_frame(backtrace_t *buffer, int size, backtrace_frame_t *frame);
int backtrace_unwind_from_frame_cb(unwind_trace_fn callback, void *context, backtrace_frame_t *frame);

static inline int __attribute__((always_inline)) backtrace_unwind(backtrace_t *buffer, int size)
static inline backtrace_frame_t __attribute__((always_inline)) construct_backtrace_frame()
{
/* Get the current pc */
register uint32_t pc;
uint32_t pc;
__asm__ volatile("mov %0, pc" : "=r"(pc));

/* Initialize the stack frame */
Expand All @@ -60,8 +66,19 @@ static inline int __attribute__((always_inline)) backtrace_unwind(backtrace_t *b
frame.lr = (uint32_t)__builtin_return_address(0);
frame.pc = pc;

/* Let it rip */
return _backtrace_unwind(buffer, size, &frame);
return frame;
}

static inline int __attribute__((always_inline)) backtrace_unwind(backtrace_t *buffer, int size)
{
backtrace_frame_t frame = construct_backtrace_frame();
return backtrace_unwind_from_frame(buffer, size, &frame);
}

static inline int __attribute__((always_inline)) backtrace_unwind_cb(unwind_trace_fn callback, void *context)
{
backtrace_frame_t frame = construct_backtrace_frame();
return backtrace_unwind_from_frame_cb(callback, context, &frame);
}

#endif /* BACKTRACE_H_ */

0 comments on commit b112095

Please sign in to comment.