Bob-the-Kuhn
7 years ago
committed by
GitHub
13 changed files with 3053 additions and 625 deletions
@ -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 |
|
@ -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_
|
|
@ -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*)(((uint32_t)(&has_function_names)) & (-4))) [-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 & 0xFFFFFFFE; // Remove Thumb bit
|
||||
|
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
|
||||
|
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, ®->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, ®->v)) { |
||||
|
reg->o = REG_VAL_FROM_MEMORY; |
||||
|
return true; |
||||
|
} |
||||
|
/* Not in the hash, and failed to read from memory */ |
||||
|
else { |
||||
|
return false; |
||||
|
} |
||||
|
} |
||||
|
#endif |
||||
|
|
@ -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 */ |
||||
|
|
||||
|
|
@ -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 |
File diff suppressed because it is too large
@ -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 |
||||
|
|
@ -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 */ |
@ -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 |
@ -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 |
||||
|
|
@ -0,0 +1,61 @@ |
|||||
|
/***************************************************************************
|
||||
|
* 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 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) { |
||||
|
// > 16 because there are default entries we can´t supress
|
||||
|
return ((char*)(&__exidx_end) - (char*)(&__exidx_start)) > 16 ? 1 : 0; |
||||
|
} |
||||
|
|
||||
|
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 |
||||
|
|
||||
|
|
@ -0,0 +1,184 @@ |
|||||
|
/***************************************************************************
|
||||
|
* 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. */ |
||||
|
void (*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. |
||||
|
* |
||||
|
* -If the program was compiled with -funwind-tables , it will use them to |
||||
|
* perform the traceback. Otherwise, brute force will be employed |
||||
|
* -If the program was compiled with -mpoke-function-name, then you will |
||||
|
* get function names in the traceback. Otherwise, you will not. |
||||
|
*/ |
||||
|
UnwResult UnwindStart(UnwindFrame* frame, const UnwindCallbacks *cb, void *data); |
||||
|
|
||||
|
#ifdef __cplusplus |
||||
|
} |
||||
|
#endif |
||||
|
|
||||
|
#endif /* UNWINDER_H */ |
Loading…
Reference in new issue