Merge branch 'feature/twdt_prints_backtrace_v3.3' into 'release/v3.3'

Add Task Watchdog backtrace printing (v3.3)

See merge request espressif/esp-idf!12624
This commit is contained in:
Angus Gratton 2021-03-31 05:17:23 +00:00
commit 08ce445ab6
16 changed files with 329 additions and 36 deletions

View File

@ -16,6 +16,8 @@ else()
"coexist.c" "coexist.c"
"cpu_start.c" "cpu_start.c"
"crosscore_int.c" "crosscore_int.c"
"debug_helpers.c"
"debug_helpers_asm.S"
"dbg_stubs.c" "dbg_stubs.c"
"dport_access.c" "dport_access.c"
"dport_panic_highint_hdl.S" "dport_panic_highint_hdl.S"

View File

@ -18,6 +18,7 @@
#include "esp_err.h" #include "esp_err.h"
#include "esp_intr.h" #include "esp_intr.h"
#include "esp_intr_alloc.h" #include "esp_intr_alloc.h"
#include "esp_panic.h"
#include "rom/ets_sys.h" #include "rom/ets_sys.h"
#include "rom/uart.h" #include "rom/uart.h"
@ -36,6 +37,7 @@
#define REASON_YIELD BIT(0) #define REASON_YIELD BIT(0)
#define REASON_FREQ_SWITCH BIT(1) #define REASON_FREQ_SWITCH BIT(1)
#define REASON_PRINT_BACKTRACE BIT(2)
static portMUX_TYPE reason_spinlock = portMUX_INITIALIZER_UNLOCKED; static portMUX_TYPE reason_spinlock = portMUX_INITIALIZER_UNLOCKED;
static volatile uint32_t reason[ portNUM_PROCESSORS ]; static volatile uint32_t reason[ portNUM_PROCESSORS ];
@ -76,6 +78,9 @@ static void IRAM_ATTR esp_crosscore_isr(void *arg) {
* to allow DFS features without the extra latency of the ISR hook. * to allow DFS features without the extra latency of the ISR hook.
*/ */
} }
if (my_reason_val & REASON_PRINT_BACKTRACE) {
esp_backtrace_print(100);
}
} }
//Initialize the crosscore interrupt on this core. Call this once //Initialize the crosscore interrupt on this core. Call this once
@ -117,3 +122,7 @@ void IRAM_ATTR esp_crosscore_int_send_freq_switch(int core_id)
esp_crosscore_int_send(core_id, REASON_FREQ_SWITCH); esp_crosscore_int_send(core_id, REASON_FREQ_SWITCH);
} }
void IRAM_ATTR esp_crosscore_int_send_print_backtrace(int core_id)
{
esp_crosscore_int_send(core_id, REASON_PRINT_BACKTRACE);
}

View File

