Add the esp-gdbstub to enable debugging (#1506)

* Initial version of gdbstub support

* Add DEBUG argument to Makefile to add more debug data and turn off -Os

* Add the output redirection handling

* Added documentation

* Cleaned up the english a bit

* Make gdboutput actually trap all the output

* Review comments
This commit is contained in:
Philip Gladstone 2016-09-20 21:38:12 -04:00 committed by Johny Mattsson
parent 11436d8dad
commit 04e93b2433
14 changed files with 1517 additions and 4 deletions

View File

@ -24,6 +24,13 @@ SDK_DIR:=$(TOP_DIR)/sdk/esp_iot_sdk_v$(SDK_VER)
CCFLAGS:= -I$(TOP_DIR)/sdk-overrides/include -I$(SDK_DIR)/include CCFLAGS:= -I$(TOP_DIR)/sdk-overrides/include -I$(SDK_DIR)/include
LDFLAGS:= -L$(SDK_DIR)/lib -L$(SDK_DIR)/ld $(LDFLAGS) LDFLAGS:= -L$(SDK_DIR)/lib -L$(SDK_DIR)/ld $(LDFLAGS)
ifdef DEBUG
CCFLAGS += -ggdb -O0
LDFLAGS += -ggdb
else
CCFLAGS += -Os
endif
############################################################# #############################################################
# Select compile # Select compile
# #
@ -38,11 +45,11 @@ ifeq ($(OS),Windows_NT)
CPP = xt-cpp CPP = xt-cpp
OBJCOPY = xt-objcopy OBJCOPY = xt-objcopy
#MAKE = xt-make #MAKE = xt-make
CCFLAGS += -Os --rename-section .text=.irom0.text --rename-section .literal=.irom0.literal CCFLAGS += --rename-section .text=.irom0.text --rename-section .literal=.irom0.literal
else else
# It is gcc, may be cygwin # It is gcc, may be cygwin
# Can we use -fdata-sections? # Can we use -fdata-sections?
CCFLAGS += -Os -ffunction-sections -fno-jump-tables -fdata-sections CCFLAGS += -ffunction-sections -fno-jump-tables -fdata-sections
AR = xtensa-lx106-elf-ar AR = xtensa-lx106-elf-ar
CC = xtensa-lx106-elf-gcc CC = xtensa-lx106-elf-gcc
NM = xtensa-lx106-elf-nm NM = xtensa-lx106-elf-nm
@ -69,7 +76,7 @@ else
else else
ESPPORT = $(COMPORT) ESPPORT = $(COMPORT)
endif endif
CCFLAGS += -Os -ffunction-sections -fno-jump-tables -fdata-sections CCFLAGS += -ffunction-sections -fno-jump-tables -fdata-sections
AR = xtensa-lx106-elf-ar AR = xtensa-lx106-elf-ar
CC = xtensa-lx106-elf-gcc CC = xtensa-lx106-elf-gcc
NM = xtensa-lx106-elf-nm NM = xtensa-lx106-elf-nm

View File

@ -44,6 +44,7 @@ SUBDIRS= \
net \ net \
http \ http \
fatfs \ fatfs \
esp-gdbstub \
websocket websocket
endif # } PDIR endif # } PDIR
@ -90,7 +91,8 @@ COMPONENTS_eagle.app.v6 = \
dhtlib/libdhtlib.a \ dhtlib/libdhtlib.a \
tsl2561/tsl2561lib.a \ tsl2561/tsl2561lib.a \
http/libhttp.a \ http/libhttp.a \
websocket/libwebsocket.a \ websocket/libwebsocket.a \
esp-gdbstub/libgdbstub.a \
net/libnodemcu_net.a \ net/libnodemcu_net.a \
modules/libmodules.a \ modules/libmodules.a \

View File

@ -36,6 +36,8 @@ extern UartDevice UartDev;
static os_timer_t autobaud_timer; static os_timer_t autobaud_timer;
static void (*alt_uart0_tx)(char txchar);
LOCAL void ICACHE_RAM_ATTR LOCAL void ICACHE_RAM_ATTR
uart0_rx_intr_handler(void *para); uart0_rx_intr_handler(void *para);
@ -125,6 +127,11 @@ uart0_alt(uint8 on)
STATUS ICACHE_FLASH_ATTR STATUS ICACHE_FLASH_ATTR
uart_tx_one_char(uint8 uart, uint8 TxChar) uart_tx_one_char(uint8 uart, uint8 TxChar)
{ {
if (uart == 0 && alt_uart0_tx) {
(*alt_uart0_tx)(TxChar);
return OK;
}
while (true) while (true)
{ {
uint32 fifo_cnt = READ_PERI_REG(UART_STATUS(uart)) & (UART_TXFIFO_CNT<<UART_TXFIFO_CNT_S); uint32 fifo_cnt = READ_PERI_REG(UART_STATUS(uart)) & (UART_TXFIFO_CNT<<UART_TXFIFO_CNT_S);
@ -339,3 +346,7 @@ uart_setup(uint8 uart_no)
uart_config(uart_no); uart_config(uart_no);
ETS_UART_INTR_ENABLE(); ETS_UART_INTR_ENABLE();
} }
void ICACHE_FLASH_ATTR uart_set_alt_output_uart0(void (*fn)(char)) {
alt_uart0_tx = fn;
}

24
app/esp-gdbstub/License Normal file
View File

@ -0,0 +1,24 @@
ESPRESSIF MIT License
Copyright (c) 2015 <ESPRESSIF SYSTEMS (SHANGHAI) PTE LTD>
Permission is hereby granted for use on ESPRESSIF SYSTEMS ESP8266 only, in which case, it is free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
乐鑫 MIT 许可证
版权 (c) 2015 <乐鑫信息科技(上海)有限公司>
该许可证授权仅限于乐鑫信息科技 ESP8266 产品的应用开发。在此情况下,该许可证免费授权任何获得该软件及其相关文档(统称为“软件”)的人无限制地经营该软件,包括无限制的使用、复制、修改、合并、出版发行、散布、再授权、及贩售软件及软件副本的权利。被授权人在享受这些权利的同时,需服从下面的条件:
在软件和软件的所有副本中都必须包含以上的版权声明和授权声明。
该软件按本来的样子提供,没有任何明确或暗含的担保,包括但不仅限于关于试销性、适合某一特定用途和非侵权的保证。作者和版权持有人在任何情况下均不就由软件或软件使用引起的以合同形式、民事侵权或其它方式提出的任何索赔、损害或其它责任负责。

45
app/esp-gdbstub/Makefile Normal file
View File

@ -0,0 +1,45 @@
#############################################################
# Required variables for each makefile
# Discard this section from all parent makefiles
# Expected variables (with automatic defaults):
# CSRCS (all "C" files in the dir)
# SUBDIRS (all subdirs with a Makefile)
# GEN_LIBS - list of libs to be generated ()
# GEN_IMAGES - list of images to be generated ()
# COMPONENTS_xxx - a list of libs/objs in the form
# subdir/lib to be extracted and rolled up into
# a generated lib/image xxx.a ()
#
ifndef PDIR
GEN_LIBS = libgdbstub.a
endif
#############################################################
# Configuration i.e. compile options etc.
# Target specific stuff (defines etc.) goes in here!
# Generally values applying to a tree are captured in the
# makefile at its root level - these are then overridden
# for a subtree within the makefile rooted therein
#
#DEFINES +=
#############################################################
# Recursion Magic - Don't touch this!!
#
# Each subtree potentially has an include directory
# corresponding to the common APIs applicable to modules
# rooted at that subtree. Accordingly, the INCLUDE PATH
# of a module can only contain the include directories up
# its parent path, and not its siblings
#
# Required for each makefile to inherit from the parent
#
INCLUDES += -I $(PDIR)include
INCLUDES += -I ./
INCLUDES += -I ../../include/ets
PDIR := ../$(PDIR)
sinclude $(PDIR)Makefile

View File

@ -0,0 +1,64 @@
#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
#ifndef ATTR_GDBFN
#define ATTR_GDBFN ICACHE_RAM_ATTR
#endif
#endif

View File

@ -0,0 +1,406 @@
/******************************************************************************
* 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
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

View File

@ -0,0 +1,25 @@
#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

805
app/esp-gdbstub/gdbstub.c Normal file
View File

@ -0,0 +1,805 @@
/******************************************************************************
* 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 "ets_sys.h"
#include "eagle_soc.h"
#include "c_types.h"
#include "gpio.h"
#include "xtensa/corebits.h"
#include "driver/uart.h"
#include "gdbstub.h"
#include "gdbstub-entry.h"
#include "gdbstub-cfg.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;
//'reason' is abused for both the debug and the exception vector: if bit 7 is set,
//this contains an exception reason, otherwise it contains a debug vector bitmap.
uint32_t reason;
};
struct XTensa_rtos_int_frame_s {
uint32_t exitPtr;
uint32_t pc;
uint32_t ps;
uint32_t a[16];
uint32_t sar;
};
#if GDBSTUB_FREERTOS
/*
Definitions for FreeRTOS. This redefines some os_* functions to use their non-os* counterparts. It
also sets up some function pointers for ROM functions that aren't in the FreeRTOS ld files.
*/
#include <string.h>
#include <stdio.h>
void _xt_isr_attach(int inum, void *fn);
void _xt_isr_unmask(int inum);
void os_install_putc1(void (*p)(char c));
#define os_printf(...) printf(__VA_ARGS__)
#define os_memcpy(a,b,c) memcpy(a,b,c)
typedef void wdtfntype();
static wdtfntype *ets_wdt_disable=(wdtfntype *)0x400030f0;
static wdtfntype *ets_wdt_enable=(wdtfntype *)0x40002fa0;
#else
/*
OS-less SDK defines. Defines some headers for things that aren't in the include files, plus
the xthal stack frame struct.
*/
#include "osapi.h"
#include "user_interface.h"
//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)));
extern void xthal_set_intenable(int);
#endif
#define EXCEPTION_GDB_SP_OFFSET 0x100
//We need some UART register defines.
#define ETS_UART_INUM 5
#define REG_UART_BASE( i ) (0x60000000+(i)*0xf00)
#define UART_STATUS( i ) (REG_UART_BASE( i ) + 0x1C)
#define UART_RXFIFO_CNT 0x000000FF
#define UART_RXFIFO_CNT_S 0
#define UART_TXFIFO_CNT 0x000000FF
#define UART_TXFIFO_CNT_S 16
#define UART_FIFO( i ) (REG_UART_BASE( i ) + 0x0)
//#define UART_INT_ENA(i) (REG_UART_BASE(i) + 0xC)
//#define UART_INT_CLR(i) (REG_UART_BASE(i) + 0x10)
#define UART_RXFIFO_TOUT_INT_ENA (BIT(8))
#define UART_RXFIFO_FULL_INT_ENA (BIT(0))
#define UART_RXFIFO_TOUT_INT_CLR (BIT(8))
#define UART_RXFIFO_FULL_INT_CLR (BIT(0))
//Length of buffer used to reserve GDB commands. Has to be at least able to fit the G command, which
//implies a minimum size of about 190 bytes.
#define PBUFLEN 256
//Length of gdb stdout buffer, for console redirection
#define OBUFLEN 32
//The asm stub saves the Xtensa registers here when a debugging exception happens.
struct XTensa_exception_frame_s gdbstub_savedRegs;
#if GDBSTUB_USE_OWN_STACK
//This is the debugging exception stack.
int exceptionStack[256];
#endif
static unsigned char cmd[PBUFLEN]; //GDB command input buffer
static char chsum; //Running checksum of the output packet
static unsigned char obuf[OBUFLEN]; //GDB stdout buffer
static int obufpos=0; //Current position in the buffer
static int32_t singleStepPs=-1; //Stores ps when single-stepping instruction. -1 when not in use.
//Small function to feed the hardware watchdog. Needed to stop the ESP from resetting
//due to a watchdog timeout while reading a command.
static void ATTR_GDBFN keepWDTalive() {
uint64_t *wdtval=(uint64_t*)0x3ff21048;
uint64_t *wdtovf=(uint64_t*)0x3ff210cc;
int *wdtctl=(int*)0x3ff210c8;
*wdtovf=*wdtval+1600000;
*wdtctl|=(1<<31);
}
//Receive a char from the uart. Uses polling and feeds the watchdog.
static int ATTR_GDBFN gdbRecvChar() {
int i;
while (((READ_PERI_REG(UART_STATUS(0))>>UART_RXFIFO_CNT_S)&UART_RXFIFO_CNT)==0) {
keepWDTalive();
}
i=READ_PERI_REG(UART_FIFO(0));
return i;
}
//Send a char to the uart.
static void ATTR_GDBFN gdbSendChar(char c) {
while (((READ_PERI_REG(UART_STATUS(0))>>UART_TXFIFO_CNT_S)&UART_TXFIFO_CNT)>=126) ;
WRITE_PERI_REG(UART_FIFO(0), c);
}
//Send the start of a packet; reset checksum calculation.
static void ATTR_GDBFN gdbPacketStart() {
chsum=0;
gdbSendChar('$');
}
//Send a char as part of a packet
static void ATTR_GDBFN gdbPacketChar(char c) {
if (c=='#' || c=='$' || c=='}' || c=='*') {
gdbSendChar('}');
gdbSendChar(c^0x20);
chsum+=(c^0x20)+'}';
} else {
gdbSendChar(c);
chsum+=c;
}
}
//Send a string as part of a packet
static void ATTR_GDBFN gdbPacketStr(char *c) {
while (*c!=0) {
gdbPacketChar(*c);
c++;
}
}
//Send a hex val as part of a packet. 'bits'/4 dictates the number of hex chars sent.
static void ATTR_GDBFN gdbPacketHex(int val, int bits) {
char hexChars[]="0123456789abcdef";
int i;
for (i=bits; i>0; i-=4) {
gdbPacketChar(hexChars[(val>>(i-4))&0xf]);
}
}
//Finish sending a packet.
static void ATTR_GDBFN gdbPacketEnd() {
gdbSendChar('#');
gdbPacketHex(chsum, 8);
}
//Error states used by the routines that grab stuff from the incoming gdb packet
#define ST_ENDPACKET -1
#define ST_ERR -2
#define ST_OK -3
#define ST_CONT -4
//Grab a hex value from the gdb packet. Ptr will get positioned on the end
//of the hex string, as far as the routine has read into it. Bits/4 indicates
//the max amount of hex chars it gobbles up. Bits can be -1 to eat up as much
//hex chars as possible.
static long ATTR_GDBFN gdbGetHexVal(unsigned char **ptr, int bits) {
int i;
int no;
unsigned int v=0;
char c;
no=bits/4;
if (bits==-1) no=64;
for (i=0; i<no; i++) {
c=**ptr;
(*ptr)++;
if (c>='0' && c<='9') {
v<<=4;
v|=(c-'0');
} else if (c>='A' && c<='F') {
v<<=4;
v|=(c-'A')+10;
} else if (c>='a' && c<='f') {
v<<=4;
v|=(c-'a')+10;
} else if (c=='#') {
if (bits==-1) {
(*ptr)--;
return v;
}
return ST_ENDPACKET;
} else {
if (bits==-1) {
(*ptr)--;
return v;
}
return ST_ERR;
}
}
return v;
}
//Swap an int into the form gdb wants it
static int ATTR_GDBFN iswap(int i) {
int r;
r=((i>>24)&0xff);
r|=((i>>16)&0xff)<<8;
r|=((i>>8)&0xff)<<16;
r|=((i>>0)&0xff)<<24;
return r;
}
//Read a byte from the ESP8266 memory.
static unsigned char ATTR_GDBFN readbyte(unsigned int p) {
int *i=(int*)(p&(~3));
if (p<0x20000000 || p>=0x60000000) return -1;
return *i>>((p&3)*8);
}
//Write a byte to the ESP8266 memory.
static void ATTR_GDBFN writeByte(unsigned int p, unsigned char d) {
int *i=(int*)(p&(~3));
if (p<0x20000000 || p>=0x60000000) return;
if ((p&3)==0) *i=(*i&0xffffff00)|(d<<0);
if ((p&3)==1) *i=(*i&0xffff00ff)|(d<<8);
if ((p&3)==2) *i=(*i&0xff00ffff)|(d<<16);
if ((p&3)==3) *i=(*i&0x00ffffff)|(d<<24);
}
//Returns 1 if it makes sense to write to addr p
static int ATTR_GDBFN validWrAddr(int p) {
if (p>=0x3ff00000 && p<0x40000000) return 1;
if (p>=0x40100000 && p<0x40140000) return 1;
if (p>=0x60000000 && p<0x60002000) return 1;
return 0;
}
/*
Register file in the format lx106 gdb port expects it.
Inspired by gdb/regformats/reg-xtensa.dat from
https://github.com/jcmvbkbc/crosstool-NG/blob/lx106-g%2B%2B/overlays/xtensa_lx106.tar
As decoded by Cesanta.
*/
struct regfile {
uint32_t a[16];
uint32_t pc;
uint32_t sar;
uint32_t litbase;
uint32_t sr176;
uint32_t sr208;
uint32_t ps;
};
//Send the reason execution is stopped to GDB.
static void ATTR_GDBFN sendReason() {
#if 0
char *reason=""; //default
#endif
//exception-to-signal mapping
char exceptionSignal[]={4,31,11,11,2,6,8,0,6,7,0,0,7,7,7,7};
int i=0;
gdbPacketStart();
gdbPacketChar('T');
if (gdbstub_savedRegs.reason==0xff) {
gdbPacketHex(2, 8); //sigint
} else if (gdbstub_savedRegs.reason&0x80) {
//We stopped because of an exception. Convert exception code to a signal number and send it.
i=gdbstub_savedRegs.reason&0x7f;
if (i<sizeof(exceptionSignal)) gdbPacketHex(exceptionSignal[i], 8); else gdbPacketHex(11, 8);
} else {
//We stopped because of a debugging exception.
gdbPacketHex(5, 8); //sigtrap
//Current Xtensa GDB versions don't seem to request this, so let's leave it off.
#if 0
if (gdbstub_savedRegs.reason&(1<<0)) reason="break";
if (gdbstub_savedRegs.reason&(1<<1)) reason="hwbreak";
if (gdbstub_savedRegs.reason&(1<<2)) reason="watch";
if (gdbstub_savedRegs.reason&(1<<3)) reason="swbreak";
if (gdbstub_savedRegs.reason&(1<<4)) reason="swbreak";
gdbPacketStr(reason);
gdbPacketChar(':');
//ToDo: watch: send address
#endif
}
gdbPacketEnd();
}
//Handle a command as received from GDB.
static int ATTR_GDBFN gdbHandleCommand(unsigned char *cmd, int len) {
//Handle a command
int i, j, k;
unsigned char *data=cmd+1;
if (cmd[0]=='g') { //send all registers to gdb
gdbPacketStart();
gdbPacketHex(iswap(gdbstub_savedRegs.a0), 32);
gdbPacketHex(iswap(gdbstub_savedRegs.a1), 32);
for (i=2; i<16; i++) gdbPacketHex(iswap(gdbstub_savedRegs.a[i-2]), 32);
gdbPacketHex(iswap(gdbstub_savedRegs.pc), 32);
gdbPacketHex(iswap(gdbstub_savedRegs.sar), 32);
gdbPacketHex(iswap(gdbstub_savedRegs.litbase), 32);
gdbPacketHex(iswap(gdbstub_savedRegs.sr176), 32);
gdbPacketHex(0, 32);
gdbPacketHex(iswap(gdbstub_savedRegs.ps), 32);
gdbPacketEnd();
} else if (cmd[0]=='G') { //receive content for all registers from gdb
gdbstub_savedRegs.a0=iswap(gdbGetHexVal(&data, 32));
gdbstub_savedRegs.a1=iswap(gdbGetHexVal(&data, 32));
for (i=2; i<16; i++) gdbstub_savedRegs.a[i-2]=iswap(gdbGetHexVal(&data, 32));
gdbstub_savedRegs.pc=iswap(gdbGetHexVal(&data, 32));
gdbstub_savedRegs.sar=iswap(gdbGetHexVal(&data, 32));
gdbstub_savedRegs.litbase=iswap(gdbGetHexVal(&data, 32));
gdbstub_savedRegs.sr176=iswap(gdbGetHexVal(&data, 32));
gdbGetHexVal(&data, 32);
gdbstub_savedRegs.ps=iswap(gdbGetHexVal(&data, 32));
gdbPacketStart();
gdbPacketStr("OK");
gdbPacketEnd();
} else if (cmd[0]=='m') { //read memory to gdb
i=gdbGetHexVal(&data, -1);
data++;
j=gdbGetHexVal(&data, -1);
gdbPacketStart();
for (k=0; k<j; k++) {
gdbPacketHex(readbyte(i++), 8);
}
gdbPacketEnd();
} else if (cmd[0]=='M') { //write memory from gdb
i=gdbGetHexVal(&data, -1); //addr
data++; //skip ,
j=gdbGetHexVal(&data, -1); //length
data++; //skip :
if (validWrAddr(i) && validWrAddr(i+j)) {
for (k=0; k<j; k++) {
writeByte(i, gdbGetHexVal(&data, 8));
i++;
}
//Make sure caches are up-to-date. Procedure according to Xtensa ISA document, ISYNC inst desc.
asm volatile("ISYNC\nISYNC\n");
gdbPacketStart();
gdbPacketStr("OK");
gdbPacketEnd();
} else {
//Trying to do a software breakpoint on a flash proc, perhaps?
gdbPacketStart();
gdbPacketStr("E01");
gdbPacketEnd();
}
} else if (cmd[0]=='?') { //Reply with stop reason
sendReason();
// } else if (strncmp(cmd, "vCont?", 6)==0) {
// gdbPacketStart();
// gdbPacketStr("vCont;c;s");
// gdbPacketEnd();
} else if (strncmp((char*)cmd, "vCont;c", 7)==0 || cmd[0]=='c') { //continue execution
return ST_CONT;
} else if (strncmp((char*)cmd, "vCont;s", 7)==0 || cmd[0]=='s') { //single-step instruction
//Single-stepping can go wrong if an interrupt is pending, especially when it is e.g. a task switch:
//the ICOUNT register will overflow in the task switch code. That is why we disable interupts when
//doing single-instruction stepping.
singleStepPs=gdbstub_savedRegs.ps;
gdbstub_savedRegs.ps=(gdbstub_savedRegs.ps & ~0xf)|(XCHAL_DEBUGLEVEL-1);
gdbstub_icount_ena_single_step();
return ST_CONT;
} else if (cmd[0]=='q') { //Extended query
if (strncmp((char*)&cmd[1], "Supported", 9)==0) { //Capabilities query
gdbPacketStart();
gdbPacketStr("swbreak+;hwbreak+;PacketSize=255");
gdbPacketEnd();
} else {
//We don't support other queries.
gdbPacketStart();
gdbPacketEnd();
return ST_ERR;
}
} else if (cmd[0]=='Z') { //Set hardware break/watchpoint.
data+=2; //skip 'x,'
i=gdbGetHexVal(&data, -1);
data++; //skip ','
j=gdbGetHexVal(&data, -1);
gdbPacketStart();
if (cmd[1]=='1') { //Set breakpoint
if (gdbstub_set_hw_breakpoint(i, j)) {
gdbPacketStr("OK");
} else {
gdbPacketStr("E01");
}
} else if (cmd[1]=='2' || cmd[1]=='3' || cmd[1]=='4') { //Set watchpoint
int access=0;
int mask=0;
if (cmd[1]=='2') access=2; //write
if (cmd[1]=='3') access=1; //read
if (cmd[1]=='4') access=3; //access
if (j==1) mask=0x3F;
if (j==2) mask=0x3E;
if (j==4) mask=0x3C;
if (j==8) mask=0x38;
if (j==16) mask=0x30;
if (j==32) mask=0x20;
if (j==64) mask=0x00;
if (mask!=0 && gdbstub_set_hw_watchpoint(i,mask, access)) {
gdbPacketStr("OK");
} else {
gdbPacketStr("E01");
}
}
gdbPacketEnd();
} else if (cmd[0]=='z') { //Clear hardware break/watchpoint
data+=2; //skip 'x,'
i=gdbGetHexVal(&data, -1);
data++; //skip ','
j=gdbGetHexVal(&data, -1);
gdbPacketStart();
if (cmd[1]=='1') { //hardware breakpoint
if (gdbstub_del_hw_breakpoint(i)) {
gdbPacketStr("OK");
} else {
gdbPacketStr("E01");
}
} else if (cmd[1]=='2' || cmd[1]=='3' || cmd[1]=='4') { //hardware watchpoint
if (gdbstub_del_hw_watchpoint(i)) {
gdbPacketStr("OK");
} else {
gdbPacketStr("E01");
}
}
gdbPacketEnd();
} else {
//We don't recognize or support whatever GDB just sent us.
gdbPacketStart();
gdbPacketEnd();
return ST_ERR;
}
return ST_OK;
}
//Lower layer: grab a command packet and check the checksum
//Calls gdbHandleCommand on the packet if the checksum is OK
//Returns ST_OK on success, ST_ERR when checksum fails, a
//character if it is received instead of the GDB packet
//start char.
static int ATTR_GDBFN gdbReadCommand() {
unsigned char c;
unsigned char chsum=0, rchsum;
unsigned char sentchs[2];
int p=0;
unsigned char *ptr;
c=gdbRecvChar();
if (c!='$') return c;
while(1) {
c=gdbRecvChar();
if (c=='#') { //end of packet, checksum follows
cmd[p]=0;
break;
}
chsum+=c;
if (c=='$') {
//Wut, restart packet?
chsum=0;
p=0;
continue;
}
if (c=='}') { //escape the next char
c=gdbRecvChar();
chsum+=c;
c^=0x20;
}
cmd[p++]=c;
if (p>=PBUFLEN) return ST_ERR;
}
//A # has been received. Get and check the received chsum.
sentchs[0]=gdbRecvChar();
sentchs[1]=gdbRecvChar();
ptr=&sentchs[0];
rchsum=gdbGetHexVal(&ptr, 8);
// os_printf("c %x r %x\n", chsum, rchsum);
if (rchsum!=chsum) {
gdbSendChar('-');
return ST_ERR;
} else {
gdbSendChar('+');
return gdbHandleCommand(cmd, p);
}
}
//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];
}
//Set the value of one of the A registers
static void ATTR_GDBFN setaregval(int reg, unsigned int val) {
os_printf("%x -> %x\n", val, reg);
if (reg==0) gdbstub_savedRegs.a0=val;
if (reg==1) gdbstub_savedRegs.a1=val;
gdbstub_savedRegs.a[reg-2]=val;
}
//Emulate the l32i/s32i instruction we're stopped at.
static void ATTR_GDBFN emulLdSt() {
unsigned char i0=readbyte(gdbstub_savedRegs.pc);
unsigned char i1=readbyte(gdbstub_savedRegs.pc+1);
unsigned char i2=readbyte(gdbstub_savedRegs.pc+2);
int *p;
if ((i0&0xf)==2 && (i1&0xf0)==0x20) {
//l32i
p=(int*)getaregval(i1&0xf)+(i2*4);
setaregval(i0>>4, *p);
gdbstub_savedRegs.pc+=3;
} else if ((i0&0xf)==0x8) {
//l32i.n
p=(int*)getaregval(i1&0xf)+((i1>>4)*4);
setaregval(i0>>4, *p);
gdbstub_savedRegs.pc+=2;
} else if ((i0&0xf)==2 && (i1&0xf0)==0x60) {
//s32i
p=(int*)getaregval(i1&0xf)+(i2*4);
*p=getaregval(i0>>4);
gdbstub_savedRegs.pc+=3;
} else if ((i0&0xf)==0x9) {
//s32i.n
p=(int*)getaregval(i1&0xf)+((i1>>4)*4);
*p=getaregval(i0>>4);
gdbstub_savedRegs.pc+=2;
} else {
os_printf("GDBSTUB: No l32i/s32i instruction: %x %x %x. Huh?", i2, i1, i0);
}
}
//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() {
ets_wdt_disable();
if (singleStepPs!=-1) {
//We come here after single-stepping an instruction. Interrupts are disabled
//for the single step. Re-enable them here.
gdbstub_savedRegs.ps=(gdbstub_savedRegs.ps&~0xf)|(singleStepPs&0xf);
singleStepPs=-1;
}
sendReason();
xthal_set_intenable(0);
while(gdbReadCommand()!=ST_CONT);
if ((gdbstub_savedRegs.reason&0x84)==0x4) {
//We stopped due to a watchpoint. We can't re-execute the current instruction
//because it will happily re-trigger the same watchpoint, so we emulate it
//while we're still in debugger space.
emulLdSt();
} else if ((gdbstub_savedRegs.reason&0x88)==0x8) {
//We stopped due to a BREAK instruction. Skip over it.
//Check the instruction first; gdb may have replaced it with the original instruction
//if it's one of the breakpoints it set.
if (readbyte(gdbstub_savedRegs.pc+2)==0 &&
(readbyte(gdbstub_savedRegs.pc+1)&0xf0)==0x40 &&
(readbyte(gdbstub_savedRegs.pc)&0x0f)==0x00) {
gdbstub_savedRegs.pc+=3;
}
} else if ((gdbstub_savedRegs.reason&0x90)==0x10) {
//We stopped due to a BREAK.N instruction. Skip over it, after making sure the instruction
//actually is a BREAK.N
if ((readbyte(gdbstub_savedRegs.pc+1)&0xf0)==0xf0 &&
readbyte(gdbstub_savedRegs.pc)==0x2d) {
gdbstub_savedRegs.pc+=3;
}
}
ets_wdt_enable();
}
#if GDBSTUB_FREERTOS
//Freetos exception. This routine is called by an assembly routine in gdbstub-entry.S
void ATTR_GDBFN gdbstub_handle_user_exception() {
ets_wdt_disable();
gdbstub_savedRegs.reason|=0x80; //mark as an exception reason
sendReason();
while(gdbReadCommand()!=ST_CONT);
ets_wdt_enable();
}
#else
//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.reason|=0x80; //mark as an exception reason
ets_wdt_disable();
sendReason();
xthal_set_intenable(0);
while(gdbReadCommand()!=ST_CONT);
ets_wdt_enable();
//Copy any changed registers back to the frame the Xtensa HAL uses.
os_memcpy(frame, &gdbstub_savedRegs, 19*4);
}
#endif
static void ATTR_GDBFN gdb_flush_output_buffer() {
if (obufpos > 0) {
int i;
gdbPacketStart();
gdbPacketChar('O');
for (i=0; i<obufpos; i++) gdbPacketHex(obuf[i], 8);
gdbPacketEnd();
obufpos=0;
}
}
//Replacement putchar1 routine. Instead of spitting out the character directly, it will buffer up to
//OBUFLEN characters (or up to a \n, whichever comes earlier) and send it out as a gdb stdout packet.
static void ATTR_GDBFN gdb_semihost_putchar1(char c) {
obuf[obufpos++]=c;
if (c=='\n' || obufpos==OBUFLEN) {
gdb_flush_output_buffer();
}
}
#if !GDBSTUB_FREERTOS
//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() {
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], (exception_handler_fn) gdb_exception_handler);
}
}
#else
//FreeRTOS doesn't use the Xtensa HAL for exceptions, but uses its own fatal exception handler.
//We use a small hack to replace that with a jump to our own handler, which then has the task of
//decyphering and re-instating the registers the FreeRTOS code left.
extern void user_fatal_exception_handler();
extern void gdbstub_user_exception_entry();
static void ATTR_GDBINIT install_exceptions() {
//Replace the user_fatal_exception_handler by a jump to our own code
int *ufe=(int*)user_fatal_exception_handler;
//This mess encodes as a relative jump instruction to user_fatal_exception_handler
*ufe=((((int)gdbstub_user_exception_entry-(int)user_fatal_exception_handler)-4)<<6)|6;
}
#endif
#if GDBSTUB_CTRLC_BREAK
#if !GDBSTUB_FREERTOS
static void ATTR_GDBFN uart_hdlr(void *arg, void *frame) {
int doDebug=0, fifolen=0;
//Save the extra registers the Xtensa HAL doesn't save
gdbstub_save_extra_sfrs_for_exception();
fifolen=(READ_PERI_REG(UART_STATUS(0))>>UART_RXFIFO_CNT_S)&UART_RXFIFO_CNT;
while (fifolen!=0) {
if ((READ_PERI_REG(UART_FIFO(0)) & 0xFF)==0x3) doDebug=1; //Check if any of the chars is control-C. Throw away rest.
fifolen--;
}
WRITE_PERI_REG(UART_INT_CLR(0), UART_RXFIFO_FULL_INT_CLR|UART_RXFIFO_TOUT_INT_CLR);
if (doDebug) {
//Copy registers the Xtensa HAL did save to gdbstub_savedRegs
os_memcpy(&gdbstub_savedRegs, frame, 19*4);
gdbstub_savedRegs.a1=(uint32_t)frame+EXCEPTION_GDB_SP_OFFSET;
gdbstub_savedRegs.reason=0xff; //mark as user break reason
ets_wdt_disable();
sendReason();
xthal_set_intenable(0);
while(gdbReadCommand()!=ST_CONT);
ets_wdt_enable();
//Copy any changed registers back to the frame the Xtensa HAL uses.
os_memcpy(frame, &gdbstub_savedRegs, 19*4);
}
}
static void ATTR_GDBINIT install_uart_hdlr() {
ets_isr_attach(ETS_UART_INUM, uart_hdlr, NULL);
SET_PERI_REG_MASK(UART_INT_ENA(0), UART_RXFIFO_FULL_INT_ENA|UART_RXFIFO_TOUT_INT_ENA);
ets_isr_unmask((1<<ETS_UART_INUM)); //enable uart interrupt
}
#else
void ATTR_GDBFN gdbstub_handle_uart_int(struct XTensa_rtos_int_frame_s *frame) {
int doDebug=0, fifolen=0, x;
fifolen=(READ_PERI_REG(UART_STATUS(0))>>UART_RXFIFO_CNT_S)&UART_RXFIFO_CNT;
while (fifolen!=0) {
if ((READ_PERI_REG(UART_FIFO(0)) & 0xFF)==0x3) doDebug=1; //Check if any of the chars is control-C. Throw away rest.
fifolen--;
}
WRITE_PERI_REG(UART_INT_CLR(0), UART_RXFIFO_FULL_INT_CLR|UART_RXFIFO_TOUT_INT_CLR);
if (doDebug) {
//Copy registers the Xtensa HAL did save to gdbstub_savedRegs
gdbstub_savedRegs.pc=frame->pc;
gdbstub_savedRegs.ps=frame->ps;
gdbstub_savedRegs.sar=frame->sar;
gdbstub_savedRegs.a0=frame->a[0];
gdbstub_savedRegs.a1=frame->a[1];
for (x=2; x<16; x++) gdbstub_savedRegs.a[x-2]=frame->a[x];
// gdbstub_savedRegs.a1=(uint32_t)frame+EXCEPTION_GDB_SP_OFFSET;
gdbstub_savedRegs.reason=0xff; //mark as user break reason
// ets_wdt_disable();
sendReason();
while(gdbReadCommand()!=ST_CONT);
// ets_wdt_enable();
//Copy any changed registers back to the frame the Xtensa HAL uses.
frame->pc=gdbstub_savedRegs.pc;
frame->ps=gdbstub_savedRegs.ps;
frame->sar=gdbstub_savedRegs.sar;
frame->a[0]=gdbstub_savedRegs.a0;
frame->a[1]=gdbstub_savedRegs.a1;
for (x=2; x<16; x++) frame->a[x]=gdbstub_savedRegs.a[x-2];
}
}
static void ATTR_GDBINIT install_uart_hdlr() {
_xt_isr_attach(ETS_UART_INUM, gdbstub_uart_entry);
SET_PERI_REG_MASK(UART_INT_ENA(0), UART_RXFIFO_FULL_INT_ENA|UART_RXFIFO_TOUT_INT_ENA);
_xt_isr_unmask((1<<ETS_UART_INUM)); //enable uart interrupt
}
#endif
#endif
void ATTR_GDBINIT gdbstub_redirect_output(int enable) {
if (enable) {
os_install_putc1(gdb_semihost_putchar1);
uart_set_alt_output_uart0(gdb_semihost_putchar1);
} else {
gdb_flush_output_buffer();
os_install_putc1(uart0_putc);
}
}
//gdbstub initialization routine.
void ATTR_GDBINIT gdbstub_init() {
#if GDBSTUB_REDIRECT_CONSOLE_OUTPUT
os_install_putc1(gdb_semihost_putchar1);
#endif
#if GDBSTUB_CTRLC_BREAK
install_uart_hdlr();
#endif
install_exceptions();
gdbstub_init_debug_entry();
#if GDBSTUB_BREAK_ON_INIT
gdbstub_do_break();
#endif
}

