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:
Shubham Kulkarni 2021-01-07 11:41:11 +05:30 committed by Anas Nashif
commit 8b7da334d5
8 changed files with 292 additions and 1 deletions

View file

@ -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

View file

@ -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)

View 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

View file

@ -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);
}

View 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

View 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;
}

View file

@ -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

View file

@ -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