@ -0,0 +1,72 @@
// Copyright 2015-2019 Espressif Systems (Shanghai) PTE LTD
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "esp_types.h"
#include "esp_attr.h"
#include "esp_err.h"
#include "esp_panic.h"
#include "rom/ets_sys.h"
#include "soc/soc_memory_layout.h"
#include "soc/cpu.h"
bool IRAM_ATTR esp_backtrace_get_next_frame(esp_backtrace_frame_t *frame)
{
//Use frame(i-1)'s BS area located below frame(i)'s sp to get frame(i-1)'s sp and frame(i-2)'s pc
void *base_save = (void *)frame->sp; //Base save area consists of 4 words under SP
frame->pc = frame->next_pc;
frame->next_pc = *((uint32_t *)(base_save - 16)); //If next_pc = 0, indicates frame(i-1) is the last frame on the stack
frame->sp = *((uint32_t *)(base_save - 12));
//Return true if both sp and pc of frame(i-1) are sane, false otherwise
return (esp_stack_ptr_is_sane(frame->sp) && esp_ptr_executable((void*)esp_cpu_process_stack_pc(frame->pc)));
}
esp_err_t IRAM_ATTR esp_backtrace_print(int depth)
{
//Check arguments
if (depth <= 0) {
return ESP_ERR_INVALID_ARG;
}
//Initialize stk_frame with first frame of stack
esp_backtrace_frame_t stk_frame;
esp_backtrace_get_start(&(stk_frame.pc), &(stk_frame.sp), &(stk_frame.next_pc));
//esp_cpu_get_backtrace_start(&stk_frame);
ets_printf("\r\n\r\nBacktrace:");
ets_printf("0x%08X:0x%08X ", esp_cpu_process_stack_pc(stk_frame.pc), stk_frame.sp);
//Check if first frame is valid
bool corrupted = (esp_stack_ptr_is_sane(stk_frame.sp) &&
esp_ptr_executable((void*)esp_cpu_process_stack_pc(stk_frame.pc))) ?
false : true;
uint32_t i = (depth <= 0) ? INT32_MAX : depth;
while (i-- > 0 && stk_frame.next_pc != 0 && !corrupted) {
if (!esp_backtrace_get_next_frame(&stk_frame)) { //Get previous stack frame
corrupted = true;
}
ets_printf("0x%08X:0x%08X ", esp_cpu_process_stack_pc(stk_frame.pc), stk_frame.sp);
}
//Print backtrace termination marker
esp_err_t ret = ESP_OK;
if (corrupted) {
ets_printf(" |<-CORRUPTED");
ret = ESP_FAIL;
} else if (stk_frame.next_pc != 0) { //Backtrace continues
ets_printf(" |<-CONTINUES");
}
ets_printf("\r\n\r\n");
return ret;
}

View File

@ -0,0 +1,57 @@
// Copyright 2015-2019 Espressif Systems (Shanghai) PTE LTD
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include <xtensa/coreasm.h>
#include <xtensa/corebits.h>
#include <xtensa/config/system.h>
#include <xtensa/hal.h>
/*
* esp_backtrace_get_start(uint32_t *pc, uint32_t *sp, uint32_t *next_pc)
*
* High Addr
* ..................
* | i-3 BS |
* | i-1 locals | Function B
* .................. i-1 SP
* | i-2 BS |
* | i locals | Function A (Start of backtrace)
* ------------------ i SP
* | i-1 BS |
* | i+1 locals | Backtracing function (e.g. esp_backtrace_print())
* ------------------ i+1 SP
* | i BS |
* | i+2 locals | esp_backtrace_get_start() <- This function
* ------------------ i+2 SP
* | i+1 BS |
* | i+3 locals | xthal_window_spill()
* ------------------ i+3 SP
* .................. Low Addr
*/
.section .iram1, "ax"
.align 4
.global esp_backtrace_get_start
.type esp_backtrace_get_start, @function
esp_backtrace_get_start:
entry a1, 32
call8 xthal_window_spill //Spill registers onto stack (excluding this function)
//a2, a3, a4 should be out arguments for i SP, i PC, i-1 PC respectively. Use a5 and a6 as scratch
l32e a5, sp, -16 //Get i PC, which is ret addres of i+1
s32i a5, a2, 0 //Store i PC to arg *pc
l32e a6, sp, -12 //Get i+1 SP. Used to access i BS
l32e a5, a6, -12 //Get i SP
s32i a5, a3, 0 //Store i SP to arg *sp
l32e a5, a6, -16 //Get i-1 PC, which is ret address of i
s32i a5, a4, 0 //Store i-1 PC to arg *next_pc
retw

View File

@ -51,4 +51,14 @@ void esp_crosscore_int_send_yield(int core_id);
*/ */
void esp_crosscore_int_send_freq_switch(int core_id); void esp_crosscore_int_send_freq_switch(int core_id);
/**
* Send an interrupt to a CPU indicating it should print its current backtrace
*
* This is use internally by the Task Watchdog to dump the backtrace of the
* opposite core and should not be called from application code.
*
* @param core_id Core that should print its backtrace
*/
void esp_crosscore_int_send_print_backtrace(int core_id);
#endif #endif

