From b831b8fb67d7ba8f5b6f5767771c428cf635f8a8 Mon Sep 17 00:00:00 2001 From: mikee47 Date: Wed, 27 Mar 2019 11:13:50 +0000 Subject: [PATCH 01/50] Initial commit Makefile changes: * Remove esp-gdbstub as third-party module * Remove all GDB stuff from `Makefile` - gdbstub is compiled into application, not framework * gdbstub can be used with release or debug builds. * Add support in all makefiles for assembly code (both .s and .S files) * Remove all COM port settings from `Makefile` - application code does all this now * Bring `Makefile-project.mk`inline with `Makefile-rboot.mk` (compiler warnings, etc.) HardwareSerial * Fix so that end() and systemDebugOutput() don't screw up initialisation of other serial port(s) * Put callback handling code into static methods - saves IRAM and simplifies code HardwareTimer * Make HardwareTimer mode selectable (maskable or NMI) user_main.cpp * Don't change serial port baud rates - do that in gdb_init() instead which is compiled into application system/crash_handler.c * Remove stack dumping code and all serial port access - moved into gdb_hooks.cpp system/SerialBuffer.h * Move non-trivial code into .cpp module * Add `getReadData()` and `skipRead()` methods for efficient block reading esp_systemapi.h * Remove uart prototypes - driver must be used instead * Ensure wdt functions are available uart.h, uart.cpp * Add UART2 and provide generic notification callback handling * Add UART_OPT_CALLBACK_RAW option * Save some IRAM by moving uart_get_uart, uart_set_callback, uart_peek_last_char, uart_rx_available and moving uart_detach into flash * Use inline functions in preference to macros * Default debug port should be undefined, not UART0 * Use bit manipulation macros to simplify/clarify code * Disable interrupts in `uart_tx_free()` and `uart_rx_available()` to ensure result is accurate gdbstub, exception handling and stack dumping * appspecific/gdb/ code is always compiled into application, contains system exception handler and stack dumper * gdb/ code is only compiled when ENABLE_GDB=1 * gdbstub revised for readability and checking * uart handling code in separate module (gdbuart.cpp) * Add build option to support both patched and unpatched GDB versions * Extend GDBSTUB_CTRLC_BREAK to prevent accidental breaking * Add gdb_enable() function * Include options for stack dumping, with defaults to provide consistent behaviour (i.e. OFF in release builds, ON in debug builds) * Add debugging code to stub which can be optionally enabled to see realtime behaviour * Implement register read/write (p & P) commands LiveDebug sample * Revise to highlight possible debugging issues with hardware timer, and illustrate other timer types which are 'debug safe' * Add serial read callback to demonstrate UART2 function --- .gitmodules | 4 - Sming/Makefile | 65 +- Sming/Makefile-project.mk | 34 +- Sming/Makefile-rboot.mk | 54 +- Sming/SmingCore/HardwareSerial.cpp | 91 ++- Sming/SmingCore/HardwareSerial.h | 11 +- Sming/SmingCore/HardwareTimer.cpp | 15 +- Sming/SmingCore/HardwareTimer.h | 13 +- Sming/SmingCore/SmingCore.h | 1 + Sming/appinit/user_main.cpp | 18 +- Sming/appspecific/gdb/gdb_hooks.cpp | 193 ++++++ Sming/appspecific/gdb/gdbstub-entry.S | 314 +++++++++ Sming/gdb/GdbPacket.cpp | 126 ++++ Sming/gdb/GdbPacket.h | 107 +++ Sming/gdb/exceptions.h | 61 ++ Sming/gdb/gdb/registers.h | 179 +++++ Sming/gdb/gdb/signals.def | 202 ++++++ Sming/gdb/gdb/signals.h | 58 ++ Sming/gdb/gdbcmds | 28 + Sming/gdb/gdbstub-cfg.h | 166 +++++ Sming/gdb/gdbstub-entry.h | 31 + Sming/gdb/gdbstub-entry.s | 54 -- Sming/gdb/gdbstub-internal.h | 78 +++ Sming/gdb/gdbstub.c | 118 ---- Sming/gdb/gdbstub.cpp | 898 ++++++++++++++++++++++++++ Sming/gdb/gdbstub.h | 39 ++ Sming/gdb/gdbuart.cpp | 372 +++++++++++ Sming/gdb/gdbuart.h | 22 + Sming/gdb/readme.md | 106 +++ Sming/gdb/todo.md | 19 + Sming/gdb/xtensa/xtruntime-frames.h | 160 +++++ Sming/system/SerialBuffer.cpp | 66 ++ Sming/system/crash_handler.c | 101 +-- Sming/system/include/SerialBuffer.h | 102 ++- Sming/system/include/esp_systemapi.h | 18 +- Sming/system/include/espinc/uart.h | 77 ++- Sming/system/include/gdb_hooks.h | 76 +++ Sming/system/uart.cpp | 503 +++++++++------ Sming/third-party/esp-gdbstub | 1 - samples/LiveDebug/app/application.cpp | 48 +- 40 files changed, 3938 insertions(+), 691 deletions(-) create mode 100644 Sming/appspecific/gdb/gdb_hooks.cpp create mode 100644 Sming/appspecific/gdb/gdbstub-entry.S create mode 100644 Sming/gdb/GdbPacket.cpp create mode 100644 Sming/gdb/GdbPacket.h create mode 100644 Sming/gdb/exceptions.h create mode 100644 Sming/gdb/gdb/registers.h create mode 100644 Sming/gdb/gdb/signals.def create mode 100644 Sming/gdb/gdb/signals.h create mode 100644 Sming/gdb/gdbcmds create mode 100644 Sming/gdb/gdbstub-cfg.h create mode 100644 Sming/gdb/gdbstub-entry.h delete mode 100644 Sming/gdb/gdbstub-entry.s create mode 100644 Sming/gdb/gdbstub-internal.h delete mode 100644 Sming/gdb/gdbstub.c create mode 100644 Sming/gdb/gdbstub.cpp create mode 100644 Sming/gdb/gdbstub.h create mode 100644 Sming/gdb/gdbuart.cpp create mode 100644 Sming/gdb/gdbuart.h create mode 100644 Sming/gdb/readme.md create mode 100644 Sming/gdb/todo.md create mode 100644 Sming/gdb/xtensa/xtruntime-frames.h create mode 100644 Sming/system/SerialBuffer.cpp create mode 100644 Sming/system/include/gdb_hooks.h delete mode 160000 Sming/third-party/esp-gdbstub diff --git a/.gitmodules b/.gitmodules index 616a3d0197..e6d068e36b 100644 --- a/.gitmodules +++ b/.gitmodules @@ -6,10 +6,6 @@ path = docs/wiki url = https://github.com/SmingHub/Sming.wiki.git ignore = dirty -[submodule "Sming/third-party/esp-gdbstub"] - path = Sming/third-party/esp-gdbstub - url = https://github.com/espressif/esp-gdbstub.git - ignore = dirty [submodule "Sming/third-party/pwm"] path = Sming/third-party/pwm url = https://github.com/StefanBruens/ESP8266_new_pwm.git diff --git a/Sming/Makefile b/Sming/Makefile index 035ce98eaa..602b617ebc 100644 --- a/Sming/Makefile +++ b/Sming/Makefile @@ -22,16 +22,6 @@ # MacOS / Linux # SMING_HOME = /opt/esp-open-sdk -## COM port parameter is reqruied to flash firmware correctly. -## Windows: -# COM_PORT = COM3 - -# MacOS / Linux: -# COM_PORT = /dev/tty.usbserial - -# Com port speed -COM_SPEED = 115200 - # Git command GIT ?= git @@ -117,7 +107,7 @@ export COMPILE := gcc export PATH := $(ESP_HOME)/xtensa-lx106-elf/bin:$(PATH) XTENSA_TOOLS_ROOT := $(ESP_HOME)/xtensa-lx106-elf/bin -# select which tools to use as compiler, librarian and linker +# select which tools to use as assembler, compiler, librarian and linker AS := $(XTENSA_TOOLS_ROOT)/xtensa-lx106-elf-gcc CC := $(XTENSA_TOOLS_ROOT)/xtensa-lx106-elf-gcc CXX := $(XTENSA_TOOLS_ROOT)/xtensa-lx106-elf-g++ @@ -126,16 +116,6 @@ LD := $(XTENSA_TOOLS_ROOT)/xtensa-lx106-elf-gcc OBJCOPY := $(XTENSA_TOOLS_ROOT)/xtensa-lx106-elf-objcopy OBJDUMP := $(XTENSA_TOOLS_ROOT)/xtensa-lx106-elf-objdump -## COM port parameters -# Default COM port speed (generic) -COM_SPEED ?= 115200 - -# Default COM port speed (used for flashing) -COM_SPEED_ESPTOOL ?= $(COM_SPEED) - -# Default COM port speed (used in code) -COM_SPEED_SERIAL ?= $(COM_SPEED) - ### Debug output parameters # By default `debugf` does not print file name and line number. If you want this enabled set the directive below to 1 DEBUG_PRINT_FILENAME_AND_LINE ?= 0 @@ -220,15 +200,6 @@ ifneq (,$(findstring third-party/ESP8266_NONOS_SDK, $(SDK_BASE))) SDK_INTERNAL = 1 endif -# => esp-gdbstub -ifeq ($(ENABLE_GDB), 1) - THIRD_PARTY_DATA += third-party/esp-gdbstub/Makefile - MODULES += third-party/esp-gdbstub - EXTRA_INCDIR += third-party/esp-gdbstub -else ifneq ($(SMING_RELEASE),1) - MODULES += gdb -endif - # => umm_malloc (custom heap allocation) ifeq ($(ENABLE_CUSTOM_HEAP), 1) THIRD_PARTY_DATA += third-party/umm_malloc/Makefile @@ -284,16 +255,21 @@ MFORCE32 := $(shell $(CC) --help=target | grep mforce-l32) CFLAGS_COMMON = -Wl,-EL -nostdlib -mlongcalls -mtext-section-literals -finline-functions -fdata-sections -ffunction-sections # compiler flags using during compilation of source files. Add '-pg' for debugging CFLAGS += -Wall -Wundef -Wpointer-arith -Wno-comment $(CFLAGS_COMMON) \ - -D__ets__ -DICACHE_FLASH -DUSE_OPTIMIZE_PRINTF -DARDUINO=106 -DCOM_SPEED_SERIAL=$(COM_SPEED_SERIAL) -DENABLE_CMD_EXECUTOR=$(ENABLE_CMD_EXECUTOR) -DESP8266=1 -DSMING_INCLUDED=1 + -D__ets__ -DICACHE_FLASH -DUSE_OPTIMIZE_PRINTF -DARDUINO=106 -DENABLE_CMD_EXECUTOR=$(ENABLE_CMD_EXECUTOR) -DESP8266=1 -DSMING_INCLUDED=1 ifneq ($(STRICT),1) CFLAGS += -Werror -Wno-sign-compare -Wno-parentheses -Wno-unused-variable -Wno-unused-but-set-variable -Wno-strict-aliasing -Wno-return-type -Wno-maybe-uninitialized endif + +ifeq ($(ENABLE_GDB), 1) + CFLAGS += -ggdb -DENABLE_GDB=1 +endif + ifeq ($(SMING_RELEASE),1) # See: https://gcc.gnu.org/onlinedocs/gcc/Optimize-Options.html # for full list of optimization options CFLAGS += -Os -DSMING_RELEASE=1 -DLWIP_NOASSERT else ifeq ($(ENABLE_GDB), 1) - CFLAGS += -Og -ggdb -DGDBSTUB_FREERTOS=0 -DENABLE_GDB=1 -DGDBSTUB_CTRLC_BREAK=0 + CFLAGS += -Og else CFLAGS += -Os -g endif @@ -319,16 +295,8 @@ ifneq ($(STRICT),1) CXXFLAGS += -Wno-reorder endif -# linker flags used to generate the main object file -LDFLAGS = -nostdlib -u call_user_start -Wl,-static -Wl,--gc-sections -Wl,-wrap,system_restart_local - -# linker script used for the above linkier step -LD_PATH = compiler/ld/ -LD_SCRIPT = $(LD_PATH)eagle.app.v6.cpp.ld - # various paths from the SDK used in this project SDK_LIBDIR = lib -SDK_LDDIR = ld SDK_INCDIR = include include/json # SSL support using axTLS @@ -359,15 +327,11 @@ SDK_LIBDIR := $(addprefix $(SDK_BASE)/,$(SDK_LIBDIR)) SDK_INCDIR := $(addprefix -I$(SDK_BASE)/,$(SDK_INCDIR)) AS_SRC := $(foreach sdir,$(SRC_DIR),$(wildcard $(sdir)/*.s)) -ifeq ($(ENABLE_GDB), 1) - AS_SRC += $(foreach sdir,$(SRC_DIR),$(wildcard $(sdir)/*.S)) -endif +AS_SRC += $(foreach sdir,$(SRC_DIR),$(wildcard $(sdir)/*.S)) C_SRC := $(foreach sdir,$(SRC_DIR),$(wildcard $(sdir)/*.c)) CXX_SRC := $(foreach sdir,$(SRC_DIR),$(wildcard $(sdir)/*.cpp)) AS_OBJ := $(patsubst %.s,$(BUILD_BASE)/%.o,$(AS_SRC)) -ifeq ($(ENABLE_GDB), 1) - AS_OBJ += $(patsubst %.S,$(BUILD_BASE)/%.o,$(AS_SRC)) -endif +AS_OBJ += $(patsubst %.S,$(BUILD_BASE)/%.o,$(AS_SRC)) C_OBJ := $(patsubst %.c,$(BUILD_BASE)/%.o,$(C_SRC)) CXX_OBJ := $(patsubst %.cpp,$(BUILD_BASE)/%.o,$(CXX_SRC)) OBJ := $(AS_OBJ) $(C_OBJ) $(CXX_OBJ) @@ -375,9 +339,6 @@ LIBS := $(addprefix -l,$(LIBS)) APP_AR := $(addprefix $(BUILD_BASE)/,$(TARGET)_app.a) TARGET_OUT := $(addprefix $(BUILD_BASE)/,$(TARGET).out) -#LD_SCRIPT := $(addprefix -T$(SDK_BASE)/$(SDK_LDDIR)/,$(LD_SCRIPT)) -LD_SCRIPT := $(addprefix -T,$(LD_SCRIPT)) - INCDIR := $(addprefix -I,$(SRC_DIR)) EXTRA_INCDIR := $(addprefix -I,$(EXTRA_INCDIR)) MODULE_INCDIR := $(addsuffix /include,$(INCDIR)) @@ -399,19 +360,15 @@ endif vpath %.c $(SRC_DIR) vpath %.cpp $(SRC_DIR) vpath %.s $(SRC_DIR) -ifeq ($(ENABLE_GDB), 1) - vpath %.S $(SRC_DIR) -endif +vpath %.S $(SRC_DIR) define compile-objects $1/%.o: %.s $(vecho) "AS $$<" $(Q) $(AS) $(INCDIR) $(MODULE_INCDIR) $(EXTRA_INCDIR) $(SDK_INCDIR) $(CFLAGS) -c $$< -o $$@ -ifeq ($(ENABLE_GDB), 1) $1/%.o: %.S $(vecho) "AS $$<" $(Q) $(AS) $(INCDIR) $(MODULE_INCDIR) $(EXTRA_INCDIR) $(SDK_INCDIR) $(CFLAGS) -c $$< -o $$@ -endif $1/%.o: %.c $1/%.c.d $(vecho) "CC $$<" $(Q) $(CC) $(INCDIR) $(MODULE_INCDIR) $(EXTRA_INCDIR) $(SDK_INCDIR) $(CFLAGS) -c $$< -o $$@ diff --git a/Sming/Makefile-project.mk b/Sming/Makefile-project.mk index 48f1ea0731..667a6b3223 100644 --- a/Sming/Makefile-project.mk +++ b/Sming/Makefile-project.mk @@ -206,6 +206,7 @@ endif # define your custom directories in the project's own Makefile before including this one MODULES ?= app # default to app if not set by user EXTRA_INCDIR ?= include # default to include if not set by user +MODULES += $(SMING_HOME)/appspecific/gdb ENABLE_CUSTOM_LWIP ?= 1 LWIP_INCDIR = $(SMING_HOME)/system/esp-lwip/lwip/include @@ -261,23 +262,32 @@ ifeq ($(ENABLE_WPS),1) endif # compiler flags using during compilation of source files -CFLAGS = -Wpointer-arith -Wundef -Werror -Wl,-EL -nostdlib -mlongcalls -mtext-section-literals -finline-functions -fdata-sections -ffunction-sections \ +CFLAGS = -Wall -Wundef -Wpointer-arith -Wno-comment -Wl,-EL -nostdlib -mlongcalls -mtext-section-literals -finline-functions -fdata-sections -ffunction-sections \ -D__ets__ -DICACHE_FLASH -DARDUINO=106 -DCOM_SPEED_SERIAL=$(COM_SPEED_SERIAL) $(USER_CFLAGS) -DENABLE_CMD_EXECUTOR=$(ENABLE_CMD_EXECUTOR) -DSMING_INCLUDED=1 +ifneq ($(STRICT),1) +CFLAGS += -Werror -Wno-sign-compare -Wno-parentheses -Wno-unused-variable -Wno-unused-but-set-variable -Wno-strict-aliasing -Wno-return-type -Wno-maybe-uninitialized +endif + # => SDK ifneq (,$(findstring third-party/ESP8266_NONOS_SDK, $(SDK_BASE))) CFLAGS += -DSDK_INTERNAL endif + +ifeq ($(ENABLE_GDB), 1) + CFLAGS += -ggdb -DENABLE_GDB=1 + MODULES += $(SMING_HOME)/gdb +endif + ifeq ($(SMING_RELEASE),1) # See: https://gcc.gnu.org/onlinedocs/gcc/Optimize-Options.html # for full list of optimization options CFLAGS += -Os -DSMING_RELEASE=1 else ifeq ($(ENABLE_GDB), 1) - CFLAGS += -Og -ggdb -DGDBSTUB_FREERTOS=0 -DENABLE_GDB=1 -DGDBSTUB_CTRLC_BREAK=0 - MODULES += $(THIRD_PARTY_DIR)/esp-gdbstub - EXTRA_INCDIR += $(THIRD_PARTY_DIR)/esp-gdbstub + CFLAGS += -Og else CFLAGS += -Os -g endif + ifeq ($(ENABLE_WPS),1) CFLAGS += -DENABLE_WPS=1 endif @@ -320,7 +330,8 @@ ifeq ($(DISABLE_SPIFFS), 1) endif # linker flags used to generate the main object file -LDFLAGS = -nostdlib -u call_user_start -u custom_crash_callback -Wl,-static -Wl,--gc-sections -Wl,-Map=$(FW_BASE)/firmware.map -Wl,-wrap,system_restart_local +LDFLAGS = -nostdlib -u call_user_start -u custom_crash_callback \ + -Wl,-static -Wl,--gc-sections -Wl,-Map=$(FW_BASE)/firmware.map -Wl,-wrap,system_restart_local # linker script used for the above linkier step LD_PATH = $(SMING_HOME)/compiler/ld @@ -377,7 +388,8 @@ SDK_LIBDIR = lib SDK_LDDIR = ld SDK_INCDIR = include -# select which tools to use as compiler, librarian and linker +# select which tools to use as assembler, compiler, librarian and linker +AS := $(XTENSA_TOOLS_ROOT)/xtensa-lx106-elf-gcc CC := $(XTENSA_TOOLS_ROOT)/xtensa-lx106-elf-gcc CXX := $(XTENSA_TOOLS_ROOT)/xtensa-lx106-elf-g++ AR := $(XTENSA_TOOLS_ROOT)/xtensa-lx106-elf-ar @@ -396,7 +408,11 @@ CXX_SRC := $(foreach sdir,$(SRC_DIR),$(wildcard $(sdir)/*.cpp)) C_OBJ := $(patsubst %.c,$(BUILD_BASE)/%.o,$(C_SRC)) CXX_OBJ := $(patsubst %.cpp,$(BUILD_BASE)/%.o,$(CXX_SRC)) + +AS_SRC := $(foreach sdir,$(SRC_DIR),$(wildcard $(sdir)/*.s)) AS_OBJ := $(patsubst %.s,$(BUILD_BASE)/%.o,$(AS_SRC)) +AS_SRC := $(foreach sdir,$(SRC_DIR),$(wildcard $(sdir)/*.S)) +AS_OBJ += $(patsubst %.S,$(BUILD_BASE)/%.o,$(AS_SRC)) OBJ := $(AS_OBJ) $(C_OBJ) $(CXX_OBJ) @@ -422,6 +438,12 @@ endif define compile-objects +${BUILD_BASE}/$1/%.o: $1/%.s + $(vecho) "AS $$<" + $(Q) $(AS) $(INCDIR) $(MODULE_INCDIR) $(EXTRA_INCDIR) $(SDK_INCDIR) $(CFLAGS) -c $$< -o $$@ +${BUILD_BASE}/$1/%.o: $1/%.S + $(vecho) "AS $$<" + $(Q) $(AS) $(INCDIR) $(MODULE_INCDIR) $(EXTRA_INCDIR) $(SDK_INCDIR) $(CFLAGS) -c $$< -o $$@ ${BUILD_BASE}/$1/%.o: $1/%.c ${BUILD_BASE}/$1/%.c.d $(vecho) "CC $$<" $(Q) $(CC) $(INCDIR) $(MODULE_INCDIR) $(EXTRA_INCDIR) $(SDK_INCDIR) $(CFLAGS) -c $$< -o $$@ diff --git a/Sming/Makefile-rboot.mk b/Sming/Makefile-rboot.mk index 3c4eb5ad4a..11bf270a42 100644 --- a/Sming/Makefile-rboot.mk +++ b/Sming/Makefile-rboot.mk @@ -48,6 +48,8 @@ BLANK_BIN_ADDR = 0x4b000 # filenames and options for generating rBoot rom images with esptool2 RBOOT_E2_SECTS ?= .text .data .rodata RBOOT_E2_USER_ARGS ?= -quiet -bin -boot2 +# GDB path +GDB ?= $(ESP_HOME)/xtensa-lx106-elf/bin/xtensa-lx106-elf-gdb ## COM port parameters # Default COM port speed (generic) @@ -217,6 +219,7 @@ endif MODULES ?= app # default to app if not set by user MODULES += $(THIRD_PARTY_DIR)/rboot/appcode MODULES += $(SMING_HOME)/appspecific/rboot +MODULES += $(SMING_HOME)/appspecific/gdb EXTRA_INCDIR ?= include # default to include if not set by user ENABLE_CUSTOM_LWIP ?= 1 @@ -246,17 +249,22 @@ endif ifneq (,$(findstring third-party/ESP8266_NONOS_SDK, $(SDK_BASE))) CFLAGS += -DSDK_INTERNAL endif + +ifeq ($(ENABLE_GDB), 1) + CFLAGS += -ggdb -DENABLE_GDB=1 + MODULES += $(SMING_HOME)/gdb +endif + ifeq ($(SMING_RELEASE),1) # See: https://gcc.gnu.org/onlinedocs/gcc/Optimize-Options.html # for full list of optimization options CFLAGS += -Os -DSMING_RELEASE=1 else ifeq ($(ENABLE_GDB), 1) - CFLAGS += -Og -ggdb -DGDBSTUB_FREERTOS=0 -DENABLE_GDB=1 -DGDBSTUB_CTRLC_BREAK=0 - MODULES += $(THIRD_PARTY_DIR)/esp-gdbstub - EXTRA_INCDIR += $(THIRD_PARTY_DIR)/esp-gdbstub + CFLAGS += -Og else CFLAGS += -Os -g endif + ifeq ($(ENABLE_WPS),1) CFLAGS += -DENABLE_WPS=1 endif @@ -360,7 +368,8 @@ ifeq ($(DISABLE_SPIFFS), 1) endif # linker flags used to generate the main object file -LDFLAGS = -nostdlib -u call_user_start -u Cache_Read_Enable_New -u spiffs_get_storage_config -u custom_crash_callback -Wl,-static -Wl,--gc-sections -Wl,-Map=$(basename $@).map -Wl,-wrap,system_restart_local +LDFLAGS = -nostdlib -u call_user_start -u Cache_Read_Enable_New -u spiffs_get_storage_config -u custom_crash_callback \ + -Wl,-static -Wl,--gc-sections -Wl,-Map=$(basename $@).map -Wl,-wrap,system_restart_local ifeq ($(SPI_SPEED), 26) flashimageoptions = -ff 26m @@ -413,7 +422,8 @@ SDK_LIBDIR = lib SDK_LDDIR = ld SDK_INCDIR = include -# select which tools to use as compiler, librarian and linker +# select which tools to use as assembler, compiler, librarian and linker +AS := $(XTENSA_TOOLS_ROOT)/xtensa-lx106-elf-gcc CC := $(XTENSA_TOOLS_ROOT)/xtensa-lx106-elf-gcc CXX := $(XTENSA_TOOLS_ROOT)/xtensa-lx106-elf-g++ AR := $(XTENSA_TOOLS_ROOT)/xtensa-lx106-elf-ar @@ -428,11 +438,16 @@ SDK_LIBDIR := $(addprefix $(SDK_BASE)/,$(SDK_LIBDIR)) SDK_INCDIR := $(addprefix -I$(SDK_BASE)/,$(SDK_INCDIR)) C_SRC := $(foreach sdir,$(SRC_DIR),$(wildcard $(sdir)/*.c)) -CXX_SRC := $(foreach sdir,$(SRC_DIR),$(wildcard $(sdir)/*.cpp)) - C_OBJ := $(patsubst %.c,$(BUILD_BASE)/%.o,$(C_SRC)) + +CXX_SRC := $(foreach sdir,$(SRC_DIR),$(wildcard $(sdir)/*.cpp)) CXX_OBJ := $(patsubst %.cpp,$(BUILD_BASE)/%.o,$(CXX_SRC)) + +AS_SRC := $(foreach sdir,$(SRC_DIR),$(wildcard $(sdir)/*.s)) AS_OBJ := $(patsubst %.s,$(BUILD_BASE)/%.o,$(AS_SRC)) +AS_SRC := $(foreach sdir,$(SRC_DIR),$(wildcard $(sdir)/*.S)) +AS_OBJ += $(patsubst %.S,$(BUILD_BASE)/%.o,$(AS_SRC)) + OBJ := $(AS_OBJ) $(C_OBJ) $(CXX_OBJ) @@ -503,6 +518,12 @@ endif define compile-objects +${BUILD_BASE}/$1/%.o: $1/%.s + $(vecho) "AS $$<" + $(Q) $(AS) $(INCDIR) $(MODULE_INCDIR) $(EXTRA_INCDIR) $(SDK_INCDIR) $(CFLAGS) -c $$< -o $$@ +${BUILD_BASE}/$1/%.o: $1/%.S + $(vecho) "AS $$<" + $(Q) $(AS) $(INCDIR) $(MODULE_INCDIR) $(EXTRA_INCDIR) $(SDK_INCDIR) $(CFLAGS) -c $$< -o $$@ ${BUILD_BASE}/$1/%.o: $1/%.c ${BUILD_BASE}/$1/%.c.d $(vecho) "CC $$<" $(Q) $(CC) $(INCDIR) $(MODULE_INCDIR) $(EXTRA_INCDIR) $(SDK_INCDIR) $(CFLAGS) -c $$< -o $$@ @@ -517,7 +538,7 @@ ${BUILD_BASE}/$1/%.cpp.d: $1/%.cpp .PRECIOUS: ${BUILD_BASE}/$1/%.c.d ${BUILD_BASE}/$1/%.cpp.d endef -.PHONY: all checkdirs spiff_update spiff_clean clean +.PHONY: all checkdirs spiff_update spiff_clean clean kill_term terminal gdb all: $(USER_LIBDIR)/lib$(LIBSMING).a checkdirs $(LIBMAIN_DST) $(RBOOT_BIN) $(RBOOT_ROM_0) $(RBOOT_ROM_1) $(SPIFF_BIN_OUT) $(FW_FILE_1) $(FW_FILE_2) @@ -611,10 +632,8 @@ endif flashboot: $(USER_LIBDIR)/lib$(LIBSMING).a $(RBOOT_BIN) $(ESPTOOL) -p $(COM_PORT) -b $(COM_SPEED_ESPTOOL) write_flash $(flashimageoptions) 0x00000 $(RBOOT_BIN) - -flashconfig: - $(vecho) "Killing Terminal to free $(COM_PORT)" - -$(Q) $(KILL_TERM) + +flashconfig: kill_term $(vecho) "Deleting rBoot config sector" $(ESPTOOL) -p $(COM_PORT) -b $(COM_SPEED_ESPTOOL) write_flash $(flashimageoptions) 0x01000 $(SDK_BASE)/bin/blank.bin @@ -628,9 +647,7 @@ else $(ESPTOOL) -p $(COM_PORT) -b $(COM_SPEED_ESPTOOL) write_flash $(flashimageoptions) $(RBOOT_SPIFFS_0) $(SPIFF_BIN_OUT) endif -flash: all - $(vecho) "Killing Terminal to free $(COM_PORT)" - -$(Q) $(KILL_TERM) +flash: all kill_term ifeq ($(DISABLE_SPIFFS), 1) # flashes rboot and first rom $(ESPTOOL) -p $(COM_PORT) -b $(COM_SPEED_ESPTOOL) write_flash $(flashimageoptions) 0x00000 $(RBOOT_BIN) 0x02000 $(RBOOT_ROM_0) @@ -644,11 +661,16 @@ otaserver: all $(vecho) "Starting OTA server for TESTING" $(Q) cd $(FW_BASE) && python -m SimpleHTTPServer $(SERVER_OTA_PORT) -terminal: +kill_term: $(vecho) "Killing Terminal to free $(COM_PORT)" -$(Q) $(KILL_TERM) + +terminal: kill_term $(TERMINAL) +gdb: kill_term + $(GDB) -x $(SMING_HOME)/gdb/gdbcmds -b $(COM_SPEED_SERIAL) -ex "target remote $(COM_PORT)" + flashinit: $(vecho) "Flash init data default and blank data." $(ESPTOOL) -p $(COM_PORT) -b $(COM_SPEED_ESPTOOL) erase_flash diff --git a/Sming/SmingCore/HardwareSerial.cpp b/Sming/SmingCore/HardwareSerial.cpp index 624764daac..3ea354c9af 100644 --- a/Sming/SmingCore/HardwareSerial.cpp +++ b/Sming/SmingCore/HardwareSerial.cpp @@ -44,17 +44,22 @@ void HardwareSerial::begin(uint32_t baud, SerialConfig config, SerialMode mode, .rx_size = rxSize, .tx_size = txSize}; uart = uart_init_ex(cfg); + + updateUartCallback(); } void HardwareSerial::end() { + if(uart == nullptr) { + return; + } + if(uart_get_debug() == uartNr) { uart_set_debug(UART_NO); } uart_uninit(uart); uart = nullptr; - uart_detach(uartNr); } size_t HardwareSerial::setRxBufferSize(size_t size) @@ -91,71 +96,53 @@ void HardwareSerial::systemDebugOutput(bool enabled) } else { uart_set_debug(UART_NO); } - } else { - // don't print debugf() data at all + } else if(uart_get_debug() == uartNr) { + // Disable system debug messages on this interface + uart_set_debug(UART_NO); + // and don't print debugf() data at all m_setPuts(nullptr); - // and disable system debug messages on this interface - if(uart_get_debug() == uartNr) { - uart_set_debug(UART_NO); - } } } +void HardwareSerial::staticOnTransmitComplete(uint32_t param) +{ + auto serial = reinterpret_cast(param); + if(serial->transmitComplete) { + serial->transmitComplete(*serial); + } +} + +void HardwareSerial::staticOnReceive(uint32_t param) +{ + auto serial = reinterpret_cast(param); + auto receivedChar = uart_peek_last_char(serial->uart); + if(serial->HWSDelegate) { + serial->HWSDelegate(*serial, receivedChar, uart_rx_available(serial->uart)); + } +#if ENABLE_CMD_EXECUTOR + if(serial->commandExecutor) { + serial->commandExecutor->executorReceive(receivedChar); + } +#endif +} + void HardwareSerial::callbackHandler(uint32_t status) { // Transmit complete ? - if(status & _BV(UIFE)) { - if(transmitComplete) - System.queueCallback( - [](uint32_t param) { - auto serial = reinterpret_cast(param); - if(serial->transmitComplete) { - serial->transmitComplete(*serial); - } - }, - reinterpret_cast(this)); + if(bitRead(status, UIFE) && transmitComplete) { + System.queueCallback(staticOnTransmitComplete, uint32_t(this)); } // RX FIFO Full or RX FIFO Timeout ? - if((status & (_BV(UIFF) | _BV(UITO))) == 0) { - return; - } - + if((status & (_BV(UIFF) | _BV(UITO))) != 0) { #if ENABLE_CMD_EXECUTOR - if(HWSDelegate || commandExecutor) { + if(HWSDelegate || commandExecutor) { #else - if(HWSDelegate) { + if(HWSDelegate) { #endif - // Pack parameters for callback into a single word - union __packed SerialParam { - struct { - uint16_t charCount; - uint8_t receivedChar; - uint8_t uartNr; - }; - uint32_t param; - }; - - SerialParam serialParam = {{.charCount = static_cast(uart_rx_available(uart)), - .receivedChar = (uint8_t)uart_peek_last_char(uart), - .uartNr = static_cast(uartNr)}}; - - System.queueCallback( - [](uint32_t param) { - SerialParam serialParam = {.param = param}; - auto uart = uart_get_uart(serialParam.uartNr); - auto serial = reinterpret_cast(uart_get_callback_param(uart)); - if(serial->HWSDelegate) { - serial->HWSDelegate(*serial, serialParam.receivedChar, serialParam.charCount); - } -#if ENABLE_CMD_EXECUTOR - if(serial->commandExecutor) { - serial->commandExecutor->executorReceive(serialParam.receivedChar); - } -#endif - }, - serialParam.param); + System.queueCallback(staticOnReceive, uint32_t(this)); + } } } diff --git a/Sming/SmingCore/HardwareSerial.h b/Sming/SmingCore/HardwareSerial.h index e023fbca21..20c90dae72 100644 --- a/Sming/SmingCore/HardwareSerial.h +++ b/Sming/SmingCore/HardwareSerial.h @@ -24,7 +24,7 @@ #define UART_ID_0 0 ///< ID of UART 0 #define UART_ID_1 1 ///< ID of UART 1 -#define NUMBER_UARTS 2 ///< Quantity of UARTs available +#define NUMBER_UARTS UART_COUNT ///< Quantity of UARTs available class HardwareSerial; @@ -110,6 +110,11 @@ class HardwareSerial : public ReadWriteStream uartNr = uartPort; } + int getPort() + { + return uartNr; + } + /** @brief Initialise the serial port * @param baud BAUD rate of the serial port (Default: 9600) */ @@ -277,7 +282,7 @@ class HardwareSerial : public ReadWriteStream /** @brief Flush all pending data to the serial port * @note Not to be confused with uart_flush() which is different. See clear() method. */ - void flush() + void flush() override // Stream { uart_wait_tx_empty(uart); } @@ -429,6 +434,8 @@ class HardwareSerial : public ReadWriteStream */ static void IRAM_ATTR staticCallbackHandler(uart_t* uart, uint32_t status); void IRAM_ATTR callbackHandler(uint32_t status); + static void staticOnTransmitComplete(uint32_t param); + static void staticOnReceive(uint32_t param); /** * @brief Called whenever one of the user callbacks change diff --git a/Sming/SmingCore/HardwareTimer.cpp b/Sming/SmingCore/HardwareTimer.cpp index a422b088a8..633f627aae 100644 --- a/Sming/SmingCore/HardwareTimer.cpp +++ b/Sming/SmingCore/HardwareTimer.cpp @@ -62,11 +62,6 @@ typedef enum { //timer interrupt mode TM_EDGE_INT = 0, //edge interrupt } TIMER_INT_MODE; -typedef enum { - FRC1_SOURCE = 0, - NMI_SOURCE = 1, -} FRC1_TIMER_SOURCE_TYPE; - // Used by ISR static HardwareTimer* isrTimer; @@ -77,11 +72,15 @@ static void IRAM_ATTR hw_timer_isr_cb() } } -HardwareTimer::HardwareTimer() +HardwareTimer::HardwareTimer(HardwareTimerMode mode) { assert(isrTimer == nullptr); isrTimer = this; - ETS_FRC_TIMER1_NMI_INTR_ATTACH(hw_timer_isr_cb); + if(mode == eHWT_Maskable) { + ETS_FRC_TIMER1_INTR_ATTACH(ets_isr_t(hw_timer_isr_cb), nullptr); + } else { + ETS_FRC_TIMER1_NMI_INTR_ATTACH(hw_timer_isr_cb); + } } HardwareTimer::~HardwareTimer() @@ -153,9 +152,7 @@ bool HardwareTimer::setIntervalUs(uint32_t microseconds) void HardwareTimer::setCallback(InterruptCallback interrupt) { - ETS_INTR_LOCK(); callback = interrupt; - ETS_INTR_UNLOCK(); if(!interrupt) { stop(); diff --git a/Sming/SmingCore/HardwareTimer.h b/Sming/SmingCore/HardwareTimer.h index 8ecf3e0108..0f715b57ea 100644 --- a/Sming/SmingCore/HardwareTimer.h +++ b/Sming/SmingCore/HardwareTimer.h @@ -24,6 +24,12 @@ #define MAX_HW_TIMER_INTERVAL_US 0x7fffff ///< Maximum timer interval in microseconds #define MIN_HW_TIMER_INTERVAL_US 0x32 ///< Minimum hardware interval in microseconds +// Hardware Timer operating mode +enum HardwareTimerMode { + eHWT_Maskable, + eHWT_NonMaskable, +}; + /** @brief Convert microseconds into timer ticks. * @note Replaces the previous US_TO_RTC_TIMER_TICKS macro to guarantee we use the correct timer prescale value. */ @@ -39,8 +45,13 @@ class HardwareTimer { public: /** @brief Hardware timer + * @param mode + * @note NMI has highest interrupt priority on system and can therefore occur within + * any other interrupt service routine. Similarly, the NMI service routine cannot + * itself be interrupted. This provides the most stable and reliable timing possible, + * and is therefore the default behaviour. */ - HardwareTimer(); + HardwareTimer(HardwareTimerMode mode = eHWT_NonMaskable); ~HardwareTimer(); /** @brief Initialise hardware timer diff --git a/Sming/SmingCore/SmingCore.h b/Sming/SmingCore/SmingCore.h index fc5c474080..9c89eecdcb 100644 --- a/Sming/SmingCore/SmingCore.h +++ b/Sming/SmingCore/SmingCore.h @@ -15,6 +15,7 @@ #include +#include "gdb_hooks.h" #include "WiringFrameworkIncludes.h" #include "Delegate.h" diff --git a/Sming/appinit/user_main.cpp b/Sming/appinit/user_main.cpp index 3f6be475b3..39767f50a1 100644 --- a/Sming/appinit/user_main.cpp +++ b/Sming/appinit/user_main.cpp @@ -4,17 +4,14 @@ * http://github.com/anakod/Sming * All files of the Sming Core are provided under the LGPL v3 license. * + * user_main.cpp + * */ #include #include "Platform/System.h" #include "espinc/uart.h" - -#ifndef SMING_RELEASE -extern "C" { - void gdbstub_init(); -} -#endif +#include "gdb_hooks.h" extern void init(); @@ -25,9 +22,6 @@ extern "C" void __attribute__((weak)) user_init(void) // Initialise UARTs to a known state uart_detach_all(); - for(unsigned i = 0; i < UART_COUNT; ++i) { - uart_set_baudrate_reg(i, SERIAL_BAUD_RATE); - } /* Note: System is a static class so it's safe to call initialize() before cpp_core_initialize() * We need to do this so that class constructors can use the task queue or onReady() @@ -38,16 +32,16 @@ extern "C" void __attribute__((weak)) user_init(void) #ifdef SMING_RELEASE // disable all debug output for release builds uart_set_debug(UART_NO); -#else - gdbstub_init(); #endif + + gdb_init(); + init(); // User code init } // For compatibility with SDK v1.1 extern "C" void __attribute__((weak)) user_rf_pre_init(void) { - uart_set_baudrate_reg(UART0, SERIAL_BAUD_RATE); // RTC startup fix, author pvvx volatile uint32 * ptr_reg_rtc_ram = (volatile uint32 *)0x60001000; if((ptr_reg_rtc_ram[24] >> 16) > 4) { diff --git a/Sming/appspecific/gdb/gdb_hooks.cpp b/Sming/appspecific/gdb/gdb_hooks.cpp new file mode 100644 index 0000000000..5eadf08463 --- /dev/null +++ b/Sming/appspecific/gdb/gdb_hooks.cpp @@ -0,0 +1,193 @@ +/**** + * Sming Framework Project - Open Source framework for high efficiency native ESP8266 development. + * Created 2015 by Skurydin Alexey + * http://github.com/SmingHub/Sming + * All files of the Sming Core are provided under the LGPL v3 license. + * + * gdb_hooks.cpp + * + ****/ + +#include "gdb_hooks.h" +#include "gdb/gdbstub.h" +#include "gdb/gdbstub-entry.h" +#include "gdb/exceptions.h" + +extern "C" { + +void Cache_Read_Enable_New(); + +typedef void (*xtos_handler_func)(UserFrame* frame); +void _xtos_set_exception_handler(int, xtos_handler_func); +} + +// List of GDB signals used for exceptions +const uint8_t gdb_exception_signals[] GDB_PROGMEM = { +#define XX(ex, sig, desc) sig, + SYSTEM_EXCEPTION_MAP(XX) +#undef XX +}; + +// List of exception names +#define XX(ex, sig, desc) static DEFINE_PSTR(exception_str_##ex, #ex) +SYSTEM_EXCEPTION_MAP(XX) +#undef XX +static PGM_P const exceptionNames[] PROGMEM = { +#define XX(ex, sig, desc) exception_str_##ex, + SYSTEM_EXCEPTION_MAP(XX) +#undef XX +}; + +// The asm stub saves the Xtensa registers here when an exception is raised +GdbstubSavedRegisters gdbstub_savedRegs; + +void debug_print_stack(uint32_t start, uint32_t end) +{ + m_puts(_F("\nStack dump:\n")); + PSTR_ARRAY(instructions, "To decode the stack dump call from command line:\n" + " python $SMING_HOME/../tools/decode-stacktrace.py out/build/app.out\n" + "and copy & paste the text enclosed in '===='.\n"); + PSTR_ARRAY(separatorLine, "\n================================================================\n"); + m_puts(instructions); + m_puts(separatorLine); + for(uint32_t addr = start; addr < end; addr += 0x10) { + uint32_t* values = (uint32_t*)addr; + // rough indicator: stack frames usually have SP saved as the second word + bool looksLikeStackFrame = (values[2] == addr + 0x10); + + m_printf(_F("%08x: %08x %08x %08x %08x %c\n"), addr, values[0], values[1], values[2], values[3], + (looksLikeStackFrame) ? '<' : ' '); + } + m_puts(separatorLine); + m_puts(instructions); +} + +void debug_crash_callback(const rst_info* rst_info, uint32_t stack, uint32_t stack_end) +{ +#if ENABLE_CRASH_DUMP + switch(rst_info->reason) { + case REASON_EXCEPTION_RST: + m_printf(_F("\n\n***** Exception Reset (%u):\n" + "epc1=0x%08x epc2=0x%08x epc3=0x%08x excvaddr=0x%08x depc=0x%08x\n"), + rst_info->exccause, rst_info->epc1, rst_info->epc2, rst_info->epc3, rst_info->excvaddr, + rst_info->depc); + break; + case REASON_SOFT_WDT_RST: + m_puts(_F("\n\n***** Software Watchdog Reset\n")); + break; + default: + // + ; + } + + debug_print_stack(stack, stack_end); +#endif +} + +#if ENABLE_EXCEPTION_DUMP + +void dumpExceptionInfo() +{ + ets_wdt_disable(); + auto& reg = gdbstub_savedRegs; + + m_printf(_F("\n\n***** Fatal exception %u"), reg.cause); + if(reg.cause <= EXCCAUSE_MAX) { + m_putc(' '); + m_putc('('); + char name[32]; + memcpy_P(name, exceptionNames[reg.cause], sizeof(name)); + name[sizeof(name) - 1] = '\0'; + m_puts(name); + m_putc(')'); + } + m_putc('\n'); + + // EXCVADDR isn't set for all exceptions, so zero it out rather than show potentially misleading information + if(reg.cause < EXCCAUSE_UNALIGNED && reg.cause != EXCCAUSE_IFETCHERROR && reg.cause != EXCCAUSE_LOAD_STORE_ERROR) { + reg.excvaddr = 0; + } + + m_printf(_F("pc=0x%08x sp=0x%08x excvaddr=0x%08x\n"), reg.pc, reg.a[1], reg.excvaddr); + m_printf(_F("ps=0x%08x sar=0x%08x vpri=0x%08x\n"), reg.ps, reg.sar, reg.vpri); + for(int i = 0; i < 16; i++) { + uint32_t r = reg.a[i]; + m_printf(_F("r%02u: 0x%08x=%10d "), i, r, r); + if(i % 3 == 2) { + m_putc('\n'); + } + } + m_putc('\n'); + debug_print_stack(reg.a[1], 0x3fffffb0); + ets_wdt_enable(); +} +#endif + +#ifdef HOOK_SYSTEM_EXCEPTIONS + +// Main exception handler code +static void __attribute__((noinline)) gdbstub_exception_handler_flash(UserFrame* frame) +{ +#if ENABLE_EXCEPTION_DUMP + dumpExceptionInfo(); +#endif + +#if defined(ENABLE_GDB) && GDBSTUB_BREAK_ON_EXCEPTION + gdbstub_handle_exception(frame); + return; +#endif + + // Wait for watchdog to reset system + while(true) + ; +} + +// Non-OS exception handler. Gets called by the Xtensa HAL. +static void IRAM_ATTR gdbstub_exception_handler(UserFrame* frame) +{ + // Save the extra registers the Xtensa HAL doesn't save + gdbstub_save_extra_sfrs_for_exception(); + Cache_Read_Enable_New(); + gdbstub_exception_handler_flash(frame); +} + +// 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 installExceptionHandler() +{ + for(unsigned ex = 0; ex <= EXCCAUSE_MAX; ++ex) { + unsigned signal = pgm_read_byte(&gdb_exception_signals[ex]); + if(signal != 0) { + _xtos_set_exception_handler(ex, gdbstub_exception_handler); + } + } +} + +#endif // HOOK_SYSTEM_EXCEPTIONS + +void ATTR_GDBINIT gdb_init() +{ +#ifdef HOOK_SYSTEM_EXCEPTIONS + installExceptionHandler(); +#endif + +#ifdef ENABLE_GDB + gdbstub_init(); +#endif +} + +#ifndef ENABLE_GDB + +extern "C" { + +static bool IRAM_ATTR __gdb_no_op() +{ + return false; +} + +void gdb_enable(bool state) __attribute__((weak, alias("__gdb_no_op"))); +void gdb_do_break(void) __attribute__((weak, alias("__gdb_no_op"))); +bool gdb_present(void) __attribute__((weak, alias("__gdb_no_op"))); +}; + +#endif diff --git a/Sming/appspecific/gdb/gdbstub-entry.S b/Sming/appspecific/gdb/gdbstub-entry.S new file mode 100644 index 0000000000..f760582e33 --- /dev/null +++ b/Sming/appspecific/gdb/gdbstub-entry.S @@ -0,0 +1,314 @@ +/****************************************************************************** + * Copyright 2015 Espressif Systems + * + * Description: Assembly routines for the gdbstub + * + * License: ESPRESSIF MIT License + *******************************************************************************/ + +#include "gdb/gdbstub-internal.h" + +#include +#include +#include + +#define DEBUG_PC (EPC + XCHAL_DEBUGLEVEL) +#define DEBUG_EXCSAVE (EXCSAVE + XCHAL_DEBUGLEVEL) +#define DEBUG_PS (EPS + XCHAL_DEBUGLEVEL) + +.global gdbstub_savedRegs // GdbstubSavedRegisters + +#if GDBSTUB_USE_OWN_STACK +.global gdbstub_exceptionStack +#endif + + ASATTR_GDBFN +.literal_position + + ASATTR_GDBINIT +.literal_position + + ASATTR_GDBFN + .align 4 + +#ifdef ENABLE_GDB + +/* +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. +*/ + .type gdbstub_debug_exception_entry, @function +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, GDBSR_A(0) + s32i a1, a2, GDBSR_A(1) + rsr a0, DEBUG_PS + s32i a0, a2, GDBSR_ps + rsr a0, DEBUG_EXCSAVE //was R2 + s32i a0, a2, GDBSR_A(2) + s32i a3, a2, GDBSR_A(3) + s32i a4, a2, GDBSR_A(4) + s32i a5, a2, GDBSR_A(5) + s32i a6, a2, GDBSR_A(6) + s32i a7, a2, GDBSR_A(7) + s32i a8, a2, GDBSR_A(8) + s32i a9, a2, GDBSR_A(9) + s32i a10, a2, GDBSR_A(10) + s32i a11, a2, GDBSR_A(11) + s32i a12, a2, GDBSR_A(12) + s32i a13, a2, GDBSR_A(13) + s32i a14, a2, GDBSR_A(14) + s32i a15, a2, GDBSR_A(15) + rsr a0, SAR + s32i a0, a2, GDBSR_sar + rsr a0, LITBASE + s32i a0, a2, GDBSR_litbase + rsr a0, 176 + s32i a0, a2, GDBSR_sr176 + rsr a0, 208 + s32i a0, a2, GDBSR_sr208 + rsr a0, DEBUGCAUSE + s32i a0, a2, GDBSR_cause + rsr a4, DEBUG_PC + s32i a4, a2, GDBSR_pc + +#if GDBSTUB_USE_OWN_STACK + //Move to our own stack + movi a1, gdbstub_exceptionStack + (GDBSTUB_STACK_SIZE - 1) * 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, GDBSR_pc + wsr a0, DEBUG_PC +// l32i a0, a2, 0x58 +// wsr a0, 208 + l32i a0, a2, GDBSR_sr176 + //wsr a0, 176 //Some versions of gcc do not understand this... + .byte 0x00, 176, 0x13 //so we hand-assemble the instruction. + l32i a0, a2, GDBSR_litbase + wsr a0, LITBASE + l32i a0, a2, GDBSR_sar + wsr a0, SAR + l32i a15, a2, GDBSR_A(15) + l32i a14, a2, GDBSR_A(14) + l32i a13, a2, GDBSR_A(13) + l32i a12, a2, GDBSR_A(12) + l32i a11, a2, GDBSR_A(11) + l32i a10, a2, GDBSR_A(10) + l32i a9, a2, GDBSR_A(8) + l32i a8, a2, GDBSR_A(8) + l32i a7, a2, GDBSR_A(7) + l32i a6, a2, GDBSR_A(6) + l32i a5, a2, GDBSR_A(5) + l32i a4, a2, GDBSR_A(4) + l32i a3, a2, GDBSR_A(3) + l32i a0, a2, GDBSR_A(2) + wsr a0, DEBUG_EXCSAVE //was R2 + l32i a0, a2, GDBSR_ps + wsr a0, DEBUG_PS + l32i a1, a2, GDBSR_A(1) + l32i a0, a2, GDBSR_A(0) + + //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 + .size gdbstub_debug_exception_entry, .-gdbstub_debug_exception_entry + + +#endif // ENABLE_GDB + + +#ifdef HOOK_SYSTEM_EXCEPTIONS + + .global gdbstub_save_extra_sfrs_for_exception + .type gdbstub_save_extra_sfrs_for_exception, @function + ASATTR_GDBFN + .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, GDBSR_litbase + rsr a3, 176 + s32i a3, a2, GDBSR_sr176 + rsr a3, 208 + s32i a3, a2, GDBSR_sr208 + rsr a3, EXCCAUSE + s32i a3, a2, GDBSR_cause + rsr a3, EXCVADDR + s32i a3, a2, GDBSR_excvaddr + ret + .size gdbstub_save_extra_sfrs_for_exception, .-gdbstub_save_extra_sfrs_for_exception + +#endif // HOOK_SYSTEM_EXCEPTIONS + +#ifdef ENABLE_GDB + + .global gdbstub_init_debug_entry + .global _DebugExceptionVector + .type gdbstub_init_debug_entry, @function + ASATTR_GDBINIT + .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 + .size gdbstub_init_debug_entry, .-gdbstub_init_debug_entry + + +//Set up ICOUNT register to step one single instruction + .global gdbstub_icount_ena_single_step + .type gdbstub_icount_ena_single_step, @function + ASATTR_GDBFN + .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 + .size gdbstub_icount_ena_single_step, .-gdbstub_icount_ena_single_step + + +//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 + .type gdbstub_set_hw_breakpoint, @function + ASATTR_GDBFN +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 + .size gdbstub_set_hw_breakpoint, .-gdbstub_set_hw_breakpoint + + .global gdbstub_del_hw_breakpoint + .type gdbstub_del_hw_breakpoint, @function + ASATTR_GDBFN +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 + .size gdbstub_del_hw_breakpoint, .-gdbstub_del_hw_breakpoint + + .global gdbstub_set_hw_watchpoint + .type gdbstub_set_hw_watchpoint, @function + ASATTR_GDBFN + //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 + .size gdbstub_set_hw_watchpoint, .-gdbstub_set_hw_watchpoint + + + .global gdbstub_del_hw_watchpoint + .type gdbstub_del_hw_watchpoint, @function + ASATTR_GDBFN + //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 + .size gdbstub_del_hw_watchpoint, .-gdbstub_del_hw_watchpoint + +#endif // ENABLE_GDB diff --git a/Sming/gdb/GdbPacket.cpp b/Sming/gdb/GdbPacket.cpp new file mode 100644 index 0000000000..ab7fa0f2e1 --- /dev/null +++ b/Sming/gdb/GdbPacket.cpp @@ -0,0 +1,126 @@ +/**** + * Sming Framework Project - Open Source framework for high efficiency native ESP8266 development. + * Created 2015 by Skurydin Alexey + * http://github.com/SmingHub/Sming + * All files of the Sming Core are provided under the LGPL v3 license. + * + * @author: 2019 - Mikee47 + * + * Manages GDB packet encoding + * + ****/ + +#include "GdbPacket.h" + +// Swap byte ordering +#define bswap32(value) __builtin_bswap32(value) + +// Send the start of a packet; reset checksum calculation. +void ATTR_GDBEXTERNFN GdbPacket::start() +{ + gdbSendChar('$'); +} + +void ATTR_GDBEXTERNFN GdbPacket::end() +{ + gdbSendChar('#'); + writeHexByte(checksum); + debug_i("> PKT [%u]", packetLength); +} + +void ATTR_GDBEXTERNFN GdbPacket::writeChar(char c) +{ + gdbSendChar(c); + ++packetLength; + checksum += uint8_t(c); +} + +void ATTR_GDBEXTERNFN GdbPacket::writeCharEscaped(char c) +{ + if(c == '#' || c == '$' || c == '}' || c == '*') { + writeChar('}'); + c ^= 0x20; + } + writeChar(c); +} + +void ATTR_GDBEXTERNFN GdbPacket::write(const void* data, unsigned length) +{ + for(unsigned i = 0; i < length; ++i) { + checksum += static_cast(data)[i]; + } + gdbSendData(data, length); + packetLength += length; +} + +void ATTR_GDBEXTERNFN GdbPacket::writeX32() +{ + for(int i = 0; i < 8; i++) { + writeChar('x'); + } +} + +void ATTR_GDBEXTERNFN GdbPacket::writeHexByte(uint8_t value) +{ + writeChar(hexchar((value >> 4) & 0x0f)); + writeChar(hexchar(value & 0x0f)); +} + +void ATTR_GDBEXTERNFN GdbPacket::writeHexWord16(uint16_t value) +{ + writeHexByte(value >> 8); + writeHexByte(value); +} + +void ATTR_GDBEXTERNFN GdbPacket::writeHexWord32(uint32_t value) +{ + writeHexByte(value >> 24); + writeHexByte(value >> 16); + writeHexByte(value >> 8); + writeHexByte(value); +} + +void ATTR_GDBEXTERNFN GdbPacket::writeHexBlock(const void* src, size_t size) +{ + for(unsigned i = 0; i < size; ++i) { + writeHexByte(static_cast(src)[i]); + } +} + +uint32_t ATTR_GDBEXTERNFN GdbPacket::readHexValue(const char*& data) +{ + uint32_t result = 0; + int8_t c; + while((c = unhex(*data)) >= 0) { + result = (result << 4) | c; + ++data; + } + return result; +} + +void ATTR_GDBEXTERNFN GdbPacket::encodeHexBlock(char* dst, const void* src, size_t size) +{ + for(unsigned i = 0; i < size; ++i) { + uint8_t value = static_cast(src)[i]; + *dst++ = hexchar((value >> 4) & 0x0f); + *dst++ = hexchar(value & 0x0f); + } +} + +size_t ATTR_GDBEXTERNFN GdbPacket::decodeHexBlock(void* dst, const char*& src) +{ + auto out = static_cast(dst); + for(;;) { + int8_t c1 = unhex(src[0]); + if(c1 < 0) { + break; + } + int8_t c0 = unhex(src[1]); + if(c0 < 0) { + break; + } + src += 2; + *out++ = (c1 << 4) | c0; + } + return out - static_cast(dst); +} diff --git a/Sming/gdb/GdbPacket.h b/Sming/gdb/GdbPacket.h new file mode 100644 index 0000000000..0b00710c29 --- /dev/null +++ b/Sming/gdb/GdbPacket.h @@ -0,0 +1,107 @@ +/**** + * Sming Framework Project - Open Source framework for high efficiency native ESP8266 development. + * Created 2015 by Skurydin Alexey + * http://github.com/SmingHub/Sming + * All files of the Sming Core are provided under the LGPL v3 license. + * + * @author: 2019 - Mikee47 + * + * Manages GDB packet encoding + * + ****/ + +#ifndef _GDB_GDB_PACKET_H_ +#define _GDB_GDB_PACKET_H_ + +#include "gdbuart.h" + +class GdbPacket +{ +public: + // Automatically start sending a packet + __forceinline GdbPacket() + { + start(); + } + + // Finish sending a packet + __forceinline ~GdbPacket() + { + end(); + } + + // Send a char as part of a packet + void writeChar(char c); + + // Send a character, escaping if required + void writeCharEscaped(char c); + + /** @brief Output 8-bit value */ + void writeHexByte(uint8_t value); + + /** @brief Output 16-bit value */ + void writeHexWord16(uint16_t value); + + /** @brief Output 32-bit value */ + void writeHexWord32(uint32_t value); + + /** + * @brief Output a block of data, hex-encoded + * @param src + * @param size + */ + void writeHexBlock(const void* src, size_t size); + + /** @brief Output 'xxxxxxxx' to indicate undefined register value */ + void writeX32(); + + /** @brief Output block of data exactly as given without escaping */ + void write(const void* data, unsigned length); + + /** @brief Output a null-terminated string exactly as given without escaping */ + __forceinline void ATTR_GDBEXTERNFN writeStr(const char* str) + { + write(str, strlen(str)); + } + + size_t getLength() + { + return packetLength + 3; // Include # and checksum digits + } + + /** + * @brief Decode a variable-length hex value, MSB first + * @param data will get positioned on the end of the hex string, as far as the routine has read into it + * @retval uint32_t decoded value + */ + static uint32_t readHexValue(const char*& data); + + /** @brief Encode a value as hex characters, LSB first + * @param dst Location for output, will NOT be nul-terminated + * @param srcData Data bytes to encode + * @param size Size of source data in bytes + * @Note destination buffer must have enough space for (size * 2) chars + */ + static void encodeHexBlock(char* dst, const void* src, size_t size); + + /** @brief Decode hex-encoded data block + * @param dst buffer for decoded hex bytes (may be same as src) + * @param src source data, on return points to first character after hex data + * @retval size_t number of decoded bytes + * @note Output is always smaller than input so safe to overwrite src + */ + static size_t decodeHexBlock(void* dst, const char*& src); + +private: + // Send the start of a packet; reset checksum calculation. + void start(); + + // Finish sending a packet. + void end(); + +private: + uint8_t checksum = 0; ///< Running checksum of the output packet + unsigned packetLength = 0; +}; + +#endif /* _GDB_GDB_PACKET_H_ */ diff --git a/Sming/gdb/exceptions.h b/Sming/gdb/exceptions.h new file mode 100644 index 0000000000..2072dce910 --- /dev/null +++ b/Sming/gdb/exceptions.h @@ -0,0 +1,61 @@ +/**** + * Sming Framework Project - Open Source framework for high efficiency native ESP8266 development. + * Created 2015 by Skurydin Alexey + * http://github.com/SmingHub/Sming + * All files of the Sming Core are provided under the LGPL v3 license. + * + * exceptions.h + * + * @author: 2019 - Mikee47 + * + ****/ + +#ifndef _GDB_EXCEPTIONS_H_ +#define _GDB_EXCEPTIONS_H_ + +#include +#include "gdb/signals.h" + +/* + * Trapped exceptions and the corresponding GDB signal + * Ordered from 0 to last used exception + * + * EXCCAUSE_*, signal, desc + * + * Only those exceptions where `signal` is non-zero are trapped + */ +#define SYSTEM_EXCEPTION_MAP(XX) \ + XX(ILLEGAL, GDB_SIGNAL_ILL, "Illegal Instruction") \ + XX(SYSCALL, GDB_SIGNAL_USR2, "System Call") \ + XX(INSTR_ERROR, GDB_SIGNAL_SEGV, "Instruction Fetch Error") \ + XX(LOAD_STORE_ERROR, GDB_SIGNAL_SEGV, "Load Store Error") \ + XX(LEVEL1_INTERRUPT, 0, "Level 1 Interrupt") \ + XX(ALLOCA, 0, "MOVSP inst") \ + XX(DIVIDE_BY_ZERO, GDB_SIGNAL_FPE, "Integer Divide by Zero") \ + XX(SPECULATION, 0, "") \ + XX(PRIVILEGED, GDB_SIGNAL_ABRT, "Privileged Instruction") \ + XX(UNALIGNED, GDB_SIGNAL_EMT, "Unaligned Load/Store") \ + XX(RSVD_10, 0, "") \ + XX(RSVD_11, 0, "") \ + XX(INSTR_DATA_ERROR, GDB_SIGNAL_EMT, "PIF Data Error on Instruction Fetch") \ + XX(LOAD_STORE_DATA_ERROR, GDB_SIGNAL_EMT, "PIF Data Error on Load/Store") \ + XX(INSTR_ADDR_ERROR, GDB_SIGNAL_EMT, "PIF Address Error on Instruction Fetch") \ + XX(LOAD_STORE_ADDR_ERROR, GDB_SIGNAL_EMT, "PIF Address Error on Load/Store") \ + XX(ITLB_MISS, 0, "ITLB Miss") \ + XX(ITLB_MULTIHIT, 0, "ITLB Multihit") \ + XX(INSTR_RING, 0, "Ring Privilege Violation on Instruction Fetch") \ + XX(RSVD_19, 0, "") \ + XX(INSTR_PROHIBITED, GDB_SIGNAL_SEGV, "Cache Attribute does not allow Instruction Fetch") \ + XX(RSVD_21, 0, "") \ + XX(RSVD_22, 0, "") \ + XX(RSVD_23, 0, "") \ + XX(DTLB_MISS, 0, "DTLB Miss") \ + XX(DTLB_MULTIHIT, 0, "TBLD Multihit") \ + XX(LOAD_STORE_RING, 0, "Ring Privilege Violation on Load/Store") \ + XX(RSVD_27, 0, "") \ + XX(LOAD_PROHIBITED, GDB_SIGNAL_SEGV, "Cache Attribute does not allow Load") \ + XX(STORE_PROHIBITED, GDB_SIGNAL_SEGV, "Cache Attribute does not allow Store") + +#define EXCCAUSE_MAX EXCCAUSE_STORE_PROHIBITED + +#endif /* _GDB_EXCEPTION_MAP_H_ */ diff --git a/Sming/gdb/gdb/registers.h b/Sming/gdb/gdb/registers.h new file mode 100644 index 0000000000..ec5e8eeb22 --- /dev/null +++ b/Sming/gdb/gdb/registers.h @@ -0,0 +1,179 @@ +/**** + * Sming Framework Project - Open Source framework for high efficiency native ESP8266 development. + * Created 2015 by Skurydin Alexey + * http://github.com/SmingHub/Sming + * All files of the Sming Core are provided under the LGPL v3 license. + * + * @author: 2019 - Mikee47 + * + * GDB register definitions + * + ****/ + +#ifndef _GDB_REGISTERS_H_ +#define _GDB_REGISTERS_H_ + +#include "gdbstub-cfg.h" +#include "signals.h" + +#if GDBSTUB_GDB_PATCHED == 1 +/* + * Patched GDB uses a small subset of registers + */ +#define GDB_REGCOUNT 22 +#define XT_REGISTER_MAP(XTREG) \ + XX(0, a0) \ + XX(1, a1) \ + XX(2, a2) \ + XX(3, a3) \ + XX(4, a4) \ + XX(5, a5) \ + XX(6, a6) \ + XX(7, a7) \ + XX(8, a8) \ + XX(9, a9) \ + XX(10, a10) \ + XX(11, a11) \ + XX(12, a12) \ + XX(13, a13) \ + XX(14, a14) \ + XX(15, a15) \ + XX(16, pc) \ + XX(17, sar) \ + XX(18, litbase) \ + XX(19, sr176) \ + XX(20, sr208) \ + XX(21, ps) + +#else +/* + * Unpatched GDB full register set + * Note that our stub doesn't return all of these, we send 'xxxxxxxx' for those + */ +#define GDB_REGCOUNT 113 +// This table is extracted from xtensa-config.c in GDB source code +#define XT_REGISTER_MAP(XX) \ + XX(0, pc) \ + XX(1, ar0) \ + XX(2, ar1) \ + XX(3, ar2) \ + XX(4, ar3) \ + XX(5, ar4) \ + XX(6, ar5) \ + XX(7, ar6) \ + XX(8, ar7) \ + XX(9, ar8) \ + XX(10, ar9) \ + XX(11, ar10) \ + XX(12, ar11) \ + XX(13, ar12) \ + XX(14, ar13) \ + XX(15, ar14) \ + XX(16, ar15) \ + XX(17, ar16) \ + XX(18, ar17) \ + XX(19, ar18) \ + XX(20, ar19) \ + XX(21, ar20) \ + XX(22, ar21) \ + XX(23, ar22) \ + XX(24, ar23) \ + XX(25, ar24) \ + XX(26, ar25) \ + XX(27, ar26) \ + XX(28, ar27) \ + XX(29, ar28) \ + XX(30, ar29) \ + XX(31, ar30) \ + XX(32, ar31) \ + XX(33, lbeg) \ + XX(34, lend) \ + XX(35, lcount) \ + XX(36, sar) \ + XX(37, litbase) \ + XX(38, windowbase) \ + XX(39, windowstart) \ + XX(40, sr176) \ + XX(41, sr208) \ + XX(42, ps) \ + XX(43, threadptr) \ + XX(44, scompare1) \ + XX(45, ptevaddr) \ + XX(46, mmid) \ + XX(47, rasid) \ + XX(48, itlbcfg) \ + XX(49, dtlbcfg) \ + XX(50, ibreakenable) \ + XX(51, ddr) \ + XX(52, ibreaka0) \ + XX(53, ibreaka1) \ + XX(54, dbreaka0) \ + XX(55, dbreaka1) \ + XX(56, dbreakc0) \ + XX(57, dbreakc1) \ + XX(58, epc1) \ + XX(59, epc2) \ + XX(60, epc3) \ + XX(61, epc4) \ + XX(62, epc5) \ + XX(63, epc6) \ + XX(64, epc7) \ + XX(65, depc) \ + XX(66, eps2) \ + XX(67, eps3) \ + XX(68, eps4) \ + XX(69, eps5) \ + XX(70, eps6) \ + XX(71, eps7) \ + XX(72, excsave1) \ + XX(73, excsave2) \ + XX(74, excsave3) \ + XX(75, excsave4) \ + XX(76, excsave5) \ + XX(77, excsave6) \ + XX(78, excsave7) \ + XX(79, cpenable) \ + XX(80, interrupt) \ + XX(81, intset) \ + XX(82, intclear) \ + XX(83, intenable) \ + XX(84, vecbase) \ + XX(85, exccause) \ + XX(86, debugcause) \ + XX(87, ccount) \ + XX(88, prid) \ + XX(89, icount) \ + XX(90, icountlevel) \ + XX(91, excvaddr) \ + XX(92, ccompare0) \ + XX(93, ccompare1) \ + XX(94, ccompare2) \ + XX(95, misc0) \ + XX(96, misc1) \ + XX(97, a0) \ + XX(98, a1) \ + XX(99, a2) \ + XX(100, a3) \ + XX(101, a4) \ + XX(102, a5) \ + XX(103, a6) \ + XX(104, a7) \ + XX(105, a8) \ + XX(106, a9) \ + XX(107, a10) \ + XX(108, a11) \ + XX(109, a12) \ + XX(110, a13) \ + XX(111, a14) \ + XX(112, a15) + +#endif + +// Register numbers +enum GdbReg { +#define XX(idx, name) GdbReg_##name = idx, + XT_REGISTER_MAP(XX) +#undef XX +}; + +#endif /* _GDB_REGISTERS_H_ */ diff --git a/Sming/gdb/gdb/signals.def b/Sming/gdb/gdb/signals.def new file mode 100644 index 0000000000..571a22a13e --- /dev/null +++ b/Sming/gdb/gdb/signals.def @@ -0,0 +1,202 @@ +/* Target signal numbers for GDB and the GDB remote protocol. + Copyright (C) 2010-2019 Free Software Foundation, Inc. + + This file is part of GDB. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . */ + +/* Used some places (e.g. stop_signal) to record the concept that + there is no signal. */ +SET (GDB_SIGNAL_0, 0, "0", "Signal 0") +#define GDB_SIGNAL_FIRST GDB_SIGNAL_0 +SET (GDB_SIGNAL_HUP, 1, "SIGHUP", "Hangup") +SET (GDB_SIGNAL_INT, 2, "SIGINT", "Interrupt") +SET (GDB_SIGNAL_QUIT, 3, "SIGQUIT", "Quit") +SET (GDB_SIGNAL_ILL, 4, "SIGILL", "Illegal instruction") +SET (GDB_SIGNAL_TRAP, 5, "SIGTRAP", "Trace/breakpoint trap") +SET (GDB_SIGNAL_ABRT, 6, "SIGABRT", "Aborted") +SET (GDB_SIGNAL_EMT, 7, "SIGEMT", "Emulation trap") +SET (GDB_SIGNAL_FPE, 8, "SIGFPE", "Arithmetic exception") +SET (GDB_SIGNAL_KILL, 9, "SIGKILL", "Killed") +SET (GDB_SIGNAL_BUS, 10, "SIGBUS", "Bus error") +SET (GDB_SIGNAL_SEGV, 11, "SIGSEGV", "Segmentation fault") +SET (GDB_SIGNAL_SYS, 12, "SIGSYS", "Bad system call") +SET (GDB_SIGNAL_PIPE, 13, "SIGPIPE", "Broken pipe") +SET (GDB_SIGNAL_ALRM, 14, "SIGALRM", "Alarm clock") +SET (GDB_SIGNAL_TERM, 15, "SIGTERM", "Terminated") +SET (GDB_SIGNAL_URG, 16, "SIGURG", "Urgent I/O condition") +SET (GDB_SIGNAL_STOP, 17, "SIGSTOP", "Stopped (signal)") +SET (GDB_SIGNAL_TSTP, 18, "SIGTSTP", "Stopped (user)") +SET (GDB_SIGNAL_CONT, 19, "SIGCONT", "Continued") +SET (GDB_SIGNAL_CHLD, 20, "SIGCHLD", "Child status changed") +SET (GDB_SIGNAL_TTIN, 21, "SIGTTIN", "Stopped (tty input)") +SET (GDB_SIGNAL_TTOU, 22, "SIGTTOU", "Stopped (tty output)") +SET (GDB_SIGNAL_IO, 23, "SIGIO", "I/O possible") +SET (GDB_SIGNAL_XCPU, 24, "SIGXCPU", "CPU time limit exceeded") +SET (GDB_SIGNAL_XFSZ, 25, "SIGXFSZ", "File size limit exceeded") +SET (GDB_SIGNAL_VTALRM, 26, "SIGVTALRM", "Virtual timer expired") +SET (GDB_SIGNAL_PROF, 27, "SIGPROF", "Profiling timer expired") +SET (GDB_SIGNAL_WINCH, 28, "SIGWINCH", "Window size changed") +SET (GDB_SIGNAL_LOST, 29, "SIGLOST", "Resource lost") +SET (GDB_SIGNAL_USR1, 30, "SIGUSR1", "User defined signal 1") +SET (GDB_SIGNAL_USR2, 31, "SIGUSR2", "User defined signal 2") +SET (GDB_SIGNAL_PWR, 32, "SIGPWR", "Power fail/restart") +/* Similar to SIGIO. Perhaps they should have the same number. */ +SET (GDB_SIGNAL_POLL, 33, "SIGPOLL", "Pollable event occurred") +SET (GDB_SIGNAL_WIND, 34, "SIGWIND", "SIGWIND") +SET (GDB_SIGNAL_PHONE, 35, "SIGPHONE", "SIGPHONE") +SET (GDB_SIGNAL_WAITING, 36, "SIGWAITING", "Process's LWPs are blocked") +SET (GDB_SIGNAL_LWP, 37, "SIGLWP", "Signal LWP") +SET (GDB_SIGNAL_DANGER, 38, "SIGDANGER", "Swap space dangerously low") +SET (GDB_SIGNAL_GRANT, 39, "SIGGRANT", "Monitor mode granted") +SET (GDB_SIGNAL_RETRACT, 40, "SIGRETRACT", + "Need to relinquish monitor mode") +SET (GDB_SIGNAL_MSG, 41, "SIGMSG", "Monitor mode data available") +SET (GDB_SIGNAL_SOUND, 42, "SIGSOUND", "Sound completed") +SET (GDB_SIGNAL_SAK, 43, "SIGSAK", "Secure attention") +SET (GDB_SIGNAL_PRIO, 44, "SIGPRIO", "SIGPRIO") +SET (GDB_SIGNAL_REALTIME_33, 45, "SIG33", "Real-time event 33") +SET (GDB_SIGNAL_REALTIME_34, 46, "SIG34", "Real-time event 34") +SET (GDB_SIGNAL_REALTIME_35, 47, "SIG35", "Real-time event 35") +SET (GDB_SIGNAL_REALTIME_36, 48, "SIG36", "Real-time event 36") +SET (GDB_SIGNAL_REALTIME_37, 49, "SIG37", "Real-time event 37") +SET (GDB_SIGNAL_REALTIME_38, 50, "SIG38", "Real-time event 38") +SET (GDB_SIGNAL_REALTIME_39, 51, "SIG39", "Real-time event 39") +SET (GDB_SIGNAL_REALTIME_40, 52, "SIG40", "Real-time event 40") +SET (GDB_SIGNAL_REALTIME_41, 53, "SIG41", "Real-time event 41") +SET (GDB_SIGNAL_REALTIME_42, 54, "SIG42", "Real-time event 42") +SET (GDB_SIGNAL_REALTIME_43, 55, "SIG43", "Real-time event 43") +SET (GDB_SIGNAL_REALTIME_44, 56, "SIG44", "Real-time event 44") +SET (GDB_SIGNAL_REALTIME_45, 57, "SIG45", "Real-time event 45") +SET (GDB_SIGNAL_REALTIME_46, 58, "SIG46", "Real-time event 46") +SET (GDB_SIGNAL_REALTIME_47, 59, "SIG47", "Real-time event 47") +SET (GDB_SIGNAL_REALTIME_48, 60, "SIG48", "Real-time event 48") +SET (GDB_SIGNAL_REALTIME_49, 61, "SIG49", "Real-time event 49") +SET (GDB_SIGNAL_REALTIME_50, 62, "SIG50", "Real-time event 50") +SET (GDB_SIGNAL_REALTIME_51, 63, "SIG51", "Real-time event 51") +SET (GDB_SIGNAL_REALTIME_52, 64, "SIG52", "Real-time event 52") +SET (GDB_SIGNAL_REALTIME_53, 65, "SIG53", "Real-time event 53") +SET (GDB_SIGNAL_REALTIME_54, 66, "SIG54", "Real-time event 54") +SET (GDB_SIGNAL_REALTIME_55, 67, "SIG55", "Real-time event 55") +SET (GDB_SIGNAL_REALTIME_56, 68, "SIG56", "Real-time event 56") +SET (GDB_SIGNAL_REALTIME_57, 69, "SIG57", "Real-time event 57") +SET (GDB_SIGNAL_REALTIME_58, 70, "SIG58", "Real-time event 58") +SET (GDB_SIGNAL_REALTIME_59, 71, "SIG59", "Real-time event 59") +SET (GDB_SIGNAL_REALTIME_60, 72, "SIG60", "Real-time event 60") +SET (GDB_SIGNAL_REALTIME_61, 73, "SIG61", "Real-time event 61") +SET (GDB_SIGNAL_REALTIME_62, 74, "SIG62", "Real-time event 62") +SET (GDB_SIGNAL_REALTIME_63, 75, "SIG63", "Real-time event 63") + +/* Used internally by Solaris threads. See signal(5) on Solaris. */ +SET (GDB_SIGNAL_CANCEL, 76, "SIGCANCEL", "LWP internal signal") + +/* Yes, this pains me, too. But LynxOS didn't have SIG32, and now + GNU/Linux does, and we can't disturb the numbering, since it's + part of the remote protocol. Note that in some GDB's + GDB_SIGNAL_REALTIME_32 is number 76. */ +SET (GDB_SIGNAL_REALTIME_32, 77, "SIG32", "Real-time event 32") +/* Yet another pain, IRIX 6 has SIG64. */ +SET (GDB_SIGNAL_REALTIME_64, 78, "SIG64", "Real-time event 64") +/* Yet another pain, GNU/Linux MIPS might go up to 128. */ +SET (GDB_SIGNAL_REALTIME_65, 79, "SIG65", "Real-time event 65") +SET (GDB_SIGNAL_REALTIME_66, 80, "SIG66", "Real-time event 66") +SET (GDB_SIGNAL_REALTIME_67, 81, "SIG67", "Real-time event 67") +SET (GDB_SIGNAL_REALTIME_68, 82, "SIG68", "Real-time event 68") +SET (GDB_SIGNAL_REALTIME_69, 83, "SIG69", "Real-time event 69") +SET (GDB_SIGNAL_REALTIME_70, 84, "SIG70", "Real-time event 70") +SET (GDB_SIGNAL_REALTIME_71, 85, "SIG71", "Real-time event 71") +SET (GDB_SIGNAL_REALTIME_72, 86, "SIG72", "Real-time event 72") +SET (GDB_SIGNAL_REALTIME_73, 87, "SIG73", "Real-time event 73") +SET (GDB_SIGNAL_REALTIME_74, 88, "SIG74", "Real-time event 74") +SET (GDB_SIGNAL_REALTIME_75, 89, "SIG75", "Real-time event 75") +SET (GDB_SIGNAL_REALTIME_76, 90, "SIG76", "Real-time event 76") +SET (GDB_SIGNAL_REALTIME_77, 91, "SIG77", "Real-time event 77") +SET (GDB_SIGNAL_REALTIME_78, 92, "SIG78", "Real-time event 78") +SET (GDB_SIGNAL_REALTIME_79, 93, "SIG79", "Real-time event 79") +SET (GDB_SIGNAL_REALTIME_80, 94, "SIG80", "Real-time event 80") +SET (GDB_SIGNAL_REALTIME_81, 95, "SIG81", "Real-time event 81") +SET (GDB_SIGNAL_REALTIME_82, 96, "SIG82", "Real-time event 82") +SET (GDB_SIGNAL_REALTIME_83, 97, "SIG83", "Real-time event 83") +SET (GDB_SIGNAL_REALTIME_84, 98, "SIG84", "Real-time event 84") +SET (GDB_SIGNAL_REALTIME_85, 99, "SIG85", "Real-time event 85") +SET (GDB_SIGNAL_REALTIME_86, 100, "SIG86", "Real-time event 86") +SET (GDB_SIGNAL_REALTIME_87, 101, "SIG87", "Real-time event 87") +SET (GDB_SIGNAL_REALTIME_88, 102, "SIG88", "Real-time event 88") +SET (GDB_SIGNAL_REALTIME_89, 103, "SIG89", "Real-time event 89") +SET (GDB_SIGNAL_REALTIME_90, 104, "SIG90", "Real-time event 90") +SET (GDB_SIGNAL_REALTIME_91, 105, "SIG91", "Real-time event 91") +SET (GDB_SIGNAL_REALTIME_92, 106, "SIG92", "Real-time event 92") +SET (GDB_SIGNAL_REALTIME_93, 107, "SIG93", "Real-time event 93") +SET (GDB_SIGNAL_REALTIME_94, 108, "SIG94", "Real-time event 94") +SET (GDB_SIGNAL_REALTIME_95, 109, "SIG95", "Real-time event 95") +SET (GDB_SIGNAL_REALTIME_96, 110, "SIG96", "Real-time event 96") +SET (GDB_SIGNAL_REALTIME_97, 111, "SIG97", "Real-time event 97") +SET (GDB_SIGNAL_REALTIME_98, 112, "SIG98", "Real-time event 98") +SET (GDB_SIGNAL_REALTIME_99, 113, "SIG99", "Real-time event 99") +SET (GDB_SIGNAL_REALTIME_100, 114, "SIG100", "Real-time event 100") +SET (GDB_SIGNAL_REALTIME_101, 115, "SIG101", "Real-time event 101") +SET (GDB_SIGNAL_REALTIME_102, 116, "SIG102", "Real-time event 102") +SET (GDB_SIGNAL_REALTIME_103, 117, "SIG103", "Real-time event 103") +SET (GDB_SIGNAL_REALTIME_104, 118, "SIG104", "Real-time event 104") +SET (GDB_SIGNAL_REALTIME_105, 119, "SIG105", "Real-time event 105") +SET (GDB_SIGNAL_REALTIME_106, 120, "SIG106", "Real-time event 106") +SET (GDB_SIGNAL_REALTIME_107, 121, "SIG107", "Real-time event 107") +SET (GDB_SIGNAL_REALTIME_108, 122, "SIG108", "Real-time event 108") +SET (GDB_SIGNAL_REALTIME_109, 123, "SIG109", "Real-time event 109") +SET (GDB_SIGNAL_REALTIME_110, 124, "SIG110", "Real-time event 110") +SET (GDB_SIGNAL_REALTIME_111, 125, "SIG111", "Real-time event 111") +SET (GDB_SIGNAL_REALTIME_112, 126, "SIG112", "Real-time event 112") +SET (GDB_SIGNAL_REALTIME_113, 127, "SIG113", "Real-time event 113") +SET (GDB_SIGNAL_REALTIME_114, 128, "SIG114", "Real-time event 114") +SET (GDB_SIGNAL_REALTIME_115, 129, "SIG115", "Real-time event 115") +SET (GDB_SIGNAL_REALTIME_116, 130, "SIG116", "Real-time event 116") +SET (GDB_SIGNAL_REALTIME_117, 131, "SIG117", "Real-time event 117") +SET (GDB_SIGNAL_REALTIME_118, 132, "SIG118", "Real-time event 118") +SET (GDB_SIGNAL_REALTIME_119, 133, "SIG119", "Real-time event 119") +SET (GDB_SIGNAL_REALTIME_120, 134, "SIG120", "Real-time event 120") +SET (GDB_SIGNAL_REALTIME_121, 135, "SIG121", "Real-time event 121") +SET (GDB_SIGNAL_REALTIME_122, 136, "SIG122", "Real-time event 122") +SET (GDB_SIGNAL_REALTIME_123, 137, "SIG123", "Real-time event 123") +SET (GDB_SIGNAL_REALTIME_124, 138, "SIG124", "Real-time event 124") +SET (GDB_SIGNAL_REALTIME_125, 139, "SIG125", "Real-time event 125") +SET (GDB_SIGNAL_REALTIME_126, 140, "SIG126", "Real-time event 126") +SET (GDB_SIGNAL_REALTIME_127, 141, "SIG127", "Real-time event 127") + +SET (GDB_SIGNAL_INFO, 142, "SIGINFO", "Information request") + +/* Some signal we don't know about. */ +SET (GDB_SIGNAL_UNKNOWN, 143, NULL, "Unknown signal") + +/* Use whatever signal we use when one is not specifically specified + (for passing to proceed and so on). */ +SET (GDB_SIGNAL_DEFAULT, 144, NULL, + "Internal error: printing GDB_SIGNAL_DEFAULT") + +/* Mach exceptions. In versions of GDB before 5.2, these were just before + GDB_SIGNAL_INFO if you were compiling on a Mach host (and missing + otherwise). */ +SET (GDB_EXC_BAD_ACCESS, 145, "EXC_BAD_ACCESS", "Could not access memory") +SET (GDB_EXC_BAD_INSTRUCTION, 146, "EXC_BAD_INSTRUCTION", + "Illegal instruction/operand") +SET (GDB_EXC_ARITHMETIC, 147, "EXC_ARITHMETIC", "Arithmetic exception") +SET (GDB_EXC_EMULATION, 148, "EXC_EMULATION", "Emulation instruction") +SET (GDB_EXC_SOFTWARE, 149, "EXC_SOFTWARE", "Software generated exception") +SET (GDB_EXC_BREAKPOINT, 150, "EXC_BREAKPOINT", "Breakpoint") + +SET (GDB_SIGNAL_LIBRT, 151, "SIGLIBRT", "librt internal signal") + +/* If you are adding a new signal, add it just above this comment. */ + +/* Last and unused enum value, for sizing arrays, etc. */ +SET (GDB_SIGNAL_LAST, 152, NULL, "GDB_SIGNAL_LAST") diff --git a/Sming/gdb/gdb/signals.h b/Sming/gdb/gdb/signals.h new file mode 100644 index 0000000000..d9b193a052 --- /dev/null +++ b/Sming/gdb/gdb/signals.h @@ -0,0 +1,58 @@ +/* Target signal numbers for GDB and the GDB remote protocol. + Copyright (C) 1986-2019 Free Software Foundation, Inc. + + This file is part of GDB. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . */ + +#ifndef GDB_SIGNALS_H +#define GDB_SIGNALS_H + +/* The numbering of these signals is chosen to match traditional unix + signals (insofar as various unices use the same numbers, anyway). + It is also the numbering of the GDB remote protocol. Other remote + protocols, if they use a different numbering, should make sure to + translate appropriately. + + Since these numbers have actually made it out into other software + (stubs, etc.), you mustn't disturb the assigned numbering. If you + need to add new signals here, add them to the end of the explicitly + numbered signals, at the comment marker. Add them unconditionally, + not within any #if or #ifdef. + + This is based strongly on Unix/POSIX signals for several reasons: + (1) This set of signals represents a widely-accepted attempt to + represent events of this sort in a portable fashion, (2) we want a + signal to make it from wait to child_wait to the user intact, (3) many + remote protocols use a similar encoding. However, it is + recognized that this set of signals has limitations (such as not + distinguishing between various kinds of SIGSEGV, or not + distinguishing hitting a breakpoint from finishing a single step). + So in the future we may get around this either by adding additional + signals for breakpoint, single-step, etc., or by adding signal + codes; the latter seems more in the spirit of what BSD, System V, + etc. are doing to address these issues. */ + +/* For an explanation of what each signal means, see + gdb_signal_to_string. */ + +enum gdb_signal + { +#define SET(symbol, constant, name, string) \ + symbol = constant, +#include "signals.def" +#undef SET + }; + +#endif /* #ifndef GDB_SIGNALS_H */ diff --git a/Sming/gdb/gdbcmds b/Sming/gdb/gdbcmds new file mode 100644 index 0000000000..cb40f0946f --- /dev/null +++ b/Sming/gdb/gdbcmds @@ -0,0 +1,28 @@ +# Enable this if you want to log all traffic between GDB and the stub +# set remotelogfile gdb_rsp_logfile.txt + +# ESP8266 HW limits the number of breakpoints/watchpoints +set remote hardware-breakpoint-limit 1 +set remote hardware-watchpoint-limit 1 + +# Some GDBstub settings +set remote interrupt-on-connect on +set remote kill-packet off +set remote symbol-lookup-packet off +set remote verbose-resume-packet off + +# The memory map, so GDB knows where it can install SW breakpoints +mem 0x20000000 0x3fefffff ro cache +mem 0x3ff00000 0x3fffffff rw +mem 0x40000000 0x400fffff ro cache +mem 0x40100000 0x4013ffff rw cache +mem 0x40140000 0x5fffffff ro cache +mem 0x60000000 0x60001fff rw + +# +file out/build/app_0.out + +# Change the following to your serial port and baud +#set serial baud 115200 +#target remote /dev/ttyUSB0 +#target remote /COM4 diff --git a/Sming/gdb/gdbstub-cfg.h b/Sming/gdb/gdbstub-cfg.h new file mode 100644 index 0000000000..5bc6780535 --- /dev/null +++ b/Sming/gdb/gdbstub-cfg.h @@ -0,0 +1,166 @@ +/**** + * Sming Framework Project - Open Source framework for high efficiency native ESP8266 development. + * Created 2015 by Skurydin Alexey + * http://github.com/SmingHub/Sming + * All files of the Sming Core are provided under the LGPL v3 license. + * + * GDB Stub and exception/crash dumping configuration + * + * Unless otherwise noted, settings only take effect when application is built with ENABLE_GDB=1 + * + ****/ + +#ifndef _GDB_GDBSTUB_CFG_H_ +#define _GDB_GDBSTUB_CFG_H_ + +/* + * When enabled, an exception or crash dumps a stack trace to debug output + * Default is ON for debug builds, OFF for release builds + * + * Note: Not dependent upon ENABLE_GDB + * +*/ +#ifndef ENABLE_EXCEPTION_DUMP +#ifdef SMING_RELEASE +#define ENABLE_EXCEPTION_DUMP 0 +#else +#define ENABLE_EXCEPTION_DUMP 1 +#endif +#endif + +/* + * When enabled, an unexpected reset (i.e. system crash) dumps a stack trace to debug output + * Default is ON for debug builds, OFF for release builds + * + * Note: Not dependent upon ENABLE_GDB +*/ +#ifndef ENABLE_CRASH_DUMP +#ifdef SMING_RELEASE +#define ENABLE_CRASH_DUMP 0 +#else +#define ENABLE_CRASH_DUMP 1 +#endif +#endif + +/* + * When defined, GDB communications are echoed to UART1 for testing GDB stub operation. + * + * 0: No debug output + * 1: Show decoded commands and responses + * 2: Show packet content + * 3: Show debug output for internal routines + */ +#ifndef GDBSTUB_ENABLE_DEBUG +#define GDBSTUB_ENABLE_DEBUG 0 +#endif + +/* + * The HardwareTimer defaults to non-maskable mode, and will continue to operate whilst paused. + * Set this value to 1 to disable during a debugging exception, and restore it afterwards. + * Note that to debug HardwareTimer callback routines, the timer must be initialise in maskable mode. + */ +#ifndef GDBSTUB_PAUSE_HARDWARE_TIMER +#define GDBSTUB_PAUSE_HARDWARE_TIMER 0 +#endif + +/* + * Espressif provide a patched version of GDB which emits only those registered present in the lx106. + * Set to 0 if an unpatched version of GDB is used. + */ +#ifndef GDBSTUB_GDB_PATCHED +#define GDBSTUB_GDB_PATCHED 1 +#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 +#define GDBSTUB_STACK_SIZE 256 // In dwords +#endif + +/* + * Enable this to cause the program to pause and wait for gdb to be connected when an exception is encountered. + */ +#ifndef GDBSTUB_BREAK_ON_EXCEPTION +#define GDBSTUB_BREAK_ON_EXCEPTION 1 +#endif + +/* + * If this is defined, gdbstub will break the program when you press Ctrl-C in gdb. + * It does this by monitoring for the 'x03' character in the serial receive routine. Any preceding + * characters are passed through to the application via UART2. + * If your application uses the serial port for terminal (text) communications you should be OK, + * but binary transfers are likely to cause problems and this option should probably be disabled. + * Instead, use GDBSTUB_BREAK_ON_INIT, or call gdbstub_do_break() in your application. + * + * Specify: + * 0 to disable Ctrl+C break checking completely + * 1 to allow Ctrl+C break only when debugger is attached + * 2 to allow Ctrl+C break at any time. Ensure you have + * set remote interrupt-on-connect on + * in GDB command file, so it will send a Ctrl+C sequence when attempting to connect + */ +#ifndef GDBSTUB_CTRLC_BREAK +#define GDBSTUB_CTRLC_BREAK 2 +#endif + +/* + * The GDB stub has exclusive access to UART0, so applications cannot use it directly and attempts to + * open it will fail. + * + * If this option is enabled, the default serial port will be changed to UART2 to allow debug output + * (from m_printf, debug_*, os_printf, etc.) to show up in your GDB session. + * + * Outside of GDB terminal applications should work as normal, with the following caveats: + * + * If GDBSTUB_BREAK_ON_INIT is defined, then at startup your application will display `$T05#b9` and stop. + * A similar thing will happen if GDBSTUB_CTRLC_BREAK=2 and you type Ctrl+C. + * Continue by typing `$D#44` (without the quotes), or exit the terminal and start GDB. + * + * See GDB remote serial protocol for further details. + * + * Disabling this option releases some IRAM. You may be instead able to use UART1 for debug output, + * adding `Serial.setPort(UART_ID_1);` in your application's `init()` function. +*/ +#ifndef GDBSTUB_ENABLE_UART2 +#define GDBSTUB_ENABLE_UART2 1 +#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 1 +#endif + +/* + * Some commands are not required by GDB, so if neccessary can be disabled to save memory. + */ +// Read/write individual registers +#ifndef GDBSTUB_CMDENABLE_P +#define GDBSTUB_CMDENABLE_P 1 +#endif + +/* + * Specify a timeout (in milliseconds) when stub is reading from serial port. + * Set to 0 to wait indefinitely. + */ +#ifndef GDBSTUB_UART_READ_TIMEOUT +#define GDBSTUB_UART_READ_TIMEOUT 2000 +#endif + +/* + * Wherever possible gdbstub code is placed in flash memory. + * This is fine for most cases, but if debugging whilst flash is disabled or busy (eg during SPI operations + * or flash write/erase) then you will need to enable this option to move stub code into IRAM. + */ +#ifndef GDBSTUB_FORCE_IRAM +#define GDBSTUB_FORCE_IRAM 0 +#endif + +#endif /* _GDB_GDBSTUB_CFG_H_ */ diff --git a/Sming/gdb/gdbstub-entry.h b/Sming/gdb/gdbstub-entry.h new file mode 100644 index 0000000000..09575bec1f --- /dev/null +++ b/Sming/gdb/gdbstub-entry.h @@ -0,0 +1,31 @@ +/****************************************************************************** + * Copyright 2015 Espressif Systems + * + * Description: Assembly routines for the gdbstub + * + * License: ESPRESSIF MIT License + *******************************************************************************/ + +#ifndef _GDBSTUB_ENTRY_H_ +#define _GDBSTUB_ENTRY_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +void gdbstub_init_debug_entry(); +void gdbstub_icount_ena_single_step(); +void gdbstub_save_extra_sfrs_for_exception(); + +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 /* _GDBSTUB_ENTRY_H_ */ diff --git a/Sming/gdb/gdbstub-entry.s b/Sming/gdb/gdbstub-entry.s deleted file mode 100644 index 3968d5253e..0000000000 --- a/Sming/gdb/gdbstub-entry.s +++ /dev/null @@ -1,54 +0,0 @@ -/****************************************************************************** - * Copyright 2015 Espressif Systems - * - * Description: Assembly routines for the gdbstub - * - * License: ESPRESSIF MIT License - *******************************************************************************/ - - -#include -#include -#include - -.global gdbstub_savedRegs - - .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; - uint32_t excvaddr; -*/ - - .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 diff --git a/Sming/gdb/gdbstub-internal.h b/Sming/gdb/gdbstub-internal.h new file mode 100644 index 0000000000..f6bcd9e68d --- /dev/null +++ b/Sming/gdb/gdbstub-internal.h @@ -0,0 +1,78 @@ +/**** + * Sming Framework Project - Open Source framework for high efficiency native ESP8266 development. + * Created 2015 by Skurydin Alexey + * http://github.com/SmingHub/Sming + * All files of the Sming Core are provided under the LGPL v3 license. + * + * @author: 2019 - Mikee47 + * + * Standard definitions for gdbstub code modules, both C++ and assembler (.S only). + * + ****/ + +#ifndef _GDB_GDBSTUB_INTERNAL_H_ +#define _GDB_GDBSTUB_INTERNAL_H_ + +#include "xtensa/xtruntime-frames.h" +#include "gdbstub-cfg.h" + +#define UINT32_T unsigned int + +/* + * Defines our debugging exception frame which is provided to GDB when paused. + * Our exception handlers save register values here, restoring them on continuation. + * Any register changes made by GDB are stored here. + * + * System exceptions are initially handled by the Xtensa HAL, which saves some (but not all) + * registers into a `UserFrame` structure (see xtensa.h). Control passes to gdbstub_exception_handler + * which copies those values, plus some extra ones. + * + * Debug exceptions are handled entirely by gdbstub_debug_exception_entry, which saves + * registers, calls gdbstub_handle_debug_exception, then restores them. + * + * Using the macros provided by xtensa allow changes to this structure to be reflected into + * both C and assembler code. + */ +STRUCT_BEGIN +STRUCT_FIELD(UINT32_T, 4, GDBSR_, pc) +STRUCT_FIELD(UINT32_T, 4, GDBSR_, ps) +STRUCT_FIELD(UINT32_T, 4, GDBSR_, sar) +STRUCT_FIELD(UINT32_T, 4, GDBSR_, vpri) +STRUCT_AFIELD(UINT32_T, 4, GDBSR_, a, 16) // a0..a15 +#define GDBSR_A(n) GDBSR_a+((n)*4) +// These are added manually by the exception code; the HAL doesn't set these on an exception. +STRUCT_FIELD(UINT32_T, 4, GDBSR_, litbase) +STRUCT_FIELD(UINT32_T, 4, GDBSR_, sr176) +STRUCT_FIELD(UINT32_T, 4, GDBSR_, sr208) +STRUCT_FIELD(UINT32_T, 4, GDBSR_, cause) ///< Either EXCCAUSE or DBGCAUSE, depending on exception type +STRUCT_FIELD(UINT32_T, 4, GDBSR_, excvaddr) ///< Exception Virtual Address +STRUCT_END(GdbstubSavedRegisters) + +#undef UINT32_T + +// Don't include debug output for this module unless excplitly requested +#if GDBSTUB_ENABLE_DEBUG == 0 +#undef DEBUG_BUILD +#define DEBUG_BUILD 0 +#endif + +#if(defined(ENABLE_GDB) && GDBSTUB_BREAK_ON_EXCEPTION) || ENABLE_EXCEPTION_DUMP +#define HOOK_SYSTEM_EXCEPTIONS +#endif + +/* + * Code memory allocation attributes + */ +#define ATTR_GDBINIT ICACHE_FLASH_ATTR + +#if GDBSTUB_FORCE_IRAM +#define ATTR_GDBEXTERNFN IRAM_ATTR +#else +#define ATTR_GDBEXTERNFN ICACHE_FLASH_ATTR +#endif + +// Section definitions for assembler +#define ASATTR_GDBINIT .section .irom0.text +#define ASATTR_GDBFN .section .iram.text + +#endif /* _GDB_GDBSTUB_INTERNAL_H_ */ diff --git a/Sming/gdb/gdbstub.c b/Sming/gdb/gdbstub.c deleted file mode 100644 index 70ea3e44df..0000000000 --- a/Sming/gdb/gdbstub.c +++ /dev/null @@ -1,118 +0,0 @@ -/****************************************************************************** - * Copyright 2015 Espressif Systems - * - * Description: A stub to make the ESP8266 debuggable by GDB over the serial - * port. - * - * License: ESPRESSIF MIT License - *******************************************************************************/ - -#include -#include "ets_sys.h" -#include "eagle_soc.h" -#include "c_types.h" -#include "gpio.h" -#include "xtensa/corebits.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 - // The following 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 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) { - uint32_t pos = 0; - os_printf("\nStack dump:\n"); - os_printf("To decode the stack dump call from command line:\n python $SMING_HOME/../tools/decode-stacktrace.py out/build/app.out\n"); - os_printf("and copy & paste the text enclosed in '===='.\n"); - os_printf("================================================================\n"); - for (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("%08x: %08x %08x %08x %08x %c\n", - pos, values[0], values[1], values[2], values[3], (looksLikeStackFrame)?'<':' '); - } - os_printf("\n"); - os_printf("================================================================\n"); - os_printf("To decode the stack dump call from command line:\n python $SMING_HOME/../tools/decode-stacktrace.py out/build/app.out\n"); - os_printf("and copy & paste the text enclosed in '===='.\n"); -} - -void _xtos_set_exception_handler(int cause, void (exhandler)(struct XTensa_exception_frame_s *frame)); - -// Print exception info to console -static void printReason() { - int i=0; - //register uint32_t sp asm("a1"); - struct XTensa_exception_frame_s *reg = &gdbstub_savedRegs; - os_printf("\n\n***** Fatal exception %u\n", reg->reason); - os_printf("pc=0x%08x sp=0x%08x excvaddr=0x%08x\n", reg->pc, reg->a1, reg->excvaddr); - os_printf("ps=0x%08x sar=0x%08x vpri=0x%08x\n", reg->ps, reg->sar, reg->vpri); - for (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(getaregval(1), 0x3fffffb0); -} - -extern void ets_wdt_disable(); -extern void ets_wdt_enable(); - -// Non-OS exception handler. Gets called by the Xtensa HAL. -static void gdb_exception_handler(struct XTensa_exception_frame_s *frame) { - //Save the extra registers the Xtensa HAL doesn't save - extern void gdbstub_save_extra_sfrs_for_exception(); - 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; - - ets_wdt_disable(); - 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. -void gdbstub_init() { - 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); - } -} - -//extern void gdb_init() __attribute__((weak, alias("gdbstub_init"))); diff --git a/Sming/gdb/gdbstub.cpp b/Sming/gdb/gdbstub.cpp new file mode 100644 index 0000000000..5b09f916a9 --- /dev/null +++ b/Sming/gdb/gdbstub.cpp @@ -0,0 +1,898 @@ +/****************************************************************************** + * Copyright 2015 Espressif Systems + * + * Description: A stub to make the ESP8266 debuggable by GDB over the serial port. + * + * License: ESPRESSIF MIT License + * + * @author mikee47 + * + * Sming doesn't require recompiling to work with GDB + * GDB may be used for debug or release builds + * If enabled, stub code is compiled into user application not into Sming library + * Uses UART driver rather than accessing hardware directly. + * GDB stub uses UART0 (tx/rx required) exclusively + * Applications may use UART2, which is virtualised (using a generic callback mechanism) so + * output may sent to debug console when GDB is attached. + * +*********************************************************************************/ + +#include +#include + +#include "gdbstub.h" +#include "gdbstub-entry.h" +#include "gdb_hooks.h" +#include "GdbPacket.h" +#include "BitManipulations.h" +#include "exceptions.h" +#include "gdb/registers.h" + +extern "C" { +void system_restart_core(); +void Cache_Read_Enable_New(); +}; + +/* Bit numbers for `cause` field for debug exceptions */ +enum DebugCause { + DBGCAUSE_ICOUNT = DEBUGCAUSE_ICOUNT_SHIFT, + DBGCAUSE_IBREAK = DEBUGCAUSE_IBREAK_SHIFT, // Instruction breakpoint (hardware) + DBGCAUSE_DBREAK = DEBUGCAUSE_DBREAK_SHIFT, // Data breakpoint (hardware Watchpoint) + DBGCAUSE_BREAK = DEBUGCAUSE_BREAK_SHIFT, // Break instruction + DBGCAUSE_BREAKN = DEBUGCAUSE_BREAKN_SHIFT, // Break.N instruction (16-bit form of BREAK) + DBGCAUSE_DEBUG_INTERRUPT = DEBUGCAUSE_DEBUGINT_SHIFT, // Hardware debugger interrupt +}; + +// Register names +#define XX(num, name) static DEFINE_PSTR(xtreg_str_##name, #name) +XT_REGISTER_MAP(XX) +#undef XX +static PGM_P const registerNames[] PROGMEM = { +#define XX(num, name) xtreg_str_##name, + XT_REGISTER_MAP(XX) +#undef XX +}; + +extern GdbstubSavedRegisters gdbstub_savedRegs; + +/* Additional debugging flags */ +enum DebugFlag { + DBGFLAG_DEBUG_EXCEPTION, ///< For debug exceptions, cause is DBGCAUSE (see DebugCause bits) + DBGFLAG_SYSTEM_EXCEPTION, ///< For system exceptions, cause is EXCCAUSE (see EXCCAUSE_* values) + DBGFLAG_CTRL_BREAK, ///< Break caused by call to gdbstub_ctrl_break() +}; +static uint8_t debugFlags = 0; + +/* + * Maximum length of a GDB command packet. Has to be at least able to fit the G command. + */ +#define MAX_COMMAND_LENGTH (16 + (GDB_REGCOUNT * 2 * 4)) + +#if GDBSTUB_USE_OWN_STACK +// This is the debugging exception stack - referred to in gdbstub-entry.S +uint32_t exceptionStack[GDBSTUB_STACK_SIZE]; +#endif + +bool gdb_attached; ///< true if GDB is attached to stub +bool gdb_enabled; ///< Debugging may be disabled via gdb_enable() +static char commandBuffer[MAX_COMMAND_LENGTH + 1]; ///< Buffer for incoming/outgoing GDB commands +static int32_t singleStepPs = -1; // Stores ps (Program State) when single-stepping instruction. -1 when not in use. + +// Error states used by the routines that grab stuff from the incoming gdb packet +enum GdbResult { + ST_ENDPACKET = -1, + ST_ERR = -2, + ST_OK = -3, + ST_CONT = -4, + ST_DETACH = -5, +}; + +// For simplifying access to word-aligned data +union WordStruct { + uint32_t value; + uint8_t bytes[4]; +}; + +/* + * @brief Read a byte from the ESP8266 memory + * @param addr Address to read from + * @retval uint8_t + * @note No check is performed for `addr`, use validRdAddr() before calling this function + */ +static uint8_t ATTR_GDBEXTERNFN readMemoryByte(uint32_t addr) +{ + WordStruct word; + word.value = *reinterpret_cast(addr & ~3); + return word.bytes[addr & 3]; +} + +/* + * @brief Write a byte to the ESP8266 memory + * @param addr Address to write to + * @param data + * @note No check is performed for `addr`, use validWrAddr() before calling this function + */ +static void ATTR_GDBEXTERNFN writeMemoryByte(uint32_t addr, uint8_t data) +{ + auto valuePtr = reinterpret_cast(addr & ~3); + WordStruct word; + word.value = *valuePtr; + word.bytes[addr & 3] = data; + *valuePtr = word.value; +} + +/* + * Return true if it makes sense to write to addr + */ +static bool ATTR_GDBEXTERNFN isValidWriteAddr(uint32_t addr) +{ + // See xtensa/config/core-isa.h for memory range information + return (addr >= 0x3ff00000 && addr < 0x40000000) || (addr >= 0x40100000 && addr < 0x40140000) || + (addr >= 0x60000000 && addr < 0x60002000); +} + +static bool ATTR_GDBEXTERNFN isValidWriteAddr(uint32_t startAddr, size_t length) +{ + return isValidWriteAddr(startAddr) && isValidWriteAddr(startAddr + length - 1); +} + +/* + * Return true if it makes sense to read from addr + */ +static bool ATTR_GDBEXTERNFN isValidReadAddr(uint32_t addr) +{ + // @todo Consider whether it's necessary to restrict read addresses at all + return addr >= 0x20000000 && addr < 0x60000000; +} + +static bool ATTR_GDBEXTERNFN isValidReadAddr(uint32_t startAddr, size_t length) +{ + return isValidReadAddr(startAddr) && isValidReadAddr(startAddr + length - 1); +} + +/* + * Send the reason execution is stopped to GDB. + * The code sent is a standard GDB signal number. See signals.def. + */ +static void ATTR_GDBEXTERNFN sendReason() +{ + GdbPacket packet; + packet.writeChar('T'); + + uint8_t signal; + auto cause = gdbstub_savedRegs.cause; + if(bitRead(debugFlags, DBGFLAG_SYSTEM_EXCEPTION)) { + // Convert exception code to a signal number + signal = (cause <= EXCCAUSE_MAX) ? pgm_read_byte(&gdb_exception_signals[cause]) : 0; + packet.writeHexByte(signal); + } else { + // Debugging exception + signal = bitRead(debugFlags, DBGFLAG_CTRL_BREAK) ? GDB_SIGNAL_INT : GDB_SIGNAL_TRAP; + packet.writeHexByte(signal); +// Current Xtensa GDB versions don't seem to request this, so let's leave it off. +#if 0 + if(bitRead(cause, 0)) { + packet.writeStr(GDB_F("break")); // Single-step (ICOUNT hits 0) + } else if(bitRead(cause, DBGCAUSE_IBREAK)) { + packet.writeStr(GDB_F("hwbreak")); + } else if(bitRead(cause, DBGCAUSE_DBREAK)) { + packet.writeStr(GDB_F("watch:")); + // ToDo: send address + } else if(bitRead(cause, DBGCAUSE_BREAK)) { + packet.writeStr(GDB_F("swbreak")); + } else if(bitRead(cause, DBGCAUSE_BREAKN)) { + packet.writeStr(GDB_F("swbreak")); + } +#endif + } +} + +static void ATTR_GDBEXTERNFN sendOK() +{ + GdbPacket packet; + packet.write(GDB_F("OK"), 2); +} + +static void ATTR_GDBEXTERNFN sendError(uint8_t err) +{ + GdbPacket packet; + packet.writeChar('E'); + packet.writeHexByte(err); +} + +static void ATTR_GDBEXTERNFN sendReply(uint8_t err) +{ + if(err == 0) { + sendOK(); + } else { + sendError(err); + } +} + +static void ATTR_GDBEXTERNFN sendEmptyResponse() +{ + GdbPacket packet; +} + +#if GDBSTUB_CMDENABLE_P +static uint32_t* ATTR_GDBEXTERNFN getSavedReg(unsigned regnum) +{ + switch(regnum) { + case GdbReg_pc: + return &gdbstub_savedRegs.pc; + case GdbReg_sar: + return &gdbstub_savedRegs.sar; + case GdbReg_litbase: + return &gdbstub_savedRegs.litbase; + case GdbReg_sr176: + return &gdbstub_savedRegs.sr176; + case GdbReg_sr208: + return &gdbstub_savedRegs.sr208; + case GdbReg_ps: + return &gdbstub_savedRegs.ps; + default: + regnum -= GdbReg_a0; + if(regnum < 16) { + return &gdbstub_savedRegs.a[regnum]; + } else { + return nullptr; + } + } +} +#endif + +/* + * A command start character '$' has been detected, read the rest of the command packet. + * @retval unsigned length of command, 0 on failure + */ +static unsigned ATTR_GDBEXTERNFN readCommand() +{ + uint8_t checksum = 0; + size_t cmdLen = 0; + int c; + while((c = gdbReceiveChar()) != '#') { // end of packet, checksum follows + if(c < 0) { + gdbSendChar('-'); + debug_e("CMD TIMEOUT"); + return 0; + } + if(c == '$') { + // Wut, restart packet? + debug_e("Unexpected '$' received"); + checksum = 0; + cmdLen = 0; + continue; + } + if(c == '}') { // escape the next char + c = gdbReceiveChar() ^ 0x20; + } + if(cmdLen >= MAX_COMMAND_LENGTH) { + // Received more than the size of the command buffer + debug_e("Command '%c' buffer overflow", commandBuffer[0]); + return 0; + } + checksum += uint8_t(c); + commandBuffer[cmdLen++] = c; + } + commandBuffer[cmdLen] = '\0'; + +#if GDBSTUB_ENABLE_DEBUG + debug_i("cmd '%c', len %u", commandBuffer[0], cmdLen); +#endif + + // Read checksum and verify + char checksumChars[] = {char(gdbReceiveChar()), char(gdbReceiveChar()), '\0'}; + const char* ptr = checksumChars; + auto receivedChecksum = GdbPacket::readHexValue(ptr); + if(receivedChecksum == checksum) { + // Acknowledge the command + gdbSendChar('+'); + } else { + // Command received, but checksum failed + gdbSendChar('-'); + debug_e("Checksum mismatch, got 0x%02x, expected 0x%02x", receivedChecksum, checksum); + cmdLen = 0; + } + return cmdLen; +} + +/* + * Handle a command as received from GDB. + */ +static GdbResult ATTR_GDBEXTERNFN handleCommand(unsigned cmdLen) +{ + debug_hex(ERR, "CMD", commandBuffer, std::min(32U, cmdLen)); + + uint8_t commandChar = commandBuffer[0]; + const char* data = &commandBuffer[1]; + switch(commandChar) { + /* + * Send all registers to gdb + */ + case 'g': { + // Lay register values out as expected by GDB + memset(commandBuffer, 'x', GDB_REGCOUNT * 8); // All values undefined by default + auto setreg = [&](unsigned index, uint32_t value) { + GdbPacket::encodeHexBlock(&commandBuffer[index * 8], &value, sizeof(value)); + }; + for(int i = 0; i < 16; i++) { + setreg(GdbReg_a0 + i, gdbstub_savedRegs.a[i]); + } + setreg(GdbReg_pc, gdbstub_savedRegs.pc); + setreg(GdbReg_sar, gdbstub_savedRegs.sar); + setreg(GdbReg_litbase, gdbstub_savedRegs.litbase); + setreg(GdbReg_sr176, gdbstub_savedRegs.sr176); + setreg(GdbReg_sr208, gdbstub_savedRegs.sr208); + setreg(GdbReg_ps, gdbstub_savedRegs.ps); + GdbPacket packet; + packet.write(commandBuffer, GDB_REGCOUNT * 8); + break; + } + + /* + * Receive content for all registers from gdb + */ + case 'G': { + // Decode hex values into array of words + auto len = GdbPacket::decodeHexBlock(commandBuffer, data); + if(len != GDB_REGCOUNT * 4) { + debug_e("Invalid register block: got %u bytes, expected %u", len, GDB_REGCOUNT * 4); + sendError(EINVAL); + break; + } + // Store decoded values + auto values = reinterpret_cast(commandBuffer); + for(int i = 0; i < 16; i++) { + gdbstub_savedRegs.a[i] = values[GdbReg_a0 + i]; + } + gdbstub_savedRegs.pc = values[GdbReg_pc]; + gdbstub_savedRegs.sar = values[GdbReg_sar]; + gdbstub_savedRegs.litbase = values[GdbReg_litbase]; + gdbstub_savedRegs.sr176 = values[GdbReg_sr176]; + gdbstub_savedRegs.sr208 = values[GdbReg_sr208]; + gdbstub_savedRegs.ps = values[GdbReg_ps]; + sendOK(); + break; + } + +#if GDBSTUB_CMDENABLE_P + /* + * Read/write an individual register + * + * Read: `p n' + * Write: `P n=r` + * + * n is the register number in hex + * r is hex-encoded value in target byte order + * + * For read command, return data is hex-encoded content in target byte order + */ + case 'p': + case 'P': { + auto regnum = GdbPacket::readHexValue(data); + auto regPtr = getSavedReg(regnum); +#if GDBSTUB_ENABLE_DEBUG + char regName[16]; + memcpy_P(regName, registerNames[regnum], sizeof(regName)); +// debug_i("GET %s", name); +#endif + + if(commandChar == 'p') { // read + GdbPacket packet; +#if GDBSTUB_ENABLE_DEBUG + debug_i("GET %s", regName); +#endif + if(regPtr == nullptr) { + packet.writeX32(); + } else { + packet.writeHexBlock(regPtr, sizeof(uint32_t)); + } + } else { // write + data++; // skip = + uint32_t value = 0; + GdbPacket::decodeHexBlock(&value, data); +#if GDBSTUB_ENABLE_DEBUG + debug_i("SET %s = 0x%08x", regName, value); +#endif + if(regPtr == nullptr) { + sendError(EINVAL); + } else { + *regPtr = value; + sendOK(); + } + } + break; + } +#endif // GDBSTUB_CMDENABLE_P + + /* + * read/write memory to gdb + * + * Read: `m addr,length` + * Write: `M addr,length:XX...` + * + * `addr` is address in hex, MSB first + * `length` is number of bytes, in hex, MSB first + */ + case 'm': + case 'M': { + uint32_t addr = GdbPacket::readHexValue(data); + data++; // skip , + unsigned length = GdbPacket::readHexValue(data); + + if(commandChar == 'm') { // read memory to gdb + if(isValidReadAddr(addr, length)) { + GdbPacket packet; + for(unsigned i = 0; i < length; i++) { + packet.writeHexByte(readMemoryByte(addr)); + ++addr; + } + } else { + sendReply(EFAULT); + } + break; + } + + uint8_t err = 0; + + // write memory from gdb + if(isValidWriteAddr(addr, length)) { + data++; // skip : + auto bin = reinterpret_cast(commandBuffer); + auto len = GdbPacket::decodeHexBlock(bin, data); + if(len == length) { + for(unsigned i = 0; i < length; i++, addr++) { + writeMemoryByte(addr, *bin++); + } + // Make sure caches are up-to-date. Procedure according to Xtensa ISA document, ISYNC inst desc. + asm volatile("ISYNC\nISYNC\n"); + } + } else { + // Trying to do a software breakpoint on a flash proc, perhaps? + err = EFAULT; + } + + sendReply(err); + break; + } + + /* + * Indicate the reason the target halted. + */ + case '?': + sendReason(); + break; + + /* + * Continue execution + */ + case 'c': + return ST_CONT; + + /* + * 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. + */ + case 's': + singleStepPs = gdbstub_savedRegs.ps; + gdbstub_savedRegs.ps = (gdbstub_savedRegs.ps & ~0xf) | (XCHAL_DEBUGLEVEL - 1); + gdbstub_icount_ena_single_step(); + return ST_CONT; + + /* + * Detach from debugger and continue with application execution + */ + case 'D': + sendOK(); + return ST_DETACH; + + /* + * Kill + * + * For a bare-metal target we restart the system. No reply is expected. + */ + case 'k': + system_restart_core(); + break; + + /* + * Extended query + */ + case 'q': { + GdbPacket packet; + if(strncmp(data, GDB_F("Supported"), 9) == 0) { // Capabilities query + packet.writeStr(GDB_F("swbreak+;hwbreak+;PacketSize=")); + packet.writeHexWord16(MAX_COMMAND_LENGTH); + } else if(strncmp(data, GDB_F("Attached"), 8) == 0) { + // Let gdb know that it is attaching to a running program + // In general that just means it detaches instead of killing when it exits + packet.writeChar('1'); + } else { + // We don't support other queries, response will be empty + } + break; + } + + /* + * break/watchpoint + * + * Remove: `z#,addr,kind` + * Insert: `Z#,addr,kind` + * + * Where # is: + * 0 - software breakpoint (unsupported) + * 1 - hardware breakpoint + * 2 - write watchpoint + * 3 - read watchpoint + * 4 - access watchpoint + */ + case 'z': + case 'Z': { + char idx = *data; + if(idx < '1' || idx > '4') { + sendEmptyResponse(); + break; + } + + uint8_t err = 0; + data += 2; // skip 'x,' + int addr = GdbPacket::readHexValue(data); + data++; // skip ',' + int len = GdbPacket::readHexValue(data); + if(commandChar == 'Z') { // Set hardware break/watchpoint + if(idx == '1') { // breakpoint + if(!gdbstub_set_hw_breakpoint(addr, len)) { + err = EPERM; + } + } else { // watchpoint + int access; + unsigned int mask = 0; + if(idx == '2') + access = 2; // write + else if(idx == '3') + access = 1; // read + else if(idx == '4') + access = 3; // access + if(len == 1) + mask = 0x3F; + else if(len == 2) + mask = 0x3E; + else if(len == 4) + mask = 0x3C; + else if(len == 8) + mask = 0x38; + else if(len == 16) + mask = 0x30; + else if(len == 32) + mask = 0x20; + else + err = EINVAL; + + if(err == 0 && !gdbstub_set_hw_watchpoint(addr, mask, access)) { + err = EPERM; + } + } + } else { // Clear hardware break/watchpoint + bool ok; + if(idx == '1') { // breakpoint + ok = gdbstub_del_hw_breakpoint(addr); + } else { // watchpoint + ok = gdbstub_del_hw_watchpoint(addr); + } + if(!ok) { + err = EPERM; + } + } + sendReply(err); + break; + } + +#ifdef GDBSTUB_CMDENABLE_V + /* + * Packet starting with 'v' are identified by a multi-letter name, up to the first ':' or '?' (or end of packet) + */ + case 'v': + /* + * `vFile:operation:parameter...` + * + * @todo Consider whether this might be useful + */ + if(strncmp(data, GDB_F("File:"), 5) == 0) { + data += 5; + if(strncmp(data, GDB_F("open:"), 5) == 0) { + data += 5; + char* filename; + size_t len = GdbPacket::decodeHexBlock(data, filename); + filename[len] = '\0'; + debug_i("File:open('%s')", filename); + sendOK(); + break; + } + } + /* + * vFlashErase:addr,length + */ + else if(strncmp(data, GDB_F("FlashErase:"), 11) == 0) { + data += 11; + // @todo + } + /* + * vFlashWrite:addr:XX... + */ + else if(strncmp(data, GDB_F("FlashWrite:"), 11) == 0) { + data += 11; + // @todo + } + /* + * vFlashDone + * + * Indicates that programming operation is finished + */ + else if(strncmp(data, GDB_F("FlashDone:"), 10) == 0) { + data += 10; + // @todo + } + sendEmptyResponse(); + break; +#endif + +#ifdef GDBSTUB_CMDENABLE_X + /* + * Write data to memory, where the data is transmitted in binary, + * which is more efficient than hex packets (see 'M'). + * + * `X addr,length:XX...` + * + */ + case 'X': + // @todo + break; +#endif + + default: + // We don't recognize or support whatever GDB just sent us. + sendEmptyResponse(); + } + return ST_OK; +} + +/** + * @brief Wait for incoming commands and process them, only returning when instructed to do so by GDB + * @retval GdbResult cause of command loop exit + * @note Flags that gdb has been attached whenever a gdb-formatted packet is received + * Keeps reading commands until either a continue, detach, or kill command is received + * It is not necessary for gdb to be attached for it to be paused + * For example, during an exception break, the program is paused but gdb might not be attached yet +*/ +GdbResult ATTR_GDBEXTERNFN commandLoop() +{ + GdbResult result = ST_OK; + do { + while(gdbReceiveChar() != '$') { + // wait for start + } + auto cmdLen = readCommand(); + if(cmdLen != 0) { + gdb_attached = true; + result = handleCommand(cmdLen); + } + } while(result == ST_OK); + + if(result == ST_DETACH) { + gdb_attached = false; + } + + return result; +} + +/* + * Emulate the l32i/s32i instruction we're stopped at + */ +static void ATTR_GDBEXTERNFN emulLdSt() +{ + // Lower 16 bits of instruction + union { + uint8_t b[2]; + struct { + uint16_t op0 : 4; // Opcode + uint16_t t : 4; // Target 'A' register + uint16_t s : 4; // Source 'A' register + uint16_t r : 4; // Instruction dependent + }; + } inst = {readMemoryByte(gdbstub_savedRegs.pc), readMemoryByte(gdbstub_savedRegs.pc + 1)}; + +#if GDBSTUB_ENABLE_DEBUG >= 3 + debug_i("emulLdSt(%u, %u, %u, %u)", inst.op0, inst.t, inst.s, inst.r); +#endif + + auto& reg = gdbstub_savedRegs; + if(inst.op0 == 2 && (inst.r & 0x0b) == 2) { + // l32i or s32i + uint8_t imm8 = readMemoryByte(reg.pc + 2); + auto p = reinterpret_cast(reg.a[inst.s]) + imm8; // offset in words + if(inst.r == 2) { // l32i + reg.a[inst.t] = *p; + } else { // s32i + *p = reg.a[inst.t]; + } + reg.pc += 3; + } else if((inst.op0 & 0xe) == 8) { + // l32i.n or s32i.n + auto p = reinterpret_cast(reg.a[inst.s]) + inst.r; // offset in words + if(inst.op0 == 8) { // l32i.n +#if GDBSTUB_ENABLE_DEBUG >= 3 + debug_i("l32i.n A%u, A%u, %u (0x%08x)", inst.t, inst.s, inst.r, *p); +#endif + reg.a[inst.t] = *p; + } else { +#if GDBSTUB_ENABLE_DEBUG >= 3 + debug_i("s32i.n A%u, A%u, %u (0x%08x)", inst.t, inst.s, inst.r, reg.a[inst.t]); +#endif + *p = reg.a[inst.t]; + } + reg.pc += 2; + } else { + debug_e("GDBSTUB: No l32i/s32i instruction: (%u, %u, %u, %u)", inst.op0, inst.t, inst.s, inst.r); + } +} + +/** + * @brief If the hardware timer is operating using non-maskable interrupts, we must explicitly stop it + * @param pause true to stop the timer, false to resume it + */ +static void pauseHardwareTimer(bool pause) +{ +#if GDBSTUB_PAUSE_HARDWARE_TIMER + static bool edgeIntEnable; + if(pause) { + edgeIntEnable = bitRead(READ_PERI_REG(EDGE_INT_ENABLE_REG), 1); + TM1_EDGE_INT_DISABLE(); + } else if(edgeIntEnable) { + TM1_EDGE_INT_ENABLE(); + } +#endif +} + +// Main exception handler +static void __attribute__((noinline)) gdbstub_handle_debug_exception_flash() +{ + bool isEnabled = gdb_enabled; + + if(isEnabled) { + pauseHardwareTimer(true); + + bitSet(debugFlags, DBGFLAG_DEBUG_EXCEPTION); + + if(singleStepPs >= 0) { + // 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(); + commandLoop(); + } + + // Watchpoint ? + if(bitRead(gdbstub_savedRegs.cause, DBGCAUSE_DBREAK)) { + // 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(); + } + + // BREAK ? + else if(bitRead(gdbstub_savedRegs.cause, DBGCAUSE_BREAK)) { + // Skip instruction, first confirming it's actually a BREAK as GDB may have replaced it with + // the original instruction if it's one of the breakpoints it set. + if(readMemoryByte(gdbstub_savedRegs.pc + 2) == 0 && (readMemoryByte(gdbstub_savedRegs.pc + 1) & 0xf0) == 0x40 && + (readMemoryByte(gdbstub_savedRegs.pc) & 0x0f) == 0x00) { + gdbstub_savedRegs.pc += 3; + } + } + + // BREAK.N ? + else if(bitRead(gdbstub_savedRegs.cause, DBGCAUSE_BREAKN)) { + // Skip instruction, first confirming it is actually a BREAK.N + if((readMemoryByte(gdbstub_savedRegs.pc + 1) & 0xf0) == 0xf0 && readMemoryByte(gdbstub_savedRegs.pc) == 0x2d) { + gdbstub_savedRegs.pc += 3; + } + } + + debugFlags = 0; + + if(isEnabled) { + pauseHardwareTimer(false); + } +} + +// We just caught a debug exception and need to handle it. This is called from an assembly routine in gdbstub-entry.S +extern "C" void IRAM_ATTR gdbstub_handle_debug_exception() +{ + Cache_Read_Enable_New(); + gdbstub_handle_debug_exception_flash(); +} + +#if GDBSTUB_BREAK_ON_EXCEPTION +void gdbstub_handle_exception(UserFrame* frame) +{ + if(!gdb_enabled) { + return; + } + + pauseHardwareTimer(true); + bitSet(debugFlags, DBGFLAG_SYSTEM_EXCEPTION); + + // Copy registers the Xtensa HAL did save to gdbstub_savedRegs + memcpy(&gdbstub_savedRegs, frame, 5 * 4); + memcpy(&gdbstub_savedRegs.a[2], &frame->a2, 14 * 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. + const uint32_t EXCEPTION_GDB_SP_OFFSET = 0x100; + gdbstub_savedRegs.a[1] = uint32_t(frame) + EXCEPTION_GDB_SP_OFFSET; + + sendReason(); + while(commandLoop() != ST_CONT) { + } + + // Copy any changed registers back to the frame the Xtensa HAL uses. + memcpy(frame, &gdbstub_savedRegs, 5 * 4); + memcpy(&frame->a2, &gdbstub_savedRegs.a[2], 14 * 4); + + debugFlags = 0; + pauseHardwareTimer(false); +} +#endif + +// gdb_init() calls this after any exception handler initialisation +void ATTR_GDBINIT gdbstub_init() +{ + gdbstub_init_debug_entry(); + +#define SD(xx) debug_i(#xx " = %u", xx) + SD(ENABLE_EXCEPTION_DUMP); + SD(ENABLE_CRASH_DUMP); + SD(GDBSTUB_ENABLE_DEBUG); + SD(GDBSTUB_PAUSE_HARDWARE_TIMER); + SD(GDBSTUB_GDB_PATCHED); + SD(GDBSTUB_USE_OWN_STACK); + SD(GDBSTUB_BREAK_ON_EXCEPTION); + SD(GDBSTUB_CTRLC_BREAK); + SD(GDBSTUB_BREAK_ON_INIT); +#undef SD + + gdb_enabled = gdb_uart_init(); + +#if GDBSTUB_BREAK_ON_INIT + if(gdb_enabled) { + gdbstub_do_break(); + } +#endif +} + +/* Hook functions */ + +bool IRAM_ATTR gdb_present() +{ + return true; +} + +void gdb_enable(bool state) +{ + gdb_enabled = state; +} + +void IRAM_ATTR gdb_do_break() +{ + gdbstub_do_break(); +} + +void IRAM_ATTR gdbstub_ctrl_break() +{ + bitSet(debugFlags, DBGFLAG_CTRL_BREAK); + gdbstub_do_break(); +} diff --git a/Sming/gdb/gdbstub.h b/Sming/gdb/gdbstub.h new file mode 100644 index 0000000000..b104b20393 --- /dev/null +++ b/Sming/gdb/gdbstub.h @@ -0,0 +1,39 @@ +/**** + * Sming Framework Project - Open Source framework for high efficiency native ESP8266 development. + * Created 2015 by Skurydin Alexey + * http://github.com/SmingHub/Sming + * All files of the Sming Core are provided under the LGPL v3 license. + * + * gdbstub.h + * + * @author: 2019 - Mikee47 + * + ****/ + +#ifndef _GDB_GDBSTUB_H_ +#define _GDB_GDBSTUB_H_ + +#include "gdbstub-internal.h" + +#include + +// GDB_xx macro versions required to ensure no flash access if requested +#if GDBSTUB_FORCE_IRAM +#define GDB_F(str) str +#define GDB_PROGMEM +#else +#define GDB_F(str) _F(str) +#define GDB_PROGMEM PROGMEM +#endif + +#define gdbstub_do_break() asm("break 0,0") + +extern bool gdb_attached; +extern bool gdb_enabled; +extern const uint8_t gdb_exception_signals[]; + +void gdbstub_init(); +void gdbstub_handle_exception(UserFrame* frame); +void gdbstub_ctrl_break(); + +#endif /* _GDB_GDBSTUB_H_ */ diff --git a/Sming/gdb/gdbuart.cpp b/Sming/gdb/gdbuart.cpp new file mode 100644 index 0000000000..5861ecc228 --- /dev/null +++ b/Sming/gdb/gdbuart.cpp @@ -0,0 +1,372 @@ +/**** + * Sming Framework Project - Open Source framework for high efficiency native ESP8266 development. + * Created 2015 by Skurydin Alexey + * http://github.com/SmingHub/Sming + * All files of the Sming Core are provided under the LGPL v3 license. + * + * gdbuart.cpp + * + * @author: 2019 - Mikee47 + * + ****/ + +#include "gdbuart.h" +#include "GdbPacket.h" +#include "espinc/uart.h" +#include "SerialBuffer.h" +#include "Platform/System.h" +#include "HardwareSerial.h" +#include "HardwareTimer.h" + +#define GDB_UART UART0 // Only UART0 supports for debugging as RX/TX required + +static uart_t* gdb_uart; // Port debugger is attached to + +#if GDBSTUB_ENABLE_UART2 +static uart_t* user_uart; // If open, virtual port being used for user passthrough +static volatile bool userDataSending; // Transmit completion callback invoked on user uart +static volatile unsigned userPacketCount; // For discarding of acknowledgement characters +static bool sendUserDataQueued; // Ensures only one call to sendUserData() is queued at a time +#endif + +// Get number of characters in receive FIFO +__forceinline static uint8_t uart_rxfifo_count(uint8_t nr) +{ + return (USS(nr) >> USRXC) & 0x7f; +} + +// Get number of characters in transmit FIFO +__forceinline static uint8_t uart_txfifo_count(uint8_t nr) +{ + return (USS(nr) >> USTXC) & 0xff; +} + +// Get available free characters in transmit FIFO +__forceinline static uint8_t uart_txfifo_free(uint8_t nr) +{ + return UART_TX_FIFO_SIZE - uart_txfifo_count(nr) - 1; +} + +// Return true if transmit FIFO is full +__forceinline static bool uart_txfifo_full(uint8_t nr) +{ + return uart_txfifo_count(nr) >= (UART_TX_FIFO_SIZE - 1); +} + +/* + * Minimal version of uart_read_char() + */ +static int ATTR_GDBEXTERNFN gdb_uart_read_char() +{ + int c; + if(uart_rxfifo_count(GDB_UART) == 0) { + c = -1; + } else { + c = USF(GDB_UART) & 0xff; + } + return c; +} + +/* + * Minimal version of uart_write(). Blocks until all data queued. + */ +static size_t ATTR_GDBEXTERNFN gdb_uart_write(const void* data, size_t length) +{ + if(data == nullptr || length == 0) { + return 0; + } + + for(unsigned i = 0; i < length; ++i) { + while(uart_txfifo_full(GDB_UART)) { + // + } + USF(GDB_UART) = static_cast(data)[i]; + } + + // Enable TX FIFO EMPTY interrupt + bitSet(USIE(GDB_UART), UIFE); + + return length; +} + +/* + * Minimal version of uart_write_char(). Blocks until data queued. + */ +static size_t ATTR_GDBEXTERNFN gdb_uart_write_char(char c) +{ + while(uart_txfifo_full(GDB_UART)) { + // + } + USF(GDB_UART) = c; + + // Enable TX FIFO EMPTY interrupt + bitSet(USIE(GDB_UART), UIFE); + + return 1; +} + +/* + * Receive a char from the uart. Uses polling and feeds the watchdog. + */ +int ATTR_GDBEXTERNFN gdbReceiveChar() +{ +#if GDBSTUB_UART_READ_TIMEOUT + auto timeout = usToTimerTicks(GDBSTUB_UART_READ_TIMEOUT * 1000U); + auto startTicks = NOW(); +#define checkTimeout() (NOW() - startTicks >= timeout) +#else +#define checkTimeout() (false) +#endif + + do { + wdt_feed(); + system_soft_wdt_feed(); + int c = gdb_uart_read_char(); + if(c >= 0) { +#if GDBSTUB_ENABLE_DEBUG >= 3 + m_putc(c); +#endif + return c; + } + } while(!checkTimeout()); + + return -1; +} + +/* + * Send a block of data to the uart + */ +size_t ATTR_GDBEXTERNFN gdbSendData(const void* data, size_t length) +{ +#if GDBSTUB_ENABLE_DEBUG >= 3 + m_nputs(static_cast(data), length); +#endif + return gdb_uart_write(data, length); +} + +/* + * Send a char to the uart + */ +size_t ATTR_GDBEXTERNFN gdbSendChar(char c) +{ +#if GDBSTUB_ENABLE_DEBUG >= 3 + m_putc(c); +#endif + return gdb_uart_write_char(c); +} + +#if GDBSTUB_ENABLE_UART2 +/** + * @brief Send some user data from the user_uart TX buffer to the GDB serial port, + * packetising it if necessary. + * @note Data flows from user uart TX buffer to UART0 either during uart_write() call + * (via notify callback) or via task callback queued from ISR. We don't do this inside + * the ISR as all the code (including packetising) would need to be in IRAM. + */ +static void sendUserData() +{ + sendUserDataQueued = false; + + auto txbuf = user_uart == nullptr ? nullptr : user_uart->tx_buffer; + if(txbuf == nullptr) { + return; // Uart not open or tx not enabled + } + + void* data; + size_t avail; + while((avail = user_uart->tx_buffer->getReadData(data)) != 0) { + size_t charCount; + unsigned used = uart_txfifo_count(GDB_UART); + unsigned space = UART_TX_FIFO_SIZE - used - 1; + if(gdb_attached) { + // $Onn#CC is smallest packet, for a single character, but we want to avoid that as it's inefficient + if(used >= 8) { + break; + } + + charCount = std::min((space - 3) / 2, avail); + GdbPacket packet; + packet.writeChar('O'); + packet.writeHexBlock(data, charCount); + ETS_UART_INTR_DISABLE(); + ++userPacketCount; + ETS_UART_INTR_ENABLE(); + } else { + charCount = gdb_uart_write(data, std::min(space, avail)); + } + + userDataSending = true; + + user_uart->tx_buffer->skipRead(charCount); + if(charCount != avail) { + break; // That's all for now + } + } +} + +__forceinline void queueSendUserData() +{ + if(!sendUserDataQueued) { + System.queueCallback(TaskCallback(sendUserData)); + sendUserDataQueued = true; + } +} + +/** + * @brief Notify callback for user uart, called from uart driver + */ +static void userUartNotify(uart_t* uart, uart_notify_code_t code) +{ + switch(code) { + case UART_NOTIFY_AFTER_OPEN: + user_uart = uart; + break; + + case UART_NOTIFY_BEFORE_CLOSE: + user_uart = nullptr; + break; + + case UART_NOTIFY_AFTER_WRITE: { + /* + * Driver calls here from uart_write() after data has been written to tx buffer. + * Mostly we rely on task callback to transfer data from buffer to serial port, + * but we need to send some data directly if the buffer is full otherwise we'll + * loop indefinitely if UART_OPT_TXWAIT is set. + */ + if(uart->tx_buffer->isFull()) { + sendUserData(); + } else { + queueSendUserData(); + } + break; + } + + case UART_NOTIFY_BEFORE_READ: + break; + } +} +#endif + +static void IRAM_ATTR gdb_uart_callback(uart_t* uart, uint32_t status) +{ +#if GDBSTUB_ENABLE_UART2 + uint32_t user_status = status; + + // TX FIFO empty ? + if(bitRead(status, UIFE)) { + // Disable TX FIFO EMPTY interrupt to stop it recurring - re-enabled by uart_write() + if(uart_txfifo_count(GDB_UART) == 0) { + bitClear(USIE(GDB_UART), UIFE); + } + + auto txbuf = user_uart == nullptr ? nullptr : user_uart->tx_buffer; + if(txbuf != nullptr) { + // More user data to send? + if(!txbuf->isEmpty()) { + // Yes - send it via task callback (because packetising code may not be in IRAM) + queueSendUserData(); + bitClear(user_status, UIFE); + } else if(userDataSending) { + // User data has now all been sent - UIFE will remain set + userDataSending = false; + } else { + // No user data was in transit, so this event doesn't apply to user uart + bitClear(user_status, UIFE); + } + } + } +#endif + + // RX FIFO Full or RX FIFO Timeout ? + if(status & (_BV(UIFF) | _BV(UITO))) { +#if GDBSTUB_ENABLE_UART2 + bool isUserData = false; + auto rxbuf = user_uart == nullptr ? nullptr : user_uart->rx_buffer; +#endif + + while(uart_rxfifo_count(GDB_UART) != 0) { + char c = USF(GDB_UART); +#if GDBSTUB_CTRLC_BREAK + bool breakCheck = gdb_enabled; +#if GDBSTUB_CTRLC_BREAK == 1 + if(!gdb_attached) { + breakCheck = false; + } +#endif + if(breakCheck && c == '\x03') { + gdbstub_ctrl_break(); + continue; + } +#endif +#if GDBSTUB_ENABLE_UART2 + if(rxbuf != nullptr) { + if(userPacketCount != 0) { + --userPacketCount; // Discard acknowledgement '+' + } else { + if(rxbuf->writeChar(c) == 0) { + bitSet(user_status, UIOF); // Overflow + } + isUserData = true; + } + } +#endif + } + + // We cleared status flags above, but this one gets re-set almost immediately so clear it again now + USIC(GDB_UART) = _BV(UITO); + +#if GDBSTUB_ENABLE_UART2 + if(!isUserData) { + // This event doesn't apply to user uart + bitClear(user_status, UIFF); + bitClear(user_status, UITO); + } +#endif + } + +#if GDBSTUB_ENABLE_UART2 + if(user_status != 0 && user_uart != nullptr && user_uart->callback != nullptr) { + user_uart->callback(user_uart, user_status); + } +#endif +} + +bool ATTR_GDBINIT gdb_uart_init() +{ + uart_set_debug(UART_NO); + + // Additional buffering not supported because interrupts are disabled when debugger halted + uart_config cfg = {.uart_nr = GDB_UART, + .tx_pin = 1, + .mode = UART_FULL, + .options = _BV(UART_OPT_TXWAIT) | _BV(UART_OPT_CALLBACK_RAW), + .baudrate = SERIAL_BAUD_RATE, + .config = UART_8N1, + .rx_size = 0, + .tx_size = 0}; + gdb_uart = uart_init_ex(cfg); + if(gdb_uart == nullptr) { + return false; + } + uart_set_callback(gdb_uart, gdb_uart_callback, nullptr); + +#if GDBSTUB_ENABLE_UART2 + // Virtualise user serial access via UART2 + uart_set_notify(UART2, userUartNotify); + Serial.setPort(UART2); +#endif + +#if GDBSTUB_ENABLE_DEBUG + auto uart1 = uart_init(UART1, SERIAL_BAUD_RATE, UART_8N1, UART_TX_ONLY, 1, 0, 0); + if(uart1 != nullptr) { + uart_set_debug(UART1); + + using namespace std::placeholders; + m_setPuts(std::bind(&uart_write, uart1, _1, _2)); + } + + debug_i("\n\nGDB STUB RUNNING"); +#endif + + return true; +} diff --git a/Sming/gdb/gdbuart.h b/Sming/gdb/gdbuart.h new file mode 100644 index 0000000000..cb0e673c33 --- /dev/null +++ b/Sming/gdb/gdbuart.h @@ -0,0 +1,22 @@ +/**** + * Sming Framework Project - Open Source framework for high efficiency native ESP8266 development. + * Created 2015 by Skurydin Alexey + * http://github.com/SmingHub/Sming + * All files of the Sming Core are provided under the LGPL v3 license. + * + * gdbuart.h + * + ****/ + +#ifndef _GDB_GDBUART_H_ +#define _GDB_GDBUART_H_ + +#include "gdbstub.h" + +bool gdb_uart_init(); + +int gdbReceiveChar(); +size_t gdbSendData(const void* data, size_t length); +size_t gdbSendChar(char c); + +#endif /* _GDB_GDBUART_H_ */ diff --git a/Sming/gdb/readme.md b/Sming/gdb/readme.md new file mode 100644 index 0000000000..51bdc0a6ff --- /dev/null +++ b/Sming/gdb/readme.md @@ -0,0 +1,106 @@ +GDBSTUB for Sming +================= + +Background +---------- + +This is a rewrite of gdbstub based on the [esp8266 Arduino project]https://github.com/esp8266/Arduino/pull/5559. + +To use the GNU Debugger (GDB) with Sming requires your application to include some code (`gdbstub`) which communicates via the serial port. On the ESP8266 only UART0 may be used for this as UART1 is transmit-only. + +The gdbstub code will only be built if you specify `ENABLE_GDB=1` when compiling your application. At startup, before your init() function is called, it will claim UART0 so your application will be unable to use it directly. Therefore, the default port for `Serial` is changed to `UART2`. + +UART2 is a 'virtual' serial port to enable serial communications to work correctly when GDB-enabled. Read/write calls and serial callbacks are handled via gdbstub. Baud rate changes affect UART0 directly. + +Note that `target` refers to the application being debugged, and `host` the development system running the GDB program. + +GDB +--- + +This is the application which runs on your development system and talks to `gdbstub`. + + * Linux: A version of this should be available in $ESP_HOME/xtesnsa-lx106-elf/bin/xtensa-lx106-elf-gdb + + * Windows: At time of writing, UDK doesn't provide a GDB application. You can find pre-built version of this at [SysProgs][http://gnutoolchains.com/esp8266/]. Download and run the executable installer, then copy the `C:\SysGCC\esp8266\opt\xtensa-lx106-elf\bin\xtensa-lx106-elf-gdb.exe` to a suitable location. + + * Mac: ? + +Usage +----- + + * Configure gdbstub by editing `gdbstub-cfg.h` as required. You can also configure the options by setting `USER_CFLAGS` in your project's `Makefile-user.mk` file. e.g `USER_CFLAGS=-DGDBSTUB_BREAK_ON_INIT=0`. + * Optional: Add `gdb_do_break()` statements to your application. + * Run `make clean`, then `make ENABLE_GDB=1 flash` to build and flash the application with debugging enabled + * Run gdb, depending on your configuration immediately after resetting the board or after it has run into +an exception. The easiest way to do it is to use the provided script: xtensa-lx106-elf-gdb -x $SMING_HOME/gdb/gdbcmds -b 115200 +Change the '115200' into the baud rate your code uses. You may need to change the `gdbcmds` script to fit the +configuration of your hardware and build environment. + +Useful GDB commands +------------------- + +`c` Continue execution +`q` Quit and detach +`bt` Show stack backtrace +`disass` Disassemble +`disass/m` Disassemble, mix with source code +`print expr` Display a variable or other value +`print func()` Call a function, display result +`call func()` Call a function, discard result +`tui enable` Provides a windowed interface within the console (only seems to work in Linux) + +Eclipse +------- + +Windows: + + * Ensure `Use external console for inferior` is checked. + * In connection settings, specify COM port like with leading /, e.g. `/COM4` + +Problems connecting? + + * Switch to the debug perspective before connecting + * Ensure serial baud rate matches your application + * Remove or disable all breakpoints before attaching. Eclipse will attempt to set these on connection, and if any are invalid it will hang and timeout. + * Check connectivity using command-line GDB + +Known Issues and Limitations +---------------------------- + +Unable to set requested break/watch points +- Cause: Due to hardware limitations, only one hardware breakpount and one hardware watchpoint are available +- Solution: None (hardware limitation) + +System crashes if debugger is paused for too long +- Cause: The WiFi hardware is designed to be serviced by software periodically. It has some buffers so it will behave OK when some data comes in while the processor is busy, but these buffers are not infinite. If the WiFi hardware receives lots of data while the debugger has stopped the CPU, it is bound to crash. This will happen mostly when working with UDP and/or ICMP; TCP-connections in general will not send much more data when the other side doesn't send any ACKs. +- Solution: In such situations avoid pausing the debugger for extended periods + + +Software breakpoints/watchpoints ('break' and 'watch') don't work on flash code +- Cause: GDB handles these by replacing code with a debugging instruction, therefore the code must be in RAM. +- Solution: Use hardware breakpoint ('hbreak') or use GDB_IRAM_ATTR for code which requires testing + +If hardware breakpoint is set, single-stepping won't work unless code is in RAM. +- Cause: GDB reverts to software breakpoints if no hardware breakpoints are available +- Solution: Delete hardware breakpoint before single-stepping + + +Crash occurs when setting breakpoint in HardwareTimer callback routine +- Cause: By default, HardwareTimer uses Non-maskable Interrupts (NMI) which keep running when the debugger is paused +- Solution: Use the timer in non-maskable mode, or enable GDBSTUB_PAUSE_HARDWARE_TIMER option + +If gdbstub isn't initialised then UART2 won't work, though initialisation will succeed +- Cause: By design, uart callbacks can be registered for UART2 at any time, before or after initialisation +- Solution: Not really an issue, just something to be aware of + +Error reported, "packet reply is too long" +- Cause: Mismatch between GDB version and stub code +- Solution: Set `GDBSTUB_GDB_PATCHED=1` or use an unpatched version of GDB + +Whilst GDB is attached, input cannot be passed to application +- Cause: GDB buffers keystrokes and replays them only when the target is interrupted (e.g. via ctrl+C), rather than passing them via serial connection. +- Solution: Unclear. Console I/O is synchronous, so would stall application whilst waiting for input. + +No apparent way to have second 'console' (windows terminology) separate from GDB interface +- Cause: Unknown +- Solution: Is this possible with remote targets? diff --git a/Sming/gdb/todo.md b/Sming/gdb/todo.md new file mode 100644 index 0000000000..ef726a804b --- /dev/null +++ b/Sming/gdb/todo.md @@ -0,0 +1,19 @@ + +Console and File I/O +-------------------- + +This is a potential future enhancement to the gdbstub. See GDB manual for full details. + + "The File I/O remote protocol extension allows the target to use the host’s file system and console I/O to perform various system calls. System calls on the target system are translated into a remote protocol packet to the host system, which then performs the needed actions and returns a response packet to the target system. This simulates file system operations even on targets that lack fle systems." + +As an example, we might implement a function in gdbstub called `gdb_console_read`: + +``` +char buffer[32]; +int len = gdb_console_read(buffer, sizeof(buffer)); +m_printf("gdb_console_read() returned %d\n", len); +``` + +This pauses the user application whilst waiting for input from the GDB console. Hitting return ends the call and the data is stored in buffer. Note: it _may_ be possible to have the application continue executing while the system call is in progress. + +The same approach can be used to write to the console, to read/write host files or to make system calls. This could help speed application development by using files from the host, instead of a local file system (e.g. SPIFFS). diff --git a/Sming/gdb/xtensa/xtruntime-frames.h b/Sming/gdb/xtensa/xtruntime-frames.h new file mode 100644 index 0000000000..793a69f0ce --- /dev/null +++ b/Sming/gdb/xtensa/xtruntime-frames.h @@ -0,0 +1,160 @@ +/* xtruntime-frames.h - exception stack frames for single-threaded run-time */ +/* $Id: //depot/rel/Boreal/Xtensa/OS/include/xtensa/xtruntime-frames.h#2 $ */ + +/* + * Copyright (c) 2002-2007 Tensilica Inc. + * + * Permission is hereby granted, 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. + */ + +#ifndef _XTRUNTIME_FRAMES_H_ +#define _XTRUNTIME_FRAMES_H_ + +#include + +/* Macros that help define structures for both C and assembler: */ +#if defined(_ASMLANGUAGE) || defined(__ASSEMBLER__) +#define STRUCT_BEGIN .pushsection .text; .struct 0 +#define STRUCT_FIELD(ctype,size,pre,name) pre##name: .space size +#define STRUCT_AFIELD(ctype,size,pre,name,n) pre##name: .space (size)*(n) +#define STRUCT_END(sname) sname##Size:; .popsection +#else /*_ASMLANGUAGE||__ASSEMBLER__*/ +#define STRUCT_BEGIN typedef struct { +#define STRUCT_FIELD(ctype,size,pre,name) ctype name; +#define STRUCT_AFIELD(ctype,size,pre,name,n) ctype name[n]; +#define STRUCT_END(sname) } sname; +#endif /*_ASMLANGUAGE||__ASSEMBLER__*/ + + +/* + * Kernel vector mode exception stack frame. + * + * NOTE: due to the limited range of addi used in the current + * kernel exception vector, and the fact that historically + * the vector is limited to 12 bytes, the size of this + * stack frame is limited to 128 bytes (currently at 64). + */ +STRUCT_BEGIN +STRUCT_FIELD (long,4,KEXC_,pc) /* "parm" */ +STRUCT_FIELD (long,4,KEXC_,ps) +STRUCT_AFIELD(long,4,KEXC_,areg, 4) /* a12 .. a15 */ +STRUCT_FIELD (long,4,KEXC_,sar) /* "save" */ +#if XCHAL_HAVE_LOOPS +STRUCT_FIELD (long,4,KEXC_,lcount) +STRUCT_FIELD (long,4,KEXC_,lbeg) +STRUCT_FIELD (long,4,KEXC_,lend) +#endif +#if XCHAL_HAVE_MAC16 +STRUCT_FIELD (long,4,KEXC_,acclo) +STRUCT_FIELD (long,4,KEXC_,acchi) +STRUCT_AFIELD(long,4,KEXC_,mr, 4) +#endif +STRUCT_END(KernelFrame) + + +/* + * User vector mode exception stack frame: + * + * WARNING: if you modify this structure, you MUST modify the + * computation of the pad size (ALIGNPAD) accordingly. + */ +STRUCT_BEGIN +STRUCT_FIELD (long,4,UEXC_,pc) +STRUCT_FIELD (long,4,UEXC_,ps) +STRUCT_FIELD (long,4,UEXC_,sar) +STRUCT_FIELD (long,4,UEXC_,vpri) +#ifdef __XTENSA_CALL0_ABI__ +STRUCT_FIELD (long,4,UEXC_,a0) +#endif +STRUCT_FIELD (long,4,UEXC_,a2) +STRUCT_FIELD (long,4,UEXC_,a3) +STRUCT_FIELD (long,4,UEXC_,a4) +STRUCT_FIELD (long,4,UEXC_,a5) +#ifdef __XTENSA_CALL0_ABI__ +STRUCT_FIELD (long,4,UEXC_,a6) +STRUCT_FIELD (long,4,UEXC_,a7) +STRUCT_FIELD (long,4,UEXC_,a8) +STRUCT_FIELD (long,4,UEXC_,a9) +STRUCT_FIELD (long,4,UEXC_,a10) +STRUCT_FIELD (long,4,UEXC_,a11) +STRUCT_FIELD (long,4,UEXC_,a12) +STRUCT_FIELD (long,4,UEXC_,a13) +STRUCT_FIELD (long,4,UEXC_,a14) +STRUCT_FIELD (long,4,UEXC_,a15) +#endif +STRUCT_FIELD (long,4,UEXC_,exccause) /* NOTE: can probably rid of this one (pass direct) */ +#if XCHAL_HAVE_LOOPS +STRUCT_FIELD (long,4,UEXC_,lcount) +STRUCT_FIELD (long,4,UEXC_,lbeg) +STRUCT_FIELD (long,4,UEXC_,lend) +#endif +#if XCHAL_HAVE_MAC16 +STRUCT_FIELD (long,4,UEXC_,acclo) +STRUCT_FIELD (long,4,UEXC_,acchi) +STRUCT_AFIELD(long,4,UEXC_,mr, 4) +#endif +/* ALIGNPAD is the 16-byte alignment padding. */ +#ifdef __XTENSA_CALL0_ABI__ +# define CALL0_ABI 1 +#else +# define CALL0_ABI 0 +#endif +#define ALIGNPAD ((3 + XCHAL_HAVE_LOOPS*1 + XCHAL_HAVE_MAC16*2 + CALL0_ABI*1) & 3) +#if ALIGNPAD +STRUCT_AFIELD(long,4,UEXC_,pad, ALIGNPAD) /* 16-byte alignment padding */ +#endif +/*STRUCT_AFIELD(char,1,UEXC_,ureg, (XCHAL_CPEXTRA_SA_SIZE_TOR2+3)&-4)*/ /* not used, and doesn't take alignment into account */ +STRUCT_END(UserFrame) + + +#if defined(_ASMLANGUAGE) || defined(__ASSEMBLER__) + + +/* Check for UserFrameSize small enough not to require rounding...: */ + /* Skip 16-byte save area, then 32-byte space for 8 regs of call12 + * (which overlaps with 16-byte GCC nested func chaining area), + * then exception stack frame: */ + .set UserFrameTotalSize, 16+32+UserFrameSize + /* Greater than 112 bytes? (max range of ADDI, both signs, when aligned to 16 bytes): */ + .ifgt UserFrameTotalSize-112 + /* Round up to 256-byte multiple to accelerate immediate adds: */ + .set UserFrameTotalSize, ((UserFrameTotalSize+255) & 0xFFFFFF00) + .endif +# define ESF_TOTALSIZE UserFrameTotalSize + +#endif /* _ASMLANGUAGE || __ASSEMBLER__ */ + + +#if XCHAL_NUM_CONTEXTS > 1 +/* Structure of info stored on new context's stack for setup: */ +STRUCT_BEGIN +STRUCT_FIELD (long,4,INFO_,sp) +STRUCT_FIELD (long,4,INFO_,arg1) +STRUCT_FIELD (long,4,INFO_,funcpc) +STRUCT_FIELD (long,4,INFO_,prevps) +STRUCT_END(SetupInfo) +#endif + + +#define KERNELSTACKSIZE 1024 + + +#endif /* _XTRUNTIME_FRAMES_H_ */ + diff --git a/Sming/system/SerialBuffer.cpp b/Sming/system/SerialBuffer.cpp new file mode 100644 index 0000000000..10d95d2da0 --- /dev/null +++ b/Sming/system/SerialBuffer.cpp @@ -0,0 +1,66 @@ +/**** + * Sming Framework Project - Open Source framework for high efficiency native ESP8266 development. + * Created 2015 by Skurydin Alexey + * http://github.com/anakod/Sming + * All files of the Sming Core are provided under the LGPL v3 license. + * + * SerialBuffer.cpp + * + * @author 22 Aug 2018 - mikee47 + * + ****/ + +#include "SerialBuffer.h" + +/** @brief find a character in the buffer + * @param c + * @retval int position relative to current read pointer, -1 if character not found + */ +int SerialBuffer::find(uint8_t c) +{ + size_t offset = readPos; + size_t pos = 0; + size_t avail = available(); + while(pos < avail) { + if(buffer[offset + pos] == c) { + return pos; + } + + pos++; + if(pos + offset == writePos) { + break; + } + + if(pos + offset == size) { + offset = -pos; + } + } + + return -1; +} + +// Must be called with interrupts disabled +size_t SerialBuffer::resize(size_t newSize) +{ + if(size == newSize) { + return size; + } + + auto new_buf = new char[newSize]; + if(new_buf == nullptr) { + return size; + } + + size_t new_wpos = 0; + size_t avail = available(); + while(avail-- && new_wpos < newSize) { + new_buf[new_wpos++] = readChar(); + } + + delete[] buffer; + buffer = new_buf; + size = newSize; + readPos = 0; + writePos = new_wpos; + return size; +} diff --git a/Sming/system/crash_handler.c b/Sming/system/crash_handler.c index aeb3306a12..8b31fd3a6e 100644 --- a/Sming/system/crash_handler.c +++ b/Sming/system/crash_handler.c @@ -20,117 +20,54 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ - #include -#include "espinc/peri.h" +#include "gdb_hooks.h" extern void __real_system_restart_local(); -extern int ets_printf(const char*, ...); - -void uart_write_char_d(char c); -static void uart0_write_char_d(char c); -static void uart1_write_char_d(char c); -static void print_stack(uint32_t start, uint32_t end) SMING_UNUSED; -//static void print_pcs(uint32_t start, uint32_t end); - extern void __custom_crash_callback( struct rst_info * rst_info, uint32_t stack, uint32_t stack_end ) { } extern void custom_crash_callback( struct rst_info * rst_info, uint32_t stack, uint32_t stack_end ) __attribute__ ((weak, alias("__custom_crash_callback"))); void __wrap_system_restart_local() { - register uint32_t sp asm("a1"); + register uint32_t sp_reg asm("a1"); + uint32_t sp = sp_reg; struct rst_info rst_info = {0}; system_rtc_mem_read(0, &rst_info, sizeof(rst_info)); - if (rst_info.reason != REASON_SOFT_WDT_RST && - rst_info.reason != REASON_EXCEPTION_RST && - rst_info.reason != REASON_WDT_RST) - { - return; - } - -#ifndef SMING_RELEASE - ets_install_putc1(&uart_write_char_d); - - if (rst_info.reason == REASON_EXCEPTION_RST) { - os_printf("\n\n***** Exception Reset (%d):\nepc1=0x%08x epc2=0x%08x epc3=0x%08x excvaddr=0x%08x depc=0x%08x\n", - rst_info.exccause, rst_info.epc1, rst_info.epc2, rst_info.epc3, rst_info.excvaddr, rst_info.depc); - } - else if (rst_info.reason == REASON_SOFT_WDT_RST) { - os_printf("\n\n***** Software Watchdog Reset\n"); - } -#endif - - uint32_t stack_end; - // amount of stack taken by interrupt or exception handler // and everything up to __wrap_system_restart_local // (determined empirically, might break) uint32_t offset = 0; - if (rst_info.reason == REASON_SOFT_WDT_RST) { + switch(rst_info.reason) { + case REASON_SOFT_WDT_RST: offset = 0x1b0; - } - else if (rst_info.reason == REASON_EXCEPTION_RST) { + break; + case REASON_EXCEPTION_RST: offset = 0x1a0; - } - else if (rst_info.reason == REASON_WDT_RST) { + break; + case REASON_WDT_RST: offset = 0x10; + break; + case REASON_DEFAULT_RST: + case REASON_SOFT_RESTART: + case REASON_DEEP_SLEEP_AWAKE: + case REASON_EXT_SYS_RST: + default: + return; } - stack_end = 0x3fffffb0; + uint32_t stack_end = 0x3fffffb0; // it's actually 0x3ffffff0, but the stuff below ets_run // is likely not really relevant to the crash -#ifndef SMING_RELEASE - print_stack(sp + offset, stack_end); -#endif + debug_crash_callback(&rst_info, sp + offset, stack_end); - custom_crash_callback( &rst_info, sp + offset, stack_end ); + custom_crash_callback(&rst_info, sp + offset, stack_end); os_delay_us(10000); __real_system_restart_local(); } -static void print_stack(uint32_t start, uint32_t end) { - uint32_t pos = 0; - - PSTR_ARRAY(separatorLine, "\n================================================================\n"); - - os_printf_plus(separatorLine); - for (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("%08x: %08x %08x %08x %08x %c\n", - pos, values[0], values[1], values[2], values[3], (looksLikeStackFrame)?'<':' '); - } - os_printf_plus(separatorLine); -} - -void uart_write_char_d(char c) { - uart0_write_char_d(c); - uart1_write_char_d(c); -} - -static void uart0_write_char_d(char c) { - while (((USS(0) >> USTXC) & 0xff)) { } - - if (c == '\n') { - USF(0) = '\r'; - } - USF(0) = c; -} - -static void uart1_write_char_d(char c) { - while (((USS(1) >> USTXC) & 0xff) >= 0x7e) { } - - if (c == '\n') { - USF(1) = '\r'; - } - USF(1) = c; -} diff --git a/Sming/system/include/SerialBuffer.h b/Sming/system/include/SerialBuffer.h index 36e4aae775..689b7587d3 100644 --- a/Sming/system/include/SerialBuffer.h +++ b/Sming/system/include/SerialBuffer.h @@ -13,10 +13,12 @@ #ifndef _SYSTEM_INCLUDE_SERIAL_BUFFER_H_ #define _SYSTEM_INCLUDE_SERIAL_BUFFER_H_ +#include + /** @brief FIFO buffer used for both receive and transmit data * @note For receive operations, data is written via ISR and read via task * For transmit operations, data is written via task and read via ISR - * Only call routines marked with __forceinline or IRAM_ATTR from an interrupt context. + * Only routines marked with __forceinline or IRAM_ATTR may be called from interrupt context. */ struct SerialBuffer { public: @@ -36,8 +38,9 @@ struct SerialBuffer { __forceinline size_t available() { int ret = writePos - readPos; - if(ret < 0) + if(ret < 0) { ret += size; + } return ret; } @@ -46,15 +49,24 @@ struct SerialBuffer { */ __forceinline size_t getFreeSpace() { + if(buffer == nullptr) { + return 0; + } int ret = readPos - writePos - 1; - if(ret < 0) + if(ret < 0) { ret += size; + } return ret; } __forceinline bool isEmpty() { - return writePos == readPos; + return (buffer == nullptr) || (writePos == readPos); + } + + __forceinline bool isFull() + { + return getFreeSpace() == 0; } /** @brief see if there's anything in the buffer @@ -62,10 +74,7 @@ struct SerialBuffer { */ __forceinline int peekChar() { - if(!buffer || isEmpty()) - return -1; - - return buffer[readPos]; + return isEmpty() ? -1 : buffer[readPos]; } /* @@ -73,16 +82,14 @@ struct SerialBuffer { */ __forceinline int peekLastChar() { - if(!buffer || isEmpty()) - return -1; - - return buffer[getPrevPos(writePos)]; + return isEmpty() ? -1 : buffer[getPrevPos(writePos)]; } __forceinline int readChar() { - if(!buffer || isEmpty()) + if(isEmpty()) { return -1; + } uint8_t c = buffer[readPos]; readPos = getNextPos(readPos); @@ -92,8 +99,10 @@ struct SerialBuffer { __forceinline size_t writeChar(uint8_t c) { size_t nextPos = getNextPos(writePos); - if(nextPos == readPos) + if(nextPos == readPos) { return 0; + } + buffer[writePos] = c; writePos = nextPos; return 1; @@ -103,52 +112,37 @@ struct SerialBuffer { * @param c * @retval int position relative to current read pointer, -1 if character not found */ - int find(uint8_t c) - { - size_t offset = readPos; - size_t pos = 0; - size_t avail = available(); - while(pos < avail) { - if(buffer[offset + pos] == c) - return pos; - - pos++; - if(pos + offset == writePos) - break; - - if(pos + offset == size) - offset = -pos; - } + int find(uint8_t c); + + // Must be called with interrupts disabled + size_t resize(size_t newSize); - return -1; + void clear() + { + readPos = writePos = 0; } - // Must be called with interrupts disabled - size_t resize(size_t newSize) + /** @brief Access data directly within buffer + * @param void*& OUT: the data + * @retval size_t number of chars available + */ + __forceinline size_t getReadData(void*& data) { - if(size == newSize) - return size; - - uint8_t* new_buf = new uint8_t[newSize]; - if(!new_buf) - return size; - - size_t new_wpos = 0; - size_t avail = available(); - while(avail-- && new_wpos < newSize) - new_buf[new_wpos++] = readChar(); - - delete[] buffer; - buffer = new_buf; - size = newSize; - readPos = 0; - writePos = new_wpos; - return size; + data = buffer + readPos; + auto wp = writePos; // Guard against ISR changing value + return (wp < readPos) ? size - readPos : wp - readPos; } - void clear() + /** @brief Skip a number of chars starting at the given read position + * @param length MUST be <= value returned from peek() + * @note Provided for efficient buffer access + */ + __forceinline void skipRead(size_t length) { - readPos = writePos = 0; + readPos += length; + if(readPos == size) { + readPos = 0; + } } private: @@ -169,7 +163,7 @@ struct SerialBuffer { size_t size = 0; size_t readPos = 0; size_t writePos = 0; - uint8_t* buffer = nullptr; + char* buffer = nullptr; }; #endif // _SYSTEM_INCLUDE_SERIAL_BUFFER_H_ diff --git a/Sming/system/include/esp_systemapi.h b/Sming/system/include/esp_systemapi.h index e65e2ad526..061a728b95 100644 --- a/Sming/system/include/esp_systemapi.h +++ b/Sming/system/include/esp_systemapi.h @@ -65,16 +65,15 @@ } while(0) #define SYSTEM_ERROR(fmt, ...) debug_e("ERROR: " fmt "\r\n", ##__VA_ARGS__) +extern void ets_wdt_enable(void); +extern void ets_wdt_disable(void); +extern void wdt_feed(void); + #ifndef SDK_INTERNAL extern void ets_timer_arm_new(ETSTimer *ptimer, uint32_t milliseconds, bool repeat_flag, int isMstimer); extern void ets_timer_disarm(ETSTimer *a); extern void ets_timer_setfn(ETSTimer *t, ETSTimerFunc *pfunction, void *parg); -//extern void ets_wdt_init(uint32_t val); // signature? -extern void ets_wdt_enable(void); -extern void ets_wdt_disable(void); -extern void wdt_feed(void); -//extern void wd_reset_cnt(void); extern void ets_delay_us(uint32_t us); extern void ets_isr_mask(unsigned intr); @@ -82,21 +81,17 @@ extern void ets_isr_unmask(unsigned intr); typedef void (* ets_isr_t)(void *); -//extern void ets_isr_attach(int intr, void *handler, void *arg); extern void ets_isr_attach(int i, ets_isr_t func, void *arg); extern int ets_memcmp(const void *s1, const void *s2, size_t n); extern void *ets_memcpy(void *dest, const void *src, size_t n); extern void *ets_memset(void *s, int c, size_t n); -//extern void ets_install_putc1(void *routine); extern void ets_install_putc1(void (*p)(char c)); extern int ets_sprintf(char *str, const char *format, ...) __attribute__ ((format (printf, 2, 3))); extern int ets_str2macaddr(void *, void *); extern int ets_strcmp(const char *s1, const char *s2); extern char *ets_strcpy(char *dest, const char *src); -//extern int os_random(); -//extern char *ets_strdup(const char *str); // :( const char * ets_strrchr(const char *str, int character); extern int ets_strlen(const char *s); extern int ets_strncmp(const char *s1, const char *s2, unsigned int len); @@ -112,10 +107,6 @@ extern void pvPortFree(void *ptr); extern void vPortFree(void *ptr, const char *file, uint32 line); extern void *vPortMalloc(size_t xWantedSize); -extern void uart_div_modify(uint8 uart_no, uint32 DivLatchValue); -extern int ets_uart_printf(const char *fmt, ...); -extern void uart_tx_one_char(char ch); - extern void ets_intr_lock(); extern void ets_intr_unlock(); @@ -131,7 +122,6 @@ extern uint32_t ets_get_cpu_frequency(); extern void xt_disable_interrupts(); extern void xt_enable_interrupts(); -extern void uart_tx_one_char(char ch); extern void ets_isr_mask(unsigned intr); extern void ets_isr_unmask(unsigned intr); diff --git a/Sming/system/include/espinc/uart.h b/Sming/system/include/espinc/uart.h index e8b13443e2..e1ed4fb91a 100644 --- a/Sming/system/include/espinc/uart.h +++ b/Sming/system/include/espinc/uart.h @@ -30,23 +30,25 @@ * Code is now C++ only; reference to this header has been removed from esp_systemapi.h uart structure should be * treated as opaque and only accessed using the functions defined in this header. * Callback is invoked on transmit completion. + * + * Note: uart_detach_all() should be called at startup, i.e. from user_init(). */ #ifndef ESP_UART_H #define ESP_UART_H -#include -#include - #if defined (__cplusplus) extern "C" { #endif +#include + #define UART0 0 #define UART1 1 -#define UART_NO -1 ///< No UART specified -#define UART_COUNT 2 ///< Number of UARTs on the system - +#define UART2 2 ///< Virtualised UART0 +#define UART_NO -1 ///< No UART specified +#define UART_PHYSICAL_COUNT 2 ///< Number of physical UARTs on the system +#define UART_COUNT 3 ///< Number of UARTs on the system, virtual or otherwise // Options for `config` argument of uart_init #define UART_NB_BIT_MASK 0B00001100 @@ -104,7 +106,8 @@ typedef uint8_t uart_options_t; * @note use _BV(opt) to specify values */ enum uart_option_bits_t { - UART_OPT_TXWAIT, ///< If buffers are full then uart_write() will wait for free space + UART_OPT_TXWAIT, ///< If buffers are full then uart_write() will wait for free space + UART_OPT_CALLBACK_RAW, ///< ISR invokes user callback function with no pre-processing }; #define UART_RX_FIFO_SIZE 0x80 @@ -120,6 +123,41 @@ typedef struct uart_ uart_t; */ typedef void (*uart_callback_t)(uart_t* uart, uint32_t status); + +/* + * Port notifications + */ + +/** @brief Indicates notification, parameters refer to uart_notify_info_t structure + */ +enum uart_notify_code_t { + /** @brief Called when uart has been iniitialised successfully */ + UART_NOTIFY_AFTER_OPEN, + + /** @brief Called immediately before uart is closed and destroyed */ + UART_NOTIFY_BEFORE_CLOSE, + + /** @brief Called after data has been written into tx buffer */ + UART_NOTIFY_AFTER_WRITE, + + /** @brief Called before data is read from rx buffer */ + UART_NOTIFY_BEFORE_READ, +}; + +/** @brief Port notification callback function type + * @param info + * @retval bool true if callback handled operation, false to default to normal operation + */ +typedef void (*uart_notify_callback_t)(uart_t* uart, uart_notify_code_t code); + +/** @brief Set the notification callback function + * @param uart_nr Which uart to register notifications for + * @param callback + * @retval bool true on success + */ +bool uart_set_notify(unsigned uart_nr, uart_notify_callback_t callback); + + struct SerialBuffer; struct uart_ { @@ -147,7 +185,7 @@ struct uart_config { size_t tx_size; }; -// @deprecated for legacy support +// @deprecated Use `uart_init_ex()` instead uart_t* uart_init(uint8_t uart_nr, uint32_t baudrate, uint32_t config, uart_mode_t mode, uint8_t tx_pin, size_t rx_size, size_t tx_size = 0); uart_t* uart_init_ex(const uart_config& cfg); @@ -163,14 +201,14 @@ __forceinline int uart_get_nr(uart_t* uart) * @param uart_nr * @retval uart_t* Returns nullptr if uart isn't initialised */ -uart_t* IRAM_ATTR uart_get_uart(uint8_t uart_nr); +uart_t* uart_get_uart(uint8_t uart_nr); /** @brief Set callback handler for serial port * @param uart * @param callback specify nullptr to disable callbacks * @param param user parameter passed to callback */ -void IRAM_ATTR uart_set_callback(uart_t* uart, uart_callback_t callback, void* param); +void uart_set_callback(uart_t* uart, uart_callback_t callback, void* param); /** @brief Get the callback parameter specified by uart_set_callback() * @param uart @@ -229,10 +267,7 @@ uint32_t uart_set_baudrate(uart_t* uart, uint32_t baud_rate); * @param uart * @retval uint32_t the baud rate, 0 on failure */ -static inline uint32_t uart_get_baudrate(uart_t* uart) -{ - return uart ? uart->baud_rate : 0; -} +uint32_t uart_get_baudrate(uart_t* uart); size_t uart_resize_rx_buffer(uart_t* uart, size_t new_size); size_t uart_rx_buffer_size(uart_t* uart); @@ -290,7 +325,7 @@ int uart_peek_char(uart_t* uart); * @note this is only useful if an rx buffer has been allocated of sufficient size * to contain a message. This function then indicates the terminating character. */ -int IRAM_ATTR uart_peek_last_char(uart_t* uart); +int uart_peek_last_char(uart_t* uart); /* * @brief Find a character in the receive buffer @@ -305,7 +340,7 @@ int uart_rx_find(uart_t* uart, char c); * @retval size_t * @note this obtains a count of data both in the memory buffer and hardware FIFO */ -size_t IRAM_ATTR uart_rx_available(uart_t* uart); +size_t uart_rx_available(uart_t* uart); /** @brief return free space in transmit buffer */ size_t uart_tx_free(uart_t* uart); @@ -330,12 +365,18 @@ void uart_start_isr(uart_t* uart); /** @brief disable interrupts for a UART * @param uart */ -void IRAM_ATTR uart_stop_isr(uart_t* uart); +void __forceinline uart_stop_isr(uart_t* uart) +{ + extern void uart_detach(int); + if (uart != nullptr) { + uart_detach(uart->uart_nr); + } +} /** @brief detach a UART interrupt service routine * @param uart_nr */ -void IRAM_ATTR uart_detach(int uart_nr); +void uart_detach(int uart_nr); /** @brief detach all UART interrupt service routines * @note call at startup to put all UARTs into a known state diff --git a/Sming/system/include/gdb_hooks.h b/Sming/system/include/gdb_hooks.h new file mode 100644 index 0000000000..022cdbed13 --- /dev/null +++ b/Sming/system/include/gdb_hooks.h @@ -0,0 +1,76 @@ +/* + gdb_hooks.h - Hooks for GDB Stub library + Copyright (c) 2018 Ivan Grokhotkov. All right reserved. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#ifndef _GDB_HOOKS_H_ +#define _GDB_HOOKS_H_ + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Initialise GDB stub, if present + * @note Called by framework at startup, but does nothing if gdbstub is not linked. + */ +void gdb_init(void); + +/** + * @brief Dynamically enable/disable GDB stub + * @param state true to enable, false to disable + * @note has no effect if gdbstub is not present + * + * Calling with `enable = false` overrides configured behaviour as follows: + * + * - Debug exceptions will be silently ignored. + * - System exceptions will cause a watchdog reset, as for `GDBSTUB_BREAK_ON_EXCEPTION = 0`. + * - All incoming serial data is passed through to UART2 without inspection, + * as for `GDBSTUB_CTRLC_BREAK = 0` and `GDBSTUB_FREE_ATTACH = 0`. + */ +void gdb_enable(bool state); + +/** + * @brief Break into GDB, if present + * @note When present, gdbstub triggers entry into the debugger, which looks like a breakpoint hit. + */ +void gdb_do_break(void); + +/** + * @brief Check if GDB stub is present + */ +bool gdb_present(void); + +/** + * @brief Called on unexpected system reset + */ +void debug_crash_callback(const struct rst_info* rst_info, uint32_t stack, uint32_t stack_end); + +/** + * @brief Send a stack dump to debug output + * @param start + * @param end + */ +void debug_print_stack(uint32_t start, uint32_t end); + +#ifdef __cplusplus +} +#endif + +#endif /* _GDB_HOOKS_H_ */ diff --git a/Sming/system/uart.cpp b/Sming/system/uart.cpp index 9634d303ad..3afc72fc45 100644 --- a/Sming/system/uart.cpp +++ b/Sming/system/uart.cpp @@ -24,7 +24,6 @@ */ - /** * UART GPIOs * @@ -52,25 +51,58 @@ #include "SerialBuffer.h" -static int s_uart_debug_nr = UART0; +static int s_uart_debug_nr = UART_NO; // Get number of characters in receive FIFO -#define UART_RXCOUNT(nr) ((USS(nr) >> USRXC) & 0x7f) +__forceinline static uint8_t uart_rxfifo_count(uint8_t nr) +{ + return (USS(nr) >> USRXC) & 0x7f; +} // Get number of characters in transmit FIFO -#define UART_TXCOUNT(nr) (USS(nr) >> USTXC) +__forceinline static uint8_t uart_txfifo_count(uint8_t nr) +{ + return (USS(nr) >> USTXC) & 0xff; +} + +// Get available free characters in transmit FIFO +__forceinline static uint8_t uart_txfifo_free(uint8_t nr) +{ + return UART_TX_FIFO_SIZE - uart_txfifo_count(nr) - 1; +} // Return true if transmit FIFO is full -#define UART_TXFULL(nr) (UART_TXCOUNT(nr) >= 0x7f) +__forceinline static bool uart_txfifo_full(uint8_t nr) +{ + return uart_txfifo_count(nr) >= (UART_TX_FIFO_SIZE - 1); +} // Keep track of interrupt enable state for each UART static uint8_t isrMask; // Keep a reference to all created UARTS - required because they share an ISR static uart_t* uartInstances[UART_COUNT]; -#define UART_ISR_ENABLED(nr) (isrMask & _BV(nr)) +// Registered port callback functions +static uart_notify_callback_t notifyCallbacks[UART_COUNT]; -uart_t* IRAM_ATTR uart_get_uart(uint8_t uart_nr) +/** @brief Invoke a port callback, if one has been registered + * @param uart + * @param code + */ +static void notify(uart_t* uart, uart_notify_code_t code) +{ + auto callback = notifyCallbacks[uart->uart_nr]; + if(callback != nullptr) { + callback(uart, code); + } +} + +__forceinline static bool uart_isr_enabled(uint8_t nr) +{ + return bitRead(isrMask, nr); +} + +uart_t* uart_get_uart(uint8_t uart_nr) { return (uart_nr < UART_COUNT) ? uartInstances[uart_nr] : nullptr; } @@ -88,24 +120,56 @@ __forceinline static uint8_t uart_disable_interrupts() */ __forceinline static void uart_restore_interrupts() { - if(isrMask) + if(isrMask != 0) { ETS_UART_INTR_ENABLE(); + } } -void IRAM_ATTR uart_set_callback(uart_t* uart, uart_callback_t callback, void* param) +bool uart_set_notify(unsigned uart_nr, uart_notify_callback_t callback) { - if(uart) { - uart_disable_interrupts(); - uart->callback = callback; + if(uart_nr >= UART_COUNT) { + return false; + } + + notifyCallbacks[uart_nr] = callback; + return true; +} + +/** @brief Determine if the given uart is a real uart or a virtual one + */ +static __forceinline bool is_physical(int uart_nr) +{ + return (uart_nr >= 0) && (uart_nr < UART_PHYSICAL_COUNT); +} + +static __forceinline bool is_physical(uart_t* uart) +{ + return uart != nullptr && is_physical(uart->uart_nr); +} + +/** @brief If given a virtual uart, obtain the related physical one + */ +static uart_t* get_physical(uart_t* uart) +{ + if(uart != nullptr && uart->uart_nr == UART2) { + uart = uartInstances[UART0]; + } + return uart; +} + +void uart_set_callback(uart_t* uart, uart_callback_t callback, void* param) +{ + if(uart != nullptr) { + uart->callback = nullptr; // In case interrupt fires between setting param and callback uart->param = param; - uart_restore_interrupts(); + uart->callback = callback; } } static bool realloc_buffer(SerialBuffer*& buffer, size_t new_size) { - if(buffer) { - if (new_size == 0) { + if(buffer != nullptr) { + if(new_size == 0) { uart_disable_interrupts(); delete buffer; buffer = nullptr; @@ -116,11 +180,12 @@ static bool realloc_buffer(SerialBuffer*& buffer, size_t new_size) return buffer->resize(new_size) == new_size; } - if (new_size == 0) + if(new_size == 0) { return true; + } auto new_buf = new SerialBuffer; - if(new_buf && new_buf->resize(new_size)) { + if(new_buf != nullptr && new_buf->resize(new_size) == new_size) { buffer = new_buf; return true; } @@ -131,8 +196,9 @@ static bool realloc_buffer(SerialBuffer*& buffer, size_t new_size) size_t uart_resize_rx_buffer(uart_t* uart, size_t new_size) { - if (uart_rx_enabled(uart)) + if(uart_rx_enabled(uart)) { realloc_buffer(uart->rx_buffer, new_size); + } return uart_rx_buffer_size(uart); } @@ -143,71 +209,85 @@ size_t uart_rx_buffer_size(uart_t* uart) size_t uart_resize_tx_buffer(uart_t* uart, size_t new_size) { - if (uart_tx_enabled(uart)) + if(uart_tx_enabled(uart)) { realloc_buffer(uart->tx_buffer, new_size); + } return uart_tx_buffer_size(uart); } size_t uart_tx_buffer_size(uart_t* uart) { - return uart && uart->tx_buffer ? uart->tx_buffer->getSize() : 0; + return uart != nullptr && uart->tx_buffer != nullptr ? uart->tx_buffer->getSize() : 0; } int uart_peek_char(uart_t* uart) { - return uart && uart->rx_buffer ? uart->rx_buffer->peekChar() : -1; + return uart != nullptr && uart->rx_buffer ? uart->rx_buffer->peekChar() : -1; } int uart_rx_find(uart_t* uart, char c) { - if (!uart || !uart->rx_buffer) + if(uart == nullptr || uart->rx_buffer == nullptr) { return -1; + } return uart->rx_buffer->find(c); } int uart_peek_last_char(uart_t* uart) { - return uart && uart->rx_buffer ? uart->rx_buffer->peekLastChar() : -1; + return uart != nullptr && uart->rx_buffer != nullptr ? uart->rx_buffer->peekLastChar() : -1; } size_t uart_read(uart_t* uart, void* buffer, size_t size) { - if(!uart_rx_enabled(uart) || !buffer || !size) + if(!uart_rx_enabled(uart) || buffer == nullptr || size == 0) { return 0; + } + + notify(uart, UART_NOTIFY_BEFORE_READ); size_t read = 0; auto buf = static_cast(buffer); // If RX buffer not in use or it's empty then read directly from hardware FIFO - if(uart->rx_buffer) + if(uart->rx_buffer != nullptr) { while(read < size && !uart->rx_buffer->isEmpty()) buf[read++] = uart->rx_buffer->readChar(); + } - while(read < size && UART_RXCOUNT(uart->uart_nr) != 0) - buf[read++] = USF(uart->uart_nr); + if(is_physical(uart)) { + while(read < size && uart_rxfifo_count(uart->uart_nr) != 0) { + buf[read++] = USF(uart->uart_nr); + } - // FIFO full may have been disabled if buffer overflowed, re-enabled it now - USIE(uart->uart_nr) |= _BV(UIFF) | _BV(UITO); + // FIFO full may have been disabled if buffer overflowed, re-enabled it now + USIE(uart->uart_nr) |= _BV(UIFF) | _BV(UITO); + } return read; } size_t uart_rx_available(uart_t* uart) { - if(!uart_rx_enabled(uart)) + if(!uart_rx_enabled(uart)) { return 0; + } + + uart_disable_interrupts(); - size_t avail = UART_RXCOUNT(uart->uart_nr); + size_t avail = is_physical(uart) ? uart_rxfifo_count(uart->uart_nr) : 0; - if(uart->rx_buffer) + if(uart->rx_buffer != nullptr) { avail += uart->rx_buffer->available(); + } + + uart_restore_interrupts(); return avail; } - /** * @brief service interrupts for a UART * @param uart_nr identifies which UART to check @@ -218,8 +298,9 @@ static void IRAM_ATTR handle_uart_interrupt(uint8_t uart_nr, uart_t* uart) uint32_t usis = USIS(uart_nr); // If status is clear there's no interrupt to service on this UART - if (usis == 0) + if(usis == 0) { return; + } // Clear all status before proceeeding USIC(uart_nr) = usis; @@ -231,69 +312,66 @@ static void IRAM_ATTR handle_uart_interrupt(uint8_t uart_nr, uart_t* uart) * UART1 for debug output but leave UART0 alone. However, the SDK has enabled some interrupt sources * which we're not expecting. * - * @todo Call uart_detach() for all UARTs at power-on. + * (Calling uart_detach_all() at startup pre-empts all this.) */ - if (!uart || !UART_ISR_ENABLED(uart_nr)) { + if(uart == nullptr || !uart_isr_enabled(uart_nr)) { USIE(uart_nr) = 0; return; } + // Deal with the event, unless we're in raw mode + if(!bitRead(uart->options, UART_OPT_CALLBACK_RAW)) { + // Rx FIFO full or timeout + if(usis & (_BV(UIFF) | _BV(UITO))) { + size_t read = 0; + + // Read as much data as possible from the RX FIFO into buffer + if(uart->rx_buffer != nullptr) { + size_t space = uart->rx_buffer->getFreeSpace(); + while(space-- && uart_rxfifo_count(uart_nr) != 0) { + uart->rx_buffer->writeChar(USF(uart_nr)); + ++read; + } + } - // Rx FIFO full or timeout - if (usis & (_BV(UIFF) | _BV(UITO))) { - size_t read = 0; + // We cleared status flags above, but this one gets re-set almost immediately so clear it again now + USIC(uart_nr) = _BV(UITO); - // Read as much data as possible from the RX FIFO into buffer - if(uart->rx_buffer) { - size_t space = uart->rx_buffer->getFreeSpace(); - while(space-- && UART_RXCOUNT(uart_nr) != 0) { - uart->rx_buffer->writeChar(USF(uart_nr)); - ++read; + /* + * If the FIFO is full and we didn't read any of the data then need to mask the interrupt out or it'll recur. + * The interrupt gets re-enabled by a call to uart_read() or uart_flush() + */ + if(read == 0) { + USIE(uart_nr) &= ~(_BV(UIFF) | _BV(UITO)); } } - // We cleared status flags above, but this one gets re-set almost immediately so clear it again now - USIC(uart_nr) |= _BV(UITO); - - /* - * If the FIFO is full and we didn't read any of the data then need to mask the interrupt out or it'll recur. - * The interrupt gets re-enabled by a call to uart_read() or uart_flush() - */ - if (read == 0) - USIE(uart_nr) &= ~(_BV(UIFF) | _BV(UITO)); - } - - - // Unless we replenish TX FIFO, disable after handling interrupt - bool tx_fifo_empty = (usis & _BV(UIFE)) != 0; - - if (tx_fifo_empty) { - // Dump as much data as we can from buffer into the TX FIFO - if(uart->tx_buffer && !uart->tx_buffer->isEmpty()) { - size_t avail = uart->tx_buffer->available(); - while(avail-- && !UART_TXFULL(uart_nr)) - USF(uart_nr) = uart->tx_buffer->readChar(); + // Unless we replenish TX FIFO, disable after handling interrupt + if(bitRead(usis, UIFE)) { + // Dump as much data as we can from buffer into the TX FIFO + if(uart->tx_buffer != nullptr) { + size_t avail = uart->tx_buffer->available(); + while(avail-- && !uart_txfifo_full(uart_nr)) { + USF(uart_nr) = uart->tx_buffer->readChar(); + } + } - // We've topped up TX FIFO so defer callback until next time - if (UART_TXCOUNT(uart_nr) != 0) { - usis &= ~_BV(UIFE); - tx_fifo_empty = false; + if(uart_txfifo_count(uart_nr) == 0) { + // If TX FIFO remains empty then we must disable TX FIFO EMPTY interrupt to stop it recurring. + // The interrupt gets re-enabled by uart_write() + bitClear(USIE(uart_nr), UIFE); + } else { + // We've topped up TX FIFO so defer callback until next time + bitClear(usis, UIFE); } } } - /* - * If TX FIFO remains empty then we must disable TX FIFO EMPTY interrupt to stop it recurring. - * The interrupt gets re-enabled by uart_write() - */ - if (tx_fifo_empty) - USIE(uart_nr) &= ~_BV(UIFE); - - if(usis && uart->callback) + if(usis != 0 && uart->callback != nullptr) { uart->callback(uart, usis); + } } - /** @brief UART interrupt service routine * @note both UARTS share the same ISR, although UART1 only supports transmit */ @@ -303,11 +381,11 @@ static void IRAM_ATTR uart_isr(void* arg) handle_uart_interrupt(UART1, uartInstances[UART1]); } - void uart_start_isr(uart_t* uart) { - if (!uart || uart->uart_nr >= UART_COUNT) + if(!is_physical(uart)) { return; + } uint32_t usc1 = 0; uint32_t usie = 0; @@ -317,11 +395,11 @@ void uart_start_isr(uart_t* uart) * UCTOT: RX TimeOut Treshold * UCTOE: RX TimeOut Enable */ - usc1 = (127 << UCFFT) | (0x02 << UCTOT) | _BV(UCTOE); + usc1 = (120 << UCFFT) | (0x02 << UCTOT) | _BV(UCTOE); usie = _BV(UIFF) | _BV(UIFR) | _BV(UITO); } - if (uart_tx_enabled(uart)) { + if(uart_tx_enabled(uart)) { /* * We can interrupt when TX FIFO is empty; at 1Mbit that gives us 800 CPU * cycles before the last character has actually gone over the wire. Even if @@ -331,7 +409,7 @@ void uart_start_isr(uart_t* uart) */ // TX FIFO empty threshold - usc1 |= (0 << UCFET); + // usc1 |= (0 << UCFET); // TX FIFO empty interrupt only gets enabled via uart_write function() } @@ -341,7 +419,7 @@ void uart_start_isr(uart_t* uart) uint8_t oldmask = isrMask; - isrMask |= _BV(uart->uart_nr); + bitSet(isrMask, uart->uart_nr); if(oldmask == 0) { ETS_UART_INTR_DISABLE(); @@ -350,101 +428,123 @@ void uart_start_isr(uart_t* uart) } } -void IRAM_ATTR uart_stop_isr(uart_t* uart) -{ - if (uart) - uart_detach(uart->uart_nr); -} - - size_t uart_write(uart_t* uart, const void* buffer, size_t size) { - if(!uart_tx_enabled(uart) || !buffer || !size) + if(!uart_tx_enabled(uart) || buffer == nullptr || size == 0) { return 0; + } size_t written = 0; auto buf = static_cast(buffer); - for (;;) { - // If TX buffer not in use or it's empty then write directly to hardware FIFO - if(!uart->tx_buffer || uart->tx_buffer->isEmpty()) - while(written < size && !UART_TXFULL(uart->uart_nr)) - USF(uart->uart_nr) = buf[written++]; + bool isPhysical = is_physical(uart); + + while(written < size) { + if(isPhysical) { + // If TX buffer not in use or it's empty then write directly to hardware FIFO + if(uart->tx_buffer == nullptr || uart->tx_buffer->isEmpty()) { + while(written < size && !uart_txfifo_full(uart->uart_nr)) { + USF(uart->uart_nr) = buf[written++]; + } + } + } - // Write any remaining data into buffer - if(uart->tx_buffer) { - while(written < size && uart->tx_buffer->writeChar(buf[written])) + // Write any remaining data into transmit buffer + if(uart->tx_buffer != nullptr) { + while(written < size && uart->tx_buffer->writeChar(buf[written])) { ++written; + } } + notify(uart, UART_NOTIFY_AFTER_WRITE); - if (written >= size || !(uart->options & _BV(UART_OPT_TXWAIT))) + if(!bitRead(uart->options, UART_OPT_TXWAIT)) { break; + } } // Enable TX FIFO EMPTY interrupt - if (written) - USIE(uart->uart_nr) |= _BV(UIFE); + if(written != 0 && isPhysical) { + bitSet(USIE(uart->uart_nr), UIFE); + } return written; } size_t uart_tx_free(uart_t* uart) { - if(!uart_tx_enabled(uart)) + if(!uart_tx_enabled(uart)) { return 0; + } + + uart_disable_interrupts(); - size_t space = UART_TX_FIFO_SIZE - UART_TXCOUNT(uart->uart_nr); - if(uart->tx_buffer) + size_t space = is_physical(uart) ? uart_txfifo_free(uart->uart_nr) : 0; + if(uart->tx_buffer != nullptr) { space += uart->tx_buffer->getFreeSpace(); + } + + uart_restore_interrupts(); + return space; } void uart_wait_tx_empty(uart_t* uart) { - if(!uart_tx_enabled(uart)) + if(!uart_tx_enabled(uart)) { return; + } - if (uart->tx_buffer) - while (!uart->tx_buffer->isEmpty()) { + if(uart->tx_buffer != nullptr) { + while(!uart->tx_buffer->isEmpty()) { delay(0); } + } - while(UART_TXCOUNT(uart->uart_nr) != 0) - delay(0); + if(is_physical(uart)) { + while(uart_txfifo_count(uart->uart_nr) != 0) + delay(0); + } } void uart_flush(uart_t* uart) { - if(!uart) + if(uart == nullptr) { return; + } uart_disable_interrupts(); - if(uart->rx_buffer) + if(uart->rx_buffer != nullptr) { uart->rx_buffer->clear(); + } - if(uart->tx_buffer) + if(uart->tx_buffer != nullptr) { uart->tx_buffer->clear(); + } - // Prevend TX FIFO EMPTY interrupts - don't need them until uart_write is called again - USIE(uart->uart_nr) &= ~_BV(UIFE); + if(is_physical(uart)) { + // Prevent TX FIFO EMPTY interrupts - don't need them until uart_write is called again + bitClear(USIE(uart->uart_nr), UIFE); - // If receive overflow occurred then these interrupts will be masked - if(uart_rx_enabled(uart)) - USIE(uart->uart_nr) |= _BV(UIFF) | _BV(UITO); + // If receive overflow occurred then these interrupts will be masked + if(uart_rx_enabled(uart)) { + USIE(uart->uart_nr) |= _BV(UIFF) | _BV(UITO); + } - uint32_t tmp = _BV(UCRXRST) | _BV(UCTXRST); - USC0(uart->uart_nr) |= tmp; - USC0(uart->uart_nr) &= ~tmp; + uint32_t tmp = _BV(UCRXRST) | _BV(UCTXRST); + USC0(uart->uart_nr) |= tmp; + USC0(uart->uart_nr) &= ~tmp; + } uart_restore_interrupts(); } uint32_t uart_set_baudrate_reg(int uart_nr, uint32_t baud_rate) { - if(uart_nr < UART0 || uart_nr > UART1 || baud_rate == 0) + if(!is_physical(uart_nr) || baud_rate == 0) { return 0; + } uint32_t clkdiv = ESP8266_CLOCK / baud_rate; USD(uart_nr) = clkdiv; @@ -455,8 +555,10 @@ uint32_t uart_set_baudrate_reg(int uart_nr, uint32_t baud_rate) uint32_t uart_set_baudrate(uart_t* uart, uint32_t baud_rate) { - if(!uart) + uart = get_physical(uart); + if(uart == nullptr) { return 0; + } baud_rate = uart_set_baudrate_reg(uart->uart_nr, baud_rate); // Store the actual baud rate in use @@ -464,43 +566,63 @@ uint32_t uart_set_baudrate(uart_t* uart, uint32_t baud_rate) return baud_rate; } +uint32_t uart_get_baudrate(uart_t* uart) +{ + uart = get_physical(uart); + return (uart == nullptr) ? 0 : uart->baud_rate; +} + uart_t* uart_init_ex(const uart_config& cfg) { // Already initialised? - if (uart_get_uart(cfg.uart_nr)) + if(uart_get_uart(cfg.uart_nr) != nullptr) { return nullptr; + } auto uart = new uart_t; - if(!uart) + if(uart == nullptr) { return nullptr; + } memset(uart, 0, sizeof(uart_t)); uart->uart_nr = cfg.uart_nr; uart->mode = cfg.mode; uart->options = cfg.options; + uart->tx_pin = 255; + uart->rx_pin = 255; + + auto rxBufferSize = cfg.rx_size; + auto txBufferSize = cfg.tx_size; switch(cfg.uart_nr) { case UART0: - uart->rx_pin = uart_rx_enabled(uart) ? 3 : 255; + case UART2: + // Virtual uart requires a minimum RAM buffer + if(cfg.uart_nr == UART2) { + rxBufferSize += UART_RX_FIFO_SIZE; + txBufferSize += UART_TX_FIFO_SIZE; + } - if(uart_rx_enabled(uart)) - if (!realloc_buffer(uart->rx_buffer, cfg.rx_size)) { - delete uart; - return nullptr; - } + if(uart_rx_enabled(uart) && !realloc_buffer(uart->rx_buffer, rxBufferSize)) { + delete uart; + return nullptr; + } - if(uart_tx_enabled(uart)) { - if (!realloc_buffer(uart->tx_buffer, cfg.tx_size)) { - delete uart->rx_buffer; - delete uart; - return nullptr; - } + if(uart_tx_enabled(uart) && !realloc_buffer(uart->tx_buffer, txBufferSize)) { + delete uart->rx_buffer; + delete uart; + return nullptr; + } + + if(cfg.uart_nr == UART2) { + break; } // OK, buffers allocated so setup hardware uart_detach(cfg.uart_nr); if(uart_rx_enabled(uart)) { + uart->rx_pin = 3; pinMode(uart->rx_pin, SPECIAL); } @@ -512,10 +634,10 @@ uart_t* uart_init_ex(const uart_config& cfg) uart->tx_pin = 1; pinMode(uart->tx_pin, FUNCTION_0); } - } else - uart->tx_pin = 255; + } - IOSWAP &= ~(1 << IOSWAPU0); + bitClear(IOSWAP, IOSWAPU0); + USC0(UART0) = cfg.config; break; case UART1: @@ -525,20 +647,19 @@ uart_t* uart_init_ex(const uart_config& cfg) return nullptr; } uart->mode = UART_TX_ONLY; - uart->rx_pin = 255; - // GPIO7 as TX not possible! See GPIO pins used by UART - uart->tx_pin = 2; // Transmit buffer optional - if(cfg.tx_size) - if (!realloc_buffer(uart->tx_buffer, cfg.tx_size)) { - delete uart; - return nullptr; - } + if(!realloc_buffer(uart->tx_buffer, txBufferSize)) { + delete uart; + return nullptr; + } // Setup hardware uart_detach(cfg.uart_nr); + // GPIO7 as TX not possible! See GPIO pins used by UART + uart->tx_pin = 2; pinMode(uart->tx_pin, SPECIAL); + USC0(UART1) = cfg.config; break; default: @@ -548,23 +669,28 @@ uart_t* uart_init_ex(const uart_config& cfg) } uart_set_baudrate(uart, cfg.baudrate); - USC0(cfg.uart_nr) = cfg.config; uart_flush(uart); uartInstances[cfg.uart_nr] = uart; uart_start_isr(uart); + notify(uart, UART_NOTIFY_AFTER_OPEN); + return uart; } void uart_uninit(uart_t* uart) { - if(!uart) + if(uart == nullptr) { return; + } + + notify(uart, UART_NOTIFY_BEFORE_CLOSE); uart_stop_isr(uart); // If debug output being sent to this UART, disable it - if (uart->uart_nr == s_uart_debug_nr) + if(uart->uart_nr == s_uart_debug_nr) { uart_set_debug(UART_NO); + } switch(uart->rx_pin) { case 3: @@ -595,27 +721,25 @@ void uart_uninit(uart_t* uart) delete uart; } - -uart_t* uart_init(uint8_t uart_nr, uint32_t baudrate, uint32_t config, uart_mode_t mode, uint8_t tx_pin, size_t rx_size, size_t tx_size) +uart_t* uart_init(uint8_t uart_nr, uint32_t baudrate, uint32_t config, uart_mode_t mode, uint8_t tx_pin, size_t rx_size, + size_t tx_size) { - uart_config cfg = { - .uart_nr = uart_nr, - .tx_pin = tx_pin, - .mode = mode, - .options = _BV(UART_OPT_TXWAIT), - .baudrate = baudrate, - .config = config, - .rx_size = rx_size, - .tx_size = tx_size - }; + uart_config cfg = {.uart_nr = uart_nr, + .tx_pin = tx_pin, + .mode = mode, + .options = _BV(UART_OPT_TXWAIT), + .baudrate = baudrate, + .config = config, + .rx_size = rx_size, + .tx_size = tx_size}; return uart_init_ex(cfg); } - void uart_swap(uart_t* uart, int tx_pin) { - if(!uart) + if(uart == nullptr) { return; + } switch(uart->uart_nr) { case UART0: @@ -637,7 +761,7 @@ void uart_swap(uart_t* uart, int tx_pin) if(uart_rx_enabled(uart)) pinMode(uart->rx_pin, FUNCTION_4); - IOSWAP |= (1 << IOSWAPU0); + bitSet(IOSWAP, IOSWAPU0); } else { if(uart_tx_enabled(uart)) { pinMode(uart->tx_pin, INPUT); @@ -655,7 +779,7 @@ void uart_swap(uart_t* uart, int tx_pin) if(uart_rx_enabled(uart)) pinMode(3, SPECIAL); - IOSWAP &= ~(1 << IOSWAPU0); + bitClear(IOSWAP, IOSWAPU0); } break; @@ -671,8 +795,9 @@ void uart_swap(uart_t* uart, int tx_pin) void uart_set_tx(uart_t* uart, int tx_pin) { - if(!uart) + if(uart == nullptr) { return; + } switch(uart->uart_nr) { case UART0: @@ -701,8 +826,9 @@ void uart_set_tx(uart_t* uart, int tx_pin) void uart_set_pins(uart_t* uart, int tx, int rx) { - if(!uart) + if(uart == nullptr) { return; + } // Only UART0 allows pin changes if(uart->uart_nr == UART0) { @@ -710,38 +836,31 @@ void uart_set_pins(uart_t* uart, int tx, int rx) if(rx == 13 && tx == 15) uart_swap(uart, 15); else if(rx == 3 && (tx == 1 || tx == 2)) { - if(uart->rx_pin != rx) + if(uart->rx_pin != rx) { uart_swap(uart, tx); - else + } else { uart_set_tx(uart, tx); + } } } - if(uart_rx_enabled(uart) && uart->rx_pin != rx && rx == 13 && tx == 15) + if(uart_rx_enabled(uart) && uart->rx_pin != rx && rx == 13 && tx == 15) { uart_swap(uart, 15); + } } } - static void uart_debug_putc(char c) { uart_t* uart = uart_get_uart(s_uart_debug_nr); - if (uart) + if(uart != nullptr) { uart_write_char(uart, c); + } } void uart_set_debug(int uart_nr) { - uart_t* uart = uart_get_uart(uart_nr); - - if (uart == nullptr) { - s_uart_debug_nr = UART_NO; - system_set_os_print(false); - } - else { - s_uart_debug_nr = uart_nr; - system_set_os_print(true); - } - + s_uart_debug_nr = uart_nr; + system_set_os_print(uart_nr >= 0); ets_install_putc1(uart_debug_putc); } @@ -750,10 +869,14 @@ int uart_get_debug() return s_uart_debug_nr; } -void IRAM_ATTR uart_detach(int uart_nr) +void uart_detach(int uart_nr) { + if(!is_physical(uart_nr)) { + return; + } + uart_disable_interrupts(); - isrMask &= ~_BV(uart_nr); + bitClear(isrMask, uart_nr); USC1(uart_nr) = 0; USIC(uart_nr) = 0xffff; USIE(uart_nr) = 0; @@ -763,7 +886,7 @@ void IRAM_ATTR uart_detach(int uart_nr) void uart_detach_all() { uart_disable_interrupts(); - for (unsigned uart_nr = 0; uart_nr < UART_COUNT; ++uart_nr) { + for(unsigned uart_nr = 0; uart_nr < UART_PHYSICAL_COUNT; ++uart_nr) { USC1(uart_nr) = 0; USIC(uart_nr) = 0xffff; USIE(uart_nr) = 0; diff --git a/Sming/third-party/esp-gdbstub b/Sming/third-party/esp-gdbstub deleted file mode 160000 index c86fefef41..0000000000 --- a/Sming/third-party/esp-gdbstub +++ /dev/null @@ -1 +0,0 @@ -Subproject commit c86fefef41ec2b9f192e600760c2145a8382e803 diff --git a/samples/LiveDebug/app/application.cpp b/samples/LiveDebug/app/application.cpp index 9f52a87070..e32078fd84 100644 --- a/samples/LiveDebug/app/application.cpp +++ b/samples/LiveDebug/app/application.cpp @@ -1,8 +1,12 @@ #include -#include "SmingCore.h" +#include #include "HardwareTimer.h" -#define LED_PIN 2 // GPIO2 +#define LED_PIN 2 // Note: LED is attached to UART1 TX output + +#define TIMERTYPE_HARDWARE 1 +#define TIMERTYPE_SIMPLE 2 +#define TIMERTYPE_TIMER 3 /* * This example uses the hardware timer for best timing accuracy. There is only one of these on the ESP8266, @@ -10,21 +14,57 @@ * Most timing applications can use a SimpleTimer, which is good for intervals of up to about 268 seconds. * For longer intervals, use a Timer. */ -HardwareTimer procTimer; +#define TIMER_TYPE TIMERTYPE_HARDWARE + +/* + * HardwareTimer defaults to non-maskable mode, so the timer callback cannot be interrupted even by the + * debugger. To use break/watchpoints we must set the timer to use maskable mode. + */ +#define HWTIMER_TYPE eHWT_Maskable + +#if TIMER_TYPE == TIMERTYPE_HARDWARE +HardwareTimer procTimer(HWTIMER_TYPE); +// Hardware timer callbacks must always be in IRAM +#define CALLBACK_ATTR IRAM_ATTR +#elif TIMER_TYPE == TIMERTYPE_SIMPLE +SimpleTimer procTimer; +#define CALLBACK_ATTR GDB_IRAM_ATTR +#else +Timer procTimer; +#define CALLBACK_ATTR GDB_IRAM_ATTR +#endif + bool state = true; /* * Notice: Software breakpoints work only on code that is in RAM. * In Sming you have to use the GDB_IRAM_ATTR to do this. */ -void GDB_IRAM_ATTR blink() +void CALLBACK_ATTR blink() { digitalWrite(LED_PIN, state); state = !state; } +void onDataReceived(Stream& source, char arrivedChar, unsigned short availableCharsCount) +{ + if(arrivedChar == '\n' && availableCharsCount != 0) { + char buffer[availableCharsCount]; + auto count = Serial.readMemoryBlock(buffer, availableCharsCount); + Serial.print(_F("You typed: ")); + Serial.write(buffer, count); + } +} + void GDB_IRAM_ATTR init() { + Serial.begin(SERIAL_BAUD_RATE); + Serial.onDataReceived(onDataReceived); pinMode(LED_PIN, OUTPUT); +#if TIMER_TYPE == TIMERTYPE_SIMPLE + procTimer.setCallback(SimpleTimerCallback(blink)); + procTimer.startMs(1000, true); +#else procTimer.initializeMs(1000, blink).start(); +#endif } From e82c5bf4e4f8004e97618dd52abc08c72d288793 Mon Sep 17 00:00:00 2001 From: mikee47 Date: Tue, 2 Apr 2019 12:38:21 +0100 Subject: [PATCH 02/50] Fix error in `gdbstub-entry.S` DebugExceptionExit, wrong register value --- Sming/appspecific/gdb/gdbstub-entry.S | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sming/appspecific/gdb/gdbstub-entry.S b/Sming/appspecific/gdb/gdbstub-entry.S index f760582e33..15cbf885d8 100644 --- a/Sming/appspecific/gdb/gdbstub-entry.S +++ b/Sming/appspecific/gdb/gdbstub-entry.S @@ -131,7 +131,7 @@ DebugExceptionExit: l32i a12, a2, GDBSR_A(12) l32i a11, a2, GDBSR_A(11) l32i a10, a2, GDBSR_A(10) - l32i a9, a2, GDBSR_A(8) + l32i a9, a2, GDBSR_A(9) l32i a8, a2, GDBSR_A(8) l32i a7, a2, GDBSR_A(7) l32i a6, a2, GDBSR_A(6) From 5d4674bc84f3154e3c65aa9000b906213c263039 Mon Sep 17 00:00:00 2001 From: mikee47 Date: Tue, 2 Apr 2019 12:41:27 +0100 Subject: [PATCH 03/50] Bugfix: `uart_rxfifo_count()` values _include_ 0x80 so mask must be 0xff, not 0x7f --- Sming/gdb/gdbuart.cpp | 2 +- Sming/system/uart.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Sming/gdb/gdbuart.cpp b/Sming/gdb/gdbuart.cpp index 5861ecc228..afb8b45dc1 100644 --- a/Sming/gdb/gdbuart.cpp +++ b/Sming/gdb/gdbuart.cpp @@ -32,7 +32,7 @@ static bool sendUserDataQueued; // Ensures only one call to sendUserData() i // Get number of characters in receive FIFO __forceinline static uint8_t uart_rxfifo_count(uint8_t nr) { - return (USS(nr) >> USRXC) & 0x7f; + return (USS(nr) >> USRXC) & 0xff; } // Get number of characters in transmit FIFO diff --git a/Sming/system/uart.cpp b/Sming/system/uart.cpp index 3afc72fc45..342a32b111 100644 --- a/Sming/system/uart.cpp +++ b/Sming/system/uart.cpp @@ -56,7 +56,7 @@ static int s_uart_debug_nr = UART_NO; // Get number of characters in receive FIFO __forceinline static uint8_t uart_rxfifo_count(uint8_t nr) { - return (USS(nr) >> USRXC) & 0x7f; + return (USS(nr) >> USRXC) & 0xff; } // Get number of characters in transmit FIFO From 8df1e568c7abf57d81186523c1bb55d5be3b4bc8 Mon Sep 17 00:00:00 2001 From: mikee47 Date: Tue, 2 Apr 2019 12:48:29 +0100 Subject: [PATCH 04/50] Implement 'X' command and correct checksum calculation when un-escaping incoming data --- Sming/gdb/gdbstub-cfg.h | 4 +++ Sming/gdb/gdbstub.cpp | 79 ++++++++++++++++++++++++++++++----------- 2 files changed, 62 insertions(+), 21 deletions(-) diff --git a/Sming/gdb/gdbstub-cfg.h b/Sming/gdb/gdbstub-cfg.h index 5bc6780535..5bb29c07a6 100644 --- a/Sming/gdb/gdbstub-cfg.h +++ b/Sming/gdb/gdbstub-cfg.h @@ -145,6 +145,10 @@ #ifndef GDBSTUB_CMDENABLE_P #define GDBSTUB_CMDENABLE_P 1 #endif +// Write binary-encoded data +#ifndef GDBSTUB_CMDENABLE_X +#define GDBSTUB_CMDENABLE_X 1 +#endif /* * Specify a timeout (in milliseconds) when stub is reading from serial port. diff --git a/Sming/gdb/gdbstub.cpp b/Sming/gdb/gdbstub.cpp index 5b09f916a9..5bd42dafed 100644 --- a/Sming/gdb/gdbstub.cpp +++ b/Sming/gdb/gdbstub.cpp @@ -62,6 +62,7 @@ enum DebugFlag { DBGFLAG_CTRL_BREAK, ///< Break caused by call to gdbstub_ctrl_break() }; static uint8_t debugFlags = 0; +typedef uint8_t ERRNO_T; /* * Maximum length of a GDB command packet. Has to be at least able to fit the G command. @@ -121,14 +122,21 @@ static void ATTR_GDBEXTERNFN writeMemoryByte(uint32_t addr, uint8_t data) *valuePtr = word.value; } +/* + * Return true if addr lives within a RAM segment + * See xtensa/config/core-isa.h for memory range information + */ +static bool ATTR_GDBEXTERNFN isRamAddr(uint32_t addr) +{ + return (addr >= 0x3ff00000 && addr < 0x40000000) || (addr >= 0x40100000 && addr < 0x40140000); +} + /* * Return true if it makes sense to write to addr */ static bool ATTR_GDBEXTERNFN isValidWriteAddr(uint32_t addr) { - // See xtensa/config/core-isa.h for memory range information - return (addr >= 0x3ff00000 && addr < 0x40000000) || (addr >= 0x40100000 && addr < 0x40140000) || - (addr >= 0x60000000 && addr < 0x60002000); + return isRamAddr(addr) || (addr >= 0x60000000 && addr < 0x60002000); } static bool ATTR_GDBEXTERNFN isValidWriteAddr(uint32_t startAddr, size_t length) @@ -150,6 +158,34 @@ static bool ATTR_GDBEXTERNFN isValidReadAddr(uint32_t startAddr, size_t length) return isValidReadAddr(startAddr) && isValidReadAddr(startAddr + length - 1); } +/** @brief Write a block of memory + * @param addr + * @param length + * @retval uint8_t error number + */ +static ERRNO_T ATTR_GDBEXTERNFN writeMemoryBlock(uint32_t addr, const void* data, unsigned length) +{ + // write memory from gdb + if(!isValidWriteAddr(addr, length)) { + return EFAULT; + } + + // If writing to RAM, use memcpy for efficiency + if(isRamAddr(addr)) { + memcpy(reinterpret_cast(addr), data, length); + } else { + // Registers must be read/written in 32-bit words + for(unsigned i = 0; i < length; i++, addr++) { + writeMemoryByte(addr, static_cast(data)[i]); + } + } + + // Make sure caches are up-to-date. Procedure according to Xtensa ISA document, ISYNC inst desc. + asm volatile("ISYNC\nISYNC\n"); + + return 0; +} + /* * Send the reason execution is stopped to GDB. * The code sent is a standard GDB signal number. See signals.def. @@ -263,15 +299,17 @@ static unsigned ATTR_GDBEXTERNFN readCommand() cmdLen = 0; continue; } + checksum += uint8_t(c); if(c == '}') { // escape the next char - c = gdbReceiveChar() ^ 0x20; + c = gdbReceiveChar(); + checksum += uint8_t(c); + c ^= 0x20; } if(cmdLen >= MAX_COMMAND_LENGTH) { // Received more than the size of the command buffer debug_e("Command '%c' buffer overflow", commandBuffer[0]); return 0; } - checksum += uint8_t(c); commandBuffer[cmdLen++] = c; } commandBuffer[cmdLen] = '\0'; @@ -433,23 +471,16 @@ static GdbResult ATTR_GDBEXTERNFN handleCommand(unsigned cmdLen) break; } - uint8_t err = 0; + ERRNO_T err = 0; // write memory from gdb - if(isValidWriteAddr(addr, length)) { - data++; // skip : - auto bin = reinterpret_cast(commandBuffer); - auto len = GdbPacket::decodeHexBlock(bin, data); - if(len == length) { - for(unsigned i = 0; i < length; i++, addr++) { - writeMemoryByte(addr, *bin++); - } - // Make sure caches are up-to-date. Procedure according to Xtensa ISA document, ISYNC inst desc. - asm volatile("ISYNC\nISYNC\n"); - } + data++; // skip : + auto bin = reinterpret_cast(commandBuffer); + auto len = GdbPacket::decodeHexBlock(bin, data); + if(len == length) { + err = writeMemoryBlock(addr, bin, length); } else { - // Trying to do a software breakpoint on a flash proc, perhaps? - err = EFAULT; + err = EINVAL; } sendReply(err); @@ -647,9 +678,15 @@ static GdbResult ATTR_GDBEXTERNFN handleCommand(unsigned cmdLen) * `X addr,length:XX...` * */ - case 'X': - // @todo + case 'X': { + uint32_t addr = GdbPacket::readHexValue(data); + data++; // skip , + unsigned length = GdbPacket::readHexValue(data); + data++; // skip : + uint8_t err = writeMemoryBlock(addr, data, length); + sendReply(err); break; + } #endif default: From 62aec17a6155fdf7702e5b9e7fa1b5525f67db24 Mon Sep 17 00:00:00 2001 From: mikee47 Date: Tue, 2 Apr 2019 13:46:47 +0100 Subject: [PATCH 05/50] Fix uart receive overflow handling and error/status reporting Whilst testing I idly pasted a large file into the serial terminal. In normal build (without gdbstub) it went pop. Behaved a little better with gdbstub included. Investigation showed task queue getting flooded (there should only be 1 or 2 tasks on there at a time) and too many interrupts getting fired. uart * Make `uart_disable_interrupts()` and `uart_restore_interrupts()` public * Add `uart_get_status()` function * Don't invoke callback on receive FIFO full unless buffer is almost full, subject to minimum headroom * Revise interrupt handler to store status flags (thus capturing a BREAK condition), and simplify loops/reduce code size. gdbstub * Use global structure to share state between code modules, simplifies code gdbuart * Use separate task callback function to avoid flooding task queue, so `sendUserDataQueued` isn't cleared when `gdbstub_send_user_data()` called directly. HardwareSerial * Add `getStatus()` method and `SerialStatus` enumeration for application use * Rework interrupt callback handling to prevent a task being queued if the previous one hasn't been handled yet. --- Sming/SmingCore/HardwareSerial.cpp | 95 +++++++++++--------- Sming/SmingCore/HardwareSerial.h | 32 +++++-- Sming/gdb/gdbstub.cpp | 43 ++++----- Sming/gdb/gdbstub.h | 23 +++-- Sming/gdb/gdbuart.cpp | 140 ++++++++++++++--------------- Sming/gdb/gdbuart.h | 20 +++++ Sming/system/include/espinc/uart.h | 32 ++++++- Sming/system/uart.cpp | 116 ++++++++++++++++++------ 8 files changed, 315 insertions(+), 186 deletions(-) diff --git a/Sming/SmingCore/HardwareSerial.cpp b/Sming/SmingCore/HardwareSerial.cpp index 3ea354c9af..19abbaa576 100644 --- a/Sming/SmingCore/HardwareSerial.cpp +++ b/Sming/SmingCore/HardwareSerial.cpp @@ -44,7 +44,6 @@ void HardwareSerial::begin(uint32_t baud, SerialConfig config, SerialMode mode, .rx_size = rxSize, .tx_size = txSize}; uart = uart_init_ex(cfg); - updateUartCallback(); } @@ -104,73 +103,81 @@ void HardwareSerial::systemDebugOutput(bool enabled) } } -void HardwareSerial::staticOnTransmitComplete(uint32_t param) +void HardwareSerial::invokeCallbacks() { - auto serial = reinterpret_cast(param); - if(serial->transmitComplete) { - serial->transmitComplete(*serial); - } -} - -void HardwareSerial::staticOnReceive(uint32_t param) -{ - auto serial = reinterpret_cast(param); - auto receivedChar = uart_peek_last_char(serial->uart); - if(serial->HWSDelegate) { - serial->HWSDelegate(*serial, receivedChar, uart_rx_available(serial->uart)); - } -#if ENABLE_CMD_EXECUTOR - if(serial->commandExecutor) { - serial->commandExecutor->executorReceive(receivedChar); - } -#endif -} + uart_disable_interrupts(); + auto status = callbackStatus; + callbackStatus = 0; + callbackQueued = false; + uart_restore_interrupts(); -void HardwareSerial::callbackHandler(uint32_t status) -{ // Transmit complete ? if(bitRead(status, UIFE) && transmitComplete) { - System.queueCallback(staticOnTransmitComplete, uint32_t(this)); + transmitComplete(*this); } // RX FIFO Full or RX FIFO Timeout ? - if((status & (_BV(UIFF) | _BV(UITO))) != 0) { -#if ENABLE_CMD_EXECUTOR - if(HWSDelegate || commandExecutor) { -#else + if(status & (_BV(UIFF) | _BV(UITO))) { + auto receivedChar = uart_peek_last_char(uart); if(HWSDelegate) { + HWSDelegate(*this, receivedChar, uart_rx_available(uart)); + } +#if ENABLE_CMD_EXECUTOR + if(commandExecutor) { + commandExecutor->executorReceive(receivedChar); + } #endif + } +} - System.queueCallback(staticOnReceive, uint32_t(this)); - } +/* + * Called via task queue + */ +void HardwareSerial::staticOnStatusChange(uint32_t param) +{ + auto serial = reinterpret_cast(param); + if(serial != nullptr) { + serial->invokeCallbacks(); } } +/* + * Called from uart interrupt handler + */ void HardwareSerial::staticCallbackHandler(uart_t* uart, uint32_t status) { - auto serial = reinterpret_cast(uart_get_callback_param(uart)); - if(serial) { - serial->callbackHandler(status); + auto serial = static_cast(uart_get_callback_param(uart)); + if(serial == nullptr) { + return; + } + + serial->callbackStatus |= status; + + // If required, queue a callback + if((status & serial->statusMask) != 0 && !serial->callbackQueued) { + System.queueCallback(staticOnStatusChange, uint32_t(serial)); + serial->callbackQueued = true; } } bool HardwareSerial::updateUartCallback() { - if(uart == nullptr) { - return false; - } - + uint16_t mask = 0; #if ENABLE_CMD_EXECUTOR - if(HWSDelegate || transmitComplete || commandExecutor) { + if(HWSDelegate || commandExecutor) { #else - if(HWSDelegate || transmitComplete) { + if(HWSDelegate) { #endif - setUartCallback(staticCallbackHandler, this); - return true; - } else { - setUartCallback(nullptr, nullptr); - return false; + mask |= _BV(UIFF) | _BV(UITO); } + + if(transmitComplete) { + mask |= _BV(UIFE); + } + + statusMask = mask; + + setUartCallback(mask == 0 ? nullptr : staticCallbackHandler, this); } void HardwareSerial::commandProcessing(bool reqEnable) diff --git a/Sming/SmingCore/HardwareSerial.h b/Sming/SmingCore/HardwareSerial.h index 20c90dae72..f80af9776e 100644 --- a/Sming/SmingCore/HardwareSerial.h +++ b/Sming/SmingCore/HardwareSerial.h @@ -90,6 +90,14 @@ enum SerialMode { SERIAL_FULL = UART_FULL, SERIAL_RX_ONLY = UART_RX_ONLY, SERIAL #define DEFAULT_TX_BUFFER_SIZE 0 #endif +/** @brief Notification and error status bits */ +enum SerialStatus { + eSERS_BreakDetected = UIBD, ///< Break condition detected on receive line + eSERS_Overflow = UIOF, ///< Receive buffer overflowed + eSERS_FramingError = UIFR, ///< Receive framing error + eSERS_ParityError = UIPE, ///< Parity check failed on received data +}; + /// Hardware serial class class HardwareSerial : public ReadWriteStream { @@ -313,7 +321,7 @@ class HardwareSerial : public ReadWriteStream void commandProcessing(bool reqEnable); /** @brief Set handler for received data - * @param reqCallback Function to handle received data + * @param dataReceivedDelegate Function to handle received data * @retval bool Returns true if the callback was set correctly */ bool setCallback(StreamDataReceivedDelegate dataReceivedDelegate) @@ -322,7 +330,7 @@ class HardwareSerial : public ReadWriteStream } /** @brief Set handler for received data - * @param reqCallback Function to handle received data + * @param dataReceivedDelegate Function to handle received data * @retval bool Returns true if the callback was set correctly */ bool onDataReceived(StreamDataReceivedDelegate dataReceivedDelegate) @@ -332,7 +340,7 @@ class HardwareSerial : public ReadWriteStream } /** @brief Set handler for received data - * @param reqCallback Function to handle received data + * @param transmitCompleteDelegate Function to handle received data * @retval bool Returns true if the callback was set correctly */ bool onTransmitComplete(TransmitCompleteDelegate transmitCompleteDelegate) @@ -415,6 +423,16 @@ class HardwareSerial : public ReadWriteStream return uart; } + /** + * @brief Get status error flags and clear them + * @retval unsigned Status flags, combination of SerialStatus bits + * @see SerialStatus + */ + unsigned getStatus() + { + return uart_get_status(uart); + } + private: int uartNr = -1; TransmitCompleteDelegate transmitComplete = nullptr; ///< Callback for transmit completion @@ -426,6 +444,9 @@ class HardwareSerial : public ReadWriteStream uart_options_t options = _BV(UART_OPT_TXWAIT); size_t txSize = DEFAULT_TX_BUFFER_SIZE; size_t rxSize = DEFAULT_RX_BUFFER_SIZE; + volatile uint16_t statusMask = 0; ///< Which serial events require a callback + volatile uint16_t callbackStatus = 0; ///< Persistent uart status flags for callback + volatile bool callbackQueued = false; /** * @brief Serial interrupt handler, called by serial driver @@ -433,9 +454,8 @@ class HardwareSerial : public ReadWriteStream * @param status UART status flags indicating cause(s) of interrupt */ static void IRAM_ATTR staticCallbackHandler(uart_t* uart, uint32_t status); - void IRAM_ATTR callbackHandler(uint32_t status); - static void staticOnTransmitComplete(uint32_t param); - static void staticOnReceive(uint32_t param); + static void staticOnStatusChange(uint32_t param); + void invokeCallbacks(); /** * @brief Called whenever one of the user callbacks change diff --git a/Sming/gdb/gdbstub.cpp b/Sming/gdb/gdbstub.cpp index 5bd42dafed..7d00f6931a 100644 --- a/Sming/gdb/gdbstub.cpp +++ b/Sming/gdb/gdbstub.cpp @@ -27,6 +27,7 @@ #include "BitManipulations.h" #include "exceptions.h" #include "gdb/registers.h" +#include "gdbsyscall.h" extern "C" { void system_restart_core(); @@ -55,13 +56,6 @@ static PGM_P const registerNames[] PROGMEM = { extern GdbstubSavedRegisters gdbstub_savedRegs; -/* Additional debugging flags */ -enum DebugFlag { - DBGFLAG_DEBUG_EXCEPTION, ///< For debug exceptions, cause is DBGCAUSE (see DebugCause bits) - DBGFLAG_SYSTEM_EXCEPTION, ///< For system exceptions, cause is EXCCAUSE (see EXCCAUSE_* values) - DBGFLAG_CTRL_BREAK, ///< Break caused by call to gdbstub_ctrl_break() -}; -static uint8_t debugFlags = 0; typedef uint8_t ERRNO_T; /* @@ -74,8 +68,7 @@ typedef uint8_t ERRNO_T; uint32_t exceptionStack[GDBSTUB_STACK_SIZE]; #endif -bool gdb_attached; ///< true if GDB is attached to stub -bool gdb_enabled; ///< Debugging may be disabled via gdb_enable() +volatile gdb_state_t gdb_state; ///< Global state static char commandBuffer[MAX_COMMAND_LENGTH + 1]; ///< Buffer for incoming/outgoing GDB commands static int32_t singleStepPs = -1; // Stores ps (Program State) when single-stepping instruction. -1 when not in use. @@ -197,13 +190,13 @@ static void ATTR_GDBEXTERNFN sendReason() uint8_t signal; auto cause = gdbstub_savedRegs.cause; - if(bitRead(debugFlags, DBGFLAG_SYSTEM_EXCEPTION)) { + if(bitRead(gdb_state.flags, DBGFLAG_SYSTEM_EXCEPTION)) { // Convert exception code to a signal number signal = (cause <= EXCCAUSE_MAX) ? pgm_read_byte(&gdb_exception_signals[cause]) : 0; packet.writeHexByte(signal); } else { // Debugging exception - signal = bitRead(debugFlags, DBGFLAG_CTRL_BREAK) ? GDB_SIGNAL_INT : GDB_SIGNAL_TRAP; + signal = bitRead(gdb_state.flags, DBGFLAG_CTRL_BREAK) ? GDB_SIGNAL_INT : GDB_SIGNAL_TRAP; packet.writeHexByte(signal); // Current Xtensa GDB versions don't seem to request this, so let's leave it off. #if 0 @@ -713,13 +706,13 @@ GdbResult ATTR_GDBEXTERNFN commandLoop() } auto cmdLen = readCommand(); if(cmdLen != 0) { - gdb_attached = true; + gdb_state.attached = true; result = handleCommand(cmdLen); } } while(result == ST_OK); if(result == ST_DETACH) { - gdb_attached = false; + gdb_state.attached = false; } return result; @@ -796,12 +789,12 @@ static void pauseHardwareTimer(bool pause) // Main exception handler static void __attribute__((noinline)) gdbstub_handle_debug_exception_flash() { - bool isEnabled = gdb_enabled; + bool isEnabled = gdb_state.enabled; if(isEnabled) { pauseHardwareTimer(true); - bitSet(debugFlags, DBGFLAG_DEBUG_EXCEPTION); + bitSet(gdb_state.flags, DBGFLAG_DEBUG_EXCEPTION); if(singleStepPs >= 0) { // We come here after single-stepping an instruction @@ -840,7 +833,7 @@ static void __attribute__((noinline)) gdbstub_handle_debug_exception_flash() } } - debugFlags = 0; + gdb_state.flags = 0; if(isEnabled) { pauseHardwareTimer(false); @@ -857,12 +850,12 @@ extern "C" void IRAM_ATTR gdbstub_handle_debug_exception() #if GDBSTUB_BREAK_ON_EXCEPTION void gdbstub_handle_exception(UserFrame* frame) { - if(!gdb_enabled) { + if(!gdb_state.enabled) { return; } pauseHardwareTimer(true); - bitSet(debugFlags, DBGFLAG_SYSTEM_EXCEPTION); + bitSet(gdb_state.flags, DBGFLAG_SYSTEM_EXCEPTION); // Copy registers the Xtensa HAL did save to gdbstub_savedRegs memcpy(&gdbstub_savedRegs, frame, 5 * 4); @@ -880,7 +873,7 @@ void gdbstub_handle_exception(UserFrame* frame) memcpy(frame, &gdbstub_savedRegs, 5 * 4); memcpy(&frame->a2, &gdbstub_savedRegs.a[2], 14 * 4); - debugFlags = 0; + gdb_state.flags = 0; pauseHardwareTimer(false); } #endif @@ -902,10 +895,10 @@ void ATTR_GDBINIT gdbstub_init() SD(GDBSTUB_BREAK_ON_INIT); #undef SD - gdb_enabled = gdb_uart_init(); + gdb_state.enabled = gdb_uart_init(); #if GDBSTUB_BREAK_ON_INIT - if(gdb_enabled) { + if(gdb_state.enabled) { gdbstub_do_break(); } #endif @@ -920,16 +913,10 @@ bool IRAM_ATTR gdb_present() void gdb_enable(bool state) { - gdb_enabled = state; + gdb_state.enabled = state; } void IRAM_ATTR gdb_do_break() { gdbstub_do_break(); } - -void IRAM_ATTR gdbstub_ctrl_break() -{ - bitSet(debugFlags, DBGFLAG_CTRL_BREAK); - gdbstub_do_break(); -} diff --git a/Sming/gdb/gdbstub.h b/Sming/gdb/gdbstub.h index b104b20393..4e82d24429 100644 --- a/Sming/gdb/gdbstub.h +++ b/Sming/gdb/gdbstub.h @@ -13,9 +13,9 @@ #ifndef _GDB_GDBSTUB_H_ #define _GDB_GDBSTUB_H_ -#include "gdbstub-internal.h" -#include +#include "gdbstub-internal.h" +#include // GDB_xx macro versions required to ensure no flash access if requested #if GDBSTUB_FORCE_IRAM @@ -28,12 +28,25 @@ #define gdbstub_do_break() asm("break 0,0") -extern bool gdb_attached; -extern bool gdb_enabled; +// Additional debugging flags +enum GdbDebugFlag { + DBGFLAG_DEBUG_EXCEPTION, ///< For debug exceptions, cause is DBGCAUSE (see DebugCause bits) + DBGFLAG_SYSTEM_EXCEPTION, ///< For system exceptions, cause is EXCCAUSE (see EXCCAUSE_* values) + DBGFLAG_CTRL_BREAK, ///< Break caused by call to gdbstub_ctrl_break() +}; + +// State information in shared global structure +struct gdb_state_t { + bool attached; ///< true if GDB is attached to stub + bool enabled; ///< Debugging may be disabled via gdb_enable() + uint8_t flags; ///< Combination of GdbDebugFlag + unsigned ack_count; ///< For discarding of acknowledgement characters +}; + +extern volatile gdb_state_t gdb_state; extern const uint8_t gdb_exception_signals[]; void gdbstub_init(); void gdbstub_handle_exception(UserFrame* frame); -void gdbstub_ctrl_break(); #endif /* _GDB_GDBSTUB_H_ */ diff --git a/Sming/gdb/gdbuart.cpp b/Sming/gdb/gdbuart.cpp index afb8b45dc1..f617b2fdf3 100644 --- a/Sming/gdb/gdbuart.cpp +++ b/Sming/gdb/gdbuart.cpp @@ -23,10 +23,10 @@ static uart_t* gdb_uart; // Port debugger is attached to #if GDBSTUB_ENABLE_UART2 -static uart_t* user_uart; // If open, virtual port being used for user passthrough -static volatile bool userDataSending; // Transmit completion callback invoked on user uart -static volatile unsigned userPacketCount; // For discarding of acknowledgement characters -static bool sendUserDataQueued; // Ensures only one call to sendUserData() is queued at a time +static uart_t* user_uart; // If open, virtual port being used for user passthrough +static uint32_t user_uart_status; // See gdb_uart_callback (ISR handler) +static volatile bool userDataSending; // Transmit completion callback invoked on user uart +static bool sendUserDataQueued; // Ensures only one call to gdbstub_send_user_data() is queued at a time #endif // Get number of characters in receive FIFO @@ -105,9 +105,6 @@ static size_t ATTR_GDBEXTERNFN gdb_uart_write_char(char c) return 1; } -/* - * Receive a char from the uart. Uses polling and feeds the watchdog. - */ int ATTR_GDBEXTERNFN gdbReceiveChar() { #if GDBSTUB_UART_READ_TIMEOUT @@ -133,9 +130,6 @@ int ATTR_GDBEXTERNFN gdbReceiveChar() return -1; } -/* - * Send a block of data to the uart - */ size_t ATTR_GDBEXTERNFN gdbSendData(const void* data, size_t length) { #if GDBSTUB_ENABLE_DEBUG >= 3 @@ -144,9 +138,6 @@ size_t ATTR_GDBEXTERNFN gdbSendData(const void* data, size_t length) return gdb_uart_write(data, length); } -/* - * Send a char to the uart - */ size_t ATTR_GDBEXTERNFN gdbSendChar(char c) { #if GDBSTUB_ENABLE_DEBUG >= 3 @@ -156,58 +147,53 @@ size_t ATTR_GDBEXTERNFN gdbSendChar(char c) } #if GDBSTUB_ENABLE_UART2 -/** - * @brief Send some user data from the user_uart TX buffer to the GDB serial port, - * packetising it if necessary. - * @note Data flows from user uart TX buffer to UART0 either during uart_write() call - * (via notify callback) or via task callback queued from ISR. We don't do this inside - * the ISR as all the code (including packetising) would need to be in IRAM. - */ -static void sendUserData() +void gdbstub_send_user_data() { - sendUserDataQueued = false; + size_t avail = 0; auto txbuf = user_uart == nullptr ? nullptr : user_uart->tx_buffer; - if(txbuf == nullptr) { - return; // Uart not open or tx not enabled - } + if(txbuf != nullptr) { + void* data; + while((avail = txbuf->getReadData(data)) != 0) { + size_t charCount; + unsigned used = uart_txfifo_count(GDB_UART); + unsigned space = UART_TX_FIFO_SIZE - used - 1; + if(gdb_state.attached) { + // $Onn#CC is smallest packet, for a single character, but we want to avoid that as it's inefficient + if(used >= 8) { + break; + } - void* data; - size_t avail; - while((avail = user_uart->tx_buffer->getReadData(data)) != 0) { - size_t charCount; - unsigned used = uart_txfifo_count(GDB_UART); - unsigned space = UART_TX_FIFO_SIZE - used - 1; - if(gdb_attached) { - // $Onn#CC is smallest packet, for a single character, but we want to avoid that as it's inefficient - if(used >= 8) { - break; + charCount = std::min((space - 3) / 2, avail); + GdbPacket packet; + packet.writeChar('O'); + packet.writeHexBlock(data, charCount); + uart_disable_interrupts(); + ++gdb_state.ack_count; + uart_restore_interrupts(); + } else { + charCount = gdb_uart_write(data, std::min(space, avail)); } - charCount = std::min((space - 3) / 2, avail); - GdbPacket packet; - packet.writeChar('O'); - packet.writeHexBlock(data, charCount); - ETS_UART_INTR_DISABLE(); - ++userPacketCount; - ETS_UART_INTR_ENABLE(); - } else { - charCount = gdb_uart_write(data, std::min(space, avail)); - } - - userDataSending = true; + userDataSending = true; - user_uart->tx_buffer->skipRead(charCount); - if(charCount != avail) { - break; // That's all for now + user_uart->tx_buffer->skipRead(charCount); + if(charCount != avail) { + break; // That's all for now + } } } + +static void SendUserDataQueued(uint32_t) +{ + sendUserDataQueued = false; + gdbstub_send_user_data(); } __forceinline void queueSendUserData() { if(!sendUserDataQueued) { - System.queueCallback(TaskCallback(sendUserData)); + System.queueCallback(SendUserDataQueued); sendUserDataQueued = true; } } @@ -234,7 +220,7 @@ static void userUartNotify(uart_t* uart, uart_notify_code_t code) * loop indefinitely if UART_OPT_TXWAIT is set. */ if(uart->tx_buffer->isFull()) { - sendUserData(); + gdbstub_send_user_data(); } else { queueSendUserData(); } @@ -250,7 +236,7 @@ static void userUartNotify(uart_t* uart, uart_notify_code_t code) static void IRAM_ATTR gdb_uart_callback(uart_t* uart, uint32_t status) { #if GDBSTUB_ENABLE_UART2 - uint32_t user_status = status; + user_uart_status = status; // TX FIFO empty ? if(bitRead(status, UIFE)) { @@ -265,13 +251,13 @@ static void IRAM_ATTR gdb_uart_callback(uart_t* uart, uint32_t status) if(!txbuf->isEmpty()) { // Yes - send it via task callback (because packetising code may not be in IRAM) queueSendUserData(); - bitClear(user_status, UIFE); + bitClear(user_uart_status, UIFE); } else if(userDataSending) { // User data has now all been sent - UIFE will remain set userDataSending = false; } else { // No user data was in transit, so this event doesn't apply to user uart - bitClear(user_status, UIFE); + bitClear(user_uart_status, UIFE); } } } @@ -287,46 +273,52 @@ static void IRAM_ATTR gdb_uart_callback(uart_t* uart, uint32_t status) while(uart_rxfifo_count(GDB_UART) != 0) { char c = USF(GDB_UART); #if GDBSTUB_CTRLC_BREAK - bool breakCheck = gdb_enabled; + bool breakCheck = gdb_state.enabled; #if GDBSTUB_CTRLC_BREAK == 1 - if(!gdb_attached) { + if(!gdb_state.attached) { breakCheck = false; } #endif if(breakCheck && c == '\x03') { - gdbstub_ctrl_break(); + bitSet(gdb_state.flags, DBGFLAG_CTRL_BREAK); + gdbstub_do_break(); continue; } #endif + continue; + } +#endif + #if GDBSTUB_ENABLE_UART2 if(rxbuf != nullptr) { - if(userPacketCount != 0) { - --userPacketCount; // Discard acknowledgement '+' - } else { - if(rxbuf->writeChar(c) == 0) { - bitSet(user_status, UIOF); // Overflow - } - isUserData = true; + if(gdb_state.ack_count != 0 && (c == '-' || c == '+')) { + --gdb_state.ack_count; // Discard acknowledgement '+' + } else if(rxbuf->writeChar(c) == 0) { + bitSet(user_uart_status, UIOF); // Overflow } } #endif } - // We cleared status flags above, but this one gets re-set almost immediately so clear it again now - USIC(GDB_UART) = _BV(UITO); - #if GDBSTUB_ENABLE_UART2 - if(!isUserData) { - // This event doesn't apply to user uart - bitClear(user_status, UIFF); - bitClear(user_status, UITO); + if(rxbuf != nullptr) { + if(rxbuf->isEmpty()) { + // This event doesn't apply to user uart + bitClear(user_uart_status, UIFF); + bitClear(user_uart_status, UITO); + } else if(rxbuf->getFreeSpace() > UART_RX_FIFO_SIZE + user_uart->rx_headroom) { + bitClear(user_uart_status, UIFF); + } } #endif } #if GDBSTUB_ENABLE_UART2 - if(user_status != 0 && user_uart != nullptr && user_uart->callback != nullptr) { - user_uart->callback(user_uart, user_status); + if(user_uart_status != 0 && user_uart != nullptr) { + user_uart->status |= user_uart_status; + if(user_uart->callback != nullptr) { + user_uart->callback(user_uart, user_uart_status); + } } #endif } diff --git a/Sming/gdb/gdbuart.h b/Sming/gdb/gdbuart.h index cb0e673c33..17fd3f48e2 100644 --- a/Sming/gdb/gdbuart.h +++ b/Sming/gdb/gdbuart.h @@ -15,8 +15,28 @@ bool gdb_uart_init(); +/* + * Receive a char from the uart. Uses polling and feeds the watchdog. + */ int gdbReceiveChar(); + +/* + * Send a block of data to the uart + */ size_t gdbSendData(const void* data, size_t length); + +/* + * Send a char to the uart + */ size_t gdbSendChar(char c); +/** + * @brief Send some user data from the user_uart TX buffer to the GDB serial port, + * packetising it if necessary. + * @note Data flows from user uart TX buffer to UART0 either during uart_write() call + * (via notify callback) or via task callback queued from ISR. We don't do this inside + * the ISR as all the code (including packetising) would need to be in IRAM. + */ +void gdbstub_send_user_data(); + #endif /* _GDB_GDBUART_H_ */ diff --git a/Sming/system/include/espinc/uart.h b/Sming/system/include/espinc/uart.h index e1ed4fb91a..1048402b5e 100644 --- a/Sming/system/include/espinc/uart.h +++ b/Sming/system/include/espinc/uart.h @@ -119,7 +119,13 @@ typedef struct uart_ uart_t; /** @brief callback invoked directly from ISR * @param arg the UART object * @param status UART USIS STATUS flag bits indicating cause of interrupt - * @param param user-supplied callback parameter + * @note Values can be: + * UIFE: TX FIFO Empty + * UIFF: RX FIFO Full + * UITO: RX FIFO Timeout + * UIBD: Break Detected + * + * Errors can be detected via uart_get_status(). */ typedef void (*uart_callback_t)(uart_t* uart, uint32_t status); @@ -167,6 +173,8 @@ struct uart_ { uint8_t options; uint8_t rx_pin; uint8_t tx_pin; + uint8_t rx_headroom; ///< Callback when rx_buffer free space <= headroom + uint16_t status; ///< All status flags reported to callback since last uart_get_status() call struct SerialBuffer* rx_buffer; ///< Optional receive buffer struct SerialBuffer* tx_buffer; ///< Optional transmit buffer uart_callback_t callback; ///< Optional User callback routine @@ -230,6 +238,19 @@ static inline void uart_set_options(uart_t* uart, uart_options_t options) uart->options = options; } +/** @brief Get error flags and clear them + * @param uart + * @retval Status error bits: + * @note To detect errors during a transaction, call at the start to clear the flags, + * then check the value at the end. + * Only these values are cleared/returned: + * UIBD: Break Detected + * UIOF: RX FIFO OverFlow + * UIFR: Frame Error + * UIPE: Parity Error + */ +uint8_t IRAM_ATTR uart_get_status(uart_t* uart); + static inline uart_options_t uart_get_options(uart_t* uart) { return uart ? uart->options : 0; @@ -384,6 +405,15 @@ void uart_detach(int uart_nr); void uart_detach_all(); +/** @brief disable interrupts and return current interrupt state + * @retval state non-zero if any UART interrupts were active + */ +uint8_t uart_disable_interrupts(); + +/** @brief re-enable interrupts after calling uart_disable_interrupts() + */ +void uart_restore_interrupts(); + #if defined (__cplusplus) } // extern "C" #endif diff --git a/Sming/system/uart.cpp b/Sming/system/uart.cpp index 342a32b111..767364117f 100644 --- a/Sming/system/uart.cpp +++ b/Sming/system/uart.cpp @@ -51,6 +51,24 @@ #include "SerialBuffer.h" +/* + * Parameters relating to RX FIFO and buffer thresholds + * + * 'headroom' is the number of characters which may be received before a receive overrun + * condition occurs and data is lost. + * + * For the hardware FIFO, data is processed via interrupt so the headroom can be fairly small. + * The greater the headroom, the more interrupts will be generated thus reducing efficiency. + */ +#define RX_FIFO_FULL_THRESHOLD 120 ///< UIFF interrupt when FIFO bytes > threshold +#define RX_FIFO_HEADROOM (UART_RX_FIFO_SIZE - RX_FIFO_FULL_THRESHOLD) ///< Chars between UIFF and UIOF +/* + * Using a buffer, data is typically processed via task callback so requires additional time. + * This figure is set to a nominal default which should provide robust operation for most situations. + * It can be adjusted if necessary via the rx_headroom parameter. +*/ +#define DEFAULT_RX_HEADROOM (32 - RX_FIFO_HEADROOM) + static int s_uart_debug_nr = UART_NO; // Get number of characters in receive FIFO @@ -107,18 +125,13 @@ uart_t* uart_get_uart(uint8_t uart_nr) return (uart_nr < UART_COUNT) ? uartInstances[uart_nr] : nullptr; } -/** @brief disable interrupts and return current interrupt state - * @retval state non-zero if any UART interrupts were active - */ -__forceinline static uint8_t uart_disable_interrupts() +uint8_t uart_disable_interrupts() { ETS_UART_INTR_DISABLE(); return isrMask; } -/** @brief re-enable interrupts after calling uart_disable_interrupts() - */ -__forceinline static void uart_restore_interrupts() +void uart_restore_interrupts() { if(isrMask != 0) { ETS_UART_INTR_ENABLE(); @@ -204,7 +217,7 @@ size_t uart_resize_rx_buffer(uart_t* uart, size_t new_size) size_t uart_rx_buffer_size(uart_t* uart) { - return uart && uart->rx_buffer ? uart->rx_buffer->getSize() : 0; + return uart != nullptr && uart->rx_buffer != nullptr ? uart->rx_buffer->getSize() : 0; } size_t uart_resize_tx_buffer(uart_t* uart, size_t new_size) @@ -251,18 +264,20 @@ size_t uart_read(uart_t* uart, void* buffer, size_t size) auto buf = static_cast(buffer); - // If RX buffer not in use or it's empty then read directly from hardware FIFO + // First read data from RX buffer if in use if(uart->rx_buffer != nullptr) { while(read < size && !uart->rx_buffer->isEmpty()) buf[read++] = uart->rx_buffer->readChar(); } + // Top up from hardware FIFO if(is_physical(uart)) { while(read < size && uart_rxfifo_count(uart->uart_nr) != 0) { buf[read++] = USF(uart->uart_nr); } // FIFO full may have been disabled if buffer overflowed, re-enabled it now + USIC(uart->uart_nr) = _BV(UIFF) | _BV(UITO); USIE(uart->uart_nr) |= _BV(UIFF) | _BV(UITO); } @@ -302,9 +317,6 @@ static void IRAM_ATTR handle_uart_interrupt(uint8_t uart_nr, uart_t* uart) return; } - // Clear all status before proceeeding - USIC(uart_nr) = usis; - /* * If we haven't asked for interrupts on this UART, then disable all interrupt sources for it. * @@ -319,6 +331,9 @@ static void IRAM_ATTR handle_uart_interrupt(uint8_t uart_nr, uart_t* uart) return; } + // Value to be passed to callback + uint32_t status = usis; + // Deal with the event, unless we're in raw mode if(!bitRead(uart->options, UART_OPT_CALLBACK_RAW)) { // Rx FIFO full or timeout @@ -327,15 +342,20 @@ static void IRAM_ATTR handle_uart_interrupt(uint8_t uart_nr, uart_t* uart) // Read as much data as possible from the RX FIFO into buffer if(uart->rx_buffer != nullptr) { + size_t avail = uart_rxfifo_count(uart_nr); size_t space = uart->rx_buffer->getFreeSpace(); - while(space-- && uart_rxfifo_count(uart_nr) != 0) { + read = (avail <= space) ? avail : space; + space -= read; + avail -= read; + for(size_t i = 0; i < read; ++i) { uart->rx_buffer->writeChar(USF(uart_nr)); - ++read; } - } - // We cleared status flags above, but this one gets re-set almost immediately so clear it again now - USIC(uart_nr) = _BV(UITO); + // Don't call back until buffer is (almost) full + if(space > uart->rx_headroom) { + bitClear(status, UIFF); + } + } /* * If the FIFO is full and we didn't read any of the data then need to mask the interrupt out or it'll recur. @@ -348,28 +368,39 @@ static void IRAM_ATTR handle_uart_interrupt(uint8_t uart_nr, uart_t* uart) // Unless we replenish TX FIFO, disable after handling interrupt if(bitRead(usis, UIFE)) { + size_t space = uart_txfifo_free(uart_nr); + // Dump as much data as we can from buffer into the TX FIFO if(uart->tx_buffer != nullptr) { size_t avail = uart->tx_buffer->available(); - while(avail-- && !uart_txfifo_full(uart_nr)) { + size_t count = (avail <= space) ? avail : space; + space -= count; + avail -= count; + while(count-- != 0) { USF(uart_nr) = uart->tx_buffer->readChar(); } } + // If TX FIFO remains empty then we must disable TX FIFO EMPTY interrupt to stop it recurring. if(uart_txfifo_count(uart_nr) == 0) { - // If TX FIFO remains empty then we must disable TX FIFO EMPTY interrupt to stop it recurring. // The interrupt gets re-enabled by uart_write() bitClear(USIE(uart_nr), UIFE); } else { // We've topped up TX FIFO so defer callback until next time - bitClear(usis, UIFE); + bitClear(status, UIFE); } } } - if(usis != 0 && uart->callback != nullptr) { - uart->callback(uart, usis); + // Keep a note of persistent flags - cleared via uart_get_status() + uart->status |= status; + + if(status != 0 && uart->callback != nullptr) { + uart->callback(uart, status); } + + // Final step is to clear status flags + USIC(uart_nr) = usis; } /** @brief UART interrupt service routine @@ -396,7 +427,19 @@ void uart_start_isr(uart_t* uart) * UCTOE: RX TimeOut Enable */ usc1 = (120 << UCFFT) | (0x02 << UCTOT) | _BV(UCTOE); - usie = _BV(UIFF) | _BV(UIFR) | _BV(UITO); + /* + * UIBD: Break Detected + * UIOF: RX FIFO OverFlow + * UIFF: RX FIFO Full + * UIFR: Frame Error + * UIPE: Parity Error + * UITO: RX FIFO Timeout + * + * There is little benefit in generating interrupts on errors, instead these + * should be cleared at the start of a transaction and checked at the end. + * See uart_get_status(). + */ + usie = _BV(UIFF) | _BV(UITO) | _BV(UIBD); } if(uart_tx_enabled(uart)) { @@ -447,6 +490,9 @@ size_t uart_write(uart_t* uart, const void* buffer, size_t size) while(written < size && !uart_txfifo_full(uart->uart_nr)) { USF(uart->uart_nr) = buf[written++]; } + // Enable TX FIFO EMPTY interrupt + USIC(uart->uart_nr) = _BV(UIFE); + bitSet(USIE(uart->uart_nr), UIFE); } } @@ -464,11 +510,6 @@ size_t uart_write(uart_t* uart, const void* buffer, size_t size) } } - // Enable TX FIFO EMPTY interrupt - if(written != 0 && isPhysical) { - bitSet(USIE(uart->uart_nr), UIFE); - } - return written; } @@ -508,6 +549,24 @@ void uart_wait_tx_empty(uart_t* uart) } } +uint8_t uart_get_status(uart_t* uart) +{ + uint8_t status = 0; + if(uart != nullptr) { + uart_disable_interrupts(); + // Get break/overflow flags from actual uart (physical or otherwise) + status = uart->status & (_BV(UIBD) | _BV(UIOF)); + uart->status = 0; + // Read raw status register directly from real uart, masking out non-error bits + uart = get_physical(uart); + status |= USIR(uart->uart_nr) & (_BV(UIBD) | _BV(UIOF) | _BV(UIFR) | _BV(UIPE)); + // Clear errors + USIC(uart->uart_nr) = status; + uart_restore_interrupts(); + } + return status; +} + void uart_flush(uart_t* uart) { if(uart == nullptr) { @@ -590,6 +649,7 @@ uart_t* uart_init_ex(const uart_config& cfg) uart->options = cfg.options; uart->tx_pin = 255; uart->rx_pin = 255; + uart->rx_headroom = DEFAULT_RX_HEADROOM; auto rxBufferSize = cfg.rx_size; auto txBufferSize = cfg.tx_size; From 5f8ef4004d185a109918fd0e5c4716ad677863ae Mon Sep 17 00:00:00 2001 From: mikee47 Date: Tue, 2 Apr 2019 13:54:27 +0100 Subject: [PATCH 06/50] Add uart notification to support `uart_wait_tx()` function, and handle in gdbstub --- Sming/gdb/gdbuart.cpp | 10 ++++++++++ Sming/system/include/espinc/uart.h | 3 +++ Sming/system/uart.cpp | 2 ++ 3 files changed, 15 insertions(+) diff --git a/Sming/gdb/gdbuart.cpp b/Sming/gdb/gdbuart.cpp index f617b2fdf3..83f678559d 100644 --- a/Sming/gdb/gdbuart.cpp +++ b/Sming/gdb/gdbuart.cpp @@ -227,6 +227,16 @@ static void userUartNotify(uart_t* uart, uart_notify_code_t code) break; } + case UART_NOTIFY_WAIT_TX: { + /* + * Ensure all data has been written to hardware + */ + while(!uart->tx_buffer->isEmpty()) { + gdbstub_send_user_data(); + } + break; + } + case UART_NOTIFY_BEFORE_READ: break; } diff --git a/Sming/system/include/espinc/uart.h b/Sming/system/include/espinc/uart.h index 1048402b5e..f5575a666a 100644 --- a/Sming/system/include/espinc/uart.h +++ b/Sming/system/include/espinc/uart.h @@ -148,6 +148,9 @@ enum uart_notify_code_t { /** @brief Called before data is read from rx buffer */ UART_NOTIFY_BEFORE_READ, + + /** @brief Called to ensure all buffered data is output */ + UART_NOTIFY_WAIT_TX, }; /** @brief Port notification callback function type diff --git a/Sming/system/uart.cpp b/Sming/system/uart.cpp index 767364117f..7cd35d73d4 100644 --- a/Sming/system/uart.cpp +++ b/Sming/system/uart.cpp @@ -537,6 +537,8 @@ void uart_wait_tx_empty(uart_t* uart) return; } + notify(uart, UART_NOTIFY_WAIT_TX); + if(uart->tx_buffer != nullptr) { while(!uart->tx_buffer->isEmpty()) { delay(0); From d95df2111122abe1d0700532d4464bb8ee3f061f Mon Sep 17 00:00:00 2001 From: mikee47 Date: Tue, 2 Apr 2019 13:55:05 +0100 Subject: [PATCH 07/50] Add mode parameter to uart_flush() so tx/rx can be flushed independently, e.g. if a receive overrun occurs. --- Sming/SmingCore/HardwareSerial.h | 7 +++--- Sming/system/include/espinc/uart.h | 3 ++- Sming/system/uart.cpp | 35 ++++++++++++++++++++---------- 3 files changed, 30 insertions(+), 15 deletions(-) diff --git a/Sming/SmingCore/HardwareSerial.h b/Sming/SmingCore/HardwareSerial.h index f80af9776e..1e9821728d 100644 --- a/Sming/SmingCore/HardwareSerial.h +++ b/Sming/SmingCore/HardwareSerial.h @@ -280,11 +280,12 @@ class HardwareSerial : public ReadWriteStream } /** @brief Clear the serial port transmit/receive buffers - * @note All un-read buffered data is removed + * @param mode Whether to flush TX, RX or both (the default) + * @note All un-read buffered data is removed and any error condition cleared */ - void clear() + void clear(SerialMode mode = SERIAL_FULL) { - uart_flush(uart); + uart_flush(uart, uart_mode_t(mode)); } /** @brief Flush all pending data to the serial port diff --git a/Sming/system/include/espinc/uart.h b/Sming/system/include/espinc/uart.h index f5575a666a..f8d7a00a76 100644 --- a/Sming/system/include/espinc/uart.h +++ b/Sming/system/include/espinc/uart.h @@ -374,9 +374,10 @@ void uart_wait_tx_empty(uart_t* uart); /** @brief discard any buffered data and reset hardware FIFOs * @param uart + * @param mode Whether to flush TX, RX or both (the default) * @note this function does not wait for any transmissions to complete */ -void uart_flush(uart_t* uart); +void uart_flush(uart_t* uart, uart_mode_t mode = UART_FULL); void uart_set_debug(int uart_nr); int uart_get_debug(); diff --git a/Sming/system/uart.cpp b/Sming/system/uart.cpp index 7cd35d73d4..7aab0e2d23 100644 --- a/Sming/system/uart.cpp +++ b/Sming/system/uart.cpp @@ -569,33 +569,46 @@ uint8_t uart_get_status(uart_t* uart) return status; } -void uart_flush(uart_t* uart) +void uart_flush(uart_t* uart, uart_mode_t mode) { if(uart == nullptr) { return; } + bool flushRx = mode != UART_TX_ONLY && uart->mode != UART_TX_ONLY; + bool flushTx = mode != UART_RX_ONLY && uart->mode != UART_RX_ONLY; + uart_disable_interrupts(); - if(uart->rx_buffer != nullptr) { + if(flushRx && uart->rx_buffer != nullptr) { uart->rx_buffer->clear(); } - if(uart->tx_buffer != nullptr) { + if(flushTx && uart->tx_buffer != nullptr) { uart->tx_buffer->clear(); } if(is_physical(uart)) { - // Prevent TX FIFO EMPTY interrupts - don't need them until uart_write is called again - bitClear(USIE(uart->uart_nr), UIFE); + // Clear the hardware FIFOs + uint32_t flushBits = 0; + if(flushTx) { + bitSet(flushBits, UCTXRST); + } + if(flushRx) { + bitSet(flushBits, UCRXRST); + } + USC0(uart->uart_nr) |= flushBits; + USC0(uart->uart_nr) &= ~flushBits; - // If receive overflow occurred then these interrupts will be masked - if(uart_rx_enabled(uart)) { - USIE(uart->uart_nr) |= _BV(UIFF) | _BV(UITO); + if(flushTx) { + // Prevent TX FIFO EMPTY interrupts - don't need them until uart_write is called again + bitClear(USIE(uart->uart_nr), UIFE); } - uint32_t tmp = _BV(UCRXRST) | _BV(UCTXRST); - USC0(uart->uart_nr) |= tmp; - USC0(uart->uart_nr) &= ~tmp; + // If receive overflow occurred then these interrupts will be masked + if(flushRx) { + USIC(uart->uart_nr) = 0xffff & ~_BV(UIFE); + USIE(uart->uart_nr) |= _BV(UIFF) | _BV(UITO); // | _BV(UIOF); + } } uart_restore_interrupts(); From b4ef2d51a3b19ab22e9ba449f601540e2c3b02ca Mon Sep 17 00:00:00 2001 From: mikee47 Date: Tue, 2 Apr 2019 14:03:06 +0100 Subject: [PATCH 08/50] Add `uart_set_break()` function --- Sming/system/include/espinc/uart.h | 6 ++++++ Sming/system/uart.cpp | 8 ++++++++ 2 files changed, 14 insertions(+) diff --git a/Sming/system/include/espinc/uart.h b/Sming/system/include/espinc/uart.h index f8d7a00a76..3fa55ce26b 100644 --- a/Sming/system/include/espinc/uart.h +++ b/Sming/system/include/espinc/uart.h @@ -372,6 +372,12 @@ size_t uart_tx_free(uart_t* uart); /** @deprecated don't use this - causes extended delays - use callback notification */ void uart_wait_tx_empty(uart_t* uart); +/** @brief Set or clear a break condition on the TX line + * @param uart + * @param state + */ +void uart_set_break(uart_t* uart, bool state); + /** @brief discard any buffered data and reset hardware FIFOs * @param uart * @param mode Whether to flush TX, RX or both (the default) diff --git a/Sming/system/uart.cpp b/Sming/system/uart.cpp index 7aab0e2d23..1403a9e95e 100644 --- a/Sming/system/uart.cpp +++ b/Sming/system/uart.cpp @@ -551,6 +551,14 @@ void uart_wait_tx_empty(uart_t* uart) } } +void uart_set_break(uart_t* uart, bool state) +{ + uart = get_physical(uart); + if(uart != nullptr) { + bitWrite(USC0(uart->uart_nr), UCBRK, state); + } +} + uint8_t uart_get_status(uart_t* uart) { uint8_t status = 0; From fc1dbbeaa0f113a3dba92c9b85df1d6dfe91f9f3 Mon Sep 17 00:00:00 2001 From: mikee47 Date: Tue, 2 Apr 2019 14:05:36 +0100 Subject: [PATCH 09/50] Add support for GDB system calls and update LiveDebug sample --- Sming/appspecific/gdb/gdb_hooks.cpp | 9 +- Sming/gdb/GdbPacket.cpp | 15 +- Sming/gdb/GdbPacket.h | 12 +- Sming/gdb/gdbcmds | 3 + Sming/gdb/gdbstub-cfg.h | 8 + Sming/gdb/gdbstub.cpp | 32 +++- Sming/gdb/gdbstub.h | 8 + Sming/gdb/gdbsyscall.cpp | 252 +++++++++++++++++++++++++ Sming/gdb/gdbsyscall.h | 33 ++++ Sming/gdb/gdbuart.cpp | 15 ++ Sming/system/include/gdb_syscall.h | 255 ++++++++++++++++++++++++++ samples/LiveDebug/app/application.cpp | 254 +++++++++++++++++++++++++ 12 files changed, 881 insertions(+), 15 deletions(-) create mode 100644 Sming/gdb/gdbsyscall.cpp create mode 100644 Sming/gdb/gdbsyscall.h create mode 100644 Sming/system/include/gdb_syscall.h diff --git a/Sming/appspecific/gdb/gdb_hooks.cpp b/Sming/appspecific/gdb/gdb_hooks.cpp index 5eadf08463..dc3fd745b5 100644 --- a/Sming/appspecific/gdb/gdb_hooks.cpp +++ b/Sming/appspecific/gdb/gdb_hooks.cpp @@ -8,7 +8,8 @@ * ****/ -#include "gdb_hooks.h" +#include +#include #include "gdb/gdbstub.h" #include "gdb/gdbstub-entry.h" #include "gdb/exceptions.h" @@ -179,7 +180,6 @@ void ATTR_GDBINIT gdb_init() #ifndef ENABLE_GDB extern "C" { - static bool IRAM_ATTR __gdb_no_op() { return false; @@ -191,3 +191,8 @@ bool gdb_present(void) __attribute__((weak, alias("__gdb_no_op"))); }; #endif + +int __attribute__((weak)) gdb_syscall(const GdbSyscallInfo& info) +{ + return -1; +} diff --git a/Sming/gdb/GdbPacket.cpp b/Sming/gdb/GdbPacket.cpp index ab7fa0f2e1..1c1bc5cc11 100644 --- a/Sming/gdb/GdbPacket.cpp +++ b/Sming/gdb/GdbPacket.cpp @@ -12,9 +12,6 @@ #include "GdbPacket.h" -// Swap byte ordering -#define bswap32(value) __builtin_bswap32(value) - // Send the start of a packet; reset checksum calculation. void ATTR_GDBEXTERNFN GdbPacket::start() { @@ -53,6 +50,18 @@ void ATTR_GDBEXTERNFN GdbPacket::write(const void* data, unsigned length) packetLength += length; } +void ATTR_GDBEXTERNFN GdbPacket::writeStr(const char* str) +{ + write(str, strlen(str)); +} + +void ATTR_GDBEXTERNFN GdbPacket::writeStrRef(const char* str) +{ + writeHexWord32(uint32_t(str)); + writeChar('/'); + writeHexWord16(strlen_P(str) + 1); +} + void ATTR_GDBEXTERNFN GdbPacket::writeX32() { for(int i = 0; i < 8; i++) { diff --git a/Sming/gdb/GdbPacket.h b/Sming/gdb/GdbPacket.h index 0b00710c29..6e24c5c901 100644 --- a/Sming/gdb/GdbPacket.h +++ b/Sming/gdb/GdbPacket.h @@ -6,7 +6,9 @@ * * @author: 2019 - Mikee47 * - * Manages GDB packet encoding + * Manages GDB packet encoding. See here for details: + * + * https://sourceware.org/gdb/current/onlinedocs/gdb/Remote-Protocol.html * ****/ @@ -59,10 +61,10 @@ class GdbPacket void write(const void* data, unsigned length); /** @brief Output a null-terminated string exactly as given without escaping */ - __forceinline void ATTR_GDBEXTERNFN writeStr(const char* str) - { - write(str, strlen(str)); - } + void writeStr(const char* str); + + /** @brief Output a string reference in addr/len format */ + void writeStrRef(const char* str); size_t getLength() { diff --git a/Sming/gdb/gdbcmds b/Sming/gdb/gdbcmds index cb40f0946f..5d63cf6acb 100644 --- a/Sming/gdb/gdbcmds +++ b/Sming/gdb/gdbcmds @@ -5,6 +5,9 @@ set remote hardware-breakpoint-limit 1 set remote hardware-watchpoint-limit 1 +# Enable the 'system' syscall +set remote system-call-allowed 1 + # Some GDBstub settings set remote interrupt-on-connect on set remote kill-packet off diff --git a/Sming/gdb/gdbstub-cfg.h b/Sming/gdb/gdbstub-cfg.h index 5bb29c07a6..e3fbd95ca9 100644 --- a/Sming/gdb/gdbstub-cfg.h +++ b/Sming/gdb/gdbstub-cfg.h @@ -130,6 +130,14 @@ #define GDBSTUB_ENABLE_UART2 1 #endif +/* + * Enable gdb_syscall_* functions for use by application. + * If undefined, calls will do nothing and return -1. + */ +#ifndef GDBSTUB_ENABLE_SYSCALL +#define GDBSTUB_ENABLE_SYSCALL 1 +#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. diff --git a/Sming/gdb/gdbstub.cpp b/Sming/gdb/gdbstub.cpp index 7d00f6931a..fba2bda91b 100644 --- a/Sming/gdb/gdbstub.cpp +++ b/Sming/gdb/gdbstub.cpp @@ -513,6 +513,20 @@ static GdbResult ATTR_GDBEXTERNFN handleCommand(unsigned cmdLen) sendOK(); return ST_DETACH; +#if GDBSTUB_ENABLE_SYSCALL + /* + * A file (or console) I/O request has finished. + */ + case 'F': + if(gdb_syscall_complete(data)) { + // Ctrl+C was pressed + sendReason(); + return ST_OK; + } else { + return ST_CONT; + } +#endif + /* * Kill * @@ -697,13 +711,17 @@ static GdbResult ATTR_GDBEXTERNFN handleCommand(unsigned cmdLen) * It is not necessary for gdb to be attached for it to be paused * For example, during an exception break, the program is paused but gdb might not be attached yet */ -GdbResult ATTR_GDBEXTERNFN commandLoop() +GdbResult ATTR_GDBEXTERNFN commandLoop(bool waitForStart = true) { GdbResult result = ST_OK; do { - while(gdbReceiveChar() != '$') { - // wait for start + if(waitForStart) { + while(gdbReceiveChar() != '$') { + // wait for start + } } + waitForStart = true; + auto cmdLen = readCommand(); if(cmdLen != 0) { gdb_state.attached = true; @@ -803,8 +821,12 @@ static void __attribute__((noinline)) gdbstub_handle_debug_exception_flash() singleStepPs = -1; } - sendReason(); - commandLoop(); + if(bitRead(gdb_state.flags, DBGFLAG_PACKET_STARTED)) { + commandLoop(false); + } else { + sendReason(); + commandLoop(); + } } // Watchpoint ? diff --git a/Sming/gdb/gdbstub.h b/Sming/gdb/gdbstub.h index 4e82d24429..99c246396d 100644 --- a/Sming/gdb/gdbstub.h +++ b/Sming/gdb/gdbstub.h @@ -33,12 +33,20 @@ enum GdbDebugFlag { DBGFLAG_DEBUG_EXCEPTION, ///< For debug exceptions, cause is DBGCAUSE (see DebugCause bits) DBGFLAG_SYSTEM_EXCEPTION, ///< For system exceptions, cause is EXCCAUSE (see EXCCAUSE_* values) DBGFLAG_CTRL_BREAK, ///< Break caused by call to gdbstub_ctrl_break() + DBGFLAG_PACKET_STARTED, ///< Incoming packet detected by uart interrupt handler +}; + +enum SyscallState { + syscall_ready, ///< Ready for new syscall + syscall_pending, ///< Syscall queued but not yet sent to GDB + syscall_active, ///< Syscall executing, awaiting response from GDB }; // State information in shared global structure struct gdb_state_t { bool attached; ///< true if GDB is attached to stub bool enabled; ///< Debugging may be disabled via gdb_enable() + SyscallState syscall; ///< State of system call uint8_t flags; ///< Combination of GdbDebugFlag unsigned ack_count; ///< For discarding of acknowledgement characters }; diff --git a/Sming/gdb/gdbsyscall.cpp b/Sming/gdb/gdbsyscall.cpp new file mode 100644 index 0000000000..7cb1543bdd --- /dev/null +++ b/Sming/gdb/gdbsyscall.cpp @@ -0,0 +1,252 @@ +/**** + * Sming Framework Project - Open Source framework for high efficiency native ESP8266 development. + * Created 2015 by Skurydin Alexey + * http://github.com/SmingHub/Sming + * All files of the Sming Core are provided under the LGPL v3 license. + * + * gdbsyscall.cpp - Support for GDB file I/O + * + * @author: 2019 - Mikee47 + * + ****/ + +#include "gdbstub-cfg.h" + +#if GDBSTUB_ENABLE_SYSCALL + +#include +#include "GdbPacket.h" +#include +#include "Platform/System.h" + +// Whilst GCC has intrinsics to do this, they eat IRAM +//#define bswap32(value) __builtin_bswap32(value) +//#define bswap64(value) __builtin_bswap64(value) + +/** @brief Swap byte order in 32-bit value */ +static uint32_t ATTR_GDBEXTERNFN bswap32(uint32_t value) +{ + return (value >> 24) | ((value >> 8) & 0xff00) | ((value << 8) & 0xff0000) | (value << 24); +} + +/** @brief Swap byte order of array of 32-bit values + * @param words + * @param size + */ +static void ATTR_GDBEXTERNFN bswap32p(uint32_t* words, unsigned count) +{ + for(unsigned i = 0; i < count; ++i) { + words[i] = bswap32(words[i]); + } +} + +/** @brief Swap word order in 64-bit value */ +__forceinline void bswap64wp(uint64_t* value) +{ + *value = (*value >> 32) | (*value << 32); +} + +static GdbSyscallInfo syscall_info; ///< The syscall packet + +/* + * If there is user data being packetised we need to wait until that's finished + * before starting a syscall. + * + * If the call is asynchronous, we can set it up now and send it when user data has finished. + * Otherwise we need to force send of uart data via sendUserData() (in gdbuart.cpp). + * + * Both of these deal with packetising whilst the debugger is attached but not paused. + * We should combine this - + */ +int gdb_syscall(const GdbSyscallInfo& info) +{ + if(!gdb_state.attached) { + return -EPERM; + } + if(gdb_state.syscall != syscall_ready) { + return -EAGAIN; + } + + syscall_info = info; + gdb_state.syscall = syscall_pending; + + do { + gdbstub_send_user_data(); + } while(info.callback == nullptr && gdb_state.syscall != syscall_ready); + + return syscall_info.result; +} + +void gdbstub_syscall_execute() +{ + if(gdb_state.syscall != syscall_pending) { + // Nothing to execute + return; + } + + auto& info = syscall_info; + { + GdbPacket packet; + packet.writeChar('F'); + switch(info.command) { + case eGDBSYS_open: + packet.writeStr(GDB_F("open,")); + packet.writeStrRef(info.open.filename); + packet.writeChar(','); + packet.writeHexWord32(info.open.flags); + packet.writeChar(','); + packet.writeHexByte(0); // mode + break; + + case eGDBSYS_close: + packet.writeStr(GDB_F("close,")); + packet.writeHexByte(info.close.fd); + break; + + case eGDBSYS_read: + packet.writeStr(GDB_F("read,")); + packet.writeHexByte(info.read.fd); + packet.writeChar(','); + packet.writeHexWord32(uint32_t(info.read.buffer)); + packet.writeChar(','); + packet.writeHexWord16(info.read.bufSize); + break; + + case eGDBSYS_write: + packet.writeStr(GDB_F("write,")); + packet.writeHexByte(info.write.fd); + packet.writeChar(','); + packet.writeHexWord32(uint32_t(info.write.buffer)); + packet.writeChar(','); + packet.writeHexWord16(info.write.count); + break; + + case eGDBSYS_lseek: + packet.writeStr(GDB_F("lseek,")); + packet.writeHexByte(info.lseek.fd); + packet.writeChar(','); + packet.writeHexWord32(info.lseek.offset); + packet.writeChar(','); + packet.writeHexByte(info.lseek.whence); + break; + + case eGDBSYS_rename: + packet.writeStr(GDB_F("rename,")); + packet.writeStrRef(info.rename.oldpath); + packet.writeChar(','); + packet.writeStrRef(info.rename.newpath); + break; + + case eGDBSYS_unlink: + packet.writeStr(GDB_F("unlink,")); + packet.writeStrRef(info.unlink.pathname); + break; + + case eGDBSYS_stat: + packet.writeStr(GDB_F("stat,")); + packet.writeStrRef(info.stat.pathname); + packet.writeChar(','); + packet.writeHexWord32(uint32_t(info.stat.buf)); + break; + + case eGDBSYS_fstat: + packet.writeStr(GDB_F("fstat,")); + packet.writeHexByte(info.fstat.fd); + packet.writeChar(','); + packet.writeHexWord32(uint32_t(info.fstat.buf)); + break; + + case eGDBSYS_gettimeofday: + packet.writeStr(GDB_F("gettimeofday,")); + packet.writeHexWord32(uint32_t(info.gettimeofday.tv)); + packet.writeChar(','); + packet.writeHexWord32(uint32_t(info.gettimeofday.tz)); + break; + + case eGDBSYS_isatty: + packet.writeStr(GDB_F("isatty,")); + packet.writeHexByte(info.isatty.fd); + break; + + case eGDBSYS_system: + packet.writeStr(GDB_F("system,")); + packet.writeStrRef(info.system.command); + break; + } + } + + // Discard incoming '+' acknolwedgement from packet + ++gdb_state.ack_count; + gdb_state.syscall = syscall_active; + + if(info.callback == nullptr) { + // No callback, wait for completion + while(gdb_state.syscall != syscall_ready) { + // Watchdog will fire if this takes too long - intentional + } + } +} + +bool ATTR_GDBEXTERNFN gdb_syscall_complete(const char* data) +{ + // normally a hex value, but on error it's -1 + bool isNeg = false; + if(*data == '-') { + ++data; + isNeg = true; + } + int len = GdbPacket::readHexValue(data); + if(isNeg) { + len = -len; + } + + char ctrl_c_flag = '\0'; + + if(*data == ',') { + ++data; + uint8_t err = GdbPacket::readHexValue(data); + syscall_info.result = -err; + } else { + syscall_info.result = len; + + switch(syscall_info.command) { + case eGDBSYS_gettimeofday: { + auto tv = syscall_info.gettimeofday.tv; + if(tv != nullptr) { + // Content written in big-endian, so we need to correct it + bswap32p(reinterpret_cast(tv), sizeof(gdb_timeval_t) / 4); + bswap64wp(&tv->tv_usec); + } + break; + } + + case eGDBSYS_stat: + case eGDBSYS_fstat: { + auto stat = syscall_info.stat.buf; + if(stat != nullptr) { + bswap32p(reinterpret_cast(stat), sizeof(gdb_stat_t) / 4); + bswap64wp(&stat->st_size); + bswap64wp(&stat->st_blksize); + bswap64wp(&stat->st_blocks); + } + break; + } + + default:; // No processing required for other commands + } + } + + if(*data == ',') { + ++data; + ctrl_c_flag = *data; + } + + if(syscall_info.callback != nullptr) { + System.queueCallback(TaskCallback(syscall_info.callback), uint32_t(&syscall_info)); + } + gdb_state.syscall = syscall_ready; + + return ctrl_c_flag; +} + +#endif // GDBSTUB_ENABLE_SYSCALL diff --git a/Sming/gdb/gdbsyscall.h b/Sming/gdb/gdbsyscall.h new file mode 100644 index 0000000000..6d1959ab9b --- /dev/null +++ b/Sming/gdb/gdbsyscall.h @@ -0,0 +1,33 @@ +/**** + * Sming Framework Project - Open Source framework for high efficiency native ESP8266 development. + * Created 2015 by Skurydin Alexey + * http://github.com/SmingHub/Sming + * All files of the Sming Core are provided under the LGPL v3 license. + * + * gdbsyscall.h - Support for GDB file I/O + * + * @author: 2019 - Mikee47 + * + ****/ + +#ifndef _GDB_GDBSYSCALL_H_ +#define _GDB_GDBSYSCALL_H_ + +#include "gdbstub.h" + +/** + * @brief Called from gdbstub_send_user_data() when user data has finished sending + */ +void gdbstub_syscall_execute(); + +/** + * @brief Command handler for 'F' + * @param data first character after 'F' + * @retval bool true means Ctrl-C was pressed + * @note A file (or console) I/O request ends with receipt of an 'F' command: + * + * Fretcode,errno,Ctrl-C flag;call-specific attachment + */ +bool gdb_syscall_complete(const char* data); + +#endif /* _GDB_GDBSYSCALL_H_ */ diff --git a/Sming/gdb/gdbuart.cpp b/Sming/gdb/gdbuart.cpp index 83f678559d..6cc1c8b379 100644 --- a/Sming/gdb/gdbuart.cpp +++ b/Sming/gdb/gdbuart.cpp @@ -17,6 +17,7 @@ #include "Platform/System.h" #include "HardwareSerial.h" #include "HardwareTimer.h" +#include "gdbsyscall.h" #define GDB_UART UART0 // Only UART0 supports for debugging as RX/TX required @@ -184,6 +185,14 @@ void gdbstub_send_user_data() } } +#if GDBSTUB_ENABLE_SYSCALL + // When all data has been sent, see if there's a pending syscall to send + if(avail == 0) { + gdbstub_syscall_execute(); + } +#endif +} + static void SendUserDataQueued(uint32_t) { sendUserDataQueued = false; @@ -295,6 +304,12 @@ static void IRAM_ATTR gdb_uart_callback(uart_t* uart, uint32_t status) continue; } #endif + +#if GDBSTUB_ENABLE_SYSCALL + // If packet start detected whilst handling a system call, break into debugger to process it + if(c == '$' && gdb_state.syscall == syscall_active) { + bitSet(gdb_state.flags, DBGFLAG_PACKET_STARTED); + gdbstub_do_break(); continue; } #endif diff --git a/Sming/system/include/gdb_syscall.h b/Sming/system/include/gdb_syscall.h new file mode 100644 index 0000000000..3acfc6e486 --- /dev/null +++ b/Sming/system/include/gdb_syscall.h @@ -0,0 +1,255 @@ +/**** + * Sming Framework Project - Open Source framework for high efficiency native ESP8266 development. + * Created 2015 by Skurydin Alexey + * http://github.com/SmingHub/Sming + * All files of the Sming Core are provided under the LGPL v3 license. + * + * gdb_syscall.h - Support for GDB file I/O + * + * @author: 2019 - Mikee47 + * + * An API is defined for communicating with GDB using the file-I/O protocol, but can also perform + * functions not related to file (or console) I/O. + * + * This is a synchronous protocol, so only one request may be in progress at a time. + * + * To use in your application, build with GDBSTUB_ENABLE_SYSCALL=1 and #include this header. + * + * @see https://sourceware.org/gdb/current/onlinedocs/gdb/File_002dI_002fO-Remote-Protocol-Extension.html + * + ****/ + +#ifndef _SYSTEM_INCLUDE_GDB_SYSCALL_H_ +#define _SYSTEM_INCLUDE_GDB_SYSCALL_H_ + +#include +#include +#include +#include + +/* GDB uses a specific version of the stat structure, 64 bytes in size */ +struct __attribute__((packed)) gdb_stat_t { + uint32_t st_dev; // device + uint32_t st_ino; // inode + mode_t st_mode; // protection + uint32_t st_nlink; // number of hard links + uint32_t st_uid; // user ID of owner + uint32_t st_gid; // group ID of owner + uint32_t st_rdev; // device type (if inode device) + uint64_t st_size; // total size, in bytes + uint64_t st_blksize; // blocksize for filesystem I/O + uint64_t st_blocks; // number of blocks allocated + time_t st_atime; // time of last access + time_t st_mtime; // time of last modification + time_t st_ctime; // time of last change +}; + +/* GDB uses a specific version of the timeval structure, 12 bytes in size (manual says 8, which is wrong) */ +struct __attribute__((packed)) gdb_timeval_t { + time_t tv_sec; // second + uint64_t tv_usec; // microsecond +}; + +/* GDB Syscall interface */ + +enum GdbSyscallCommand { + eGDBSYS_open, + eGDBSYS_close, + eGDBSYS_read, + eGDBSYS_write, + eGDBSYS_lseek, + eGDBSYS_rename, + eGDBSYS_unlink, + eGDBSYS_stat, + eGDBSYS_fstat, + eGDBSYS_gettimeofday, + eGDBSYS_isatty, + eGDBSYS_system, +}; + +/** @brief GDB Syscall completion callback function */ +struct GdbSyscallInfo; +typedef void (*gdb_syscall_callback_t)(const GdbSyscallInfo& info); + +/** @brief GDB Syscall command information */ +struct GdbSyscallInfo { + GdbSyscallCommand command; ///< The syscall command + gdb_syscall_callback_t callback; ///< User-supplied callback (if any) + void* param; ///< User-supplied parameter for callback + int result; ///< Final result of syscall + // Command parameters + union { + struct { + const char* filename; + int flags; + } open; + struct { + int fd; + } close; + struct { + int fd; + void* buffer; + size_t bufSize; + } read; + struct { + int fd; + const void* buffer; + size_t count; + } write; + struct { + int fd; + long offset; + int whence; + } lseek; + struct { + const char* oldpath; + const char* newpath; + } rename; + struct { + const char* pathname; + } unlink; + struct { + const char* pathname; + gdb_stat_t* buf; + } stat; + struct { + int fd; + struct stat* buf; + } fstat; + struct { + gdb_timeval_t* tv; + void* tz; + } gettimeofday; + struct { + int fd; + } isatty; + struct { + const char* command; + } system; + }; +}; + +int gdb_syscall(const GdbSyscallInfo& info); + +static inline int gdb_syscall_open(const char* filename, int flags, gdb_syscall_callback_t callback = nullptr, + void* param = nullptr) +{ + GdbSyscallInfo info = {eGDBSYS_open, callback, param}; + info.open.filename = filename; + info.open.flags = flags; + return gdb_syscall(info); +} + +static inline int gdb_syscall_close(int fd, gdb_syscall_callback_t callback = nullptr, void* param = nullptr) +{ + GdbSyscallInfo info = {eGDBSYS_close, callback, param}; + info.close.fd = fd; + return gdb_syscall(info); +} + +static inline int gdb_syscall_read(int fd, void* buffer, size_t bufSize, gdb_syscall_callback_t callback = nullptr, + void* param = nullptr) +{ + GdbSyscallInfo info = {eGDBSYS_read, callback, param}; + info.read.fd = fd; + info.read.buffer = buffer; + info.read.bufSize = bufSize; + return gdb_syscall(info); +} + +static inline int gdb_syscall_write(int fd, const void* buffer, size_t count, gdb_syscall_callback_t callback = nullptr, + void* param = nullptr) +{ + GdbSyscallInfo info = {eGDBSYS_write, callback, param}; + info.write.fd = fd; + info.write.buffer = buffer; + info.write.count = count; + return gdb_syscall(info); +} + +static inline int gdb_syscall_lseek(int fd, long offset, int whence, gdb_syscall_callback_t callback = nullptr, + void* param = nullptr) +{ + GdbSyscallInfo info = {eGDBSYS_lseek, callback, param}; + info.lseek.fd = fd; + info.lseek.offset = offset; + info.lseek.whence = whence; + return gdb_syscall(info); +} + +static inline int gdb_syscall_rename(const char* oldpath, const char* newpath, + gdb_syscall_callback_t callback = nullptr, void* param = nullptr) +{ + GdbSyscallInfo info = {eGDBSYS_rename, callback, param}; + info.rename.oldpath = oldpath; + info.rename.newpath = newpath; + return gdb_syscall(info); +} + +static inline int gdb_syscall_unlink(const char* pathname, gdb_syscall_callback_t callback = nullptr, + void* param = nullptr) +{ + GdbSyscallInfo info = {eGDBSYS_unlink, callback, param}; + info.unlink.pathname = pathname; + return gdb_syscall(info); +} + +static inline int gdb_syscall_stat(const char* pathname, gdb_stat_t* buf, gdb_syscall_callback_t callback = nullptr, + void* param = nullptr) +{ + GdbSyscallInfo info = {eGDBSYS_stat, callback, param}; + info.stat.pathname = pathname; + info.stat.buf = buf; + return gdb_syscall(info); +} + +static inline int gdb_syscall_fstat(int fd, struct stat* buf, gdb_syscall_callback_t callback = nullptr, + void* param = nullptr) +{ + GdbSyscallInfo info = {eGDBSYS_fstat, callback, param}; + info.fstat.fd = fd; + info.fstat.buf = buf; + return gdb_syscall(info); +} + +static inline int gdb_syscall_gettimeofday(gdb_timeval_t* tv, void* tz, gdb_syscall_callback_t callback = nullptr, + void* param = nullptr) +{ + GdbSyscallInfo info = {eGDBSYS_gettimeofday, callback, param}; + info.gettimeofday.tv = tv; + info.gettimeofday.tz = tz; + return gdb_syscall(info); +} + +static inline int gdb_syscall_isatty(int fd, gdb_syscall_callback_t callback = nullptr, void* param = nullptr) +{ + GdbSyscallInfo info = {eGDBSYS_isatty, callback, param}; + info.isatty.fd = fd; + return gdb_syscall(info); +} + +static inline int gdb_syscall_system(const char* command, gdb_syscall_callback_t callback = nullptr, + void* param = nullptr) +{ + GdbSyscallInfo info = {eGDBSYS_system, callback, param}; + info.system.command = command; + return gdb_syscall(info); +} + +/* + * IMPORTANT: Reading console will not complete until user types return (or Ctrl+D, Ctrl+C). It must therefore + * be an asynchronous call or a watchdog reset will occur. + */ +static inline int gdb_console_read(void* buffer, size_t bufSize, gdb_syscall_callback_t callback = nullptr, + void* param = nullptr) +{ + return gdb_syscall_read(STDIN_FILENO, buffer, bufSize, callback, param); +} + +static inline int gdb_console_write(const void* buffer, size_t count, gdb_syscall_callback_t callback = nullptr, + void* param = nullptr) +{ + return gdb_syscall_write(STDOUT_FILENO, buffer, count, callback, param); +} + +#endif /* _SYSTEM_INCLUDE_GDB_SYSCALL_H_ */ diff --git a/samples/LiveDebug/app/application.cpp b/samples/LiveDebug/app/application.cpp index e32078fd84..66e9dd3aae 100644 --- a/samples/LiveDebug/app/application.cpp +++ b/samples/LiveDebug/app/application.cpp @@ -1,6 +1,7 @@ #include #include #include "HardwareTimer.h" +#include #define LED_PIN 2 // Note: LED is attached to UART1 TX output @@ -56,10 +57,263 @@ void onDataReceived(Stream& source, char arrivedChar, unsigned short availableCh } } +/* + * Demonstrate opening and reading a file from the host. + */ +void readFile(const char* filename, bool display) +{ + auto start = millis(); + int fd = gdb_syscall_open(filename, O_RDONLY); + Serial.printf(_F("gdb_syscall_open(\"%s\") = %d\r\n"), filename, fd); + if(fd > 0) { + char buf[256]; + size_t total = 0; + int len; + do { + len = gdb_syscall_read(fd, buf, sizeof(buf)); + if(len > 0) { + total += size_t(len); + if(display) { + Serial.write(buf, len); + } + } + } while(len == sizeof(buf)); + auto elapsed = millis() - start; + Serial.printf(_F("gdb_syscall_read() = %d, total = %u, elapsed = %u ms, av. %u bytes/sec\r\n"), len, total, + elapsed, total == 0 ? 0 : 1000U * total / elapsed); + + gdb_syscall_close(fd); + } +} + +// Forward declaration +void readConsole(); + +/* + * Opening, reading and closing a fill are all done using asynchronous syscalls. + * The initial open() is performed via readFileAsync(), the remaining operations are handled + * in this callback function. + */ +void asyncReadCallback(const GdbSyscallInfo& info) +{ + // Buffer for performing asynchronous reads + static char buf[256]; + + // State information so we can calculate average throughput + static struct { + long start; // millis() when open() call completed + size_t total; // Total number of file bytes read so far + } transfer; + + switch(info.command) { + case eGDBSYS_open: { + int fd = info.result; + String filename(FPSTR(info.open.filename)); + Serial.printf(_F("gdb_syscall_open(\"%s\") = %d\r\n"), filename.c_str(), fd); + if(fd > 0) { + transfer.start = millis(); + transfer.total = 0; + gdb_syscall_read(fd, buf, sizeof(buf), asyncReadCallback); + } + break; + } + + case eGDBSYS_read: { + // m_printf(_F("\rgdb_syscall_read() = %d, total = %u"), info.result, prog.total); + if(info.result > 0) { + transfer.total += info.result; + } + if(info.result == sizeof(buf)) { + gdb_syscall_read(info.read.fd, buf, sizeof(buf), asyncReadCallback); + } else { + gdb_syscall_close(info.read.fd, asyncReadCallback); + } + break; + } + + case eGDBSYS_close: { + long elapsed = millis() - transfer.start; + long bps = (transfer.total == 0) ? 0 : 1000U * transfer.total / elapsed; + Serial.printf(_F("readFileAsync: total = %u, elapsed = %u ms, av. %u bytes/sec\r\n"), transfer.total, elapsed, + bps); + readConsole(); + } + + default:; + } +} + +/* + * Note that filename must be in persistent memory (e.g. flash string) as call may not be started + * immediately. + */ +void readFileAsync(const char* filename) +{ + gdb_syscall_open(filename, O_RDONLY, asyncReadCallback); +} + +void fileStat(const char* filename) +{ + gdb_stat_t stat; + int res = gdb_syscall_stat(filename, &stat); + Serial.printf(_F("gdb_syscall_stat(\"%s\") returned %d\r\n"), filename, res); + if(res != 0) { + return; + } + + Serial.printf(_F("sizeof(stat) == %u, words = %u\r\n"), sizeof(stat), sizeof(stat) / 4); +#define PRT(x) Serial.printf(_F(" " #x " = %u\r\n"), stat.x) +#define PRT_HEX(x) Serial.printf(_F(" " #x " = 0x%08x\r\n"), stat.x) +#define PRT_TIME(x) \ + Serial.print(_F(" " #x " = ")); \ + Serial.println(DateTime(stat.x).toFullDateTimeString()); + + PRT(st_dev); + PRT(st_ino); + PRT_HEX(st_mode); + PRT(st_nlink); + PRT_HEX(st_uid); + PRT_HEX(st_gid); + PRT(st_rdev); + PRT(st_size); + PRT(st_blksize); + PRT(st_blocks); + PRT_TIME(st_atime); + PRT_TIME(st_mtime); + PRT_TIME(st_ctime); +#undef PRT +#undef PRT_HEX +#undef PRT_TIME +} + +time_t getTimeOfDay() +{ + gdb_timeval_t tv; + int res = gdb_syscall_gettimeofday(&tv, nullptr); + if(res < 0) { + Serial.printf(_F("gdb_syscall_gettimeofday() returned %d\r\n"), res); + return 0; + } else { + Serial.printf(_F("tv_sec = %u, tv_usec = %u, "), tv.tv_sec, uint32_t(tv.tv_usec)); + Serial.println(DateTime(tv.tv_sec).toFullDateTimeString() + _F(" UTC")); + return tv.tv_sec; + } +} + +/* + * Completion callback for console read test. See readConsole(). + * + * When the syscall is executed, GDB is instructed to read a line of text from the console. + * GDB implements a line-editor, so information will only be sent when you hit return. + * Typing Ctrl+D sends the line immediately without any return (note: this doesn't work on Windows.) + * + * We continue running until GDB is ready to send the result, which is written to the + * buffer provided in the original call. This callback function then gets called via the + * task queue. + * + * Data received will include any return character typed. + */ +void onConsoleReadCompleted(const GdbSyscallInfo& info) +{ + Serial.print(_F("gdb_read_console() returned ")); + Serial.print(info.result); + char* bufptr = static_cast(info.read.buffer); + if(info.result <= 0) { + Serial.println(); + delete bufptr; + } else { + unsigned len = info.result; + if(bufptr[len - 1] == '\n') { + --len; + } + String cmd(bufptr, len); + delete bufptr; + Serial.print(_F(": \"")); + Serial.print(cmd); + Serial.println('"'); + if(cmd.equalsIgnoreCase(F("readfile1"))) { + // Read a small file and display it + readFile(_F("Makefile"), true); + } else if(cmd.equalsIgnoreCase(F("readfile2"))) { + // Read a larger file asynchronously and analyse transfer speed + Serial.println(_F("Please wait...")); + readFileAsync(PSTR("README.md")); + return; // When read has completed, readConsole() will be called again + } else if(cmd.equalsIgnoreCase(F("stat"))) { + fileStat(_F("Makefile")); + } else if(cmd.equalsIgnoreCase(F("time"))) { + getTimeOfDay(); + } else if(cmd.equalsIgnoreCase(F("ls"))) { + int res = gdb_syscall_system(PSTR("ls -la")); + Serial.printf(_F("gdb_syscall_system() returned %d\r\n"), res); + } else if(cmd.equalsIgnoreCase(F("break"))) { + gdb_do_break(); + } else if(cmd.equalsIgnoreCase(F("crash"))) { + Serial.println(_F("Crashing app by writing to address 0\n" + "At GDB prompt, enter `set $pc = $pc + 3` to skip offending instruction,\n" + "then enter `c` to continue")); + Serial.flush(); + *(uint8_t*)0 = 0; + Serial.println("...still running!"); + } else if(cmd.equalsIgnoreCase(F("exit"))) { + // End console test + Serial.println(_F("Resuming normal program execution.")); + return; + } else if(cmd.equalsIgnoreCase(F("help"))) { + Serial.print(_F("LiveDebug interactive debugger sample. Available commands:\n" + " readfile1 : read and display a test file\n" + " readfile2 : read a larger file asynchronously\n" + " stat : issue a 'stat' call\n" + " ls : list directory\n" + " break : break into debugger\n" + " crash : write to address 0x00000000, see what happens\n" + " exit : resume normal application execution\n")); + } else { + Serial.println(_F("Unknown command, try 'help'")); + } + } + + // Start another console read + readConsole(); +} + +/* + * Demonstrate GDB console access. + */ +void readConsole() +{ + // Note GDB can read memory from any location, including flash, so PSTR() is fine + int res = gdb_console_write(PSTR("(LiveDebug) "), 12); + if(res < 0) { + Serial.printf(_F("gdb_console_write() failed, %d\r\n"), res); + Serial.println(_F("Is GDBSTUB_ENABLE_SYSCALL enabled ?")); + Serial.println(_F("Did you build with ENABLE_GDB=1 ?")); + return; + } + + // Issue the syscall + const unsigned bufsize = 256; + auto buffer = new char[bufsize]; + res = gdb_console_read(buffer, bufsize, onConsoleReadCompleted); + if(res < 0) { + Serial.printf(_F("gdb_syscall_read() failed, %d\r\n"), res); + delete buffer; + } + + /* + * GDB executes the system call, finished in onReadCompleted(). + * Note that any serial output gets ignored by GDB whilst executing a system + * call. + */ +} + void GDB_IRAM_ATTR init() { Serial.begin(SERIAL_BAUD_RATE); Serial.onDataReceived(onDataReceived); + + readConsole(); + pinMode(LED_PIN, OUTPUT); #if TIMER_TYPE == TIMERTYPE_SIMPLE procTimer.setCallback(SimpleTimerCallback(blink)); From 84fc6ee58cbbfb916c877d3fbc1d3f975798be0b Mon Sep 17 00:00:00 2001 From: mikee47 Date: Tue, 2 Apr 2019 14:11:33 +0100 Subject: [PATCH 10/50] Coding style, docs, optimise stub code via #pragma --- Sming/appspecific/gdb/gdb_hooks.cpp | 8 +++-- Sming/gdb/gdbstub-internal.h | 2 +- Sming/gdb/gdbstub.cpp | 11 +++++-- Sming/gdb/gdbstub.h | 2 ++ Sming/gdb/readme.md | 9 ++++-- Sming/system/include/gdb_hooks.h | 2 +- samples/LiveDebug/README.md | 48 +++++++---------------------- 7 files changed, 34 insertions(+), 48 deletions(-) diff --git a/Sming/appspecific/gdb/gdb_hooks.cpp b/Sming/appspecific/gdb/gdb_hooks.cpp index dc3fd745b5..493d021785 100644 --- a/Sming/appspecific/gdb/gdb_hooks.cpp +++ b/Sming/appspecific/gdb/gdb_hooks.cpp @@ -185,9 +185,11 @@ static bool IRAM_ATTR __gdb_no_op() return false; } -void gdb_enable(bool state) __attribute__((weak, alias("__gdb_no_op"))); -void gdb_do_break(void) __attribute__((weak, alias("__gdb_no_op"))); -bool gdb_present(void) __attribute__((weak, alias("__gdb_no_op"))); +#define NOOP __attribute__((weak, alias("__gdb_no_op"))) + +void gdb_enable(bool state) NOOP; +void gdb_do_break(void) NOOP; +bool gdb_present(void) NOOP; }; #endif diff --git a/Sming/gdb/gdbstub-internal.h b/Sming/gdb/gdbstub-internal.h index f6bcd9e68d..91fb316d24 100644 --- a/Sming/gdb/gdbstub-internal.h +++ b/Sming/gdb/gdbstub-internal.h @@ -39,7 +39,7 @@ STRUCT_FIELD(UINT32_T, 4, GDBSR_, ps) STRUCT_FIELD(UINT32_T, 4, GDBSR_, sar) STRUCT_FIELD(UINT32_T, 4, GDBSR_, vpri) STRUCT_AFIELD(UINT32_T, 4, GDBSR_, a, 16) // a0..a15 -#define GDBSR_A(n) GDBSR_a+((n)*4) +#define GDBSR_A(n) GDBSR_a + ((n)*4) // These are added manually by the exception code; the HAL doesn't set these on an exception. STRUCT_FIELD(UINT32_T, 4, GDBSR_, litbase) STRUCT_FIELD(UINT32_T, 4, GDBSR_, sr176) diff --git a/Sming/gdb/gdbstub.cpp b/Sming/gdb/gdbstub.cpp index fba2bda91b..5b8f0d53bc 100644 --- a/Sming/gdb/gdbstub.cpp +++ b/Sming/gdb/gdbstub.cpp @@ -68,7 +68,7 @@ typedef uint8_t ERRNO_T; uint32_t exceptionStack[GDBSTUB_STACK_SIZE]; #endif -volatile gdb_state_t gdb_state; ///< Global state +volatile gdb_state_t gdb_state; ///< Global state static char commandBuffer[MAX_COMMAND_LENGTH + 1]; ///< Buffer for incoming/outgoing GDB commands static int32_t singleStepPs = -1; // Stores ps (Program State) when single-stepping instruction. -1 when not in use. @@ -404,8 +404,13 @@ static GdbResult ATTR_GDBEXTERNFN handleCommand(unsigned cmdLen) auto regPtr = getSavedReg(regnum); #if GDBSTUB_ENABLE_DEBUG char regName[16]; - memcpy_P(regName, registerNames[regnum], sizeof(regName)); -// debug_i("GET %s", name); + /* + * @todo Trying to read flash here sometimes causes a LEVEL1 interrupt exception (even though + * we haven't hooked it?!) According to the docs. we need to look at the INTERRUPT register + * and INTENABLE to determine the actual cause. + */ + regName[0] = '\0'; +// memcpy_P(regName, registerNames[regnum], sizeof(regName)); #endif if(commandChar == 'p') { // read diff --git a/Sming/gdb/gdbstub.h b/Sming/gdb/gdbstub.h index 99c246396d..fa4d33f69d 100644 --- a/Sming/gdb/gdbstub.h +++ b/Sming/gdb/gdbstub.h @@ -13,6 +13,8 @@ #ifndef _GDB_GDBSTUB_H_ #define _GDB_GDBSTUB_H_ +// Always optimise GDB stub code for size, regardless of application settings +#pragma GCC optimize("Os") #include "gdbstub-internal.h" #include diff --git a/Sming/gdb/readme.md b/Sming/gdb/readme.md index 51bdc0a6ff..487a8b51e2 100644 --- a/Sming/gdb/readme.md +++ b/Sming/gdb/readme.md @@ -4,7 +4,7 @@ GDBSTUB for Sming Background ---------- -This is a rewrite of gdbstub based on the [esp8266 Arduino project]https://github.com/esp8266/Arduino/pull/5559. +This is a rewrite of gdbstub based on the [esp8266 Arduino project](https://github.com/esp8266/Arduino/pull/5559). To use the GNU Debugger (GDB) with Sming requires your application to include some code (`gdbstub`) which communicates via the serial port. On the ESP8266 only UART0 may be used for this as UART1 is transmit-only. @@ -14,6 +14,9 @@ UART2 is a 'virtual' serial port to enable serial communications to work correct Note that `target` refers to the application being debugged, and `host` the development system running the GDB program. +Refer to the official [GDB documention](https://sourceware.org/gdb/current/onlinedocs/gdb/index.html) for further details. + + GDB --- @@ -21,7 +24,7 @@ This is the application which runs on your development system and talks to `gdbs * Linux: A version of this should be available in $ESP_HOME/xtesnsa-lx106-elf/bin/xtensa-lx106-elf-gdb - * Windows: At time of writing, UDK doesn't provide a GDB application. You can find pre-built version of this at [SysProgs][http://gnutoolchains.com/esp8266/]. Download and run the executable installer, then copy the `C:\SysGCC\esp8266\opt\xtensa-lx106-elf\bin\xtensa-lx106-elf-gdb.exe` to a suitable location. + * Windows: At time of writing, UDK doesn't provide a GDB application. You can find pre-built version of this at [SysProgs](http://gnutoolchains.com/esp8266/). Download and run the executable installer, then copy the `C:\SysGCC\esp8266\opt\xtensa-lx106-elf\bin\xtensa-lx106-elf-gdb.exe` to a suitable location. * Mac: ? @@ -99,7 +102,7 @@ Error reported, "packet reply is too long" Whilst GDB is attached, input cannot be passed to application - Cause: GDB buffers keystrokes and replays them only when the target is interrupted (e.g. via ctrl+C), rather than passing them via serial connection. -- Solution: Unclear. Console I/O is synchronous, so would stall application whilst waiting for input. +- Solution: Application may use gdb_syscall interface to communicate with debugger. See `$(SMING_HOME)/system/gdb_syscall.h`. No apparent way to have second 'console' (windows terminology) separate from GDB interface - Cause: Unknown diff --git a/Sming/system/include/gdb_hooks.h b/Sming/system/include/gdb_hooks.h index 022cdbed13..de8fb91025 100644 --- a/Sming/system/include/gdb_hooks.h +++ b/Sming/system/include/gdb_hooks.h @@ -70,7 +70,7 @@ void debug_crash_callback(const struct rst_info* rst_info, uint32_t stack, uint3 void debug_print_stack(uint32_t start, uint32_t end); #ifdef __cplusplus -} +} // extern "C" #endif #endif /* _GDB_HOOKS_H_ */ diff --git a/samples/LiveDebug/README.md b/samples/LiveDebug/README.md index 3d42e0deec..b9971b67b6 100644 --- a/samples/LiveDebug/README.md +++ b/samples/LiveDebug/README.md @@ -1,5 +1,4 @@ This project is an example of how to integrate GDB debugging into your project. -It relies on the GDBStub project to do the heavy-lifting. Exception Handling ------------------ @@ -7,7 +6,7 @@ Sming comes with a built-in exception handling that takes care to display the st leading to the issue. Usually it looks like this ``` -***** Fatal exception 28 +***** Fatal exception 28 (LOAD_PROHIBITED) pc=0x40100e96 sp=0x3ffff640 excvaddr=0x000015b8 ps=0x00000033 sar=0x00000018 vpri=0x000000f0 r00: 0x40100d69=1074793833 r01: 0x3ffff640=1073739328 r02: 0x3fff3900=1073690880 @@ -55,55 +54,30 @@ With the help of `decode-stacktrace.py` you can decode the stack trace to someth Using the information about the type of the exception (ex: `***** Fatal exception 28`) and the sequence of commands might help us figure out the issue. -But that information might not be enough. And finding the -root cause may take quite some time. +But that information might not be enough. And finding the root cause may take quite some time. GDB Debugging ------------- Debugging is a powerful technique allowing you to interactively run your code and be able to see much more information about the things that went wrong. -There is already existing GDBStub that tries to make it easier to use software -debugger. And this project is an example of what you need to do in order to -integrate it. - Here are the commands that you need to execute: -1. You will need a version of the Sming library with enabled GDBStub functionality. -For that purpose you should compile Sming with ENABLE_GDB flag. Under Linux -you should do the following: - -```bash -cd $SMING_HOME -make dist-clean -ENABLE_GDB=1 make -``` - -2. In your project inside of your Makefile-user.mk file you should add the following -variable: +1. (Re)compile your project with the `ENABLE_GDB` option and flash it to the board. -```make -ENABLE_GDB=1 -``` - -If you are looking for an example then take a look at the Makefile-user.mk file -that is in the same directory as this README.md file. - -3. Now compile your project and flash it to the board. ```bash -make ENABLE_GDB=1 +make clean +make ENABLE_GDB=1 make flash ``` -4. Run gdb immediately after resetting the board or after it has run into an exception. -The easiest way to do it is to use the provided script: +2. Run gdb immediately after resetting the board or after it has run into an exception. +The easiest way to do it is to use the provided script: + ```bash -xtensa-lx106-elf-gdb -x /Basic_Debug/gdbcmds -b 115200 +make gdb ``` -115200 stands for the baud rate your program is using. Change it accordingly. -You may also need to change the gdbcmds script to fit the configuration of your hardware and build environment. - -5. Software breakpoints ('br') only work on code that is in RAM. During development you can use the GDB_IRAM_ATTR attribute in your function declarations. +3. Software breakpoints ('br') only work on code that is in RAM. During development you can use the GDB_IRAM_ATTR attribute in your function declarations. Code in flash can only have a hardware breakpoint ('hbr'). -Read the [Notes](https://github.com/espressif/esp-gdbstub#notes) for more information. +Read the GDB stub [Notes](https://github.com/SmingHub/Sming/tree/develop/Sming/gdb/readme.md) for more information. From affa79757d37c5b72a3afdebbbff51eeacdb0101 Mon Sep 17 00:00:00 2001 From: mikee47 Date: Tue, 2 Apr 2019 15:49:58 +0100 Subject: [PATCH 11/50] Testing serial operation using Basic_Serial sample, overflow still not quite right. Need to respond to overflow interrupt. Update Basic_Serial sample to check and display status. RX Overflow test: Paste a large block of data into the serial terminal Serial BREAK test: On miniterm, hit Ctrl+T then Ctrl+B, and repeat. --- Sming/SmingCore/HardwareSerial.cpp | 6 ++--- Sming/system/uart.cpp | 14 +++++++----- .../app/SerialReadingDelegateDemo.cpp | 22 ++++++++++++++++++- 3 files changed, 32 insertions(+), 10 deletions(-) diff --git a/Sming/SmingCore/HardwareSerial.cpp b/Sming/SmingCore/HardwareSerial.cpp index 19abbaa576..cd37401b67 100644 --- a/Sming/SmingCore/HardwareSerial.cpp +++ b/Sming/SmingCore/HardwareSerial.cpp @@ -116,8 +116,8 @@ void HardwareSerial::invokeCallbacks() transmitComplete(*this); } - // RX FIFO Full or RX FIFO Timeout ? - if(status & (_BV(UIFF) | _BV(UITO))) { + // RX FIFO Full or RX FIFO Timeout or RX Overflow ? + if(status & (_BV(UIFF) | _BV(UITO) | _BV(UIOF))) { auto receivedChar = uart_peek_last_char(uart); if(HWSDelegate) { HWSDelegate(*this, receivedChar, uart_rx_available(uart)); @@ -168,7 +168,7 @@ bool HardwareSerial::updateUartCallback() #else if(HWSDelegate) { #endif - mask |= _BV(UIFF) | _BV(UITO); + mask |= _BV(UIFF) | _BV(UITO) | _BV(UIOF); } if(transmitComplete) { diff --git a/Sming/system/uart.cpp b/Sming/system/uart.cpp index 1403a9e95e..e2d21f588b 100644 --- a/Sming/system/uart.cpp +++ b/Sming/system/uart.cpp @@ -277,8 +277,8 @@ size_t uart_read(uart_t* uart, void* buffer, size_t size) } // FIFO full may have been disabled if buffer overflowed, re-enabled it now - USIC(uart->uart_nr) = _BV(UIFF) | _BV(UITO); - USIE(uart->uart_nr) |= _BV(UIFF) | _BV(UITO); + USIC(uart->uart_nr) = _BV(UIFF) | _BV(UITO) | _BV(UIOF); + USIE(uart->uart_nr) |= _BV(UIFF) | _BV(UITO) | _BV(UIOF); } return read; @@ -337,7 +337,7 @@ static void IRAM_ATTR handle_uart_interrupt(uint8_t uart_nr, uart_t* uart) // Deal with the event, unless we're in raw mode if(!bitRead(uart->options, UART_OPT_CALLBACK_RAW)) { // Rx FIFO full or timeout - if(usis & (_BV(UIFF) | _BV(UITO))) { + if(usis & (_BV(UIFF) | _BV(UITO) | _BV(UIOF))) { size_t read = 0; // Read as much data as possible from the RX FIFO into buffer @@ -361,7 +361,9 @@ static void IRAM_ATTR handle_uart_interrupt(uint8_t uart_nr, uart_t* uart) * If the FIFO is full and we didn't read any of the data then need to mask the interrupt out or it'll recur. * The interrupt gets re-enabled by a call to uart_read() or uart_flush() */ - if(read == 0) { + if(bitRead(usis, UIOF)) { + bitClear(USIE(uart_nr), UIOF); + } else if(read == 0) { USIE(uart_nr) &= ~(_BV(UIFF) | _BV(UITO)); } } @@ -439,7 +441,7 @@ void uart_start_isr(uart_t* uart) * should be cleared at the start of a transaction and checked at the end. * See uart_get_status(). */ - usie = _BV(UIFF) | _BV(UITO) | _BV(UIBD); + usie = _BV(UIFF) | _BV(UITO) | _BV(UIBD) | _BV(UIOF); } if(uart_tx_enabled(uart)) { @@ -615,7 +617,7 @@ void uart_flush(uart_t* uart, uart_mode_t mode) // If receive overflow occurred then these interrupts will be masked if(flushRx) { USIC(uart->uart_nr) = 0xffff & ~_BV(UIFE); - USIE(uart->uart_nr) |= _BV(UIFF) | _BV(UITO); // | _BV(UIOF); + USIE(uart->uart_nr) |= _BV(UIFF) | _BV(UITO) | _BV(UIOF); } } diff --git a/samples/Basic_Serial/app/SerialReadingDelegateDemo.cpp b/samples/Basic_Serial/app/SerialReadingDelegateDemo.cpp index f6aeab657a..2978e1b47f 100644 --- a/samples/Basic_Serial/app/SerialReadingDelegateDemo.cpp +++ b/samples/Basic_Serial/app/SerialReadingDelegateDemo.cpp @@ -28,10 +28,30 @@ void SerialReadingDelegateDemo::onData(Stream& stream, char arrivedChar, unsigne serial->print(_F("Class Delegate Demo Time = ")); serial->print(micros()); serial->print(_F(" char = 0x")); - serial->print(String(arrivedChar, HEX)); // char hex code + serial->print(arrivedChar, HEX); // char hex code serial->print(_F(" available = ")); serial->println(availableCharsCount); + // Error detection + unsigned status = serial->getStatus(); + if(status != 0) { + if(bitRead(status, eSERS_Overflow)) { + serial->println(_F("** RECEIVE OVERFLOW **")); + } + if(bitRead(status, eSERS_BreakDetected)) { + serial->println(_F("** BREAK DETECTED **")); + } + if(bitRead(status, eSERS_FramingError)) { + serial->println(_F("** FRAMING ERROR **")); + } + if(bitRead(status, eSERS_ParityError)) { + serial->println(_F("** PARITY ERROR **")); + } + // Discard what is likely to be garbage + serial->clear(SERIAL_RX_ONLY); + return; + } + numCallback++; if(arrivedChar == '\n') // Lets show data! From 96b519de8727e50731f463409f9d1386a3b8806c Mon Sep 17 00:00:00 2001 From: mikee47 Date: Tue, 2 Apr 2019 15:56:21 +0100 Subject: [PATCH 12/50] Update LiveDebug sample to deal with errors in receive handler --- samples/LiveDebug/app/application.cpp | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/samples/LiveDebug/app/application.cpp b/samples/LiveDebug/app/application.cpp index 66e9dd3aae..d07463a9f0 100644 --- a/samples/LiveDebug/app/application.cpp +++ b/samples/LiveDebug/app/application.cpp @@ -49,6 +49,29 @@ void CALLBACK_ATTR blink() void onDataReceived(Stream& source, char arrivedChar, unsigned short availableCharsCount) { + // Error detection + unsigned status = Serial.getStatus(); + if(status != 0) { + if(bitRead(status, eSERS_Overflow)) { + Serial.println(_F("** RECEIVE OVERFLOW **")); + } + if(bitRead(status, eSERS_BreakDetected)) { + Serial.println(_F("** BREAK DETECTED **")); + } + if(bitRead(status, eSERS_FramingError)) { + Serial.println(_F("** FRAMING ERROR **")); + } + if(bitRead(status, eSERS_ParityError)) { + Serial.println(_F("** PARITY ERROR **")); + } + // Discard what is likely to be garbage + Serial.print(_F("Discarding ")); + Serial.print(availableCharsCount); + Serial.println(_F(" chars")); + Serial.clear(SERIAL_RX_ONLY); + return; + } + if(arrivedChar == '\n' && availableCharsCount != 0) { char buffer[availableCharsCount]; auto count = Serial.readMemoryBlock(buffer, availableCharsCount); @@ -79,7 +102,7 @@ void readFile(const char* filename, bool display) } } while(len == sizeof(buf)); auto elapsed = millis() - start; - Serial.printf(_F("gdb_syscall_read() = %d, total = %u, elapsed = %u ms, av. %u bytes/sec\r\n"), len, total, + Serial.printf(_F("\r\ngdb_syscall_read() = %d, total = %u, elapsed = %u ms, av. %u bytes/sec\r\n"), len, total, elapsed, total == 0 ? 0 : 1000U * total / elapsed); gdb_syscall_close(fd); From e6540c8f283b1a3a02f35f92528c8000f1c56cc2 Mon Sep 17 00:00:00 2001 From: mikee47 Date: Tue, 2 Apr 2019 21:38:15 +0100 Subject: [PATCH 13/50] Fix stack dumper - registers incorrect --- Sming/appspecific/gdb/gdb_hooks.cpp | 25 +++++++++++++++++++------ Sming/gdb/gdbstub.cpp | 14 +------------- Sming/gdb/gdbstub.h | 2 +- 3 files changed, 21 insertions(+), 20 deletions(-) diff --git a/Sming/appspecific/gdb/gdb_hooks.cpp b/Sming/appspecific/gdb/gdb_hooks.cpp index 493d021785..61a11c51a7 100644 --- a/Sming/appspecific/gdb/gdb_hooks.cpp +++ b/Sming/appspecific/gdb/gdb_hooks.cpp @@ -87,7 +87,7 @@ void debug_crash_callback(const rst_info* rst_info, uint32_t stack, uint32_t sta #if ENABLE_EXCEPTION_DUMP -void dumpExceptionInfo() +void dumpExceptionInfo(UserFrame* frame) { ets_wdt_disable(); auto& reg = gdbstub_savedRegs; @@ -129,12 +129,25 @@ void dumpExceptionInfo() // Main exception handler code static void __attribute__((noinline)) gdbstub_exception_handler_flash(UserFrame* frame) { + // Copy registers the Xtensa HAL did save to gdbstub_savedRegs + memcpy(&gdbstub_savedRegs, frame, 5 * 4); + memcpy(&gdbstub_savedRegs.a[2], &frame->a2, 14 * 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. + const uint32_t EXCEPTION_GDB_SP_OFFSET = 0x100; + gdbstub_savedRegs.a[1] = uint32_t(frame) + EXCEPTION_GDB_SP_OFFSET; + #if ENABLE_EXCEPTION_DUMP - dumpExceptionInfo(); + dumpExceptionInfo(frame); #endif #if defined(ENABLE_GDB) && GDBSTUB_BREAK_ON_EXCEPTION - gdbstub_handle_exception(frame); + gdbstub_handle_exception(); + + // Copy any changed registers back to the frame the Xtensa HAL uses. + memcpy(frame, &gdbstub_savedRegs, 5 * 4); + memcpy(&frame->a2, &gdbstub_savedRegs.a[2], 14 * 4); + return; #endif @@ -180,16 +193,16 @@ void ATTR_GDBINIT gdb_init() #ifndef ENABLE_GDB extern "C" { -static bool IRAM_ATTR __gdb_no_op() +static unsigned IRAM_ATTR __gdb_no_op() { - return false; + return 0; } #define NOOP __attribute__((weak, alias("__gdb_no_op"))) void gdb_enable(bool state) NOOP; void gdb_do_break(void) NOOP; -bool gdb_present(void) NOOP; +GdbState gdb_present(void) NOOP; }; #endif diff --git a/Sming/gdb/gdbstub.cpp b/Sming/gdb/gdbstub.cpp index 5b8f0d53bc..d1c4a3f4a6 100644 --- a/Sming/gdb/gdbstub.cpp +++ b/Sming/gdb/gdbstub.cpp @@ -875,7 +875,7 @@ extern "C" void IRAM_ATTR gdbstub_handle_debug_exception() } #if GDBSTUB_BREAK_ON_EXCEPTION -void gdbstub_handle_exception(UserFrame* frame) +void gdbstub_handle_exception() { if(!gdb_state.enabled) { return; @@ -884,22 +884,10 @@ void gdbstub_handle_exception(UserFrame* frame) pauseHardwareTimer(true); bitSet(gdb_state.flags, DBGFLAG_SYSTEM_EXCEPTION); - // Copy registers the Xtensa HAL did save to gdbstub_savedRegs - memcpy(&gdbstub_savedRegs, frame, 5 * 4); - memcpy(&gdbstub_savedRegs.a[2], &frame->a2, 14 * 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. - const uint32_t EXCEPTION_GDB_SP_OFFSET = 0x100; - gdbstub_savedRegs.a[1] = uint32_t(frame) + EXCEPTION_GDB_SP_OFFSET; - sendReason(); while(commandLoop() != ST_CONT) { } - // Copy any changed registers back to the frame the Xtensa HAL uses. - memcpy(frame, &gdbstub_savedRegs, 5 * 4); - memcpy(&frame->a2, &gdbstub_savedRegs.a[2], 14 * 4); - gdb_state.flags = 0; pauseHardwareTimer(false); } diff --git a/Sming/gdb/gdbstub.h b/Sming/gdb/gdbstub.h index fa4d33f69d..7aa34be291 100644 --- a/Sming/gdb/gdbstub.h +++ b/Sming/gdb/gdbstub.h @@ -57,6 +57,6 @@ extern volatile gdb_state_t gdb_state; extern const uint8_t gdb_exception_signals[]; void gdbstub_init(); -void gdbstub_handle_exception(UserFrame* frame); +void gdbstub_handle_exception(); #endif /* _GDB_GDBSTUB_H_ */ From a6a2cbc53123e47f84d5493579c90b7058889461 Mon Sep 17 00:00:00 2001 From: mikee47 Date: Tue, 2 Apr 2019 21:52:07 +0100 Subject: [PATCH 14/50] Flush serial data to hardware before sending a stop packet --- Sming/gdb/gdbstub.cpp | 4 ++++ Sming/gdb/gdbuart.cpp | 7 ++++--- Sming/gdb/gdbuart.h | 3 ++- 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/Sming/gdb/gdbstub.cpp b/Sming/gdb/gdbstub.cpp index d1c4a3f4a6..3e5cdee80e 100644 --- a/Sming/gdb/gdbstub.cpp +++ b/Sming/gdb/gdbstub.cpp @@ -185,6 +185,10 @@ static ERRNO_T ATTR_GDBEXTERNFN writeMemoryBlock(uint32_t addr, const void* data */ static void ATTR_GDBEXTERNFN sendReason() { + // Flush any debug info to serial + while(gdbstub_send_user_data() != 0) { + // + } GdbPacket packet; packet.writeChar('T'); diff --git a/Sming/gdb/gdbuart.cpp b/Sming/gdb/gdbuart.cpp index 6cc1c8b379..3863881899 100644 --- a/Sming/gdb/gdbuart.cpp +++ b/Sming/gdb/gdbuart.cpp @@ -148,7 +148,7 @@ size_t ATTR_GDBEXTERNFN gdbSendChar(char c) } #if GDBSTUB_ENABLE_UART2 -void gdbstub_send_user_data() +size_t gdbstub_send_user_data() { size_t avail = 0; @@ -191,6 +191,8 @@ void gdbstub_send_user_data() gdbstub_syscall_execute(); } #endif + + return avail; } static void SendUserDataQueued(uint32_t) @@ -240,8 +242,7 @@ static void userUartNotify(uart_t* uart, uart_notify_code_t code) /* * Ensure all data has been written to hardware */ - while(!uart->tx_buffer->isEmpty()) { - gdbstub_send_user_data(); + while(gdbstub_send_user_data() != 0) { } break; } diff --git a/Sming/gdb/gdbuart.h b/Sming/gdb/gdbuart.h index 17fd3f48e2..07439d0da4 100644 --- a/Sming/gdb/gdbuart.h +++ b/Sming/gdb/gdbuart.h @@ -33,10 +33,11 @@ size_t gdbSendChar(char c); /** * @brief Send some user data from the user_uart TX buffer to the GDB serial port, * packetising it if necessary. + * @retval size_t Number of characters still remaining in buffer * @note Data flows from user uart TX buffer to UART0 either during uart_write() call * (via notify callback) or via task callback queued from ISR. We don't do this inside * the ISR as all the code (including packetising) would need to be in IRAM. */ -void gdbstub_send_user_data(); +size_t gdbstub_send_user_data(); #endif /* _GDB_GDBUART_H_ */ From bc2bb9cf955c45b31b1c1d35ba0be6d5acba5eed Mon Sep 17 00:00:00 2001 From: mikee47 Date: Tue, 2 Apr 2019 21:53:05 +0100 Subject: [PATCH 15/50] Change `gdb_present()` to return attachment state --- Sming/gdb/gdbstub.cpp | 4 ++-- Sming/system/include/gdb_hooks.h | 8 +++++++- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/Sming/gdb/gdbstub.cpp b/Sming/gdb/gdbstub.cpp index 3e5cdee80e..4bc9660268 100644 --- a/Sming/gdb/gdbstub.cpp +++ b/Sming/gdb/gdbstub.cpp @@ -925,9 +925,9 @@ void ATTR_GDBINIT gdbstub_init() /* Hook functions */ -bool IRAM_ATTR gdb_present() +GdbState IRAM_ATTR gdb_present() { - return true; + return gdb_state.attached ? eGDB_Attached : eGDB_Detached; } void gdb_enable(bool state) diff --git a/Sming/system/include/gdb_hooks.h b/Sming/system/include/gdb_hooks.h index de8fb91025..9a1f8c02be 100644 --- a/Sming/system/include/gdb_hooks.h +++ b/Sming/system/include/gdb_hooks.h @@ -52,10 +52,16 @@ void gdb_enable(bool state); */ void gdb_do_break(void); +typedef enum { + eGDB_NotPresent, + eGDB_Detached, + eGDB_Attached, +} GdbState; + /** * @brief Check if GDB stub is present */ -bool gdb_present(void); +GdbState gdb_present(void); /** * @brief Called on unexpected system reset From 33d305e71efd0587c3cfd81ed36d3e86d79cf02d Mon Sep 17 00:00:00 2001 From: mikee47 Date: Tue, 2 Apr 2019 21:54:33 +0100 Subject: [PATCH 16/50] Implement simple serial line editor in LiveDebug sample to provide command prompt running under terminal --- samples/LiveDebug/app/application.cpp | 209 +++++++++++++++++--------- 1 file changed, 137 insertions(+), 72 deletions(-) diff --git a/samples/LiveDebug/app/application.cpp b/samples/LiveDebug/app/application.cpp index d07463a9f0..f615ea755b 100644 --- a/samples/LiveDebug/app/application.cpp +++ b/samples/LiveDebug/app/application.cpp @@ -47,11 +47,33 @@ void CALLBACK_ATTR blink() state = !state; } +void showPrompt() +{ + switch(gdb_present()) { + case eGDB_Attached: + Serial.print(_F("\r(Attached) ")); + break; + case eGDB_Detached: + Serial.print(_F("\r(Detached) ")); + break; + case eGDB_NotPresent: + default: + Serial.print(_F("\r(Non-GDB) ")); + } +} + +bool handleCommand(const String& cmd); + void onDataReceived(Stream& source, char arrivedChar, unsigned short availableCharsCount) { + static unsigned commandLength; + const unsigned MAX_COMMAND_LENGTH = 16; + static char commandBuffer[MAX_COMMAND_LENGTH + 1]; + // Error detection unsigned status = Serial.getStatus(); if(status != 0) { + Serial.println(); if(bitRead(status, eSERS_Overflow)) { Serial.println(_F("** RECEIVE OVERFLOW **")); } @@ -65,18 +87,41 @@ void onDataReceived(Stream& source, char arrivedChar, unsigned short availableCh Serial.println(_F("** PARITY ERROR **")); } // Discard what is likely to be garbage - Serial.print(_F("Discarding ")); - Serial.print(availableCharsCount); - Serial.println(_F(" chars")); Serial.clear(SERIAL_RX_ONLY); + commandLength = 0; + showPrompt(); return; } - if(arrivedChar == '\n' && availableCharsCount != 0) { - char buffer[availableCharsCount]; - auto count = Serial.readMemoryBlock(buffer, availableCharsCount); - Serial.print(_F("You typed: ")); - Serial.write(buffer, count); + int c; + while((c = Serial.read()) >= 0) { + switch(c) { + case '\b': // delete (backspace) + if(commandLength > 0) { + --commandLength; + Serial.print('\b'); + Serial.print(' '); + Serial.print('\b'); + } + break; + case '\r': + case '\n': + // m_printHex("CMD", commandBuffer, commandLength); + if(commandLength > 0) { + Serial.println(); + String cmd(commandBuffer, commandLength); + commandLength = 0; + Serial.clear(SERIAL_RX_ONLY); + handleCommand(cmd); + } + showPrompt(); + break; + default: + if(c >= 0x20 && c <= 0x7f && commandLength < MAX_COMMAND_LENGTH) { + commandBuffer[commandLength++] = c; + Serial.print(char(c)); + } + } } } @@ -223,6 +268,57 @@ time_t getTimeOfDay() } } +/** + * @brief User typed a command. Deal with it. + * @retval bool true to continue reading another command + */ +bool handleCommand(const String& cmd) +{ + if(cmd.equalsIgnoreCase(F("readfile1"))) { + // Read a small file and display it + readFile(_F("Makefile"), true); + } else if(cmd.equalsIgnoreCase(F("readfile2"))) { + // Read a larger file asynchronously and analyse transfer speed + Serial.println(_F("Please wait...")); + readFileAsync(PSTR("README.md")); + return false; // When read has completed, readConsole() will be called again + } else if(cmd.equalsIgnoreCase(F("stat"))) { + fileStat(_F("Makefile")); + } else if(cmd.equalsIgnoreCase(F("time"))) { + getTimeOfDay(); + } else if(cmd.equalsIgnoreCase(F("ls"))) { + int res = gdb_syscall_system(PSTR("ls -la")); + Serial.printf(_F("gdb_syscall_system() returned %d\r\n"), res); + } else if(cmd.equalsIgnoreCase(F("break"))) { + gdb_do_break(); + } else if(cmd.equalsIgnoreCase(F("crash"))) { + Serial.println(_F("Crashing app by writing to address 0\n" + "At GDB prompt, enter `set $pc = $pc + 3` to skip offending instruction,\n" + "then enter `c` to continue")); + Serial.flush(); + *(uint8_t*)0 = 0; + Serial.println("...still running!"); + } else if(cmd.equalsIgnoreCase(F("exit"))) { + // End console test + Serial.println(_F("Resuming normal program execution.")); + return false; + } else if(cmd.equalsIgnoreCase(F("help"))) { + Serial.print(_F("LiveDebug interactive debugger sample. Available commands:\n" + " readfile1 : read and display a test file\n" + " readfile2 : read a larger file asynchronously\n" + " stat : issue a 'stat' call\n" + " ls : list directory\n" + " break : break into debugger\n" + " crash : write to address 0x00000000, see what happens\n" + " exit : resume normal application execution\n")); + } else { + Serial.println(_F("Unknown command, try 'help'")); + } + + // Another command, please + return true; +} + /* * Completion callback for console read test. See readConsole(). * @@ -249,50 +345,18 @@ void onConsoleReadCompleted(const GdbSyscallInfo& info) if(bufptr[len - 1] == '\n') { --len; } - String cmd(bufptr, len); - delete bufptr; - Serial.print(_F(": \"")); - Serial.print(cmd); - Serial.println('"'); - if(cmd.equalsIgnoreCase(F("readfile1"))) { - // Read a small file and display it - readFile(_F("Makefile"), true); - } else if(cmd.equalsIgnoreCase(F("readfile2"))) { - // Read a larger file asynchronously and analyse transfer speed - Serial.println(_F("Please wait...")); - readFileAsync(PSTR("README.md")); - return; // When read has completed, readConsole() will be called again - } else if(cmd.equalsIgnoreCase(F("stat"))) { - fileStat(_F("Makefile")); - } else if(cmd.equalsIgnoreCase(F("time"))) { - getTimeOfDay(); - } else if(cmd.equalsIgnoreCase(F("ls"))) { - int res = gdb_syscall_system(PSTR("ls -la")); - Serial.printf(_F("gdb_syscall_system() returned %d\r\n"), res); - } else if(cmd.equalsIgnoreCase(F("break"))) { - gdb_do_break(); - } else if(cmd.equalsIgnoreCase(F("crash"))) { - Serial.println(_F("Crashing app by writing to address 0\n" - "At GDB prompt, enter `set $pc = $pc + 3` to skip offending instruction,\n" - "then enter `c` to continue")); - Serial.flush(); - *(uint8_t*)0 = 0; - Serial.println("...still running!"); - } else if(cmd.equalsIgnoreCase(F("exit"))) { - // End console test - Serial.println(_F("Resuming normal program execution.")); - return; - } else if(cmd.equalsIgnoreCase(F("help"))) { - Serial.print(_F("LiveDebug interactive debugger sample. Available commands:\n" - " readfile1 : read and display a test file\n" - " readfile2 : read a larger file asynchronously\n" - " stat : issue a 'stat' call\n" - " ls : list directory\n" - " break : break into debugger\n" - " crash : write to address 0x00000000, see what happens\n" - " exit : resume normal application execution\n")); + if(len == 0) { + Serial.println(); } else { - Serial.println(_F("Unknown command, try 'help'")); + String cmd(bufptr, len); + delete bufptr; + Serial.print(_F(": \"")); + Serial.print(cmd); + Serial.println('"'); + + if(!handleCommand(cmd)) { + return; + } } } @@ -305,29 +369,30 @@ void onConsoleReadCompleted(const GdbSyscallInfo& info) */ void readConsole() { - // Note GDB can read memory from any location, including flash, so PSTR() is fine - int res = gdb_console_write(PSTR("(LiveDebug) "), 12); - if(res < 0) { - Serial.printf(_F("gdb_console_write() failed, %d\r\n"), res); - Serial.println(_F("Is GDBSTUB_ENABLE_SYSCALL enabled ?")); - Serial.println(_F("Did you build with ENABLE_GDB=1 ?")); - return; - } + showPrompt(); + if(gdb_present() == eGDB_Attached) { + // Issue the syscall + const unsigned bufsize = 256; + auto buffer = new char[bufsize]; + int res = gdb_console_read(buffer, bufsize, onConsoleReadCompleted); + if(res < 0) { + Serial.printf(_F("gdb_console_read() failed, %d\r\n"), res); + Serial.println(_F("Is GDBSTUB_ENABLE_SYSCALL enabled ?")); + Serial.println(_F("Did you build with ENABLE_GDB=1 ?")); + delete buffer; + showPrompt(); + } - // Issue the syscall - const unsigned bufsize = 256; - auto buffer = new char[bufsize]; - res = gdb_console_read(buffer, bufsize, onConsoleReadCompleted); - if(res < 0) { - Serial.printf(_F("gdb_syscall_read() failed, %d\r\n"), res); - delete buffer; + /* + * GDB executes the system call, finished in onReadCompleted(). + * Note that any serial output gets ignored by GDB whilst executing a system + * call. + */ + } else { + /* + * GDB is either detached or not present, serial callback will process input + */ } - - /* - * GDB executes the system call, finished in onReadCompleted(). - * Note that any serial output gets ignored by GDB whilst executing a system - * call. - */ } void GDB_IRAM_ATTR init() From 1123ee1a04819ae2515ee1a142e24b112c6630f5 Mon Sep 17 00:00:00 2001 From: mikee47 Date: Wed, 3 Apr 2019 08:42:25 +0100 Subject: [PATCH 17/50] Fix codacy advisories (uart.cpp) --- Sming/system/uart.cpp | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/Sming/system/uart.cpp b/Sming/system/uart.cpp index e2d21f588b..f68f0e96ae 100644 --- a/Sming/system/uart.cpp +++ b/Sming/system/uart.cpp @@ -346,8 +346,7 @@ static void IRAM_ATTR handle_uart_interrupt(uint8_t uart_nr, uart_t* uart) size_t space = uart->rx_buffer->getFreeSpace(); read = (avail <= space) ? avail : space; space -= read; - avail -= read; - for(size_t i = 0; i < read; ++i) { + while(read-- != 0) { uart->rx_buffer->writeChar(USF(uart_nr)); } @@ -370,14 +369,11 @@ static void IRAM_ATTR handle_uart_interrupt(uint8_t uart_nr, uart_t* uart) // Unless we replenish TX FIFO, disable after handling interrupt if(bitRead(usis, UIFE)) { - size_t space = uart_txfifo_free(uart_nr); - // Dump as much data as we can from buffer into the TX FIFO if(uart->tx_buffer != nullptr) { + size_t space = uart_txfifo_free(uart_nr); size_t avail = uart->tx_buffer->available(); size_t count = (avail <= space) ? avail : space; - space -= count; - avail -= count; while(count-- != 0) { USF(uart_nr) = uart->tx_buffer->readChar(); } From 45e2eb0530552df5e41e8e4cdc48ad770ead2358 Mon Sep 17 00:00:00 2001 From: mikee47 Date: Wed, 3 Apr 2019 14:09:23 +0100 Subject: [PATCH 18/50] Fix problem with writing to IRAM --- Sming/gdb/gdbstub.cpp | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/Sming/gdb/gdbstub.cpp b/Sming/gdb/gdbstub.cpp index 4bc9660268..357636fac8 100644 --- a/Sming/gdb/gdbstub.cpp +++ b/Sming/gdb/gdbstub.cpp @@ -116,20 +116,23 @@ static void ATTR_GDBEXTERNFN writeMemoryByte(uint32_t addr, uint8_t data) } /* - * Return true if addr lives within a RAM segment - * See xtensa/config/core-isa.h for memory range information + * Return true if addr lives within a given segment + * See xtensa/config/core-isa.h for memory range information, XCHAL_* values */ -static bool ATTR_GDBEXTERNFN isRamAddr(uint32_t addr) +static bool ATTR_GDBEXTERNFN inRange(uint32_t addr, uint32_t start, uint32_t size) { - return (addr >= 0x3ff00000 && addr < 0x40000000) || (addr >= 0x40100000 && addr < 0x40140000); + return (addr >= start) && (addr < start + size); } +#define IN_RANGE(addr, name) inRange(addr, XCHAL_##name##_VADDR, XCHAL_##name##_SIZE) + /* * Return true if it makes sense to write to addr */ static bool ATTR_GDBEXTERNFN isValidWriteAddr(uint32_t addr) { - return isRamAddr(addr) || (addr >= 0x60000000 && addr < 0x60002000); + return (addr >= 0x3ff00000 && addr < 0x40000000) || (addr >= 0x40100000 && addr < 0x40140000) || + (addr >= 0x60000000 && addr < 0x60002000); } static bool ATTR_GDBEXTERNFN isValidWriteAddr(uint32_t startAddr, size_t length) @@ -163,11 +166,11 @@ static ERRNO_T ATTR_GDBEXTERNFN writeMemoryBlock(uint32_t addr, const void* data return EFAULT; } - // If writing to RAM, use memcpy for efficiency - if(isRamAddr(addr)) { + // If writing to Data RAM, use memcpy for efficiency + if(IN_RANGE(addr, DATARAM0) || IN_RANGE(addr, DATARAM1)) { memcpy(reinterpret_cast(addr), data, length); } else { - // Registers must be read/written in 32-bit words + // Instruction RAM and registers must be read/written in 32-bit words for(unsigned i = 0; i < length; i++, addr++) { writeMemoryByte(addr, static_cast(data)[i]); } From 3af913bb84d221df5f5a2b1b567232299aa19748 Mon Sep 17 00:00:00 2001 From: mikee47 Date: Wed, 3 Apr 2019 14:10:13 +0100 Subject: [PATCH 19/50] Don't produce exception dump if attached to debugger --- Sming/appspecific/gdb/gdb_hooks.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Sming/appspecific/gdb/gdb_hooks.cpp b/Sming/appspecific/gdb/gdb_hooks.cpp index 61a11c51a7..ef4f1fac51 100644 --- a/Sming/appspecific/gdb/gdb_hooks.cpp +++ b/Sming/appspecific/gdb/gdb_hooks.cpp @@ -138,7 +138,9 @@ static void __attribute__((noinline)) gdbstub_exception_handler_flash(UserFrame* gdbstub_savedRegs.a[1] = uint32_t(frame) + EXCEPTION_GDB_SP_OFFSET; #if ENABLE_EXCEPTION_DUMP - dumpExceptionInfo(frame); + if(gdb_present() != eGDB_Attached) { + dumpExceptionInfo(frame); + } #endif #if defined(ENABLE_GDB) && GDBSTUB_BREAK_ON_EXCEPTION From 160499ebec4b909237ea478d9987c2d01fdc997e Mon Sep 17 00:00:00 2001 From: mikee47 Date: Wed, 3 Apr 2019 14:28:38 +0100 Subject: [PATCH 20/50] syscalls should work with UART2 disabled --- Sming/gdb/gdbstub.cpp | 6 +- Sming/gdb/gdbsyscall.cpp | 210 +++++++++++++++++++++------------------ Sming/gdb/gdbuart.cpp | 84 +++++++++------- 3 files changed, 161 insertions(+), 139 deletions(-) diff --git a/Sming/gdb/gdbstub.cpp b/Sming/gdb/gdbstub.cpp index 357636fac8..42d8627a95 100644 --- a/Sming/gdb/gdbstub.cpp +++ b/Sming/gdb/gdbstub.cpp @@ -905,6 +905,8 @@ void ATTR_GDBINIT gdbstub_init() { gdbstub_init_debug_entry(); + gdb_state.enabled = gdb_uart_init(); + #define SD(xx) debug_i(#xx " = %u", xx) SD(ENABLE_EXCEPTION_DUMP); SD(ENABLE_CRASH_DUMP); @@ -915,10 +917,10 @@ void ATTR_GDBINIT gdbstub_init() SD(GDBSTUB_BREAK_ON_EXCEPTION); SD(GDBSTUB_CTRLC_BREAK); SD(GDBSTUB_BREAK_ON_INIT); + SD(GDBSTUB_ENABLE_UART2); + SD(GDBSTUB_ENABLE_SYSCALL); #undef SD - gdb_state.enabled = gdb_uart_init(); - #if GDBSTUB_BREAK_ON_INIT if(gdb_state.enabled) { gdbstub_do_break(); diff --git a/Sming/gdb/gdbsyscall.cpp b/Sming/gdb/gdbsyscall.cpp index 7cb1543bdd..5a6c0fb4ad 100644 --- a/Sming/gdb/gdbsyscall.cpp +++ b/Sming/gdb/gdbsyscall.cpp @@ -15,6 +15,7 @@ #if GDBSTUB_ENABLE_SYSCALL #include +#include "gdbsyscall.h" #include "GdbPacket.h" #include #include "Platform/System.h" @@ -56,135 +57,146 @@ static GdbSyscallInfo syscall_info; ///< The syscall packet * Otherwise we need to force send of uart data via sendUserData() (in gdbuart.cpp). * * Both of these deal with packetising whilst the debugger is attached but not paused. - * We should combine this - */ int gdb_syscall(const GdbSyscallInfo& info) { if(!gdb_state.attached) { + debug_e("syscall failed, not attached"); return -EPERM; } if(gdb_state.syscall != syscall_ready) { + debug_e("syscall failed, not ready"); return -EAGAIN; } syscall_info = info; gdb_state.syscall = syscall_pending; - do { - gdbstub_send_user_data(); - } while(info.callback == nullptr && gdb_state.syscall != syscall_ready); + debug_i("syscall pending"); - return syscall_info.result; + if(info.callback == nullptr) { + // Wait for all user data to be output before sending request + while(gdbstub_send_user_data() != 0) { + } + gdbstub_syscall_execute(); + // No callback, wait for completion + while(gdb_state.syscall != syscall_ready) { + // Watchdog will fire if this takes too long - intentional + } + debug_i("syscall returned %d", syscall_info.result); + return syscall_info.result; + } else { + if(gdbstub_send_user_data() == 0) { + gdbstub_syscall_execute(); + } else { + // will be executed via user task in gdbuart + } + return 0; + } } void gdbstub_syscall_execute() { if(gdb_state.syscall != syscall_pending) { // Nothing to execute + debug_w("No pending syscall"); return; } auto& info = syscall_info; - { - GdbPacket packet; - packet.writeChar('F'); - switch(info.command) { - case eGDBSYS_open: - packet.writeStr(GDB_F("open,")); - packet.writeStrRef(info.open.filename); - packet.writeChar(','); - packet.writeHexWord32(info.open.flags); - packet.writeChar(','); - packet.writeHexByte(0); // mode - break; - - case eGDBSYS_close: - packet.writeStr(GDB_F("close,")); - packet.writeHexByte(info.close.fd); - break; - - case eGDBSYS_read: - packet.writeStr(GDB_F("read,")); - packet.writeHexByte(info.read.fd); - packet.writeChar(','); - packet.writeHexWord32(uint32_t(info.read.buffer)); - packet.writeChar(','); - packet.writeHexWord16(info.read.bufSize); - break; - - case eGDBSYS_write: - packet.writeStr(GDB_F("write,")); - packet.writeHexByte(info.write.fd); - packet.writeChar(','); - packet.writeHexWord32(uint32_t(info.write.buffer)); - packet.writeChar(','); - packet.writeHexWord16(info.write.count); - break; - - case eGDBSYS_lseek: - packet.writeStr(GDB_F("lseek,")); - packet.writeHexByte(info.lseek.fd); - packet.writeChar(','); - packet.writeHexWord32(info.lseek.offset); - packet.writeChar(','); - packet.writeHexByte(info.lseek.whence); - break; - - case eGDBSYS_rename: - packet.writeStr(GDB_F("rename,")); - packet.writeStrRef(info.rename.oldpath); - packet.writeChar(','); - packet.writeStrRef(info.rename.newpath); - break; - - case eGDBSYS_unlink: - packet.writeStr(GDB_F("unlink,")); - packet.writeStrRef(info.unlink.pathname); - break; - - case eGDBSYS_stat: - packet.writeStr(GDB_F("stat,")); - packet.writeStrRef(info.stat.pathname); - packet.writeChar(','); - packet.writeHexWord32(uint32_t(info.stat.buf)); - break; - - case eGDBSYS_fstat: - packet.writeStr(GDB_F("fstat,")); - packet.writeHexByte(info.fstat.fd); - packet.writeChar(','); - packet.writeHexWord32(uint32_t(info.fstat.buf)); - break; - - case eGDBSYS_gettimeofday: - packet.writeStr(GDB_F("gettimeofday,")); - packet.writeHexWord32(uint32_t(info.gettimeofday.tv)); - packet.writeChar(','); - packet.writeHexWord32(uint32_t(info.gettimeofday.tz)); - break; - - case eGDBSYS_isatty: - packet.writeStr(GDB_F("isatty,")); - packet.writeHexByte(info.isatty.fd); - break; - - case eGDBSYS_system: - packet.writeStr(GDB_F("system,")); - packet.writeStrRef(info.system.command); - break; - } + GdbPacket packet; + packet.writeChar('F'); + switch(info.command) { + case eGDBSYS_open: + packet.writeStr(GDB_F("open,")); + packet.writeStrRef(info.open.filename); + packet.writeChar(','); + packet.writeHexWord32(info.open.flags); + packet.writeChar(','); + packet.writeHexByte(0); // mode + break; + + case eGDBSYS_close: + packet.writeStr(GDB_F("close,")); + packet.writeHexByte(info.close.fd); + break; + + case eGDBSYS_read: + packet.writeStr(GDB_F("read,")); + packet.writeHexByte(info.read.fd); + packet.writeChar(','); + packet.writeHexWord32(uint32_t(info.read.buffer)); + packet.writeChar(','); + packet.writeHexWord16(info.read.bufSize); + break; + + case eGDBSYS_write: + packet.writeStr(GDB_F("write,")); + packet.writeHexByte(info.write.fd); + packet.writeChar(','); + packet.writeHexWord32(uint32_t(info.write.buffer)); + packet.writeChar(','); + packet.writeHexWord16(info.write.count); + break; + + case eGDBSYS_lseek: + packet.writeStr(GDB_F("lseek,")); + packet.writeHexByte(info.lseek.fd); + packet.writeChar(','); + packet.writeHexWord32(info.lseek.offset); + packet.writeChar(','); + packet.writeHexByte(info.lseek.whence); + break; + + case eGDBSYS_rename: + packet.writeStr(GDB_F("rename,")); + packet.writeStrRef(info.rename.oldpath); + packet.writeChar(','); + packet.writeStrRef(info.rename.newpath); + break; + + case eGDBSYS_unlink: + packet.writeStr(GDB_F("unlink,")); + packet.writeStrRef(info.unlink.pathname); + break; + + case eGDBSYS_stat: + packet.writeStr(GDB_F("stat,")); + packet.writeStrRef(info.stat.pathname); + packet.writeChar(','); + packet.writeHexWord32(uint32_t(info.stat.buf)); + break; + + case eGDBSYS_fstat: + packet.writeStr(GDB_F("fstat,")); + packet.writeHexByte(info.fstat.fd); + packet.writeChar(','); + packet.writeHexWord32(uint32_t(info.fstat.buf)); + break; + + case eGDBSYS_gettimeofday: + packet.writeStr(GDB_F("gettimeofday,")); + packet.writeHexWord32(uint32_t(info.gettimeofday.tv)); + packet.writeChar(','); + packet.writeHexWord32(uint32_t(info.gettimeofday.tz)); + break; + + case eGDBSYS_isatty: + packet.writeStr(GDB_F("isatty,")); + packet.writeHexByte(info.isatty.fd); + break; + + case eGDBSYS_system: + packet.writeStr(GDB_F("system,")); + packet.writeStrRef(info.system.command); + break; } // Discard incoming '+' acknolwedgement from packet ++gdb_state.ack_count; gdb_state.syscall = syscall_active; - if(info.callback == nullptr) { - // No callback, wait for completion - while(gdb_state.syscall != syscall_ready) { - // Watchdog will fire if this takes too long - intentional - } - } + debug_i("syscall active"); } bool ATTR_GDBEXTERNFN gdb_syscall_complete(const char* data) diff --git a/Sming/gdb/gdbuart.cpp b/Sming/gdb/gdbuart.cpp index 3863881899..db21e684b0 100644 --- a/Sming/gdb/gdbuart.cpp +++ b/Sming/gdb/gdbuart.cpp @@ -147,64 +147,70 @@ size_t ATTR_GDBEXTERNFN gdbSendChar(char c) return gdb_uart_write_char(c); } -#if GDBSTUB_ENABLE_UART2 size_t gdbstub_send_user_data() { - size_t avail = 0; - +#if GDBSTUB_ENABLE_UART2 auto txbuf = user_uart == nullptr ? nullptr : user_uart->tx_buffer; - if(txbuf != nullptr) { - void* data; - while((avail = txbuf->getReadData(data)) != 0) { - size_t charCount; - unsigned used = uart_txfifo_count(GDB_UART); - unsigned space = UART_TX_FIFO_SIZE - used - 1; - if(gdb_state.attached) { - // $Onn#CC is smallest packet, for a single character, but we want to avoid that as it's inefficient - if(used >= 8) { - break; - } + if(txbuf == nullptr) { + return 0; + } - charCount = std::min((space - 3) / 2, avail); - GdbPacket packet; - packet.writeChar('O'); - packet.writeHexBlock(data, charCount); - uart_disable_interrupts(); - ++gdb_state.ack_count; - uart_restore_interrupts(); - } else { - charCount = gdb_uart_write(data, std::min(space, avail)); + size_t avail; + void* data; + while((avail = txbuf->getReadData(data)) != 0) { + size_t charCount; + unsigned used = uart_txfifo_count(GDB_UART); + unsigned space = UART_TX_FIFO_SIZE - used - 1; + if(gdb_state.attached) { + // $Onn#CC is smallest packet, for a single character, but we want to avoid that as it's inefficient + if(used >= 8) { + break; } - userDataSending = true; - - user_uart->tx_buffer->skipRead(charCount); - if(charCount != avail) { - break; // That's all for now - } + charCount = std::min((space - 3) / 2, avail); + GdbPacket packet; + packet.writeChar('O'); + packet.writeHexBlock(data, charCount); + uart_disable_interrupts(); + ++gdb_state.ack_count; + uart_restore_interrupts(); + } else { + charCount = gdb_uart_write(data, std::min(space, avail)); } - } -#if GDBSTUB_ENABLE_SYSCALL - // When all data has been sent, see if there's a pending syscall to send - if(avail == 0) { - gdbstub_syscall_execute(); + userDataSending = true; + + user_uart->tx_buffer->skipRead(charCount); + if(charCount != avail) { + break; // That's all for now + } } -#endif return avail; +#else + // UART2 disabled, no user data to send + return 0; +#endif } -static void SendUserDataQueued(uint32_t) +#if GDBSTUB_ENABLE_UART2 + +static void sendUserDataTask(uint32_t) { sendUserDataQueued = false; - gdbstub_send_user_data(); + + if(gdbstub_send_user_data() == 0) { +#if GDBSTUB_ENABLE_SYSCALL + // When all data has been sent, see if there's a pending syscall to send + gdbstub_syscall_execute(); +#endif + } } __forceinline void queueSendUserData() { if(!sendUserDataQueued) { - System.queueCallback(SendUserDataQueued); + System.queueCallback(sendUserDataTask); sendUserDataQueued = true; } } @@ -281,6 +287,8 @@ static void IRAM_ATTR gdb_uart_callback(uart_t* uart, uint32_t status) } } } +#else + bitClear(USIE(GDB_UART), UIFE); #endif // RX FIFO Full or RX FIFO Timeout ? From 6977b4bef9285d6ef19c04bd93e5d59c4f87e267 Mon Sep 17 00:00:00 2001 From: mikee47 Date: Wed, 3 Apr 2019 14:34:16 +0100 Subject: [PATCH 21/50] Update readme.md --- Sming/gdb/readme.md | 30 +++++++++++++++++++++--------- 1 file changed, 21 insertions(+), 9 deletions(-) diff --git a/Sming/gdb/readme.md b/Sming/gdb/readme.md index 487a8b51e2..23c2c2c724 100644 --- a/Sming/gdb/readme.md +++ b/Sming/gdb/readme.md @@ -22,7 +22,7 @@ GDB This is the application which runs on your development system and talks to `gdbstub`. - * Linux: A version of this should be available in $ESP_HOME/xtesnsa-lx106-elf/bin/xtensa-lx106-elf-gdb + * Linux: A version of this should be available in $ESP_HOME/xtensa-lx106-elf/bin/xtensa-lx106-elf-gdb * Windows: At time of writing, UDK doesn't provide a GDB application. You can find pre-built version of this at [SysProgs](http://gnutoolchains.com/esp8266/). Download and run the executable installer, then copy the `C:\SysGCC\esp8266\opt\xtensa-lx106-elf\bin\xtensa-lx106-elf-gdb.exe` to a suitable location. @@ -35,21 +35,33 @@ Usage * Optional: Add `gdb_do_break()` statements to your application. * Run `make clean`, then `make ENABLE_GDB=1 flash` to build and flash the application with debugging enabled * Run gdb, depending on your configuration immediately after resetting the board or after it has run into -an exception. The easiest way to do it is to use the provided script: xtensa-lx106-elf-gdb -x $SMING_HOME/gdb/gdbcmds -b 115200 -Change the '115200' into the baud rate your code uses. You may need to change the `gdbcmds` script to fit the -configuration of your hardware and build environment. +an exception. The easiest way to do it is to use the provided script: `make gdb`. + +To run manually in Linux: +`$ESP_HOME/xtensa-lx106-elf/bin/xtensa-lx106-elf-gdb -x $SMING_HOME/gdb/gdbcmds -b 115200 -ex "target remote /dev/ttyUSB0"` + +Windows command line: +`%ESP_HOME%\xtensa-lx106-elf\bin\xtensa-lx106-elf-gdb -x %SMING_HOME%\gdb\gdbcmds -b 115200 -ex "target remote COM4"` + +In both cases the appropriate baud rate and COM port should be substituted. Useful GDB commands ------------------- `c` Continue execution + `q` Quit and detach + +`where` Display current stopped location + `bt` Show stack backtrace -`disass` Disassemble -`disass/m` Disassemble, mix with source code + +`disass` Disassemble, `disass/m` to mix with source code + `print expr` Display a variable or other value -`print func()` Call a function, display result -`call func()` Call a function, discard result + +`print func()` Call a function, display result, or `call func()` to discard result + `tui enable` Provides a windowed interface within the console (only seems to work in Linux) Eclipse @@ -102,7 +114,7 @@ Error reported, "packet reply is too long" Whilst GDB is attached, input cannot be passed to application - Cause: GDB buffers keystrokes and replays them only when the target is interrupted (e.g. via ctrl+C), rather than passing them via serial connection. -- Solution: Application may use gdb_syscall interface to communicate with debugger. See `$(SMING_HOME)/system/gdb_syscall.h`. +- Solution: Application may use gdb_syscall interface to communicate with debugger. See `$(SMING_HOME)/system/gdb_syscall.h`, and LiveDebug sample. No apparent way to have second 'console' (windows terminology) separate from GDB interface - Cause: Unknown From 4a0a869d12d7c1c78b7c83053b6e1ae267394be1 Mon Sep 17 00:00:00 2001 From: mikee47 Date: Wed, 3 Apr 2019 14:47:20 +0100 Subject: [PATCH 22/50] Enable debug output in LiveDebug sample Want to see crash dumps with 'crash' command in non-GDB modes. Don't show prompt until system ready as system debug messages confuse command prompt. --- Sming/gdb/gdbsyscall.cpp | 2 +- samples/LiveDebug/app/application.cpp | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/Sming/gdb/gdbsyscall.cpp b/Sming/gdb/gdbsyscall.cpp index 5a6c0fb4ad..bd98567a7b 100644 --- a/Sming/gdb/gdbsyscall.cpp +++ b/Sming/gdb/gdbsyscall.cpp @@ -14,9 +14,9 @@ #if GDBSTUB_ENABLE_SYSCALL -#include #include "gdbsyscall.h" #include "GdbPacket.h" +#include #include #include "Platform/System.h" diff --git a/samples/LiveDebug/app/application.cpp b/samples/LiveDebug/app/application.cpp index f615ea755b..9a1ab8b930 100644 --- a/samples/LiveDebug/app/application.cpp +++ b/samples/LiveDebug/app/application.cpp @@ -399,8 +399,9 @@ void GDB_IRAM_ATTR init() { Serial.begin(SERIAL_BAUD_RATE); Serial.onDataReceived(onDataReceived); + Serial.systemDebugOutput(true); - readConsole(); + System.onReady(readConsole); pinMode(LED_PIN, OUTPUT); #if TIMER_TYPE == TIMERTYPE_SIMPLE From 3597d4d4fe02d8341fc51cbc3c874f8167122ccc Mon Sep 17 00:00:00 2001 From: mikee47 Date: Wed, 3 Apr 2019 15:06:37 +0100 Subject: [PATCH 23/50] Add `restart` command to LiveDebug sample --- samples/LiveDebug/app/application.cpp | 31 ++++++++++++--------------- 1 file changed, 14 insertions(+), 17 deletions(-) diff --git a/samples/LiveDebug/app/application.cpp b/samples/LiveDebug/app/application.cpp index 9a1ab8b930..3141d9ab70 100644 --- a/samples/LiveDebug/app/application.cpp +++ b/samples/LiveDebug/app/application.cpp @@ -229,7 +229,6 @@ void fileStat(const char* filename) return; } - Serial.printf(_F("sizeof(stat) == %u, words = %u\r\n"), sizeof(stat), sizeof(stat) / 4); #define PRT(x) Serial.printf(_F(" " #x " = %u\r\n"), stat.x) #define PRT_HEX(x) Serial.printf(_F(" " #x " = 0x%08x\r\n"), stat.x) #define PRT_TIME(x) \ @@ -298,19 +297,24 @@ bool handleCommand(const String& cmd) Serial.flush(); *(uint8_t*)0 = 0; Serial.println("...still running!"); + } else if(cmd.equalsIgnoreCase(F("restart"))) { + Serial.println(_F("Restarting....")); + System.restart(); + return false; } else if(cmd.equalsIgnoreCase(F("exit"))) { // End console test Serial.println(_F("Resuming normal program execution.")); return false; } else if(cmd.equalsIgnoreCase(F("help"))) { Serial.print(_F("LiveDebug interactive debugger sample. Available commands:\n" - " readfile1 : read and display a test file\n" - " readfile2 : read a larger file asynchronously\n" - " stat : issue a 'stat' call\n" - " ls : list directory\n" - " break : break into debugger\n" - " crash : write to address 0x00000000, see what happens\n" - " exit : resume normal application execution\n")); + " readfile1 : Read and display a test file\n" + " readfile2 : Read a larger file asynchronously\n" + " stat : Issue a 'stat' call\n" + " ls : List directory\n" + " break : Break into debugger\n" + " crash : Write to address 0x00000000, see what happens\n" + " restart : Restart the system\n" + " exit : Rresume normal application execution\n")); } else { Serial.println(_F("Unknown command, try 'help'")); } @@ -334,25 +338,18 @@ bool handleCommand(const String& cmd) */ void onConsoleReadCompleted(const GdbSyscallInfo& info) { - Serial.print(_F("gdb_read_console() returned ")); - Serial.print(info.result); + debug_i("gdb_read_console() returned %d", info.result); char* bufptr = static_cast(info.read.buffer); if(info.result <= 0) { - Serial.println(); delete bufptr; } else { unsigned len = info.result; if(bufptr[len - 1] == '\n') { --len; } - if(len == 0) { - Serial.println(); - } else { + if(len > 0) { String cmd(bufptr, len); delete bufptr; - Serial.print(_F(": \"")); - Serial.print(cmd); - Serial.println('"'); if(!handleCommand(cmd)) { return; From a8396de430f67398e10a7f1b105347d59941f231 Mon Sep 17 00:00:00 2001 From: mikee47 Date: Wed, 3 Apr 2019 17:13:35 +0100 Subject: [PATCH 24/50] LiveDebug sample, use static buffer for reading console (simpler) --- samples/LiveDebug/app/application.cpp | 22 ++++++++++------------ 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/samples/LiveDebug/app/application.cpp b/samples/LiveDebug/app/application.cpp index 3141d9ab70..34ab608cd5 100644 --- a/samples/LiveDebug/app/application.cpp +++ b/samples/LiveDebug/app/application.cpp @@ -5,6 +5,9 @@ #define LED_PIN 2 // Note: LED is attached to UART1 TX output +// Max length of debug command +const unsigned MAX_COMMAND_LENGTH = 64; + #define TIMERTYPE_HARDWARE 1 #define TIMERTYPE_SIMPLE 2 #define TIMERTYPE_TIMER 3 @@ -338,19 +341,17 @@ bool handleCommand(const String& cmd) */ void onConsoleReadCompleted(const GdbSyscallInfo& info) { - debug_i("gdb_read_console() returned %d", info.result); + int result = info.result; char* bufptr = static_cast(info.read.buffer); - if(info.result <= 0) { - delete bufptr; - } else { - unsigned len = info.result; + + debug_i("gdb_read_console() returned %d", result); + if(result > 0) { + unsigned len = result; if(bufptr[len - 1] == '\n') { --len; } if(len > 0) { String cmd(bufptr, len); - delete bufptr; - if(!handleCommand(cmd)) { return; } @@ -369,14 +370,11 @@ void readConsole() showPrompt(); if(gdb_present() == eGDB_Attached) { // Issue the syscall - const unsigned bufsize = 256; - auto buffer = new char[bufsize]; - int res = gdb_console_read(buffer, bufsize, onConsoleReadCompleted); + static char buffer[MAX_COMMAND_LENGTH]; + int res = gdb_console_read(buffer, MAX_COMMAND_LENGTH, onConsoleReadCompleted); if(res < 0) { Serial.printf(_F("gdb_console_read() failed, %d\r\n"), res); Serial.println(_F("Is GDBSTUB_ENABLE_SYSCALL enabled ?")); - Serial.println(_F("Did you build with ENABLE_GDB=1 ?")); - delete buffer; showPrompt(); } From 9abf8089b4595c96d81e312b7d157e8ee845d0a0 Mon Sep 17 00:00:00 2001 From: mikee47 Date: Wed, 3 Apr 2019 18:01:50 +0100 Subject: [PATCH 25/50] Add `gdb_on_attach()` function and update LiveDebug sample If sample is built with `GDBSTUB_BREAK_ON_INIT=0` then console reading loop is never executed because of the initial attachment check. If the debugger is exited (detaching from the target), then run again we'll never get the command prompt back without resetting. Defining the `gdb_on_attach()` function solves these issues. --- Sming/appspecific/gdb/gdb_hooks.cpp | 5 +-- Sming/gdb/gdbstub.cpp | 55 +++++++++++++++++---------- Sming/system/include/gdb_hooks.h | 6 +++ samples/LiveDebug/app/application.cpp | 10 ++++- 4 files changed, 50 insertions(+), 26 deletions(-) diff --git a/Sming/appspecific/gdb/gdb_hooks.cpp b/Sming/appspecific/gdb/gdb_hooks.cpp index ef4f1fac51..43792dc81e 100644 --- a/Sming/appspecific/gdb/gdb_hooks.cpp +++ b/Sming/appspecific/gdb/gdb_hooks.cpp @@ -192,8 +192,6 @@ void ATTR_GDBINIT gdb_init() #endif } -#ifndef ENABLE_GDB - extern "C" { static unsigned IRAM_ATTR __gdb_no_op() { @@ -205,10 +203,9 @@ static unsigned IRAM_ATTR __gdb_no_op() void gdb_enable(bool state) NOOP; void gdb_do_break(void) NOOP; GdbState gdb_present(void) NOOP; +void gdb_on_attach(bool attached) NOOP; }; -#endif - int __attribute__((weak)) gdb_syscall(const GdbSyscallInfo& info) { return -1; diff --git a/Sming/gdb/gdbstub.cpp b/Sming/gdb/gdbstub.cpp index 42d8627a95..1bff6a1034 100644 --- a/Sming/gdb/gdbstub.cpp +++ b/Sming/gdb/gdbstub.cpp @@ -15,6 +15,14 @@ * Applications may use UART2, which is virtualised (using a generic callback mechanism) so * output may sent to debug console when GDB is attached. * + * Note from GDB manual: + * + * At a minimum, a stub is required to support the ‘g’ and ‘G’ commands for register access, + * and the ‘m’ and ‘M’ commands for memory access. Stubs that only control single-threaded + * targets can implement run control with the ‘c’ (continue), and ‘s’ (step) commands. Stubs + * that support multi-threading targets should support the ‘vCont’ command. All other commands + * are optional. + * *********************************************************************************/ #include @@ -28,6 +36,7 @@ #include "exceptions.h" #include "gdb/registers.h" #include "gdbsyscall.h" +#include "Platform/System.h" extern "C" { void system_restart_core(); @@ -72,13 +81,11 @@ volatile gdb_state_t gdb_state; ///< Global state static char commandBuffer[MAX_COMMAND_LENGTH + 1]; ///< Buffer for incoming/outgoing GDB commands static int32_t singleStepPs = -1; // Stores ps (Program State) when single-stepping instruction. -1 when not in use. -// Error states used by the routines that grab stuff from the incoming gdb packet +// Return codes from handleCommand() enum GdbResult { - ST_ENDPACKET = -1, - ST_ERR = -2, - ST_OK = -3, - ST_CONT = -4, - ST_DETACH = -5, + ST_OK, ///< Regular command + ST_CONT, ///< Continue with program execution (but stay attached) + ST_DETACH, ///< Detach from GDB and continue with program execution }; // For simplifying access to word-aligned data @@ -335,7 +342,9 @@ static unsigned ATTR_GDBEXTERNFN readCommand() } /* - * Handle a command as received from GDB. + * @brief Handle a command as received from GDB + * @param cmdlen Number of characters in received command + * @retval GdbResult One of ST_OK, ST_CONT or ST_DETACH */ static GdbResult ATTR_GDBEXTERNFN handleCommand(unsigned cmdLen) { @@ -717,16 +726,19 @@ static GdbResult ATTR_GDBEXTERNFN handleCommand(unsigned cmdLen) /** * @brief Wait for incoming commands and process them, only returning when instructed to do so by GDB + * @param waitForStart false if start character '$' has already been received + * @param allowDetach When handling exceptions we ignore detach requests * @retval GdbResult cause of command loop exit * @note Flags that gdb has been attached whenever a gdb-formatted packet is received * Keeps reading commands until either a continue, detach, or kill command is received * It is not necessary for gdb to be attached for it to be paused * For example, during an exception break, the program is paused but gdb might not be attached yet */ -GdbResult ATTR_GDBEXTERNFN commandLoop(bool waitForStart = true) +void ATTR_GDBEXTERNFN commandLoop(bool waitForStart, bool allowDetach) { - GdbResult result = ST_OK; - do { + bool initiallyAttached = gdb_state.attached; + + while(true) { if(waitForStart) { while(gdbReceiveChar() != '$') { // wait for start @@ -737,15 +749,19 @@ GdbResult ATTR_GDBEXTERNFN commandLoop(bool waitForStart = true) auto cmdLen = readCommand(); if(cmdLen != 0) { gdb_state.attached = true; - result = handleCommand(cmdLen); + GdbResult result = handleCommand(cmdLen); + if(result == ST_CONT) { + break; + } else if(result == ST_DETACH && allowDetach) { + gdb_state.attached = false; + break; + } } - } while(result == ST_OK); - - if(result == ST_DETACH) { - gdb_state.attached = false; } - return result; + if(gdb_state.attached != initiallyAttached) { + System.queueCallback(TaskCallback(gdb_on_attach), gdb_state.attached); + } } /* @@ -834,10 +850,10 @@ static void __attribute__((noinline)) gdbstub_handle_debug_exception_flash() } if(bitRead(gdb_state.flags, DBGFLAG_PACKET_STARTED)) { - commandLoop(false); + commandLoop(false, true); } else { sendReason(); - commandLoop(); + commandLoop(true, true); } } @@ -892,8 +908,7 @@ void gdbstub_handle_exception() bitSet(gdb_state.flags, DBGFLAG_SYSTEM_EXCEPTION); sendReason(); - while(commandLoop() != ST_CONT) { - } + commandLoop(true, false); gdb_state.flags = 0; pauseHardwareTimer(false); diff --git a/Sming/system/include/gdb_hooks.h b/Sming/system/include/gdb_hooks.h index 9a1f8c02be..b023b8549c 100644 --- a/Sming/system/include/gdb_hooks.h +++ b/Sming/system/include/gdb_hooks.h @@ -63,6 +63,12 @@ typedef enum { */ GdbState gdb_present(void); +/** + * @brief Called from task queue when GDB attachment status changes + * @note User can implement this function to respond to attachment changes + */ +void gdb_on_attach(bool attached); + /** * @brief Called on unexpected system reset */ diff --git a/samples/LiveDebug/app/application.cpp b/samples/LiveDebug/app/application.cpp index 34ab608cd5..a9f36b4911 100644 --- a/samples/LiveDebug/app/application.cpp +++ b/samples/LiveDebug/app/application.cpp @@ -390,14 +390,20 @@ void readConsole() } } +extern "C" void gdb_on_attach(bool attached) +{ + debug_i("GdbAttach(%d)", attached); + if(attached) { + readConsole(); + } +} + void GDB_IRAM_ATTR init() { Serial.begin(SERIAL_BAUD_RATE); Serial.onDataReceived(onDataReceived); Serial.systemDebugOutput(true); - System.onReady(readConsole); - pinMode(LED_PIN, OUTPUT); #if TIMER_TYPE == TIMERTYPE_SIMPLE procTimer.setCallback(SimpleTimerCallback(blink)); From 84fac9a56972ecec22d34257b61c045507afdabc Mon Sep 17 00:00:00 2001 From: mikee47 Date: Thu, 4 Apr 2019 23:14:20 +0100 Subject: [PATCH 26/50] errno can be > 255 so treat as unsigned int, not uint8_t. --- Sming/gdb/gdbstub.cpp | 2 +- Sming/gdb/gdbsyscall.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Sming/gdb/gdbstub.cpp b/Sming/gdb/gdbstub.cpp index 1bff6a1034..2190626135 100644 --- a/Sming/gdb/gdbstub.cpp +++ b/Sming/gdb/gdbstub.cpp @@ -65,7 +65,7 @@ static PGM_P const registerNames[] PROGMEM = { extern GdbstubSavedRegisters gdbstub_savedRegs; -typedef uint8_t ERRNO_T; +typedef unsigned ERRNO_T; /* * Maximum length of a GDB command packet. Has to be at least able to fit the G command. diff --git a/Sming/gdb/gdbsyscall.cpp b/Sming/gdb/gdbsyscall.cpp index bd98567a7b..ea23247ae9 100644 --- a/Sming/gdb/gdbsyscall.cpp +++ b/Sming/gdb/gdbsyscall.cpp @@ -216,7 +216,7 @@ bool ATTR_GDBEXTERNFN gdb_syscall_complete(const char* data) if(*data == ',') { ++data; - uint8_t err = GdbPacket::readHexValue(data); + unsigned err = GdbPacket::readHexValue(data); syscall_info.result = -err; } else { syscall_info.result = len; From 0db6e165d764cccc51dd40b907efd8e8f4156518 Mon Sep 17 00:00:00 2001 From: mikee47 Date: Sat, 6 Apr 2019 22:18:38 +0100 Subject: [PATCH 27/50] Stack dumping code needs to use \r\n instead of just \n --- Sming/appspecific/gdb/gdb_hooks.cpp | 43 ++++++++++++++++------------- 1 file changed, 24 insertions(+), 19 deletions(-) diff --git a/Sming/appspecific/gdb/gdb_hooks.cpp b/Sming/appspecific/gdb/gdb_hooks.cpp index 43792dc81e..47db74f532 100644 --- a/Sming/appspecific/gdb/gdb_hooks.cpp +++ b/Sming/appspecific/gdb/gdb_hooks.cpp @@ -44,11 +44,12 @@ GdbstubSavedRegisters gdbstub_savedRegs; void debug_print_stack(uint32_t start, uint32_t end) { - m_puts(_F("\nStack dump:\n")); - PSTR_ARRAY(instructions, "To decode the stack dump call from command line:\n" - " python $SMING_HOME/../tools/decode-stacktrace.py out/build/app.out\n" - "and copy & paste the text enclosed in '===='.\n"); - PSTR_ARRAY(separatorLine, "\n================================================================\n"); + m_puts(_F("\r\n" + "Stack dump:\r\n")); + PSTR_ARRAY(instructions, "To decode the stack dump call from command line:\r\n" + " python $SMING_HOME/../tools/decode-stacktrace.py out/build/app.out\r\n" + "and copy & paste the text enclosed in '===='.\r\n"); + PSTR_ARRAY(separatorLine, "\n================================================================\r\n"); m_puts(instructions); m_puts(separatorLine); for(uint32_t addr = start; addr < end; addr += 0x10) { @@ -56,7 +57,7 @@ void debug_print_stack(uint32_t start, uint32_t end) // rough indicator: stack frames usually have SP saved as the second word bool looksLikeStackFrame = (values[2] == addr + 0x10); - m_printf(_F("%08x: %08x %08x %08x %08x %c\n"), addr, values[0], values[1], values[2], values[3], + m_printf(_F("%08x: %08x %08x %08x %08x %c\r\n"), addr, values[0], values[1], values[2], values[3], (looksLikeStackFrame) ? '<' : ' '); } m_puts(separatorLine); @@ -68,13 +69,17 @@ void debug_crash_callback(const rst_info* rst_info, uint32_t stack, uint32_t sta #if ENABLE_CRASH_DUMP switch(rst_info->reason) { case REASON_EXCEPTION_RST: - m_printf(_F("\n\n***** Exception Reset (%u):\n" - "epc1=0x%08x epc2=0x%08x epc3=0x%08x excvaddr=0x%08x depc=0x%08x\n"), + m_printf(_F("\r\n" + "\n" + "***** Exception Reset (%u):\r\n" + "epc1=0x%08x epc2=0x%08x epc3=0x%08x excvaddr=0x%08x depc=0x%08x\r\n"), rst_info->exccause, rst_info->epc1, rst_info->epc2, rst_info->epc3, rst_info->excvaddr, rst_info->depc); break; case REASON_SOFT_WDT_RST: - m_puts(_F("\n\n***** Software Watchdog Reset\n")); + m_puts(_F("\r\n" + "\n" + "***** Software Watchdog Reset\r\n")); break; default: // @@ -92,33 +97,33 @@ void dumpExceptionInfo(UserFrame* frame) ets_wdt_disable(); auto& reg = gdbstub_savedRegs; - m_printf(_F("\n\n***** Fatal exception %u"), reg.cause); + m_printf(_F("\r\n" + "\n" + "***** Fatal exception %u"), + reg.cause); if(reg.cause <= EXCCAUSE_MAX) { - m_putc(' '); - m_putc('('); char name[32]; memcpy_P(name, exceptionNames[reg.cause], sizeof(name)); name[sizeof(name) - 1] = '\0'; - m_puts(name); - m_putc(')'); + m_printf(_F(" (%s)"), name); } - m_putc('\n'); + m_puts("\r\n"); // EXCVADDR isn't set for all exceptions, so zero it out rather than show potentially misleading information if(reg.cause < EXCCAUSE_UNALIGNED && reg.cause != EXCCAUSE_IFETCHERROR && reg.cause != EXCCAUSE_LOAD_STORE_ERROR) { reg.excvaddr = 0; } - m_printf(_F("pc=0x%08x sp=0x%08x excvaddr=0x%08x\n"), reg.pc, reg.a[1], reg.excvaddr); - m_printf(_F("ps=0x%08x sar=0x%08x vpri=0x%08x\n"), reg.ps, reg.sar, reg.vpri); + m_printf(_F("pc=0x%08x sp=0x%08x excvaddr=0x%08x\r\n"), reg.pc, reg.a[1], reg.excvaddr); + m_printf(_F("ps=0x%08x sar=0x%08x vpri=0x%08x\r\n"), reg.ps, reg.sar, reg.vpri); for(int i = 0; i < 16; i++) { uint32_t r = reg.a[i]; m_printf(_F("r%02u: 0x%08x=%10d "), i, r, r); if(i % 3 == 2) { - m_putc('\n'); + m_puts("\r\n"); } } - m_putc('\n'); + m_puts("\r\n"); debug_print_stack(reg.a[1], 0x3fffffb0); ets_wdt_enable(); } From 623fc8dc40754b20333ee2514ae860792ac0c72f Mon Sep 17 00:00:00 2001 From: mikee47 Date: Sat, 6 Apr 2019 22:25:45 +0100 Subject: [PATCH 28/50] For all builds, reset serial ports to user-specified baud rate in `gdb_init()` --- Sming/appspecific/gdb/gdb_hooks.cpp | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/Sming/appspecific/gdb/gdb_hooks.cpp b/Sming/appspecific/gdb/gdb_hooks.cpp index 47db74f532..439bcde794 100644 --- a/Sming/appspecific/gdb/gdb_hooks.cpp +++ b/Sming/appspecific/gdb/gdb_hooks.cpp @@ -186,8 +186,15 @@ static void ATTR_GDBINIT installExceptionHandler() #endif // HOOK_SYSTEM_EXCEPTIONS -void ATTR_GDBINIT gdb_init() +extern "C" { + +void ATTR_GDBINIT gdb_init(void) { + // Reset all serial ports to user-specified baud rate + for(unsigned i = 0; i < UART_PHYSICAL_COUNT; ++i) { + uart_set_baudrate_reg(i, SERIAL_BAUD_RATE); + } + #ifdef HOOK_SYSTEM_EXCEPTIONS installExceptionHandler(); #endif @@ -197,8 +204,7 @@ void ATTR_GDBINIT gdb_init() #endif } -extern "C" { -static unsigned IRAM_ATTR __gdb_no_op() +static unsigned IRAM_ATTR __gdb_no_op(void) { return 0; } From eb0b4f4796a6b152a87746bb8fcb5f6a77991830 Mon Sep 17 00:00:00 2001 From: mikee47 Date: Sat, 6 Apr 2019 22:37:58 +0100 Subject: [PATCH 29/50] Add `gdbFlushUserData()` function and rename `gdbstub_send_user_data()` to `gdbSendUserData()` --- Sming/gdb/gdbstub.cpp | 5 +---- Sming/gdb/gdbsyscall.cpp | 6 ++---- Sming/gdb/gdbsyscall.h | 2 +- Sming/gdb/gdbuart.cpp | 24 +++++++++++++++--------- Sming/gdb/gdbuart.h | 7 ++++++- 5 files changed, 25 insertions(+), 19 deletions(-) diff --git a/Sming/gdb/gdbstub.cpp b/Sming/gdb/gdbstub.cpp index 2190626135..eaa05eea9f 100644 --- a/Sming/gdb/gdbstub.cpp +++ b/Sming/gdb/gdbstub.cpp @@ -195,10 +195,7 @@ static ERRNO_T ATTR_GDBEXTERNFN writeMemoryBlock(uint32_t addr, const void* data */ static void ATTR_GDBEXTERNFN sendReason() { - // Flush any debug info to serial - while(gdbstub_send_user_data() != 0) { - // - } + gdbFlushUserData(); GdbPacket packet; packet.writeChar('T'); diff --git a/Sming/gdb/gdbsyscall.cpp b/Sming/gdb/gdbsyscall.cpp index ea23247ae9..ecbd951829 100644 --- a/Sming/gdb/gdbsyscall.cpp +++ b/Sming/gdb/gdbsyscall.cpp @@ -75,9 +75,7 @@ int gdb_syscall(const GdbSyscallInfo& info) debug_i("syscall pending"); if(info.callback == nullptr) { - // Wait for all user data to be output before sending request - while(gdbstub_send_user_data() != 0) { - } + gdbFlushUserData(); gdbstub_syscall_execute(); // No callback, wait for completion while(gdb_state.syscall != syscall_ready) { @@ -86,7 +84,7 @@ int gdb_syscall(const GdbSyscallInfo& info) debug_i("syscall returned %d", syscall_info.result); return syscall_info.result; } else { - if(gdbstub_send_user_data() == 0) { + if(gdbSendUserData() == 0) { gdbstub_syscall_execute(); } else { // will be executed via user task in gdbuart diff --git a/Sming/gdb/gdbsyscall.h b/Sming/gdb/gdbsyscall.h index 6d1959ab9b..226e3791b3 100644 --- a/Sming/gdb/gdbsyscall.h +++ b/Sming/gdb/gdbsyscall.h @@ -16,7 +16,7 @@ #include "gdbstub.h" /** - * @brief Called from gdbstub_send_user_data() when user data has finished sending + * @brief Called from gdbSendUserData() when user data has finished sending */ void gdbstub_syscall_execute(); diff --git a/Sming/gdb/gdbuart.cpp b/Sming/gdb/gdbuart.cpp index db21e684b0..3b5c8e8ab4 100644 --- a/Sming/gdb/gdbuart.cpp +++ b/Sming/gdb/gdbuart.cpp @@ -27,7 +27,7 @@ static uart_t* gdb_uart; // Port debugger is attached to static uart_t* user_uart; // If open, virtual port being used for user passthrough static uint32_t user_uart_status; // See gdb_uart_callback (ISR handler) static volatile bool userDataSending; // Transmit completion callback invoked on user uart -static bool sendUserDataQueued; // Ensures only one call to gdbstub_send_user_data() is queued at a time +static bool sendUserDataQueued; // Ensures only one call to gdbSendUserData() is queued at a time #endif // Get number of characters in receive FIFO @@ -147,7 +147,7 @@ size_t ATTR_GDBEXTERNFN gdbSendChar(char c) return gdb_uart_write_char(c); } -size_t gdbstub_send_user_data() +size_t ATTR_GDBEXTERNFN gdbSendUserData() { #if GDBSTUB_ENABLE_UART2 auto txbuf = user_uart == nullptr ? nullptr : user_uart->tx_buffer; @@ -193,13 +193,23 @@ size_t gdbstub_send_user_data() #endif } +void ATTR_GDBEXTERNFN gdbFlushUserData() +{ +#if GDBSTUB_ENABLE_UART2 + while(gdbSendUserData() != 0) { + wdt_feed(); + system_soft_wdt_feed(); + } +#endif +} + #if GDBSTUB_ENABLE_UART2 static void sendUserDataTask(uint32_t) { sendUserDataQueued = false; - if(gdbstub_send_user_data() == 0) { + if(gdbSendUserData() == 0) { #if GDBSTUB_ENABLE_SYSCALL // When all data has been sent, see if there's a pending syscall to send gdbstub_syscall_execute(); @@ -237,7 +247,7 @@ static void userUartNotify(uart_t* uart, uart_notify_code_t code) * loop indefinitely if UART_OPT_TXWAIT is set. */ if(uart->tx_buffer->isFull()) { - gdbstub_send_user_data(); + gdbSendUserData(); } else { queueSendUserData(); } @@ -245,11 +255,7 @@ static void userUartNotify(uart_t* uart, uart_notify_code_t code) } case UART_NOTIFY_WAIT_TX: { - /* - * Ensure all data has been written to hardware - */ - while(gdbstub_send_user_data() != 0) { - } + gdbFlushUserData(); break; } diff --git a/Sming/gdb/gdbuart.h b/Sming/gdb/gdbuart.h index 07439d0da4..5c84ac9777 100644 --- a/Sming/gdb/gdbuart.h +++ b/Sming/gdb/gdbuart.h @@ -38,6 +38,11 @@ size_t gdbSendChar(char c); * (via notify callback) or via task callback queued from ISR. We don't do this inside * the ISR as all the code (including packetising) would need to be in IRAM. */ -size_t gdbstub_send_user_data(); +size_t gdbSendUserData(); + +/** + * @brief Ensure all user data has been written to serial port + */ +void gdbFlushUserData(); #endif /* _GDB_GDBUART_H_ */ From 103a5d6a9fb25b3d942633a16dfa5369bbfc357c Mon Sep 17 00:00:00 2001 From: mikee47 Date: Sat, 6 Apr 2019 23:07:56 +0100 Subject: [PATCH 30/50] Implement `gdb_detach()` --- Sming/appspecific/gdb/gdb_hooks.cpp | 1 + Sming/gdb/gdbstub.cpp | 10 ++++++++++ Sming/system/include/gdb_hooks.h | 6 ++++++ 3 files changed, 17 insertions(+) diff --git a/Sming/appspecific/gdb/gdb_hooks.cpp b/Sming/appspecific/gdb/gdb_hooks.cpp index 439bcde794..184a02cf89 100644 --- a/Sming/appspecific/gdb/gdb_hooks.cpp +++ b/Sming/appspecific/gdb/gdb_hooks.cpp @@ -215,6 +215,7 @@ void gdb_enable(bool state) NOOP; void gdb_do_break(void) NOOP; GdbState gdb_present(void) NOOP; void gdb_on_attach(bool attached) NOOP; +void gdb_detach(void) NOOP; }; int __attribute__((weak)) gdb_syscall(const GdbSyscallInfo& info) diff --git a/Sming/gdb/gdbstub.cpp b/Sming/gdb/gdbstub.cpp index eaa05eea9f..2daf59f2ea 100644 --- a/Sming/gdb/gdbstub.cpp +++ b/Sming/gdb/gdbstub.cpp @@ -947,6 +947,16 @@ GdbState IRAM_ATTR gdb_present() return gdb_state.attached ? eGDB_Attached : eGDB_Detached; } +void gdb_detach() +{ + if(gdb_state.attached) { + GdbPacket packet; + packet.writeChar('W'); + gdb_state.attached = false; + gdb_on_attach(false); + } +} + void gdb_enable(bool state) { gdb_state.enabled = state; diff --git a/Sming/system/include/gdb_hooks.h b/Sming/system/include/gdb_hooks.h index b023b8549c..ed6806f9e1 100644 --- a/Sming/system/include/gdb_hooks.h +++ b/Sming/system/include/gdb_hooks.h @@ -69,6 +69,12 @@ GdbState gdb_present(void); */ void gdb_on_attach(bool attached); +/** + * @brief Detach from GDB, if attached + * @note We send GDB an 'exit process' message + */ +void gdb_detach(); + /** * @brief Called on unexpected system reset */ From 8f8f84a98e34d4b3044dede0f89a57a9d63bb534 Mon Sep 17 00:00:00 2001 From: mikee47 Date: Sat, 6 Apr 2019 23:10:13 +0100 Subject: [PATCH 31/50] LiveDebug sample revisions * Recognise 0x7f as delete character to support use of putty in windows as serial terminal * Define commands in a table to de-clutter code and ensure consistent help output * Rename 'crash' command as 'write0', and add 'read0' command * Ensure prompt is displayed at startup if GDB isn't being used * 'exit' command calls `gdb_detach()` --- samples/LiveDebug/app/application.cpp | 194 +++++++++++++++++++------- 1 file changed, 140 insertions(+), 54 deletions(-) diff --git a/samples/LiveDebug/app/application.cpp b/samples/LiveDebug/app/application.cpp index a9f36b4911..753f451e30 100644 --- a/samples/LiveDebug/app/application.cpp +++ b/samples/LiveDebug/app/application.cpp @@ -100,16 +100,14 @@ void onDataReceived(Stream& source, char arrivedChar, unsigned short availableCh while((c = Serial.read()) >= 0) { switch(c) { case '\b': // delete (backspace) + case 0x7f: // xterm ctrl-? if(commandLength > 0) { --commandLength; - Serial.print('\b'); - Serial.print(' '); - Serial.print('\b'); + Serial.print(_F("\b \b")); } break; case '\r': case '\n': - // m_printHex("CMD", commandBuffer, commandLength); if(commandLength > 0) { Serial.println(); String cmd(commandBuffer, commandLength); @@ -161,7 +159,7 @@ void readFile(const char* filename, bool display) void readConsole(); /* - * Opening, reading and closing a fill are all done using asynchronous syscalls. + * A more advanced way to use host File I/O using asynchronous syscalls. * The initial open() is performed via readFileAsync(), the remaining operations are handled * in this callback function. */ @@ -215,6 +213,7 @@ void asyncReadCallback(const GdbSyscallInfo& info) } /* + * Read a file using callbacks. * Note that filename must be in persistent memory (e.g. flash string) as call may not be started * immediately. */ @@ -256,18 +255,129 @@ void fileStat(const char* filename) #undef PRT_TIME } -time_t getTimeOfDay() +/* + * Keep commands and their description together to ensure 'help' is consistent. + * This also helps to keep the code clean and easy to read. + */ +#define COMMAND_MAP(XX) \ + XX(readfile1, "Use syscall file I/O functions to read and display a host file") \ + XX(readfile2, "Read a larger host file asynchronously, so data is processed in a callback function") \ + XX(stat, "Use `syscall_stat` function to get details for a host file") \ + XX(ls, "Use `syscall_system` function to perform a directory listing on the host") \ + XX(time, "Use `syscall_gettimeofday` to get current time from host") \ + XX(break, "Demonstrated `gdb_do_break()` function to pause this application and obtain a GDB command prompt") \ + XX(read0, "Read from invalid address") \ + XX(write0, "Write to invalid address") \ + XX(restart, "Restart the system") \ + XX(exit, "Detach from GDB and resume normal application execution") \ + XX(help, "Display this command summary") + +/* + * Macro to simplify command handler function creation. + * Function returns true to start another 'readConsole' request. + * If the operation is completed via callback then it returns false instead, and the readConsole called at that point. + */ +#define COMMAND_HANDLER(name) static bool handleCommand_##name() + +COMMAND_HANDLER(readfile1) +{ + // Read a small file and display it + readFile(_F("Makefile"), true); + return true; +} + +COMMAND_HANDLER(readfile2) +{ + // Read a larger file asynchronously and analyse transfer speed + Serial.println(_F("Please wait...")); + readFileAsync(PSTR("README.md")); + return false; // When read has completed, readConsole() will be called again +} + +COMMAND_HANDLER(stat) +{ + fileStat(_F("Makefile")); + return true; +} + +COMMAND_HANDLER(time) { gdb_timeval_t tv; int res = gdb_syscall_gettimeofday(&tv, nullptr); if(res < 0) { Serial.printf(_F("gdb_syscall_gettimeofday() returned %d\r\n"), res); - return 0; } else { Serial.printf(_F("tv_sec = %u, tv_usec = %u, "), tv.tv_sec, uint32_t(tv.tv_usec)); Serial.println(DateTime(tv.tv_sec).toFullDateTimeString() + _F(" UTC")); - return tv.tv_sec; } + return true; +} + +COMMAND_HANDLER(ls) +{ + int res = gdb_syscall_system(PSTR("ls -la")); + Serial.printf(_F("gdb_syscall_system() returned %d\r\n"), res); + return true; +} + +COMMAND_HANDLER(break) +{ + Serial.println(_F("Calling gdb_do_break()")); + gdb_do_break(); + return true; +} + +COMMAND_HANDLER(read0) +{ + Serial.println(_F("Crashing app by reading from address 0\r\n" + "At GDB prompt, enter `set $pc = $pc + 3` to skip offending instruction,\r\n" + "then enter `c` to continue")); + Serial.flush(); + uint8_t value = *(uint8_t*)0; + Serial.printf("Value at address 0 = 0x%02x\r\n", value); + return true; +} + +COMMAND_HANDLER(write0) +{ + Serial.println(_F("Crashing app by writing to address 0\r\n" + "At GDB prompt, enter `set $pc = $pc + 3` to skip offending instruction,\r\n" + "then enter `c` to continue")); + Serial.flush(); + *(uint8_t*)0 = 0; + Serial.println("...still running!"); + return true; +} + +COMMAND_HANDLER(restart) +{ + Serial.println(_F("Restarting....")); + System.restart(); + return false; +} + +COMMAND_HANDLER(exit) +{ + // End console test + Serial.print(_F("Calling gdb_detach() - ")); + if(gdb_present() == eGDB_Attached) { + Serial.println(_F("resuming normal program execution.")); + } else if(gdb_present() == eGDB_Detached) { + Serial.println(_F("not attached, so does nothing")); + } else { + Serial.println(_F("Application isn't compiled using ENABLE_GDB so this does nothing.")); + } + Serial.flush(); + gdb_detach(); + return false; +} + +COMMAND_HANDLER(help) +{ + Serial.print(_F("LiveDebug interactive debugger sample. Available commands:\r\n")); +#define XX(tag, desc) Serial.println(_F(" " #tag " : " desc)); + COMMAND_MAP(XX) +#undef XX } /** @@ -276,53 +386,20 @@ time_t getTimeOfDay() */ bool handleCommand(const String& cmd) { - if(cmd.equalsIgnoreCase(F("readfile1"))) { - // Read a small file and display it - readFile(_F("Makefile"), true); - } else if(cmd.equalsIgnoreCase(F("readfile2"))) { - // Read a larger file asynchronously and analyse transfer speed - Serial.println(_F("Please wait...")); - readFileAsync(PSTR("README.md")); - return false; // When read has completed, readConsole() will be called again - } else if(cmd.equalsIgnoreCase(F("stat"))) { - fileStat(_F("Makefile")); - } else if(cmd.equalsIgnoreCase(F("time"))) { - getTimeOfDay(); - } else if(cmd.equalsIgnoreCase(F("ls"))) { - int res = gdb_syscall_system(PSTR("ls -la")); - Serial.printf(_F("gdb_syscall_system() returned %d\r\n"), res); - } else if(cmd.equalsIgnoreCase(F("break"))) { - gdb_do_break(); - } else if(cmd.equalsIgnoreCase(F("crash"))) { - Serial.println(_F("Crashing app by writing to address 0\n" - "At GDB prompt, enter `set $pc = $pc + 3` to skip offending instruction,\n" - "then enter `c` to continue")); - Serial.flush(); - *(uint8_t*)0 = 0; - Serial.println("...still running!"); - } else if(cmd.equalsIgnoreCase(F("restart"))) { - Serial.println(_F("Restarting....")); - System.restart(); - return false; - } else if(cmd.equalsIgnoreCase(F("exit"))) { - // End console test - Serial.println(_F("Resuming normal program execution.")); - return false; - } else if(cmd.equalsIgnoreCase(F("help"))) { - Serial.print(_F("LiveDebug interactive debugger sample. Available commands:\n" - " readfile1 : Read and display a test file\n" - " readfile2 : Read a larger file asynchronously\n" - " stat : Issue a 'stat' call\n" - " ls : List directory\n" - " break : Break into debugger\n" - " crash : Write to address 0x00000000, see what happens\n" - " restart : Restart the system\n" - " exit : Rresume normal application execution\n")); - } else { - Serial.println(_F("Unknown command, try 'help'")); + if(logFile.isValid()) { + logFile.print(_F("handleCommand('")); + logFile.print(cmd); + logFile.println(_F("')")); } - // Another command, please +#define XX(tag, desc) \ + if(cmd.equalsIgnoreCase(F(#tag))) { \ + return handleCommand_##tag(); \ + } + COMMAND_MAP(XX) +#undef XX + + Serial.printf(_F("Unknown command '%s', try 'help'\r\n"), cmd.c_str()); return true; } @@ -346,14 +423,16 @@ void onConsoleReadCompleted(const GdbSyscallInfo& info) debug_i("gdb_read_console() returned %d", result); if(result > 0) { + // Remove trailing newline character unsigned len = result; if(bufptr[len - 1] == '\n') { --len; } + if(len > 0) { String cmd(bufptr, len); if(!handleCommand(cmd)) { - return; + return; // Don't call readConsole } } } @@ -404,6 +483,13 @@ void GDB_IRAM_ATTR init() Serial.onDataReceived(onDataReceived); Serial.systemDebugOutput(true); + Serial.println(_F("LiveDebug sample\r\n" + "Explore some capabilities of the GDB debugger.\r\n")); + + if(gdb_present() != eGDB_Attached) { + System.onReady(showPrompt); + } + pinMode(LED_PIN, OUTPUT); #if TIMER_TYPE == TIMERTYPE_SIMPLE procTimer.setCallback(SimpleTimerCallback(blink)); From 531b9496afdcc09c1d99a263e80c8142b4c5c288 Mon Sep 17 00:00:00 2001 From: mikee47 Date: Sun, 7 Apr 2019 08:10:37 +0100 Subject: [PATCH 32/50] Un-patched GDB uses 'C' rather than 'c', so respond to both --- Sming/gdb/gdbstub.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/Sming/gdb/gdbstub.cpp b/Sming/gdb/gdbstub.cpp index 2daf59f2ea..14051c41f7 100644 --- a/Sming/gdb/gdbstub.cpp +++ b/Sming/gdb/gdbstub.cpp @@ -509,6 +509,7 @@ static GdbResult ATTR_GDBEXTERNFN handleCommand(unsigned cmdLen) * Continue execution */ case 'c': + case 'C': return ST_CONT; /* From 14a620c6c8f9eafd12a8f3756568af0bb0cf2eed Mon Sep 17 00:00:00 2001 From: mikee47 Date: Sun, 7 Apr 2019 08:22:47 +0100 Subject: [PATCH 33/50] Remove 'ack counting' Intended to prevent spurious +/- characters getting fed into UART2, but simpler to just block routing to UART2 whilst GDB is attached. (Note that exiting GDB then starting terminal there is usually a '+' (ack) remaining.) --- Sming/gdb/gdbstub.h | 1 - Sming/gdb/gdbsyscall.cpp | 2 -- Sming/gdb/gdbuart.cpp | 10 +++------- 3 files changed, 3 insertions(+), 10 deletions(-) diff --git a/Sming/gdb/gdbstub.h b/Sming/gdb/gdbstub.h index 7aa34be291..dffe9da2ac 100644 --- a/Sming/gdb/gdbstub.h +++ b/Sming/gdb/gdbstub.h @@ -50,7 +50,6 @@ struct gdb_state_t { bool enabled; ///< Debugging may be disabled via gdb_enable() SyscallState syscall; ///< State of system call uint8_t flags; ///< Combination of GdbDebugFlag - unsigned ack_count; ///< For discarding of acknowledgement characters }; extern volatile gdb_state_t gdb_state; diff --git a/Sming/gdb/gdbsyscall.cpp b/Sming/gdb/gdbsyscall.cpp index ecbd951829..f2bda8cad0 100644 --- a/Sming/gdb/gdbsyscall.cpp +++ b/Sming/gdb/gdbsyscall.cpp @@ -190,8 +190,6 @@ void gdbstub_syscall_execute() break; } - // Discard incoming '+' acknolwedgement from packet - ++gdb_state.ack_count; gdb_state.syscall = syscall_active; debug_i("syscall active"); diff --git a/Sming/gdb/gdbuart.cpp b/Sming/gdb/gdbuart.cpp index 3b5c8e8ab4..0214c9dabc 100644 --- a/Sming/gdb/gdbuart.cpp +++ b/Sming/gdb/gdbuart.cpp @@ -171,9 +171,6 @@ size_t ATTR_GDBEXTERNFN gdbSendUserData() GdbPacket packet; packet.writeChar('O'); packet.writeHexBlock(data, charCount); - uart_disable_interrupts(); - ++gdb_state.ack_count; - uart_restore_interrupts(); } else { charCount = gdb_uart_write(data, std::min(space, avail)); } @@ -330,10 +327,9 @@ static void IRAM_ATTR gdb_uart_callback(uart_t* uart, uint32_t status) #endif #if GDBSTUB_ENABLE_UART2 - if(rxbuf != nullptr) { - if(gdb_state.ack_count != 0 && (c == '-' || c == '+')) { - --gdb_state.ack_count; // Discard acknowledgement '+' - } else if(rxbuf->writeChar(c) == 0) { + // When attached, nothing gets routed to UART2 + if(!gdb_state.attached && rxbuf != nullptr) { + if(rxbuf->writeChar(c) == 0) { bitSet(user_uart_status, UIOF); // Overflow } } From 722bc988f8a55eb277c6f028b8e44d3d9c42d3d2 Mon Sep 17 00:00:00 2001 From: mikee47 Date: Sun, 7 Apr 2019 08:23:49 +0100 Subject: [PATCH 34/50] Add a welcome message to `gdbcmds` --- Sming/gdb/gdbcmds | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Sming/gdb/gdbcmds b/Sming/gdb/gdbcmds index 5d63cf6acb..a91d28d22f 100644 --- a/Sming/gdb/gdbcmds +++ b/Sming/gdb/gdbcmds @@ -22,10 +22,13 @@ mem 0x40100000 0x4013ffff rw cache mem 0x40140000 0x5fffffff ro cache mem 0x60000000 0x60001fff rw -# +# The target object file so GDB knows where to get symbol information file out/build/app_0.out # Change the following to your serial port and baud #set serial baud 115200 #target remote /dev/ttyUSB0 #target remote /COM4 + +# Display a welcome prompt +echo \nWelcome to SMING!\nType 'c' (continue) to run application\n\n From 373ca1809889d7eda57bee60af4c2d48ca76776d Mon Sep 17 00:00:00 2001 From: mikee47 Date: Sun, 7 Apr 2019 08:34:42 +0100 Subject: [PATCH 35/50] Document GDB syscall functions, some fixes * Add `mode` parameter to `open` call - cannot assume it's a fixed value * Correct `fstat` buf parameter, should be `gdb_stat_t` not `stat` * If syscall interrupt (via Ctrl+C) then set flag to ensure stop reason reported correctly --- Sming/gdb/gdbsyscall.cpp | 5 +- Sming/system/include/gdb_syscall.h | 185 ++++++++++++++++++++++++-- samples/LiveDebug/app/application.cpp | 4 +- 3 files changed, 179 insertions(+), 15 deletions(-) diff --git a/Sming/gdb/gdbsyscall.cpp b/Sming/gdb/gdbsyscall.cpp index f2bda8cad0..294b2c4aad 100644 --- a/Sming/gdb/gdbsyscall.cpp +++ b/Sming/gdb/gdbsyscall.cpp @@ -111,7 +111,7 @@ void gdbstub_syscall_execute() packet.writeChar(','); packet.writeHexWord32(info.open.flags); packet.writeChar(','); - packet.writeHexByte(0); // mode + packet.writeHexByte(info.open.mode); break; case eGDBSYS_close: @@ -247,6 +247,9 @@ bool ATTR_GDBEXTERNFN gdb_syscall_complete(const char* data) if(*data == ',') { ++data; ctrl_c_flag = *data; + if(ctrl_c_flag) { + bitSet(gdb_state.flags, DBGFLAG_CTRL_BREAK); + } } if(syscall_info.callback != nullptr) { diff --git a/Sming/system/include/gdb_syscall.h b/Sming/system/include/gdb_syscall.h index 3acfc6e486..572d047990 100644 --- a/Sming/system/include/gdb_syscall.h +++ b/Sming/system/include/gdb_syscall.h @@ -8,10 +8,15 @@ * * @author: 2019 - Mikee47 * - * An API is defined for communicating with GDB using the file-I/O protocol, but can also perform - * functions not related to file (or console) I/O. + * An API is defined for communicating with GDB using the 'file-I/O protocol', but the name is misleading + * as it can also perform functions not related to file (or console) I/O. The functions are generally + * POSIX compatible so for further details consult the appropriate reference for your operating system. * * This is a synchronous protocol, so only one request may be in progress at a time. + * While a request is in progress GDB will not respond to any notifications from the target - including debug output. + * + * All functions are implemented with an optional callback routine, which is essential when reading the + * console as it can take a considerable time to execute. * * To use in your application, build with GDBSTUB_ENABLE_SYSCALL=1 and #include this header. * @@ -19,12 +24,18 @@ * ****/ +/** @defgroup GDB + * @brief GDB system call API + * @{ +*/ + #ifndef _SYSTEM_INCLUDE_GDB_SYSCALL_H_ #define _SYSTEM_INCLUDE_GDB_SYSCALL_H_ #include -#include -#include +#include +#include +#include #include /* GDB uses a specific version of the stat structure, 64 bytes in size */ @@ -52,6 +63,9 @@ struct __attribute__((packed)) gdb_timeval_t { /* GDB Syscall interface */ +/** + * @brief Enumeration defining available commands + */ enum GdbSyscallCommand { eGDBSYS_open, eGDBSYS_close, @@ -67,11 +81,17 @@ enum GdbSyscallCommand { eGDBSYS_system, }; -/** @brief GDB Syscall completion callback function */ struct GdbSyscallInfo; + +/** + * @brief GDB Syscall completion callback function + * @param info Reference to request information, containing result code and user-provided 'param' + */ typedef void (*gdb_syscall_callback_t)(const GdbSyscallInfo& info); -/** @brief GDB Syscall command information */ +/** + * @brief GDB Syscall request information + */ struct GdbSyscallInfo { GdbSyscallCommand command; ///< The syscall command gdb_syscall_callback_t callback; ///< User-supplied callback (if any) @@ -82,6 +102,7 @@ struct GdbSyscallInfo { struct { const char* filename; int flags; + int mode; } open; struct { int fd; @@ -114,7 +135,7 @@ struct GdbSyscallInfo { } stat; struct { int fd; - struct stat* buf; + struct gdb_stat_t* buf; } fstat; struct { gdb_timeval_t* tv; @@ -129,17 +150,51 @@ struct GdbSyscallInfo { }; }; +/** + * @brief Stub function to perform a syscall. Implemented by GDB stub. + * @param info Call request information + * @retval Error code, < 0 for error or >= 0 for success + * + * @note If info.callback is null then the function waits until the request has been completed, + * and returns the result of the syscall. + * If info.callback is not null, then this function returns 0 to indicate the request is in progress, + * or a negative error value to indicate the request could not be started. + * + * @note A copy of the request information is made and a reference passed to the completion callback + * function if one is used. Note that this is _not_ the original structure, so it does not have to be + * persisent, although any char* or other pointers MUST remain valid until the call is completed. + */ int gdb_syscall(const GdbSyscallInfo& info); -static inline int gdb_syscall_open(const char* filename, int flags, gdb_syscall_callback_t callback = nullptr, +/** + * @brief Open a file on the host + * @param filename Name of file to open, relative to current directory (typically the Sming application folder) + * @param flags A combination of O_* flags defined in fcntl.h (e.g. O_RDONLY, O_CREAT, etc) + * @param mode See sys/stat.h + * @param callback + * @param param + * @retval int + * @see https://sourceware.org/gdb/current/onlinedocs/gdb/open.html + */ +static inline int gdb_syscall_open(const char* filename, int flags, int mode, gdb_syscall_callback_t callback = nullptr, void* param = nullptr) { GdbSyscallInfo info = {eGDBSYS_open, callback, param}; info.open.filename = filename; info.open.flags = flags; + info.open.mode = mode; return gdb_syscall(info); } +/** + * @brief Close a host file + * @param fd File handle + * @param flags A combination of O_* flags defined in fcntl.h (e.g. O_RDONLY, O_CREAT, etc) + * @param callback + * @param param + * @retval int + * @see https://sourceware.org/gdb/current/onlinedocs/gdb/close.html + */ static inline int gdb_syscall_close(int fd, gdb_syscall_callback_t callback = nullptr, void* param = nullptr) { GdbSyscallInfo info = {eGDBSYS_close, callback, param}; @@ -147,6 +202,16 @@ static inline int gdb_syscall_close(int fd, gdb_syscall_callback_t callback = nu return gdb_syscall(info); } +/** + * @brief Read data from a host file + * @param fd File handle + * @param buffer + * @param bufSize + * @param callback + * @param param + * @retval int + * @see https://sourceware.org/gdb/current/onlinedocs/gdb/read.html + */ static inline int gdb_syscall_read(int fd, void* buffer, size_t bufSize, gdb_syscall_callback_t callback = nullptr, void* param = nullptr) { @@ -157,6 +222,16 @@ static inline int gdb_syscall_read(int fd, void* buffer, size_t bufSize, gdb_sys return gdb_syscall(info); } +/** + * @brief Write data from a host file + * @param fd File handle + * @param buffer + * @param count + * @param callback + * @param param + * @retval int + * @see https://sourceware.org/gdb/current/onlinedocs/gdb/write.html + */ static inline int gdb_syscall_write(int fd, const void* buffer, size_t count, gdb_syscall_callback_t callback = nullptr, void* param = nullptr) { @@ -167,6 +242,16 @@ static inline int gdb_syscall_write(int fd, const void* buffer, size_t count, gd return gdb_syscall(info); } +/** + * @brief Get/set current file pointer position in a host file + * @param fd File handle + * @param offset Relative to whence + * @param whence SEEK_SET, SEEK_CUR or SEEK_END + * @param callback + * @param param + * @retval int + * @see https://sourceware.org/gdb/current/onlinedocs/gdb/lseek.html + */ static inline int gdb_syscall_lseek(int fd, long offset, int whence, gdb_syscall_callback_t callback = nullptr, void* param = nullptr) { @@ -177,6 +262,15 @@ static inline int gdb_syscall_lseek(int fd, long offset, int whence, gdb_syscall return gdb_syscall(info); } +/** + * @brief Rename a host file + * @param oldpath + * @param newpath + * @param callback + * @param param + * @retval int + * @see https://sourceware.org/gdb/current/onlinedocs/gdb/rename.html + */ static inline int gdb_syscall_rename(const char* oldpath, const char* newpath, gdb_syscall_callback_t callback = nullptr, void* param = nullptr) { @@ -186,6 +280,14 @@ static inline int gdb_syscall_rename(const char* oldpath, const char* newpath, return gdb_syscall(info); } +/** + * @brief Unlink/remove/delete a host file + * @param pathname + * @param callback + * @param param + * @retval int + * @see https://sourceware.org/gdb/current/onlinedocs/gdb/unlink.html + */ static inline int gdb_syscall_unlink(const char* pathname, gdb_syscall_callback_t callback = nullptr, void* param = nullptr) { @@ -194,6 +296,15 @@ static inline int gdb_syscall_unlink(const char* pathname, gdb_syscall_callback_ return gdb_syscall(info); } +/** + * @brief Obtain information about a host file given its name/path + * @param pathname + * @param buf + * @param callback + * @param param + * @retval int + * @see https://sourceware.org/gdb/current/onlinedocs/gdb/stat_002ffstat.html + */ static inline int gdb_syscall_stat(const char* pathname, gdb_stat_t* buf, gdb_syscall_callback_t callback = nullptr, void* param = nullptr) { @@ -203,7 +314,16 @@ static inline int gdb_syscall_stat(const char* pathname, gdb_stat_t* buf, gdb_sy return gdb_syscall(info); } -static inline int gdb_syscall_fstat(int fd, struct stat* buf, gdb_syscall_callback_t callback = nullptr, +/** + * @brief Obtain information about a host file given its file handle + * @param fd + * @param buf + * @param callback + * @param param + * @retval int + * @see https://sourceware.org/gdb/current/onlinedocs/gdb/stat_002ffstat.html + */ +static inline int gdb_syscall_fstat(int fd, struct gdb_stat_t* buf, gdb_syscall_callback_t callback = nullptr, void* param = nullptr) { GdbSyscallInfo info = {eGDBSYS_fstat, callback, param}; @@ -212,6 +332,15 @@ static inline int gdb_syscall_fstat(int fd, struct stat* buf, gdb_syscall_callba return gdb_syscall(info); } +/** + * @brief Get current time of day from host, in UTC + * @param tv + * @param tz Not used, must be null + * @param callback + * @param param + * @retval int + * @see https://sourceware.org/gdb/current/onlinedocs/gdb/gettimeofday.html + */ static inline int gdb_syscall_gettimeofday(gdb_timeval_t* tv, void* tz, gdb_syscall_callback_t callback = nullptr, void* param = nullptr) { @@ -221,6 +350,14 @@ static inline int gdb_syscall_gettimeofday(gdb_timeval_t* tv, void* tz, gdb_sysc return gdb_syscall(info); } +/** + * @brief Determine if the given file handle refers to a console/tty + * @param fd + * @param callback + * @param param + * @retval int + * @see https://sourceware.org/gdb/current/onlinedocs/gdb/isatty.html + */ static inline int gdb_syscall_isatty(int fd, gdb_syscall_callback_t callback = nullptr, void* param = nullptr) { GdbSyscallInfo info = {eGDBSYS_isatty, callback, param}; @@ -228,6 +365,15 @@ static inline int gdb_syscall_isatty(int fd, gdb_syscall_callback_t callback = n return gdb_syscall(info); } +/** + * @brief Invoke the 'system' command on the host + * @param command + * @param callback + * @param param + * @retval int + * @note For security reasons this command must specifically be enabled using 'set remote system-call-allowed 1' + * @see https://sourceware.org/gdb/current/onlinedocs/gdb/system.html + */ static inline int gdb_syscall_system(const char* command, gdb_syscall_callback_t callback = nullptr, void* param = nullptr) { @@ -236,9 +382,15 @@ static inline int gdb_syscall_system(const char* command, gdb_syscall_callback_t return gdb_syscall(info); } -/* - * IMPORTANT: Reading console will not complete until user types return (or Ctrl+D, Ctrl+C). It must therefore - * be an asynchronous call or a watchdog reset will occur. +/** + * @brief Read a line of text from the GDB console + * @param buffer + * @param bufSize + * @param callback + * @param param + * @retval int + * @note IMPORTANT: Reading console will not complete until user types return (or Ctrl+D, Ctrl+C). + * It must therefore be an asynchronous call or a watchdog reset will occur. */ static inline int gdb_console_read(void* buffer, size_t bufSize, gdb_syscall_callback_t callback = nullptr, void* param = nullptr) @@ -246,10 +398,19 @@ static inline int gdb_console_read(void* buffer, size_t bufSize, gdb_syscall_cal return gdb_syscall_read(STDIN_FILENO, buffer, bufSize, callback, param); } +/** + * @brief Write text to the GDB console + * @param buffer + * @param count + * @param callback + * @param param + * @retval int + */ static inline int gdb_console_write(const void* buffer, size_t count, gdb_syscall_callback_t callback = nullptr, void* param = nullptr) { return gdb_syscall_write(STDOUT_FILENO, buffer, count, callback, param); } +/** @} */ #endif /* _SYSTEM_INCLUDE_GDB_SYSCALL_H_ */ diff --git a/samples/LiveDebug/app/application.cpp b/samples/LiveDebug/app/application.cpp index 753f451e30..c157f106ad 100644 --- a/samples/LiveDebug/app/application.cpp +++ b/samples/LiveDebug/app/application.cpp @@ -132,7 +132,7 @@ void onDataReceived(Stream& source, char arrivedChar, unsigned short availableCh void readFile(const char* filename, bool display) { auto start = millis(); - int fd = gdb_syscall_open(filename, O_RDONLY); + int fd = gdb_syscall_open(filename, O_RDONLY, 0); Serial.printf(_F("gdb_syscall_open(\"%s\") = %d\r\n"), filename, fd); if(fd > 0) { char buf[256]; @@ -219,7 +219,7 @@ void asyncReadCallback(const GdbSyscallInfo& info) */ void readFileAsync(const char* filename) { - gdb_syscall_open(filename, O_RDONLY, asyncReadCallback); + gdb_syscall_open(filename, O_RDONLY, 0, asyncReadCallback); } void fileStat(const char* filename) From 2846138c91d262009861d95941e2d58e5d332b03 Mon Sep 17 00:00:00 2001 From: mikee47 Date: Sun, 7 Apr 2019 08:41:45 +0100 Subject: [PATCH 36/50] Add GdbFileStream for simpler access to host files via syscall, update LiveDebug sample --- Sming/SmingCore/Data/Stream/GdbFileStream.cpp | 134 ++++++++++++++++ Sming/SmingCore/Data/Stream/GdbFileStream.h | 149 ++++++++++++++++++ samples/LiveDebug/app/application.cpp | 26 +++ 3 files changed, 309 insertions(+) create mode 100644 Sming/SmingCore/Data/Stream/GdbFileStream.cpp create mode 100644 Sming/SmingCore/Data/Stream/GdbFileStream.h diff --git a/Sming/SmingCore/Data/Stream/GdbFileStream.cpp b/Sming/SmingCore/Data/Stream/GdbFileStream.cpp new file mode 100644 index 0000000000..f4424c909d --- /dev/null +++ b/Sming/SmingCore/Data/Stream/GdbFileStream.cpp @@ -0,0 +1,134 @@ +/**** + * Sming Framework Project - Open Source framework for high efficiency native ESP8266 development. + * Created 2015 by Skurydin Alexey + * http://github.com/anakod/Sming + * All files of the Sming Core are provided under the LGPL v3 license. + * + * GdbFileStream.cpp + * + ****/ + +#include "GdbFileStream.h" +#include "gdb_syscall.h" + +/* GdbFileStream */ + +bool GdbFileStream::open(const String& fileName, FileOpenFlags openFlags) +{ + lastError = 0; + + int flags = 0; + if((openFlags & eFO_ReadWrite) == eFO_ReadWrite) { + flags = O_RDWR; + } else if(openFlags & eFO_WriteOnly) { + flags = O_WRONLY; + } else { + flags = O_RDONLY; + } + if(openFlags & eFO_CreateIfNotExist) { + flags |= O_CREAT; + } + if(openFlags & eFO_Truncate) { + flags |= O_TRUNC; + } + + int fd = gdb_syscall_open(fileName.c_str(), flags, S_IRWXU); + if(!check(fd)) { + debug_w("File wasn't found: %s", fileName.c_str()); + return false; + } + + // Get size + int size = gdb_syscall_lseek(fd, 0, SEEK_END); + if(check(size)) { + close(); + if(fd >= 0) { + handle = fd; + this->size = size; + gdb_syscall_lseek(fd, 0, SEEK_SET); + pos = 0; + debug_d("opened file: '%s' (%u bytes) #0x%08X", fileName().c_str(), size, this); + } + return true; + } + + gdb_syscall_close(fd); + return false; +} + +void GdbFileStream::close() +{ + if(handle >= 0) { + gdb_syscall_close(handle); + handle = -1; + } + size = 0; + pos = 0; + lastError = 0; +} + +uint16_t GdbFileStream::readMemoryBlock(char* data, int bufSize) +{ + if(data == nullptr || bufSize <= 0 || pos >= size) { + return 0; + } + + int available = gdb_syscall_read(handle, data, std::min(size - pos, size_t(bufSize))); + check(available); + + // Don't move cursor now (waiting seek) + gdb_syscall_lseek(handle, pos, SEEK_SET); + + return available > 0 ? available : 0; +} + +size_t GdbFileStream::write(const uint8_t* buffer, size_t size) +{ + if(!fileExist()) { + return 0; + } + + int writePos = gdb_syscall_lseek(handle, 0, SEEK_END); + if(!check(writePos)) { + return 0; + } + + pos = size_t(writePos); + + int written = gdb_syscall_write(handle, buffer, size); + if(check(written)) { + pos += size_t(written); + if(pos > this->size) { + this->size = pos; + } + } + + return written; +} + +bool GdbFileStream::seek(int len) +{ + int newpos = gdb_syscall_lseek(handle, len, SEEK_CUR); + if(!check(newpos)) { + return false; + } + + pos = size_t(newpos); + if(pos > size) { + size = pos; + } + + return true; +} + +String GdbFileStream::id() const +{ + gdb_stat_t stat; + gdb_syscall_fstat(handle, &stat); + +#define ETAG_SIZE 16 + char buf[ETAG_SIZE]; + m_snprintf(buf, ETAG_SIZE, _F("00f-%x-%x0-%x"), stat.st_ino, stat.st_size, fileName.length()); + + return String(buf); +} diff --git a/Sming/SmingCore/Data/Stream/GdbFileStream.h b/Sming/SmingCore/Data/Stream/GdbFileStream.h new file mode 100644 index 0000000000..ae8b20021c --- /dev/null +++ b/Sming/SmingCore/Data/Stream/GdbFileStream.h @@ -0,0 +1,149 @@ +/**** + * Sming Framework Project - Open Source framework for high efficiency native ESP8266 development. + * Created 2015 by Skurydin Alexey + * http://github.com/anakod/Sming + * All files of the Sming Core are provided under the LGPL v3 license. + * + * GdbFileStream.h + * + ****/ + +#ifndef _SMING_CORE_DATA_GDB_FILE_STREAM_H_ +#define _SMING_CORE_DATA_GDB_FILE_STREAM_H_ + +#include "ReadWriteStream.h" +#include "FileSystem.h" + +/** + * @brief GDB File stream class to access to host files whilst running under debugger + * @ingroup stream data + * + * @{ + */ + +class GdbFileStream : public ReadWriteStream +{ +public: + GdbFileStream() + { + } + + /** @brief Create a file stream + * @param fileName Name of file to open + */ + GdbFileStream(const String& fileName, FileOpenFlags openFlags = eFO_ReadOnly) + { + open(fileName, openFlags); + } + + ~GdbFileStream() + { + close(); + } + + /** @brief Open a file and attach this stream object to it + * @param fileName + * @param openFlags + * @retval bool true on success, false on error + * @note call getLastError() to determine cause of failure + */ + bool open(const String& fileName, FileOpenFlags openFlags = eFO_ReadOnly); + + /** @brief Close file + */ + void close(); + + size_t write(const uint8_t* buffer, size_t size) override; + + //Use base class documentation + uint16_t readMemoryBlock(char* data, int bufSize) override; + + //Use base class documentation + bool seek(int len) override; + + //Use base class documentation + bool isFinished() override + { + return pos == size; + } + + /** @brief Filename of file stream is attached to + * @retval String invalid if stream isn't open + */ + String getFileName() const + { + return fileName; + } + + /** @brief Determine if file exists + * @retval bool true if stream contains valid file + */ + bool fileExist() const + { + return handle >= 0; + } + + String getName() const override + { + return fileName; + } + + bool isValid() const override + { + return fileExist(); + } + + /** @brief Get the offset of cursor from beginning of data + * @retval size_t Cursor offset + */ + size_t getPos() const + { + return pos; + } + + /** @brief Return the total length of the stream + * @retval int -1 is returned when the size cannot be determined + */ + int available() override + { + return size - pos; + } + + String id() const override; + + /** @brief determine if an error occurred during operation + * @retval int filesystem error code + */ + int getLastError() + { + return lastError; + } + +private: + /** @brief Check file operation result and note error code + * @param res result of fileXXX() operation to check + * @retval bool true if operation was successful, false if error occurred + */ + bool check(int res) + { + if(res >= 0) { + return true; + } + + if(lastError >= 0) { + lastError = res; + } + return false; + } + +private: + String fileName; + int handle = -1; + size_t pos = 0; + size_t size = 0; + int lastError = 0; +}; + +/** @} */ + +#endif /* _SMING_CORE_DATA_GDB_FILE_STREAM_H_ */ diff --git a/samples/LiveDebug/app/application.cpp b/samples/LiveDebug/app/application.cpp index c157f106ad..17dd1d1312 100644 --- a/samples/LiveDebug/app/application.cpp +++ b/samples/LiveDebug/app/application.cpp @@ -2,6 +2,7 @@ #include #include "HardwareTimer.h" #include +#include #define LED_PIN 2 // Note: LED is attached to UART1 TX output @@ -39,6 +40,7 @@ Timer procTimer; #endif bool state = true; +static GdbFileStream logFile; /* * Notice: Software breakpoints work only on code that is in RAM. @@ -265,6 +267,7 @@ void fileStat(const char* filename) XX(stat, "Use `syscall_stat` function to get details for a host file") \ XX(ls, "Use `syscall_system` function to perform a directory listing on the host") \ XX(time, "Use `syscall_gettimeofday` to get current time from host") \ + XX(log, "Show state of log file") \ XX(break, "Demonstrated `gdb_do_break()` function to pause this application and obtain a GDB command prompt") \ XX(read0, "Read from invalid address") \ XX(write0, "Write to invalid address") \ @@ -313,6 +316,15 @@ COMMAND_HANDLER(time) return true; } +COMMAND_HANDLER(log) +{ + if(logFile.isValid()) { + Serial.printf(_F("Log file is open, size = %u bytes\r\n"), logFile.getPos()); + } else { + Serial.println(_F("Log file not available")); + } +} + COMMAND_HANDLER(ls) { int res = gdb_syscall_system(PSTR("ls -la")); @@ -473,7 +485,21 @@ extern "C" void gdb_on_attach(bool attached) { debug_i("GdbAttach(%d)", attached); if(attached) { + // Open a log file on the host to demonstrate use of GdbFileStream + logFile.open(F("testlog.txt"), eFO_WriteOnly | eFO_CreateIfNotExist); + debug_i("open log %d", logFile.getLastError()); + logFile.println(); + + logFile.println(_F("\r\n=== OPENED ===")); + gdb_timeval_t tv; + gdb_syscall_gettimeofday(&tv, nullptr); + logFile.println(DateTime(tv.tv_sec).toFullDateTimeString()); + + // Start interacting with GDB readConsole(); + } else { + // Note: GDB is already detached so underlying call to gdb_syscall_close() will fail silently + logFile.close(); } } From 7efe77079f50887cbf9e050c5391786070ad4e57 Mon Sep 17 00:00:00 2001 From: mikee47 Date: Sun, 7 Apr 2019 08:59:47 +0100 Subject: [PATCH 37/50] Disable Ctrl+C trapping when running GDB via Makefile --- Sming/Makefile-rboot.mk | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sming/Makefile-rboot.mk b/Sming/Makefile-rboot.mk index 11bf270a42..fd9abfd91e 100644 --- a/Sming/Makefile-rboot.mk +++ b/Sming/Makefile-rboot.mk @@ -669,7 +669,7 @@ terminal: kill_term $(TERMINAL) gdb: kill_term - $(GDB) -x $(SMING_HOME)/gdb/gdbcmds -b $(COM_SPEED_SERIAL) -ex "target remote $(COM_PORT)" + $(Q) trap '' SIGINT && $(GDB) -x $(SMING_HOME)/gdb/gdbcmds -b $(COM_SPEED_SERIAL) -ex "target remote $(COM_PORT)" flashinit: $(vecho) "Flash init data default and blank data." From b70ab28197f97461f83e2988c62a34f75cf77ff8 Mon Sep 17 00:00:00 2001 From: mikee47 Date: Sun, 7 Apr 2019 09:04:45 +0100 Subject: [PATCH 38/50] Define `gdbstub_break_internal()` macro --- Sming/gdb/gdbstub.cpp | 1 - Sming/gdb/gdbstub.h | 10 +++++++++- Sming/gdb/gdbuart.cpp | 6 ++---- 3 files changed, 11 insertions(+), 6 deletions(-) diff --git a/Sming/gdb/gdbstub.cpp b/Sming/gdb/gdbstub.cpp index 14051c41f7..c8a1d9f82b 100644 --- a/Sming/gdb/gdbstub.cpp +++ b/Sming/gdb/gdbstub.cpp @@ -32,7 +32,6 @@ #include "gdbstub-entry.h" #include "gdb_hooks.h" #include "GdbPacket.h" -#include "BitManipulations.h" #include "exceptions.h" #include "gdb/registers.h" #include "gdbsyscall.h" diff --git a/Sming/gdb/gdbstub.h b/Sming/gdb/gdbstub.h index dffe9da2ac..0c2ec3f5b9 100644 --- a/Sming/gdb/gdbstub.h +++ b/Sming/gdb/gdbstub.h @@ -18,6 +18,7 @@ #include "gdbstub-internal.h" #include +#include "BitManipulations.h" // GDB_xx macro versions required to ensure no flash access if requested #if GDBSTUB_FORCE_IRAM @@ -28,9 +29,16 @@ #define GDB_PROGMEM PROGMEM #endif +// Break into debugger #define gdbstub_do_break() asm("break 0,0") -// Additional debugging flags +#define gdbstub_break_internal(flag) \ + { \ + bitSet(gdb_state.flags, flag); \ + asm("break 0,0"); \ + } + +// Additional debugging flags mainly used to qualify reason for a debugging break enum GdbDebugFlag { DBGFLAG_DEBUG_EXCEPTION, ///< For debug exceptions, cause is DBGCAUSE (see DebugCause bits) DBGFLAG_SYSTEM_EXCEPTION, ///< For system exceptions, cause is EXCCAUSE (see EXCCAUSE_* values) diff --git a/Sming/gdb/gdbuart.cpp b/Sming/gdb/gdbuart.cpp index 0214c9dabc..18e5330dcf 100644 --- a/Sming/gdb/gdbuart.cpp +++ b/Sming/gdb/gdbuart.cpp @@ -311,8 +311,7 @@ static void IRAM_ATTR gdb_uart_callback(uart_t* uart, uint32_t status) } #endif if(breakCheck && c == '\x03') { - bitSet(gdb_state.flags, DBGFLAG_CTRL_BREAK); - gdbstub_do_break(); + gdbstub_break_internal(DBGFLAG_CTRL_BREAK); continue; } #endif @@ -320,8 +319,7 @@ static void IRAM_ATTR gdb_uart_callback(uart_t* uart, uint32_t status) #if GDBSTUB_ENABLE_SYSCALL // If packet start detected whilst handling a system call, break into debugger to process it if(c == '$' && gdb_state.syscall == syscall_active) { - bitSet(gdb_state.flags, DBGFLAG_PACKET_STARTED); - gdbstub_do_break(); + gdbstub_break_internal(DBGFLAG_PACKET_STARTED); continue; } #endif From 16569d64b5aeb03af955b1c61cba0fe311e7d3a6 Mon Sep 17 00:00:00 2001 From: mikee47 Date: Sun, 7 Apr 2019 09:10:26 +0100 Subject: [PATCH 39/50] Return previous hook function from `m_setPuts()` so it can be temporarily substituted. --- Sming/system/include/m_printf.h | 3 ++- Sming/system/m_printf.cpp | 6 ++++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/Sming/system/include/m_printf.h b/Sming/system/include/m_printf.h index f4bb3b3f89..35249c5d15 100644 --- a/Sming/system/include/m_printf.h +++ b/Sming/system/include/m_printf.h @@ -29,8 +29,9 @@ typedef std::function nputs_callback_t; /** @brief set the character output routine * @param callback + * @retval nputs_callback_t The existing callback */ -void m_setPuts(nputs_callback_t callback); +nputs_callback_t m_setPuts(nputs_callback_t callback); } // extern "C++" diff --git a/Sming/system/m_printf.cpp b/Sming/system/m_printf.cpp index bd169c15af..0a0853b1b7 100644 --- a/Sming/system/m_printf.cpp +++ b/Sming/system/m_printf.cpp @@ -34,9 +34,11 @@ static int skip_atoi(const char **s) return i; } -void m_setPuts(nputs_callback_t callback) +nputs_callback_t m_setPuts(nputs_callback_t callback) { + nputs_callback_t previousCallback = _puts_callback; _puts_callback = callback; + return previousCallback; } size_t m_putc(char c) @@ -267,7 +269,7 @@ void m_printHex(const char* tag, const void* data, size_t len, int addr, size_t m_putc(is_print(c) ? c : '.'); } - m_putc('\n'); + m_puts("\r\n"); offset += n; From ce5b7f07f91b5057b39bddef9322766ad58295ae Mon Sep 17 00:00:00 2001 From: mikee47 Date: Sun, 7 Apr 2019 09:11:40 +0100 Subject: [PATCH 40/50] Add `gdbWriteConsole()` function so internal GDB code can write directly to console --- Sming/gdb/gdbuart.cpp | 12 +++++++++--- Sming/gdb/gdbuart.h | 9 +++++++++ 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/Sming/gdb/gdbuart.cpp b/Sming/gdb/gdbuart.cpp index 18e5330dcf..65120c7219 100644 --- a/Sming/gdb/gdbuart.cpp +++ b/Sming/gdb/gdbuart.cpp @@ -147,6 +147,14 @@ size_t ATTR_GDBEXTERNFN gdbSendChar(char c) return gdb_uart_write_char(c); } +size_t ATTR_GDBEXTERNFN gdbWriteConsole(const char* data, size_t length) +{ + GdbPacket packet; + packet.writeChar('O'); + packet.writeHexBlock(data, length); + return length; +} + size_t ATTR_GDBEXTERNFN gdbSendUserData() { #if GDBSTUB_ENABLE_UART2 @@ -168,9 +176,7 @@ size_t ATTR_GDBEXTERNFN gdbSendUserData() } charCount = std::min((space - 3) / 2, avail); - GdbPacket packet; - packet.writeChar('O'); - packet.writeHexBlock(data, charCount); + gdbWriteConsole(static_cast(data), charCount); } else { charCount = gdb_uart_write(data, std::min(space, avail)); } diff --git a/Sming/gdb/gdbuart.h b/Sming/gdb/gdbuart.h index 5c84ac9777..fd5fbd9a2a 100644 --- a/Sming/gdb/gdbuart.h +++ b/Sming/gdb/gdbuart.h @@ -30,6 +30,15 @@ size_t gdbSendData(const void* data, size_t length); */ size_t gdbSendChar(char c); +/** + * @brief Write a block of data to the GDB console + * @param data + * @param length + * @retval size_t Always returns length + * @note Data is encoded as a single 'O' packet. Should only be used when GDB is attached. + */ +size_t ATTR_GDBEXTERNFN gdbWriteConsole(const char* data, size_t length); + /** * @brief Send some user data from the user_uart TX buffer to the GDB serial port, * packetising it if necessary. From 8de62f0dcbadfb1e082e5feca3ee67427ae9210f Mon Sep 17 00:00:00 2001 From: mikee47 Date: Sun, 7 Apr 2019 09:25:35 +0100 Subject: [PATCH 41/50] Sort out stack dumping and system restart issues * Remove `ets_wdt_disable()` and `ets_wdt_enable()` calls from `dumpExceptionInfo()`, using normal watchdog reset instead. The `ets_wdt_enable()` call prevents the debugging breakpoint from working. Tested at 9600 baud shows reliable operation. * When GDB is attached, temporarily override any previously output routine (for m_printf, etc.) and write directly to the GDB console. This ensures that a proper explanation of the exception (or system restart) is dispayed in the GDB console. * Add `GDBSTUB_BREAK_ON_RESTART` to allow proper handling of unexpected system restarts ('crashes') with GDB. The cause of the restart is shown in the console with an appropriate signal. * Do not show stack dumps when using GDB (the cause is always shown though) * Add 'hang' command to LiveDebug sample to test behaviour (under GDB and using terminal in both GDB/non-GDB builds). Works as expected. --- Sming/appspecific/gdb/gdb_hooks.cpp | 56 ++++++++++++++++++++------- Sming/gdb/gdbstub-cfg.h | 7 ++++ Sming/gdb/gdbstub.cpp | 9 ++++- Sming/gdb/gdbstub.h | 1 + samples/LiveDebug/app/application.cpp | 11 ++++++ 5 files changed, 68 insertions(+), 16 deletions(-) diff --git a/Sming/appspecific/gdb/gdb_hooks.cpp b/Sming/appspecific/gdb/gdb_hooks.cpp index 184a02cf89..c68164eb95 100644 --- a/Sming/appspecific/gdb/gdb_hooks.cpp +++ b/Sming/appspecific/gdb/gdb_hooks.cpp @@ -11,8 +11,10 @@ #include #include #include "gdb/gdbstub.h" +#include "gdb/gdbuart.h" #include "gdb/gdbstub-entry.h" #include "gdb/exceptions.h" +#include extern "C" { @@ -59,6 +61,9 @@ void debug_print_stack(uint32_t start, uint32_t end) m_printf(_F("%08x: %08x %08x %08x %08x %c\r\n"), addr, values[0], values[1], values[2], values[3], (looksLikeStackFrame) ? '<' : ' '); + + system_soft_wdt_feed(); + wdt_feed(); } m_puts(separatorLine); m_puts(instructions); @@ -66,7 +71,13 @@ void debug_print_stack(uint32_t start, uint32_t end) void debug_crash_callback(const rst_info* rst_info, uint32_t stack, uint32_t stack_end) { -#if ENABLE_CRASH_DUMP +#ifdef ENABLE_GDB + if(gdb_state.attached) { + m_setPuts(gdbWriteConsole); + } +#endif + +#if defined(ENABLE_GDB) || ENABLE_CRASH_DUMP switch(rst_info->reason) { case REASON_EXCEPTION_RST: m_printf(_F("\r\n" @@ -86,15 +97,19 @@ void debug_crash_callback(const rst_info* rst_info, uint32_t stack, uint32_t sta ; } +#if defined(ENABLE_GDB) && GDBSTUB_BREAK_ON_RESTART + gdbstub_break_internal(DBGFLAG_RESTART); +#elif ENABLE_CRASH_DUMP debug_print_stack(stack, stack_end); #endif + +#endif // defined(ENABLE_GDB) || ENABLE_CRASH_DUMP } -#if ENABLE_EXCEPTION_DUMP +#ifdef HOOK_SYSTEM_EXCEPTIONS -void dumpExceptionInfo(UserFrame* frame) +void dumpExceptionInfo() { - ets_wdt_disable(); auto& reg = gdbstub_savedRegs; m_printf(_F("\r\n" @@ -124,12 +139,11 @@ void dumpExceptionInfo(UserFrame* frame) } } m_puts("\r\n"); - debug_print_stack(reg.a[1], 0x3fffffb0); - ets_wdt_enable(); + // Stack dump can be quite large, and not helpful to dump it to GDB console + if(gdb_present() != eGDB_Attached) { + debug_print_stack(reg.a[1], 0x3fffffb0); + } } -#endif - -#ifdef HOOK_SYSTEM_EXCEPTIONS // Main exception handler code static void __attribute__((noinline)) gdbstub_exception_handler_flash(UserFrame* frame) @@ -142,25 +156,37 @@ static void __attribute__((noinline)) gdbstub_exception_handler_flash(UserFrame* const uint32_t EXCEPTION_GDB_SP_OFFSET = 0x100; gdbstub_savedRegs.a[1] = uint32_t(frame) + EXCEPTION_GDB_SP_OFFSET; -#if ENABLE_EXCEPTION_DUMP - if(gdb_present() != eGDB_Attached) { - dumpExceptionInfo(frame); +#if defined(ENABLE_GDB) && GDBSTUB_BREAK_ON_EXCEPTION + // If GDB is attached, temporarily redirect m_printf calls to the console + nputs_callback_t oldPuts = nullptr; + if(gdb_state.attached) { + oldPuts = m_setPuts(gdbWriteConsole); + } + + dumpExceptionInfo(); + + if(gdb_state.attached) { + m_setPuts(oldPuts); } -#endif -#if defined(ENABLE_GDB) && GDBSTUB_BREAK_ON_EXCEPTION gdbstub_handle_exception(); // Copy any changed registers back to the frame the Xtensa HAL uses. memcpy(frame, &gdbstub_savedRegs, 5 * 4); memcpy(&frame->a2, &gdbstub_savedRegs.a[2], 14 * 4); - return; +#else + + dumpExceptionInfo(); + +#if defined(ENABLE_GDB) + gdbFlushUserData(); #endif // Wait for watchdog to reset system while(true) ; +#endif } // Non-OS exception handler. Gets called by the Xtensa HAL. diff --git a/Sming/gdb/gdbstub-cfg.h b/Sming/gdb/gdbstub-cfg.h index e3fbd95ca9..f84cbe779f 100644 --- a/Sming/gdb/gdbstub-cfg.h +++ b/Sming/gdb/gdbstub-cfg.h @@ -89,6 +89,13 @@ #define GDBSTUB_BREAK_ON_EXCEPTION 1 #endif +/* + * Enable this to cause the program to pause and wait for gdb to be connected when an unexpected system restart occurs. + */ +#ifndef GDBSTUB_BREAK_ON_RESTART +#define GDBSTUB_BREAK_ON_RESTART 1 +#endif + /* * If this is defined, gdbstub will break the program when you press Ctrl-C in gdb. * It does this by monitoring for the 'x03' character in the serial receive routine. Any preceding diff --git a/Sming/gdb/gdbstub.cpp b/Sming/gdb/gdbstub.cpp index c8a1d9f82b..cef440a089 100644 --- a/Sming/gdb/gdbstub.cpp +++ b/Sming/gdb/gdbstub.cpp @@ -206,7 +206,13 @@ static void ATTR_GDBEXTERNFN sendReason() packet.writeHexByte(signal); } else { // Debugging exception - signal = bitRead(gdb_state.flags, DBGFLAG_CTRL_BREAK) ? GDB_SIGNAL_INT : GDB_SIGNAL_TRAP; + if(bitRead(gdb_state.flags, DBGFLAG_CTRL_BREAK)) { + signal = GDB_SIGNAL_INT; + } else if(bitRead(gdb_state.flags, DBGFLAG_RESTART)) { + signal = GDB_SIGNAL_PWR; + } else { + signal = GDB_SIGNAL_TRAP; + } packet.writeHexByte(signal); // Current Xtensa GDB versions don't seem to request this, so let's leave it off. #if 0 @@ -927,6 +933,7 @@ void ATTR_GDBINIT gdbstub_init() SD(GDBSTUB_GDB_PATCHED); SD(GDBSTUB_USE_OWN_STACK); SD(GDBSTUB_BREAK_ON_EXCEPTION); + SD(GDBSTUB_BREAK_ON_RESTART); SD(GDBSTUB_CTRLC_BREAK); SD(GDBSTUB_BREAK_ON_INIT); SD(GDBSTUB_ENABLE_UART2); diff --git a/Sming/gdb/gdbstub.h b/Sming/gdb/gdbstub.h index 0c2ec3f5b9..39e07d076c 100644 --- a/Sming/gdb/gdbstub.h +++ b/Sming/gdb/gdbstub.h @@ -44,6 +44,7 @@ enum GdbDebugFlag { DBGFLAG_SYSTEM_EXCEPTION, ///< For system exceptions, cause is EXCCAUSE (see EXCCAUSE_* values) DBGFLAG_CTRL_BREAK, ///< Break caused by call to gdbstub_ctrl_break() DBGFLAG_PACKET_STARTED, ///< Incoming packet detected by uart interrupt handler + DBGFLAG_RESTART, ///< Breaking into debugger because of unexpected system restart }; enum SyscallState { diff --git a/samples/LiveDebug/app/application.cpp b/samples/LiveDebug/app/application.cpp index 17dd1d1312..b1d77914f7 100644 --- a/samples/LiveDebug/app/application.cpp +++ b/samples/LiveDebug/app/application.cpp @@ -269,6 +269,7 @@ void fileStat(const char* filename) XX(time, "Use `syscall_gettimeofday` to get current time from host") \ XX(log, "Show state of log file") \ XX(break, "Demonstrated `gdb_do_break()` function to pause this application and obtain a GDB command prompt") \ + XX(hang, "Enter infinite loop to force a watchdog timeout") \ XX(read0, "Read from invalid address") \ XX(write0, "Write to invalid address") \ XX(restart, "Restart the system") \ @@ -339,6 +340,16 @@ COMMAND_HANDLER(break) return true; } +COMMAND_HANDLER(hang) +{ + Serial.println(_F("Entering infinite loop...")); + Serial.flush(); + while(true) { + // + } + return true; +} + COMMAND_HANDLER(read0) { Serial.println(_F("Crashing app by reading from address 0\r\n" From 62db9574aa64378ae1dc9a625783afc9c6c52dc2 Mon Sep 17 00:00:00 2001 From: mikee47 Date: Sun, 7 Apr 2019 09:53:03 +0100 Subject: [PATCH 42/50] After flashing, run GDB instead of terminal if ENABLE_GDB is set --- Sming/Makefile-rboot.mk | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/Sming/Makefile-rboot.mk b/Sming/Makefile-rboot.mk index fd9abfd91e..64c8a72c63 100644 --- a/Sming/Makefile-rboot.mk +++ b/Sming/Makefile-rboot.mk @@ -647,6 +647,9 @@ else $(ESPTOOL) -p $(COM_PORT) -b $(COM_SPEED_ESPTOOL) write_flash $(flashimageoptions) $(RBOOT_SPIFFS_0) $(SPIFF_BIN_OUT) endif +# Full GDB command line +GDB := trap '' SIGINT && $(GDB) -x $(SMING_HOME)/gdb/gdbcmds -b $(COM_SPEED_SERIAL) -ex "target remote $(COM_PORT)" + flash: all kill_term ifeq ($(DISABLE_SPIFFS), 1) # flashes rboot and first rom @@ -655,8 +658,12 @@ else # flashes rboot, first rom and spiffs $(ESPTOOL) -p $(COM_PORT) -b $(COM_SPEED_ESPTOOL) write_flash $(flashimageoptions) 0x00000 $(RBOOT_BIN) 0x02000 $(RBOOT_ROM_0) $(RBOOT_SPIFFS_0) $(SPIFF_BIN_OUT) endif +ifeq ($(ENABLE_GDB), 1) + $(GDB) +else $(TERMINAL) - +endif + otaserver: all $(vecho) "Starting OTA server for TESTING" $(Q) cd $(FW_BASE) && python -m SimpleHTTPServer $(SERVER_OTA_PORT) @@ -669,7 +676,7 @@ terminal: kill_term $(TERMINAL) gdb: kill_term - $(Q) trap '' SIGINT && $(GDB) -x $(SMING_HOME)/gdb/gdbcmds -b $(COM_SPEED_SERIAL) -ex "target remote $(COM_PORT)" + $(GDB) flashinit: $(vecho) "Flash init data default and blank data." From 321b027f3a28b1959e80840d18c65927dacd9f4e Mon Sep 17 00:00:00 2001 From: mikee47 Date: Sun, 7 Apr 2019 10:02:49 +0100 Subject: [PATCH 43/50] Change Ctrl+C handling to 'safe' break inside task callback routine rather than directly inside uart ISR. If application does not respond then repeating Ctrl+C twice will force a break. --- Sming/gdb/gdbuart.cpp | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/Sming/gdb/gdbuart.cpp b/Sming/gdb/gdbuart.cpp index 65120c7219..ac424edf49 100644 --- a/Sming/gdb/gdbuart.cpp +++ b/Sming/gdb/gdbuart.cpp @@ -28,6 +28,7 @@ static uart_t* user_uart; // If open, virtual port being used for user passt static uint32_t user_uart_status; // See gdb_uart_callback (ISR handler) static volatile bool userDataSending; // Transmit completion callback invoked on user uart static bool sendUserDataQueued; // Ensures only one call to gdbSendUserData() is queued at a time +static uint8_t break_requests; ///< How many times Ctrl+C was received before actually breaking #endif // Get number of characters in receive FIFO @@ -268,6 +269,14 @@ static void userUartNotify(uart_t* uart, uart_notify_code_t code) } #endif +static void IRAM_ATTR doCtrlBreak() +{ + if(break_requests != 0) { + break_requests = 0; + gdbstub_break_internal(DBGFLAG_CTRL_BREAK); + } +} + static void IRAM_ATTR gdb_uart_callback(uart_t* uart, uint32_t status) { #if GDBSTUB_ENABLE_UART2 @@ -317,7 +326,13 @@ static void IRAM_ATTR gdb_uart_callback(uart_t* uart, uint32_t status) } #endif if(breakCheck && c == '\x03') { - gdbstub_break_internal(DBGFLAG_CTRL_BREAK); + if(break_requests++ == 0) { + // First attempt, break within a task callback + System.queueCallback(TaskCallback(doCtrlBreak)); + } else if(break_requests == 3) { + // Application failed to stop, break immediately + doCtrlBreak(); + } continue; } #endif From f6a9fcf57dbf1010667319ba2e395222cf005a3c Mon Sep 17 00:00:00 2001 From: mikee47 Date: Sun, 7 Apr 2019 10:12:50 +0100 Subject: [PATCH 44/50] Implement Host I/O support for `remote get`, `remote put` and `remote delete` commands * Code in separate module * Revise `fileDelete()` functions to return error code from SPIFFS * Add `GDBSTUB_ENABLE_HOSTIO` option --- Sming/SmingCore/FileSystem.cpp | 8 +- Sming/SmingCore/FileSystem.h | 6 +- Sming/gdb/GdbPacket.cpp | 7 ++ Sming/gdb/GdbPacket.h | 5 +- Sming/gdb/gdbhostio.cpp | 174 +++++++++++++++++++++++++++++++++ Sming/gdb/gdbhostio.h | 18 ++++ Sming/gdb/gdbstub-cfg.h | 8 ++ Sming/gdb/gdbstub.cpp | 46 +-------- Sming/gdb/readme.md | 10 ++ 9 files changed, 233 insertions(+), 49 deletions(-) create mode 100644 Sming/gdb/gdbhostio.cpp create mode 100644 Sming/gdb/gdbhostio.h diff --git a/Sming/SmingCore/FileSystem.cpp b/Sming/SmingCore/FileSystem.cpp index 0f7a2763cd..40a0a1bfe3 100644 --- a/Sming/SmingCore/FileSystem.cpp +++ b/Sming/SmingCore/FileSystem.cpp @@ -86,14 +86,14 @@ int fileStats(file_t file, spiffs_stat* stat) return SPIFFS_fstat(&_filesystemStorageHandle, file, stat); } -void fileDelete(const String& name) +int fileDelete(const String& name) { - SPIFFS_remove(&_filesystemStorageHandle, name.c_str()); + return SPIFFS_remove(&_filesystemStorageHandle, name.c_str()); } -void fileDelete(file_t file) +int fileDelete(file_t file) { - SPIFFS_fremove(&_filesystemStorageHandle, file); + return SPIFFS_fremove(&_filesystemStorageHandle, file); } bool fileExist(const String& name) diff --git a/Sming/SmingCore/FileSystem.h b/Sming/SmingCore/FileSystem.h index 59ece204d9..1660966b7d 100644 --- a/Sming/SmingCore/FileSystem.h +++ b/Sming/SmingCore/FileSystem.h @@ -185,13 +185,15 @@ int fileStats(file_t file, spiffs_stat* stat); /** @brief Delete file * @param name Name of file to delete + * @retval int error code, 0 on success */ -void fileDelete(const String& name); +int fileDelete(const String& name); /** @brief Delete file * @param file ID of file to delete + * @retval int error code, 0 on success */ -void fileDelete(file_t file); +int fileDelete(file_t file); /** @brief Check if a file exists on file system * @param name Name of file to check for diff --git a/Sming/gdb/GdbPacket.cpp b/Sming/gdb/GdbPacket.cpp index 1c1bc5cc11..f3e8ab10ad 100644 --- a/Sming/gdb/GdbPacket.cpp +++ b/Sming/gdb/GdbPacket.cpp @@ -41,6 +41,13 @@ void ATTR_GDBEXTERNFN GdbPacket::writeCharEscaped(char c) writeChar(c); } +void ATTR_GDBEXTERNFN GdbPacket::writeEscaped(const void* data, unsigned length) +{ + for(unsigned i = 0; i < length; ++i) { + writeCharEscaped(static_cast(data)[i]); + } +} + void ATTR_GDBEXTERNFN GdbPacket::write(const void* data, unsigned length) { for(unsigned i = 0; i < length; ++i) { diff --git a/Sming/gdb/GdbPacket.h b/Sming/gdb/GdbPacket.h index 6e24c5c901..1ab8a2d9e3 100644 --- a/Sming/gdb/GdbPacket.h +++ b/Sming/gdb/GdbPacket.h @@ -35,9 +35,12 @@ class GdbPacket // Send a char as part of a packet void writeChar(char c); - // Send a character, escaping if required + /** @brief Send a character, escaping if required */ void writeCharEscaped(char c); + /** @brief Send a block of data, escaping as required */ + void writeEscaped(const void* data, unsigned length); + /** @brief Output 8-bit value */ void writeHexByte(uint8_t value); diff --git a/Sming/gdb/gdbhostio.cpp b/Sming/gdb/gdbhostio.cpp new file mode 100644 index 0000000000..3a04409f27 --- /dev/null +++ b/Sming/gdb/gdbhostio.cpp @@ -0,0 +1,174 @@ +/**** + * Sming Framework Project - Open Source framework for high efficiency native ESP8266 development. + * Created 2015 by Skurydin Alexey + * http://github.com/SmingHub/Sming + * All files of the Sming Core are provided under the LGPL v3 license. + * + * gdbhostio.h + * + * @author: 2019 - Mikee47 + * + * Deals with Host I/O commands from GDB + * + ****/ + +#include "gdbstub-cfg.h" + +#if GDBSTUB_ENABLE_HOSTIO + +#include "GdbPacket.h" +#include +#include +#include "FileSystem.h" +#include "WString.h" + +void ATTR_GDBEXTERNFN gdbHandleHostIo(char* commandBuffer, unsigned cmdLen) +{ + const char* data = &commandBuffer[1]; + + auto match = [&](const char* tag) -> bool { + auto len = strlen(tag); + if(data[len] == ':' && memcmp(data, tag, len) == 0) { + data += len + 1; + return true; + } else { + return false; + } + }; + + // Start a packet for the result + GdbPacket packet; + + /* + * Host I/O + * + * `vFile:operation:parameter...` + */ + if(!match(GDB_F("File"))) { + return; // empty packet response + } + + /* + * open: filename, flags, mode + */ + if(match(GDB_F("open"))) { + char* filename = commandBuffer; + size_t len = GdbPacket::decodeHexBlock(filename, data); + filename[len] = '\0'; + ++data; // skip , + unsigned flags = GdbPacket::readHexValue(data); + ++data; // Skip , + unsigned mode = GdbPacket::readHexValue(data); + + FileOpenFlags openFlags; + if((flags & 0xff) == O_RDWR) { + openFlags = eFO_ReadWrite; + } else if((flags & 0xff) == O_WRONLY) { + openFlags = eFO_WriteOnly; + } else { + openFlags = eFO_ReadOnly; + } + if(flags & O_CREAT) { + openFlags = openFlags | eFO_CreateIfNotExist; + } + if(flags & O_TRUNC) { + openFlags = openFlags | eFO_Truncate; + } + int fd = fileOpen(filename, openFlags); + + debug_i("File:open('%s', 0x%04x, 0x%04x), %d", filename, flags, mode, fd); + + packet.writeChar('F'); + if(fd < 0) { + packet.writeStr(_F("-1,")); + packet.writeHexByte(ENOENT); + } else { + packet.writeHexByte(fd); + } + return; + } + + /* + * close: fd, count, offset + */ + if(match(GDB_F("close"))) { + int fd = GdbPacket::readHexValue(data); + debug_i("File:close(%d)", fd); + fileClose(fd); + + packet.writeChar('F'); + packet.writeChar('0'); + return; + } + + /* + * pread: fd, count, offset + */ + if(match(GDB_F("pread"))) { + int fd = GdbPacket::readHexValue(data); + ++data; // Skip , + unsigned count = GdbPacket::readHexValue(data); + ++data; // Skip , + unsigned offset = GdbPacket::readHexValue(data); + debug_i("File:pread(%d, %u, %u)", fd, count, offset); + + packet.writeChar('F'); + if(fileSeek(fd, offset, eSO_FileStart) == offset) { + count = fileRead(fd, commandBuffer, count); + if(int(count) >= 0) { + packet.writeHexWord16(count); + packet.writeChar(';'); + packet.writeEscaped(commandBuffer, count); + return; + } + } + packet.writeStr(_F("-1,")); + packet.writeHexByte(EIO); + return; + } + + /* + * pwrite: fd, offset, data + */ + if(match(GDB_F("pwrite"))) { + int fd = GdbPacket::readHexValue(data); + ++data; // Skip , + unsigned offset = GdbPacket::readHexValue(data); + ++data; // Skip , + unsigned size = cmdLen - (data - commandBuffer); + debug_i("File:pwrite(%d, %u, %u)", fd, offset, size); + + packet.writeChar('F'); + if(fileSeek(fd, offset, eSO_FileStart) == offset) { + int count = fileWrite(fd, data, size); + if(count >= 0) { + packet.writeHexWord16(count); + return; + } + } + packet.writeStr(_F("-1,")); + packet.writeHexByte(EIO); + } + + /* + * unlink: filename + */ + if(match(GDB_F("unlink"))) { + char* filename = commandBuffer; + size_t len = GdbPacket::decodeHexBlock(filename, data); + filename[len] = '\0'; + int res = fileDelete(filename); + debug_i("File:delete('%s'), %d", filename, res); + + packet.writeChar('F'); + if(res < 0) { + packet.writeStr(_F("-1,")); + packet.writeHexByte(ENOENT); + } else { + packet.writeHexByte(0); + } + return; + } +} + +#endif // GDBSTUB_ENABLE_HOSTIO diff --git a/Sming/gdb/gdbhostio.h b/Sming/gdb/gdbhostio.h new file mode 100644 index 0000000000..2be22e6019 --- /dev/null +++ b/Sming/gdb/gdbhostio.h @@ -0,0 +1,18 @@ +/**** + * Sming Framework Project - Open Source framework for high efficiency native ESP8266 development. + * Created 2015 by Skurydin Alexey + * http://github.com/SmingHub/Sming + * All files of the Sming Core are provided under the LGPL v3 license. + * + * gdbhostio.h + * + * @author: 2019 - Mikee47 + * + ****/ + +#ifndef GDB_GDBHOSTIO_H_ +#define GDB_GDBHOSTIO_H_ + +void gdbHandleHostIo(char* commandBuffer, unsigned cmdLen); + +#endif /* GDB_GDBHOSTIO_H_ */ diff --git a/Sming/gdb/gdbstub-cfg.h b/Sming/gdb/gdbstub-cfg.h index f84cbe779f..6fac70f8f4 100644 --- a/Sming/gdb/gdbstub-cfg.h +++ b/Sming/gdb/gdbstub-cfg.h @@ -145,6 +145,14 @@ #define GDBSTUB_ENABLE_SYSCALL 1 #endif +/* + * Enable Host I/O capability, where files may be accessed via GDB command prompt using + * `remote get`, `remote put` and `remote delete` commands. + */ +#ifndef GDBSTUB_ENABLE_HOSTIO +#define GDBSTUB_ENABLE_HOSTIO 1 +#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. diff --git a/Sming/gdb/gdbstub.cpp b/Sming/gdb/gdbstub.cpp index cef440a089..2f0a21d8a9 100644 --- a/Sming/gdb/gdbstub.cpp +++ b/Sming/gdb/gdbstub.cpp @@ -35,6 +35,7 @@ #include "exceptions.h" #include "gdb/registers.h" #include "gdbsyscall.h" +#include "gdbhostio.h" #include "Platform/System.h" extern "C" { @@ -652,52 +653,12 @@ static GdbResult ATTR_GDBEXTERNFN handleCommand(unsigned cmdLen) break; } -#ifdef GDBSTUB_CMDENABLE_V +#if GDBSTUB_ENABLE_HOSTIO /* * Packet starting with 'v' are identified by a multi-letter name, up to the first ':' or '?' (or end of packet) */ case 'v': - /* - * `vFile:operation:parameter...` - * - * @todo Consider whether this might be useful - */ - if(strncmp(data, GDB_F("File:"), 5) == 0) { - data += 5; - if(strncmp(data, GDB_F("open:"), 5) == 0) { - data += 5; - char* filename; - size_t len = GdbPacket::decodeHexBlock(data, filename); - filename[len] = '\0'; - debug_i("File:open('%s')", filename); - sendOK(); - break; - } - } - /* - * vFlashErase:addr,length - */ - else if(strncmp(data, GDB_F("FlashErase:"), 11) == 0) { - data += 11; - // @todo - } - /* - * vFlashWrite:addr:XX... - */ - else if(strncmp(data, GDB_F("FlashWrite:"), 11) == 0) { - data += 11; - // @todo - } - /* - * vFlashDone - * - * Indicates that programming operation is finished - */ - else if(strncmp(data, GDB_F("FlashDone:"), 10) == 0) { - data += 10; - // @todo - } - sendEmptyResponse(); + gdbHandleHostIo(commandBuffer, cmdLen); break; #endif @@ -938,6 +899,7 @@ void ATTR_GDBINIT gdbstub_init() SD(GDBSTUB_BREAK_ON_INIT); SD(GDBSTUB_ENABLE_UART2); SD(GDBSTUB_ENABLE_SYSCALL); + SD(GDBSTUB_ENABLE_HOSTIO); #undef SD #if GDBSTUB_BREAK_ON_INIT diff --git a/Sming/gdb/readme.md b/Sming/gdb/readme.md index 23c2c2c724..39248692ca 100644 --- a/Sming/gdb/readme.md +++ b/Sming/gdb/readme.md @@ -64,6 +64,16 @@ Useful GDB commands `tui enable` Provides a windowed interface within the console (only seems to work in Linux) + +These commands require `GDBSTUB_ENABLE_HOSTIO` to be enabled: + +`remote get targetfile hostfile` Read a file from SPIFFS (on the target) + +`remote put hostfile targetfile` Write a file to SPIFFS + +`remote delete targetfile` Delete a file from SPIFFS + + Eclipse ------- From 37be3a1e8429cc8c25e00fe50db269ef6a35cd43 Mon Sep 17 00:00:00 2001 From: mikee47 Date: Sun, 7 Apr 2019 11:00:25 +0100 Subject: [PATCH 45/50] GDBSTUB_CMDENABLE_X should be #if not #ifdef --- Sming/gdb/gdbstub.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sming/gdb/gdbstub.cpp b/Sming/gdb/gdbstub.cpp index 2f0a21d8a9..6175704901 100644 --- a/Sming/gdb/gdbstub.cpp +++ b/Sming/gdb/gdbstub.cpp @@ -662,7 +662,7 @@ static GdbResult ATTR_GDBEXTERNFN handleCommand(unsigned cmdLen) break; #endif -#ifdef GDBSTUB_CMDENABLE_X +#if GDBSTUB_CMDENABLE_X /* * Write data to memory, where the data is transmitted in binary, * which is more efficient than hex packets (see 'M'). From 7e768527222bf1524a42d5bf11284ddc9b5cd7c0 Mon Sep 17 00:00:00 2001 From: mikee47 Date: Sun, 7 Apr 2019 11:01:28 +0100 Subject: [PATCH 46/50] Correct format of n:r pairs in 'T' response - not currently used but may be in future. --- Sming/gdb/gdbstub.cpp | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/Sming/gdb/gdbstub.cpp b/Sming/gdb/gdbstub.cpp index 6175704901..90ae5d1eef 100644 --- a/Sming/gdb/gdbstub.cpp +++ b/Sming/gdb/gdbstub.cpp @@ -218,16 +218,17 @@ static void ATTR_GDBEXTERNFN sendReason() // Current Xtensa GDB versions don't seem to request this, so let's leave it off. #if 0 if(bitRead(cause, 0)) { - packet.writeStr(GDB_F("break")); // Single-step (ICOUNT hits 0) + packet.writeStr(GDB_F("break:;")); // Single-step (ICOUNT hits 0) } else if(bitRead(cause, DBGCAUSE_IBREAK)) { - packet.writeStr(GDB_F("hwbreak")); + packet.writeStr(GDB_F("hwbreak:;")); } else if(bitRead(cause, DBGCAUSE_DBREAK)) { packet.writeStr(GDB_F("watch:")); - // ToDo: send address + packet.writeHexWord32(gdbstub_savedRegs.excvaddr); // @todo confirm this is correct + packet.writeChar(';'); } else if(bitRead(cause, DBGCAUSE_BREAK)) { - packet.writeStr(GDB_F("swbreak")); + packet.writeStr(GDB_F("swbreak:;")); } else if(bitRead(cause, DBGCAUSE_BREAKN)) { - packet.writeStr(GDB_F("swbreak")); + packet.writeStr(GDB_F("swbreak:;")); } #endif } From b7a3a11c4f8cacca6213e3fc63900102c8e57a25 Mon Sep 17 00:00:00 2001 From: mikee47 Date: Sun, 7 Apr 2019 11:05:53 +0100 Subject: [PATCH 47/50] Fix documentation (doxygen) * Add `Sming/System` and `Sming/gdb` directories * Enable extraction of static members, so `static inline` code gets picked up * Create GDB group * Doxygen misbehaves with `__attribute__((packed))` so use `#pragma pack()` instead --- Sming/SmingCore/Data/Stream/GdbFileStream.h | 9 ++----- Sming/gdb/gdbstub-internal.h | 4 ++-- Sming/system/include/gdb_hooks.h | 22 ++++++++++------- Sming/system/include/gdb_syscall.h | 26 ++++++++++++++------- docs/Doxyfile | 6 +++-- 5 files changed, 39 insertions(+), 28 deletions(-) diff --git a/Sming/SmingCore/Data/Stream/GdbFileStream.h b/Sming/SmingCore/Data/Stream/GdbFileStream.h index ae8b20021c..ff101306f6 100644 --- a/Sming/SmingCore/Data/Stream/GdbFileStream.h +++ b/Sming/SmingCore/Data/Stream/GdbFileStream.h @@ -15,12 +15,9 @@ #include "FileSystem.h" /** - * @brief GDB File stream class to access to host files whilst running under debugger - * @ingroup stream data - * - * @{ + * @ingroup stream gdb_syscall + * @brief GDB File stream class to provide access to host files whilst running under debugger */ - class GdbFileStream : public ReadWriteStream { public: @@ -144,6 +141,4 @@ class GdbFileStream : public ReadWriteStream int lastError = 0; }; -/** @} */ - #endif /* _SMING_CORE_DATA_GDB_FILE_STREAM_H_ */ diff --git a/Sming/gdb/gdbstub-internal.h b/Sming/gdb/gdbstub-internal.h index 91fb316d24..b145d8f522 100644 --- a/Sming/gdb/gdbstub-internal.h +++ b/Sming/gdb/gdbstub-internal.h @@ -24,8 +24,8 @@ * Any register changes made by GDB are stored here. * * System exceptions are initially handled by the Xtensa HAL, which saves some (but not all) - * registers into a `UserFrame` structure (see xtensa.h). Control passes to gdbstub_exception_handler - * which copies those values, plus some extra ones. + * registers into a `UserFrame` structure (see xtensa/xtruntime-frames.h). + * Control passes to gdbstub_exception_handler, which copies those values plus some extra ones. * * Debug exceptions are handled entirely by gdbstub_debug_exception_entry, which saves * registers, calls gdbstub_handle_debug_exception, then restores them. diff --git a/Sming/system/include/gdb_hooks.h b/Sming/system/include/gdb_hooks.h index ed6806f9e1..cfd6082960 100644 --- a/Sming/system/include/gdb_hooks.h +++ b/Sming/system/include/gdb_hooks.h @@ -26,6 +26,10 @@ extern "C" { #endif +/** @defgroup GDB GDB debugging support + * @{ +*/ + /** * @brief Initialise GDB stub, if present * @note Called by framework at startup, but does nothing if gdbstub is not linked. @@ -39,10 +43,10 @@ void gdb_init(void); * * Calling with `enable = false` overrides configured behaviour as follows: * - * - Debug exceptions will be silently ignored. - * - System exceptions will cause a watchdog reset, as for `GDBSTUB_BREAK_ON_EXCEPTION = 0`. - * - All incoming serial data is passed through to UART2 without inspection, - * as for `GDBSTUB_CTRLC_BREAK = 0` and `GDBSTUB_FREE_ATTACH = 0`. + * - Debug exceptions will be silently ignored + * - System exceptions will cause a watchdog reset, as for `GDBSTUB_BREAK_ON_EXCEPTION = 0` + * - All incoming serial data is passed through to UART2 without inspection, as for `GDBSTUB_CTRLC_BREAK = 0` + * */ void gdb_enable(bool state); @@ -75,15 +79,15 @@ void gdb_on_attach(bool attached); */ void gdb_detach(); -/** - * @brief Called on unexpected system reset +/* + * @brief Called by framekwork on unexpected system reset */ void debug_crash_callback(const struct rst_info* rst_info, uint32_t stack, uint32_t stack_end); /** * @brief Send a stack dump to debug output - * @param start - * @param end + * @param start Start address to output + * @param end Output up to - but not including - this address */ void debug_print_stack(uint32_t start, uint32_t end); @@ -91,4 +95,6 @@ void debug_print_stack(uint32_t start, uint32_t end); } // extern "C" #endif +/** @} */ + #endif /* _GDB_HOOKS_H_ */ diff --git a/Sming/system/include/gdb_syscall.h b/Sming/system/include/gdb_syscall.h index 572d047990..df8c0bd08e 100644 --- a/Sming/system/include/gdb_syscall.h +++ b/Sming/system/include/gdb_syscall.h @@ -24,11 +24,6 @@ * ****/ -/** @defgroup GDB - * @brief GDB system call API - * @{ -*/ - #ifndef _SYSTEM_INCLUDE_GDB_SYSCALL_H_ #define _SYSTEM_INCLUDE_GDB_SYSCALL_H_ @@ -38,8 +33,17 @@ #include #include -/* GDB uses a specific version of the stat structure, 64 bytes in size */ -struct __attribute__((packed)) gdb_stat_t { +/** @defgroup gdb_syscall GDB system call API + * @ingroup GDB + * @{ +*/ + +#pragma pack(push, 4) + +/** + * @brief GDB uses a specific version of the stat structure, 64 bytes in size + */ +struct gdb_stat_t { uint32_t st_dev; // device uint32_t st_ino; // inode mode_t st_mode; // protection @@ -55,12 +59,16 @@ struct __attribute__((packed)) gdb_stat_t { time_t st_ctime; // time of last change }; -/* GDB uses a specific version of the timeval structure, 12 bytes in size (manual says 8, which is wrong) */ -struct __attribute__((packed)) gdb_timeval_t { +/** + * @brief GDB uses a specific version of the timeval structure, 12 bytes in size (manual says 8, which is wrong) + */ +struct gdb_timeval_t { time_t tv_sec; // second uint64_t tv_usec; // microsecond }; +#pragma pack(pop) + /* GDB Syscall interface */ /** diff --git a/docs/Doxyfile b/docs/Doxyfile index 7ea490734d..9f347d0748 100644 --- a/docs/Doxyfile +++ b/docs/Doxyfile @@ -427,7 +427,7 @@ EXTRACT_PACKAGE = NO # included in the documentation. # The default value is: NO. -EXTRACT_STATIC = NO +EXTRACT_STATIC = YES # If the EXTRACT_LOCAL_CLASSES tag is set to YES, classes (and structs) defined # locally in source files will be included in the documentation. If set to NO, @@ -764,7 +764,9 @@ INPUT = api.dox sdk.dox \ ../Sming/Wiring \ ../Sming/third-party/rboot \ ../Sming/Libraries \ - $(ESP_HOME)/sdk/include/gpio.h + $(ESP_HOME)/sdk/include/gpio.h \ + ../Sming/System \ + ../Sming/gdb \ # This tag can be used to specify the character encoding of the source files # that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses From 300a1473f0704b52ffe16bd9c447368d1ec008d9 Mon Sep 17 00:00:00 2001 From: mikee47 Date: Sun, 7 Apr 2019 12:11:45 +0100 Subject: [PATCH 48/50] Update `readme.md`, remove `todo.md` --- Sming/gdb/readme.md | 15 +++++++++++++-- Sming/gdb/todo.md | 19 ------------------- 2 files changed, 13 insertions(+), 21 deletions(-) delete mode 100644 Sming/gdb/todo.md diff --git a/Sming/gdb/readme.md b/Sming/gdb/readme.md index 39248692ca..ea0d4108f5 100644 --- a/Sming/gdb/readme.md +++ b/Sming/gdb/readme.md @@ -100,7 +100,6 @@ System crashes if debugger is paused for too long - Cause: The WiFi hardware is designed to be serviced by software periodically. It has some buffers so it will behave OK when some data comes in while the processor is busy, but these buffers are not infinite. If the WiFi hardware receives lots of data while the debugger has stopped the CPU, it is bound to crash. This will happen mostly when working with UDP and/or ICMP; TCP-connections in general will not send much more data when the other side doesn't send any ACKs. - Solution: In such situations avoid pausing the debugger for extended periods - Software breakpoints/watchpoints ('break' and 'watch') don't work on flash code - Cause: GDB handles these by replacing code with a debugging instruction, therefore the code must be in RAM. - Solution: Use hardware breakpoint ('hbreak') or use GDB_IRAM_ATTR for code which requires testing @@ -109,7 +108,6 @@ If hardware breakpoint is set, single-stepping won't work unless code is in RAM. - Cause: GDB reverts to software breakpoints if no hardware breakpoints are available - Solution: Delete hardware breakpoint before single-stepping - Crash occurs when setting breakpoint in HardwareTimer callback routine - Cause: By default, HardwareTimer uses Non-maskable Interrupts (NMI) which keep running when the debugger is paused - Solution: Use the timer in non-maskable mode, or enable GDBSTUB_PAUSE_HARDWARE_TIMER option @@ -129,3 +127,16 @@ Whilst GDB is attached, input cannot be passed to application No apparent way to have second 'console' (windows terminology) separate from GDB interface - Cause: Unknown - Solution: Is this possible with remote targets? + +When GDB is running under windows, appears to hang when target reset or restarted +- Cause: Unknown, may not happen on all devboards but presents with NodeMCU +- Solution + - quit GDB `quit` + - Start terminal `make terminal` + - reset board + - quit terminal + - run GDB again `make gdb` + +GDB (in Windows) doesn't respond at all to Ctrl+C +- Cause: Unknown +- Solution: Press Ctrl+Break to 'hard kill' GDB diff --git a/Sming/gdb/todo.md b/Sming/gdb/todo.md deleted file mode 100644 index ef726a804b..0000000000 --- a/Sming/gdb/todo.md +++ /dev/null @@ -1,19 +0,0 @@ - -Console and File I/O --------------------- - -This is a potential future enhancement to the gdbstub. See GDB manual for full details. - - "The File I/O remote protocol extension allows the target to use the host’s file system and console I/O to perform various system calls. System calls on the target system are translated into a remote protocol packet to the host system, which then performs the needed actions and returns a response packet to the target system. This simulates file system operations even on targets that lack fle systems." - -As an example, we might implement a function in gdbstub called `gdb_console_read`: - -``` -char buffer[32]; -int len = gdb_console_read(buffer, sizeof(buffer)); -m_printf("gdb_console_read() returned %d\n", len); -``` - -This pauses the user application whilst waiting for input from the GDB console. Hitting return ends the call and the data is stored in buffer. Note: it _may_ be possible to have the application continue executing while the system call is in progress. - -The same approach can be used to write to the console, to read/write host files or to make system calls. This could help speed application development by using files from the host, instead of a local file system (e.g. SPIFFS). From 78d7dfcf8df41abefc6151f7e1d295d35630218e Mon Sep 17 00:00:00 2001 From: mikee47 Date: Sun, 7 Apr 2019 12:12:25 +0100 Subject: [PATCH 49/50] Linux doesn't recognise `SIGINT` with `trap` command, so just use `SIG` works with both Linux and MinGW --- Sming/Makefile-rboot.mk | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sming/Makefile-rboot.mk b/Sming/Makefile-rboot.mk index 64c8a72c63..1b23890563 100644 --- a/Sming/Makefile-rboot.mk +++ b/Sming/Makefile-rboot.mk @@ -648,7 +648,7 @@ else endif # Full GDB command line -GDB := trap '' SIGINT && $(GDB) -x $(SMING_HOME)/gdb/gdbcmds -b $(COM_SPEED_SERIAL) -ex "target remote $(COM_PORT)" +GDB := trap '' INT && $(GDB) -x $(SMING_HOME)/gdb/gdbcmds -b $(COM_SPEED_SERIAL) -ex "target remote $(COM_PORT)" flash: all kill_term ifeq ($(DISABLE_SPIFFS), 1) From 45e70a53af5a739574935924a76c4e4060e8949a Mon Sep 17 00:00:00 2001 From: mikee47 Date: Sun, 7 Apr 2019 23:16:23 +0100 Subject: [PATCH 50/50] In `debug_crash_callback()`, interrupt level must be reduced to enable `break`, otherwise it's just a no-op. * Appeared to work as debug prompt appears, but that's the initial startup breakpoint. Function gets called at elevated interrupt level so need to drop it below DEBUG to enable break instruction. Now get correct signal (SIGPWR) in GDB in response to a crash. --- Sming/appspecific/gdb/gdb_hooks.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Sming/appspecific/gdb/gdb_hooks.cpp b/Sming/appspecific/gdb/gdb_hooks.cpp index c68164eb95..ff87cab2c4 100644 --- a/Sming/appspecific/gdb/gdb_hooks.cpp +++ b/Sming/appspecific/gdb/gdb_hooks.cpp @@ -98,6 +98,8 @@ void debug_crash_callback(const rst_info* rst_info, uint32_t stack, uint32_t sta } #if defined(ENABLE_GDB) && GDBSTUB_BREAK_ON_RESTART + // Drop interrupt level to enable break instruction + asm("rsil a2, 1"); // XCHAL_DEBUGLEVEL - 1 gdbstub_break_internal(DBGFLAG_RESTART); #elif ENABLE_CRASH_DUMP debug_print_stack(stack, stack_end);