15
app/esp-gdbstub/gdbstub.h Normal file
View File

@ -0,0 +1,15 @@
#ifndef GDBSTUB_H
#define GDBSTUB_H
#ifdef __cplusplus
extern "C" {
#endif
void gdbstub_init();
void gdbstub_redirect_output(int enable);
#ifdef __cplusplus
}
#endif
#endif

View File

@ -108,5 +108,6 @@ void uart0_putc(const char c);
void uart0_tx_buffer(uint8 *buf, uint16 len); void uart0_tx_buffer(uint8 *buf, uint16 len);
void uart_setup(uint8 uart_no); void uart_setup(uint8 uart_no);
STATUS uart_tx_one_char(uint8 uart, uint8 TxChar); STATUS uart_tx_one_char(uint8 uart, uint8 TxChar);
void uart_set_alt_output_uart0(void (*fn)(char));
#endif #endif

View File

@ -31,6 +31,7 @@
//#define LUA_USE_MODULES_ENCODER //#define LUA_USE_MODULES_ENCODER
//#define LUA_USE_MODULES_ENDUSER_SETUP // USE_DNS in dhcpserver.h needs to be enabled for this module to work. //#define LUA_USE_MODULES_ENDUSER_SETUP // USE_DNS in dhcpserver.h needs to be enabled for this module to work.
#define LUA_USE_MODULES_FILE #define LUA_USE_MODULES_FILE
//#define LUA_USE_MODULES_GDBSTUB
#define LUA_USE_MODULES_GPIO #define LUA_USE_MODULES_GPIO
//#define LUA_USE_MODULES_HMC5883L //#define LUA_USE_MODULES_HMC5883L
//#define LUA_USE_MODULES_HTTP //#define LUA_USE_MODULES_HTTP