View File

@ -1,5 +1,18 @@
#ifndef PANIC_H // Copyright 2015-2019 Espressif Systems (Shanghai) PTE LTD
#define PANIC_H //
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#pragma once
#ifdef __cplusplus #ifdef __cplusplus
extern "C" extern "C"
@ -21,8 +34,27 @@ extern "C"
#include "esp_err.h" #include "esp_err.h"
#include "soc/soc.h" #include "soc/soc.h"
#include "soc/soc_memory_layout.h"
/*
* @brief Structure used for backtracing
*
* This structure stores the backtrace information of a particular stack frame
* (i.e. the PC and SP). This structure is used iteratively with the
* esp_cpu_get_next_backtrace_frame() function to traverse each frame within a
* single stack. The next_pc represents the PC of the current frame's caller, thus
* a next_pc of 0 indicates that the current frame is the last frame on the stack.
*
* @note Call esp_backtrace_get_start() to obtain initialization values for
* this structure
*/
typedef struct {
uint32_t pc; /* PC of the current frame */
uint32_t sp; /* SP of the current frame */
uint32_t next_pc; /* PC of the current frame's caller */
} esp_backtrace_frame_t;
/** /**
* @brief If an OCD is connected over JTAG. set breakpoint 0 to the given function * @brief If an OCD is connected over JTAG. set breakpoint 0 to the given function
* address. Do nothing otherwise. * address. Do nothing otherwise.
@ -53,7 +85,6 @@ void esp_set_breakpoint_if_jtag(void *fn);
*/ */
esp_err_t esp_set_watchpoint(int no, void *adr, int size, int flags); esp_err_t esp_set_watchpoint(int no, void *adr, int size, int flags);
/** /**
* @brief Clear a watchpoint * @brief Clear a watchpoint
* *
@ -82,21 +113,56 @@ inline static bool esp_stack_ptr_in_extram(uint32_t sp)
} }
#endif #endif
/** /*
* @brief Checks stack pointer * Get the first frame of the current stack's backtrace
*
* Given the following function call flow (B -> A -> X -> esp_backtrace_get_start),
* this function will do the following.
* - Flush CPU registers and window frames onto the current stack
* - Return PC and SP of function A (i.e. start of the stack's backtrace)
* - Return PC of function B (i.e. next_pc)
*
* @note This function is implemented in assembly
*
* @param[out] pc PC of the first frame in the backtrace
* @param[out] sp SP of the first frame in the backtrace
* @param[out] next_pc PC of the first frame's caller
*/ */
static inline bool esp_stack_ptr_is_sane(uint32_t sp) extern void esp_backtrace_get_start(uint32_t *pc, uint32_t *sp, uint32_t *next_pc);
{
#if CONFIG_SPIRAM_ALLOW_STACK_EXTERNAL_MEMORY /**
return (esp_stack_ptr_in_dram(sp) || esp_stack_ptr_in_extram(sp)); * Get the next frame on a stack for backtracing
#else *
return esp_stack_ptr_in_dram(sp); * Given a stack frame(i), this function will obtain the next stack frame(i-1)
#endif * on the same call stack (i.e. the caller of frame(i)). This function is meant to be
} * called iteratively when doing a backtrace.
*
* Entry Conditions: Frame structure containing valid SP and next_pc
* Exit Conditions:
* - Frame structure updated with SP and PC of frame(i-1). next_pc now points to frame(i-2).
* - If a next_pc of 0 is returned, it indicates that frame(i-1) is last frame on the stack
*
* @param[inout] frame Pointer to frame structure
*
* @return
* - True if the SP and PC of the next frame(i-1) are sane
* - False otherwise
*/
bool esp_backtrace_get_next_frame(esp_backtrace_frame_t *frame);
/**
* @brief Print the backtrace of the current stack
*
* @param depth The maximum number of stack frames to print (should be > 0)
*
* @return
* - ESP_OK Backtrace successfully printed to completion or to depth limit
* - ESP_FAIL Backtrace is corrupted
*/
esp_err_t esp_backtrace_print(int depth);
#endif #endif
#ifdef __cplusplus #ifdef __cplusplus
} }
#endif #endif
#endif

