Tons of fixes to the backtracker code, and also added an extra backtracker that does not require unwind tables to work and it is used if unwind tables are absent

This commit is contained in:
ejtagle 2018-03-25 00:52:04 -03:00
parent a891af2f7a
commit 9a24c0ae3f
13 changed files with 2552 additions and 605 deletions

View file

@ -24,7 +24,7 @@
#include "../../inc/MarlinConfig.h"
#include "../../Marlin.h"
#include "backtrace/backtrace.h"
#include "backtrace/unwinder.h"
// Debug monitor that dumps to the Programming port all status when
// an exception or WDT timeout happens - And then resets the board
@ -34,6 +34,19 @@
// Serial interrupt routines or any C runtime, as we don't know the
// state we are when running them
/* These symbols point to the start and end of stack */
extern "C" const int _sstack;
extern "C" const int _estack;
/* These symbols point to the start and end of the code section */
extern "C" const int _sfixed;
extern "C" const int _efixed;
/* These symbols point to the start and end of initialized data (could be SRAM functions!) */
extern "C" const int _srelocate;
extern "C" const int _erelocate;
// A SW memory barrier, to ensure GCC does not overoptimize loops
#define sw_barrier() asm volatile("": : :"memory");
@ -112,13 +125,72 @@ static void TXDec(uint32_t v) {
} while (p != &nbrs[0]);
}
// Dump a backtrace entry
static void backtrace_dump_fn(int idx, const backtrace_t* bte, void* ctx) {
TX('#'); TXDec(idx); TX(' ');
TX(bte->name); TX('@');TXHex((uint32_t)bte->function); TX('+'); TXDec((uint32_t)bte->address - (uint32_t)bte->function);
TX(" PC:");TXHex((uint32_t)bte->address); TX('\n');
/* Validate address */
static bool validate_addr(uint16_t addr) {
// PC must point into the text (CODE) area
if (addr >= (uint32_t)&_sfixed && addr <= (uint32_t)&_efixed)
return true;
// Or into the SRAM function area
if (addr >= (uint32_t)&_srelocate && addr <= (uint32_t)&_erelocate)
return true;
// SP must point into the allocated stack area
if (addr >= (uint32_t)&_sstack && addr <= (uint32_t)&_estack)
return true;
return false;
}
static bool UnwReadW(const uint32_t a, uint32_t *v) {
if (!validate_addr(a))
return false;
*v = *(uint32_t *)a;
return true;
}
static bool UnwReadH(const uint32_t a, uint16_t *v) {
if (!validate_addr(a))
return false;
*v = *(uint16_t *)a;
return true;
}
static bool UnwReadB(const uint32_t a, uint8_t *v) {
if (!validate_addr(a))
return false;
*v = *(uint8_t *)a;
return true;
}
// Dump a backtrace entry
static bool UnwReportOut(void* ctx, const UnwReport* bte) {
TX(bte->name?bte->name:"unknown"); TX('@');TXHex(bte->function);
TX('+'); TXDec(bte->address - bte->function);
TX(" PC:");TXHex(bte->address); TX('\n');
return true;
}
/* Table of function pointers for passing to the unwinder */
static const UnwindCallbacks UnwCallbacks = {
UnwReportOut,
UnwReadW,
UnwReadH,
UnwReadB
#if defined(UNW_DEBUG)
,printf
#endif
};
/**
* HardFaultHandler_C:
* This is called from the HardFault_HandlerAsm with a pointer the Fault stack
@ -171,12 +243,12 @@ void HardFault_HandlerC(unsigned long *hardfault_args, unsigned long cause) {
// Perform a backtrace
TX("\nBacktrace:\n\n");
backtrace_frame_t btf;
UnwindFrame btf;
btf.sp = ((unsigned long)hardfault_args[7]);
btf.fp = btf.sp;
btf.lr = ((unsigned long)hardfault_args[5]);
btf.pc = ((unsigned long)hardfault_args[6]);
backtrace_dump(&btf, backtrace_dump_fn, nullptr);
UnwindStart(&btf, &UnwCallbacks, nullptr);
// Disable all NVIC interrupts
NVIC->ICER[0] = 0xFFFFFFFF;

View file

@ -1,544 +0,0 @@
/*
* Libbacktrace
* Copyright 2015 Stephen Street <stephen@redrocketcomputing.com>
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*
* This library was modified, some bugs fixed, stack address validated
* and adapted to be used in Marlin 3D printer firmware as backtracer
* for exceptions for debugging purposes in 2018 by Eduardo José Tagle.
*/
#ifdef ARDUINO_ARCH_SAM
#include "backtrace.h"
#include <stdint.h>
#include <string.h>
typedef struct unwind_control_block {
uint32_t vrs[16];
const uint32_t *current;
int remaining;
int byte;
} unwind_control_block_t;
typedef struct unwind_index {
uint32_t addr_offset;
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[];
/* This prevents the linking of libgcc unwinder code */
void __aeabi_unwind_cpp_pr0(void) {};
void __aeabi_unwind_cpp_pr1(void) {};
void __aeabi_unwind_cpp_pr2(void) {};
/* These symbols point to the start and end of stack */
extern const int _sstack;
extern const int _estack;
/* These symbols point to the start and end of the code section */
extern const int _sfixed;
extern const int _efixed;
/* These symbols point to the start and end of initialized data (could be SRAM functions!) */
extern const int _srelocate;
extern const int _erelocate;
/* Validate stack pointer (SP): It must be in the stack area */
static inline __attribute__((always_inline)) int validate_sp(const void* sp) {
// SP must point into the allocated stack area
if ((uint32_t)sp >= (uint32_t)&_sstack && (uint32_t)sp <= (uint32_t)&_estack)
return 0;
return -1;
}
/* Validate code pointer (PC): It must be either in TEXT or in SRAM */
static inline __attribute__((always_inline)) int validate_pc(const void* pc) {
// PC must point into the text (CODE) area
if ((uint32_t)pc >= (uint32_t)&_sfixed && (uint32_t)pc <= (uint32_t)&_efixed)
return 0;
// Or into the SRAM function area
if ((uint32_t)pc >= (uint32_t)&_srelocate && (uint32_t)pc <= (uint32_t)&_erelocate)
return 0;
return 0;
}
static inline __attribute__((always_inline)) uint32_t prel31_to_addr(const uint32_t *prel31) {
int32_t offset = (((int32_t)(*prel31)) << 1) >> 1;
return ((uint32_t)prel31 + offset) & 0x7fffffff;
}
static const struct unwind_index *unwind_search_index(const unwind_index_t *start, const unwind_index_t *end, uint32_t ip) {
const struct unwind_index *middle;
/* Perform a binary search of the unwind index */
while (start < end - 1) {
middle = start + ((end - start + 1) >> 1);
if (ip < prel31_to_addr(&middle->addr_offset))
end = middle;
else
start = middle;
}
return start;
}
static const char *unwind_get_function_name(void *address) {
uint32_t flag_word = *(uint32_t *)(address - 4);
if ((flag_word & 0xff000000) == 0xff000000) {
return (const char *)(address - 4 - (flag_word & 0x00ffffff));
}
return "unknown";
}
static int unwind_get_next_byte(unwind_control_block_t *ucb) {
int instruction;
/* Are there more instructions */
if (ucb->remaining == 0)
return -1;
/* Extract the current instruction */
instruction = ((*ucb->current) >> (ucb->byte << 3)) & 0xff;
/* Move the next byte */
--ucb->byte;
if (ucb->byte < 0) {
++ucb->current;
ucb->byte = 3;
}
--ucb->remaining;
return instruction;
}
static int unwind_control_block_init(unwind_control_block_t *ucb, const uint32_t *instructions, const backtrace_frame_t *frame) {
/* Initialize control block */
memset(ucb, 0, sizeof(unwind_control_block_t));
ucb->current = instructions;
/* Is a short unwind description */
if ((*instructions & 0xff000000) == 0x80000000) {
ucb->remaining = 3;
ucb->byte = 2;
/* Is a long unwind description */
} else if ((*instructions & 0xff000000) == 0x81000000) {
ucb->remaining = ((*instructions & 0x00ff0000) >> 14) + 2;
ucb->byte = 1;
} else
return -1;
/* Initialize the virtual register set */
ucb->vrs[7] = frame->fp;
ucb->vrs[13] = frame->sp;
ucb->vrs[14] = frame->lr;
ucb->vrs[15] = 0;
/* All good */
return 0;
}
static int unwind_execute_instruction(unwind_control_block_t *ucb) {
int instruction;
uint32_t mask;
uint32_t reg;
uint32_t *vsp;
/* Consume all instruction byte */
while ((instruction = unwind_get_next_byte(ucb)) != -1) {
if ((instruction & 0xc0) == 0x00) { // ARM_EXIDX_CMD_DATA_POP
/* vsp = vsp + (xxxxxx << 2) + 4 */
ucb->vrs[13] += ((instruction & 0x3f) << 2) + 4;
} else
if ((instruction & 0xc0) == 0x40) { // ARM_EXIDX_CMD_DATA_PUSH
/* vsp = vsp - (xxxxxx << 2) - 4 */
ucb->vrs[13] -= ((instruction & 0x3f) << 2) - 4;
} else
if ((instruction & 0xf0) == 0x80) {
/* pop under mask {r15-r12},{r11-r4} or refuse to unwind */
instruction = instruction << 8 | unwind_get_next_byte(ucb);
/* Check for refuse to unwind */
if (instruction == 0x8000) // ARM_EXIDX_CMD_REFUSED
return 0;
/* Pop registers using mask */ // ARM_EXIDX_CMD_REG_POP
vsp = (uint32_t *)ucb->vrs[13];
mask = instruction & 0xfff;
reg = 4;
while (mask) {
if ((mask & 1) != 0) {
if (validate_sp(vsp))
return -1;
ucb->vrs[reg] = *vsp++;
}
mask >>= 1;
++reg;
}
/* Patch up the vrs sp if it was in the mask */
if ((instruction & (1 << (13 - 4))) != 0)
ucb->vrs[13] = (uint32_t)vsp;
} else
if ((instruction & 0xf0) == 0x90 && // ARM_EXIDX_CMD_REG_TO_SP
instruction != 0x9d &&
instruction != 0x9f) {
/* vsp = r[nnnn] */
ucb->vrs[13] = ucb->vrs[instruction & 0x0f];
} else
if ((instruction & 0xf0) == 0xa0) { // ARM_EXIDX_CMD_REG_POP
/* pop r4-r[4+nnn] or pop r4-r[4+nnn], r14*/
vsp = (uint32_t *)ucb->vrs[13];
for (reg = 4; reg <= (instruction & 0x07) + 4; ++reg) {
if (validate_sp(vsp))
return -1;
ucb->vrs[reg] = *vsp++;
}
if (instruction & 0x08) { // ARM_EXIDX_CMD_REG_POP
if (validate_sp(vsp))
return -1;
ucb->vrs[14] = *vsp++;
}
ucb->vrs[13] = (uint32_t)vsp;
} else
if (instruction == 0xb0) { // ARM_EXIDX_CMD_FINISH
/* finished */
if (ucb->vrs[15] == 0)
ucb->vrs[15] = ucb->vrs[14];
/* All done unwinding */
return 0;
} else
if (instruction == 0xb1) { // ARM_EXIDX_CMD_REG_POP
/* pop register under mask {r3,r2,r1,r0} */
vsp = (uint32_t *)ucb->vrs[13];
mask = unwind_get_next_byte(ucb);
reg = 0;
while (mask) {
if ((mask & 1) != 0) {
if (validate_sp(vsp))
return -1;
ucb->vrs[reg] = *vsp++;
}
mask >>= 1;
++reg;
}
ucb->vrs[13] = (uint32_t)vsp;
} else
if (instruction == 0xb2) { // ARM_EXIDX_CMD_DATA_POP
/* vps = vsp + 0x204 + (uleb128 << 2) */
ucb->vrs[13] += 0x204 + (unwind_get_next_byte(ucb) << 2);
} else
if (instruction == 0xb3 || // ARM_EXIDX_CMD_VFP_POP
instruction == 0xc8 ||
instruction == 0xc9) {
/* pop VFP double-precision registers */
vsp = (uint32_t *)ucb->vrs[13];
/* D[ssss]-D[ssss+cccc] */
if (validate_sp(vsp))
return -1;
ucb->vrs[14] = *vsp++;
if (instruction == 0xc8) {
/* D[16+sssss]-D[16+ssss+cccc] */
ucb->vrs[14] |= 1 << 16;
}
if (instruction != 0xb3) {
/* D[sssss]-D[ssss+cccc] */
ucb->vrs[14] |= 1 << 17;
}
ucb->vrs[13] = (uint32_t)vsp;
} else
if ((instruction & 0xf8) == 0xb8 ||
(instruction & 0xf8) == 0xd0) {
/* Pop VFP double precision registers D[8]-D[8+nnn] */
ucb->vrs[14] = 0x80 | (instruction & 0x07);
if ((instruction & 0xf8) == 0xd0) {
ucb->vrs[14] = 1 << 17;
}
} else
return -1;
}
return instruction != -1;
}
static inline __attribute__((always_inline)) uint32_t *read_psp(void) {
/* Read the current PSP and return its value as a pointer */
uint32_t psp;
__asm volatile (
" mrs %0, psp \n"
: "=r" (psp) : :
);
return (uint32_t*)psp;
}
static int unwind_frame(backtrace_frame_t *frame) {
unwind_control_block_t ucb;
const unwind_index_t *index;
const uint32_t *instructions;
int execution_result;
/* Search the unwind index for the matching unwind table */
index = unwind_search_index(__exidx_start, __exidx_end, frame->pc);
if (index == NULL)
return -1;
/* Make sure we can unwind this frame */
if (index->insn == 0x00000001)
return 0;
/* Get the pointer to the first unwind instruction */
if (index->insn & 0x80000000)
instructions = &index->insn;
else
instructions = (uint32_t *)prel31_to_addr(&index->insn);
/* Initialize the unwind control block */
if (unwind_control_block_init(&ucb, instructions, frame) < 0)
return -1;
/* Execute the unwind instructions */
while ((execution_result = unwind_execute_instruction(&ucb)) > 0);
if (execution_result == -1)
return -1;
/* Set the virtual pc to the virtual lr if this is the first unwind */
if (ucb.vrs[15] == 0)
ucb.vrs[15] = ucb.vrs[14];
/* Check for exception return */
/* TODO Test with other ARM processors to verify this method. */
if ((ucb.vrs[15] & 0xf0000000) == 0xf0000000) {
/* According to the Cortex Programming Manual (p.44), the stack address is always 8-byte aligned (Cortex-M7).
Depending on where the exception came from (MSP or PSP), we need the right SP value to work with.
ucb.vrs[7] contains the right value, so take it and align it by 8 bytes, store it as the current
SP to work with (ucb.vrs[13]) which is then saved as the current (virtual) frame's SP.
*/
uint32_t *stack;
ucb.vrs[13] = (ucb.vrs[7] & ~7);
/* If we need to start from the MSP, we need to go down X words to find the PC, where:
X=2 if it was a non-floating-point exception
X=20 if it was a floating-point (VFP) exception
If we need to start from the PSP, we need to go up exactly 6 words to find the PC.
See the ARMv7-M Architecture Reference Manual p.594 and Cortex-M7 Processor Programming Manual p.44/p.45 for details.
*/
if ((ucb.vrs[15] & 0xc) == 0) {
/* Return to Handler Mode: MSP (0xffffff-1) */
stack = (uint32_t*)(ucb.vrs[13]);
/* The PC is always 2 words down from the MSP, if it was a non-floating-point exception */
stack -= 2;
/* If there was a VFP exception (0xffffffe1), the PC is located another 18 words down */
if ((ucb.vrs[15] & 0xf0) == 0xe0) {
stack -= 18;
}
}
else {
/* Return to Thread Mode: PSP (0xffffff-d) */
stack = read_psp();
/* The PC is always 6 words up from the PSP */
stack += 6;
}
/* Store the PC */
ucb.vrs[15] = *stack--;
/* Store the LR */
ucb.vrs[14] = *stack--;
}
/* We are done if current frame pc is equal to the virtual pc, prevent infinite loop */
if (frame->pc == ucb.vrs[15])
return 0;
/* Update the frame */
frame->fp = ucb.vrs[7];
frame->sp = ucb.vrs[13];
frame->lr = ucb.vrs[14];
frame->pc = ucb.vrs[15];
/* All good */
return 1;
}
// Detect if function names are available
static int __attribute__ ((noinline)) has_function_names(void) {
uint32_t flag_word = ((uint32_t*)&has_function_names)[-1];
return ((flag_word & 0xff000000) == 0xff000000) ? 1 : 0;
}
// Detect if unwind information is present or not
static int has_unwind_info(void) {
return ((char*)(&__exidx_end) - (char*)(&__exidx_start)) > 16 ? 1 : 0; // 16 because there are default entries we can´t supress
}
int backtrace_dump(backtrace_frame_t *frame, backtrace_dump_fn_t dump_entry, void* ctx )
{
backtrace_t entry;
int count = 1;
/* If there is no unwind information, perform a RAW try at it. Idea was taken from
* https://stackoverflow.com/questions/3398664/how-to-get-a-call-stack-backtrace-deeply-embedded-no-library-support
*
* And requires code to be compiled with the following flags:
* -mtpcs-frame -mtpcs-leaf-frame -fno-omit-frame-pointer
* With these options, the Stack pointer is automatically
* pushed to the stack at the beginning of each function.
*/
if (!has_unwind_info()) {
/*
* We basically iterate through the current stack finding the
* following combination of values:
* - <Frame Address>
* - <Link Address>
* This combination will occur for each function in the call stack
*/
uint32_t previous_frame_address = (uint32_t)frame->sp;
uint32_t* stack_pointer = (uint32_t*)frame->sp;
// loop following stack frames
while (1) {
// Validate stack address
if (validate_sp(stack_pointer))
break;
// Attempt to obtain next stack pointer
// The link address should come immediately after
const uint32_t possible_frame_address = *stack_pointer;
const uint32_t possible_link_address = *(stack_pointer+1);
// Next check that the frame addresss (i.e. stack pointer for the function)
// and Link address are within an acceptable range
if(possible_frame_address > previous_frame_address &&
validate_sp((const void *)possible_frame_address) == 0 &&
(possible_link_address & 1) != 0 && // in THUMB mode the address will be odd
validate_pc((const void *)possible_link_address) == 0) {
// We found two acceptable values.
entry.name = "unknown";
entry.address = (void*)possible_link_address;
entry.function = 0;
// If there are function names, try to solve name
if (has_function_names()) {
// Lets find the function name, if possible
// Align address to 4 bytes
uint32_t* pf = (uint32_t*) (((uint32_t)possible_link_address) & (-4));
// Scan backwards until we find the function name
while(validate_pc(pf-1) == 0) {
// Get name descriptor value
uint32_t v = pf[-1];
// Check if name descriptor is valid and name is terminated in 0.
if ((v & 0xffffff00) == 0xff000000 &&
(v & 0xff) > 1) {
// Assume the name was found!
entry.name = ((const char*)pf) - 4 - (v & 0xff);
entry.function = (void*)pf;
break;
}
// Go backwards to the previous word
--pf;
}
}
dump_entry(count, &entry, ctx);
++count;
// Update the book-keeping registers for the next search
previous_frame_address = possible_frame_address;
stack_pointer = (uint32_t*)(possible_frame_address + 4);
} else {
// Keep iterating through the stack until we find an acceptable combination
++stack_pointer;
}
}
} else {
/* Otherwise, unwind information is present. Use it to unwind frames */
do {
if (frame->pc == 0) {
/* Reached __exidx_end. */
entry.name = "<reached end of unwind table>";
entry.address = 0;
entry.function = 0;
dump_entry(count, &entry, ctx);
break;
}
if (frame->pc == 0x00000001) {
/* Reached .cantunwind instruction. */
entry.name = "<reached .cantunwind>";
entry.address = 0;
entry.function = 0;
dump_entry(count, &entry, ctx);
break;
}
/* Find the unwind index of the current frame pc */
const unwind_index_t *index = unwind_search_index(__exidx_start, __exidx_end, frame->pc);
/* Clear last bit (Thumb indicator) */
frame->pc &= 0xfffffffeU;
/* Generate the backtrace information */
entry.address = (void *)frame->pc;
entry.function = (void *)prel31_to_addr(&index->addr_offset);
entry.name = unwind_get_function_name(entry.function);
dump_entry(count, &entry, ctx);
/* Next backtrace frame */
++count;
} while (unwind_frame(frame) == 1);
}
/* All done */
return count;
}
#endif

View file

@ -1,53 +0,0 @@
/*
* Libbacktrace
* Copyright 2015 Stephen Street <stephen@redrocketcomputing.com>
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*
* This library was modified and adapted to be used in Marlin 3D printer
* firmware as backtracer for exceptions for debugging purposes in 2018
* by Eduardo José Tagle.
*/
/*
* For this library to work, you need to compile with the following options
* -funwind-tables => So we will have unwind information to perform the stack trace
* -mpoke-function-name => So we will have function names in the trace
*/
#ifndef _BACKTRACE_H_
#define _BACKTRACE_H_
#include <stdint.h>
#ifdef __cplusplus
extern "C" {
#endif
/* A frame */
typedef struct backtrace_frame {
uint32_t fp;
uint32_t sp;
uint32_t lr;
uint32_t pc;
} backtrace_frame_t;
/* A backtrace */
typedef struct backtrace {
void *function;
void *address;
const char *name;
} backtrace_t;
typedef void (*backtrace_dump_fn_t)(int idx, const backtrace_t* bte, void* ctx);
/* Perform a backtrace, given the specified stack start frame */
int backtrace_dump(backtrace_frame_t *startframe, backtrace_dump_fn_t fn, void* ctx );
#ifdef __cplusplus
}
#endif
#endif // _BACKTRACE_H_

View file

@ -0,0 +1,179 @@
/***************************************************************************
* ARM Stack Unwinder, Michael.McTernan.2001@cs.bris.ac.uk
* Updated, adapted and several bug fixes on 2018 by Eduardo José Tagle
*
* This program is PUBLIC DOMAIN.
* This means that there is no copyright and anyone is able to take a copy
* for free and use it as they wish, with or without modifications, and in
* any context, commercially or otherwise. The only limitation is that I
* don't guarantee that the software is fit for any purpose or accept any
* liability for it's use or misuse - this software is without warranty.
***************************************************************************
* File Description: Utility functions and glue for ARM unwinding sub-modules.
**************************************************************************/
#ifdef ARDUINO_ARCH_SAM
#define MODULE_NAME "UNWARM"
#include <stdint.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdarg.h>
#include <string.h>
#include "unwarm.h"
#include "unwarmmem.h"
#if defined(UNW_DEBUG)
/** Printf wrapper.
* This is used such that alternative outputs for any output can be selected
* by modification of this wrapper function.
*/
void UnwPrintf(const char *format, ...) {
va_list args;
va_start( args, format );
vprintf(format, args );
}
#endif
/** Invalidate all general purpose registers.
*/
void UnwInvalidateRegisterFile(RegData *regFile) {
uint8_t t = 0;
do {
regFile[t].o = REG_VAL_INVALID;
t++;
} while(t < 13);
}
/** Initialise the data used for unwinding.
*/
void UnwInitState(UnwState * const state, /**< Pointer to structure to fill. */
const UnwindCallbacks *cb, /**< Callbacks. */
void *rptData, /**< Data to pass to report function. */
uint32_t pcValue, /**< PC at which to start unwinding. */
uint32_t spValue) { /**< SP at which to start unwinding. */
UnwInvalidateRegisterFile(state->regData);
/* Store the pointer to the callbacks */
state->cb = cb;
state->reportData = rptData;
/* Setup the SP and PC */
state->regData[13].v = spValue;
state->regData[13].o = REG_VAL_FROM_CONST;
state->regData[15].v = pcValue;
state->regData[15].o = REG_VAL_FROM_CONST;
UnwPrintd3("\nInitial: PC=0x%08x SP=0x%08x\n", pcValue, spValue);
/* Invalidate all memory addresses */
memset(state->memData.used, 0, sizeof(state->memData.used));
}
// Detect if function names are available
static int __attribute__ ((noinline)) has_function_names(void) {
uint32_t flag_word = ((uint32_t*)&has_function_names)[-1];
return ((flag_word & 0xff000000) == 0xff000000) ? 1 : 0;
}
/** Call the report function to indicate some return address.
* This returns the value of the report function, which if true
* indicates that unwinding may continue.
*/
bool UnwReportRetAddr(UnwState * const state, uint32_t addr) {
UnwReport entry;
// We found two acceptable values.
entry.name = NULL;
entry.address = addr;
entry.function = 0;
// If there are function names, try to solve name
if (has_function_names()) {
// Lets find the function name, if possible
// Align address to 4 bytes
uint32_t pf = addr & (-4);
// Scan backwards until we find the function name
uint32_t v;
while(state->cb->readW(pf-4,&v)) {
// Check if name descriptor is valid and name is terminated in 0.
if ((v & 0xffffff00) == 0xff000000 &&
(v & 0xff) > 1) {
// Assume the name was found!
entry.name = ((const char*)pf) - 4 - (v & 0xff);
entry.function = pf;
break;
}
// Go backwards to the previous word
pf -= 4;;
}
}
/* Cast away const from reportData.
* The const is only to prevent the unw module modifying the data.
*/
return state->cb->report((void *)state->reportData, &entry);
}
/** Write some register to memory.
* This will store some register and meta data onto the virtual stack.
* The address for the write
* \param state [in/out] The unwinding state.
* \param wAddr [in] The address at which to write the data.
* \param reg [in] The register to store.
* \return true if the write was successful, false otherwise.
*/
bool UnwMemWriteRegister(UnwState * const state, const uint32_t addr, const RegData * const reg) {
return UnwMemHashWrite(&state->memData, addr, reg->v, M_IsOriginValid(reg->o));
}
/** Read a register from memory.
* This will read a register from memory, and setup the meta data.
* If the register has been previously written to memory using
* UnwMemWriteRegister, the local hash will be used to return the
* value while respecting whether the data was valid or not. If the
* register was previously written and was invalid at that point,
* REG_VAL_INVALID will be returned in *reg.
* \param state [in] The unwinding state.
* \param addr [in] The address to read.
* \param reg [out] The result, containing the data value and the origin
* which will be REG_VAL_FROM_MEMORY, or REG_VAL_INVALID.
* \return true if the address could be read and *reg has been filled in.
* false is the data could not be read.
*/
bool UnwMemReadRegister(UnwState * const state, const uint32_t addr, RegData * const reg) {
bool tracked;
/* Check if the value can be found in the hash */
if(UnwMemHashRead(&state->memData, addr, &reg->v, &tracked)) {
reg->o = tracked ? REG_VAL_FROM_MEMORY : REG_VAL_INVALID;
return true;
}
/* Not in the hash, so read from real memory */
else if(state->cb->readW(addr, &reg->v)) {
reg->o = REG_VAL_FROM_MEMORY;
return true;
}
/* Not in the hash, and failed to read from memory */
else {
return false;
}
}
#endif

View file

@ -0,0 +1,155 @@
/***************************************************************************
* ARM Stack Unwinder, Michael.McTernan.2001@cs.bris.ac.uk
*
* This program is PUBLIC DOMAIN.
* This means that there is no copyright and anyone is able to take a copy
* for free and use it as they wish, with or without modifications, and in
* any context, commerically or otherwise. The only limitation is that I
* don't guarantee that the software is fit for any purpose or accept any
* liablity for it's use or misuse - this software is without warranty.
***************************************************************************
* File Description: Internal interface between the ARM unwinding sub-modules.
**************************************************************************/
#ifndef UNWARM_H
#define UNWARM_H
#include "unwinder.h"
/** The maximum number of instructions to interpet in a function.
* Unwinding will be unconditionally stopped and UNWIND_EXHAUSTED returned
* if more than this number of instructions are interpreted in a single
* function without unwinding a stack frame. This prevents infinite loops
* or corrupted program memory from preventing unwinding from progressing.
*/
#define UNW_MAX_INSTR_COUNT 500
/** The size of the hash used to track reads and writes to memory.
* This should be a prime value for efficiency.
*/
#define MEM_HASH_SIZE 31
/***************************************************************************
* Type Definitions
**************************************************************************/
typedef enum {
/** Invalid value. */
REG_VAL_INVALID = 0x00,
REG_VAL_FROM_STACK = 0x01,
REG_VAL_FROM_MEMORY = 0x02,
REG_VAL_FROM_CONST = 0x04,
REG_VAL_ARITHMETIC = 0x80
} RegValOrigin;
/** Type for tracking information about a register.
* This stores the register value, as well as other data that helps unwinding.
*/
typedef struct {
/** The value held in the register. */
uint32_t v;
/** The origin of the register value.
* This is used to track how the value in the register was loaded.
*/
RegValOrigin o;
} RegData;
/** Structure used to track reads and writes to memory.
* This structure is used as a hash to store a small number of writes
* to memory.
*/
typedef struct {
/** Memory contents. */
uint32_t v[MEM_HASH_SIZE];
/** Address at which v[n] represents. */
uint32_t a[MEM_HASH_SIZE];
/** Indicates whether the data in v[n] and a[n] is occupied.
* Each bit represents one hash value.
*/
uint8_t used[(MEM_HASH_SIZE + 7) / 8];
/** Indicates whether the data in v[n] is valid.
* This allows a[n] to be set, but for v[n] to be marked as invalid.
* Specifically this is needed for when an untracked register value
* is written to memory.
*/
uint8_t tracked[(MEM_HASH_SIZE + 7) / 8];
} MemData;
/** Structure that is used to keep track of unwinding meta-data.
* This data is passed between all the unwinding functions.
*/
typedef struct {
/** The register values and meta-data. */
RegData regData[16];
/** Memory tracking data. */
MemData memData;
/** Pointer to the callback functions */
const UnwindCallbacks *cb;
/** Pointer to pass to the report function. */
const void *reportData;
} UnwState;
/***************************************************************************
* Macros
**************************************************************************/
#define M_IsOriginValid(v) (((v) & 0x7f) ? true : false)
#define M_Origin2Str(v) ((v) ? "VALID" : "INVALID")
#if defined(UNW_DEBUG)
#define UnwPrintd1(a) state->cb->printf(a)
#define UnwPrintd2(a,b) state->cb->printf(a,b)
#define UnwPrintd3(a,b,c) state->cb->printf(a,b,c)
#define UnwPrintd4(a,b,c,d) state->cb->printf(a,b,c,d)
#define UnwPrintd5(a,b,c,d,e) state->cb->printf(a,b,c,d,e)
#define UnwPrintd6(a,b,c,d,e,f) state->cb->printf(a,b,c,d,e,f)
#define UnwPrintd7(a,b,c,d,e,f,g) state->cb->printf(a,b,c,d,e,f,g)
#define UnwPrintd8(a,b,c,d,e,f,g,h) state->cb->printf(a,b,c,d,e,f,g,h)
#else
#define UnwPrintd1(a)
#define UnwPrintd2(a,b)
#define UnwPrintd3(a,b,c)
#define UnwPrintd4(a,b,c,d)
#define UnwPrintd5(a,b,c,d,e)
#define UnwPrintd6(a,b,c,d,e,f)
#define UnwPrintd7(a,b,c,d,e,f,g)
#define UnwPrintd8(a,b,c,d,e,f,g,h)
#endif
/***************************************************************************
* Function Prototypes
**************************************************************************/
#ifdef __cplusplus
extern "C" {
#endif
UnwResult UnwStartArm(UnwState * const state);
UnwResult UnwStartThumb(UnwState * const state);
void UnwInvalidateRegisterFile(RegData *regFile);
void UnwInitState(UnwState * const state, const UnwindCallbacks *cb, void *rptData, uint32_t pcValue, uint32_t spValue);
bool UnwReportRetAddr(UnwState * const state, uint32_t addr);
bool UnwMemWriteRegister(UnwState * const state, const uint32_t addr, const RegData * const reg);
bool UnwMemReadRegister(UnwState * const state, const uint32_t addr, RegData * const reg);
void UnwMemHashGC(UnwState * const state);
#ifdef __cplusplus
}
#endif
#endif /* UNWARM_H */
/* END OF FILE */

View file

@ -0,0 +1,597 @@
/***************************************************************************
* ARM Stack Unwinder, Michael.McTernan.2001@cs.bris.ac.uk
* Updated, adapted and several bug fixes on 2018 by Eduardo José Tagle
*
* This program is PUBLIC DOMAIN.
* This means that there is no copyright and anyone is able to take a copy
* for free and use it as they wish, with or without modifications, and in
* any context, commercially or otherwise. The only limitation is that I
* don't guarantee that the software is fit for any purpose or accept any
* liability for it's use or misuse - this software is without warranty.
***************************************************************************
* File Description: Abstract interpreter for ARM mode.
**************************************************************************/
#ifdef ARDUINO_ARCH_SAM
#define MODULE_NAME "UNWARM_ARM"
#include <stdio.h>
#include "unwarm.h"
/** Check if some instruction is a data-processing instruction.
* Decodes the passed instruction, checks if it is a data-processing and
* verifies that the parameters and operation really indicate a data-
* processing instruction. This is needed because some parts of the
* instruction space under this instruction can be extended or represent
* other operations such as MRS, MSR.
*
* \param[in] inst The instruction word.
* \retval true Further decoding of the instruction indicates that this is
* a valid data-processing instruction.
* \retval false This is not a data-processing instruction,
*/
static bool isDataProc(uint32_t instr) {
uint8_t opcode = (instr & 0x01e00000) >> 21;
bool S = (instr & 0x00100000) ? true : false;
if((instr & 0xfc000000) != 0xe0000000) {
return false;
} else
if(!S && opcode >= 8 && opcode <= 11) {
/* TST, TEQ, CMP and CMN all require S to be set */
return false;
} else {
return true;
}
}
UnwResult UnwStartArm(UnwState * const state) {
bool found = false;
uint16_t t = UNW_MAX_INSTR_COUNT;
do {
uint32_t instr;
/* Attempt to read the instruction */
if(!state->cb->readW(state->regData[15].v, &instr)) {
return UNWIND_IREAD_W_FAIL;
}
UnwPrintd4("A %x %x %08x:", state->regData[13].v, state->regData[15].v, instr);
/* Check that the PC is still on Arm alignment */
if(state->regData[15].v & 0x3) {
UnwPrintd1("\nError: PC misalignment\n");
return UNWIND_INCONSISTENT;
}
/* Check that the SP and PC have not been invalidated */
if(!M_IsOriginValid(state->regData[13].o) || !M_IsOriginValid(state->regData[15].o)) {
UnwPrintd1("\nError: PC or SP invalidated\n");
return UNWIND_INCONSISTENT;
}
/* Branch and Exchange (BX)
* This is tested prior to data processing to prevent
* mis-interpretation as an invalid TEQ instruction.
*/
if((instr & 0xfffffff0) == 0xe12fff10) {
uint8_t rn = instr & 0xf;
UnwPrintd4("BX r%d\t ; r%d %s\n", rn, rn, M_Origin2Str(state->regData[rn].o));
if(!M_IsOriginValid(state->regData[rn].o)) {
UnwPrintd1("\nUnwind failure: BX to untracked register\n");
return UNWIND_FAILURE;
}
/* Set the new PC value */
state->regData[15].v = state->regData[rn].v;
/* Check if the return value is from the stack */
if(state->regData[rn].o == REG_VAL_FROM_STACK) {
/* Now have the return address */
UnwPrintd2(" Return PC=%x\n", state->regData[15].v & (~0x1));
/* Report the return address */
if(!UnwReportRetAddr(state, state->regData[rn].v)) {
return UNWIND_TRUNCATED;
}
}
/* Determine the return mode */
if(state->regData[rn].v & 0x1) {
/* Branching to THUMB */
return UnwStartThumb(state);
}
else {
/* Branch to ARM */
/* Account for the auto-increment which isn't needed */
state->regData[15].v -= 4;
}
}
/* Branch */
else if((instr & 0xff000000) == 0xea000000) {
int32_t offset = (instr & 0x00ffffff);
/* Shift value */
offset = offset << 2;
/* Sign extend if needed */
if(offset & 0x02000000) {
offset |= 0xfc000000;
}
UnwPrintd2("B %d\n", offset);
/* Adjust PC */
state->regData[15].v += offset;
/* Account for pre-fetch, where normally the PC is 8 bytes
* ahead of the instruction just executed.
*/
state->regData[15].v += 4;
}
/* MRS */
else if((instr & 0xffbf0fff) == 0xe10f0000) {
#if defined(UNW_DEBUG)
bool R = (instr & 0x00400000) ? true : false;
#endif
uint8_t rd = (instr & 0x0000f000) >> 12;
UnwPrintd4("MRS r%d,%s\t; r%d invalidated", rd, R ? "SPSR" : "CPSR", rd);
/* Status registers untracked */
state->regData[rd].o = REG_VAL_INVALID;
}
/* MSR */
else if((instr & 0xffb0f000) == 0xe120f000) {
#if defined(UNW_DEBUG)
bool R = (instr & 0x00400000) ? true : false;
UnwPrintd2("MSR %s_?, ???", R ? "SPSR" : "CPSR");
#endif
/* Status registers untracked.
* Potentially this could change processor mode and switch
* banked registers r8-r14. Most likely is that r13 (sp) will
* be banked. However, invalidating r13 will stop unwinding
* when potentially this write is being used to disable/enable
* interrupts (a common case). Therefore no invalidation is
* performed.
*/
}
/* Data processing */
else if(isDataProc(instr)) {
bool I = (instr & 0x02000000) ? true : false;
uint8_t opcode = (instr & 0x01e00000) >> 21;
#if defined(UNW_DEBUG)
bool S = (instr & 0x00100000) ? true : false;
#endif
uint8_t rn = (instr & 0x000f0000) >> 16;
uint8_t rd = (instr & 0x0000f000) >> 12;
uint16_t operand2 = (instr & 0x00000fff);
uint32_t op2val;
RegValOrigin op2origin;
switch(opcode) {
case 0: UnwPrintd4("AND%s r%d,r%d,", S ? "S" : "", rd, rn); break;
case 1: UnwPrintd4("EOR%s r%d,r%d,", S ? "S" : "", rd, rn); break;
case 2: UnwPrintd4("SUB%s r%d,r%d,", S ? "S" : "", rd, rn); break;
case 3: UnwPrintd4("RSB%s r%d,r%d,", S ? "S" : "", rd, rn); break;
case 4: UnwPrintd4("ADD%s r%d,r%d,", S ? "S" : "", rd, rn); break;
case 5: UnwPrintd4("ADC%s r%d,r%d,", S ? "S" : "", rd, rn); break;
case 6: UnwPrintd4("SBC%s r%d,r%d,", S ? "S" : "", rd, rn); break;
case 7: UnwPrintd4("RSC%s r%d,r%d,", S ? "S" : "", rd, rn); break;
case 8: UnwPrintd3("TST%s r%d,", S ? "S" : "", rn); break;
case 9: UnwPrintd3("TEQ%s r%d,", S ? "S" : "", rn); break;
case 10: UnwPrintd3("CMP%s r%d,", S ? "S" : "", rn); break;
case 11: UnwPrintd3("CMN%s r%d,", S ? "S" : "", rn); break;
case 12: UnwPrintd3("ORR%s r%d,", S ? "S" : "", rn); break;
case 13: UnwPrintd3("MOV%s r%d,", S ? "S" : "", rd); break;
case 14: UnwPrintd4("BIC%s r%d,r%d", S ? "S" : "", rd, rn); break;
case 15: UnwPrintd3("MVN%s r%d,", S ? "S" : "", rd); break;
}
/* Decode operand 2 */
if (I) {
uint8_t shiftDist = (operand2 & 0x0f00) >> 8;
uint8_t shiftConst = (operand2 & 0x00ff);
/* rotate const right by 2 * shiftDist */
shiftDist *= 2;
op2val = (shiftConst >> shiftDist) |
(shiftConst << (32 - shiftDist));
op2origin = REG_VAL_FROM_CONST;
UnwPrintd2("#0x%x", op2val);
}
else {
/* Register and shift */
uint8_t rm = (operand2 & 0x000f);
uint8_t regShift = (operand2 & 0x0010) ? true : false;
uint8_t shiftType = (operand2 & 0x0060) >> 5;
uint32_t shiftDist;
#if defined(UNW_DEBUG)
const char * const shiftMnu[4] = { "LSL", "LSR", "ASR", "ROR" };
#endif
UnwPrintd2("r%d ", rm);
/* Get the shift distance */
if(regShift) {
uint8_t rs = (operand2 & 0x0f00) >> 8;
if(operand2 & 0x00800) {
UnwPrintd1("\nError: Bit should be zero\n");
return UNWIND_ILLEGAL_INSTR;
}
else if(rs == 15) {
UnwPrintd1("\nError: Cannot use R15 with register shift\n");
return UNWIND_ILLEGAL_INSTR;
}
/* Get shift distance */
shiftDist = state->regData[rs].v;
op2origin = state->regData[rs].o;
UnwPrintd7("%s r%d\t; r%d %s r%d %s", shiftMnu[shiftType], rs, rm, M_Origin2Str(state->regData[rm].o), rs, M_Origin2Str(state->regData[rs].o));
}
else {
shiftDist = (operand2 & 0x0f80) >> 7;
op2origin = REG_VAL_FROM_CONST;
if(shiftDist) {
UnwPrintd3("%s #%d", shiftMnu[shiftType], shiftDist);
}
UnwPrintd3("\t; r%d %s", rm, M_Origin2Str(state->regData[rm].o));
}
/* Apply the shift type to the source register */
switch(shiftType) {
case 0: /* logical left */
op2val = state->regData[rm].v << shiftDist;
break;
case 1: /* logical right */
if(!regShift && shiftDist == 0) {
shiftDist = 32;
}
op2val = state->regData[rm].v >> shiftDist;
break;
case 2: /* arithmetic right */
if(!regShift && shiftDist == 0) {
shiftDist = 32;
}
if(state->regData[rm].v & 0x80000000) {
/* Register shifts maybe greater than 32 */
if(shiftDist >= 32) {
op2val = 0xffffffff;
}
else {
op2val = state->regData[rm].v >> shiftDist;
op2val |= 0xffffffff << (32 - shiftDist);
}
}
else {
op2val = state->regData[rm].v >> shiftDist;
}
break;
case 3: /* rotate right */
if(!regShift && shiftDist == 0) {
/* Rotate right with extend.
* This uses the carry bit and so always has an
* untracked result.
*/
op2origin = REG_VAL_INVALID;
op2val = 0;
}
else {
/* Limit shift distance to 0-31 incase of register shift */
shiftDist &= 0x1f;
op2val = (state->regData[rm].v >> shiftDist) |
(state->regData[rm].v << (32 - shiftDist));
}
break;
default:
UnwPrintd2("\nError: Invalid shift type: %d\n", shiftType);
return UNWIND_FAILURE;
}
/* Decide the data origin */
if(M_IsOriginValid(op2origin) &&
M_IsOriginValid(state->regData[rm].o)) {
op2origin = state->regData[rm].o;
op2origin |= REG_VAL_ARITHMETIC;
}
else {
op2origin = REG_VAL_INVALID;
}
}
/* Propagate register validity */
switch(opcode) {
case 0: /* AND: Rd := Op1 AND Op2 */
case 1: /* EOR: Rd := Op1 EOR Op2 */
case 2: /* SUB: Rd:= Op1 - Op2 */
case 3: /* RSB: Rd:= Op2 - Op1 */
case 4: /* ADD: Rd:= Op1 + Op2 */
case 12: /* ORR: Rd:= Op1 OR Op2 */
case 14: /* BIC: Rd:= Op1 AND NOT Op2 */
if(!M_IsOriginValid(state->regData[rn].o) ||
!M_IsOriginValid(op2origin)) {
state->regData[rd].o = REG_VAL_INVALID;
}
else {
state->regData[rd].o = state->regData[rn].o;
state->regData[rd].o |= op2origin;
}
break;
case 5: /* ADC: Rd:= Op1 + Op2 + C */
case 6: /* SBC: Rd:= Op1 - Op2 + C */
case 7: /* RSC: Rd:= Op2 - Op1 + C */
/* CPSR is not tracked */
state->regData[rd].o = REG_VAL_INVALID;
break;
case 8: /* TST: set condition codes on Op1 AND Op2 */
case 9: /* TEQ: set condition codes on Op1 EOR Op2 */
case 10: /* CMP: set condition codes on Op1 - Op2 */
case 11: /* CMN: set condition codes on Op1 + Op2 */
break;
case 13: /* MOV: Rd:= Op2 */
case 15: /* MVN: Rd:= NOT Op2 */
state->regData[rd].o = op2origin;
break;
}
/* Account for pre-fetch by temporarily adjusting PC */
if(rn == 15) {
/* If the shift amount is specified in the instruction,
* the PC will be 8 bytes ahead. If a register is used
* to specify the shift amount the PC will be 12 bytes
* ahead.
*/
if(!I && (operand2 & 0x0010))
state->regData[rn].v += 12;
else
state->regData[rn].v += 8;
}
/* Compute values */
switch(opcode) {
case 0: /* AND: Rd := Op1 AND Op2 */
state->regData[rd].v = state->regData[rn].v & op2val;
break;
case 1: /* EOR: Rd := Op1 EOR Op2 */
state->regData[rd].v = state->regData[rn].v ^ op2val;
break;
case 2: /* SUB: Rd:= Op1 - Op2 */
state->regData[rd].v = state->regData[rn].v - op2val;
break;
case 3: /* RSB: Rd:= Op2 - Op1 */
state->regData[rd].v = op2val - state->regData[rn].v;
break;
case 4: /* ADD: Rd:= Op1 + Op2 */
state->regData[rd].v = state->regData[rn].v + op2val;
break;
case 5: /* ADC: Rd:= Op1 + Op2 + C */
case 6: /* SBC: Rd:= Op1 - Op2 + C */
case 7: /* RSC: Rd:= Op2 - Op1 + C */
case 8: /* TST: set condition codes on Op1 AND Op2 */
case 9: /* TEQ: set condition codes on Op1 EOR Op2 */
case 10: /* CMP: set condition codes on Op1 - Op2 */
case 11: /* CMN: set condition codes on Op1 + Op2 */
UnwPrintd1("\t; ????");
break;
case 12: /* ORR: Rd:= Op1 OR Op2 */
state->regData[rd].v = state->regData[rn].v | op2val;
break;
case 13: /* MOV: Rd:= Op2 */
state->regData[rd].v = op2val;
break;
case 14: /* BIC: Rd:= Op1 AND NOT Op2 */
state->regData[rd].v = state->regData[rn].v & (~op2val);
break;
case 15: /* MVN: Rd:= NOT Op2 */
state->regData[rd].v = ~op2val;
break;
}
/* Remove the prefetch offset from the PC */
if(rd != 15 && rn == 15) {
if(!I && (operand2 & 0x0010))
state->regData[rn].v -= 12;
else
state->regData[rn].v -= 8;
}
}
/* Block Data Transfer
* LDM, STM
*/
else if((instr & 0xfe000000) == 0xe8000000) {
bool P = (instr & 0x01000000) ? true : false;
bool U = (instr & 0x00800000) ? true : false;
bool S = (instr & 0x00400000) ? true : false;
bool W = (instr & 0x00200000) ? true : false;
bool L = (instr & 0x00100000) ? true : false;
uint16_t baseReg = (instr & 0x000f0000) >> 16;
uint16_t regList = (instr & 0x0000ffff);
uint32_t addr = state->regData[baseReg].v;
bool addrValid = M_IsOriginValid(state->regData[baseReg].o);
int8_t r;
#if defined(UNW_DEBUG)
/* Display the instruction */
if(L) {
UnwPrintd6("LDM%c%c r%d%s, {reglist}%s\n", P ? 'E' : 'F', U ? 'D' : 'A', baseReg, W ? "!" : "", S ? "^" : "");
}
else {
UnwPrintd6("STM%c%c r%d%s, {reglist}%s\n", !P ? 'E' : 'F', !U ? 'D' : 'A', baseReg, W ? "!" : "", S ? "^" : "");
}
#endif
/* S indicates that banked registers (untracked) are used, unless
* this is a load including the PC when the S-bit indicates that
* that CPSR is loaded from SPSR (also untracked, but ignored).
*/
if(S && (!L || (regList & (0x01 << 15)) == 0)) {
UnwPrintd1("\nError:S-bit set requiring banked registers\n");
return UNWIND_FAILURE;
}
else if(baseReg == 15) {
UnwPrintd1("\nError: r15 used as base register\n");
return UNWIND_FAILURE;
}
else if(regList == 0) {
UnwPrintd1("\nError: Register list empty\n");
return UNWIND_FAILURE;
}
/* Check if ascending or descending.
* Registers are loaded/stored in order of address.
* i.e. r0 is at the lowest address, r15 at the highest.
*/
r = U ? 0 : 15;
do {
/* Check if the register is to be transferred */
if(regList & (0x01 << r)) {
if(P)
addr += U ? 4 : -4;
if(L) {
if(addrValid) {
if(!UnwMemReadRegister(state, addr, &state->regData[r])) {
return UNWIND_DREAD_W_FAIL;
}
/* Update the origin if read via the stack pointer */
if(M_IsOriginValid(state->regData[r].o) && baseReg == 13) {
state->regData[r].o = REG_VAL_FROM_STACK;
}
UnwPrintd5(" R%d = 0x%08x\t; r%d %s\n",r,state->regData[r].v,r, M_Origin2Str(state->regData[r].o));
}
else {
/* Invalidate the register as the base reg was invalid */
state->regData[r].o = REG_VAL_INVALID;
UnwPrintd2(" R%d = ???\n", r);
}
}
else {
if(addrValid) {
if(!UnwMemWriteRegister(state, state->regData[13].v, &state->regData[r])) {
return UNWIND_DWRITE_W_FAIL;
}
}
UnwPrintd2(" R%d = 0x%08x\n", r);
}
if(!P)
addr += U ? 4 : -4;
}
/* Check the next register */
r += U ? 1 : -1;
} while(r >= 0 && r <= 15);
/* Check the writeback bit */
if(W)
state->regData[baseReg].v = addr;
/* Check if the PC was loaded */
if(L && (regList & (0x01 << 15))) {
if(!M_IsOriginValid(state->regData[15].o)) {
/* Return address is not valid */
UnwPrintd1("PC popped with invalid address\n");
return UNWIND_FAILURE;
}
else {
/* Store the return address */
if(!UnwReportRetAddr(state, state->regData[15].v)) {
return UNWIND_TRUNCATED;
}
UnwPrintd2(" Return PC=0x%x", state->regData[15].v);
/* Determine the return mode */
if(state->regData[15].v & 0x1) {
/* Branching to THUMB */
return UnwStartThumb(state);
}
else {
/* Branch to ARM */
/* Account for the auto-increment which isn't needed */
state->regData[15].v -= 4;
}
}
}
}
else {
UnwPrintd1("????");
/* Unknown/undecoded. May alter some register, so invalidate file */
UnwInvalidateRegisterFile(state->regData);
}
UnwPrintd1("\n");
/* Should never hit the reset vector */
if(state->regData[15].v == 0) return UNWIND_RESET;
/* Check next address */
state->regData[15].v += 4;
/* Garbage collect the memory hash (used only for the stack) */
UnwMemHashGC(state);
t--;
if(t == 0)
return UNWIND_EXHAUSTED;
} while(!found);
return UNWIND_UNSUPPORTED;
}
#endif

View file

@ -0,0 +1,626 @@
/***************************************************************************
* ARM Stack Unwinder, Michael.McTernan.2001@cs.bris.ac.uk
* Updated, adapted and several bug fixes on 2018 by Eduardo José Tagle
*
* This program is PUBLIC DOMAIN.
* This means that there is no copyright and anyone is able to take a copy
* for free and use it as they wish, with or without modifications, and in
* any context, commercially or otherwise. The only limitation is that I
* don't guarantee that the software is fit for any purpose or accept any
* liability for it's use or misuse - this software is without warranty.
***************************************************************************
* File Description: Abstract interpretation for Thumb mode.
**************************************************************************/
#ifdef ARDUINO_ARCH_SAM
#define MODULE_NAME "UNWARM_THUMB"
#include <stdio.h>
#include "unwarm.h"
/** Sign extend an 11 bit value.
* This function simply inspects bit 11 of the input \a value, and if
* set, the top 5 bits are set to give a 2's compliment signed value.
* \param value The value to sign extend.
* \return The signed-11 bit value stored in a 16bit data type.
*/
static int16_t signExtend11(uint16_t value) {
if(value & 0x400) {
value |= 0xf800;
}
return value;
}
UnwResult UnwStartThumb(UnwState * const state) {
bool found = false;
uint16_t t = UNW_MAX_INSTR_COUNT;
do {
uint16_t instr;
/* Attempt to read the instruction */
if(!state->cb->readH(state->regData[15].v & (~0x1), &instr)) {
return UNWIND_IREAD_H_FAIL;
}
UnwPrintd4("T %x %x %04x:", state->regData[13].v, state->regData[15].v, instr);
/* Check that the PC is still on Thumb alignment */
if(!(state->regData[15].v & 0x1)) {
UnwPrintd1("\nError: PC misalignment\n");
return UNWIND_INCONSISTENT;
}
/* Check that the SP and PC have not been invalidated */
if(!M_IsOriginValid(state->regData[13].o) || !M_IsOriginValid(state->regData[15].o)) {
UnwPrintd1("\nError: PC or SP invalidated\n");
return UNWIND_INCONSISTENT;
}
/* Format 1: Move shifted register
* LSL Rd, Rs, #Offset5
* LSR Rd, Rs, #Offset5
* ASR Rd, Rs, #Offset5
*/
if((instr & 0xe000) == 0x0000 && (instr & 0x1800) != 0x1800) {
bool signExtend;
uint8_t op = (instr & 0x1800) >> 11;
uint8_t offset5 = (instr & 0x07c0) >> 6;
uint8_t rs = (instr & 0x0038) >> 3;
uint8_t rd = (instr & 0x0007);
switch(op) {
case 0: /* LSL */
UnwPrintd6("LSL r%d, r%d, #%d\t; r%d %s", rd, rs, offset5, rs, M_Origin2Str(state->regData[rs].o));
state->regData[rd].v = state->regData[rs].v << offset5;
state->regData[rd].o = state->regData[rs].o;
state->regData[rd].o |= REG_VAL_ARITHMETIC;
break;
case 1: /* LSR */
UnwPrintd6("LSR r%d, r%d, #%d\t; r%d %s", rd, rs, offset5, rs, M_Origin2Str(state->regData[rs].o));
state->regData[rd].v = state->regData[rs].v >> offset5;
state->regData[rd].o = state->regData[rs].o;
state->regData[rd].o |= REG_VAL_ARITHMETIC;
break;
case 2: /* ASR */
UnwPrintd6("ASL r%d, r%d, #%d\t; r%d %s", rd, rs, offset5, rs, M_Origin2Str(state->regData[rs].o));
signExtend = (state->regData[rs].v & 0x8000) ? true : false;
state->regData[rd].v = state->regData[rs].v >> offset5;
if(signExtend) {
state->regData[rd].v |= 0xffffffff << (32 - offset5);
}
state->regData[rd].o = state->regData[rs].o;
state->regData[rd].o |= REG_VAL_ARITHMETIC;
break;
}
}
/* Format 2: add/subtract
* ADD Rd, Rs, Rn
* ADD Rd, Rs, #Offset3
* SUB Rd, Rs, Rn
* SUB Rd, Rs, #Offset3
*/
else if((instr & 0xf800) == 0x1800) {
bool I = (instr & 0x0400) ? true : false;
bool op = (instr & 0x0200) ? true : false;
uint8_t rn = (instr & 0x01c0) >> 6;
uint8_t rs = (instr & 0x0038) >> 3;
uint8_t rd = (instr & 0x0007);
/* Print decoding */
UnwPrintd6("%s r%d, r%d, %c%d\t;",op ? "SUB" : "ADD",rd, rs,I ? '#' : 'r',rn);
UnwPrintd5("r%d %s, r%d %s",rd, M_Origin2Str(state->regData[rd].o),rs, M_Origin2Str(state->regData[rs].o));
if(!I) {
UnwPrintd3(", r%d %s", rn, M_Origin2Str(state->regData[rn].o));
/* Perform calculation */
if(op) {
state->regData[rd].v = state->regData[rs].v - state->regData[rn].v;
}
else {
state->regData[rd].v = state->regData[rs].v + state->regData[rn].v;
}
/* Propagate the origin */
if(M_IsOriginValid(state->regData[rs].v) &&
M_IsOriginValid(state->regData[rn].v)) {
state->regData[rd].o = state->regData[rs].o;
state->regData[rd].o |= REG_VAL_ARITHMETIC;
}
else {
state->regData[rd].o = REG_VAL_INVALID;
}
}
else {
/* Perform calculation */
if(op) {
state->regData[rd].v = state->regData[rs].v - rn;
}
else {
state->regData[rd].v = state->regData[rs].v + rn;
}
/* Propagate the origin */
state->regData[rd].o = state->regData[rs].o;
state->regData[rd].o |= REG_VAL_ARITHMETIC;
}
}
/* Format 3: move/compare/add/subtract immediate
* MOV Rd, #Offset8
* CMP Rd, #Offset8
* ADD Rd, #Offset8
* SUB Rd, #Offset8
*/
else if((instr & 0xe000) == 0x2000) {
uint8_t op = (instr & 0x1800) >> 11;
uint8_t rd = (instr & 0x0700) >> 8;
uint8_t offset8 = (instr & 0x00ff);
switch(op) {
case 0: /* MOV */
UnwPrintd3("MOV r%d, #0x%x", rd, offset8);
state->regData[rd].v = offset8;
state->regData[rd].o = REG_VAL_FROM_CONST;
break;
case 1: /* CMP */
/* Irrelevant to unwinding */
UnwPrintd1("CMP ???");
break;
case 2: /* ADD */
UnwPrintd5("ADD r%d, #0x%x\t; r%d %s", rd, offset8, rd, M_Origin2Str(state->regData[rd].o));
state->regData[rd].v += offset8;
state->regData[rd].o |= REG_VAL_ARITHMETIC;
break;
case 3: /* SUB */
UnwPrintd5("SUB r%d, #0x%d\t; r%d %s", rd, offset8, rd, M_Origin2Str(state->regData[rd].o));
state->regData[rd].v -= offset8;
state->regData[rd].o |= REG_VAL_ARITHMETIC;
break;
}
}
/* Format 4: ALU operations
* AND Rd, Rs
* EOR Rd, Rs
* LSL Rd, Rs
* LSR Rd, Rs
* ASR Rd, Rs
* ADC Rd, Rs
* SBC Rd, Rs
* ROR Rd, Rs
* TST Rd, Rs
* NEG Rd, Rs
* CMP Rd, Rs
* CMN Rd, Rs
* ORR Rd, Rs
* MUL Rd, Rs
* BIC Rd, Rs
* MVN Rd, Rs
*/
else if((instr & 0xfc00) == 0x4000) {
uint8_t op = (instr & 0x03c0) >> 6;
uint8_t rs = (instr & 0x0038) >> 3;
uint8_t rd = (instr & 0x0007);
#if defined(UNW_DEBUG)
static const char * const mnu[16] = {
"AND", "EOR", "LSL", "LSR",
"ASR", "ADC", "SBC", "ROR",
"TST", "NEG", "CMP", "CMN",
"ORR", "MUL", "BIC", "MVN" };
#endif
/* Print the mnemonic and registers */
switch(op) {
case 0: /* AND */
case 1: /* EOR */
case 2: /* LSL */
case 3: /* LSR */
case 4: /* ASR */
case 7: /* ROR */
case 9: /* NEG */
case 12: /* ORR */
case 13: /* MUL */
case 15: /* MVN */
UnwPrintd8("%s r%d ,r%d\t; r%d %s, r%d %s",mnu[op],rd, rs, rd, M_Origin2Str(state->regData[rd].o), rs, M_Origin2Str(state->regData[rs].o));
break;
case 5: /* ADC */
case 6: /* SBC */
UnwPrintd4("%s r%d, r%d", mnu[op], rd, rs);
break;
case 8: /* TST */
case 10: /* CMP */
case 11: /* CMN */
/* Irrelevant to unwinding */
UnwPrintd2("%s ???", mnu[op]);
break;
case 14: /* BIC */
UnwPrintd5("r%d ,r%d\t; r%d %s", rd, rs, rs, M_Origin2Str(state->regData[rs].o));
break;
}
/* Perform operation */
switch(op) {
case 0: /* AND */
state->regData[rd].v &= state->regData[rs].v;
break;
case 1: /* EOR */
state->regData[rd].v ^= state->regData[rs].v;
break;
case 2: /* LSL */
state->regData[rd].v <<= state->regData[rs].v;
break;
case 3: /* LSR */
state->regData[rd].v >>= state->regData[rs].v;
break;
case 4: /* ASR */
if(state->regData[rd].v & 0x80000000) {
state->regData[rd].v >>= state->regData[rs].v;
state->regData[rd].v |= 0xffffffff << (32 - state->regData[rs].v);
}
else {
state->regData[rd].v >>= state->regData[rs].v;
}
break;
case 5: /* ADC */
case 6: /* SBC */
case 8: /* TST */
case 10: /* CMP */
case 11: /* CMN */
break;
case 7: /* ROR */
state->regData[rd].v = (state->regData[rd].v >> state->regData[rs].v) |
(state->regData[rd].v << (32 - state->regData[rs].v));
break;
case 9: /* NEG */
state->regData[rd].v = -state->regData[rs].v;
break;
case 12: /* ORR */
state->regData[rd].v |= state->regData[rs].v;
break;
case 13: /* MUL */
state->regData[rd].v *= state->regData[rs].v;
break;
case 14: /* BIC */
state->regData[rd].v &= ~state->regData[rs].v;
break;
case 15: /* MVN */
state->regData[rd].v = ~state->regData[rs].v;
break;
}
/* Propagate data origins */
switch(op) {
case 0: /* AND */
case 1: /* EOR */
case 2: /* LSL */
case 3: /* LSR */
case 4: /* ASR */
case 7: /* ROR */
case 12: /* ORR */
case 13: /* MUL */
case 14: /* BIC */
if(M_IsOriginValid(state->regData[rd].o) && M_IsOriginValid(state->regData[rs].o)) {
state->regData[rd].o = state->regData[rs].o;
state->regData[rd].o |= REG_VAL_ARITHMETIC;
}
else {
state->regData[rd].o = REG_VAL_INVALID;
}
break;
case 5: /* ADC */
case 6: /* SBC */
/* C-bit not tracked */
state->regData[rd].o = REG_VAL_INVALID;
break;
case 8: /* TST */
case 10: /* CMP */
case 11: /* CMN */
/* Nothing propagated */
break;
case 9: /* NEG */
case 15: /* MVN */
state->regData[rd].o = state->regData[rs].o;
state->regData[rd].o |= REG_VAL_ARITHMETIC;
break;
}
}
/* Format 5: Hi register operations/branch exchange
* ADD Rd, Hs
* ADD Hd, Rs
* ADD Hd, Hs
*/
else if((instr & 0xfc00) == 0x4400) {
uint8_t op = (instr & 0x0300) >> 8;
bool h1 = (instr & 0x0080) ? true: false;
bool h2 = (instr & 0x0040) ? true: false;
uint8_t rhs = (instr & 0x0038) >> 3;
uint8_t rhd = (instr & 0x0007);
/* Adjust the register numbers */
if(h2)
rhs += 8;
if(h1)
rhd += 8;
if(op != 3 && !h1 && !h2) {
UnwPrintd1("\nError: h1 or h2 must be set for ADD, CMP or MOV\n");
return UNWIND_ILLEGAL_INSTR;
}
switch(op) {
case 0: /* ADD */
UnwPrintd5("ADD r%d, r%d\t; r%d %s", rhd, rhs, rhs, M_Origin2Str(state->regData[rhs].o));
state->regData[rhd].v += state->regData[rhs].v;
state->regData[rhd].o = state->regData[rhs].o;
state->regData[rhd].o |= REG_VAL_ARITHMETIC;
break;
case 1: /* CMP */
/* Irrelevant to unwinding */
UnwPrintd1("CMP ???");
break;
case 2: /* MOV */
UnwPrintd5("MOV r%d, r%d\t; r%d %s", rhd, rhs, rhd, M_Origin2Str(state->regData[rhs].o));
state->regData[rhd].v = state->regData[rhs].v;
state->regData[rhd].o = state->regData[rhd].o;
break;
case 3: /* BX */
UnwPrintd4("BX r%d\t; r%d %s\n", rhs, rhs, M_Origin2Str(state->regData[rhs].o));
/* Only follow BX if the data was from the stack */
if(state->regData[rhs].o == REG_VAL_FROM_STACK) {
UnwPrintd2(" Return PC=0x%x\n", state->regData[rhs].v & (~0x1));
/* Report the return address, including mode bit */
if(!UnwReportRetAddr(state, state->regData[rhs].v)) {
return UNWIND_TRUNCATED;
}
/* Update the PC */
state->regData[15].v = state->regData[rhs].v;
/* Determine the new mode */
if(state->regData[rhs].v & 0x1) {
/* Branching to THUMB */
/* Account for the auto-increment which isn't needed */
state->regData[15].v -= 2;
}
else {
/* Branch to ARM */
return UnwStartArm(state);
}
}
else {
UnwPrintd4("\nError: BX to invalid register: r%d = 0x%x (%s)\n", rhs, state->regData[rhs].o, M_Origin2Str(state->regData[rhs].o));
return UNWIND_FAILURE;
}
}
}
/* Format 9: PC-relative load
* LDR Rd,[PC, #imm]
*/
else if((instr & 0xf800) == 0x4800) {
uint8_t rd = (instr & 0x0700) >> 8;
uint8_t word8 = (instr & 0x00ff);
uint32_t address;
/* Compute load address, adding a word to account for prefetch */
address = (state->regData[15].v & (~0x3)) + 4 + (word8 << 2);
UnwPrintd3("LDR r%d, 0x%08x", rd, address);
if(!UnwMemReadRegister(state, address, &state->regData[rd])) {
return UNWIND_DREAD_W_FAIL;
}
}
/* Format 13: add offset to Stack Pointer
* ADD sp,#+imm
* ADD sp,#-imm
*/
else if((instr & 0xff00) == 0xB000) {
uint8_t value = (instr & 0x7f) * 4;
/* Check the negative bit */
if((instr & 0x80) != 0) {
UnwPrintd2("SUB sp,#0x%x", value);
state->regData[13].v -= value;
}
else {
UnwPrintd2("ADD sp,#0x%x", value);
state->regData[13].v += value;
}
}
/* Format 14: push/pop registers
* PUSH {Rlist}
* PUSH {Rlist, LR}
* POP {Rlist}
* POP {Rlist, PC}
*/
else if((instr & 0xf600) == 0xb400) {
bool L = (instr & 0x0800) ? true : false;
bool R = (instr & 0x0100) ? true : false;
uint8_t rList = (instr & 0x00ff);
if(L) {
uint8_t r;
/* Load from memory: POP */
UnwPrintd2("POP {Rlist%s}\n", R ? ", PC" : "");
for(r = 0; r < 8; r++) {
if(rList & (0x1 << r)) {
/* Read the word */
if(!UnwMemReadRegister(state, state->regData[13].v, &state->regData[r])) {
return UNWIND_DREAD_W_FAIL;
}
/* Alter the origin to be from the stack if it was valid */
if(M_IsOriginValid(state->regData[r].o)) {
state->regData[r].o = REG_VAL_FROM_STACK;
}
state->regData[13].v += 4;
UnwPrintd3(" r%d = 0x%08x\n", r, state->regData[r].v);
}
}
/* Check if the PC is to be popped */
if(R) {
/* Get the return address */
if(!UnwMemReadRegister(state, state->regData[13].v, &state->regData[15])) {
return UNWIND_DREAD_W_FAIL;
}
/* Alter the origin to be from the stack if it was valid */
if(!M_IsOriginValid(state->regData[15].o)) {
/* Return address is not valid */
UnwPrintd1("PC popped with invalid address\n");
return UNWIND_FAILURE;
}
else {
/* The bottom bit should have been set to indicate that
* the caller was from Thumb. This would allow return
* by BX for interworking APCS.
*/
if((state->regData[15].v & 0x1) == 0) {
UnwPrintd2("Warning: Return address not to Thumb: 0x%08x\n", state->regData[15].v);
/* Pop into the PC will not switch mode */
return UNWIND_INCONSISTENT;
}
/* Store the return address */
if(!UnwReportRetAddr(state, state->regData[15].v)) {
return UNWIND_TRUNCATED;
}
/* Now have the return address */
UnwPrintd2(" Return PC=%x\n", state->regData[15].v);
/* Update the pc */
state->regData[13].v += 4;
/* Compensate for the auto-increment, which isn't needed here */
state->regData[15].v -= 2;
}
}
}
else {
int8_t r;
/* Store to memory: PUSH */
UnwPrintd2("PUSH {Rlist%s}", R ? ", LR" : "");
/* Check if the LR is to be pushed */
if(R) {
UnwPrintd3("\n lr = 0x%08x\t; %s", state->regData[14].v, M_Origin2Str(state->regData[14].o));
state->regData[13].v -= 4;
/* Write the register value to memory */
if(!UnwMemWriteRegister(state, state->regData[13].v, &state->regData[14])) {
return UNWIND_DWRITE_W_FAIL;
}
}
for(r = 7; r >= 0; r--) {
if(rList & (0x1 << r)) {
UnwPrintd4("\n r%d = 0x%08x\t; %s", r, state->regData[r].v, M_Origin2Str(state->regData[r].o));
state->regData[13].v -= 4;
if(!UnwMemWriteRegister(state, state->regData[13].v, &state->regData[r])) {
return UNWIND_DWRITE_W_FAIL;
}
}
}
}
}
/* Format 18: unconditional branch
* B label
*/
else if((instr & 0xf800) == 0xe000) {
int16_t branchValue = signExtend11(instr & 0x07ff);
/* Branch distance is twice that specified in the instruction. */
branchValue *= 2;
UnwPrintd2("B %d \n", branchValue);
/* Update PC */
state->regData[15].v += branchValue;
/* Need to advance by a word to account for pre-fetch.
* Advance by a half word here, allowing the normal address
* advance to account for the other half word.
*/
state->regData[15].v += 2;
/* Display PC of next instruction */
UnwPrintd2(" New PC=%x", state->regData[15].v + 2);
}
else {
UnwPrintd1("????");
/* Unknown/undecoded. May alter some register, so invalidate file */
UnwInvalidateRegisterFile(state->regData);
}
UnwPrintd1("\n");
/* Should never hit the reset vector */
if(state->regData[15].v == 0)
return UNWIND_RESET;
/* Check next address */
state->regData[15].v += 2;
/* Garbage collect the memory hash (used only for the stack) */
UnwMemHashGC(state);
t--;
if(t == 0)
return UNWIND_EXHAUSTED;
} while(!found);
return UNWIND_SUCCESS;
}
#endif

View file

@ -0,0 +1,443 @@
/*
* Libbacktrace
* Copyright 2015 Stephen Street <stephen@redrocketcomputing.com>
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*
* This library was modified, some bugs fixed, stack address validated
* and adapted to be used in Marlin 3D printer firmware as backtracer
* for exceptions for debugging purposes in 2018 by Eduardo José Tagle.
*/
#ifdef ARDUINO_ARCH_SAM
#include "unwarmbytab.h"
#include <stdint.h>
#include <string.h>
/* These symbols point to the unwind index and should be provide by the linker script */
extern const UnwTabEntry __exidx_start[];
extern const UnwTabEntry __exidx_end[];
/* This prevents the linking of libgcc unwinder code */
void __aeabi_unwind_cpp_pr0(void) {};
void __aeabi_unwind_cpp_pr1(void) {};
void __aeabi_unwind_cpp_pr2(void) {};
static inline __attribute__((always_inline)) uint32_t prel31_to_addr(const uint32_t *prel31) {
uint32_t offset = (((uint32_t)(*prel31)) << 1) >> 1;
return ((uint32_t)prel31 + offset) & 0x7fffffff;
}
static const UnwTabEntry *UnwTabSearchIndex(const UnwTabEntry *start, const UnwTabEntry *end, uint32_t ip) {
const UnwTabEntry *middle;
/* Perform a binary search of the unwind index */
while (start < end - 1) {
middle = start + ((end - start + 1) >> 1);
if (ip < prel31_to_addr(&middle->addr_offset))
end = middle;
else
start = middle;
}
return start;
}
/*
* Get the function name or NULL if not found
*/
static const char *UnwTabGetFunctionName(const UnwindCallbacks *cb, uint32_t address) {
uint32_t flag_word = 0;
if (!cb->readW(address-4,&flag_word))
return NULL;
if ((flag_word & 0xff000000) == 0xff000000) {
return (const char *)(address - 4 - (flag_word & 0x00ffffff));
}
return NULL;
}
/**
* Get the next frame unwinding instruction
*
* Return either the instruction or -1 to signal no more instructions
* are available
*/
static int UnwTabGetNextInstruction(const UnwindCallbacks *cb, UnwTabState *ucb) {
int instruction;
/* Are there more instructions */
if (ucb->remaining == 0)
return -1;
/* Extract the current instruction */
uint32_t v = 0;
if (!cb->readW(ucb->current, &v))
return -1;
instruction = (v >> (ucb->byte << 3)) & 0xff;
/* Move the next byte */
--ucb->byte;
if (ucb->byte < 0) {
ucb->current += 4;
ucb->byte = 3;
}
--ucb->remaining;
return instruction;
}
/**
* Initialize the frame unwinding state
*/
static UnwResult UnwTabStateInit(const UnwindCallbacks *cb, UnwTabState *ucb, uint32_t instructions, const UnwindFrame *frame) {
/* Initialize control block */
memset(ucb, 0, sizeof(UnwTabState));
ucb->current = instructions;
/* Is a short unwind description */
uint32_t v = 0;
if (!cb->readW(instructions, &v))
return UNWIND_DREAD_W_FAIL;
if ((v & 0xff000000) == 0x80000000) {
ucb->remaining = 3;
ucb->byte = 2;
/* Is a long unwind description */
} else if ((v & 0xff000000) == 0x81000000) {
ucb->remaining = ((v & 0x00ff0000) >> 14) + 2;
ucb->byte = 1;
} else
return UNWIND_UNSUPPORTED_DWARF_PERSONALITY;
/* Initialize the virtual register set */
ucb->vrs[7] = frame->fp;
ucb->vrs[13] = frame->sp;
ucb->vrs[14] = frame->lr;
ucb->vrs[15] = 0;
/* All good */
return UNWIND_SUCCESS;
}
/*
* Execute unwinding instructions
*/
static UnwResult UnwTabExecuteInstructions(const UnwindCallbacks *cb, UnwTabState *ucb) {
UnwResult err;
int instruction;
uint32_t mask;
uint32_t reg;
uint32_t vsp;
/* Consume all instruction byte */
while ((instruction = UnwTabGetNextInstruction(cb, ucb)) != -1) {
if ((instruction & 0xc0) == 0x00) { // ARM_EXIDX_CMD_DATA_POP
/* vsp = vsp + (xxxxxx << 2) + 4 */
ucb->vrs[13] += ((instruction & 0x3f) << 2) + 4;
} else
if ((instruction & 0xc0) == 0x40) { // ARM_EXIDX_CMD_DATA_PUSH
/* vsp = vsp - (xxxxxx << 2) - 4 */
ucb->vrs[13] -= ((instruction & 0x3f) << 2) - 4;
} else
if ((instruction & 0xf0) == 0x80) {
/* pop under mask {r15-r12},{r11-r4} or refuse to unwind */
instruction = instruction << 8 | UnwTabGetNextInstruction(cb, ucb);
/* Check for refuse to unwind */
if (instruction == 0x8000) // ARM_EXIDX_CMD_REFUSED
return UNWIND_REFUSED;
/* Pop registers using mask */ // ARM_EXIDX_CMD_REG_POP
vsp = ucb->vrs[13];
mask = instruction & 0xfff;
reg = 4;
while (mask) {
if ((mask & 1) != 0) {
uint32_t v;
if (!cb->readW(vsp,&v))
return UNWIND_DREAD_W_FAIL;
ucb->vrs[reg] = v;
v += 4;
}
mask >>= 1;
++reg;
}
/* Patch up the vrs sp if it was in the mask */
if ((instruction & (1 << (13 - 4))) != 0)
ucb->vrs[13] = vsp;
} else
if ((instruction & 0xf0) == 0x90 && // ARM_EXIDX_CMD_REG_TO_SP
instruction != 0x9d &&
instruction != 0x9f) {
/* vsp = r[nnnn] */
ucb->vrs[13] = ucb->vrs[instruction & 0x0f];
} else
if ((instruction & 0xf0) == 0xa0) { // ARM_EXIDX_CMD_REG_POP
/* pop r4-r[4+nnn] or pop r4-r[4+nnn], r14*/
vsp = ucb->vrs[13];
for (reg = 4; reg <= (instruction & 0x07) + 4; ++reg) {
uint32_t v;
if (!cb->readW(vsp,&v))
return UNWIND_DREAD_W_FAIL;
ucb->vrs[reg] = v;
vsp += 4;
}
if (instruction & 0x08) { // ARM_EXIDX_CMD_REG_POP
uint32_t v;
if (!cb->readW(vsp,&v))
return UNWIND_DREAD_W_FAIL;
ucb->vrs[14] = v;
vsp += 4;
}
ucb->vrs[13] = vsp;
} else
if (instruction == 0xb0) { // ARM_EXIDX_CMD_FINISH
/* finished */
if (ucb->vrs[15] == 0)
ucb->vrs[15] = ucb->vrs[14];
/* All done unwinding */
return UNWIND_SUCCESS;
} else
if (instruction == 0xb1) { // ARM_EXIDX_CMD_REG_POP
/* pop register under mask {r3,r2,r1,r0} */
vsp = ucb->vrs[13];
mask = UnwTabGetNextInstruction(cb, ucb);
reg = 0;
while (mask) {
if ((mask & 1) != 0) {
uint32_t v;
if (!cb->readW(vsp,&v))
return UNWIND_DREAD_W_FAIL;
ucb->vrs[reg] = v;
vsp += 4;
}
mask >>= 1;
++reg;
}
ucb->vrs[13] = (uint32_t)vsp;
} else
if (instruction == 0xb2) { // ARM_EXIDX_CMD_DATA_POP
/* vps = vsp + 0x204 + (uleb128 << 2) */
ucb->vrs[13] += 0x204 + (UnwTabGetNextInstruction(cb, ucb) << 2);
} else
if (instruction == 0xb3 || // ARM_EXIDX_CMD_VFP_POP
instruction == 0xc8 ||
instruction == 0xc9) {
/* pop VFP double-precision registers */
vsp = ucb->vrs[13];
/* D[ssss]-D[ssss+cccc] */
uint32_t v;
if (!cb->readW(vsp,&v))
return UNWIND_DREAD_W_FAIL;
ucb->vrs[14] = v;
vsp += 4;
if (instruction == 0xc8) {
/* D[16+sssss]-D[16+ssss+cccc] */
ucb->vrs[14] |= 1 << 16;
}
if (instruction != 0xb3) {
/* D[sssss]-D[ssss+cccc] */
ucb->vrs[14] |= 1 << 17;
}
ucb->vrs[13] = vsp;
} else
if ((instruction & 0xf8) == 0xb8 ||
(instruction & 0xf8) == 0xd0) {
/* Pop VFP double precision registers D[8]-D[8+nnn] */
ucb->vrs[14] = 0x80 | (instruction & 0x07);
if ((instruction & 0xf8) == 0xd0) {
ucb->vrs[14] = 1 << 17;
}
} else
return UNWIND_UNSUPPORTED_DWARF_INSTR;
}
return UNWIND_SUCCESS;
}
static inline __attribute__((always_inline)) uint32_t read_psp(void) {
/* Read the current PSP and return its value as a pointer */
uint32_t psp;
__asm volatile (
" mrs %0, psp \n"
: "=r" (psp) : :
);
return psp;
}
/*
* Unwind the specified frame and goto the previous one
*/
static UnwResult UnwTabUnwindFrame(const UnwindCallbacks *cb, UnwindFrame *frame) {
UnwResult err;
UnwTabState ucb;
const UnwTabEntry *index;
uint32_t instructions;
/* Search the unwind index for the matching unwind table */
index = UnwTabSearchIndex(__exidx_start, __exidx_end, frame->pc);
/* Make sure we can unwind this frame */
if (index->insn == 0x00000001)
return UNWIND_SUCCESS;
/* Get the pointer to the first unwind instruction */
if (index->insn & 0x80000000)
instructions = (uint32_t)&index->insn;
else
instructions = prel31_to_addr(&index->insn);
/* Initialize the unwind control block */
if ((err = UnwTabStateInit(cb, &ucb, instructions, frame)) < 0)
return err;
/* Execute the unwind instructions */
err = UnwTabExecuteInstructions(cb, &ucb);
if (err < 0)
return err;
/* Set the virtual pc to the virtual lr if this is the first unwind */
if (ucb.vrs[15] == 0)
ucb.vrs[15] = ucb.vrs[14];
/* Check for exception return */
/* TODO Test with other ARM processors to verify this method. */
if ((ucb.vrs[15] & 0xf0000000) == 0xf0000000) {
/* According to the Cortex Programming Manual (p.44), the stack address is always 8-byte aligned (Cortex-M7).
Depending on where the exception came from (MSP or PSP), we need the right SP value to work with.
ucb.vrs[7] contains the right value, so take it and align it by 8 bytes, store it as the current
SP to work with (ucb.vrs[13]) which is then saved as the current (virtual) frame's SP.
*/
uint32_t stack;
ucb.vrs[13] = (ucb.vrs[7] & ~7);
/* If we need to start from the MSP, we need to go down X words to find the PC, where:
X=2 if it was a non-floating-point exception
X=20 if it was a floating-point (VFP) exception
If we need to start from the PSP, we need to go up exactly 6 words to find the PC.
See the ARMv7-M Architecture Reference Manual p.594 and Cortex-M7 Processor Programming Manual p.44/p.45 for details.
*/
if ((ucb.vrs[15] & 0xc) == 0) {
/* Return to Handler Mode: MSP (0xffffff-1) */
stack = ucb.vrs[13];
/* The PC is always 2 words down from the MSP, if it was a non-floating-point exception */
stack -= 2*4;
/* If there was a VFP exception (0xffffffe1), the PC is located another 18 words down */
if ((ucb.vrs[15] & 0xf0) == 0xe0) {
stack -= 18*4;
}
}
else {
/* Return to Thread Mode: PSP (0xffffff-d) */
stack = read_psp();
/* The PC is always 6 words up from the PSP */
stack += 6*4;
}
/* Store the PC */
uint32_t v;
if (!cb->readW(stack,&v))
return UNWIND_DREAD_W_FAIL;
ucb.vrs[15] = v;
stack -= 4;
/* Store the LR */
if (!cb->readW(stack,&v))
return UNWIND_DREAD_W_FAIL;
ucb.vrs[14] = v;
stack -= 4;
}
/* We are done if current frame pc is equal to the virtual pc, prevent infinite loop */
if (frame->pc == ucb.vrs[15])
return UNWIND_SUCCESS;
/* Update the frame */
frame->fp = ucb.vrs[7];
frame->sp = ucb.vrs[13];
frame->lr = ucb.vrs[14];
frame->pc = ucb.vrs[15];
/* All good - Continue unwinding */
return UNWIND_MORE_AVAILABLE;
}
UnwResult UnwindByTableStart(UnwindFrame* frame, const UnwindCallbacks *cb, void *data) {
UnwResult err = UNWIND_SUCCESS;
UnwReport entry;
/* Use DWARF unwind information to unwind frames */
do {
if (frame->pc == 0) {
/* Reached __exidx_end. */
break;
}
if (frame->pc == 0x00000001) {
/* Reached .cantunwind instruction. */
break;
}
/* Find the unwind index of the current frame pc */
const UnwTabEntry *index = UnwTabSearchIndex(__exidx_start, __exidx_end, frame->pc);
/* Clear last bit (Thumb indicator) */
frame->pc &= 0xfffffffeU;
/* Generate the backtrace information */
entry.address = frame->pc;
entry.function = prel31_to_addr(&index->addr_offset);
entry.name = UnwTabGetFunctionName(cb, entry.function);
if (!cb->report(data,&entry))
break;
/* Unwind frame and repeat */
} while ((err = UnwTabUnwindFrame(cb, frame)) == UNWIND_MORE_AVAILABLE);
/* All done */
return err;
}
#endif

View file

@ -0,0 +1,44 @@
/***************************************************************************
* ARM Stack Unwinder, Michael.McTernan.2001@cs.bris.ac.uk
* Updated, adapted and several bug fixes on 2018 by Eduardo José Tagle
*
* This program is PUBLIC DOMAIN.
* This means that there is no copyright and anyone is able to take a copy
* for free and use it as they wish, with or without modifications, and in
* any context, commerically or otherwise. The only limitation is that I
* don't guarantee that the software is fit for any purpose or accept any
* liablity for it's use or misuse - this software is without warranty.
***************************************************************************
* File Description: Interface to the memory tracking sub-system.
**************************************************************************/
#ifndef UNWARMBYTAB_H
#define UNWARMBYTAB_H
#include "unwarm.h"
typedef struct {
uint32_t vrs[16];
uint32_t current; /* Address of current byte */
int remaining;
int byte;
} UnwTabState;
typedef struct {
uint32_t addr_offset;
uint32_t insn;
} UnwTabEntry;
#ifdef __cplusplus
extern "C" {
#endif
UnwResult UnwindByTableStart(UnwindFrame* frame, const UnwindCallbacks *cb, void *data);
#ifdef __cplusplus
}
#endif
#endif
/* END OF FILE */

View file

@ -0,0 +1,118 @@
/***************************************************************************
* ARM Stack Unwinder, Michael.McTernan.2001@cs.bris.ac.uk
* Updated, adapted and several bug fixes on 2018 by Eduardo José Tagle
*
* This program is PUBLIC DOMAIN.
* This means that there is no copyright and anyone is able to take a copy
* for free and use it as they wish, with or without modifications, and in
* any context, commerically or otherwise. The only limitation is that I
* don't guarantee that the software is fit for any purpose or accept any
* liablity for it's use or misuse - this software is without warranty.
***************************************************************************
* File Description: Implementation of the memory tracking sub-system.
**************************************************************************/
#ifdef ARDUINO_ARCH_SAM
#define MODULE_NAME "UNWARMMEM"
#include <stdio.h>
#include "unwarmmem.h"
#include "unwarm.h"
#define M_IsIdxUsed(a, v) (((a)[v >> 3] & (1 << (v & 0x7))) ? true : false)
#define M_SetIdxUsed(a, v) ((a)[v >> 3] |= (1 << (v & 0x7)))
#define M_ClrIdxUsed(a, v) ((a)[v >> 3] &= ~(1 << (v & 0x7)))
/** Search the memory hash to see if an entry is stored in the hash already.
* This will search the hash and either return the index where the item is
* stored, or -1 if the item was not found.
*/
static int16_t memHashIndex(MemData * const memData, const uint32_t addr) {
const uint16_t v = addr % MEM_HASH_SIZE;
uint16_t s = v;
do {
/* Check if the element is occupied */
if(M_IsIdxUsed(memData->used, s)) {
/* Check if it is occupied with the sought data */
if(memData->a[s] == addr) {
return s;
}
}
else {
/* Item is free, this is where the item should be stored */
return s;
}
/* Search the next entry */
s++;
if(s > MEM_HASH_SIZE) {
s = 0;
}
} while(s != v);
/* Search failed, hash is full and the address not stored */
return -1;
}
bool UnwMemHashRead(MemData * const memData, uint32_t addr,uint32_t * const data, bool * const tracked) {
int16_t i = memHashIndex(memData, addr);
if(i >= 0 && M_IsIdxUsed(memData->used, i) && memData->a[i] == addr) {
*data = memData->v[i];
*tracked = M_IsIdxUsed(memData->tracked, i);
return true;
}
else {
/* Address not found in the hash */
return false;
}
}
bool UnwMemHashWrite(MemData * const memData, uint32_t addr, uint32_t val, bool valValid) {
int16_t i = memHashIndex(memData, addr);
if(i < 0){
/* Hash full */
return false;
}
else {
/* Store the item */
memData->a[i] = addr;
M_SetIdxUsed(memData->used, i);
if(valValid)
{
memData->v[i] = val;
M_SetIdxUsed(memData->tracked, i);
}
else {
#if defined(UNW_DEBUG)
memData->v[i] = 0xdeadbeef;
#endif
M_ClrIdxUsed(memData->tracked, i);
}
return true;
}
}
void UnwMemHashGC(UnwState * const state) {
const uint32_t minValidAddr = state->regData[13].v;
MemData * const memData = &state->memData;
uint16_t t;
for(t = 0; t < MEM_HASH_SIZE; t++) {
if(M_IsIdxUsed(memData->used, t) && (memData->a[t] < minValidAddr)) {
UnwPrintd3("MemHashGC: Free elem %d, addr 0x%08x\n", t, memData->a[t]);
M_ClrIdxUsed(memData->used, t);
}
}
}
#endif

View file

@ -0,0 +1,33 @@
/***************************************************************************
* ARM Stack Unwinder, Michael.McTernan.2001@cs.bris.ac.uk
* Updated, adapted and several bug fixes on 2018 by Eduardo José Tagle
*
* This program is PUBLIC DOMAIN.
* This means that there is no copyright and anyone is able to take a copy
* for free and use it as they wish, with or without modifications, and in
* any context, commerically or otherwise. The only limitation is that I
* don't guarantee that the software is fit for any purpose or accept any
* liablity for it's use or misuse - this software is without warranty.
***************************************************************************
* File Description: Interface to the memory tracking sub-system.
**************************************************************************/
#ifndef UNWARMMEM_H
#define UNWARMMEM_H
#include "unwarm.h"
#ifdef __cplusplus
extern "C" {
#endif
bool UnwMemHashRead(MemData * const memData, uint32_t addr, uint32_t * const data, bool * const tracked);
bool UnwMemHashWrite(MemData * const memData, uint32_t addr, uint32_t val, bool valValid);
void UnwMemHashGC(UnwState * const state);
#ifdef __cplusplus
}
#endif
#endif

View file

@ -0,0 +1,98 @@
/***************************************************************************
* ARM Stack Unwinder, Michael.McTernan.2001@cs.bris.ac.uk
* Updated, adapted and several bug fixes on 2018 by Eduardo José Tagle
*
* This program is PUBLIC DOMAIN.
* This means that there is no copyright and anyone is able to take a copy
* for free and use it as they wish, with or without modifications, and in
* any context, commercially or otherwise. The only limitation is that I
* don't guarantee that the software is fit for any purpose or accept any
* liability for it's use or misuse - this software is without warranty.
***************************************************************************
* File Description: Implementation of the interface into the ARM unwinder.
**************************************************************************/
#ifdef ARDUINO_ARCH_SAM
#define MODULE_NAME "UNWINDER"
#include <stdio.h>
#include <string.h>
#include "unwinder.h"
#include "unwarm.h"
#include "unwarmbytab.h"
/* These symbols point to the start and end of stack */
extern const int _sstack;
extern const int _estack;
/* These symbols point to the start and end of the code section */
extern const int _sfixed;
extern const int _efixed;
/* These symbols point to the start and end of initialized data (could be SRAM functions!) */
extern const int _srelocate;
extern const int _erelocate;
/* Validate stack pointer (SP): It must be in the stack area */
static inline __attribute__((always_inline)) UnwResult validate_sp(const void* sp) {
// SP must point into the allocated stack area
if ((uint32_t)sp >= (uint32_t)&_sstack && (uint32_t)sp <= (uint32_t)&_estack)
return UNWIND_SUCCESS;
return UNWIND_INVALID_SP;
}
/* Validate code pointer (PC): It must be either in TEXT or in SRAM */
static inline __attribute__((always_inline)) UnwResult validate_pc(const void* pc) {
// PC must point into the text (CODE) area
if ((uint32_t)pc >= (uint32_t)&_sfixed && (uint32_t)pc <= (uint32_t)&_efixed)
return UNWIND_SUCCESS;
// Or into the SRAM function area
if ((uint32_t)pc >= (uint32_t)&_srelocate && (uint32_t)pc <= (uint32_t)&_erelocate)
return UNWIND_SUCCESS;
return UNWIND_INVALID_PC;
}
/* These symbols point to the unwind index and should be provide by the linker script */
extern const UnwTabEntry __exidx_start[];
extern const UnwTabEntry __exidx_end[];
// Detect if unwind information is present or not
static int HasUnwindTableInfo(void) {
return ((char*)(&__exidx_end) - (char*)(&__exidx_start)) > 16 ? 1 : 0; // 16 because there are default entries we can´t supress
}
UnwResult UnwindStart(UnwindFrame* frame, const UnwindCallbacks *cb, void *data)
{
if (HasUnwindTableInfo()) {
/* We have unwind information tables */
return UnwindByTableStart(frame, cb, data);
} else {
/* We don't have unwind information tables */
UnwState state;
/* Initialise the unwinding state */
UnwInitState(&state, cb, data, frame->pc, frame->sp);
/* Check the Thumb bit */
if(frame->pc & 0x1) {
return UnwStartThumb(&state);
}
else {
return UnwStartArm(&state);
}
}
}
#endif

View file

@ -0,0 +1,179 @@
/***************************************************************************
* ARM Stack Unwinder, Michael.McTernan.2001@cs.bris.ac.uk
* Updated, adapted and several bug fixes on 2018 by Eduardo José Tagle
*
* This program is PUBLIC DOMAIN.
* This means that there is no copyright and anyone is able to take a copy
* for free and use it as they wish, with or without modifications, and in
* any context, commerically or otherwise. The only limitation is that I
* don't guarantee that the software is fit for any purpose or accept any
* liablity for it's use or misuse - this software is without warranty.
**************************************************************************/
/** \file
* Interface to the ARM stack unwinding module.
**************************************************************************/
#ifndef UNWINDER_H
#define UNWINDER_H
#include <stdint.h>
#include <stdbool.h>
/** \def UNW_DEBUG
* If this define is set, additional information will be produced while
* unwinding the stack to allow debug of the unwind module itself.
*/
/* #define UNW_DEBUG 1 */
/***************************************************************************
* Type Definitions
**************************************************************************/
/** Possible results for UnwindStart to return.
*/
typedef enum {
/** Unwinding was successful and complete. */
UNWIND_SUCCESS = 0,
/** Not an error: More frames are available. */
UNWIND_MORE_AVAILABLE = 1,
/** Unsupported DWARF unwind personality. */
UNWIND_UNSUPPORTED_DWARF_PERSONALITY = -1,
/** Refused to perform unwind. */
UNWIND_REFUSED = -2,
/** Reached an invalid SP. */
UNWIND_INVALID_SP = -3,
/** Reached an invalid PC */
UNWIND_INVALID_PC = -4,
/** Unsupported DWARF instruction */
UNWIND_UNSUPPORTED_DWARF_INSTR = -5,
/** More than UNW_MAX_INSTR_COUNT instructions were interpreted. */
UNWIND_EXHAUSTED = -6,
/** Unwinding stopped because the reporting func returned false. */
UNWIND_TRUNCATED = -7,
/** Read data was found to be inconsistent. */
UNWIND_INCONSISTENT = -8,
/** Unsupported instruction or data found. */
UNWIND_UNSUPPORTED = -9,
/** General failure. */
UNWIND_FAILURE = -10,
/** Illegal instruction. */
UNWIND_ILLEGAL_INSTR = -11,
/** Unwinding hit the reset vector. */
UNWIND_RESET = -12,
/** Failed read for an instruction word. */
UNWIND_IREAD_W_FAIL = -13,
/** Failed read for an instruction half-word. */
UNWIND_IREAD_H_FAIL = -14,
/** Failed read for an instruction byte. */
UNWIND_IREAD_B_FAIL = -15,
/** Failed read for a data word. */
UNWIND_DREAD_W_FAIL = -16,
/** Failed read for a data half-word. */
UNWIND_DREAD_H_FAIL = -17,
/** Failed read for a data byte. */
UNWIND_DREAD_B_FAIL = -18,
/** Failed write for a data word. */
UNWIND_DWRITE_W_FAIL = -19
} UnwResult;
/** A backtrace report */
typedef struct {
uint32_t function; /** Starts address of function */
const char *name; /** Function name, or null if not available */
uint32_t address; /** PC on that function */
} UnwReport;
/** Type for function pointer for result callback.
* The function is passed two parameters, the first is a void * pointer,
* and the second is the return address of the function. The bottom bit
* of the passed address indicates the execution mode; if it is set,
* the execution mode at the return address is Thumb, otherwise it is
* ARM.
*
* The return value of this function determines whether unwinding should
* continue or not. If true is returned, unwinding will continue and the
* report function maybe called again in future. If false is returned,
* unwinding will stop with UnwindStart() returning UNWIND_TRUNCATED.
*/
typedef bool (*UnwindReportFunc)(void* data, const UnwReport* bte);
/** Structure that holds memory callback function pointers.
*/
typedef struct {
/** Report an unwind result. */
UnwindReportFunc report;
/** Read a 32 bit word from memory.
* The memory address to be read is passed as \a address, and
* \a *val is expected to be populated with the read value.
* If the address cannot or should not be read, false can be
* returned to indicate that unwinding should stop. If true
* is returned, \a *val is assumed to be valid and unwinding
* will continue.
*/
bool (*readW)(const uint32_t address, uint32_t *val);
/** Read a 16 bit half-word from memory.
* This function has the same usage as for readW, but is expected
* to read only a 16 bit value.
*/
bool (*readH)(const uint32_t address, uint16_t *val);
/** Read a byte from memory.
* This function has the same usage as for readW, but is expected
* to read only an 8 bit value.
*/
bool (*readB)(const uint32_t address, uint8_t *val);
#if defined(UNW_DEBUG)
/** Print a formatted line for debug. */
int (*printf)(const char *format, ...);
#endif
} UnwindCallbacks;
/* A frame */
typedef struct {
uint32_t fp;
uint32_t sp;
uint32_t lr;
uint32_t pc;
} UnwindFrame;
#ifdef __cplusplus
extern "C" {
#endif
/** Start unwinding the current stack.
* This will unwind the stack starting at the PC value supplied to in the
* link register (i.e. not a normal register) and the stack pointer value
* supplied.
*/
UnwResult UnwindStart(UnwindFrame* frame, const UnwindCallbacks *cb, void *data);
#ifdef __cplusplus
}
#endif
#endif /* UNWINDER_H */