48
app/modules/gdbstub.c Normal file
View File

@ -0,0 +1,48 @@
/*
* This module, when enabled with the LUA_USE_MODULES_GDBSTUB define causes
* the gdbstub code to be included and enabled to handle all fatal exceptions.
* This allows you to use the lx106 gdb to catch the exception and then poke
* around. You can't continue from an exception (at least not easily).
*
* This should not be included in production builds as any exception will
* put the nodemcu into a state where it is waiting for serial input and it has
* the watchdog disabled. Good for debugging. Bad for unattended operation!
*
* See the docs for more information.
*
* Philip Gladstone, N1DQ
*/
#include "module.h"
#include "lauxlib.h"
#include "platform.h"
#include "c_types.h"
#include "user_interface.h"
#include "../esp-gdbstub/gdbstub.h"
// gdbstub.brk() just executes a break instruction. Enters gdb
static int lgdbstub_break(lua_State *L) {
asm("break 0,0" ::);
return 0;
}
// gdbstub.gdboutput(1) switches the output to gdb format so that gdb can display it
static int lgdbstub_gdboutput(lua_State *L) {
gdbstub_redirect_output(lua_toboolean(L, 1));
return 0;
}
static int lgdbstub_open(lua_State *L) {
gdbstub_init();
return 0;
}
// Module function map
static const LUA_REG_TYPE gdbstub_map[] = {
{ LSTRKEY( "brk" ), LFUNCVAL( lgdbstub_break ) },
{ LSTRKEY( "gdboutput" ), LFUNCVAL( lgdbstub_gdboutput) },
{ LNILKEY, LNILVAL }
};
NODEMCU_MODULE(GDBSTUB, "gdbstub", gdbstub_map, lgdbstub_open);