View File

@ -31,6 +31,7 @@
#include "soc/cpu.h" #include "soc/cpu.h"
#include "soc/rtc.h" #include "soc/rtc.h"
#include "soc/rtc_wdt.h" #include "soc/rtc_wdt.h"
#include "soc/soc_memory_layout.h"
#include "esp_gdbstub.h" #include "esp_gdbstub.h"
#include "esp_panic.h" #include "esp_panic.h"
@ -446,33 +447,36 @@ static void esp_panic_dig_reset()
static void putEntry(uint32_t pc, uint32_t sp) static void putEntry(uint32_t pc, uint32_t sp)
{ {
if (pc & 0x80000000) {
pc = (pc & 0x3fffffff) | 0x40000000;
}
panicPutStr(" 0x"); panicPutStr(" 0x");
panicPutHex(pc); panicPutHex(pc);
panicPutStr(":0x"); panicPutStr(":0x");
panicPutHex(sp); panicPutHex(sp);
} }
static void doBacktrace(XtExcFrame *frame) static void doBacktrace(XtExcFrame *exc_frame, int depth)
{ {
uint32_t i = 0, pc = frame->pc, sp = frame->a1; //Initialize stk_frame with first frame of stack
esp_backtrace_frame_t stk_frame = {.pc = exc_frame->pc, .sp = exc_frame->a1, .next_pc = exc_frame->a0};
panicPutStr("\r\nBacktrace:"); panicPutStr("\r\nBacktrace:");
/* Do not check sanity on first entry, PC could be smashed. */ putEntry(esp_cpu_process_stack_pc(stk_frame.pc), stk_frame.sp);
putEntry(pc, sp);
pc = frame->a0; //Check if first frame is valid
while (i++ < 100) { bool corrupted = (esp_stack_ptr_is_sane(stk_frame.sp) &&
uint32_t psp = sp; esp_ptr_executable((void*)esp_cpu_process_stack_pc(stk_frame.pc))) ?
if (!esp_stack_ptr_is_sane(sp) || i++ > 100) { false : true;
break; uint32_t i = ((depth <= 0) ? INT32_MAX : depth) - 1; //Account for stack frame that's already printed
} while (i-- > 0 && stk_frame.next_pc != 0 && !corrupted) {
sp = *((uint32_t *) (sp - 0x10 + 4)); if (!esp_backtrace_get_next_frame(&stk_frame)) { //Get next stack frame
putEntry(pc - 3, sp); // stack frame addresses are return addresses, so subtract 3 to get the CALL address corrupted = true;
pc = *((uint32_t *) (psp - 0x10));
if (pc < 0x40000000) {
break;
} }
putEntry(esp_cpu_process_stack_pc(stk_frame.pc), stk_frame.sp);
}
//Print backtrace termination marker
if (corrupted) {
panicPutStr(" |<-CORRUPTED");
} else if (stk_frame.next_pc != 0) { //Backtrace continues
panicPutStr(" |<-CONTINUES");
} }
panicPutStr("\r\n"); panicPutStr("\r\n");
} }
@ -549,7 +553,7 @@ static void commonErrorHandler_dump(XtExcFrame *frame, int core_id)
panicPutStr("\r\n"); panicPutStr("\r\n");
/* With windowed ABI backtracing is easy, let's do it. */ /* With windowed ABI backtracing is easy, let's do it. */
doBacktrace(frame); doBacktrace(frame, 100);
panicPutStr("\r\n"); panicPutStr("\r\n");
} }

View File

