From 9a24c0ae3f01ec4435138dfa59b8bebeb693f737 Mon Sep 17 00:00:00 2001 From: ejtagle Date: Sun, 25 Mar 2018 00:52:04 -0300 Subject: [PATCH 1/3] Tons of fixes to the backtracker code, and also added an extra backtracker that does not require unwind tables to work and it is used if unwind tables are absent --- Marlin/src/HAL/HAL_DUE/DebugMonitor_Due.cpp | 88 ++- Marlin/src/HAL/HAL_DUE/backtrace/backtrace.c | 544 --------------- Marlin/src/HAL/HAL_DUE/backtrace/backtrace.h | 53 -- Marlin/src/HAL/HAL_DUE/backtrace/unwarm.c | 179 +++++ Marlin/src/HAL/HAL_DUE/backtrace/unwarm.h | 155 +++++ Marlin/src/HAL/HAL_DUE/backtrace/unwarm_arm.c | 597 +++++++++++++++++ .../src/HAL/HAL_DUE/backtrace/unwarm_thumb.c | 626 ++++++++++++++++++ .../src/HAL/HAL_DUE/backtrace/unwarmbytab.c | 443 +++++++++++++ .../src/HAL/HAL_DUE/backtrace/unwarmbytab.h | 44 ++ Marlin/src/HAL/HAL_DUE/backtrace/unwarmmem.c | 118 ++++ Marlin/src/HAL/HAL_DUE/backtrace/unwarmmem.h | 33 + Marlin/src/HAL/HAL_DUE/backtrace/unwinder.c | 98 +++ Marlin/src/HAL/HAL_DUE/backtrace/unwinder.h | 179 +++++ 13 files changed, 2552 insertions(+), 605 deletions(-) delete mode 100644 Marlin/src/HAL/HAL_DUE/backtrace/backtrace.c delete mode 100644 Marlin/src/HAL/HAL_DUE/backtrace/backtrace.h create mode 100644 Marlin/src/HAL/HAL_DUE/backtrace/unwarm.c create mode 100644 Marlin/src/HAL/HAL_DUE/backtrace/unwarm.h create mode 100644 Marlin/src/HAL/HAL_DUE/backtrace/unwarm_arm.c create mode 100644 Marlin/src/HAL/HAL_DUE/backtrace/unwarm_thumb.c create mode 100644 Marlin/src/HAL/HAL_DUE/backtrace/unwarmbytab.c create mode 100644 Marlin/src/HAL/HAL_DUE/backtrace/unwarmbytab.h create mode 100644 Marlin/src/HAL/HAL_DUE/backtrace/unwarmmem.c create mode 100644 Marlin/src/HAL/HAL_DUE/backtrace/unwarmmem.h create mode 100644 Marlin/src/HAL/HAL_DUE/backtrace/unwinder.c create mode 100644 Marlin/src/HAL/HAL_DUE/backtrace/unwinder.h diff --git a/Marlin/src/HAL/HAL_DUE/DebugMonitor_Due.cpp b/Marlin/src/HAL/HAL_DUE/DebugMonitor_Due.cpp index fb28de415..f9633f2be 100644 --- a/Marlin/src/HAL/HAL_DUE/DebugMonitor_Due.cpp +++ b/Marlin/src/HAL/HAL_DUE/DebugMonitor_Due.cpp @@ -24,7 +24,7 @@ #include "../../inc/MarlinConfig.h" #include "../../Marlin.h" -#include "backtrace/backtrace.h" +#include "backtrace/unwinder.h" // Debug monitor that dumps to the Programming port all status when // an exception or WDT timeout happens - And then resets the board @@ -34,6 +34,19 @@ // Serial interrupt routines or any C runtime, as we don't know the // state we are when running them + +/* These symbols point to the start and end of stack */ +extern "C" const int _sstack; +extern "C" const int _estack; + +/* These symbols point to the start and end of the code section */ +extern "C" const int _sfixed; +extern "C" const int _efixed; + +/* These symbols point to the start and end of initialized data (could be SRAM functions!) */ +extern "C" const int _srelocate; +extern "C" const int _erelocate; + // A SW memory barrier, to ensure GCC does not overoptimize loops #define sw_barrier() asm volatile("": : :"memory"); @@ -112,13 +125,72 @@ static void TXDec(uint32_t v) { } while (p != &nbrs[0]); } -// Dump a backtrace entry -static void backtrace_dump_fn(int idx, const backtrace_t* bte, void* ctx) { - TX('#'); TXDec(idx); TX(' '); - TX(bte->name); TX('@');TXHex((uint32_t)bte->function); TX('+'); TXDec((uint32_t)bte->address - (uint32_t)bte->function); - TX(" PC:");TXHex((uint32_t)bte->address); TX('\n'); +/* Validate address */ +static bool validate_addr(uint16_t addr) { + + // PC must point into the text (CODE) area + if (addr >= (uint32_t)&_sfixed && addr <= (uint32_t)&_efixed) + return true; + + // Or into the SRAM function area + if (addr >= (uint32_t)&_srelocate && addr <= (uint32_t)&_erelocate) + return true; + + // SP must point into the allocated stack area + if (addr >= (uint32_t)&_sstack && addr <= (uint32_t)&_estack) + return true; + + return false; } +static bool UnwReadW(const uint32_t a, uint32_t *v) { + + if (!validate_addr(a)) + return false; + + *v = *(uint32_t *)a; + return true; +} + +static bool UnwReadH(const uint32_t a, uint16_t *v) { + + if (!validate_addr(a)) + return false; + + *v = *(uint16_t *)a; + return true; +} + +static bool UnwReadB(const uint32_t a, uint8_t *v) { + + if (!validate_addr(a)) + return false; + + *v = *(uint8_t *)a; + return true; +} + + +// Dump a backtrace entry +static bool UnwReportOut(void* ctx, const UnwReport* bte) { + + TX(bte->name?bte->name:"unknown"); TX('@');TXHex(bte->function); + TX('+'); TXDec(bte->address - bte->function); + TX(" PC:");TXHex(bte->address); TX('\n'); + return true; +} + +/* Table of function pointers for passing to the unwinder */ +static const UnwindCallbacks UnwCallbacks = { + UnwReportOut, + UnwReadW, + UnwReadH, + UnwReadB +#if defined(UNW_DEBUG) + ,printf +#endif +}; + /** * HardFaultHandler_C: * This is called from the HardFault_HandlerAsm with a pointer the Fault stack @@ -171,12 +243,12 @@ void HardFault_HandlerC(unsigned long *hardfault_args, unsigned long cause) { // Perform a backtrace TX("\nBacktrace:\n\n"); - backtrace_frame_t btf; + UnwindFrame btf; btf.sp = ((unsigned long)hardfault_args[7]); btf.fp = btf.sp; btf.lr = ((unsigned long)hardfault_args[5]); btf.pc = ((unsigned long)hardfault_args[6]); - backtrace_dump(&btf, backtrace_dump_fn, nullptr); + UnwindStart(&btf, &UnwCallbacks, nullptr); // Disable all NVIC interrupts NVIC->ICER[0] = 0xFFFFFFFF; diff --git a/Marlin/src/HAL/HAL_DUE/backtrace/backtrace.c b/Marlin/src/HAL/HAL_DUE/backtrace/backtrace.c deleted file mode 100644 index 8d61b793c..000000000 --- a/Marlin/src/HAL/HAL_DUE/backtrace/backtrace.c +++ /dev/null @@ -1,544 +0,0 @@ -/* - * Libbacktrace - * Copyright 2015 Stephen Street - * - * 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 -#include - -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: - * - - * - - * 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 = ""; - entry.address = 0; - entry.function = 0; - dump_entry(count, &entry, ctx); - break; - } - - if (frame->pc == 0x00000001) { - /* Reached .cantunwind instruction. */ - entry.name = ""; - 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 diff --git a/Marlin/src/HAL/HAL_DUE/backtrace/backtrace.h b/Marlin/src/HAL/HAL_DUE/backtrace/backtrace.h deleted file mode 100644 index 855bc35d1..000000000 --- a/Marlin/src/HAL/HAL_DUE/backtrace/backtrace.h +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Libbacktrace - * Copyright 2015 Stephen Street - * - * 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 - -#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_ diff --git a/Marlin/src/HAL/HAL_DUE/backtrace/unwarm.c b/Marlin/src/HAL/HAL_DUE/backtrace/unwarm.c new file mode 100644 index 000000000..e43253444 --- /dev/null +++ b/Marlin/src/HAL/HAL_DUE/backtrace/unwarm.c @@ -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 +#include +#include +#include +#include +#include "unwarm.h" +#include "unwarmmem.h" + +#if defined(UNW_DEBUG) + +/** Printf wrapper. + * This is used such that alternative outputs for any output can be selected + * by modification of this wrapper function. + */ +void UnwPrintf(const char *format, ...) { + va_list args; + + va_start( args, format ); + vprintf(format, args ); +} +#endif + +/** Invalidate all general purpose registers. + */ +void UnwInvalidateRegisterFile(RegData *regFile) { + + uint8_t t = 0; + do { + regFile[t].o = REG_VAL_INVALID; + t++; + } while(t < 13); +} + + +/** Initialise the data used for unwinding. + */ +void UnwInitState(UnwState * const state, /**< Pointer to structure to fill. */ + const UnwindCallbacks *cb, /**< Callbacks. */ + void *rptData, /**< Data to pass to report function. */ + uint32_t pcValue, /**< PC at which to start unwinding. */ + uint32_t spValue) { /**< SP at which to start unwinding. */ + + UnwInvalidateRegisterFile(state->regData); + + /* Store the pointer to the callbacks */ + state->cb = cb; + state->reportData = rptData; + + /* Setup the SP and PC */ + state->regData[13].v = spValue; + state->regData[13].o = REG_VAL_FROM_CONST; + state->regData[15].v = pcValue; + state->regData[15].o = REG_VAL_FROM_CONST; + + UnwPrintd3("\nInitial: PC=0x%08x SP=0x%08x\n", pcValue, spValue); + + /* Invalidate all memory addresses */ + memset(state->memData.used, 0, sizeof(state->memData.used)); +} + +// Detect if function names are available +static int __attribute__ ((noinline)) has_function_names(void) { + + uint32_t flag_word = ((uint32_t*)&has_function_names)[-1]; + return ((flag_word & 0xff000000) == 0xff000000) ? 1 : 0; +} + +/** Call the report function to indicate some return address. + * This returns the value of the report function, which if true + * indicates that unwinding may continue. + */ +bool UnwReportRetAddr(UnwState * const state, uint32_t addr) { + + UnwReport entry; + + // We found two acceptable values. + entry.name = NULL; + entry.address = addr; + entry.function = 0; + + // If there are function names, try to solve name + if (has_function_names()) { + + // Lets find the function name, if possible + + // Align address to 4 bytes + uint32_t pf = addr & (-4); + + // Scan backwards until we find the function name + uint32_t v; + while(state->cb->readW(pf-4,&v)) { + + // Check if name descriptor is valid and name is terminated in 0. + if ((v & 0xffffff00) == 0xff000000 && + (v & 0xff) > 1) { + + // Assume the name was found! + entry.name = ((const char*)pf) - 4 - (v & 0xff); + entry.function = pf; + break; + } + + // Go backwards to the previous word + pf -= 4;; + } + } + + /* Cast away const from reportData. + * The const is only to prevent the unw module modifying the data. + */ + return state->cb->report((void *)state->reportData, &entry); +} + + +/** Write some register to memory. + * This will store some register and meta data onto the virtual stack. + * The address for the write + * \param state [in/out] The unwinding state. + * \param wAddr [in] The address at which to write the data. + * \param reg [in] The register to store. + * \return true if the write was successful, false otherwise. + */ +bool UnwMemWriteRegister(UnwState * const state, const uint32_t addr, const RegData * const reg) { + return UnwMemHashWrite(&state->memData, addr, reg->v, M_IsOriginValid(reg->o)); +} + +/** Read a register from memory. + * This will read a register from memory, and setup the meta data. + * If the register has been previously written to memory using + * UnwMemWriteRegister, the local hash will be used to return the + * value while respecting whether the data was valid or not. If the + * register was previously written and was invalid at that point, + * REG_VAL_INVALID will be returned in *reg. + * \param state [in] The unwinding state. + * \param addr [in] The address to read. + * \param reg [out] The result, containing the data value and the origin + * which will be REG_VAL_FROM_MEMORY, or REG_VAL_INVALID. + * \return true if the address could be read and *reg has been filled in. + * false is the data could not be read. + */ +bool UnwMemReadRegister(UnwState * const state, const uint32_t addr, RegData * const reg) { + + bool tracked; + + /* Check if the value can be found in the hash */ + if(UnwMemHashRead(&state->memData, addr, ®->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 + diff --git a/Marlin/src/HAL/HAL_DUE/backtrace/unwarm.h b/Marlin/src/HAL/HAL_DUE/backtrace/unwarm.h new file mode 100644 index 000000000..23c1f96c2 --- /dev/null +++ b/Marlin/src/HAL/HAL_DUE/backtrace/unwarm.h @@ -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 */ + + diff --git a/Marlin/src/HAL/HAL_DUE/backtrace/unwarm_arm.c b/Marlin/src/HAL/HAL_DUE/backtrace/unwarm_arm.c new file mode 100644 index 000000000..9170534b5 --- /dev/null +++ b/Marlin/src/HAL/HAL_DUE/backtrace/unwarm_arm.c @@ -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 +#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 diff --git a/Marlin/src/HAL/HAL_DUE/backtrace/unwarm_thumb.c b/Marlin/src/HAL/HAL_DUE/backtrace/unwarm_thumb.c new file mode 100644 index 000000000..b3fe2161c --- /dev/null +++ b/Marlin/src/HAL/HAL_DUE/backtrace/unwarm_thumb.c @@ -0,0 +1,626 @@ +/*************************************************************************** + * ARM Stack Unwinder, Michael.McTernan.2001@cs.bris.ac.uk + * Updated, adapted and several bug fixes on 2018 by Eduardo José Tagle + * + * This program is PUBLIC DOMAIN. + * This means that there is no copyright and anyone is able to take a copy + * for free and use it as they wish, with or without modifications, and in + * any context, commercially or otherwise. The only limitation is that I + * don't guarantee that the software is fit for any purpose or accept any + * liability for it's use or misuse - this software is without warranty. + *************************************************************************** + * File Description: Abstract interpretation for Thumb mode. + **************************************************************************/ + +#ifdef ARDUINO_ARCH_SAM + +#define MODULE_NAME "UNWARM_THUMB" + +#include +#include "unwarm.h" + +/** Sign extend an 11 bit value. + * This function simply inspects bit 11 of the input \a value, and if + * set, the top 5 bits are set to give a 2's compliment signed value. + * \param value The value to sign extend. + * \return The signed-11 bit value stored in a 16bit data type. + */ +static int16_t signExtend11(uint16_t value) { + + if(value & 0x400) { + value |= 0xf800; + } + + return value; +} + +UnwResult UnwStartThumb(UnwState * const state) { + + bool found = false; + uint16_t t = UNW_MAX_INSTR_COUNT; + + do { + uint16_t instr; + + /* Attempt to read the instruction */ + if(!state->cb->readH(state->regData[15].v & (~0x1), &instr)) { + return UNWIND_IREAD_H_FAIL; + } + + UnwPrintd4("T %x %x %04x:", state->regData[13].v, state->regData[15].v, instr); + + /* Check that the PC is still on Thumb alignment */ + if(!(state->regData[15].v & 0x1)) { + UnwPrintd1("\nError: PC misalignment\n"); + return UNWIND_INCONSISTENT; + } + + /* Check that the SP and PC have not been invalidated */ + if(!M_IsOriginValid(state->regData[13].o) || !M_IsOriginValid(state->regData[15].o)) { + UnwPrintd1("\nError: PC or SP invalidated\n"); + return UNWIND_INCONSISTENT; + } + + /* Format 1: Move shifted register + * LSL Rd, Rs, #Offset5 + * LSR Rd, Rs, #Offset5 + * ASR Rd, Rs, #Offset5 + */ + if((instr & 0xe000) == 0x0000 && (instr & 0x1800) != 0x1800) { + bool signExtend; + uint8_t op = (instr & 0x1800) >> 11; + uint8_t offset5 = (instr & 0x07c0) >> 6; + uint8_t rs = (instr & 0x0038) >> 3; + uint8_t rd = (instr & 0x0007); + + switch(op) { + case 0: /* LSL */ + UnwPrintd6("LSL r%d, r%d, #%d\t; r%d %s", rd, rs, offset5, rs, M_Origin2Str(state->regData[rs].o)); + state->regData[rd].v = state->regData[rs].v << offset5; + state->regData[rd].o = state->regData[rs].o; + state->regData[rd].o |= REG_VAL_ARITHMETIC; + break; + + case 1: /* LSR */ + UnwPrintd6("LSR r%d, r%d, #%d\t; r%d %s", rd, rs, offset5, rs, M_Origin2Str(state->regData[rs].o)); + state->regData[rd].v = state->regData[rs].v >> offset5; + state->regData[rd].o = state->regData[rs].o; + state->regData[rd].o |= REG_VAL_ARITHMETIC; + break; + + case 2: /* ASR */ + UnwPrintd6("ASL r%d, r%d, #%d\t; r%d %s", rd, rs, offset5, rs, M_Origin2Str(state->regData[rs].o)); + + signExtend = (state->regData[rs].v & 0x8000) ? true : false; + state->regData[rd].v = state->regData[rs].v >> offset5; + if(signExtend) { + state->regData[rd].v |= 0xffffffff << (32 - offset5); + } + state->regData[rd].o = state->regData[rs].o; + state->regData[rd].o |= REG_VAL_ARITHMETIC; + break; + } + } + /* Format 2: add/subtract + * ADD Rd, Rs, Rn + * ADD Rd, Rs, #Offset3 + * SUB Rd, Rs, Rn + * SUB Rd, Rs, #Offset3 + */ + else if((instr & 0xf800) == 0x1800) { + bool I = (instr & 0x0400) ? true : false; + bool op = (instr & 0x0200) ? true : false; + uint8_t rn = (instr & 0x01c0) >> 6; + uint8_t rs = (instr & 0x0038) >> 3; + uint8_t rd = (instr & 0x0007); + + /* Print decoding */ + UnwPrintd6("%s r%d, r%d, %c%d\t;",op ? "SUB" : "ADD",rd, rs,I ? '#' : 'r',rn); + UnwPrintd5("r%d %s, r%d %s",rd, M_Origin2Str(state->regData[rd].o),rs, M_Origin2Str(state->regData[rs].o)); + if(!I) { + + UnwPrintd3(", r%d %s", rn, M_Origin2Str(state->regData[rn].o)); + + /* Perform calculation */ + if(op) { + state->regData[rd].v = state->regData[rs].v - state->regData[rn].v; + } + else { + state->regData[rd].v = state->regData[rs].v + state->regData[rn].v; + } + + /* Propagate the origin */ + if(M_IsOriginValid(state->regData[rs].v) && + M_IsOriginValid(state->regData[rn].v)) { + state->regData[rd].o = state->regData[rs].o; + state->regData[rd].o |= REG_VAL_ARITHMETIC; + } + else { + state->regData[rd].o = REG_VAL_INVALID; + } + } + else { + /* Perform calculation */ + if(op) { + state->regData[rd].v = state->regData[rs].v - rn; + } + else { + state->regData[rd].v = state->regData[rs].v + rn; + } + + /* Propagate the origin */ + state->regData[rd].o = state->regData[rs].o; + state->regData[rd].o |= REG_VAL_ARITHMETIC; + } + } + /* Format 3: move/compare/add/subtract immediate + * MOV Rd, #Offset8 + * CMP Rd, #Offset8 + * ADD Rd, #Offset8 + * SUB Rd, #Offset8 + */ + else if((instr & 0xe000) == 0x2000) { + + uint8_t op = (instr & 0x1800) >> 11; + uint8_t rd = (instr & 0x0700) >> 8; + uint8_t offset8 = (instr & 0x00ff); + + switch(op) { + case 0: /* MOV */ + UnwPrintd3("MOV r%d, #0x%x", rd, offset8); + state->regData[rd].v = offset8; + state->regData[rd].o = REG_VAL_FROM_CONST; + break; + + case 1: /* CMP */ + /* Irrelevant to unwinding */ + UnwPrintd1("CMP ???"); + break; + + case 2: /* ADD */ + UnwPrintd5("ADD r%d, #0x%x\t; r%d %s", rd, offset8, rd, M_Origin2Str(state->regData[rd].o)); + state->regData[rd].v += offset8; + state->regData[rd].o |= REG_VAL_ARITHMETIC; + break; + + case 3: /* SUB */ + UnwPrintd5("SUB r%d, #0x%d\t; r%d %s", rd, offset8, rd, M_Origin2Str(state->regData[rd].o)); + state->regData[rd].v -= offset8; + state->regData[rd].o |= REG_VAL_ARITHMETIC; + break; + } + } + /* Format 4: ALU operations + * AND Rd, Rs + * EOR Rd, Rs + * LSL Rd, Rs + * LSR Rd, Rs + * ASR Rd, Rs + * ADC Rd, Rs + * SBC Rd, Rs + * ROR Rd, Rs + * TST Rd, Rs + * NEG Rd, Rs + * CMP Rd, Rs + * CMN Rd, Rs + * ORR Rd, Rs + * MUL Rd, Rs + * BIC Rd, Rs + * MVN Rd, Rs + */ + else if((instr & 0xfc00) == 0x4000) { + uint8_t op = (instr & 0x03c0) >> 6; + uint8_t rs = (instr & 0x0038) >> 3; + uint8_t rd = (instr & 0x0007); + +#if defined(UNW_DEBUG) + static const char * const mnu[16] = { + "AND", "EOR", "LSL", "LSR", + "ASR", "ADC", "SBC", "ROR", + "TST", "NEG", "CMP", "CMN", + "ORR", "MUL", "BIC", "MVN" }; +#endif + /* Print the mnemonic and registers */ + switch(op) { + case 0: /* AND */ + case 1: /* EOR */ + case 2: /* LSL */ + case 3: /* LSR */ + case 4: /* ASR */ + case 7: /* ROR */ + case 9: /* NEG */ + case 12: /* ORR */ + case 13: /* MUL */ + case 15: /* MVN */ + UnwPrintd8("%s r%d ,r%d\t; r%d %s, r%d %s",mnu[op],rd, rs, rd, M_Origin2Str(state->regData[rd].o), rs, M_Origin2Str(state->regData[rs].o)); + break; + + case 5: /* ADC */ + case 6: /* SBC */ + UnwPrintd4("%s r%d, r%d", mnu[op], rd, rs); + break; + + case 8: /* TST */ + case 10: /* CMP */ + case 11: /* CMN */ + /* Irrelevant to unwinding */ + UnwPrintd2("%s ???", mnu[op]); + break; + + case 14: /* BIC */ + UnwPrintd5("r%d ,r%d\t; r%d %s", rd, rs, rs, M_Origin2Str(state->regData[rs].o)); + break; + } + + /* Perform operation */ + switch(op) { + case 0: /* AND */ + state->regData[rd].v &= state->regData[rs].v; + break; + + case 1: /* EOR */ + state->regData[rd].v ^= state->regData[rs].v; + break; + + case 2: /* LSL */ + state->regData[rd].v <<= state->regData[rs].v; + break; + + case 3: /* LSR */ + state->regData[rd].v >>= state->regData[rs].v; + break; + + case 4: /* ASR */ + if(state->regData[rd].v & 0x80000000) { + state->regData[rd].v >>= state->regData[rs].v; + state->regData[rd].v |= 0xffffffff << (32 - state->regData[rs].v); + } + else { + state->regData[rd].v >>= state->regData[rs].v; + } + + break; + + case 5: /* ADC */ + case 6: /* SBC */ + case 8: /* TST */ + case 10: /* CMP */ + case 11: /* CMN */ + break; + + case 7: /* ROR */ + state->regData[rd].v = (state->regData[rd].v >> state->regData[rs].v) | + (state->regData[rd].v << (32 - state->regData[rs].v)); + break; + + case 9: /* NEG */ + state->regData[rd].v = -state->regData[rs].v; + break; + + case 12: /* ORR */ + state->regData[rd].v |= state->regData[rs].v; + break; + + case 13: /* MUL */ + state->regData[rd].v *= state->regData[rs].v; + break; + + case 14: /* BIC */ + state->regData[rd].v &= ~state->regData[rs].v; + break; + + case 15: /* MVN */ + state->regData[rd].v = ~state->regData[rs].v; + break; + } + + /* Propagate data origins */ + switch(op) { + case 0: /* AND */ + case 1: /* EOR */ + case 2: /* LSL */ + case 3: /* LSR */ + case 4: /* ASR */ + case 7: /* ROR */ + case 12: /* ORR */ + case 13: /* MUL */ + case 14: /* BIC */ + if(M_IsOriginValid(state->regData[rd].o) && M_IsOriginValid(state->regData[rs].o)) { + state->regData[rd].o = state->regData[rs].o; + state->regData[rd].o |= REG_VAL_ARITHMETIC; + } + else { + state->regData[rd].o = REG_VAL_INVALID; + } + break; + + case 5: /* ADC */ + case 6: /* SBC */ + /* C-bit not tracked */ + state->regData[rd].o = REG_VAL_INVALID; + break; + + case 8: /* TST */ + case 10: /* CMP */ + case 11: /* CMN */ + /* Nothing propagated */ + break; + + case 9: /* NEG */ + case 15: /* MVN */ + state->regData[rd].o = state->regData[rs].o; + state->regData[rd].o |= REG_VAL_ARITHMETIC; + break; + } + } + /* Format 5: Hi register operations/branch exchange + * ADD Rd, Hs + * ADD Hd, Rs + * ADD Hd, Hs + */ + else if((instr & 0xfc00) == 0x4400) { + uint8_t op = (instr & 0x0300) >> 8; + bool h1 = (instr & 0x0080) ? true: false; + bool h2 = (instr & 0x0040) ? true: false; + uint8_t rhs = (instr & 0x0038) >> 3; + uint8_t rhd = (instr & 0x0007); + + /* Adjust the register numbers */ + if(h2) + rhs += 8; + if(h1) + rhd += 8; + + if(op != 3 && !h1 && !h2) { + UnwPrintd1("\nError: h1 or h2 must be set for ADD, CMP or MOV\n"); + return UNWIND_ILLEGAL_INSTR; + } + + switch(op) { + case 0: /* ADD */ + UnwPrintd5("ADD r%d, r%d\t; r%d %s", rhd, rhs, rhs, M_Origin2Str(state->regData[rhs].o)); + state->regData[rhd].v += state->regData[rhs].v; + state->regData[rhd].o = state->regData[rhs].o; + state->regData[rhd].o |= REG_VAL_ARITHMETIC; + break; + + case 1: /* CMP */ + /* Irrelevant to unwinding */ + UnwPrintd1("CMP ???"); + break; + + case 2: /* MOV */ + UnwPrintd5("MOV r%d, r%d\t; r%d %s", rhd, rhs, rhd, M_Origin2Str(state->regData[rhs].o)); + state->regData[rhd].v = state->regData[rhs].v; + state->regData[rhd].o = state->regData[rhd].o; + break; + + case 3: /* BX */ + UnwPrintd4("BX r%d\t; r%d %s\n", rhs, rhs, M_Origin2Str(state->regData[rhs].o)); + + /* Only follow BX if the data was from the stack */ + if(state->regData[rhs].o == REG_VAL_FROM_STACK) { + UnwPrintd2(" Return PC=0x%x\n", state->regData[rhs].v & (~0x1)); + + /* Report the return address, including mode bit */ + if(!UnwReportRetAddr(state, state->regData[rhs].v)) { + return UNWIND_TRUNCATED; + } + + /* Update the PC */ + state->regData[15].v = state->regData[rhs].v; + + /* Determine the new mode */ + if(state->regData[rhs].v & 0x1) { + /* Branching to THUMB */ + + /* Account for the auto-increment which isn't needed */ + state->regData[15].v -= 2; + } + else { + /* Branch to ARM */ + return UnwStartArm(state); + } + } + else { + UnwPrintd4("\nError: BX to invalid register: r%d = 0x%x (%s)\n", rhs, state->regData[rhs].o, M_Origin2Str(state->regData[rhs].o)); + return UNWIND_FAILURE; + } + } + } + /* Format 9: PC-relative load + * LDR Rd,[PC, #imm] + */ + else if((instr & 0xf800) == 0x4800) { + uint8_t rd = (instr & 0x0700) >> 8; + uint8_t word8 = (instr & 0x00ff); + uint32_t address; + + /* Compute load address, adding a word to account for prefetch */ + address = (state->regData[15].v & (~0x3)) + 4 + (word8 << 2); + + UnwPrintd3("LDR r%d, 0x%08x", rd, address); + + if(!UnwMemReadRegister(state, address, &state->regData[rd])) { + return UNWIND_DREAD_W_FAIL; + } + } + /* Format 13: add offset to Stack Pointer + * ADD sp,#+imm + * ADD sp,#-imm + */ + else if((instr & 0xff00) == 0xB000) { + uint8_t value = (instr & 0x7f) * 4; + + /* Check the negative bit */ + if((instr & 0x80) != 0) { + UnwPrintd2("SUB sp,#0x%x", value); + state->regData[13].v -= value; + } + else { + UnwPrintd2("ADD sp,#0x%x", value); + state->regData[13].v += value; + } + } + /* Format 14: push/pop registers + * PUSH {Rlist} + * PUSH {Rlist, LR} + * POP {Rlist} + * POP {Rlist, PC} + */ + else if((instr & 0xf600) == 0xb400) { + bool L = (instr & 0x0800) ? true : false; + bool R = (instr & 0x0100) ? true : false; + uint8_t rList = (instr & 0x00ff); + + if(L) { + uint8_t r; + + /* Load from memory: POP */ + UnwPrintd2("POP {Rlist%s}\n", R ? ", PC" : ""); + + for(r = 0; r < 8; r++) { + if(rList & (0x1 << r)) { + + /* Read the word */ + if(!UnwMemReadRegister(state, state->regData[13].v, &state->regData[r])) { + return UNWIND_DREAD_W_FAIL; + } + + /* Alter the origin to be from the stack if it was valid */ + if(M_IsOriginValid(state->regData[r].o)) { + state->regData[r].o = REG_VAL_FROM_STACK; + } + + state->regData[13].v += 4; + + UnwPrintd3(" r%d = 0x%08x\n", r, state->regData[r].v); + } + } + + /* Check if the PC is to be popped */ + if(R) { + /* Get the return address */ + if(!UnwMemReadRegister(state, state->regData[13].v, &state->regData[15])) { + return UNWIND_DREAD_W_FAIL; + } + + /* Alter the origin to be from the stack if it was valid */ + if(!M_IsOriginValid(state->regData[15].o)) { + /* Return address is not valid */ + UnwPrintd1("PC popped with invalid address\n"); + return UNWIND_FAILURE; + } + else { + /* The bottom bit should have been set to indicate that + * the caller was from Thumb. This would allow return + * by BX for interworking APCS. + */ + if((state->regData[15].v & 0x1) == 0) { + UnwPrintd2("Warning: Return address not to Thumb: 0x%08x\n", state->regData[15].v); + + /* Pop into the PC will not switch mode */ + return UNWIND_INCONSISTENT; + } + + /* Store the return address */ + if(!UnwReportRetAddr(state, state->regData[15].v)) { + return UNWIND_TRUNCATED; + } + + /* Now have the return address */ + UnwPrintd2(" Return PC=%x\n", state->regData[15].v); + + /* Update the pc */ + state->regData[13].v += 4; + + /* Compensate for the auto-increment, which isn't needed here */ + state->regData[15].v -= 2; + } + } + } + else { + int8_t r; + + /* Store to memory: PUSH */ + UnwPrintd2("PUSH {Rlist%s}", R ? ", LR" : ""); + + /* Check if the LR is to be pushed */ + if(R) { + UnwPrintd3("\n lr = 0x%08x\t; %s", state->regData[14].v, M_Origin2Str(state->regData[14].o)); + + state->regData[13].v -= 4; + + /* Write the register value to memory */ + if(!UnwMemWriteRegister(state, state->regData[13].v, &state->regData[14])) { + return UNWIND_DWRITE_W_FAIL; + } + } + + for(r = 7; r >= 0; r--) { + if(rList & (0x1 << r)) { + UnwPrintd4("\n r%d = 0x%08x\t; %s", r, state->regData[r].v, M_Origin2Str(state->regData[r].o)); + + state->regData[13].v -= 4; + + if(!UnwMemWriteRegister(state, state->regData[13].v, &state->regData[r])) { + return UNWIND_DWRITE_W_FAIL; + } + } + } + } + } + /* Format 18: unconditional branch + * B label + */ + else if((instr & 0xf800) == 0xe000) { + int16_t branchValue = signExtend11(instr & 0x07ff); + + /* Branch distance is twice that specified in the instruction. */ + branchValue *= 2; + + UnwPrintd2("B %d \n", branchValue); + + /* Update PC */ + state->regData[15].v += branchValue; + + /* Need to advance by a word to account for pre-fetch. + * Advance by a half word here, allowing the normal address + * advance to account for the other half word. + */ + state->regData[15].v += 2; + + /* Display PC of next instruction */ + UnwPrintd2(" New PC=%x", state->regData[15].v + 2); + + } + else { + UnwPrintd1("????"); + + /* Unknown/undecoded. May alter some register, so invalidate file */ + UnwInvalidateRegisterFile(state->regData); + } + + UnwPrintd1("\n"); + + /* Should never hit the reset vector */ + if(state->regData[15].v == 0) + return UNWIND_RESET; + + /* Check next address */ + state->regData[15].v += 2; + + /* Garbage collect the memory hash (used only for the stack) */ + UnwMemHashGC(state); + + t--; + if(t == 0) + return UNWIND_EXHAUSTED; + + } while(!found); + + return UNWIND_SUCCESS; +} + +#endif + diff --git a/Marlin/src/HAL/HAL_DUE/backtrace/unwarmbytab.c b/Marlin/src/HAL/HAL_DUE/backtrace/unwarmbytab.c new file mode 100644 index 000000000..334654601 --- /dev/null +++ b/Marlin/src/HAL/HAL_DUE/backtrace/unwarmbytab.c @@ -0,0 +1,443 @@ +/* + * Libbacktrace + * Copyright 2015 Stephen Street + * + * 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 +#include + +/* 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 + diff --git a/Marlin/src/HAL/HAL_DUE/backtrace/unwarmbytab.h b/Marlin/src/HAL/HAL_DUE/backtrace/unwarmbytab.h new file mode 100644 index 000000000..cdf114c51 --- /dev/null +++ b/Marlin/src/HAL/HAL_DUE/backtrace/unwarmbytab.h @@ -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 */ diff --git a/Marlin/src/HAL/HAL_DUE/backtrace/unwarmmem.c b/Marlin/src/HAL/HAL_DUE/backtrace/unwarmmem.c new file mode 100644 index 000000000..57fb011fe --- /dev/null +++ b/Marlin/src/HAL/HAL_DUE/backtrace/unwarmmem.c @@ -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 +#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 diff --git a/Marlin/src/HAL/HAL_DUE/backtrace/unwarmmem.h b/Marlin/src/HAL/HAL_DUE/backtrace/unwarmmem.h new file mode 100644 index 000000000..107286b82 --- /dev/null +++ b/Marlin/src/HAL/HAL_DUE/backtrace/unwarmmem.h @@ -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 + diff --git a/Marlin/src/HAL/HAL_DUE/backtrace/unwinder.c b/Marlin/src/HAL/HAL_DUE/backtrace/unwinder.c new file mode 100644 index 000000000..e1f1d22c7 --- /dev/null +++ b/Marlin/src/HAL/HAL_DUE/backtrace/unwinder.c @@ -0,0 +1,98 @@ +/*************************************************************************** + * ARM Stack Unwinder, Michael.McTernan.2001@cs.bris.ac.uk + * Updated, adapted and several bug fixes on 2018 by Eduardo José Tagle + * + * This program is PUBLIC DOMAIN. + * This means that there is no copyright and anyone is able to take a copy + * for free and use it as they wish, with or without modifications, and in + * any context, commercially or otherwise. The only limitation is that I + * don't guarantee that the software is fit for any purpose or accept any + * liability for it's use or misuse - this software is without warranty. + *************************************************************************** + * File Description: Implementation of the interface into the ARM unwinder. + **************************************************************************/ + +#ifdef ARDUINO_ARCH_SAM + +#define MODULE_NAME "UNWINDER" + +#include +#include +#include "unwinder.h" +#include "unwarm.h" +#include "unwarmbytab.h" + + +/* These symbols point to the start and end of stack */ +extern const int _sstack; +extern const int _estack; + +/* These symbols point to the start and end of the code section */ +extern const int _sfixed; +extern const int _efixed; + +/* These symbols point to the start and end of initialized data (could be SRAM functions!) */ +extern const int _srelocate; +extern const int _erelocate; + + +/* Validate stack pointer (SP): It must be in the stack area */ +static inline __attribute__((always_inline)) UnwResult validate_sp(const void* sp) { + + // SP must point into the allocated stack area + if ((uint32_t)sp >= (uint32_t)&_sstack && (uint32_t)sp <= (uint32_t)&_estack) + return UNWIND_SUCCESS; + + return UNWIND_INVALID_SP; +} + +/* Validate code pointer (PC): It must be either in TEXT or in SRAM */ +static inline __attribute__((always_inline)) UnwResult validate_pc(const void* pc) { + + // PC must point into the text (CODE) area + if ((uint32_t)pc >= (uint32_t)&_sfixed && (uint32_t)pc <= (uint32_t)&_efixed) + return UNWIND_SUCCESS; + + // Or into the SRAM function area + if ((uint32_t)pc >= (uint32_t)&_srelocate && (uint32_t)pc <= (uint32_t)&_erelocate) + return UNWIND_SUCCESS; + + return UNWIND_INVALID_PC; +} + +/* These symbols point to the unwind index and should be provide by the linker script */ +extern const UnwTabEntry __exidx_start[]; +extern const UnwTabEntry __exidx_end[]; + +// Detect if unwind information is present or not +static int HasUnwindTableInfo(void) { + return ((char*)(&__exidx_end) - (char*)(&__exidx_start)) > 16 ? 1 : 0; // 16 because there are default entries we can´t supress +} + +UnwResult UnwindStart(UnwindFrame* frame, const UnwindCallbacks *cb, void *data) +{ + if (HasUnwindTableInfo()) { + + /* We have unwind information tables */ + return UnwindByTableStart(frame, cb, data); + + } else { + + /* We don't have unwind information tables */ + UnwState state; + + /* Initialise the unwinding state */ + UnwInitState(&state, cb, data, frame->pc, frame->sp); + + /* Check the Thumb bit */ + if(frame->pc & 0x1) { + return UnwStartThumb(&state); + } + else { + return UnwStartArm(&state); + } + } +} +#endif + + diff --git a/Marlin/src/HAL/HAL_DUE/backtrace/unwinder.h b/Marlin/src/HAL/HAL_DUE/backtrace/unwinder.h new file mode 100644 index 000000000..6a0524c75 --- /dev/null +++ b/Marlin/src/HAL/HAL_DUE/backtrace/unwinder.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, 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 +#include + +/** \def UNW_DEBUG + * If this define is set, additional information will be produced while + * unwinding the stack to allow debug of the unwind module itself. + */ +/* #define UNW_DEBUG 1 */ + +/*************************************************************************** + * Type Definitions + **************************************************************************/ + +/** Possible results for UnwindStart to return. + */ +typedef enum { + /** Unwinding was successful and complete. */ + UNWIND_SUCCESS = 0, + + /** Not an error: More frames are available. */ + UNWIND_MORE_AVAILABLE = 1, + + /** Unsupported DWARF unwind personality. */ + UNWIND_UNSUPPORTED_DWARF_PERSONALITY = -1, + + /** Refused to perform unwind. */ + UNWIND_REFUSED = -2, + + /** Reached an invalid SP. */ + UNWIND_INVALID_SP = -3, + + /** Reached an invalid PC */ + UNWIND_INVALID_PC = -4, + + /** Unsupported DWARF instruction */ + UNWIND_UNSUPPORTED_DWARF_INSTR = -5, + + /** More than UNW_MAX_INSTR_COUNT instructions were interpreted. */ + UNWIND_EXHAUSTED = -6, + + /** Unwinding stopped because the reporting func returned false. */ + UNWIND_TRUNCATED = -7, + + /** Read data was found to be inconsistent. */ + UNWIND_INCONSISTENT = -8, + + /** Unsupported instruction or data found. */ + UNWIND_UNSUPPORTED = -9, + + /** General failure. */ + UNWIND_FAILURE = -10, + + /** Illegal instruction. */ + UNWIND_ILLEGAL_INSTR = -11, + + /** Unwinding hit the reset vector. */ + UNWIND_RESET = -12, + + /** Failed read for an instruction word. */ + UNWIND_IREAD_W_FAIL = -13, + + /** Failed read for an instruction half-word. */ + UNWIND_IREAD_H_FAIL = -14, + + /** Failed read for an instruction byte. */ + UNWIND_IREAD_B_FAIL = -15, + + /** Failed read for a data word. */ + UNWIND_DREAD_W_FAIL = -16, + + /** Failed read for a data half-word. */ + UNWIND_DREAD_H_FAIL = -17, + + /** Failed read for a data byte. */ + UNWIND_DREAD_B_FAIL = -18, + + /** Failed write for a data word. */ + UNWIND_DWRITE_W_FAIL = -19 + +} UnwResult; + +/** A backtrace report */ +typedef struct { + uint32_t function; /** Starts address of function */ + const char *name; /** Function name, or null if not available */ + uint32_t address; /** PC on that function */ +} UnwReport; + +/** Type for function pointer for result callback. + * The function is passed two parameters, the first is a void * pointer, + * and the second is the return address of the function. The bottom bit + * of the passed address indicates the execution mode; if it is set, + * the execution mode at the return address is Thumb, otherwise it is + * ARM. + * + * The return value of this function determines whether unwinding should + * continue or not. If true is returned, unwinding will continue and the + * report function maybe called again in future. If false is returned, + * unwinding will stop with UnwindStart() returning UNWIND_TRUNCATED. + */ +typedef bool (*UnwindReportFunc)(void* data, const UnwReport* bte); + +/** Structure that holds memory callback function pointers. + */ +typedef struct { + + /** Report an unwind result. */ + UnwindReportFunc report; + + /** Read a 32 bit word from memory. + * The memory address to be read is passed as \a address, and + * \a *val is expected to be populated with the read value. + * If the address cannot or should not be read, false can be + * returned to indicate that unwinding should stop. If true + * is returned, \a *val is assumed to be valid and unwinding + * will continue. + */ + bool (*readW)(const uint32_t address, uint32_t *val); + + /** Read a 16 bit half-word from memory. + * This function has the same usage as for readW, but is expected + * to read only a 16 bit value. + */ + bool (*readH)(const uint32_t address, uint16_t *val); + + /** Read a byte from memory. + * This function has the same usage as for readW, but is expected + * to read only an 8 bit value. + */ + bool (*readB)(const uint32_t address, uint8_t *val); + +#if defined(UNW_DEBUG) + /** Print a formatted line for debug. */ + int (*printf)(const char *format, ...); +#endif +} UnwindCallbacks; + +/* A frame */ +typedef struct { + uint32_t fp; + uint32_t sp; + uint32_t lr; + uint32_t pc; +} UnwindFrame; + +#ifdef __cplusplus +extern "C" { +#endif + +/** Start unwinding the current stack. + * This will unwind the stack starting at the PC value supplied to in the + * link register (i.e. not a normal register) and the stack pointer value + * supplied. + */ +UnwResult UnwindStart(UnwindFrame* frame, const UnwindCallbacks *cb, void *data); + +#ifdef __cplusplus +} +#endif + +#endif /* UNWINDER_H */ From 328edea03ab115513d1710b163cdf4d15b539af0 Mon Sep 17 00:00:00 2001 From: etagle Date: Mon, 26 Mar 2018 03:42:54 -0300 Subject: [PATCH 2/3] Several fixes to the backtracer. Tested ant it works --- Marlin/src/HAL/HAL_DUE/DebugMonitor_Due.cpp | 110 ++--- Marlin/src/HAL/HAL_DUE/backtrace/unwarm.c | 6 +- .../src/HAL/HAL_DUE/backtrace/unwarm_thumb.c | 381 +++++++++++++++++- Marlin/src/HAL/HAL_DUE/backtrace/unwinder.c | 45 +-- Marlin/src/HAL/HAL_DUE/backtrace/unwinder.h | 7 +- 5 files changed, 444 insertions(+), 105 deletions(-) diff --git a/Marlin/src/HAL/HAL_DUE/DebugMonitor_Due.cpp b/Marlin/src/HAL/HAL_DUE/DebugMonitor_Due.cpp index f9633f2be..cd00165bc 100644 --- a/Marlin/src/HAL/HAL_DUE/DebugMonitor_Due.cpp +++ b/Marlin/src/HAL/HAL_DUE/DebugMonitor_Due.cpp @@ -34,19 +34,6 @@ // Serial interrupt routines or any C runtime, as we don't know the // state we are when running them - -/* These symbols point to the start and end of stack */ -extern "C" const int _sstack; -extern "C" const int _estack; - -/* These symbols point to the start and end of the code section */ -extern "C" const int _sfixed; -extern "C" const int _efixed; - -/* These symbols point to the start and end of initialized data (could be SRAM functions!) */ -extern "C" const int _srelocate; -extern "C" const int _erelocate; - // A SW memory barrier, to ensure GCC does not overoptimize loops #define sw_barrier() asm volatile("": : :"memory"); @@ -126,25 +113,20 @@ static void TXDec(uint32_t v) { } /* Validate address */ -static bool validate_addr(uint16_t addr) { +static bool validate_addr(uint32_t addr) { - // PC must point into the text (CODE) area - if (addr >= (uint32_t)&_sfixed && addr <= (uint32_t)&_efixed) + // Address must be in SRAM (0x20070000 - 0x20088000) + if (addr >= 0x20070000 && addr < 0x20088000) return true; - // Or into the SRAM function area - if (addr >= (uint32_t)&_srelocate && addr <= (uint32_t)&_erelocate) - return true; - - // SP must point into the allocated stack area - if (addr >= (uint32_t)&_sstack && addr <= (uint32_t)&_estack) + // Or in FLASH (0x00080000 - 0x00100000) + if (addr >= 0x00080000 && addr < 0x00100000) return true; return false; } static bool UnwReadW(const uint32_t a, uint32_t *v) { - if (!validate_addr(a)) return false; @@ -153,7 +135,6 @@ static bool UnwReadW(const uint32_t a, uint32_t *v) { } static bool UnwReadH(const uint32_t a, uint16_t *v) { - if (!validate_addr(a)) return false; @@ -162,7 +143,6 @@ static bool UnwReadH(const uint32_t a, uint16_t *v) { } static bool UnwReadB(const uint32_t a, uint8_t *v) { - if (!validate_addr(a)) return false; @@ -173,13 +153,27 @@ static bool UnwReadB(const uint32_t a, uint8_t *v) { // Dump a backtrace entry static bool UnwReportOut(void* ctx, const UnwReport* bte) { + int* p = (int*)ctx; - TX(bte->name?bte->name:"unknown"); TX('@');TXHex(bte->function); + (*p)++; + TX('#'); TXDec(*p); TX(" : "); + TX(bte->name?bte->name:"unknown"); TX('@'); TXHex(bte->function); TX('+'); TXDec(bte->address - bte->function); TX(" PC:");TXHex(bte->address); TX('\n'); return true; } +#if defined(UNW_DEBUG) +void UnwPrintf(const char* format, ...) { + char dest[256]; + va_list argptr; + va_start(argptr, format); + vsprintf(dest, format, argptr); + va_end(argptr); + TX(&dest[0]); +} +#endif + /* Table of function pointers for passing to the unwinder */ static const UnwindCallbacks UnwCallbacks = { UnwReportOut, @@ -187,7 +181,7 @@ static const UnwindCallbacks UnwCallbacks = { UnwReadH, UnwReadB #if defined(UNW_DEBUG) - ,printf + ,UnwPrintf #endif }; @@ -201,24 +195,27 @@ static const UnwindCallbacks UnwCallbacks = { * The function ends with a BKPT instruction to force control back into the debugger */ extern "C" -void HardFault_HandlerC(unsigned long *hardfault_args, unsigned long cause) { +void HardFault_HandlerC(unsigned long *sp, unsigned long lr, unsigned long cause) { static const char* causestr[] = { "NMI","Hard","Mem","Bus","Usage","Debug","WDT","RSTC" }; + UnwindFrame btf; + // Dump report to the Programming port (interrupts are DISABLED) TXBegin(); TX("\n\n## Software Fault detected ##\n"); TX("Cause: "); TX(causestr[cause]); TX('\n'); - TX("R0 : "); TXHex(((unsigned long)hardfault_args[0])); TX('\n'); - TX("R1 : "); TXHex(((unsigned long)hardfault_args[1])); TX('\n'); - TX("R2 : "); TXHex(((unsigned long)hardfault_args[2])); TX('\n'); - TX("R3 : "); TXHex(((unsigned long)hardfault_args[3])); TX('\n'); - TX("R12 : "); TXHex(((unsigned long)hardfault_args[4])); TX('\n'); - TX("LR : "); TXHex(((unsigned long)hardfault_args[5])); TX('\n'); - TX("PC : "); TXHex(((unsigned long)hardfault_args[6])); TX('\n'); - TX("PSR : "); TXHex(((unsigned long)hardfault_args[7])); TX('\n'); + + TX("R0 : "); TXHex(((unsigned long)sp[0])); TX('\n'); + TX("R1 : "); TXHex(((unsigned long)sp[1])); TX('\n'); + TX("R2 : "); TXHex(((unsigned long)sp[2])); TX('\n'); + TX("R3 : "); TXHex(((unsigned long)sp[3])); TX('\n'); + TX("R12 : "); TXHex(((unsigned long)sp[4])); TX('\n'); + TX("LR : "); TXHex(((unsigned long)sp[5])); TX('\n'); + TX("PC : "); TXHex(((unsigned long)sp[6])); TX('\n'); + TX("PSR : "); TXHex(((unsigned long)sp[7])); TX('\n'); // Configurable Fault Status Register // Consists of MMSR, BFSR and UFSR @@ -241,14 +238,18 @@ void HardFault_HandlerC(unsigned long *hardfault_args, unsigned long cause) { // Bus Fault Address Register TX("BFAR : "); TXHex((*((volatile unsigned long *)(0xE000ED38)))); TX('\n'); + TX("ExcLR: "); TXHex(lr); TX('\n'); + TX("ExcSP: "); TXHex((unsigned long)sp); TX('\n'); + + btf.sp = ((unsigned long)sp) + 8*4; // The original stack pointer + btf.fp = btf.sp; + btf.lr = ((unsigned long)sp[5]); + btf.pc = ((unsigned long)sp[6]) | 1; // Force Thumb, as CORTEX only support it + // Perform a backtrace TX("\nBacktrace:\n\n"); - UnwindFrame btf; - btf.sp = ((unsigned long)hardfault_args[7]); - btf.fp = btf.sp; - btf.lr = ((unsigned long)hardfault_args[5]); - btf.pc = ((unsigned long)hardfault_args[6]); - UnwindStart(&btf, &UnwCallbacks, nullptr); + int ctr = 0; + UnwindStart(&btf, &UnwCallbacks, &ctr); // Disable all NVIC interrupts NVIC->ICER[0] = 0xFFFFFFFF; @@ -274,7 +275,8 @@ __attribute__((naked)) void NMI_Handler(void) { " ite eq \n" " mrseq r0, msp \n" " mrsne r0, psp \n" - " mov r1,#0 \n" + " mov r1,lr \n" + " mov r2,#0 \n" " b HardFault_HandlerC \n" ); } @@ -285,7 +287,8 @@ __attribute__((naked)) void HardFault_Handler(void) { " ite eq \n" " mrseq r0, msp \n" " mrsne r0, psp \n" - " mov r1,#1 \n" + " mov r1,lr \n" + " mov r2,#1 \n" " b HardFault_HandlerC \n" ); } @@ -296,7 +299,8 @@ __attribute__((naked)) void MemManage_Handler(void) { " ite eq \n" " mrseq r0, msp \n" " mrsne r0, psp \n" - " mov r1,#2 \n" + " mov r1,lr \n" + " mov r2,#2 \n" " b HardFault_HandlerC \n" ); } @@ -307,7 +311,8 @@ __attribute__((naked)) void BusFault_Handler(void) { " ite eq \n" " mrseq r0, msp \n" " mrsne r0, psp \n" - " mov r1,#3 \n" + " mov r1,lr \n" + " mov r2,#3 \n" " b HardFault_HandlerC \n" ); } @@ -318,7 +323,8 @@ __attribute__((naked)) void UsageFault_Handler(void) { " ite eq \n" " mrseq r0, msp \n" " mrsne r0, psp \n" - " mov r1,#4 \n" + " mov r1,lr \n" + " mov r2,#4 \n" " b HardFault_HandlerC \n" ); } @@ -329,18 +335,21 @@ __attribute__((naked)) void DebugMon_Handler(void) { " ite eq \n" " mrseq r0, msp \n" " mrsne r0, psp \n" - " mov r1,#5 \n" + " mov r1,lr \n" + " mov r2,#5 \n" " b HardFault_HandlerC \n" ); } +/* This is NOT an exception, it is an interrupt handler - Nevertheless, the framing is the same */ __attribute__((naked)) void WDT_Handler(void) { __asm volatile ( " tst lr, #4 \n" " ite eq \n" " mrseq r0, msp \n" " mrsne r0, psp \n" - " mov r1,#6 \n" + " mov r1,lr \n" + " mov r2,#6 \n" " b HardFault_HandlerC \n" ); } @@ -351,7 +360,8 @@ __attribute__((naked)) void RSTC_Handler(void) { " ite eq \n" " mrseq r0, msp \n" " mrsne r0, psp \n" - " mov r1,#7 \n" + " mov r1,lr \n" + " mov r2,#7 \n" " b HardFault_HandlerC \n" ); } diff --git a/Marlin/src/HAL/HAL_DUE/backtrace/unwarm.c b/Marlin/src/HAL/HAL_DUE/backtrace/unwarm.c index e43253444..9da3be4e0 100644 --- a/Marlin/src/HAL/HAL_DUE/backtrace/unwarm.c +++ b/Marlin/src/HAL/HAL_DUE/backtrace/unwarm.c @@ -79,7 +79,7 @@ void UnwInitState(UnwState * const state, /**< Pointer to structure to fill. // Detect if function names are available static int __attribute__ ((noinline)) has_function_names(void) { - uint32_t flag_word = ((uint32_t*)&has_function_names)[-1]; + uint32_t flag_word = ((uint32_t*)(((uint32_t)(&has_function_names)) & (-4))) [-1]; return ((flag_word & 0xff000000) == 0xff000000) ? 1 : 0; } @@ -93,7 +93,7 @@ bool UnwReportRetAddr(UnwState * const state, uint32_t addr) { // We found two acceptable values. entry.name = NULL; - entry.address = addr; + entry.address = addr & 0xFFFFFFFE; // Remove Thumb bit entry.function = 0; // If there are function names, try to solve name @@ -108,7 +108,7 @@ bool UnwReportRetAddr(UnwState * const state, uint32_t addr) { uint32_t v; while(state->cb->readW(pf-4,&v)) { - // Check if name descriptor is valid and name is terminated in 0. + // Check if name descriptor is valid if ((v & 0xffffff00) == 0xff000000 && (v & 0xff) > 1) { diff --git a/Marlin/src/HAL/HAL_DUE/backtrace/unwarm_thumb.c b/Marlin/src/HAL/HAL_DUE/backtrace/unwarm_thumb.c index b3fe2161c..2e36a9e8d 100644 --- a/Marlin/src/HAL/HAL_DUE/backtrace/unwarm_thumb.c +++ b/Marlin/src/HAL/HAL_DUE/backtrace/unwarm_thumb.c @@ -38,6 +38,8 @@ UnwResult UnwStartThumb(UnwState * const state) { bool found = false; uint16_t t = UNW_MAX_INSTR_COUNT; + uint32_t lastJumpAddr = 0; // Last JUMP address, to try to detect infinite loops + bool loopDetected = false; // If a loop was detected do { uint16_t instr; @@ -61,12 +63,332 @@ UnwResult UnwStartThumb(UnwState * const state) { return UNWIND_INCONSISTENT; } + /* + * Detect 32bit thumb instructions + */ + if ((instr & 0xe000) == 0xe000 && (instr & 0x1800) != 0) { + uint16_t instr2; + + /* Check next address */ + state->regData[15].v += 2; + + /* Attempt to read the 2nd part of the instruction */ + if(!state->cb->readH(state->regData[15].v & (~0x1), &instr2)) { + return UNWIND_IREAD_H_FAIL; + } + + UnwPrintd3(" %x %04x:", state->regData[15].v, instr2); + + /* + * Load/Store multiple: Only interpret + * PUSH and POP + */ + if ((instr & 0xfe6f) == 0xe82d) { + bool L = (instr & 0x10) ? true : false; + uint16_t rList = instr2; + + if(L) { + uint8_t r; + + /* Load from memory: POP */ + UnwPrintd1("POP {Rlist}\n"); + + /* Load registers from stack */ + for(r = 0; r < 16; r++) { + if(rList & (0x1 << r)) { + + /* Read the word */ + if(!UnwMemReadRegister(state, state->regData[13].v, &state->regData[r])) { + return UNWIND_DREAD_W_FAIL; + } + + /* Alter the origin to be from the stack if it was valid */ + if(M_IsOriginValid(state->regData[r].o)) { + + state->regData[r].o = REG_VAL_FROM_STACK; + + /* If restoring the PC */ + if (r == 15) { + + /* The bottom bit should have been set to indicate that + * the caller was from Thumb. This would allow return + * by BX for interworking APCS. + */ + if((state->regData[15].v & 0x1) == 0) { + UnwPrintd2("Warning: Return address not to Thumb: 0x%08x\n", state->regData[15].v); + + /* Pop into the PC will not switch mode */ + return UNWIND_INCONSISTENT; + } + + /* Store the return address */ + if(!UnwReportRetAddr(state, state->regData[15].v)) { + return UNWIND_TRUNCATED; + } + + /* Now have the return address */ + UnwPrintd2(" Return PC=%x\n", state->regData[15].v); + + /* Compensate for the auto-increment, which isn't needed here */ + state->regData[15].v -= 2; + + } + + } else { + + if (r == 15) { + /* Return address is not valid */ + UnwPrintd1("PC popped with invalid address\n"); + return UNWIND_FAILURE; + } + } + + state->regData[13].v += 4; + + UnwPrintd3(" r%d = 0x%08x\n", r, state->regData[r].v); + } + } + } + else { + int8_t r; + + /* Store to memory: PUSH */ + UnwPrintd1("PUSH {Rlist}"); + + for(r = 15; r >= 0; r--) { + if(rList & (0x1 << r)) { + UnwPrintd4("\n r%d = 0x%08x\t; %s", r, state->regData[r].v, M_Origin2Str(state->regData[r].o)); + + state->regData[13].v -= 4; + + if(!UnwMemWriteRegister(state, state->regData[13].v, &state->regData[r])) { + return UNWIND_DWRITE_W_FAIL; + } + } + } + } + } + /* + * PUSH register + */ + else if (instr == 0xf84d && (instr2 & 0x0fff) == 0x0d04) { + uint8_t r = instr2 >> 12; + + /* Store to memory: PUSH */ + UnwPrintd2("PUSH {R%d}\n", r); + UnwPrintd4("\n r%d = 0x%08x\t; %s", r, state->regData[r].v, M_Origin2Str(state->regData[r].o)); + + state->regData[13].v -= 4; + + if(!UnwMemWriteRegister(state, state->regData[13].v, &state->regData[r])) { + return UNWIND_DWRITE_W_FAIL; + } + } + /* + * POP register + */ + else if (instr == 0xf85d && (instr2 & 0x0fff) == 0x0b04) { + uint8_t r = instr2 >> 12; + + /* Load from memory: POP */ + UnwPrintd2("POP {R%d}\n", r); + + /* Read the word */ + if(!UnwMemReadRegister(state, state->regData[13].v, &state->regData[r])) { + return UNWIND_DREAD_W_FAIL; + } + + /* Alter the origin to be from the stack if it was valid */ + if(M_IsOriginValid(state->regData[r].o)) { + + state->regData[r].o = REG_VAL_FROM_STACK; + + /* If restoring the PC */ + if (r == 15) { + + /* The bottom bit should have been set to indicate that + * the caller was from Thumb. This would allow return + * by BX for interworking APCS. + */ + if((state->regData[15].v & 0x1) == 0) { + UnwPrintd2("Warning: Return address not to Thumb: 0x%08x\n", state->regData[15].v); + + /* Pop into the PC will not switch mode */ + return UNWIND_INCONSISTENT; + } + + /* Store the return address */ + if(!UnwReportRetAddr(state, state->regData[15].v)) { + return UNWIND_TRUNCATED; + } + + /* Now have the return address */ + UnwPrintd2(" Return PC=%x\n", state->regData[15].v); + + /* Compensate for the auto-increment, which isn't needed here */ + state->regData[15].v -= 2; + + } + + } else { + + if (r == 15) { + /* Return address is not valid */ + UnwPrintd1("PC popped with invalid address\n"); + return UNWIND_FAILURE; + } + } + + state->regData[13].v += 4; + + UnwPrintd3(" r%d = 0x%08x\n", r, state->regData[r].v); + } + /* + * Unconditional branch + */ + else if ((instr & 0xf800) == 0xf000 && (instr2 & 0xd000) == 0x9000) { + uint32_t v; + + uint8_t S = (instr & 0x400) >> 10; + uint16_t imm10 = (instr & 0x3ff); + uint8_t J1 = (instr2 & 0x2000) >> 13; + uint8_t J2 = (instr2 & 0x0800) >> 11; + uint16_t imm11 = (instr2 & 0x7ff); + + uint8_t I1 = J1 ^ S ^ 1; + uint8_t I2 = J2 ^ S ^ 1; + uint32_t imm32 = (S << 24) | (I1 << 23) | (I2 << 22) |(imm10 << 12) | (imm11 << 1); + if (S) imm32 |= 0xfe000000; + + UnwPrintd2("B %d \n", imm32); + + /* Update PC */ + state->regData[15].v += imm32; + + /* Need to advance by a word to account for pre-fetch. + * Advance by a half word here, allowing the normal address + * advance to account for the other half word. + */ + state->regData[15].v += 2; + + /* Compute the jump address */ + v = state->regData[15].v + 2; + + /* Display PC of next instruction */ + UnwPrintd2(" New PC=%x", v); + + /* Did we detect an infinite loop ? */ + loopDetected = lastJumpAddr == v; + + /* Remember the last address we jumped to */ + lastJumpAddr = v; + } + + /* + * Branch with link + */ + else if ((instr & 0xf800) == 0xf000 && (instr2 & 0xd000) == 0xd000) { + + uint8_t S = (instr & 0x400) >> 10; + uint16_t imm10 = (instr & 0x3ff); + uint8_t J1 = (instr2 & 0x2000) >> 13; + uint8_t J2 = (instr2 & 0x0800) >> 11; + uint16_t imm11 = (instr2 & 0x7ff); + + uint8_t I1 = J1 ^ S ^ 1; + uint8_t I2 = J2 ^ S ^ 1; + uint32_t imm32 = (S << 24) | (I1 << 23) | (I2 << 22) |(imm10 << 12) | (imm11 << 1); + if (S) imm32 |= 0xfe000000; + + UnwPrintd2("BL %d \n", imm32); + + /* Never taken, as we are unwinding the stack */ + if (0) { + + /* Store return address in LR register */ + state->regData[14].v = state->regData[15].v + 2; + state->regData[14].o = REG_VAL_FROM_CONST; + + /* Update PC */ + state->regData[15].v += imm32; + + /* Need to advance by a word to account for pre-fetch. + * Advance by a half word here, allowing the normal address + * advance to account for the other half word. + */ + state->regData[15].v += 2; + + /* Display PC of next instruction */ + UnwPrintd2(" Return PC=%x", state->regData[15].v); + + /* Report the return address, including mode bit */ + if(!UnwReportRetAddr(state, state->regData[15].v)) { + return UNWIND_TRUNCATED; + } + + /* Determine the new mode */ + if(state->regData[15].v & 0x1) { + /* Branching to THUMB */ + + /* Account for the auto-increment which isn't needed */ + state->regData[15].v -= 2; + } + else { + /* Branch to ARM */ + return UnwStartArm(state); + } + } + } + + /* + * Conditional branches. Usually not taken, unless infinite loop is detected + */ + else if ((instr & 0xf800) == 0xf000 && (instr2 & 0xd000) == 0x8000) { + + uint8_t S = (instr & 0x400) >> 10; + uint16_t imm6 = (instr & 0x3f); + uint8_t J1 = (instr2 & 0x2000) >> 13; + uint8_t J2 = (instr2 & 0x0800) >> 11; + uint16_t imm11 = (instr2 & 0x7ff); + + uint8_t I1 = J1 ^ S ^ 1; + uint8_t I2 = J2 ^ S ^ 1; + uint32_t imm32 = (S << 20) | (I1 << 19) | (I2 << 18) |(imm6 << 12) | (imm11 << 1); + if (S) imm32 |= 0xffe00000; + + UnwPrintd2("Bcond %d\n", imm32); + + /* Take the jump only if a loop is detected */ + if (loopDetected) { + + /* Update PC */ + state->regData[15].v += imm32; + + /* Need to advance by a word to account for pre-fetch. + * Advance by a half word here, allowing the normal address + * advance to account for the other half word. + */ + state->regData[15].v += 2; + + /* Display PC of next instruction */ + UnwPrintd2(" New PC=%x", state->regData[15].v + 2); + } + } + else { + UnwPrintd1("???? (32)"); + + /* Unknown/undecoded. May alter some register, so invalidate file */ + UnwInvalidateRegisterFile(state->regData); + } + /* End of thumb 32bit code */ + + } /* Format 1: Move shifted register * LSL Rd, Rs, #Offset5 * LSR Rd, Rs, #Offset5 * ASR Rd, Rs, #Offset5 */ - if((instr & 0xe000) == 0x0000 && (instr & 0x1800) != 0x1800) { + else if((instr & 0xe000) == 0x0000 && (instr & 0x1800) != 0x1800) { bool signExtend; uint8_t op = (instr & 0x1800) >> 11; uint8_t offset5 = (instr & 0x07c0) >> 6; @@ -355,8 +677,8 @@ UnwResult UnwStartThumb(UnwState * const state) { } /* Format 5: Hi register operations/branch exchange * ADD Rd, Hs - * ADD Hd, Rs - * ADD Hd, Hs + * CMP Hd, Rs + * MOV Hd, Hs */ else if((instr & 0xfc00) == 0x4400) { uint8_t op = (instr & 0x0300) >> 8; @@ -371,11 +693,6 @@ UnwResult UnwStartThumb(UnwState * const state) { if(h1) rhd += 8; - if(op != 3 && !h1 && !h2) { - UnwPrintd1("\nError: h1 or h2 must be set for ADD, CMP or MOV\n"); - return UNWIND_ILLEGAL_INSTR; - } - switch(op) { case 0: /* ADD */ UnwPrintd5("ADD r%d, r%d\t; r%d %s", rhd, rhs, rhs, M_Origin2Str(state->regData[rhs].o)); @@ -407,6 +724,10 @@ UnwResult UnwStartThumb(UnwState * const state) { return UNWIND_TRUNCATED; } + /* Store return address in LR register */ + state->regData[14].v = state->regData[15].v + 2; + state->regData[14].o = REG_VAL_FROM_CONST; + /* Update the PC */ state->regData[15].v = state->regData[rhs].v; @@ -570,10 +891,42 @@ UnwResult UnwStartThumb(UnwState * const state) { } } } + + /* + * Conditional branches + * Bcond + */ + else if((instr & 0xf000) == 0xd000) { + int32_t branchValue = (instr & 0xff); + if (branchValue & 0x80) branchValue |= 0xffffff00; + + /* Branch distance is twice that specified in the instruction. */ + branchValue *= 2; + + UnwPrintd2("Bcond %d \n", branchValue); + + /* Only take the branch if a loop was detected */ + if (loopDetected) { + + /* Update PC */ + state->regData[15].v += branchValue; + + /* Need to advance by a word to account for pre-fetch. + * Advance by a half word here, allowing the normal address + * advance to account for the other half word. + */ + state->regData[15].v += 2; + + /* Display PC of next instruction */ + UnwPrintd2(" New PC=%x", state->regData[15].v + 2); + } + } + /* Format 18: unconditional branch * B label */ else if((instr & 0xf800) == 0xe000) { + uint32_t v; int16_t branchValue = signExtend11(instr & 0x07ff); /* Branch distance is twice that specified in the instruction. */ @@ -590,9 +943,17 @@ UnwResult UnwStartThumb(UnwState * const state) { */ state->regData[15].v += 2; - /* Display PC of next instruction */ - UnwPrintd2(" New PC=%x", state->regData[15].v + 2); + /* Compute the jump address */ + v = state->regData[15].v + 2; + /* Display PC of next instruction */ + UnwPrintd2(" New PC=%x", v); + + /* Did we detect an infinite loop ? */ + loopDetected = lastJumpAddr == v; + + /* Remember the last address we jumped to */ + lastJumpAddr = v; } else { UnwPrintd1("????"); diff --git a/Marlin/src/HAL/HAL_DUE/backtrace/unwinder.c b/Marlin/src/HAL/HAL_DUE/backtrace/unwinder.c index e1f1d22c7..a6e572186 100644 --- a/Marlin/src/HAL/HAL_DUE/backtrace/unwinder.c +++ b/Marlin/src/HAL/HAL_DUE/backtrace/unwinder.c @@ -22,55 +22,18 @@ #include "unwarm.h" #include "unwarmbytab.h" - -/* These symbols point to the start and end of stack */ -extern const int _sstack; -extern const int _estack; - -/* These symbols point to the start and end of the code section */ -extern const int _sfixed; -extern const int _efixed; - -/* These symbols point to the start and end of initialized data (could be SRAM functions!) */ -extern const int _srelocate; -extern const int _erelocate; - - -/* Validate stack pointer (SP): It must be in the stack area */ -static inline __attribute__((always_inline)) UnwResult validate_sp(const void* sp) { - - // SP must point into the allocated stack area - if ((uint32_t)sp >= (uint32_t)&_sstack && (uint32_t)sp <= (uint32_t)&_estack) - return UNWIND_SUCCESS; - - return UNWIND_INVALID_SP; -} - -/* Validate code pointer (PC): It must be either in TEXT or in SRAM */ -static inline __attribute__((always_inline)) UnwResult validate_pc(const void* pc) { - - // PC must point into the text (CODE) area - if ((uint32_t)pc >= (uint32_t)&_sfixed && (uint32_t)pc <= (uint32_t)&_efixed) - return UNWIND_SUCCESS; - - // Or into the SRAM function area - if ((uint32_t)pc >= (uint32_t)&_srelocate && (uint32_t)pc <= (uint32_t)&_erelocate) - return UNWIND_SUCCESS; - - return UNWIND_INVALID_PC; -} - /* These symbols point to the unwind index and should be provide by the linker script */ extern const UnwTabEntry __exidx_start[]; extern const UnwTabEntry __exidx_end[]; // Detect if unwind information is present or not static int HasUnwindTableInfo(void) { - return ((char*)(&__exidx_end) - (char*)(&__exidx_start)) > 16 ? 1 : 0; // 16 because there are default entries we can´t supress + // > 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) -{ +UnwResult UnwindStart(UnwindFrame* frame, const UnwindCallbacks *cb, void *data) { + if (HasUnwindTableInfo()) { /* We have unwind information tables */ diff --git a/Marlin/src/HAL/HAL_DUE/backtrace/unwinder.h b/Marlin/src/HAL/HAL_DUE/backtrace/unwinder.h index 6a0524c75..8ff80ebc8 100644 --- a/Marlin/src/HAL/HAL_DUE/backtrace/unwinder.h +++ b/Marlin/src/HAL/HAL_DUE/backtrace/unwinder.h @@ -149,7 +149,7 @@ typedef struct { #if defined(UNW_DEBUG) /** Print a formatted line for debug. */ - int (*printf)(const char *format, ...); + void (*printf)(const char *format, ...); #endif } UnwindCallbacks; @@ -169,6 +169,11 @@ extern "C" { * 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); From 8934a2c49bf95055f3a4fec4099f8fb669e9a3e3 Mon Sep 17 00:00:00 2001 From: etagle Date: Tue, 27 Mar 2018 04:30:38 -0300 Subject: [PATCH 3/3] Added some missing Thumb instructions to the traceback follower, so now it is able to traceback through switch() statements --- .../src/HAL/HAL_DUE/backtrace/unwarm_thumb.c | 164 ++++++++++++++++-- 1 file changed, 153 insertions(+), 11 deletions(-) diff --git a/Marlin/src/HAL/HAL_DUE/backtrace/unwarm_thumb.c b/Marlin/src/HAL/HAL_DUE/backtrace/unwarm_thumb.c index 2e36a9e8d..38f091e52 100644 --- a/Marlin/src/HAL/HAL_DUE/backtrace/unwarm_thumb.c +++ b/Marlin/src/HAL/HAL_DUE/backtrace/unwarm_thumb.c @@ -25,10 +25,10 @@ * \param value The value to sign extend. * \return The signed-11 bit value stored in a 16bit data type. */ -static int16_t signExtend11(uint16_t value) { +static int32_t signExtend11(uint16_t value) { if(value & 0x400) { - value |= 0xf800; + value |= 0xfffff800; } return value; @@ -243,6 +243,40 @@ UnwResult UnwStartThumb(UnwState * const state) { UnwPrintd3(" r%d = 0x%08x\n", r, state->regData[r].v); } + /* + * TBB / TBH + */ + else if ((instr & 0xfff0) == 0xe8d0 && (instr2 & 0xffe0) == 0xf000) { + /* We are only interested in + * the forms + * TBB [PC, ...] + * TBH [PC, ..., LSL #1] + * as those are used by the C compiler to implement + * the switch clauses + */ + uint8_t rn = instr & 0xf; + uint8_t rm = instr2 & 0xf; + bool H = (instr2 & 0x10) ? true : false; + + UnwPrintd5("TB%c [r%d,r%d%s]\n", H ? 'H' : 'B', rn, rm, H ? ",LSL #1" : ""); + + // We are only interested if the RN is the PC. Let´s choose the 1st destination + if (rn == 15) { + if (H) { + uint16_t rv; + if(!state->cb->readH((state->regData[15].v & (~1)) + 2, &rv)) { + return UNWIND_DREAD_H_FAIL; + } + state->regData[15].v += rv * 2; + } else { + uint8_t rv; + if(!state->cb->readB((state->regData[15].v & (~1)) + 2, &rv)) { + return UNWIND_DREAD_B_FAIL; + } + state->regData[15].v += rv * 2; + } + } + } /* * Unconditional branch */ @@ -374,6 +408,118 @@ UnwResult UnwStartThumb(UnwState * const state) { UnwPrintd2(" New PC=%x", state->regData[15].v + 2); } } + /* + * PC-relative load + * LDR Rd,[PC, #+/-imm] + */ + else if((instr & 0xff7f) == 0xf85f) { + uint8_t rt = (instr2 & 0xf000) >> 12; + uint8_t imm12 = (instr2 & 0x0fff); + bool A = (instr & 0x80) ? true : false; + uint32_t address; + + /* Compute load address, adding a word to account for prefetch */ + address = (state->regData[15].v & (~0x3)) + 4; + if (A) address += imm12; + else address -= imm12; + + UnwPrintd4("LDR r%d,[PC #%c0x%08x]", rt, A?'+':'-', address); + + if(!UnwMemReadRegister(state, address, &state->regData[rt])) { + return UNWIND_DREAD_W_FAIL; + } + } + /* + * LDR immediate. + * We are only interested when destination is PC. + * LDR Rt,[Rn , #n] + */ + else if ((instr & 0xfff0) == 0xf8d0) { + uint8_t rn = (instr & 0xf); + uint8_t rt = (instr2 & 0xf000) >> 12; + uint16_t imm12 = (instr2 & 0xfff); + + /* If destination is PC and we don't know the source value, then fail */ + if (!M_IsOriginValid(state->regData[rn].o)) { + state->regData[rt].o = state->regData[rn].o; + } else { + uint32_t address = state->regData[rn].v + imm12; + if(!UnwMemReadRegister(state, address, &state->regData[rt])) { + return UNWIND_DREAD_W_FAIL; + } + } + } + /* + * LDR immediate + * We are only interested when destination is PC. + * LDR Rt,[Rn , #-n] + * LDR Rt,[Rn], #+/-n] + * LDR Rt,[Rn, #+/-n]! + */ + else if ((instr & 0xfff0) == 0xf850 && (instr2 & 0x0800) == 0x0800) { + uint8_t rn = (instr & 0xf); + uint8_t rt = (instr2 & 0xf000) >> 12; + uint16_t imm8 = (instr2 & 0xff); + bool P = (instr2 & 0x400) ? true : false; + bool U = (instr2 & 0x200) ? true : false; + bool W = (instr2 & 0x100) ? true : false; + + if (!M_IsOriginValid(state->regData[rn].o)) { + state->regData[rt].o = state->regData[rn].o; + } else { + uint32_t offaddress = state->regData[rn].v + imm8; + if (U) offaddress += imm8; + else offaddress -= imm8; + + uint32_t address; + if (P) { + address = offaddress; + } else { + address = state->regData[rn].v; + } + + if(!UnwMemReadRegister(state, address, &state->regData[rt])) { + return UNWIND_DREAD_W_FAIL; + } + + if (W) { + state->regData[rn].v = offaddress; + } + } + } + /* + * LDR (register). + * We are interested in the form + * ldr Rt, [Rn, Rm, lsl #x] + * Where Rt is PC, Rn value is known, Rm is not known or unknown + */ + else if ((instr & 0xfff0) == 0xf850 && (instr2 & 0x0fc0) == 0x0000) { + uint8_t rn = (instr & 0xf); + uint8_t rt = (instr2 & 0xf000) >> 12; + uint8_t rm = (instr2 & 0xf); + uint8_t imm2 = (instr2 & 0x30) >> 4; + + if (!M_IsOriginValid(state->regData[rn].o) || + !M_IsOriginValid(state->regData[rm].o)) { + + /* If Rt is PC, and Rn is known, then do an exception and assume + Rm equals 0 => This takes the first case in a switch() */ + if (rt == 15 && M_IsOriginValid(state->regData[rn].o)) { + uint32_t address = state->regData[rn].v; + if(!UnwMemReadRegister(state, address, &state->regData[rt])) { + return UNWIND_DREAD_W_FAIL; + } + } else { + /* Propagate unknown value */ + state->regData[rt].o = state->regData[rn].o; + } + } else { + uint32_t address = state->regData[rn].v + (state->regData[rm].v << imm2); + if(!UnwMemReadRegister(state, address, &state->regData[rt])) { + return UNWIND_DREAD_W_FAIL; + } + } + } else { UnwPrintd1("???? (32)"); @@ -452,8 +598,8 @@ UnwResult UnwStartThumb(UnwState * const state) { } /* Propagate the origin */ - if(M_IsOriginValid(state->regData[rs].v) && - M_IsOriginValid(state->regData[rn].v)) { + if(M_IsOriginValid(state->regData[rs].o) && + M_IsOriginValid(state->regData[rn].o)) { state->regData[rd].o = state->regData[rs].o; state->regData[rd].o |= REG_VAL_ARITHMETIC; } @@ -715,8 +861,8 @@ UnwResult UnwStartThumb(UnwState * const state) { case 3: /* BX */ UnwPrintd4("BX r%d\t; r%d %s\n", rhs, rhs, M_Origin2Str(state->regData[rhs].o)); - /* Only follow BX if the data was from the stack */ - if(state->regData[rhs].o == REG_VAL_FROM_STACK) { + /* Only follow BX if the data was from the stack or BX LR */ + if(rhs == 14 || state->regData[rhs].o == REG_VAL_FROM_STACK) { UnwPrintd2(" Return PC=0x%x\n", state->regData[rhs].v & (~0x1)); /* Report the return address, including mode bit */ @@ -724,10 +870,6 @@ UnwResult UnwStartThumb(UnwState * const state) { return UNWIND_TRUNCATED; } - /* Store return address in LR register */ - state->regData[14].v = state->regData[15].v + 2; - state->regData[14].o = REG_VAL_FROM_CONST; - /* Update the PC */ state->regData[15].v = state->regData[rhs].v; @@ -927,7 +1069,7 @@ UnwResult UnwStartThumb(UnwState * const state) { */ else if((instr & 0xf800) == 0xe000) { uint32_t v; - int16_t branchValue = signExtend11(instr & 0x07ff); + int32_t branchValue = signExtend11(instr & 0x07ff); /* Branch distance is twice that specified in the instruction. */ branchValue *= 2;