/****************************************************************************** * Copyright 2015 Espressif Systems * * Description: A stub to make the ESP8266 debuggable by GDB over the serial * port. * * License: ESPRESSIF MIT License *******************************************************************************/ #include #include "gdbstub.h" #include "ets_sys.h" #include "eagle_soc.h" #include "c_types.h" #include "gpio.h" #include "xtensa/corebits.h" #include "gdbstub.h" #include "gdbstub-entry.h" #include "gdbstub-cfg.h" // From xtruntime-frames.h struct XTensa_exception_frame_s { uint32_t pc; uint32_t ps; uint32_t sar; uint32_t vpri; uint32_t a0; uint32_t a[14]; //a2..a15 // These are added manually by the exception code; the HAL doesn't // set these on an exception. uint32_t litbase; uint32_t sr176; uint32_t sr208; uint32_t a1; // 'reason' is abused for both the debug and the exception vector: if bit 7 is set, // this contains an exception reason, otherwise it contains a debug vector bitmap. uint32_t reason; }; struct XTensa_rtos_int_frame_s { uint32_t exitPtr; uint32_t pc; uint32_t ps; uint32_t a[16]; uint32_t sar; }; // OS-less SDK defines. Defines some headers for things that aren't in the // include files, plus the xthal stack frame struct. #include "sdk/sdk.h" #define EXCEPTION_GDB_SP_OFFSET 0x100 //We need some UART register defines. #define ETS_UART_INUM 5 #define REG_UART_BASE( i ) (0x60000000+(i)*0xf00) #define UART_STATUS( i ) (REG_UART_BASE( i ) + 0x1C) #define UART_RXFIFO_CNT 0x000000FF #define UART_RXFIFO_CNT_S 0 #define UART_TXFIFO_CNT 0x000000FF #define UART_TXFIFO_CNT_S 16 #define UART_FIFO( i ) (REG_UART_BASE( i ) + 0x0) #define UART_INT_ENA(i) (REG_UART_BASE(i) + 0xC) #define UART_INT_CLR(i) (REG_UART_BASE(i) + 0x10) #define UART_RXFIFO_TOUT_INT_ENA (BIT(8)) #define UART_RXFIFO_FULL_INT_ENA (BIT(0)) #define UART_RXFIFO_TOUT_INT_CLR (BIT(8)) #define UART_RXFIFO_FULL_INT_CLR (BIT(0)) // Length of buffer used to reserve GDB commands. Has to be at least able to // fit the G command, which implies a minimum size of about 190 bytes. #define PBUFLEN 256 // Length of gdb stdout buffer, for console redirection #define OBUFLEN 32 // The asm stub saves the Xtensa registers here when a debugging exception // happens. struct XTensa_exception_frame_s gdbstub_savedRegs; #if GDBSTUB_USE_OWN_STACK // This is the debugging exception stack. int exceptionStack[256]; #endif static unsigned char cmd[PBUFLEN]; // GDB command input buffer static char chsum; // Running checksum of the output packet #if GDBSTUB_REDIRECT_CONSOLE_OUTPUT static unsigned char obuf[OBUFLEN]; // GDB stdout buffer static int obufpos=0; // Current position in the buffer #endif static int32_t singleStepPs = -1; // Stores ps when single-stepping // instruction. -1 when not in use. // Small function to feed the hardware watchdog. Needed to stop the ESP from // resetting due to a watchdog timeout while reading a command. static void ATTR_GDBFN keepWDTalive(void) { uint64_t *wdtval = (uint64_t*)0x3ff21048; uint64_t *wdtovf = (uint64_t*)0x3ff210cc; int *wdtctl = (int*)0x3ff210c8; *wdtovf = *wdtval+1600000; *wdtctl |= (1 << 31); } // Receive a char from the uart. Uses polling and feeds the watchdog. static int ATTR_GDBFN gdbRecvChar(void) { int i; while (((READ_PERI_REG(UART_STATUS(0)) >> UART_RXFIFO_CNT_S)&UART_RXFIFO_CNT)==0) keepWDTalive(); i = READ_PERI_REG(UART_FIFO(0)); return i; } // Send a char to the uart. static void ATTR_GDBFN gdbSendChar(char c) { while (((READ_PERI_REG(UART_STATUS(0)) >> UART_TXFIFO_CNT_S)&UART_TXFIFO_CNT)>=126) ; WRITE_PERI_REG(UART_FIFO(0), c); } // Send the start of a packet; reset checksum calculation. static void ATTR_GDBFN gdbPacketStart(void) { chsum=0; gdbSendChar('$'); } // Send a char as part of a packet static void ATTR_GDBFN gdbPacketChar(char c) { if (c == '#' || c == '$' || c == '}' || c == '*') { gdbSendChar('}'); gdbSendChar(c ^ 0x20); chsum += (c ^ 0x20) +'}'; } else { gdbSendChar(c); chsum += c; } } // Send a string as part of a packet static void ATTR_GDBFN gdbPacketStr(char *c) { while (*c != 0) { gdbPacketChar(*c); c++; } } // Send a hex val as part of a packet. 'bits'/4 dictates the number of hex chars sent. static void ATTR_GDBFN gdbPacketHex(int val, int bits) { char hexChars[]="0123456789abcdef"; int i; for (i = bits; i > 0; i -= 4) gdbPacketChar(hexChars[(val >> (i-4)) & 0xf]); } // Finish sending a packet. static void ATTR_GDBFN gdbPacketEnd(void) { gdbSendChar('#'); gdbPacketHex(chsum, 8); } // Error states used by the routines that grab stuff from the incoming gdb packet #define ST_ENDPACKET -1 #define ST_ERR -2 #define ST_OK -3 #define ST_CONT -4 // Grab a hex value from the gdb packet. Ptr will get positioned on the end // of the hex string, as far as the routine has read into it. Bits/4 indicates // the max amount of hex chars it gobbles up. Bits can be -1 to eat up as much // hex chars as possible. static long ATTR_GDBFN gdbGetHexVal(unsigned char **ptr, int bits) { int i; int no; unsigned int v=0; no=bits/4; if (bits==-1) no=64; for (i=0; i='0' && c<='9') { v <<= 4; v|=(c-'0'); } else if (c>='A' && c<='F') { v <<= 4; v|=(c-'A')+10; } else if (c>='a' && c<='f') { v <<= 4; v|=(c-'a')+10; } else if (c=='#') { if (bits==-1) { (*ptr)--; return v; } return ST_ENDPACKET; } else { if (bits==-1) { (*ptr)--; return v; } return ST_ERR; } } return v; } // Swap an int into the form gdb wants it static int ATTR_GDBFN iswap(int i) { int r; r = ((i >> 24) & 0xff); r |= ((i >> 16) & 0xff) << 8; r |= ((i >> 8) & 0xff) << 16; r |= ((i >> 0) & 0xff) << 24; return r; } // Read a byte from the ESP8266 memory. static unsigned char ATTR_GDBFN readbyte(unsigned int p) { int *i = (int*)(p & (~3)); if (p < 0x20000000 || p >= 0x60000000) return -1; return *i >> ((p & 3) * 8); } // Write a byte to the ESP8266 memory. static void ATTR_GDBFN writeByte(unsigned int p, unsigned char d) { int *i = (int*)(p & (~3)); if (p < 0x20000000 || p >= 0x60000000) return; if ((p & 3) == 0) *i = (*i & 0xffffff00) | (d << 0); if ((p & 3) == 1) *i = (*i & 0xffff00ff) | (d << 8); if ((p & 3) == 2) *i = (*i & 0xff00ffff) | (d << 16); if ((p & 3) == 3) *i = (*i & 0x00ffffff) | (d << 24); } // Returns 1 if it makes sense to write to addr p static int ATTR_GDBFN validWrAddr(int p) { if (p >= 0x3ff00000 && p < 0x40000000) return 1; if (p >= 0x40100000 && p < 0x40140000) return 1; if (p >= 0x60000000 && p < 0x60002000) return 1; return 0; } /* Register file in the format lx106 gdb port expects it. Inspired by gdb/regformats/reg-xtensa.dat from https://github.com/jcmvbkbc/crosstool-NG/blob/lx106-g%2B%2B/overlays/xtensa_lx106.tar As decoded by Cesanta. */ struct regfile { uint32_t a[16]; uint32_t pc; uint32_t sar; uint32_t litbase; uint32_t sr176; uint32_t sr208; uint32_t ps; }; //Send the reason execution is stopped to GDB. static void ATTR_GDBFN sendReason(void) { #if 0 char *reason=""; //default #endif gdbPacketStart(); gdbPacketChar('T'); if (gdbstub_savedRegs.reason == 0xff) { gdbPacketHex(2, 8); //sigint } else if (gdbstub_savedRegs.reason & 0x80) { // exception-to-signal mapping char exceptionSignal[] = { 4, 31, 11, 11, 2, 6 , 8, 0, 6, 7, 0, 0, 7, 7, 7, 7}; unsigned int i=0; // We stopped because of an exception. // Convert exception code to a signal number and send it. i = gdbstub_savedRegs.reason & 0x7f; if (i < sizeof(exceptionSignal)) gdbPacketHex(exceptionSignal[i], 8); else gdbPacketHex(11, 8); } else { // We stopped because of a debugging exception. gdbPacketHex(5, 8); //sigtrap // Current Xtensa GDB versions don't seem to request this, so let's // leave it off. #if 0 if (gdbstub_savedRegs.reason&(1 << 0)) reason="break"; if (gdbstub_savedRegs.reason&(1 << 1)) reason="hwbreak"; if (gdbstub_savedRegs.reason&(1 << 2)) reason="watch"; if (gdbstub_savedRegs.reason&(1 << 3)) reason="swbreak"; if (gdbstub_savedRegs.reason&(1 << 4)) reason="swbreak"; gdbPacketStr(reason); gdbPacketChar(':'); //ToDo: watch: send address #endif } gdbPacketEnd(); } // Handle a command as received from GDB. static int ATTR_GDBFN gdbHandleCommand(unsigned char *cmd, int len) { // Handle a command int i, j, k; unsigned char *data=cmd+1; if (cmd[0] == 'g') { // send all registers to gdb gdbPacketStart(); gdbPacketHex(iswap(gdbstub_savedRegs.a0), 32); gdbPacketHex(iswap(gdbstub_savedRegs.a1), 32); for (i = 2; i < 16; i++) gdbPacketHex(iswap(gdbstub_savedRegs.a[i-2]), 32); gdbPacketHex(iswap(gdbstub_savedRegs.pc), 32); gdbPacketHex(iswap(gdbstub_savedRegs.sar), 32); gdbPacketHex(iswap(gdbstub_savedRegs.litbase), 32); gdbPacketHex(iswap(gdbstub_savedRegs.sr176), 32); gdbPacketHex(0, 32); gdbPacketHex(iswap(gdbstub_savedRegs.ps), 32); gdbPacketEnd(); } else if (cmd[0]=='G') { //receive content for all registers from gdb gdbstub_savedRegs.a0 = iswap(gdbGetHexVal(&data, 32)); gdbstub_savedRegs.a1 = iswap(gdbGetHexVal(&data, 32)); for (i = 2; i < 16; i++) gdbstub_savedRegs.a[i-2]=iswap(gdbGetHexVal(&data, 32)); gdbstub_savedRegs.pc = iswap(gdbGetHexVal(&data, 32)); gdbstub_savedRegs.sar = iswap(gdbGetHexVal(&data, 32)); gdbstub_savedRegs.litbase = iswap(gdbGetHexVal(&data, 32)); gdbstub_savedRegs.sr176 = iswap(gdbGetHexVal(&data, 32)); gdbGetHexVal(&data, 32); gdbstub_savedRegs.ps = iswap(gdbGetHexVal(&data, 32)); gdbPacketStart(); gdbPacketStr("OK"); gdbPacketEnd(); } else if (cmd[0]=='m') { //read memory to gdb i=gdbGetHexVal(&data, -1); data++; j=gdbGetHexVal(&data, -1); gdbPacketStart(); for (k=0; k=PBUFLEN) return ST_ERR; } //A # has been received. Get and check the received chsum. sentchs[0]=gdbRecvChar(); sentchs[1]=gdbRecvChar(); ptr=&sentchs[0]; rchsum=gdbGetHexVal(&ptr, 8); // ets_printf("c %x r %x\n", chsum, rchsum); if (rchsum!=chsum) { gdbSendChar('-'); return ST_ERR; } else { gdbSendChar('+'); return gdbHandleCommand(cmd, p); } } //Get the value of one of the A registers static unsigned int ATTR_GDBFN getaregval(int reg) { if (reg==0) return gdbstub_savedRegs.a0; if (reg==1) return gdbstub_savedRegs.a1; return gdbstub_savedRegs.a[reg-2]; } //Set the value of one of the A registers static void ATTR_GDBFN setaregval(int reg, unsigned int val) { ets_printf("%x -> %x\n", val, reg); if (reg==0) gdbstub_savedRegs.a0=val; if (reg==1) gdbstub_savedRegs.a1=val; gdbstub_savedRegs.a[reg-2]=val; } //Emulate the l32i/s32i instruction we're stopped at. static void ATTR_GDBFN emulLdSt(void) { unsigned char i0=readbyte(gdbstub_savedRegs.pc); unsigned char i1=readbyte(gdbstub_savedRegs.pc+1); unsigned char i2=readbyte(gdbstub_savedRegs.pc+2); int *p; if ((i0 & 0xf)==2 && (i1 & 0xf0)==0x20) { //l32i p=(int*)getaregval(i1 & 0xf)+(i2*4); setaregval(i0 >> 4, *p); gdbstub_savedRegs.pc+=3; } else if ((i0 & 0xf)==0x8) { //l32i.n p=(int*)getaregval(i1 & 0xf)+((i1 >> 4)*4); setaregval(i0 >> 4, *p); gdbstub_savedRegs.pc+=2; } else if ((i0 & 0xf)==2 && (i1 & 0xf0)==0x60) { //s32i p=(int*)getaregval(i1 & 0xf)+(i2*4); *p=getaregval(i0 >> 4); gdbstub_savedRegs.pc+=3; } else if ((i0 & 0xf)==0x9) { //s32i.n p=(int*)getaregval(i1 & 0xf)+((i1 >> 4)*4); *p=getaregval(i0 >> 4); gdbstub_savedRegs.pc+=2; } else { ets_printf("GDBSTUB: No l32i/s32i instruction: %x %x %x. Huh?", i2, i1, i0); } } // We just caught a debug exception and need to handle it. This is called // from an assembly routine in gdbstub-entry.S void ATTR_GDBFN gdbstub_handle_debug_exception(void) { ets_wdt_disable(); if (singleStepPs != -1) { // We come here after single-stepping an instruction. Interrupts are // disabled for the single step. Re-enable them here. gdbstub_savedRegs.ps = (gdbstub_savedRegs.ps & ~0xf) | (singleStepPs & 0xf); singleStepPs = -1; } sendReason(); while (gdbReadCommand()!=ST_CONT) ; if ((gdbstub_savedRegs.reason & 0x84) == 0x4) { //We stopped due to a watchpoint. We can't re-execute the current instruction //because it will happily re-trigger the same watchpoint, so we emulate it //while we're still in debugger space. emulLdSt(); } else if ((gdbstub_savedRegs.reason & 0x88)==0x8) { //We stopped due to a BREAK instruction. Skip over it. //Check the instruction first; gdb may have replaced it with the original instruction //if it's one of the breakpoints it set. if (readbyte(gdbstub_savedRegs.pc+2)==0 && (readbyte(gdbstub_savedRegs.pc+1) & 0xf0)==0x40 && (readbyte(gdbstub_savedRegs.pc) & 0x0f)==0x00) { gdbstub_savedRegs.pc+=3; } } else if ((gdbstub_savedRegs.reason & 0x90)==0x10) { //We stopped due to a BREAK.N instruction. Skip over it, after making sure the instruction //actually is a BREAK.N if ((readbyte(gdbstub_savedRegs.pc+1) & 0xf0)==0xf0 && readbyte(gdbstub_savedRegs.pc)==0x2d) { gdbstub_savedRegs.pc+=3; } } ets_wdt_enable(); } #ifdef MODULE_ESP_SDK_INT_HANDLING // Non-OS exception handler. Gets called by the Xtensa HAL. static void ATTR_GDBFN gdb_exception_handler (void* arg) { struct XTensa_exception_frame_s* frame = (struct XTensa_exception_frame_s*)arg; // Save the extra registers the Xtensa HAL doesn't save gdbstub_save_extra_sfrs_for_exception(); // Copy registers the Xtensa HAL did save to gdbstub_savedRegs ets_memcpy(&gdbstub_savedRegs, frame, 19*4); // Credits go to Cesanta for this trick. A1 seems to be destroyed, but because it // has a fixed offset from the address of the passed frame, we can recover it. gdbstub_savedRegs.a1 = (uint32_t)frame + EXCEPTION_GDB_SP_OFFSET; gdbstub_savedRegs.reason |= 0x80; //mark as an exception reason ets_wdt_disable(); sendReason(); while (gdbReadCommand() != ST_CONT) ; ets_wdt_enable(); // Copy any changed registers back to the frame the Xtensa HAL uses. ets_memcpy(frame, &gdbstub_savedRegs, 19*4); } #else /* TODO static void ATTR_GDBFN gdb_exception_handler (XtExcFrame *frame) { ets_printf("GDB EXCEPTION\n"); while (1) ; } */ #endif // MODULE_ESP_SDK_INT_HANDLING #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_GDBFN gdb_semihost_putchar1(char c) { obuf[obufpos++]=c; if (c=='\n' || obufpos==OBUFLEN) { int i; gdbPacketStart(); gdbPacketChar('O'); for (i=0; i> 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 ets_memcpy(&gdbstub_savedRegs, frame, 19*4); gdbstub_savedRegs.a1=(uint32_t)frame+EXCEPTION_GDB_SP_OFFSET; //mark as user break reason gdbstub_savedRegs.reason=0xff; ets_wdt_disable(); sendReason(); while(gdbReadCommand()!=ST_CONT); ets_wdt_enable(); // Copy any changed registers back to the frame the Xtensa HAL uses. ets_memcpy(frame, &gdbstub_savedRegs, 19*4); } } static void ATTR_GDBINIT install_uart_hdlr(void) { ets_isr_attach(ETS_UART_INUM, (ets_isr_t)uart_hdlr, NULL); SET_PERI_REG_MASK(UART_INT_ENA(0), UART_RXFIFO_FULL_INT_ENA|UART_RXFIFO_TOUT_INT_ENA); // enable uart interrupt ets_isr_unmask((1 << ETS_UART_INUM)); } #endif // GDBSTUB_CTRLC_BREAK // gdbstub initialization routine. void ATTR_GDBINIT gdbstub_init(void) { #if GDBSTUB_REDIRECT_CONSOLE_OUTPUT ets_install_putc1(gdb_semihost_putchar1); #endif #if GDBSTUB_CTRLC_BREAK install_uart_hdlr(); #endif install_exceptions(); gdbstub_init_debug_entry(); #if GDBSTUB_BREAK_ON_INIT gdbstub_do_break(); #endif }