Commit 30aae54c authored by Thorsten von Eicken's avatar Thorsten von Eicken

esp8266: add exception handler to print stack dump

parent 23d8b78e
......@@ -675,7 +675,7 @@ endif
ifdef DEBUG
#OPTIMIZEFLAGS=-Os -g
ifeq ($(FAMILY),ESP8266)
OPTIMIZEFLAGS=-g -Os
OPTIMIZEFLAGS=-g -Os -std=gnu11 -fgnu89-inline -Wl,--allow-multiple-definition
else
OPTIMIZEFLAGS=-g
endif
......@@ -1597,6 +1597,8 @@ SOURCES += targets/esp8266/uart.c \
targets/esp8266/jshardware.c \
targets/esp8266/i2c_master.c \
targets/esp8266/esp8266_board_utils.c \
targets/esp8266/gdbstub.c \
targets/esp8266/gdbstub-entry.S \
libs/network/esp8266/network_esp8266.c
# The tool used for building the firmware and flashing
......
#ifndef GDBSTUB_CFG_H
#define GDBSTUB_CFG_H
/*
Enable this define if you're using the RTOS SDK. It will use a custom exception handler instead of the HAL
and do some other magic to make everything work and compile under FreeRTOS.
*/
#ifndef GDBSTUB_FREERTOS
#define GDBSTUB_FREERTOS 0
#endif
/*
Enable this to make the exception and debugging handlers switch to a private stack. This will use
up 1K of RAM, but may be useful if you're debugging stack or stack pointer corruption problems. It's
normally disabled because not many situations need it. If for some reason the GDB communication
stops when you run into an error in your code, try enabling this.
*/
#ifndef GDBSTUB_USE_OWN_STACK
#define GDBSTUB_USE_OWN_STACK 0
#endif
/*
If this is defined, gdbstub will break the program when you press Ctrl-C in gdb. it does this by
hooking the UART interrupt. Unfortunately, this means receiving stuff over the serial port won't
work for your program anymore. This will fail if your program sets an UART interrupt handler after
the gdbstub_init call.
*/
#ifndef GDBSTUB_CTRLC_BREAK
#define GDBSTUB_CTRLC_BREAK 0
#endif
/*
Enabling this will redirect console output to GDB. This basically means that printf/os_printf output
will show up in your gdb session, which is useful if you use gdb to do stuff. It also means that if
you use a normal terminal, you can't read the printfs anymore.
*/
#ifndef GDBSTUB_REDIRECT_CONSOLE_OUTPUT
#define GDBSTUB_REDIRECT_CONSOLE_OUTPUT 0
#endif
/*
Enable this if you want the GDB stub to wait for you to attach GDB before running. It does this by
breaking in the init routine; use the gdb 'c' command (continue) to start the program.
*/
#ifndef GDBSTUB_BREAK_ON_INIT
#define GDBSTUB_BREAK_ON_INIT 0
#endif
/*
Function attributes for function types.
Gdbstub functions are placed in flash or IRAM using attributes, as defined here. The gdbinit function
(and related) can always be in flash, because it's called in the normal code flow. The rest of the
gdbstub functions can be in flash too, but only if there's no chance of them being called when the
flash somehow is disabled (eg during SPI operations or flash write/erase operations). If the routines
are called when the flash is disabled (eg due to a Ctrl-C at the wrong time), the ESP8266 will most
likely crash.
*/
#define ATTR_GDBINIT ICACHE_FLASH_ATTR
#define ATTR_GDBFN
#endif
/******************************************************************************
* Copyright 2015 Espressif Systems
*
* Description: Assembly routines for the gdbstub
*
* License: ESPRESSIF MIT License
*******************************************************************************/
#include "gdbstub-cfg.h"
#include <xtensa/config/specreg.h>
#include <xtensa/config/core-isa.h>
#include <xtensa/corebits.h>
#define DEBUG_PC (EPC + XCHAL_DEBUGLEVEL)
#define DEBUG_EXCSAVE (EXCSAVE + XCHAL_DEBUGLEVEL)
#define DEBUG_PS (EPS + XCHAL_DEBUGLEVEL)
.global gdbstub_savedRegs
#if GDBSTUB_USE_OWN_STACK
.global gdbstub_exceptionStack
#endif
.text
.literal_position
.text
.align 4
/*
The savedRegs struct:
uint32_t pc;
uint32_t ps;
uint32_t sar;
uint32_t vpri;
uint32_t a0;
uint32_t a[14]; //a2..a15
uint32_t litbase;
uint32_t sr176;
uint32_t sr208;
uint32_t a1;
uint32_t reason;
*/
/*
This is the debugging exception routine; it's called by the debugging vector
We arrive here with all regs intact except for a2. The old contents of A2 are saved
into the DEBUG_EXCSAVE special function register. EPC is the original PC.
*/
gdbstub_debug_exception_entry:
/*
//Minimum no-op debug exception handler, for debug
rsr a2,DEBUG_PC
addi a2,a2,3
wsr a2,DEBUG_PC
xsr a2, DEBUG_EXCSAVE
rfi XCHAL_DEBUGLEVEL
*/
//Save all regs to structure
movi a2, gdbstub_savedRegs
s32i a0, a2, 0x10
s32i a1, a2, 0x58
rsr a0, DEBUG_PS
s32i a0, a2, 0x04
rsr a0, DEBUG_EXCSAVE //was R2
s32i a0, a2, 0x14
s32i a3, a2, 0x18
s32i a4, a2, 0x1c
s32i a5, a2, 0x20
s32i a6, a2, 0x24
s32i a7, a2, 0x28
s32i a8, a2, 0x2c
s32i a9, a2, 0x30
s32i a10, a2, 0x34
s32i a11, a2, 0x38
s32i a12, a2, 0x3c
s32i a13, a2, 0x40
s32i a14, a2, 0x44
s32i a15, a2, 0x48
rsr a0, SAR
s32i a0, a2, 0x08
rsr a0, LITBASE
s32i a0, a2, 0x4C
rsr a0, 176
s32i a0, a2, 0x50
rsr a0, 208
s32i a0, a2, 0x54
rsr a0, DEBUGCAUSE
s32i a0, a2, 0x5C
rsr a4, DEBUG_PC
s32i a4, a2, 0x00
#if GDBSTUB_USE_OWN_STACK
//Move to our own stack
movi a1, exceptionStack+255*4
#endif
//If ICOUNT is -1, disable it by setting it to 0, otherwise we will keep triggering on the same instruction.
rsr a2, ICOUNT
movi a3, -1
bne a2, a3, noIcountReset
movi a3, 0
wsr a3, ICOUNT
noIcountReset:
rsr a2, ps
addi a2, a2, -PS_EXCM_MASK
wsr a2, ps
rsync
//Call into the C code to do the actual handling.
call0 gdbstub_handle_debug_exception
DebugExceptionExit:
rsr a2, ps
addi a2, a2, PS_EXCM_MASK
wsr a2, ps
rsync
//Restore registers from the gdbstub_savedRegs struct
movi a2, gdbstub_savedRegs
l32i a0, a2, 0x00
wsr a0, DEBUG_PC
// l32i a0, a2, 0x54
// wsr a0, 208
l32i a0, a2, 0x50
//wsr a0, 176 //Some versions of gcc do not understand this...
.byte 0x00, 176, 0x13 //so we hand-assemble the instruction.
l32i a0, a2, 0x4C
wsr a0, LITBASE
l32i a0, a2, 0x08
wsr a0, SAR
l32i a15, a2, 0x48
l32i a14, a2, 0x44
l32i a13, a2, 0x40
l32i a12, a2, 0x3c
l32i a11, a2, 0x38
l32i a10, a2, 0x34
l32i a9, a2, 0x30
l32i a8, a2, 0x2c
l32i a7, a2, 0x28
l32i a6, a2, 0x24
l32i a5, a2, 0x20
l32i a4, a2, 0x1c
l32i a3, a2, 0x18
l32i a0, a2, 0x14
wsr a0, DEBUG_EXCSAVE //was R2
l32i a0, a2, 0x04
wsr a0, DEBUG_PS
l32i a1, a2, 0x58
l32i a0, a2, 0x10
//Read back vector-saved a2 value, put back address of this routine.
movi a2, gdbstub_debug_exception_entry
xsr a2, DEBUG_EXCSAVE
//All done. Return to where we came from.
rfi XCHAL_DEBUGLEVEL
#if GDBSTUB_FREERTOS
/*
FreeRTOS exception handling code. For some reason or another, we can't just hook the main exception vector: it
seems FreeRTOS uses that for something else too (interrupts). FreeRTOS has its own fatal exception handler, and we
hook that. Unfortunately, that one is called from a few different places (eg directly in the DoubleExceptionVector)
so the precise location of the original register values are somewhat of a mystery when we arrive here...
As a 'solution', we'll just decode the most common case of the user_fatal_exception_handler being called from
the user exception handler vector:
- excsave1 - orig a0
- a1: stack frame:
sf+16: orig a1
sf+8: ps
sf+4: epc
sf+12: orig a0
sf: magic no?
*/
.global gdbstub_handle_user_exception
.global gdbstub_user_exception_entry
.align 4
gdbstub_user_exception_entry:
//Save all regs to structure
movi a0, gdbstub_savedRegs
s32i a1, a0, 0x14 //was a2
s32i a3, a0, 0x18
s32i a4, a0, 0x1c
s32i a5, a0, 0x20
s32i a6, a0, 0x24
s32i a7, a0, 0x28
s32i a8, a0, 0x2c
s32i a9, a0, 0x30
s32i a10, a0, 0x34
s32i a11, a0, 0x38
s32i a12, a0, 0x3c
s32i a13, a0, 0x40
s32i a14, a0, 0x44
s32i a15, a0, 0x48
rsr a2, SAR
s32i a2, a0, 0x08
rsr a2, LITBASE
s32i a2, a0, 0x4C
rsr a2, 176
s32i a2, a0, 0x50
rsr a2, 208
s32i a2, a0, 0x54
rsr a2, EXCCAUSE
s32i a2, a0, 0x5C
//Get the rest of the regs from the stack struct
l32i a3, a1, 12
s32i a3, a0, 0x10
l32i a3, a1, 16
s32i a3, a0, 0x58
l32i a3, a1, 8
s32i a3, a0, 0x04
l32i a3, a1, 4
s32i a3, a0, 0x00
#if GDBSTUB_USE_OWN_STACK
movi a1, exceptionStack+255*4
#endif
rsr a2, ps
addi a2, a2, -PS_EXCM_MASK
wsr a2, ps
rsync
call0 gdbstub_handle_user_exception
UserExceptionExit:
/*
Okay, from here on, it Does Not Work. There's not really any continuing from an exception in the
FreeRTOS case; there isn't any effort put in reversing the mess the exception code made yet. Maybe this
is still something we need to implement later, if there's any demand for it, or maybe we should modify
FreeRTOS to allow this in the future. (Which will then kill backwards compatibility... hmmm.)
*/
j UserExceptionExit
.global gdbstub_handle_uart_int
.global gdbstub_uart_entry
.align 4
gdbstub_uart_entry:
//On entry, the stack frame is at SP+16.
//This is a small stub to present that as the first arg to the gdbstub_handle_uart function.
movi a2, 16
add a2, a2, a1
movi a3, gdbstub_handle_uart_int
jx a3
#endif
.global gdbstub_save_extra_sfrs_for_exception
.align 4
//The Xtensa OS HAL does not save all the special function register things. This bit of assembly
//fills the gdbstub_savedRegs struct with them.
gdbstub_save_extra_sfrs_for_exception:
movi a2, gdbstub_savedRegs
rsr a3, LITBASE
s32i a3, a2, 0x4C
rsr a3, 176
s32i a3, a2, 0x50
rsr a3, 208
s32i a3, a2, 0x54
rsr a3, EXCCAUSE
s32i a3, a2, 0x5C
rsr a3, EXCVADDR
s32i a3, a2, 0x60
ret
.global gdbstub_init_debug_entry
.global _DebugExceptionVector
.align 4
gdbstub_init_debug_entry:
//This puts the following 2 instructions into the debug exception vector:
// xsr a2, DEBUG_EXCSAVE
// jx a2
movi a2, _DebugExceptionVector
movi a3, 0xa061d220
s32i a3, a2, 0
movi a3, 0x00000002
s32i a3, a2, 4
//Tell the just-installed debug vector where to go.
movi a2, gdbstub_debug_exception_entry
wsr a2, DEBUG_EXCSAVE
ret
//Set up ICOUNT register to step one single instruction
.global gdbstub_icount_ena_single_step
.align 4
gdbstub_icount_ena_single_step:
movi a3, XCHAL_DEBUGLEVEL //Only count steps in non-debug mode
movi a2, -2
wsr a3, ICOUNTLEVEL
wsr a2, ICOUNT
isync
ret
//These routines all assume only one breakpoint and watchpoint is available, which
//is the case for the ESP8266 Xtensa core.
.global gdbstub_set_hw_breakpoint
gdbstub_set_hw_breakpoint:
//a2 - addr, a3 - len (unused here)
rsr a4, IBREAKENABLE
bbsi a4, 0, return_w_error
wsr a2, IBREAKA
movi a2, 1
wsr a2, IBREAKENABLE
isync
movi a2, 1
ret
.global gdbstub_del_hw_breakpoint
gdbstub_del_hw_breakpoint:
//a2 - addr
rsr a5, IBREAKENABLE
bbci a5, 0, return_w_error
rsr a3, IBREAKA
bne a3, a2, return_w_error
movi a2,0
wsr a2, IBREAKENABLE
isync
movi a2, 1
ret
.global gdbstub_set_hw_watchpoint
//a2 - addr, a3 - mask, a4 - type (1=read, 2=write, 3=access)
gdbstub_set_hw_watchpoint:
//Check if any of the masked address bits are set. If so, that is an error.
movi a5,0x0000003F
xor a5, a5, a3
bany a2, a5, return_w_error
//Check if watchpoint already is set
rsr a5, DBREAKC
movi a6, 0xC0000000
bany a6, a5, return_w_error
//Set watchpoint
wsr a2, DBREAKA
//Combine type and mask
movi a6, 0x3F
and a3, a3, a6
slli a4, a4, 30
or a3, a3, a4
wsr a3, DBREAKC
// movi a2, 1
mov a2, a3
isync
ret
.global gdbstub_del_hw_watchpoint
//a2 - addr
gdbstub_del_hw_watchpoint:
//See if the address matches
rsr a3, DBREAKA
bne a3, a2, return_w_error
//See if the bp actually is set
rsr a3, DBREAKC
movi a2, 0xC0000000
bnone a3, a2, return_w_error
//Disable bp
movi a2,0
wsr a2,DBREAKC
movi a2,1
isync
ret
return_w_error:
movi a2, 0
ret
//Breakpoint, with an attempt at a functional function prologue and epilogue...
.global gdbstub_do_break_breakpoint_addr
.global gdbstub_do_break
.align 4
gdbstub_do_break:
addi a1, a1, -16
s32i a15, a1, 12
mov a15, a1
gdbstub_do_break_breakpoint_addr:
break 0,0
mov a1, a15
l32i a15, a1, 12
addi a1, a1, 16
ret
#ifndef GDBSTUB_ENTRY_H
#define GDBSTUB_ENTRY_H
#ifdef __cplusplus
extern "C" {
#endif
void gdbstub_init_debug_entry();
void gdbstub_do_break();
void gdbstub_icount_ena_single_step();
void gdbstub_save_extra_sfrs_for_exception();
void gdbstub_uart_entry();
int gdbstub_set_hw_breakpoint(int addr, int len);
int gdbstub_set_hw_watchpoint(int addr, int len, int type);
int gdbstub_del_hw_breakpoint(int addr);
int gdbstub_del_hw_watchpoint(int addr);
extern void* gdbstub_do_break_breakpoint_addr;
#ifdef __cplusplus
{
#endif
#endif
/******************************************************************************
* Copyright 2015 Espressif Systems
*
* Description: A stub to make the ESP8266 debuggable by GDB over the serial
* port.
*
* License: ESPRESSIF MIT License
*******************************************************************************/
#include "gdbstub.h"
#include <stddef.h>
#include "ets_sys.h"
#include "eagle_soc.h"
#include "c_types.h"
#include "gpio.h"
#include "xtensa/corebits.h"
#include "gdbstub.h"
#include "gdbstub-entry.h"
#include "gdbstub-cfg.h"
#include "osapi.h"
#include "user_interface.h"
#include "espmissingincludes.h"
//From xtruntime-frames.h
struct XTensa_exception_frame_s {
uint32_t pc;
uint32_t ps;
uint32_t sar;
uint32_t vpri;
uint32_t a0;
uint32_t a[14]; //a2..a15
//These are added manually by the exception code; the HAL doesn't set these on an exception.
uint32_t litbase;
uint32_t sr176;
uint32_t sr208;
uint32_t a1;
uint32_t reason;
uint32_t excvaddr;
};
//The asm stub saves the Xtensa registers here when a debugging exception happens.
struct XTensa_exception_frame_s gdbstub_savedRegs;
//Get the value of one of the A registers
static unsigned int ATTR_GDBFN getaregval(int reg) {
if (reg==0) return gdbstub_savedRegs.a0;
if (reg==1) return gdbstub_savedRegs.a1;
return gdbstub_savedRegs.a[reg-2];
}
static void print_stack(uint32_t start, uint32_t end) {
os_printf("\nStack dump:\n");
for (uint32_t pos = start; pos < end; pos += 0x10) {
uint32_t* values = (uint32_t*)(pos);
// rough indicator: stack frames usually have SP saved as the second word
bool looksLikeStackFrame = (values[2] == pos + 0x10);
os_printf("%08lx: %08lx %08lx %08lx %08lx %c\n",
pos, values[0], values[1], values[2], values[3], (looksLikeStackFrame)?'<':' ');
}
os_printf("\n");
}
void _xtos_set_exception_handler(int cause, void (exhandler)(struct XTensa_exception_frame_s *frame));
int os_printf_plus(const char *format, ...) __attribute__ ((format (printf, 1, 2)));
#define EXCEPTION_GDB_SP_OFFSET 0x100
// Print exception info to console
static void printReason() {
//register uint32_t sp asm("a1");
struct XTensa_exception_frame_s *reg = &gdbstub_savedRegs;
os_printf("Fatal exception %ld\n", reg->reason);
os_printf("pc=0x%08lx sp=0x%08lx excvaddr=0x%08lx\n", reg->pc, reg->a1, reg->excvaddr);
os_printf("ps=0x%08lx sar=0x%08lx vpri=0x%08lx\n", reg->ps, reg->sar, reg->vpri);
for (int i=0; i<16; i++) {
unsigned int r = getaregval(i);
os_printf("r%02d: 0x%08x=%10d ", i, r, r);
if (i%3 == 2) os_printf("\n");
}
os_printf("\n");
//print_stack(reg->pc, sp, 0x3fffffb0);
print_stack(reg->pc, getaregval(1), 0x3fffffb0);
}
extern void ets_wdt_disable();
extern void ets_wdt_enable();
//We just caught a debug exception and need to handle it. This is called from an assembly
//routine in gdbstub-entry.S
void ATTR_GDBFN gdbstub_handle_debug_exception() {
}
//Non-OS exception handler. Gets called by the Xtensa HAL.
static void ATTR_GDBFN gdb_exception_handler(struct XTensa_exception_frame_s *frame) {
//Save the extra registers the Xtensa HAL doesn't save
gdbstub_save_extra_sfrs_for_exception();
//Copy registers the Xtensa HAL did save to gdbstub_savedRegs
os_memcpy(&gdbstub_savedRegs, frame, 19*4);
//Credits go to Cesanta for this trick. A1 seems to be destroyed, but because it
//has a fixed offset from the address of the passed frame, we can recover it.
//gdbstub_savedRegs.a1=(uint32_t)frame+EXCEPTION_GDB_SP_OFFSET;
gdbstub_savedRegs.a1=(uint32_t)frame;
//gdbstub_savedRegs.reason|=0x80; //mark as an exception reason
ets_wdt_disable();
os_printf("Oooops, exception!\n");
printReason();
ets_wdt_enable();
while(1) ;
}
//The OS-less SDK uses the Xtensa HAL to handle exceptions. We can use those functions to catch any
//fatal exceptions and invoke the debugger when this happens.
static void ATTR_GDBINIT install_exceptions() {
unsigned int i;
int exno[]={EXCCAUSE_ILLEGAL, EXCCAUSE_SYSCALL, EXCCAUSE_INSTR_ERROR, EXCCAUSE_LOAD_STORE_ERROR,
EXCCAUSE_DIVIDE_BY_ZERO, EXCCAUSE_UNALIGNED, EXCCAUSE_INSTR_DATA_ERROR, EXCCAUSE_LOAD_STORE_DATA_ERROR,
EXCCAUSE_INSTR_ADDR_ERROR, EXCCAUSE_LOAD_STORE_ADDR_ERROR, EXCCAUSE_INSTR_PROHIBITED,
EXCCAUSE_LOAD_PROHIBITED, EXCCAUSE_STORE_PROHIBITED};
for (i=0; i<(sizeof(exno)/sizeof(exno[0])); i++) {
_xtos_set_exception_handler(exno[i], gdb_exception_handler);
}
}
//gdbstub initialization routine.
void ATTR_GDBINIT gdbstub_init() {
install_exceptions();
}
extern void gdb_init() __attribute__((weak, alias("gdbstub_init")));
#ifndef GDBSTUB_H
#define GDBSTUB_H
#ifdef __cplusplus
extern "C" {
#endif
void gdbstub_init();
#ifdef __cplusplus
}
#endif
#endif
#! /bin/bash
# This little script allows you to get an approximate stack backtrace when Espruino crashes
# and prints the crash info to the debug console. It looks something like this:
# 37608> Stack:
# 37609> 3ffff9d0: 3ffff9f0 00000000 00000001 40201837
# 37614> 3ffff9e0: 000783b7 00000000 3fff2a48 40212e62
# 37619> 3ffff9f0: 3ffece2c 00000000 00000000 4020193e
# 37624> 3ffffa00: 3fff29c8 3fff1714 3fff29a8 3fff1714
# ...
# 38072> 3fffff90: 00001000 00000002 00000000 00000000
# 38077> 3fffffa0: 000002a1 00000000 00074f20 40100000
# In such a case, run the script (possibly after editing the path below) and at the prompt paste
# the entire stack dump. You will see something like:
# 0x40221f5c: jsfLoadBootCodeFromFlash at /home/src/esp8266/Espruino/src/jswrap_flash.c:527
# 0x40216034: jswInit at /home/src/esp8266/Espruino/gen/jswrapper.c:1740
# 0x402104d5: jsiSoftInit at /home/src/esp8266/Espruino/src/jsinteractive.c:460
# 0x4021060a: jsiSemiInit at /home/src/esp8266/Espruino/src/jsinteractive.c:745
# 0x40210655: jsiInit at /home/src/esp8266/Espruino/src/jsinteractive.c:794
# 0x4022cfc7: initDone at /home/src/esp8266/Espruino/targets/esp8266/user_main.c:256
# 0x40242a59: user_uart_wait_tx_fifo_empty at ??:?
# 0x40242a65: user_uart_wait_tx_fifo_empty at ??:?
# You need to set DEBUG=1 when building in order to see the files and line numbers, if you don't
# you'll just get the function names, which is often sufficient...
# WARNING: this script doesn't parse the stack dump, it simply extracts all memory words that
# contain an address that lies in program memory and prints those. Thus there may be a few extra
# lines if a pointer to code happens to be on the stack. You need to apply a tad of judgement...
export PATH=$PATH:$(pwd)/../esp-open-sdk/xtensa-lx106-elf/bin/
echo "Please paste the stack and hit ctrl-D:"
code=$(sed -e 's/^.*: *//' -e 's/ */\n/g' | egrep '^40[02][0-9a-f]{5}|4010[01234567][0-9a-f]{3}')
re='402[01234567][0-9a-f]{4}'
if [[ "$code" =~ $re ]]; then
elf=espruino_esp8266_user1.elf
else
elf=espruino_esp8266_user2.elf
fi
set -x
xtensa-lx106-elf-addr2line -aipfC -e $elf $code
......@@ -29,6 +29,7 @@ typedef long long int64_t;
#include <jswrap_esp8266.h>
#include <ota.h>
#include <log.h>
#include <gdbstub.h>
#include "ESP8266_board.h"
// --- Constants
......@@ -241,6 +242,8 @@ static void initDone() {
os_printf("> initDone\n");
otaInit(88);
gdbstub_init();
// Discard any junk data in the input as this is a boot.
//uart_rx_discard();
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment