esp32: Refactor backtrace and add esp_backtrace_print()

This commit refactors backtracing within the panic handler so that a common
function esp_backtrace_get_next_frame() is used iteratively to traverse a
callstack.

A esp_backtrace_print() function has also be added that allows the printing
of a backtrace at runtime. The esp_backtrace_print() function allows unity to
print the backtrace of failed test cases and jump back to the main test menu
without the need reset the chip. esp_backtrace_print() can also be used as a
debugging function by users.

- esp_stack_ptr_is_sane() moved to soc_memory_layout.h
- removed uncessary includes of "esp_debug_helpers.h"
This commit is contained in:
Darian Leung 2018-11-29 17:06:21 +08:00
parent 28e0a17e0a
commit 037c079e9a
17 changed files with 293 additions and 32 deletions

View file

@ -59,7 +59,6 @@
#include "esp_phy_init.h" #include "esp_phy_init.h"
#include "esp32/cache_err_int.h" #include "esp32/cache_err_int.h"
#include "esp_coexist_internal.h" #include "esp_coexist_internal.h"
#include "esp_debug_helpers.h"
#include "esp_core_dump.h" #include "esp_core_dump.h"
#include "esp_app_trace.h" #include "esp_app_trace.h"
#include "esp_private/dbg_stubs.h" #include "esp_private/dbg_stubs.h"

View file

@ -22,8 +22,8 @@
#include "esp32/rom/ets_sys.h" #include "esp32/rom/ets_sys.h"
#include "soc/uart_periph.h" #include "soc/uart_periph.h"
#include "soc/gpio_periph.h" #include "soc/gpio_periph.h"
#include "soc/soc_memory_layout.h"
#include "esp_private/gdbstub.h" #include "esp_private/gdbstub.h"
#include "esp_debug_helpers.h"
#include "driver/gpio.h" #include "driver/gpio.h"
#include "freertos/FreeRTOS.h" #include "freertos/FreeRTOS.h"
#include "freertos/task.h" #include "freertos/task.h"

View file

@ -30,6 +30,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_private/gdbstub.h" #include "esp_private/gdbstub.h"
#include "esp_debug_helpers.h" #include "esp_debug_helpers.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

@ -13,7 +13,7 @@
// limitations under the License. // limitations under the License.
#include <string.h> #include <string.h>
#include <stdbool.h> #include <stdbool.h>
#include "esp_debug_helpers.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

@ -78,7 +78,6 @@ task.h is included from an application file. */
#include "esp32/rom/ets_sys.h" #include "esp32/rom/ets_sys.h"
#include "esp_newlib.h" #include "esp_newlib.h"
#include "esp_debug_helpers.h"
/* FreeRTOS includes. */ /* FreeRTOS includes. */
#include "FreeRTOS.h" #include "FreeRTOS.h"

View file

@ -14,7 +14,6 @@
#include "unity.h" #include "unity.h"
#include "esp_heap_caps.h" #include "esp_heap_caps.h"
#include "esp_debug_helpers.h"
#include "sdkconfig.h" #include "sdkconfig.h"

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

@ -203,3 +203,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_debug_helpers.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

@ -1,4 +1,4 @@
set(COMPONENT_SRCS "eri.c" "trax.c") set(COMPONENT_SRCS "eri.c" "trax.c" "debug_helpers.c" "debug_helpers_asm.S")
set(COMPONENT_ADD_INCLUDEDIRS "include" "${IDF_TARGET}/include") set(COMPONENT_ADD_INCLUDEDIRS "include" "${IDF_TARGET}/include")

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_debug_helpers.h"
#include "esp32/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

@ -1,3 +1,17 @@
// 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.
#pragma once #pragma once
#ifdef __cplusplus #ifdef __cplusplus
@ -12,6 +26,24 @@ extern "C" {
#define ESP_WATCHPOINT_STORE 0x80000000 #define ESP_WATCHPOINT_STORE 0x80000000
#define ESP_WATCHPOINT_ACCESS 0xC0000000 #define ESP_WATCHPOINT_ACCESS 0xC0000000
/*
* @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.
@ -37,7 +69,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
* *
@ -47,12 +78,53 @@ esp_err_t esp_set_watchpoint(int no, void *adr, int size, int flags);
void esp_clear_watchpoint(int no); void esp_clear_watchpoint(int no);
/** /**
* @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);
{
return !(sp < 0x3ffae010UL || sp > 0x3ffffff0UL || ((sp & 0xf) != 0)); /**
} * Get the next frame on a stack for backtracing
*
* Given a stack frame(i), this function will obtain the next stack frame(i-1)
* 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

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