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