@ -28,6 +28,7 @@
#include "esp_intr.h" #include "esp_intr.h"
#include "esp_intr_alloc.h" #include "esp_intr_alloc.h"
#include "esp_attr.h" #include "esp_attr.h"
#include "esp_panic.h"
#include "esp_freertos_hooks.h" #include "esp_freertos_hooks.h"
#include "soc/timer_group_struct.h" #include "soc/timer_group_struct.h"
#include "soc/timer_group_reg.h" #include "soc/timer_group_reg.h"
@ -36,6 +37,7 @@
#include "driver/periph_ctrl.h" #include "driver/periph_ctrl.h"
#include "esp_task_wdt.h" #include "esp_task_wdt.h"
#include "esp_system_internal.h" #include "esp_system_internal.h"
#include "esp_crosscore_int.h"
static const char *TAG = "task_wdt"; static const char *TAG = "task_wdt";
@ -167,13 +169,24 @@ static void task_wdt_isr(void *arg)
} }
esp_task_wdt_isr_user_handler(); esp_task_wdt_isr_user_handler();
if (twdt_config->panic){ //Trigger Panic if configured to do so if (twdt_config->panic){ //Trigger Panic if configured to do so
ESP_EARLY_LOGE(TAG, "Aborting."); ESP_EARLY_LOGE(TAG, "Aborting.");
portEXIT_CRITICAL_ISR(&twdt_spinlock); portEXIT_CRITICAL_ISR(&twdt_spinlock);
esp_reset_reason_set_hint(ESP_RST_TASK_WDT); esp_reset_reason_set_hint(ESP_RST_TASK_WDT);
abort(); abort();
} else {
int current_core = xPortGetCoreID();
//Print backtrace of current core
ESP_EARLY_LOGE(TAG, "Print CPU %d (current core) backtrace", current_core);
esp_backtrace_print(100);
#if !CONFIG_FREERTOS_UNICORE
//Print backtrace of other core
ESP_EARLY_LOGE(TAG, "Print CPU %d backtrace", !current_core);
esp_crosscore_int_send_print_backtrace(!current_core);
#endif
} }
portEXIT_CRITICAL_ISR(&twdt_spinlock); portEXIT_CRITICAL_ISR(&twdt_spinlock);
} }

View File

@ -14,6 +14,7 @@
#include <string.h> #include <string.h>
#include <stdbool.h> #include <stdbool.h>
#include "esp_panic.h" #include "esp_panic.h"
#include "soc/soc_memory_layout.h"
#include "esp_core_dump_priv.h" #include "esp_core_dump_priv.h"
const static DRAM_ATTR char TAG[] __attribute__((unused)) = "esp_core_dump_port"; const static DRAM_ATTR char TAG[] __attribute__((unused)) = "esp_core_dump_port";

View File

@ -110,4 +110,25 @@ void esp_cpu_reset(int cpu_id);
*/ */
bool esp_cpu_in_ocd_debug_mode(); bool esp_cpu_in_ocd_debug_mode();
/**
* @brief Convert the PC register value to its true address
*
* The address of the current instruction is not stored as an exact uint32_t
* representation in PC register. This function will convert the value stored in
* the PC register to a uint32_t address.
*
* @param pc_raw The PC as stored in register format.
*
* @return Address in uint32_t format
*/
static inline uint32_t esp_cpu_process_stack_pc(uint32_t pc)
{
if (pc & 0x80000000) {
//Top two bits of a0 (return address) specify window increment. Overwrite to map to address space.
pc = (pc & 0x3fffffff) | 0x40000000;
}
//Minus 3 to get PC of previous instruction (i.e. instruction executed before return address)
return pc - 3;
}
#endif #endif

View File

