/****************************************************************************** * Copyright 2015 Espressif Systems * * Description: A stub to make the ESP8266 debuggable by GDB over the serial * port. * * License: ESPRESSIF MIT License *******************************************************************************/ #include #include #include #include "ets_sys.h" #include "eagle_soc.h" #include "c_types.h" #include "gpio.h" #include "xtensa/corebits.h" #include "uart_register.h" #include "gdbstub-entry.h" #include "gdbstub-cfg.h" //From xtruntime-frames.h struct XTensa_exception_frame_s { uint32_t pc; uint32_t ps; uint32_t sar; uint32_t vpri; uint32_t a[16]; //a0..a15 //These are added manually by the exception code; the HAL doesn't set these on an exception. uint32_t litbase; uint32_t sr176; uint32_t sr208; //'reason' is abused for both the debug and the exception vector: if bit 7 is set, //this contains an exception reason, otherwise it contains a debug vector bitmap. uint32_t reason; }; #if GDBSTUB_FREERTOS struct XTensa_rtos_int_frame_s { uint32_t exitPtr; uint32_t pc; uint32_t ps; uint32_t a[16]; uint32_t sar; }; /* Definitions for FreeRTOS. This redefines some os_* functions to use their non-os* counterparts. It also sets up some function pointers for ROM functions that aren't in the FreeRTOS ld files. */ #include #include void os_isr_attach(int inum, void *fn); void os_install_putc1(void (*p)(char c)); #define os_printf(...) printf(__VA_ARGS__) #define os_strncmp(...) strncmp(__VA_ARGS__) typedef void wdtfntype(); static wdtfntype *ets_wdt_disable = (wdtfntype *)0x400030f0; static wdtfntype *ets_wdt_enable = (wdtfntype *)0x40002fa0; #else /* OS-less SDK defines. Defines some headers for things that aren't in the include files, plus the xthal stack frame struct. */ #include "osapi.h" #include "user_interface.h" void _xtos_set_exception_handler(int cause, void (exhandler)(struct XTensa_exception_frame_s *frame)); #endif #define EXCEPTION_GDB_SP_OFFSET 0x100 //Length of buffer used to reserve GDB commands. Has to be at least able to fit the G command, which //implies a minimum size of about 190 bytes. #define PBUFLEN 256 //The asm stub saves the Xtensa registers here when a debugging exception happens. struct XTensa_exception_frame_s gdbstub_savedRegs; #if GDBSTUB_USE_OWN_STACK //This is the debugging exception stack. int exceptionStack[256]; #endif static bool gdb_attached = false; static unsigned char cmd[PBUFLEN]; //GDB command input buffer static char chsum; //Running checksum of the output packet #if GDBSTUB_CTRLC_BREAK static void (*uart_isr_callback)(void*, uint8_t) = NULL; static void* uart_isr_arg = NULL; #endif #if GDBSTUB_REDIRECT_CONSOLE_OUTPUT static void (*uart_putc1_callback)(char) = NULL; #endif //Stores ps when single-stepping instruction. -1 when not in use. static int32_t singleStepPs = -1; //Uart libs can reference these to see if gdb is attaching to them bool gdbstub_has_putc1_control() { #if GDBSTUB_REDIRECT_CONSOLE_OUTPUT return true; #else return false; #endif } bool gdbstub_has_uart_isr_control() { #if GDBSTUB_CTRLC_BREAK return true; #else return false; #endif } //Small function to feed the hardware watchdog. Needed to stop the ESP from resetting //due to a watchdog timeout while reading a command. static void ATTR_GDBFN keepWDTalive() { uint64_t *wdtval = (uint64_t*)0x3ff21048; uint64_t *wdtovf = (uint64_t*)0x3ff210cc; int *wdtctl = (int*)0x3ff210c8; *wdtovf = *wdtval + 1600000; *wdtctl |= 1 << 31; } //Error states used by the routines that grab stuff from the incoming gdb packet #define ST_ENDPACKET -1 #define ST_ERR -2 #define ST_OK -3 #define ST_CONT -4 #define ST_DETACH -5 //Grab a hex value from the gdb packet. Ptr will get positioned on the end //of the hex string, as far as the routine has read into it. Bits/4 indicates //the max amount of hex chars it gobbles up. Bits can be -1 to eat up as much //hex chars as possible. static long gdbGetHexVal(unsigned char **ptr, int bits) { int i; int no; unsigned int v = 0; char c; no = bits / 4; if (bits == -1) no = 64; for (i = 0; i < no; i++) { c = **ptr; (*ptr)++; if (c >= '0' && c <= '9') { v <<= 4; v |= (c-'0'); } else if (c >= 'A' && c <= 'F') { v <<= 4; v |= (c-'A') + 10; } else if (c >= 'a' && c <= 'f') { v <<= 4; v |= (c-'a') + 10; } else if (c == '#') { if (bits == -1) { (*ptr)--; return v; } return ST_ENDPACKET; } else { if (bits == -1) { (*ptr)--; return v; } return ST_ERR; } } return v; } //Swap an int into the form gdb wants it static int iswap(int i) { return ((i >> 24) & 0xff) | (((i >> 16) & 0xff) << 8) | (((i >> 8) & 0xff) << 16) | (((i >> 0) & 0xff) << 24); } //Read a byte from the ESP8266 memory. static unsigned char readbyte(unsigned int p) { if (p < 0x20000000 || p >= 0x60000000) return -1; int *i = (int*)(p & ~3); return *i >> ((p & 3) * 8); } //Write a byte to the ESP8266 memory. static void writeByte(unsigned int p, unsigned char d) { if (p < 0x20000000 || p >= 0x60000000) return; int *i = (int*)(p & ~3); if ((p & 3) == 0) *i = (*i & 0xffffff00) | (d << 0); else if ((p & 3) == 1) *i = (*i & 0xffff00ff) | (d << 8); else if ((p & 3) == 2) *i = (*i & 0xff00ffff) | (d << 16); else if ((p & 3) == 3) *i = (*i & 0x00ffffff) | (d << 24); } //Returns 1 if it makes sense to write to addr p static int validWrAddr(int p) { return (p >= 0x3ff00000 && p < 0x40000000) || (p >= 0x40100000 && p < 0x40140000) || (p >= 0x60000000 && p < 0x60002000); } static inline bool ATTR_GDBFN gdbRxFifoIsEmpty() { return ((READ_PERI_REG(UART_STATUS(0)) >> UART_RXFIFO_CNT_S) & UART_RXFIFO_CNT) == 0; } static inline bool ATTR_GDBFN gdbTxFifoIsFull() { return ((READ_PERI_REG(UART_STATUS(0)) >> UART_TXFIFO_CNT_S) & UART_TXFIFO_CNT) >= 126; } //Receive a char from the uart. Uses polling and feeds the watchdog. static inline int gdbRecvChar() { while (gdbRxFifoIsEmpty()) { keepWDTalive(); } return READ_PERI_REG(UART_FIFO(0)); } //Send a char to the uart. static void gdbSendChar(char c) { while (gdbTxFifoIsFull()) ; WRITE_PERI_REG(UART_FIFO(0), c); } //Send the start of a packet; reset checksum calculation. static void gdbPacketStart() { chsum = 0; gdbSendChar('$'); } //Send a char as part of a packet static void gdbPacketChar(char c) { if (c == '#' || c == '$' || c == '}' || c == '*') { gdbSendChar('}'); chsum += '}'; c ^= 0x20; } gdbSendChar(c); chsum += c; } //Send a hex val as part of a packet. 'bits'/4 dictates the number of hex chars sent. static void gdbPacketHex(int val, int bits) { static const char hexChars[] = "0123456789abcdef"; int i; for (i = bits; i > 0; i -= 4) { gdbPacketChar(hexChars[(val >> (i - 4)) & 0xf]); } } //Send a hex val as part of a packet. 'bits'/4 dictates the number of hex chars sent. static void gdbPacketSwappedHexInt(int val) { gdbPacketHex(iswap(val), 32); } static void gdbPacketXXXXInt() { for (int i=0; i<8; i++) gdbPacketChar('x'); } //Finish sending a packet. static void gdbPacketEnd() { gdbSendChar('#'); //Ok to use packet version here since hex char can never be an //excape-requiring character gdbPacketHex(chsum, 8); } // Send a complete packet containing str static void gdbSendPacketStr(const char *c) { gdbPacketStart(); while (*c != 0) { gdbPacketChar(*c); c++; } gdbPacketEnd(); } // Send a complete packet containing str as an output message static inline void ATTR_GDBEXTERNFN gdbSendOutputPacketStr(const unsigned char* buf, size_t size) { size_t i; gdbPacketStart(); gdbPacketChar('O'); for (i = 0; i < size; i++) gdbPacketHex(buf[i], 8); gdbPacketEnd(); } // Send a complete packet containing c as an output message static inline void ATTR_GDBEXTERNFN gdbSendOutputPacketChar(unsigned char c) { gdbPacketStart(); gdbPacketChar('O'); gdbPacketHex(c, 8); gdbPacketEnd(); } static long gdbGetSwappedHexInt(unsigned char **ptr) { return iswap(gdbGetHexVal(ptr, 32)); } //Send the reason execution is stopped to GDB. static void sendReason() { static const char exceptionSignal[] = {4,31,11,11,2,6,8,0,6,7,0,0,7,7,7,7}; #if 0 char *reason=""; //default #endif //exception-to-signal mapping size_t i; gdbPacketStart(); gdbPacketChar('T'); if (gdbstub_savedRegs.reason == 0xff) { gdbPacketHex(2, 8); //sigint } else if (gdbstub_savedRegs.reason & 0x80) { //We stopped because of an exception. Convert exception code to a signal number and send it. i = gdbstub_savedRegs.reason & 0x7f; if (i < sizeof(exceptionSignal)) gdbPacketHex(exceptionSignal[i], 8); else gdbPacketHex(11, 8); } else { //We stopped because of a debugging exception. gdbPacketHex(5, 8); //sigtrap //Current Xtensa GDB versions don't seem to request this, so let's leave it off. #if 0 if (gdbstub_savedRegs.reason&(1<<0)) reason="break"; if (gdbstub_savedRegs.reason&(1<<1)) reason="hwbreak"; if (gdbstub_savedRegs.reason&(1<<2)) reason="watch"; if (gdbstub_savedRegs.reason&(1<<3)) reason="swbreak"; if (gdbstub_savedRegs.reason&(1<<4)) reason="swbreak"; gdbPacketStr(reason); gdbPacketChar(':'); //ToDo: watch: send address #endif } gdbPacketEnd(); } static inline void ATTR_GDBFN gdbSendPacketOK() { gdbSendPacketStr("OK"); } static inline void ATTR_GDBFN gdbSendPacketE01() { gdbSendPacketStr("E01"); } static inline void ATTR_GDBFN gdbSendEmptyPacket() { gdbPacketStart(); gdbPacketEnd(); } void ATTR_GDBEXTERNFN gdbstub_write_char(char c) { if (gdb_attached) { ETS_UART_INTR_DISABLE(); gdbSendOutputPacketChar(c); ETS_UART_INTR_ENABLE(); } else { gdbSendChar(c); } } void ATTR_GDBEXTERNFN gdbstub_write(const char* buf, size_t size) { size_t i; if (gdb_attached) { ETS_UART_INTR_DISABLE(); gdbSendOutputPacketStr((const unsigned char *)buf, size); ETS_UART_INTR_ENABLE(); } else { for (i = 0; i < size; i++) { gdbSendChar(buf[i]); } } } /* Register file in the format lx106 gdb port expects it. Inspired by gdb/regformats/reg-xtensa.dat from https://github.com/jcmvbkbc/crosstool-NG/blob/lx106-g%2B%2B/overlays/xtensa_lx106.tar As decoded by Cesanta. struct regfile { uint32_t a[16]; uint32_t pc; uint32_t sar; uint32_t litbase; uint32_t sr176; uint32_t sr208; uint32_t ps; }; */ //Handle a command as received from GDB. static inline int gdbHandleCommand() { //Handle a command int i, j, k; unsigned char *data = cmd + 1; if (cmd[0]=='g') { //send all registers to gdb gdbPacketStart(); gdbPacketSwappedHexInt(gdbstub_savedRegs.pc); for (int i=1; i<=35; i++) gdbPacketXXXXInt(); gdbPacketSwappedHexInt(gdbstub_savedRegs.sar); gdbPacketSwappedHexInt(gdbstub_savedRegs.litbase); for (int i=38; i<=39; i++) gdbPacketXXXXInt(); gdbPacketSwappedHexInt(gdbstub_savedRegs.sr176); for (int i=41; i<=41; i++) gdbPacketXXXXInt(); gdbPacketSwappedHexInt(gdbstub_savedRegs.ps); for (int i=43; i<=96; i++) gdbPacketXXXXInt(); for (i=0; i<16; i++) gdbPacketSwappedHexInt(gdbstub_savedRegs.a[i]); gdbPacketEnd(); } else if (cmd[0]=='G') { //receive content for all registers from gdb gdbstub_savedRegs.pc=gdbGetSwappedHexInt(&data); for (int i=1; i<=35; i++) gdbGetHexVal(&data, 32); gdbstub_savedRegs.sar=gdbGetSwappedHexInt(&data); gdbstub_savedRegs.litbase=gdbGetSwappedHexInt(&data); for (int i=38; i<=39; i++) gdbGetHexVal(&data, 32); gdbstub_savedRegs.sr176=gdbGetSwappedHexInt(&data); for (int i=41; i<=41; i++) gdbGetHexVal(&data, 32); gdbstub_savedRegs.ps=gdbGetSwappedHexInt(&data); for (int i=43; i<=96; i++) gdbGetHexVal(&data, 32); for (i=0; i<16; i++) gdbstub_savedRegs.a[i]=gdbGetSwappedHexInt(&data); gdbSendPacketOK(); } else if ((cmd[0] | 0x20) == 'm') { //read/write memory to gdb i = gdbGetHexVal(&data, -1); //addr data++; j = gdbGetHexVal(&data, -1); //length if (cmd[0] == 'm') { //read memory to gdb gdbPacketStart(); for (k = 0; k < j; k++) { gdbPacketHex(readbyte(i++), 8); } gdbPacketEnd(); } else { //write memory from gdb if (validWrAddr(i) && validWrAddr(i + j)) { data++; //skip : for (k = 0; k < j; k++, i++) { writeByte(i, gdbGetHexVal(&data, 8)); } //Make sure caches are up-to-date. Procedure according to Xtensa ISA document, ISYNC inst desc. asm volatile("ISYNC\nISYNC\n"); gdbSendPacketOK(); } else { //Trying to do a software breakpoint on a flash proc, perhaps? gdbSendPacketE01(); } } } else if (cmd[0] == '?') { //Reply with stop reason sendReason(); } else if (cmd[0] == 'c') { //continue execution return ST_CONT; } else if (cmd[0] == 's') { //single-step instruction //Single-stepping can go wrong if an interrupt is pending, especially when it is e.g. a task switch: //the ICOUNT register will overflow in the task switch code. That is why we disable interupts when //doing single-instruction stepping. singleStepPs=gdbstub_savedRegs.ps; gdbstub_savedRegs.ps=(gdbstub_savedRegs.ps & ~0xf) | (XCHAL_DEBUGLEVEL - 1); gdbstub_icount_ena_single_step(); return ST_CONT; } else if (cmd[0] == 'D') { //detach gdbSendPacketOK(); return ST_DETACH; } else if (cmd[0] == 'k') { //kill system_restart_core(); } else if (cmd[0] == 'q') { //Extended query if (os_strncmp((char*)&cmd[1], "Supported", 9) == 0) { //Capabilities query gdbSendPacketStr("swbreak+;hwbreak+;PacketSize=FF"); //PacketSize is in hex } else if (os_strncmp((char*)&cmd[1], "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 gdbSendPacketStr("1"); } else { //We don't support other queries. gdbSendEmptyPacket(); } // case insensitive compare matches 'Z' or 'z' } else if ((cmd[0] | 0x20) == 'z' && cmd[1] >= '1' && cmd[2] <= '4') { //hardware break/watchpoint int result; data += 2; //skip 'x,' i = gdbGetHexVal(&data, -1); data++; //skip ',' j = gdbGetHexVal(&data, -1); if (cmd[0] == 'Z') { //Set hardware break/watchpoint if (cmd[1] == '1') { //Set breakpoint result = gdbstub_set_hw_breakpoint(i, j); } else { //Set watchpoint int access; unsigned int mask = 0; if (cmd[1] == '2') access = 2; //write if (cmd[1] == '3') access = 1; //read if (cmd[1] == '4') access = 3; //access if (j == 1) mask = 0x3F; if (j == 2) mask = 0x3E; if (j == 4) mask = 0x3C; if (j == 8) mask = 0x38; if (j == 16) mask = 0x30; if (j == 32) mask = 0x20; result = mask != 0 && gdbstub_set_hw_watchpoint(i, mask, access); } } else { //Clear hardware break/watchpoint if (cmd[1] == '1') { //hardware breakpoint result = gdbstub_del_hw_breakpoint(i); } else { //hardware watchpoint result = gdbstub_del_hw_watchpoint(i); } } if (result) { gdbSendPacketOK(); } else { gdbSendPacketE01(); } } else { //We don't recognize or support whatever GDB just sent us. gdbSendEmptyPacket(); } return ST_OK; } //Lower layer: grab a command packet and check the checksum //Calls gdbHandleCommand on the packet if the checksum is OK //Returns only if execution of the user program should continue //Otherwise keeps reading uart data and executing commands //Flags that gdb has been attached whenever a gdb formatted // packet is received //While gdb is attached, checks for ctl-c (\x03) if it's not // already paused //Keeps reading commands if it is paused, 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 static int gdbReadCommand() { unsigned char chsum; unsigned char sentchs[2]; size_t p; unsigned char c; unsigned char *ptr; int result; ETS_UART_INTR_DISABLE(); ets_wdt_disable(); sendReason(); while (true) { gdbReadCommand_start: while (gdbRecvChar() != '$') ; gdbReadCommand_packetBegin: chsum = 0; p = 0; while ((c = gdbRecvChar()) != '#') { //end of packet, checksum follows if (c == '$') { //Wut, restart packet? goto gdbReadCommand_packetBegin; } if (c == '}') { //escape the next char c = gdbRecvChar() ^ 0x20; } chsum += c; cmd[p++] = c; if (p >= PBUFLEN) { //Received more than the size of the command buffer goto gdbReadCommand_start; } } cmd[p] = 0; sentchs[0] = gdbRecvChar(); sentchs[1] = gdbRecvChar(); ptr = &sentchs[0]; if (gdbGetHexVal(&ptr, 8) == chsum) { gdb_attached = true; gdbSendChar('+'); result = gdbHandleCommand(); if (result != ST_OK) { break; } } else { gdbSendChar('-'); } } if (result == ST_DETACH) { gdb_attached = false; } ets_wdt_enable(); ETS_UART_INTR_ENABLE(); return result; } //Get the value of one of the A registers static unsigned int ATTR_GDBFN getaregval(int reg) { return gdbstub_savedRegs.a[reg]; } //Set the value of one of the A registers static inline void ATTR_GDBFN setaregval(int reg, unsigned int val) { // os_printf("%x -> %x\n", val, reg); gdbstub_savedRegs.a[reg] = val; } //Emulate the l32i/s32i instruction we're stopped at. static inline void emulLdSt() { unsigned char i0 = readbyte(gdbstub_savedRegs.pc); unsigned char i1 = readbyte(gdbstub_savedRegs.pc + 1); unsigned char i2; int *p; if ((i0 & 0xf) == 2 && (i1 & 0xb0) == 0x20) { //l32i or s32i i2 = readbyte(gdbstub_savedRegs.pc + 2); p = (int*)getaregval(i1 & 0xf) + (i2 * 4); i0 >>= 4; if ((i1 & 0xf0) == 0x20) { //l32i setaregval(i0, *p); } else { //s32i *p = getaregval(i0); } gdbstub_savedRegs.pc += 3; } else if ((i0 & 0xe) == 0x8) { //l32i.n or s32i.n p = (int*)getaregval(i1 & 0xf) + ((i1 >> 4) * 4); if ((i0 & 0xf) == 0x8) { //l32i.n setaregval(i0 >> 4, *p); } else { *p = getaregval(i0 >> 4); } gdbstub_savedRegs.pc += 2; // } else { // os_printf("GDBSTUB: No l32i/s32i instruction: %x %x. Huh?", i1, i0); } } //We just caught a debug exception and need to handle it. This is called from an assembly //routine in gdbstub-entry.S static void gdbstub_handle_debug_exception_flash(); void ATTR_GDBFN gdbstub_handle_debug_exception() { Cache_Read_Enable_New(); gdbstub_handle_debug_exception_flash(); } static void __attribute__((noinline)) gdbstub_handle_debug_exception_flash() { if (singleStepPs != -1) { //We come here after single-stepping an instruction. Interrupts are disabled //for the single step. Re-enable them here. gdbstub_savedRegs.ps = (gdbstub_savedRegs.ps & ~0xf) | (singleStepPs & 0xf); singleStepPs =- 1; } gdbReadCommand(); if ((gdbstub_savedRegs.reason & 0x80) == 0) { //Watchpoint/BREAK/BREAK.N if ((gdbstub_savedRegs.reason & 0x4) != 0) { //We stopped due to a watchpoint. We can't re-execute the current instruction //because it will happily re-trigger the same watchpoint, so we emulate it //while we're still in debugger space. emulLdSt(); } else if ((((gdbstub_savedRegs.reason & 0x8) != 0) //We stopped due to a BREAK instruction. Skip over it. //Check the instruction first; gdb may have replaced it with the original instruction //if it's one of the breakpoints it set. && readbyte(gdbstub_savedRegs.pc + 2) == 0 && (readbyte(gdbstub_savedRegs.pc + 1) & 0xf0) == 0x40 && (readbyte(gdbstub_savedRegs.pc) & 0x0f) == 0x00) || (((gdbstub_savedRegs.reason & 0x10) != 0) //We stopped due to a BREAK.N instruction. Skip over it, after making sure the instruction //actually is a BREAK.N && (readbyte(gdbstub_savedRegs.pc + 1) & 0xf0) == 0xf0 && readbyte(gdbstub_savedRegs.pc) == 0x2d)) { gdbstub_savedRegs.pc += 3; } } } #if GDBSTUB_BREAK_ON_EXCEPTION || GDBSTUB_CTRLC_BREAK #if !GDBSTUB_FREERTOS static inline int gdbReadCommandWithFrame(void* frame) { //Copy registers the Xtensa HAL did save to gdbstub_savedRegs os_memcpy(&gdbstub_savedRegs, frame, 5 * 4); os_memcpy(&gdbstub_savedRegs.a[2], ((uint32_t*)frame) + 5, 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. gdbstub_savedRegs.a[1] = (uint32_t)frame + EXCEPTION_GDB_SP_OFFSET; int result = gdbReadCommand(); //Copy any changed registers back to the frame the Xtensa HAL uses. os_memcpy(frame, &gdbstub_savedRegs, 5 * 4); os_memcpy(((uint32_t*)frame) + 5, &gdbstub_savedRegs.a[2], 14 * 4); return result; } #endif #endif #if GDBSTUB_BREAK_ON_EXCEPTION #if GDBSTUB_FREERTOS //Freertos exception. This routine is called by an assembly routine in gdbstub-entry.S void ATTR_GDBFN gdbstub_handle_user_exception() { gdbstub_savedRegs.reason |= 0x80; //mark as an exception reason while (gdbReadCommand() != ST_CONT) ; } //FreeRTOS doesn't use the Xtensa HAL for exceptions, but uses its own fatal exception handler. //We use a small hack to replace that with a jump to our own handler, which then has the task of //decyphering and re-instating the registers the FreeRTOS code left. extern void user_fatal_exception_handler(); extern void gdbstub_user_exception_entry(); static void ATTR_GDBINIT install_exceptions() { //Replace the user_fatal_exception_handler by a jump to our own code int *ufe = (int*)user_fatal_exception_handler; //This mess encodes as a relative jump instruction to user_fatal_exception_handler *ufe = ((((int)gdbstub_user_exception_entry - (int)user_fatal_exception_handler) - 4) << 6) | 6; } #else //Non-OS exception handler. Gets called by the Xtensa HAL. static void gdbstub_exception_handler_flash(struct XTensa_exception_frame_s *frame); static void ATTR_GDBFN gdbstub_exception_handler(struct XTensa_exception_frame_s *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); } static void __attribute__((noinline)) gdbstub_exception_handler_flash(struct XTensa_exception_frame_s *frame) { gdbstub_savedRegs.reason |= 0x80; //mark as an exception reason while (gdbReadCommandWithFrame((void*)frame) != ST_CONT) ; } //The OS-less SDK uses the Xtensa HAL to handle exceptions. We can use those functions to catch any //fatal exceptions and invoke the debugger when this happens. static void ATTR_GDBINIT install_exceptions() { static 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}; unsigned int i; for (i = 0; i < (sizeof(exno) / sizeof(exno[0])); i++) { _xtos_set_exception_handler(exno[i], gdbstub_exception_handler); } } #endif #endif #if GDBSTUB_REDIRECT_CONSOLE_OUTPUT //Replacement putchar1 routine. Instead of spitting out the character directly, it will buffer up to //OBUFLEN characters (or up to a \n, whichever comes earlier) and send it out as a gdb stdout packet. static void ATTR_GDBEXTERNFN gdbstub_semihost_putchar1(char c) { if (!gdb_attached && uart_putc1_callback != NULL) { uart_putc1_callback(c); } else { gdbstub_write_char(c); } } void ATTR_GDBINIT gdbstub_set_putc1_callback(void (*func)(char)) { uart_putc1_callback = func; } #endif #if GDBSTUB_FREERTOS static void ATTR_GDBINIT configure_uart() {} #else static void ATTR_GDBINIT configure_uart() { #ifdef ARDUINO // Set the UART input/output pins to TX=1, RX=3 pinMode(3, SPECIAL); pinMode(1, FUNCTION_0); #endif WRITE_PERI_REG(UART_CONF0(0), 0b00011100); //8N1 SET_PERI_REG_MASK(UART_CONF0(0), UART_RXFIFO_RST | UART_TXFIFO_RST); //RESET FIFO CLEAR_PERI_REG_MASK(UART_CONF0(0), UART_RXFIFO_RST | UART_TXFIFO_RST); } #endif #if GDBSTUB_CTRLC_BREAK #if GDBSTUB_FREERTOS void ATTR_GDBFN gdbstub_handle_uart_int(struct XTensa_rtos_int_frame_s *frame) { int doDebug = 0, fifolen, x; fifolen = (READ_PERI_REG(UART_STATUS(0)) >> UART_RXFIFO_CNT_S)&UART_RXFIFO_CNT; while (fifolen != 0) { //Check if any of the chars is control-C. Throw away rest. if ((READ_PERI_REG(UART_FIFO(0)) & 0xFF) == 0x3) doDebug = 1; fifolen--; } WRITE_PERI_REG(UART_INT_CLR(0), UART_RXFIFO_FULL_INT_CLR | UART_RXFIFO_TOUT_INT_CLR); if (doDebug) { //Copy registers the Xtensa HAL did save to gdbstub_savedRegs gdbstub_savedRegs.pc = frame->pc; gdbstub_savedRegs.ps = frame->ps; gdbstub_savedRegs.sar = frame->sar; for (x = 0; x < 16; x++) gdbstub_savedRegs.a[x] = frame->a[x]; // gdbstub_savedRegs.a1=(uint32_t)frame+EXCEPTION_GDB_SP_OFFSET; gdbstub_savedRegs.reason = 0xff; //mark as user break reason gdbReadCommand(); //Copy any changed registers back to the frame the Xtensa HAL uses. frame->pc = gdbstub_savedRegs.pc; frame->ps = gdbstub_savedRegs.ps; frame->sar = gdbstub_savedRegs.sar; for (x = 0; x < 16; x++) frame->a[x] = gdbstub_savedRegs.a[x]; } } static void ATTR_GDBINIT install_uart_hdlr() { os_isr_attach(ETS_UART_INUM, gdbstub_uart_entry); SET_PERI_REG_MASK(UART_INT_ENA(0), UART_RXFIFO_FULL_INT_ENA | UART_RXFIFO_TOUT_INT_ENA); ETS_UART_INTR_ENABLE(); } #else static void ATTR_GDBFN gdbstub_uart_hdlr(void* arg, void* frame) { (void) arg; unsigned char c; //Save the extra registers the Xtensa HAL doesn't save gdbstub_save_extra_sfrs_for_exception(); ETS_UART_INTR_DISABLE(); WRITE_PERI_REG(UART_INT_CLR(0), UART_RXFIFO_FULL_INT_CLR | UART_RXFIFO_TOUT_INT_CLR); int fifolen = (READ_PERI_REG(UART_STATUS(0)) >> UART_RXFIFO_CNT_S)&UART_RXFIFO_CNT; while (true) { if (fifolen == 0) { ETS_UART_INTR_ENABLE(); return; } c = READ_PERI_REG(UART_FIFO(0)) & 0xFF; //Check if any of the chars is control-C if (c == 0x3) { break; } #if GDBSTUB_CTRLC_BREAK if (!gdb_attached && uart_isr_callback != NULL) { uart_isr_callback(uart_isr_arg, c); } #endif fifolen--; } gdbstub_savedRegs.reason = 0xff; //mark as user break reason gdbReadCommandWithFrame(frame); } static void ATTR_GDBINIT install_uart_hdlr() { ETS_UART_INTR_DISABLE(); ETS_UART_INTR_ATTACH(gdbstub_uart_hdlr, NULL); configure_uart(); WRITE_PERI_REG(UART_CONF1(0), ((16 & UART_RXFIFO_FULL_THRHD) << UART_RXFIFO_FULL_THRHD_S) | ((0x02 & UART_RX_TOUT_THRHD) << UART_RX_TOUT_THRHD_S) | UART_RX_TOUT_EN); WRITE_PERI_REG(UART_INT_CLR(0), 0xffff); SET_PERI_REG_MASK(UART_INT_ENA(0), UART_RXFIFO_FULL_INT_ENA | UART_RXFIFO_TOUT_INT_ENA); ETS_UART_INTR_ENABLE(); } void ATTR_GDBINIT gdbstub_set_uart_isr_callback(void (*func)(void*, uint8_t), void* arg) { ETS_UART_INTR_DISABLE(); uart_isr_callback = func; uart_isr_arg = arg; ETS_UART_INTR_ENABLE(); } #endif #endif //gdbstub initialization routine. void gdbstub_init() { #if GDBSTUB_REDIRECT_CONSOLE_OUTPUT os_install_putc1(gdbstub_semihost_putchar1); #endif #if GDBSTUB_CTRLC_BREAK install_uart_hdlr(); #else configure_uart(); #endif #if GDBSTUB_BREAK_ON_EXCEPTION install_exceptions(); #endif gdbstub_init_debug_entry(); #if GDBSTUB_BREAK_ON_INIT gdbstub_do_break(); #endif } bool ATTR_GDBEXTERNFN gdb_present() { return true; } void ATTR_GDBFN gdb_do_break() { gdbstub_do_break(); } void gdb_init() __attribute__((alias("gdbstub_init")));