From 8b7da334d5fcfd4170117565c771b53b96b7066e Mon Sep 17 00:00:00 2001 From: Shubham Kulkarni Date: Thu, 7 Jan 2021 11:41:11 +0530 Subject: [PATCH] arch: xtensa: Print backtrace from panic handler This change uses stack frame to print backtrace once exception occurs Printing backtrace helps to identify the cause of exception Signed-off-by: Shubham Kulkarni --- arch/xtensa/Kconfig | 7 ++ arch/xtensa/core/CMakeLists.txt | 4 + arch/xtensa/core/debug_helpers_asm.S | 38 ++++++ arch/xtensa/core/fatal.c | 11 +- arch/xtensa/core/include/xtensa_backtrace.h | 96 +++++++++++++++ arch/xtensa/core/xtensa_backtrace.c | 126 ++++++++++++++++++++ arch/xtensa/include/xtensa-asm2-context.h | 9 ++ arch/xtensa/include/xtensa-asm2-s.h | 2 + 8 files changed, 292 insertions(+), 1 deletion(-) create mode 100644 arch/xtensa/core/debug_helpers_asm.S create mode 100644 arch/xtensa/core/include/xtensa_backtrace.h create mode 100644 arch/xtensa/core/xtensa_backtrace.c diff --git a/arch/xtensa/Kconfig b/arch/xtensa/Kconfig index 436ecf5c285..f92b2e298b2 100644 --- a/arch/xtensa/Kconfig +++ b/arch/xtensa/Kconfig @@ -71,4 +71,11 @@ config XTENSA_KERNEL_CPU_PTR_SR Specify which special register to store the pointer to _kernel.cpus[] for the current CPU. +config XTENSA_ENABLE_BACKTRACE + bool "Enable backtrace on panic exception" + default y + depends on SOC_ESP32 + help + Enable this config option to print backtrace on panic exception + endmenu diff --git a/arch/xtensa/core/CMakeLists.txt b/arch/xtensa/core/CMakeLists.txt index f8e62262610..96fcc299fc8 100644 --- a/arch/xtensa/core/CMakeLists.txt +++ b/arch/xtensa/core/CMakeLists.txt @@ -17,5 +17,9 @@ zephyr_library_sources_ifndef(CONFIG_ATOMIC_OPERATIONS_C atomic.S) zephyr_library_sources_ifdef(CONFIG_XTENSA_USE_CORE_CRT1 crt1.S) zephyr_library_sources_ifdef(CONFIG_IRQ_OFFLOAD irq_offload.c) zephyr_library_sources_ifdef(CONFIG_THREAD_LOCAL_STORAGE tls.c) +zephyr_library_sources_ifdef(CONFIG_XTENSA_ENABLE_BACKTRACE xtensa_backtrace.c) +zephyr_library_sources_ifdef(CONFIG_XTENSA_ENABLE_BACKTRACE debug_helpers_asm.S) + +zephyr_library_include_directories(include) add_subdirectory(startup) diff --git a/arch/xtensa/core/debug_helpers_asm.S b/arch/xtensa/core/debug_helpers_asm.S new file mode 100644 index 00000000000..2c0a9bba2e0 --- /dev/null +++ b/arch/xtensa/core/debug_helpers_asm.S @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2020 Espressif Systems (Shanghai) Co., Ltd. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include +#include + + .section .iram1, "ax" + .align 4 + .global z_xtensa_backtrace_get_start + .type z_xtensa_backtrace_get_start, @function +z_xtensa_backtrace_get_start: + entry a1, 32 + /* Spill registers onto stack (excluding this function) */ + call8 xthal_window_spill + /* a2, a3, a4 should be out arguments for i PC, i SP, i-1 PC respectively. + * Use a6 and a7 as scratch */ + + /* Load address for interrupted stack */ + l32i a6, a5, 0 + /* Load i PC in a7 */ + l32i a7, a6, BSA_PC_OFF + /* Store value of i PC in a2 */ + s32i a7, a2, 0 + /* Load value for (i-1) PC, which return address of i into a7 */ + l32i a7, a6, BSA_A0_OFF + /* Store value of (i-1) PC in a4 */ + s32i a7, a4, 0 + /* Add BASE_SAVE_AREA_SIZE in interrupted stack to get i SP */ + addi a6, a6, BASE_SAVE_AREA_SIZE + /* Store i SP in a3 */ + s32i a6, a3, 0 + retw diff --git a/arch/xtensa/core/fatal.c b/arch/xtensa/core/fatal.c index cea4c4f8203..4f749b0e513 100644 --- a/arch/xtensa/core/fatal.c +++ b/arch/xtensa/core/fatal.c @@ -10,6 +10,11 @@ #include #include #include +#if defined(CONFIG_XTENSA_ENABLE_BACKTRACE) +#if XCHAL_HAVE_WINDOWED +#include +#endif +#endif #include LOG_MODULE_DECLARE(os, CONFIG_KERNEL_LOG_LEVEL); @@ -92,7 +97,11 @@ void z_xtensa_fatal_error(unsigned int reason, const z_arch_esf_t *esf) if (esf) { z_xtensa_dump_stack(esf); } - +#if defined(CONFIG_XTENSA_ENABLE_BACKTRACE) +#if XCHAL_HAVE_WINDOWED + z_xtensa_backtrace_print(100, (int *)esf); +#endif +#endif z_fatal_error(reason, esf); } diff --git a/arch/xtensa/core/include/xtensa_backtrace.h b/arch/xtensa/core/include/xtensa_backtrace.h new file mode 100644 index 00000000000..96e3fe86948 --- /dev/null +++ b/arch/xtensa/core/include/xtensa_backtrace.h @@ -0,0 +1,96 @@ +/* + * Copyright (c) 2020 Espressif Systems (Shanghai) Co., Ltd. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +#ifndef __ASSEMBLER__ + +#include +#include + +/* + * @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 + * z_xtensa_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 + */ +struct z_xtensa_backtrace_frame_t { + 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 */ +}; + +/** + * 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 + * @param[in] interrupted_stack Pointer to interrupted stack + */ +void z_xtensa_backtrace_get_start(uint32_t *pc, + uint32_t *sp, + uint32_t *next_pc, + int *interrupted_stack); + +/** + * 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 z_xtensa_backtrace_get_next_frame(struct z_xtensa_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) + * @param interrupted_stack Pointer to interrupted stack + * + * @return + * - 0 Backtrace successfully printed to completion or to depth limit + * - -1 Backtrace is corrupted + */ +int z_xtensa_backtrace_print(int depth, int *interrupted_stack); + +#endif +#ifdef __cplusplus +} +#endif diff --git a/arch/xtensa/core/xtensa_backtrace.c b/arch/xtensa/core/xtensa_backtrace.c new file mode 100644 index 00000000000..cde3747d450 --- /dev/null +++ b/arch/xtensa/core/xtensa_backtrace.c @@ -0,0 +1,126 @@ +/* + * Copyright (c) 2020 Espressif Systems (Shanghai) Co., Ltd. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "xtensa/corebits.h" +#include "xtensa_backtrace.h" +#include "sys/printk.h" +#if defined(CONFIG_SOC_ESP32) +#include "soc/soc_memory_layout.h" +#endif +static int mask, cause; + +static inline uint32_t z_xtensa_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. + */ + if (cause != EXCCAUSE_INSTR_PROHIBITED) { + pc = (pc & 0x3fffffff) | mask; + } else { + pc = (pc & 0x3fffffff) | 0x40000000; + } + } + /* Minus 3 to get PC of previous instruction + * (i.e. instruction executed before return address) + */ + return pc - 3; +} + +static inline bool z_xtensa_stack_ptr_is_sane(uint32_t sp) +{ +#if defined(CONFIG_SOC_ESP32) + return esp_stack_ptr_is_sane(sp); +#else +#warning "z_xtensa_stack_ptr_is_sane is not defined for this platform" +#endif +} + +static inline bool z_xtensa_ptr_executable(const void *p) +{ +#if defined(CONFIG_SOC_ESP32) + return esp_ptr_executable(p); +#else +#warning "z_xtensa_ptr_executable is not defined for this platform" +#endif +} + +bool z_xtensa_backtrace_get_next_frame(struct z_xtensa_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 + */ + + /* Base save area consists of 4 words under SP */ + char *base_save = (char *)frame->sp; + + frame->pc = frame->next_pc; + /* If next_pc = 0, indicates frame(i-1) is the last + * frame on the stack + */ + frame->next_pc = *((uint32_t *)(base_save - 16)); + frame->sp = *((uint32_t *)(base_save - 12)); + + /* Return true if both sp and pc of frame(i-1) are sane, + * false otherwise + */ + return (z_xtensa_stack_ptr_is_sane(frame->sp) && + z_xtensa_ptr_executable((void *) + z_xtensa_cpu_process_stack_pc(frame->pc))); +} + +int z_xtensa_backtrace_print(int depth, int *interrupted_stack) +{ + /* Check arguments */ + if (depth <= 0) { + return -1; + } + + /* Initialize stk_frame with first frame of stack */ + struct z_xtensa_backtrace_frame_t stk_frame; + + z_xtensa_backtrace_get_start(&(stk_frame.pc), &(stk_frame.sp), + &(stk_frame.next_pc), interrupted_stack); + __asm__ volatile("l32i a4, a3, 0"); + __asm__ volatile("l32i a4, a4, 4"); + __asm__ volatile("mov %0, a4" : "=r"(cause)); + if (cause != EXCCAUSE_INSTR_PROHIBITED) { + mask = stk_frame.pc & 0xc0000000; + } + printk("\r\n\r\nBacktrace:"); + printk("0x%08X:0x%08X ", + z_xtensa_cpu_process_stack_pc(stk_frame.pc), + stk_frame.sp); + + /* Check if first frame is valid */ + bool corrupted = !(z_xtensa_stack_ptr_is_sane(stk_frame.sp) && + (z_xtensa_ptr_executable((void *) + z_xtensa_cpu_process_stack_pc(stk_frame.pc)) || + /* Ignore the first corrupted PC in case of InstrFetchProhibited */ + cause == EXCCAUSE_INSTR_PROHIBITED)); + + uint32_t i = (depth <= 0) ? INT32_MAX : depth; + + while (i-- > 0 && stk_frame.next_pc != 0 && !corrupted) { + /* Get previous stack frame */ + if (!z_xtensa_backtrace_get_next_frame(&stk_frame)) { + corrupted = true; + } + printk("0x%08X:0x%08X ", z_xtensa_cpu_process_stack_pc(stk_frame.pc), stk_frame.sp); + } + + /* Print backtrace termination marker */ + int ret = 0; + + if (corrupted) { + printk(" |<-CORRUPTED"); + ret = -1; + } else if (stk_frame.next_pc != 0) { /* Backtrace continues */ + printk(" |<-CONTINUES"); + } + printk("\r\n\r\n"); + return ret; +} diff --git a/arch/xtensa/include/xtensa-asm2-context.h b/arch/xtensa/include/xtensa-asm2-context.h index 1f21c03774a..cac96fcb78c 100644 --- a/arch/xtensa/include/xtensa-asm2-context.h +++ b/arch/xtensa/include/xtensa-asm2-context.h @@ -65,6 +65,7 @@ */ #define BASE_SAVE_AREA_SIZE_COMMON 44 +#define BASE_SAVE_AREA_SIZE_EXCCAUSE 4 #if XCHAL_HAVE_LOOPS #define BASE_SAVE_AREA_SIZE_LOOPS 12 @@ -87,6 +88,7 @@ #define BASE_SAVE_AREA_SIZE \ (BASE_SAVE_AREA_SIZE_COMMON + \ BASE_SAVE_AREA_SIZE_LOOPS + \ + BASE_SAVE_AREA_SIZE_EXCCAUSE + \ BASE_SAVE_AREA_SIZE_SCOMPARE + \ BASE_SAVE_AREA_SIZE_THREADPTR) @@ -101,11 +103,17 @@ #define BSA_LEND_OFF (BASE_SAVE_AREA_SIZE - 52) #define BSA_LCOUNT_OFF (BASE_SAVE_AREA_SIZE - 56) +#define BSA_EXCCAUSE_OFF \ + (BASE_SAVE_AREA_SIZE - \ + (BASE_SAVE_AREA_SIZE_COMMON + \ + BASE_SAVE_AREA_SIZE_LOOPS + \ + BASE_SAVE_AREA_SIZE_EXCCAUSE)) #if XCHAL_HAVE_S32C1I #define BSA_SCOMPARE1_OFF \ (BASE_SAVE_AREA_SIZE - \ (BASE_SAVE_AREA_SIZE_COMMON + \ BASE_SAVE_AREA_SIZE_LOOPS + \ + BASE_SAVE_AREA_SIZE_EXCCAUSE + \ BASE_SAVE_AREA_SIZE_SCOMPARE)) #endif @@ -114,6 +122,7 @@ (BASE_SAVE_AREA_SIZE - \ (BASE_SAVE_AREA_SIZE_COMMON + \ BASE_SAVE_AREA_SIZE_LOOPS + \ + BASE_SAVE_AREA_SIZE_EXCCAUSE + \ BASE_SAVE_AREA_SIZE_SCOMPARE + \ BASE_SAVE_AREA_SIZE_THREADPTR)) #endif diff --git a/arch/xtensa/include/xtensa-asm2-s.h b/arch/xtensa/include/xtensa-asm2-s.h index f7848814eac..cbfe81a0970 100644 --- a/arch/xtensa/include/xtensa-asm2-s.h +++ b/arch/xtensa/include/xtensa-asm2-s.h @@ -110,6 +110,8 @@ rsr.LCOUNT a0 s32i a0, a1, BSA_LCOUNT_OFF #endif + rsr.exccause a0 + s32i a0, a1, BSA_EXCCAUSE_OFF #if XCHAL_HAVE_S32C1I rsr.SCOMPARE1 a0 s32i a0, a1, BSA_SCOMPARE1_OFF