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 <shubham.kulkarni@espressif.com>
This commit is contained in:
parent
774103d050
commit
8b7da334d5
8 changed files with 292 additions and 1 deletions
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
38
arch/xtensa/core/debug_helpers_asm.S
Normal file
38
arch/xtensa/core/debug_helpers_asm.S
Normal file
|
@ -0,0 +1,38 @@
|
|||
/*
|
||||
* Copyright (c) 2020 Espressif Systems (Shanghai) Co., Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include <xtensa/coreasm.h>
|
||||
#include <xtensa/corebits.h>
|
||||
#include <xtensa/config/system.h>
|
||||
#include <xtensa/hal.h>
|
||||
#include <xtensa-asm2-context.h>
|
||||
|
||||
.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
|
|
@ -10,6 +10,11 @@
|
|||
#include <kernel_arch_data.h>
|
||||
#include <xtensa/config/specreg.h>
|
||||
#include <xtensa-asm2-context.h>
|
||||
#if defined(CONFIG_XTENSA_ENABLE_BACKTRACE)
|
||||
#if XCHAL_HAVE_WINDOWED
|
||||
#include <xtensa_backtrace.h>
|
||||
#endif
|
||||
#endif
|
||||
#include <logging/log.h>
|
||||
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);
|
||||
}
|
||||
|
||||
|
|
96
arch/xtensa/core/include/xtensa_backtrace.h
Normal file
96
arch/xtensa/core/include/xtensa_backtrace.h
Normal file
|
@ -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 <stdbool.h>
|
||||
#include <stdint.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
|
||||
* 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
|
126
arch/xtensa/core/xtensa_backtrace.c
Normal file
126
arch/xtensa/core/xtensa_backtrace.c
Normal file
|
@ -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;
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue