From c5730cf711aaec8f8fe46d35f66340cccd3a5f9b Mon Sep 17 00:00:00 2001 From: etagle Date: Thu, 22 Mar 2018 03:31:25 -0300 Subject: [PATCH 1/3] As Bob-The-Khun suggested, resetting the USB peripheral solves the huge startup delays that happen when a WDT reset happens and we are connected through the native port --- Marlin/src/HAL/HAL_DUE/usb/usb_task.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Marlin/src/HAL/HAL_DUE/usb/usb_task.c b/Marlin/src/HAL/HAL_DUE/usb/usb_task.c index 468385b508..528db591c3 100644 --- a/Marlin/src/HAL/HAL_DUE/usb/usb_task.c +++ b/Marlin/src/HAL/HAL_DUE/usb/usb_task.c @@ -301,7 +301,11 @@ void usb_task_init(void) { uint16_t *ptr; + // Disable USB peripheral so we start clean and avoid lockups + otg_disable(); udd_disable(); + + // Set the USB interrupt to our stack UDD_SetStack(&USBD_ISR); // Start USB stack to authorize VBus monitoring From b210bdf032436aa0250544a8d9c430ea2de49d3d Mon Sep 17 00:00:00 2001 From: etagle Date: Thu, 22 Mar 2018 03:34:03 -0300 Subject: [PATCH 2/3] Now the Crash reporter uses the configured BAUDRATE to send the report through the Programming port. And also shows the traceback of functions as discussed. For that latest feature to work, you need to compile the project with -funwind-tables and -mpoke-function-name compiler flags --- Marlin/src/HAL/HAL_DUE/DebugMonitor_Due.cpp | 38 +- Marlin/src/HAL/HAL_DUE/backtrace/backtrace.c | 415 +++++++++++++++++++ Marlin/src/HAL/HAL_DUE/backtrace/backtrace.h | 53 +++ 3 files changed, 504 insertions(+), 2 deletions(-) create mode 100644 Marlin/src/HAL/HAL_DUE/backtrace/backtrace.c create mode 100644 Marlin/src/HAL/HAL_DUE/backtrace/backtrace.h diff --git a/Marlin/src/HAL/HAL_DUE/DebugMonitor_Due.cpp b/Marlin/src/HAL/HAL_DUE/DebugMonitor_Due.cpp index b7130a45cb..28cb5c5165 100644 --- a/Marlin/src/HAL/HAL_DUE/DebugMonitor_Due.cpp +++ b/Marlin/src/HAL/HAL_DUE/DebugMonitor_Due.cpp @@ -24,6 +24,7 @@ #include "../../inc/MarlinConfig.h" #include "../../Marlin.h" +#include "backtrace/backtrace.h" // Debug monitor that dumps to the Programming port all status when // an exception or WDT timeout happens - And then resets the board @@ -57,8 +58,8 @@ static void TXBegin(void) { // Configure mode: 8bit, No parity, 1 bit stop UART->UART_MR = UART_MR_CHMODE_NORMAL | US_MR_CHRL_8_BIT | US_MR_NBSTOP_1_BIT | UART_MR_PAR_NO; - // Configure baudrate (asynchronous, no oversampling) to 250000 bauds - UART->UART_BRGR = (SystemCoreClock / (250000 << 4)); + // Configure baudrate (asynchronous, no oversampling) to BAUDRATE bauds + UART->UART_BRGR = (SystemCoreClock / (BAUDRATE << 4)); // Enable receiver and transmitter UART->UART_CR = UART_CR_RXEN | UART_CR_TXEN; @@ -92,6 +93,30 @@ static void TXHex(uint32_t v) { } } +// Send Decimal number thru UART +static void TXDec(uint32_t v) { + if (!v) { + TX('0'); + return; + } + + char nbrs[14]; + char *p = &nbrs[0]; + while (v != 0) { + *p++ = '0' + (v % 10); + v /= 10; + } + do { + p--; + TX(*p); + } 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->address); TX('\n'); +} + /** * HardFaultHandler_C: * This is called from the HardFault_HandlerAsm with a pointer the Fault stack @@ -142,6 +167,15 @@ void HardFault_HandlerC(unsigned long *hardfault_args, unsigned long cause) { // Bus Fault Address Register TX("BFAR : "); TXHex((*((volatile unsigned long *)(0xE000ED38)))); TX('\n'); + // Perform a backtrace + TX("\nBacktrace:\n\n"); + backtrace_frame_t 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); + // 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 new file mode 100644 index 0000000000..c31120a102 --- /dev/null +++ b/Marlin/src/HAL/HAL_DUE/backtrace/backtrace.c @@ -0,0 +1,415 @@ +/* + * 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. + */ + +#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; + +/* Validate stack pointer */ +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; + 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 the a short unwind description */ + if ((*instructions & 0xff000000) == 0x80000000) { + ucb->remaining = 3; + ucb->byte = 2; + /* Is the 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 */ + if (frame) { + 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) { + /* vsp = vsp + (xxxxxx << 2) + 4 */ + ucb->vrs[13] += ((instruction & 0x3f) << 2) + 4; + + } else if ((instruction & 0xc0) == 0x40) { + /* 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) + return 0; + + /* Pop registers using mask */ + vsp = (uint32_t *)ucb->vrs[13]; + mask = instruction & 0xfff; + + reg = 4; + while (mask != 0) { + if ((mask & 0x001) != 0) { + if (validate_sp(vsp)) + return -1; + ucb->vrs[reg] = *vsp++; + } + mask = mask >> 1; + ++reg; + } + + /* Patch up the vrs sp if it was in the mask */ + if ((mask & (1 << (13 - 4))) != 0) + ucb->vrs[13] = (uint32_t)vsp; + + } else if ((instruction & 0xf0) == 0x90 && instruction != 0x9d && instruction != 0x9f) { + /* vsp = r[nnnn] */ + ucb->vrs[13] = ucb->vrs[instruction & 0x0f]; + + } else if ((instruction & 0xf0) == 0xa0) { + /* 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 & 0x80) { + if (validate_sp(vsp)) + return -1; + ucb->vrs[14] = *vsp++; + } + + ucb->vrs[13] = (uint32_t)vsp; + + } else if (instruction == 0xb0) { + /* finished */ + if (ucb->vrs[15] == 0) + ucb->vrs[15] = ucb->vrs[14]; + + /* All done unwinding */ + return 0; + + } else if (instruction == 0xb1) { + /* 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) { + if (validate_sp(vsp)) + return -1; + ucb->vrs[reg] = *vsp++; + } + mask = mask >> 1; + ++reg; + } + ucb->vrs[13] = (uint32_t)vsp; + + } else if (instruction == 0xb2) { + /* vps = vsp + 0x204 + (uleb128 << 2) */ + ucb->vrs[13] += 0x204 + (unwind_get_next_byte(ucb) << 2); + + } else if (instruction == 0xb3 || 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 TODO range check the stack pointer */ + 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; +} + +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 (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 new file mode 100644 index 0000000000..855bc35d1e --- /dev/null +++ b/Marlin/src/HAL/HAL_DUE/backtrace/backtrace.h @@ -0,0 +1,53 @@ +/* + * 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_ From c3b23974bdc7472760714af96df790300ef5cc9e Mon Sep 17 00:00:00 2001 From: etagle Date: Fri, 23 Mar 2018 05:22:45 -0300 Subject: [PATCH 3/3] 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 | 267 ++++++++++++++----- platformio.ini | 12 + 3 files changed, 226 insertions(+), 70 deletions(-) diff --git a/Marlin/src/HAL/HAL_DUE/DebugMonitor_Due.cpp b/Marlin/src/HAL/HAL_DUE/DebugMonitor_Due.cpp index 28cb5c5165..fb28de415a 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 c31120a102..8d61b793cb 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; - if (frame->pc == 0x00000001) { - /* Reached .cantunwind instruction. */ - entry.name = ""; - entry.address = 0; - entry.function = 0; - dump_entry(count, &entry, ctx); - break; + // 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; + } } - /* Find the unwind index of the current frame pc */ - const unwind_index_t *index = unwind_search_index(__exidx_start, __exidx_end, frame->pc); + } 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; + } - /* Clear last bit (Thumb indicator) */ - frame->pc &= 0xfffffffeU; + if (frame->pc == 0x00000001) { + /* Reached .cantunwind instruction. */ + entry.name = ""; + entry.address = 0; + entry.function = 0; + dump_entry(count, &entry, ctx); + break; + } - /* 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); + /* 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; - } while (unwind_frame(frame) == 1); + /* 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; diff --git a/platformio.ini b/platformio.ini index 1f50153953..e809b009c9 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