diff --git a/Sming/Makefile-rboot.mk b/Sming/Makefile-rboot.mk index 5450043e37..638b45effc 100644 --- a/Sming/Makefile-rboot.mk +++ b/Sming/Makefile-rboot.mk @@ -50,8 +50,6 @@ 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 -# File containing boot ROM debug symbols for GDB -BOOTROM_ELF := bootrom.elf ## COM port parameters # Default COM port speed (generic) @@ -255,6 +253,7 @@ endif ifeq ($(ENABLE_GDB), 1) CFLAGS += -ggdb -DENABLE_GDB=1 MODULES += $(SMING_HOME)/gdb + GDB_SYMBOLS := gdb_symbols endif ifeq ($(SMING_RELEASE),1) @@ -542,11 +541,13 @@ endef .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) $(BUILD_BASE)/$(BOOTROM_ELF) +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) $(GDB_SYMBOLS) -# File contains boot rom symbols required by GDB, put it somewhere easy to find -$(BUILD_BASE)/$(BOOTROM_ELF): - cp $(SMING_HOME)/gdb/$(BOOTROM_ELF) $(BUILD_BASE) +# Copy symbols required by GDB into build directory +gdb_symbols: $(BUILD_BASE)/bootrom.elf + +$(BUILD_BASE)/%.elf: + cp $(SMING_HOME)/gdb/symbols/$(notdir $@) $@ $(RBOOT_BIN): $(MAKE) -C $(THIRD_PARTY_DIR)/rboot RBOOT_GPIO_ENABLED=$(RBOOT_GPIO_ENABLED) RBOOT_SILENT=$(RBOOT_SILENT) diff --git a/Sming/SmingCore/SimpleTimer.h b/Sming/SmingCore/SimpleTimer.h index 8ea2ec87b1..813cbb566f 100644 --- a/Sming/SmingCore/SimpleTimer.h +++ b/Sming/SmingCore/SimpleTimer.h @@ -37,6 +37,8 @@ extern "C" { */ #define MAX_OS_TIMER_INTERVAL_US 268435000 +typedef os_timer_func_t* SimpleTimerCallback; + class SimpleTimer { public: @@ -89,7 +91,7 @@ class SimpleTimer * @param interrupt Function to be called on timer trigger * @note Classic c-type callback method */ - void setCallback(os_timer_func_t callback, void* arg = nullptr) + void setCallback(SimpleTimerCallback callback, void* arg = nullptr) { stop(); ets_timer_setfn(&osTimer, callback, arg); diff --git a/Sming/appspecific/gdb/gdb_hooks.cpp b/Sming/appspecific/gdb/gdb_hooks.cpp index 626ac3109a..638593a9d9 100644 --- a/Sming/appspecific/gdb/gdb_hooks.cpp +++ b/Sming/appspecific/gdb/gdb_hooks.cpp @@ -120,10 +120,7 @@ void dumpExceptionInfo() "***** Fatal exception %u"), reg.cause); if(reg.cause <= EXCCAUSE_MAX) { - char name[32]; - memcpy_P(name, exceptionNames[reg.cause], sizeof(name)); - name[sizeof(name) - 1] = '\0'; - m_printf(_F(" (%s)"), name); + m_printf(_F(" (%s)"), exceptionNames[reg.cause]); } m_puts("\r\n"); @@ -243,7 +240,6 @@ static unsigned IRAM_ATTR __gdb_no_op(void) #define NOOP __attribute__((weak, alias("__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; void gdb_detach(void) NOOP; diff --git a/Sming/appspecific/gdb/gdbstub-entry.S b/Sming/appspecific/gdb/gdbstub-entry.S index 15cbf885d8..4403f316db 100644 --- a/Sming/appspecific/gdb/gdbstub-entry.S +++ b/Sming/appspecific/gdb/gdbstub-entry.S @@ -213,6 +213,10 @@ gdbstub_init_debug_entry: ASATTR_GDBFN .align 4 gdbstub_icount_ena_single_step: + +//Make sure we're running at sufficiently high interrupt level to prevent early triggering + rsil a2,XCHAL_DEBUGLEVEL + movi a3, XCHAL_DEBUGLEVEL //Only count steps in non-debug mode movi a2, -2 wsr a3, ICOUNTLEVEL diff --git a/Sming/gdb/GdbPacket.cpp b/Sming/gdb/GdbPacket.cpp index f3e8ab10ad..51ca564620 100644 --- a/Sming/gdb/GdbPacket.cpp +++ b/Sming/gdb/GdbPacket.cpp @@ -15,6 +15,9 @@ // Send the start of a packet; reset checksum calculation. void ATTR_GDBEXTERNFN GdbPacket::start() { +#if GDBSTUB_ENABLE_DEBUG + m_puts(_F("> PKT ")); +#endif gdbSendChar('$'); } @@ -22,7 +25,9 @@ void ATTR_GDBEXTERNFN GdbPacket::end() { gdbSendChar('#'); writeHexByte(checksum); - debug_i("> PKT [%u]", packetLength); +#if GDBSTUB_ENABLE_DEBUG + m_puts("\r\n"); +#endif } void ATTR_GDBEXTERNFN GdbPacket::writeChar(char c) diff --git a/Sming/gdb/gdbcmds b/Sming/gdb/gdbcmds index df3291708f..18760ec02d 100644 --- a/Sming/gdb/gdbcmds +++ b/Sming/gdb/gdbcmds @@ -1,5 +1,5 @@ # Enable this if you want to log all traffic between GDB and the stub -# set remotelogfile gdb_rsp_logfile.txt +#set remotelogfile gdb_rsp_logfile.txt # ESP8266 HW limits the number of breakpoints/watchpoints set remote hardware-breakpoint-limit 1 @@ -29,6 +29,9 @@ file out/build/app_0.out # See https://github.com/jcmvbkbc/esp-elf-rom add-symbol-file out/build/bootrom.elf 0x40000000 -readnow +# Useful for lower-level debugging to see the result of stepping through assembler code +#set disassemble-next-line on + # Change the following to your serial port and baud #set serial baud 115200 #target remote /dev/ttyUSB0 diff --git a/Sming/gdb/gdbhostio.cpp b/Sming/gdb/gdbhostio.cpp index 3a04409f27..c73631b84b 100644 --- a/Sming/gdb/gdbhostio.cpp +++ b/Sming/gdb/gdbhostio.cpp @@ -113,7 +113,7 @@ void ATTR_GDBEXTERNFN gdbHandleHostIo(char* commandBuffer, unsigned cmdLen) debug_i("File:pread(%d, %u, %u)", fd, count, offset); packet.writeChar('F'); - if(fileSeek(fd, offset, eSO_FileStart) == offset) { + if(fileSeek(fd, offset, eSO_FileStart) == int(offset)) { count = fileRead(fd, commandBuffer, count); if(int(count) >= 0) { packet.writeHexWord16(count); @@ -139,7 +139,7 @@ void ATTR_GDBEXTERNFN gdbHandleHostIo(char* commandBuffer, unsigned cmdLen) debug_i("File:pwrite(%d, %u, %u)", fd, offset, size); packet.writeChar('F'); - if(fileSeek(fd, offset, eSO_FileStart) == offset) { + if(fileSeek(fd, offset, eSO_FileStart) == int(offset)) { int count = fileWrite(fd, data, size); if(count >= 0) { packet.writeHexWord16(count); diff --git a/Sming/gdb/gdbstub-cfg.h b/Sming/gdb/gdbstub-cfg.h index 6fac70f8f4..d31120ee5e 100644 --- a/Sming/gdb/gdbstub-cfg.h +++ b/Sming/gdb/gdbstub-cfg.h @@ -54,15 +54,6 @@ #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. @@ -102,7 +93,7 @@ * 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. + * Instead, use GDBSTUB_BREAK_ON_INIT, or call gdb_do_break() in your application. * * Specify: * 0 to disable Ctrl+C break checking completely @@ -142,7 +133,7 @@ * If undefined, calls will do nothing and return -1. */ #ifndef GDBSTUB_ENABLE_SYSCALL -#define GDBSTUB_ENABLE_SYSCALL 1 +#define GDBSTUB_ENABLE_SYSCALL 0 #endif /* @@ -178,7 +169,7 @@ * Set to 0 to wait indefinitely. */ #ifndef GDBSTUB_UART_READ_TIMEOUT -#define GDBSTUB_UART_READ_TIMEOUT 2000 +#define GDBSTUB_UART_READ_TIMEOUT 0 #endif /* diff --git a/Sming/gdb/gdbstub.cpp b/Sming/gdb/gdbstub.cpp index 90ae5d1eef..b432cde6e8 100644 --- a/Sming/gdb/gdbstub.cpp +++ b/Sming/gdb/gdbstub.cpp @@ -195,6 +195,11 @@ static ERRNO_T ATTR_GDBEXTERNFN writeMemoryBlock(uint32_t addr, const void* data */ static void ATTR_GDBEXTERNFN sendReason() { + if(gdb_state.syscall == syscall_active) { + // GDB ignores this whilst processing a system call - we'll send when it's completed via 'F' response + return; + } + gdbFlushUserData(); GdbPacket packet; packet.writeChar('T'); @@ -300,12 +305,18 @@ static unsigned ATTR_GDBEXTERNFN readCommand() while((c = gdbReceiveChar()) != '#') { // end of packet, checksum follows if(c < 0) { gdbSendChar('-'); +#if GDBSTUB_ENABLE_DEBUG + m_puts("\r\n"); debug_e("CMD TIMEOUT"); +#endif return 0; } if(c == '$') { // Wut, restart packet? +#if GDBSTUB_ENABLE_DEBUG + m_puts("\r\n"); debug_e("Unexpected '$' received"); +#endif checksum = 0; cmdLen = 0; continue; @@ -318,21 +329,26 @@ static unsigned ATTR_GDBEXTERNFN readCommand() } if(cmdLen >= MAX_COMMAND_LENGTH) { // Received more than the size of the command buffer +#if GDBSTUB_ENABLE_DEBUG + m_puts("\r\n"); debug_e("Command '%c' buffer overflow", commandBuffer[0]); +#endif return 0; } 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 GDBSTUB_ENABLE_DEBUG + m_puts("\r\n"); + debug_i("cmd '%c', len %u", commandBuffer[0], cmdLen); +#endif + if(receivedChecksum == checksum) { // Acknowledge the command gdbSendChar('+'); @@ -423,14 +439,7 @@ static GdbResult ATTR_GDBEXTERNFN handleCommand(unsigned cmdLen) auto regnum = GdbPacket::readHexValue(data); auto regPtr = getSavedReg(regnum); #if GDBSTUB_ENABLE_DEBUG - char regName[16]; - /* - * @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)); + const char* regName = (regPtr == nullptr) ? PSTR("(unsupported)") : registerNames[regnum]; #endif if(commandChar == 'p') { // read @@ -542,14 +551,31 @@ static GdbResult ATTR_GDBEXTERNFN handleCommand(unsigned cmdLen) #if GDBSTUB_ENABLE_SYSCALL /* * A file (or console) I/O request has finished. + * + * This can happen whilst the application is running, or when it has stopped due to + * exception or debug break. + * + * Whilst an I/O request is in progress, GDB will ignore anything we send so if already + * paused we need to call sendReason() again. + * + * If the user hit Ctrl+C then the syscall completes and returns true to tell us this. + * + * Note that GDB expects us to be paused during the entire 'F' request / reply operation, + * but that makes console reading a lot less useful for us so don't actually pause until + * the start of the response packet (indicated by DBGFLAG_PACKET_STARTED). */ case 'F': if(gdb_syscall_complete(data)) { // Ctrl+C was pressed sendReason(); return ST_OK; - } else { + } else if(bitRead(gdb_state.flags, DBGFLAG_PACKET_STARTED)) { + // Paused to deal with packet response, continue running normally return ST_CONT; + } else { + // Paused for some other reason, keep debugging + sendReason(); + return ST_OK; } #endif @@ -618,7 +644,7 @@ static GdbResult ATTR_GDBEXTERNFN handleCommand(unsigned cmdLen) access = 2; // write else if(idx == '3') access = 1; // read - else if(idx == '4') + else // can only be idx == '4' as we've checked above access = 3; // access if(len == 1) mask = 0x3F; @@ -701,6 +727,8 @@ static GdbResult ATTR_GDBEXTERNFN handleCommand(unsigned cmdLen) */ void ATTR_GDBEXTERNFN commandLoop(bool waitForStart, bool allowDetach) { + debug_i(">> ENTER CMDLOOP"); + bool initiallyAttached = gdb_state.attached; while(true) { @@ -727,6 +755,8 @@ void ATTR_GDBEXTERNFN commandLoop(bool waitForStart, bool allowDetach) if(gdb_state.attached != initiallyAttached) { System.queueCallback(TaskCallback(gdb_on_attach), gdb_state.attached); } + + debug_i("<< LEAVE CMDLOOP"); } /* @@ -780,31 +810,14 @@ static void ATTR_GDBEXTERNFN emulLdSt() } } -/** - * @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() { + debug_i(">> DBG 0x%02x, PC = %p", gdb_state.flags, gdbstub_savedRegs.pc); + bool isEnabled = gdb_state.enabled; if(isEnabled) { - pauseHardwareTimer(true); - bitSet(gdb_state.flags, DBGFLAG_DEBUG_EXCEPTION); if(singleStepPs >= 0) { @@ -849,10 +862,6 @@ static void __attribute__((noinline)) gdbstub_handle_debug_exception_flash() } gdb_state.flags = 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 @@ -869,14 +878,12 @@ void gdbstub_handle_exception() return; } - pauseHardwareTimer(true); bitSet(gdb_state.flags, DBGFLAG_SYSTEM_EXCEPTION); sendReason(); commandLoop(true, false); gdb_state.flags = 0; - pauseHardwareTimer(false); } #endif @@ -891,7 +898,6 @@ void ATTR_GDBINIT gdbstub_init() 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); @@ -905,7 +911,7 @@ void ATTR_GDBINIT gdbstub_init() #if GDBSTUB_BREAK_ON_INIT if(gdb_state.enabled) { - gdbstub_do_break(); + gdb_do_break(); } #endif } @@ -931,8 +937,3 @@ void gdb_enable(bool state) { gdb_state.enabled = state; } - -void IRAM_ATTR gdb_do_break() -{ - gdbstub_do_break(); -} diff --git a/Sming/gdb/gdbstub.h b/Sming/gdb/gdbstub.h index 39e07d076c..1048388323 100644 --- a/Sming/gdb/gdbstub.h +++ b/Sming/gdb/gdbstub.h @@ -13,9 +13,6 @@ #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 #include "BitManipulations.h" @@ -29,13 +26,10 @@ #define GDB_PROGMEM PROGMEM #endif -// Break into debugger -#define gdbstub_do_break() asm("break 0,0") - #define gdbstub_break_internal(flag) \ { \ bitSet(gdb_state.flags, flag); \ - asm("break 0,0"); \ + gdb_do_break(); \ } // Additional debugging flags mainly used to qualify reason for a debugging break @@ -67,4 +61,9 @@ extern const uint8_t gdb_exception_signals[]; void gdbstub_init(); void gdbstub_handle_exception(); +#if GDBSTUB_ENABLE_DEBUG == 0 +// Optimise GDB stub code for size, regardless of application settings +#pragma GCC optimize("Os") +#endif + #endif /* _GDB_GDBSTUB_H_ */ diff --git a/Sming/gdb/gdbuart.cpp b/Sming/gdb/gdbuart.cpp index ac424edf49..611e434728 100644 --- a/Sming/gdb/gdbuart.cpp +++ b/Sming/gdb/gdbuart.cpp @@ -312,7 +312,6 @@ static void IRAM_ATTR gdb_uart_callback(uart_t* uart, uint32_t status) // 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 @@ -361,7 +360,7 @@ static void IRAM_ATTR gdb_uart_callback(uart_t* uart, uint32_t status) // 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) { + } else if(rxbuf->getFreeSpace() > size_t(UART_RX_FIFO_SIZE) + user_uart->rx_headroom) { bitClear(user_uart_status, UIFF); } } diff --git a/Sming/gdb/readme.md b/Sming/gdb/readme.md index ea0d4108f5..9e26efe10f 100644 --- a/Sming/gdb/readme.md +++ b/Sming/gdb/readme.md @@ -64,6 +64,19 @@ Useful GDB commands `tui enable` Provides a windowed interface within the console (only seems to work in Linux) +`x/32xw $sp` Display contents of stack + +`info reg` Display register values + +`info break` Display details of currently set breakpoints + +`delete` Delete all breakpoints + +`br` Set a breakpoint at the given address or function name + +`hbr` Set a hardware breakpoint + +`watch` Set a hardware watchpoint to detect when the value of a variable changes These commands require `GDBSTUB_ENABLE_HOSTIO` to be enabled: @@ -88,6 +101,16 @@ Problems 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 + + +GDB System Calls +---------------- + +Applications may interact with GDB directly using system calls, for example reading input from the GDB command prompt. +See the `LiveDebug` sample for a demonstration. + +Note that system calls are disabled in the default configuration, so set `GDBSTUB_ENABLE_SYSCALL=1` to use this feature with your application. + Known Issues and Limitations ---------------------------- @@ -140,3 +163,7 @@ When GDB is running under windows, appears to hang when target reset or restarte GDB (in Windows) doesn't respond at all to Ctrl+C - Cause: Unknown - Solution: Press Ctrl+Break to 'hard kill' GDB + +Debug messages don't appear in Eclipse +- Cause: Unknown +- Solution: Use command-line GDB, or a better visual debugger diff --git a/Sming/gdb/bootrom.elf b/Sming/gdb/symbols/bootrom.elf similarity index 100% rename from Sming/gdb/bootrom.elf rename to Sming/gdb/symbols/bootrom.elf diff --git a/Sming/system/include/gdb_hooks.h b/Sming/system/include/gdb_hooks.h index cfd6082960..6eda397051 100644 --- a/Sming/system/include/gdb_hooks.h +++ b/Sming/system/include/gdb_hooks.h @@ -52,9 +52,14 @@ 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); +#ifdef ENABLE_GDB +#define gdb_do_break() asm("break 0,0") +#else +#define gdb_do_break() \ + do { \ + } while(0) +#endif typedef enum { eGDB_NotPresent, diff --git a/samples/LiveDebug/Makefile-user.mk b/samples/LiveDebug/Makefile-user.mk index b3d02def5d..224faac795 100644 --- a/samples/LiveDebug/Makefile-user.mk +++ b/samples/LiveDebug/Makefile-user.mk @@ -33,3 +33,17 @@ DISABLE_SPIFFS = 1 ENABLE_GDB=1 export ENABLE_GDB + +ENABLE_GDB_CONSOLE ?= 1 + +ifeq ($(ENABLE_GDB_CONSOLE), 1) + USER_CFLAGS += -DGDBSTUB_ENABLE_SYSCALL +endif + +ifeq ($(ENABLE_GDB_CONSOLE), 1) +all: + $(warning WARNING! Enabling the GDB console may interfere with visual debuggers, like eclipse) + $(warning If required, please build with `make ENABLE_GDB_CONSOLE=0`) +endif + +export USER_CFLAGS diff --git a/samples/LiveDebug/README.md b/samples/LiveDebug/README.md index b9971b67b6..50578c4d3c 100644 --- a/samples/LiveDebug/README.md +++ b/samples/LiveDebug/README.md @@ -1,4 +1,36 @@ +LiveDebug +========= + This project is an example of how to integrate GDB debugging into your project. +It provides a basic command interface which you can use via regular serial terminal or with the GDB application. + +To use this sample application with the command-line GDB application, simply build and flash the project as usual: + +```bash +make clean +make flash +``` + +You should be presented with the GDB command prompt. Enter 'c' to continue running the application. + + (gdb) c + Continuing. + (attached) + +The `(attached)` prompt is displayed by the LiveDebug application. Type `help` to get a list of available commands. + +Note that if you run this application via serial terminal (`make terminal`) you'll get the `(Detached)` prompt instead. + +2. Debugging under eclipse + +Interacting with the GDB console causes problems for eclipse, so compile with the `ENABLE_GDB_CONSOLE=0` option: + +```bash +make clean +make flash ENABLE_CONSOLE=0 +``` + +Alternatively, use the `consoleOff` command from the GDB command prompt, then quit the debugger and connect via eclipse. Exception Handling ------------------ @@ -58,11 +90,10 @@ But that information might not be enough. And finding the root cause may take qu 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. -Here are the commands that you need to execute: +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. -1. (Re)compile your project with the `ENABLE_GDB` option and flash it to the board. +To use, (Re)compile your project with the `ENABLE_GDB` option and flash it to the board. ```bash make clean @@ -70,14 +101,15 @@ make ENABLE_GDB=1 make flash ``` -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: +Instead of a terminal, the GDB console will be opened automatically. + +If you need to run GDB manually after resetting the board or after it has run into an exception, use the provided script: ```bash make gdb ``` -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. +Note that 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 GDB stub [Notes](https://github.com/SmingHub/Sming/tree/develop/Sming/gdb/readme.md) for more information. diff --git a/samples/LiveDebug/app/application.cpp b/samples/LiveDebug/app/application.cpp index 3810ce829e..94389ce0cc 100644 --- a/samples/LiveDebug/app/application.cpp +++ b/samples/LiveDebug/app/application.cpp @@ -41,18 +41,31 @@ Timer procTimer; #define CALLBACK_ATTR GDB_IRAM_ATTR #endif -bool state = true; +// See blink() +bool ledState = true; + +// A simple log file stored on the host static GdbFileStream logFile; +#define LOG_FILENAME "testlog.txt" + +// Handles messages from SDK static OsMessageInterceptor osMessageInterceptor; +// Supports `consoleOff` command to prevent re-enabling when debugger is attached +bool consoleOffRequested = false; + +// Forward declarations +bool handleCommand(const String& cmd); +void readConsole(); + /* * 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 CALLBACK_ATTR blink() { - digitalWrite(LED_PIN, state); - state = !state; + digitalWrite(LED_PIN, ledState); + ledState = !ledState; } void showPrompt() @@ -70,11 +83,11 @@ void showPrompt() } } -bool handleCommand(const String& cmd); - void onDataReceived(Stream& source, char arrivedChar, unsigned short availableCharsCount) { - static LineBuffer line; + static unsigned commandLength; + const unsigned MAX_COMMAND_LENGTH = 16; + static char commandBuffer[MAX_COMMAND_LENGTH + 1]; // Error detection unsigned status = Serial.getStatus(); @@ -94,32 +107,37 @@ void onDataReceived(Stream& source, char arrivedChar, unsigned short availableCh } // Discard what is likely to be garbage Serial.clear(SERIAL_RX_ONLY); - line.clear(); + commandLength = 0; showPrompt(); return; } int c; while((c = Serial.read()) >= 0) { - if(c == '\b' || c == 0x7f) { // delete (backspace) or xterm ctrl-? - if(line.backspace()) { + switch(c) { + case '\b': // delete (backspace) + case 0x7f: // xterm ctrl-? + if(commandLength > 0) { + --commandLength; Serial.print(_F("\b \b")); } - continue; - } - - c = line.addChar(c); - if(c == '\n') { - if(line.getLength() != 0) { + break; + case '\r': + case '\n': + if(commandLength > 0) { Serial.println(); - String cmd(line.getBuffer(), line.getLength()); - line.clear(); + String cmd(commandBuffer, commandLength); + commandLength = 0; Serial.clear(SERIAL_RX_ONLY); handleCommand(cmd); } showPrompt(); - } else if(c != '\0') { - Serial.print(char(c)); + break; + default: + if(c >= 0x20 && c <= 0x7f && commandLength < MAX_COMMAND_LENGTH) { + commandBuffer[commandLength++] = c; + Serial.print(char(c)); + } } } } @@ -153,9 +171,6 @@ void readFile(const char* filename, bool display) } } -// Forward declaration -void readConsole(); - /* * 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 @@ -258,20 +273,37 @@ void fileStat(const char* filename) * 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(readfile1, "Use syscall file I/O functions to read and display a host file\n" \ + "Calls are blocking so the application is paused during the entire operation") \ + XX(readfile2, "Read a larger host file asynchronously\n" \ + "Data is processed in a callback function to avoid pausing the application un-necessarily") \ 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(hang, "Enter infinite loop to force a watchdog timeout") \ - XX(read0, "Read from invalid address") \ - XX(write0, "Write to invalid address") \ + XX(log, "Show state of log file\n" \ + "The log file is written to \"" LOG_FILENAME "\" in the current working directory") \ + XX(break, "Demonstrate `gdb_do_break()` function to pause this application and obtain a GDB command prompt\n" \ + "Similar to Ctrl+C except we control exactly where the application stops") \ + XX(queueBreak, "Demonstrate `gdb_do_break()` function called via task queue\n" \ + "If you run `bt` you'll see a much smaller stack trace") \ + XX(consoleOff, "Break into debugger and stop reading from console\n" \ + "Do this if you need to set live breakpoints or observe debug output,\n" \ + "as both are blocked during console reading") \ + XX(hang, "Enter infinite loop to force a watchdog timeout\n" \ + "Tests the crash handler which should display a message,\n" \ + "then break into the debugger, if available") \ + XX(read0, "Read from invalid address\n" \ + "Attempting to read from address #0 will trigger a LOAD_PROHIBITED exception") \ + XX(write0, "Write to invalid address\n" \ + "Attempting to write to address #0 will trigger a STORE_PROHIBITED exception") \ XX(malloc0, "Call malloc(0)") \ XX(freetwice, "Free allocated memory twice") \ - XX(restart, "Restart the system") \ - XX(exit, "Detach from GDB and resume normal application execution") \ + XX(restart, "Restart the system\n" \ + "GDB should reconnect automatically, but if not run from a terminal.\n" \ + "Windows versions of GDB don't handle serial control lines well,\n" \ + "so a nodeMCU, for example, may restart in the wrong mode") \ + XX(disconnect, "Terminates the connection between the debugger and the remote debug target\n" \ + "Calls gdb_detach() - the application will resume normal operation") \ XX(help, "Display this command summary") /* @@ -322,6 +354,7 @@ COMMAND_HANDLER(log) } else { Serial.println(_F("Log file not available")); } + return true; } COMMAND_HANDLER(ls) @@ -338,6 +371,22 @@ COMMAND_HANDLER(break) return true; } +COMMAND_HANDLER(queueBreak) +{ + Serial.println(_F("Queuing a call to gdb_do_break()\r\n" + "This differs from `break` in that a console read will be in progress when the break is called")); + System.queueCallback(TaskCallback(handleCommand_break)); + return true; +} + +COMMAND_HANDLER(consoleOff) +{ + Serial.println(_F("To re-enable console reading, enter `call readConsole()` from GDB prompt")); + gdb_do_break(); + consoleOffRequested = true; + return false; +} + COMMAND_HANDLER(hang) { Serial.println(_F("Entering infinite loop...")); @@ -436,7 +485,7 @@ COMMAND_HANDLER(restart) return false; } -COMMAND_HANDLER(exit) +COMMAND_HANDLER(disconnect) { // End console test Serial.print(_F("Calling gdb_detach() - ")); @@ -455,9 +504,40 @@ COMMAND_HANDLER(exit) COMMAND_HANDLER(help) { Serial.print(_F("LiveDebug interactive debugger sample. Available commands:\r\n")); -#define XX(tag, desc) Serial.println(_F(" " #tag " : " desc)); + + auto print = [](const char* tag, const char* desc) { + const unsigned indent = 10; + Serial.print(" "); + String s(tag); + s.reserve(indent); + while(s.length() < indent) { + s += ' '; + } + Serial.print(s); + Serial.print(" : "); + + // Print multi-line descriptions in sections to maintain correct line indentation + s.setLength(2 + indent + 3); + memset(s.begin(), ' ', s.length()); + + for(;;) { + auto end = strchr(desc, '\n'); + if(end == nullptr) { + Serial.println(desc); + break; + } else { + Serial.write(desc, end - desc); + Serial.println(); + desc = end + 1; + Serial.print(s); + } + } + }; + +#define XX(tag, desc) print(_F(#tag), _F(desc)); COMMAND_MAP(XX) #undef XX + return true; } /** @@ -523,30 +603,35 @@ void onConsoleReadCompleted(const GdbSyscallInfo& info) /* * Demonstrate GDB console access. + * We actually queue this so it can be called directly from GDB to re-enable console reading + * after using the `consoleOff` command. */ void readConsole() { - showPrompt(); - if(gdb_present() == eGDB_Attached) { - // Issue the syscall - 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 ?")); - showPrompt(); - } + consoleOffRequested = false; + System.queueCallback([](uint32_t) { + showPrompt(); + if(gdb_present() == eGDB_Attached) { + // Issue the syscall + 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 ?")); + showPrompt(); + } - /* - * 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. + */ + } else { + /* + * GDB is either detached or not present, serial callback will process input + */ + } + }); } extern "C" void gdb_on_attach(bool attached) @@ -554,7 +639,7 @@ 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); + logFile.open(F(LOG_FILENAME), eFO_WriteOnly | eFO_CreateIfNotExist); debug_i("open log %d", logFile.getLastError()); logFile.println(); @@ -564,7 +649,9 @@ extern "C" void gdb_on_attach(bool attached) logFile.println(DateTime(tv.tv_sec).toFullDateTimeString()); // Start interacting with GDB - readConsole(); + if(!consoleOffRequested) { + readConsole(); + } } else { // Note: GDB is already detached so underlying call to gdb_syscall_close() will fail silently logFile.close();