From c3b23974bdc7472760714af96df790300ef5cc9e Mon Sep 17 00:00:00 2001 From: etagle Date: Fri, 23 Mar 2018 05:22:45 -0300 Subject: [PATCH] Added detection of case when no unwind tables are available --- Marlin/src/HAL/HAL_DUE/DebugMonitor_Due.cpp | 17 +- Marlin/src/HAL/HAL_DUE/backtrace/backtrace.c | 271 ++++++++++++++----- platformio.ini | 12 + 3 files changed, 228 insertions(+), 72 deletions(-) diff --git a/Marlin/src/HAL/HAL_DUE/DebugMonitor_Due.cpp b/Marlin/src/HAL/HAL_DUE/DebugMonitor_Due.cpp index 28cb5c516..fb28de415 100644 --- a/Marlin/src/HAL/HAL_DUE/DebugMonitor_Due.cpp +++ b/Marlin/src/HAL/HAL_DUE/DebugMonitor_Due.cpp @@ -114,7 +114,9 @@ static void TXDec(uint32_t v) { // 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->address); TX('\n'); + 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'); } /** @@ -176,6 +178,19 @@ void HardFault_HandlerC(unsigned long *hardfault_args, unsigned long cause) { btf.pc = ((unsigned long)hardfault_args[6]); backtrace_dump(&btf, backtrace_dump_fn, nullptr); + // Disable all NVIC interrupts + NVIC->ICER[0] = 0xFFFFFFFF; + NVIC->ICER[1] = 0xFFFFFFFF; + + // Relocate VTOR table to default position + SCB->VTOR = 0; + + // Disable USB + otg_disable(); + + // Restart watchdog + WDT_Restart(WDT); + // Reset controller NVIC_SystemReset(); while(1) { WDT_Restart(WDT); } diff --git a/Marlin/src/HAL/HAL_DUE/backtrace/backtrace.c b/Marlin/src/HAL/HAL_DUE/backtrace/backtrace.c index c31120a10..8d61b793c 100644 --- a/Marlin/src/HAL/HAL_DUE/backtrace/backtrace.c +++ b/Marlin/src/HAL/HAL_DUE/backtrace/backtrace.c @@ -6,9 +6,9 @@ * 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. + * 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 @@ -43,10 +43,30 @@ void __aeabi_unwind_cpp_pr2(void) {}; extern const int _sstack; extern const int _estack; -/* Validate stack pointer */ +/* 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) { - if ((uint32_t)sp < (uint32_t)&_sstack || (uint32_t)sp > (uint32_t)&_estack) - return -1; + // 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; } @@ -103,11 +123,11 @@ static int unwind_control_block_init(unwind_control_block_t *ucb, const uint32_t memset(ucb, 0, sizeof(unwind_control_block_t)); ucb->current = instructions; - /* Is the a short unwind description */ + /* Is a short unwind description */ if ((*instructions & 0xff000000) == 0x80000000) { ucb->remaining = 3; ucb->byte = 2; - /* Is the a long unwind description */ + /* Is a long unwind description */ } else if ((*instructions & 0xff000000) == 0x81000000) { ucb->remaining = ((*instructions & 0x00ff0000) >> 14) + 2; ucb->byte = 1; @@ -115,12 +135,10 @@ static int unwind_control_block_init(unwind_control_block_t *ucb, const uint32_t return -1; /* Initialize the virtual register set */ - if (frame) { - ucb->vrs[7] = frame->fp; - ucb->vrs[13] = frame->sp; - ucb->vrs[14] = frame->lr; - ucb->vrs[15] = 0; - } + ucb->vrs[7] = frame->fp; + ucb->vrs[13] = frame->sp; + ucb->vrs[14] = frame->lr; + ucb->vrs[15] = 0; /* All good */ return 0; @@ -136,46 +154,49 @@ static int unwind_execute_instruction(unwind_control_block_t *ucb) { /* Consume all instruction byte */ while ((instruction = unwind_get_next_byte(ucb)) != -1) { - if ((instruction & 0xc0) == 0x00) { + 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) { + } 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) { + } 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) + if (instruction == 0x8000) // ARM_EXIDX_CMD_REFUSED return 0; - /* Pop registers using mask */ + /* Pop registers using mask */ // ARM_EXIDX_CMD_REG_POP vsp = (uint32_t *)ucb->vrs[13]; mask = instruction & 0xfff; reg = 4; - while (mask != 0) { - if ((mask & 0x001) != 0) { + while (mask) { + if ((mask & 1) != 0) { if (validate_sp(vsp)) return -1; ucb->vrs[reg] = *vsp++; } - mask = mask >> 1; + mask >>= 1; ++reg; } /* Patch up the vrs sp if it was in the mask */ - if ((mask & (1 << (13 - 4))) != 0) + if ((instruction & (1 << (13 - 4))) != 0) ucb->vrs[13] = (uint32_t)vsp; - } else if ((instruction & 0xf0) == 0x90 && instruction != 0x9d && instruction != 0x9f) { + } 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) { + } 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]; @@ -185,7 +206,7 @@ static int unwind_execute_instruction(unwind_control_block_t *ucb) { ucb->vrs[reg] = *vsp++; } - if (instruction & 0x80) { + if (instruction & 0x08) { // ARM_EXIDX_CMD_REG_POP if (validate_sp(vsp)) return -1; ucb->vrs[14] = *vsp++; @@ -193,7 +214,8 @@ static int unwind_execute_instruction(unwind_control_block_t *ucb) { ucb->vrs[13] = (uint32_t)vsp; - } else if (instruction == 0xb0) { + } else + if (instruction == 0xb0) { // ARM_EXIDX_CMD_FINISH /* finished */ if (ucb->vrs[15] == 0) ucb->vrs[15] = ucb->vrs[14]; @@ -201,28 +223,34 @@ static int unwind_execute_instruction(unwind_control_block_t *ucb) { /* All done unwinding */ return 0; - } else if (instruction == 0xb1) { + } 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 != 0) { - if ((mask & 0x01) != 0) { + while (mask) { + if ((mask & 1) != 0) { if (validate_sp(vsp)) return -1; ucb->vrs[reg] = *vsp++; } - mask = mask >> 1; + mask >>= 1; ++reg; } ucb->vrs[13] = (uint32_t)vsp; - } else if (instruction == 0xb2) { + } 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 || instruction == 0xc8 || instruction == 0xc9) { + } else + if (instruction == 0xb3 || // ARM_EXIDX_CMD_VFP_POP + instruction == 0xc8 || + instruction == 0xc9) { + /* pop VFP double-precision registers */ vsp = (uint32_t *)ucb->vrs[13]; @@ -243,7 +271,10 @@ static int unwind_execute_instruction(unwind_control_block_t *ucb) { ucb->vrs[13] = (uint32_t)vsp; - } else if ((instruction & 0xf8) == 0xb8 || (instruction & 0xf8) == 0xd0) { + } else + if ((instruction & 0xf8) == 0xb8 || + (instruction & 0xf8) == 0xd0) { + /* Pop VFP double precision registers D[8]-D[8+nnn] */ ucb->vrs[14] = 0x80 | (instruction & 0x07); @@ -296,7 +327,7 @@ static int unwind_frame(backtrace_frame_t *frame) { if (unwind_control_block_init(&ucb, instructions, frame) < 0) return -1; - /* Execute the unwind instructions TODO range check the stack pointer */ + /* Execute the unwind instructions */ while ((execution_result = unwind_execute_instruction(&ucb)) > 0); if (execution_result == -1) return -1; @@ -332,8 +363,7 @@ static int unwind_frame(backtrace_frame_t *frame) { stack -= 2; /* If there was a VFP exception (0xffffffe1), the PC is located another 18 words down */ - if ((ucb.vrs[15] & 0xf0) == 0xe0) - { + if ((ucb.vrs[15] & 0xf0) == 0xe0) { stack -= 18; } } @@ -366,47 +396,146 @@ static int unwind_frame(backtrace_frame_t *frame) { 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; - /* Unwind all frames */ - do { - if (frame->pc == 0) { - /* Reached __exidx_end. */ - entry.name = ""; - entry.address = 0; - entry.function = 0; - dump_entry(count, &entry, ctx); - break; + /* 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; + } } - if (frame->pc == 0x00000001) { - /* Reached .cantunwind instruction. */ - entry.name = ""; - entry.address = 0; - entry.function = 0; + } 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); - break; - } - /* Find the unwind index of the current frame pc */ - const unwind_index_t *index = unwind_search_index(__exidx_start, __exidx_end, frame->pc); + /* Next backtrace frame */ + ++count; - /* 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); + } while (unwind_frame(frame) == 1); + } /* All done */ return count; diff --git a/platformio.ini b/platformio.ini index 1f5015395..e809b009c 100644 --- a/platformio.ini +++ b/platformio.ini @@ -97,6 +97,18 @@ lib_deps = ${common.lib_deps} lib_ignore = c1921b4 src_filter = ${common.default_src_filter} monitor_baud = 250000 +[env:DUE_debug] +# Used when WATCHDOG_RESET_MANUAL is enabled +platform = atmelsam +framework = arduino +board = due +build_flags = ${common.build_flags} + -funwind-tables + -mpoke-function-name +lib_deps = ${common.lib_deps} +lib_ignore = c1921b4 +src_filter = ${common.default_src_filter} +monitor_baud = 250000 # # NXP LPC1768 ARM Cortex-M3