View File

@ -0,0 +1,59 @@
# gdbstub Module
| Since | Origin / Contributor | Maintainer | Source |
| :----- | :-------------------- | :---------- | :------ |
| 2016-09-18 | [Philip Gladstone](https://github.com/pjsg) | [Philip Gladstone](https://github.com/pjsg) | [gdbstub.c](../../../app/modules/gdbstub.c)|
This module provides basic source code debugging of the firmware when used in conjunction with a version of gdb built for the lx106. If you
enable this module, then fatal errors (like invalid memory reads) will trap into the gdbstub. This uses UART0 to talk to GDB. If
this happens while the UART0 is connected to a terminal (or some IDE like esplorer) then you will see a string starting with `$T` and a few
more characters after that. This is the signal that a trap has happened, and control should be passed to gdb.
`GDB` can then be started at connected to the nodemcu platform. If this is connected to the host system via a serial port, then the following
(or close variant) ought to work:
```
gdb bin/firmwarefile.bin
target remote /dev/ttyUSB0
```
At this point, you can just poke around and see what happened, but you cannot continue execution.
In order to do interactive debugging, add a call to `gdbstub.brk()` in your lua code. This will trigger a break instruction and
will trap into gdb as above. However, continuation is supported from a break instruction and so you can single step, set breakpoints, etc.
Note that the lx106 processor as configured by Espressif only supports a single hardware breakpoint -- so this means that you
can only put a single breakpoint in flash code. You can single step as much as you like.
## gdbstub.brk()
Enters gdb by executing a `break 0,0` instruction.
#### Syntax
`gdbstub.brk()`
## gdbstub.gdboutput()
Controls whether system output is encapsulated in gdb remote debugging protocol. This turns out not to be as useful as you
would hope -- mostly because you can't send input to the nodemcu board. Also because you really only should make this call
*after* you get gdb running and connected to the nodemcu. The example below first does the break and then switches to
redirect the output. This works (but you are unable to send any more console input).
#### Syntax
`gdbstub.gdboutput(enable)`
#### Parameters
- `enable` If true, then output is wrapped in gdb remote debugging protocol. If false, then it is sent straight to the Uart.
#### Example
function entergdb()
gdbstub.brk()
gdbstub.gdboutput(1)
print("Active")
end
entergdb()
#### Notes
Once you attach gdb to the nodemcu, then any further output from the nodemcu will be discarded (as it does not
match the gdb remote debugging protocol). This may (or may not) be a problem. If you want to run under gdb and see
the output from the nodemcu, then call `gdbstub.gdboutput(1)` and then output will be wrapped in the gdb protocol and display
on the gdb console. You don't want to do this until gdb is attached as each packet requires an explicit ack in order to continue.