@ -206,3 +206,10 @@ inline static bool IRAM_ATTR esp_ptr_in_diram_dram(const void *p) {
inline static bool IRAM_ATTR esp_ptr_in_diram_iram(const void *p) { inline static bool IRAM_ATTR esp_ptr_in_diram_iram(const void *p) {
return ((intptr_t)p >= SOC_DIRAM_IRAM_LOW && (intptr_t)p < SOC_DIRAM_IRAM_HIGH); return ((intptr_t)p >= SOC_DIRAM_IRAM_LOW && (intptr_t)p < SOC_DIRAM_IRAM_HIGH);
} }
inline static bool IRAM_ATTR esp_stack_ptr_is_sane(uint32_t sp)
{
//Check if stack ptr is in between SOC_DRAM_LOW and SOC_DRAM_HIGH, and 16 byte aligned.
return !(sp < SOC_DRAM_LOW + 0x10 || sp > SOC_DRAM_HIGH - 0x10 || ((sp & 0xF) != 0));
}

View File

@ -4,6 +4,10 @@ set(COMPONENT_SRCS "unity/src/unity.c"
set(COMPONENT_ADD_INCLUDEDIRS "include" set(COMPONENT_ADD_INCLUDEDIRS "include"
"unity/src") "unity/src")
if(CONFIG_UNITY_ENABLE_BACKTRACE_ON_FAIL)
list(APPEND COMPONENT_PRIV_INCLUDEDIRS "include/priv")
endif()
if(CONFIG_UNITY_ENABLE_IDF_TEST_RUNNER) if(CONFIG_UNITY_ENABLE_IDF_TEST_RUNNER)
list(APPEND COMPONENT_SRCS "unity_runner.c") list(APPEND COMPONENT_SRCS "unity_runner.c")
endif() endif()

View File

@ -42,4 +42,12 @@ menu "Unity unit testing library"
the build. These provide an optional set of macros and functions to the build. These provide an optional set of macros and functions to
implement test groups. implement test groups.
endmenu # "Unity unit testing library" config UNITY_ENABLE_BACKTRACE_ON_FAIL
bool "Print a backtrace when a unit test fails"
default n
help
If set, the unity framework will print the backtrace information before
jumping back to the test menu. The jumping is usually occurs in assert
functions such as TEST_ASSERT, TEST_FAIL etc.
endmenu # "Unity unit testing library"

View File

@ -9,6 +9,10 @@ endif
COMPONENT_ADD_INCLUDEDIRS = include unity/src COMPONENT_ADD_INCLUDEDIRS = include unity/src
COMPONENT_SRCDIRS = unity/src . COMPONENT_SRCDIRS = unity/src .
ifdef CONFIG_UNITY_ENABLE_BACKTRACE_ON_FAIL
COMPONENT_PRIV_INCLUDEDIRS += include/priv
endif
ifndef CONFIG_UNITY_ENABLE_IDF_TEST_RUNNER ifndef CONFIG_UNITY_ENABLE_IDF_TEST_RUNNER
COMPONENT_OBJEXCLUDE += unity_runner.o COMPONENT_OBJEXCLUDE += unity_runner.o
endif endif

View File

@ -0,0 +1,14 @@
#include_next <setjmp.h>
#include "esp_panic.h"
/*
* This is the middle layer of setjmp to be used with the unity.
*/
/** Insert backtrace before longjmp (TEST_ABORT).
*
* Currently we only do long jump before test is ignored or failed.
* If this is also called when test pass, we may need to add some check before
* backtrace is called.
*/
#define longjmp(buf, val) do {esp_backtrace_print(100); longjmp(buf, val);} while(0)

View File

@ -31,3 +31,4 @@ CONFIG_SPI_MASTER_IN_IRAM=y
CONFIG_EFUSE_VIRTUAL=y CONFIG_EFUSE_VIRTUAL=y
CONFIG_SPIRAM_BANKSWITCH_ENABLE=n CONFIG_SPIRAM_BANKSWITCH_ENABLE=n
CONFIG_FATFS_ALLOC_EXTRAM_FIRST=y CONFIG_FATFS_ALLOC_EXTRAM_FIRST=y
CONFIG_UNITY_ENABLE_BACKTRACE_ON_FAIL=y