]> granicus.if.org Git - esp-idf/commitdiff
gdbstub: move to a separate component, support multiple targets
authorIvan Grokhotkov <ivan@espressif.com>
Thu, 13 Jun 2019 16:43:49 +0000 (00:43 +0800)
committerIvan Grokhotkov <ivan@espressif.com>
Tue, 2 Jul 2019 09:06:25 +0000 (11:06 +0200)
16 files changed:
components/esp32/CMakeLists.txt
components/esp32/Kconfig
components/esp32/gdbstub.c [deleted file]
components/esp_common/Kconfig
components/esp_gdbstub/CMakeLists.txt [new file with mode: 0644]
components/esp_gdbstub/Kconfig [new file with mode: 0644]
components/esp_gdbstub/component.mk [new file with mode: 0644]
components/esp_gdbstub/esp32/gdbstub_esp32.c [new file with mode: 0644]
components/esp_gdbstub/esp32/gdbstub_target_config.h [new file with mode: 0644]
components/esp_gdbstub/include/esp_gdbstub.h [new file with mode: 0644]
components/esp_gdbstub/linker.lf [new file with mode: 0644]
components/esp_gdbstub/private_include/esp_gdbstub_common.h [new file with mode: 0644]
components/esp_gdbstub/src/gdbstub.c [new file with mode: 0644]
components/esp_gdbstub/src/packet.c [new file with mode: 0644]
components/esp_gdbstub/xtensa/esp_gdbstub_arch.h [new file with mode: 0644]
components/esp_gdbstub/xtensa/gdbstub_xtensa.c [new file with mode: 0644]

index 916113b5333ba43df66a3cfc1535ee67e79cc9e9..f0ef0350679479e4e06121109f4084c46d2f2202 100644 (file)
@@ -16,7 +16,6 @@ else()
         "esp_adapter.c"
         "esp_timer_esp32.c"
         "esp_himem.c"
-        "gdbstub.c"
         "hw_random.c"
         "int_wdt.c"
         "intr_alloc.c"
index ad7f228d9a91b2bc9d892d401150ce4d59825b67..6fdfcc3e2a3235db12fa813cb583b880f299e1a2 100644 (file)
@@ -425,6 +425,7 @@ menu "ESP32-specific"
 
         config ESP32_PANIC_GDBSTUB
             bool "Invoke GDBStub"
+            select ESP_GDBSTUB_ENABLED
             help
                 Invoke gdbstub on the serial port, allowing for gdb to attach to it to do a postmortem
                 of the crash.
diff --git a/components/esp32/gdbstub.c b/components/esp32/gdbstub.c
deleted file mode 100644 (file)
index c690e42..0000000
+++ /dev/null
@@ -1,557 +0,0 @@
-// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-/******************************************************************************
- * Description: A stub to make the ESP32 debuggable by GDB over the serial
- * port, at least enough to do a backtrace on panic. This gdbstub is read-only:
- * it allows inspecting the ESP32 state
- *******************************************************************************/
-
-#include <string.h>
-#include "esp32/rom/ets_sys.h"
-#include "soc/uart_periph.h"
-#include "soc/gpio_periph.h"
-#include "soc/soc_memory_layout.h"
-#include "esp_private/gdbstub.h"
-#include "driver/gpio.h"
-#include "freertos/FreeRTOS.h"
-#include "freertos/task.h"
-#include "sdkconfig.h"
-
-//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 320 bytes.
-#define PBUFLEN 512
-
-static unsigned char cmd[PBUFLEN];             //GDB command input buffer
-static char chsum;                                             //Running checksum of the output packet
-
-#define ATTR_GDBFN
-
-//Receive a char from the uart. Uses polling and feeds the watchdog.
-static int ATTR_GDBFN gdbRecvChar() {
-       int i;
-       while (((READ_PERI_REG(UART_STATUS_REG(0))>>UART_RXFIFO_CNT_S)&UART_RXFIFO_CNT)==0) ;
-       i=READ_PERI_REG(UART_FIFO_REG(0));
-       return i;
-}
-
-//Send a char to the uart.
-static void ATTR_GDBFN gdbSendChar(char c) {
-       while (((READ_PERI_REG(UART_STATUS_REG(0))>>UART_TXFIFO_CNT_S)&UART_TXFIFO_CNT)>=126) ;
-       WRITE_PERI_REG(UART_FIFO_REG(0), c);
-}
-
-//Send the start of a packet; reset checksum calculation.
-static void ATTR_GDBFN gdbPacketStart() {
-       chsum=0;
-       gdbSendChar('$');
-}
-
-//Send a char as part of a packet
-static void ATTR_GDBFN gdbPacketChar(char c) {
-       if (c=='#' || c=='$' || c=='}' || c=='*') {
-               gdbSendChar('}');
-               gdbSendChar(c^0x20);
-               chsum+=(c^0x20)+'}';
-       } else {
-               gdbSendChar(c);
-               chsum+=c;
-       }
-}
-
-//Send a string as part of a packet
-static void ATTR_GDBFN gdbPacketStr(const char *c) {
-       while (*c!=0) {
-               gdbPacketChar(*c);
-               c++;
-       }
-}
-
-//Send a hex val as part of a packet. 'bits'/4 dictates the number of hex chars sent.
-static void ATTR_GDBFN gdbPacketHex(int val, int bits) {
-       char hexChars[]="0123456789abcdef";
-       int i;
-       for (i=bits; i>0; i-=4) {
-               gdbPacketChar(hexChars[(val>>(i-4))&0xf]);
-       }
-}
-
-//Finish sending a packet.
-static void ATTR_GDBFN gdbPacketEnd() {
-       gdbSendChar('#');
-       gdbPacketHex(chsum, 8);
-}
-
-//Error states used by the routines that grab stuff from the incoming gdb packet
-#define ST_ENDPACKET -1
-#define ST_ERR -2
-#define ST_OK -3
-#define ST_CONT -4
-
-//Grab a hex value from the gdb packet. Ptr will get positioned on the end
-//of the hex string, as far as the routine has read into it. Bits/4 indicates
-//the max amount of hex chars it gobbles up. Bits can be -1 to eat up as much
-//hex chars as possible.
-static long ATTR_GDBFN gdbGetHexVal(unsigned char **ptr, int bits) {
-       int i;
-       int no;
-       unsigned int v=0;
-       char c;
-       no=bits/4;
-       if (bits==-1) no=64;
-       for (i=0; i<no; i++) {
-               c=**ptr;
-               (*ptr)++;
-               if (c>='0' && c<='9') {
-                       v<<=4;
-                       v|=(c-'0');
-               } else if (c>='A' && c<='F') {
-                       v<<=4;
-                       v|=(c-'A')+10;
-               } else if (c>='a' && c<='f') {
-                       v<<=4;
-                       v|=(c-'a')+10;
-               } else if (c=='#') {
-                       if (bits==-1) {
-                               (*ptr)--;
-                               return v;
-                       }
-                       return ST_ENDPACKET;
-               } else {
-                       if (bits==-1) {
-                               (*ptr)--;
-                               return v;
-                       }
-                       return ST_ERR;
-               }
-       }
-       return v;
-}
-
-//Swap an int into the form gdb wants it
-static int ATTR_GDBFN iswap(int i) {
-       int r;
-       r=((i>>24)&0xff);
-       r|=((i>>16)&0xff)<<8;
-       r|=((i>>8)&0xff)<<16;
-       r|=((i>>0)&0xff)<<24;
-       return r;
-}
-
-//Read a byte from ESP32 memory.
-static unsigned char ATTR_GDBFN readbyte(unsigned int p) {
-       int *i=(int*)(p&(~3));
-       if (p<0x20000000 || p>=0x80000000) return -1;
-       return *i>>((p&3)*8);
-}
-
-
-//Register file in the format exp108 gdb port expects it.
-//Inspired by gdb/regformats/reg-xtensa.dat
-typedef struct {
-       uint32_t pc;
-       uint32_t a[64];
-       uint32_t lbeg;
-       uint32_t lend;
-       uint32_t lcount;
-       uint32_t sar;
-       uint32_t windowbase;
-       uint32_t windowstart;
-       uint32_t configid0;
-       uint32_t configid1;
-       uint32_t ps;
-       uint32_t threadptr;
-       uint32_t br;
-       uint32_t scompare1;
-       uint32_t acclo;
-       uint32_t acchi;
-       uint32_t m0;
-       uint32_t m1;
-       uint32_t m2;
-       uint32_t m3;
-       uint32_t expstate;  //I'm going to assume this is exccause...
-       uint32_t f64r_lo;
-       uint32_t f64r_hi;
-       uint32_t f64s;
-       uint32_t f[16];
-       uint32_t fcr;
-       uint32_t fsr;
-} GdbRegFile;
-
-
-GdbRegFile gdbRegFile;
-
-/*
-//Register format as the Xtensa HAL has it:
-STRUCT_FIELD (long, 4, XT_STK_EXIT,     exit)
-STRUCT_FIELD (long, 4, XT_STK_PC,       pc)
-STRUCT_FIELD (long, 4, XT_STK_PS,       ps)
-STRUCT_FIELD (long, 4, XT_STK_A0,       a0)
-[..]
-STRUCT_FIELD (long, 4, XT_STK_A15,      a15)
-STRUCT_FIELD (long, 4, XT_STK_SAR,      sar)
-STRUCT_FIELD (long, 4, XT_STK_EXCCAUSE, exccause)
-STRUCT_FIELD (long, 4, XT_STK_EXCVADDR, excvaddr)
-STRUCT_FIELD (long, 4, XT_STK_LBEG,   lbeg)
-STRUCT_FIELD (long, 4, XT_STK_LEND,   lend)
-STRUCT_FIELD (long, 4, XT_STK_LCOUNT, lcount)
-// Temporary space for saving stuff during window spill
-STRUCT_FIELD (long, 4, XT_STK_TMP0,   tmp0)
-STRUCT_FIELD (long, 4, XT_STK_TMP1,   tmp1)
-STRUCT_FIELD (long, 4, XT_STK_TMP2,   tmp2)
-STRUCT_FIELD (long, 4, XT_STK_VPRI,   vpri)
-STRUCT_FIELD (long, 4, XT_STK_OVLY,   ovly)
-#endif
-STRUCT_END(XtExcFrame)
-*/
-
-static void commonRegfile() {
-       if (gdbRegFile.a[0] & 0x8000000U) gdbRegFile.a[0] = (gdbRegFile.a[0] & 0x3fffffffU) | 0x40000000U;
-       if (!esp_stack_ptr_is_sane(gdbRegFile.a[1])) gdbRegFile.a[1] = 0xDEADBEEF;
-       gdbRegFile.windowbase=0; //0
-       gdbRegFile.windowstart=0x1; //1
-       gdbRegFile.configid0=0xdeadbeef; //ToDo
-       gdbRegFile.configid1=0xdeadbeef; //ToDo
-       gdbRegFile.threadptr=0xdeadbeef; //ToDo
-       gdbRegFile.br=0xdeadbeef; //ToDo
-       gdbRegFile.scompare1=0xdeadbeef; //ToDo
-       gdbRegFile.acclo=0xdeadbeef; //ToDo
-       gdbRegFile.acchi=0xdeadbeef; //ToDo
-       gdbRegFile.m0=0xdeadbeef; //ToDo
-       gdbRegFile.m1=0xdeadbeef; //ToDo
-       gdbRegFile.m2=0xdeadbeef; //ToDo
-       gdbRegFile.m3=0xdeadbeef; //ToDo
-}
-
-static void dumpHwToRegfile(XtExcFrame *frame) {
-       int i;
-       long *frameAregs=&frame->a0;
-       gdbRegFile.pc=(frame->pc & 0x3fffffffU)|0x40000000U;
-       for (i=0; i<16; i++) gdbRegFile.a[i]=frameAregs[i];
-       for (i=16; i<64; i++) gdbRegFile.a[i]=0xDEADBEEF;
-       gdbRegFile.lbeg=frame->lbeg;
-       gdbRegFile.lend=frame->lend;
-       gdbRegFile.lcount=frame->lcount;
-       gdbRegFile.ps=(frame->ps & PS_UM) ? (frame->ps & ~PS_EXCM) : frame->ps;
-       //All windows have been spilled to the stack by the ISR routines. The following values should indicate that.
-       gdbRegFile.sar=frame->sar;
-       commonRegfile();
-       gdbRegFile.expstate=frame->exccause; //ToDo
-}
-
-//Send the reason execution is stopped to GDB.
-static void sendReason() {
-       //exception-to-signal mapping
-       char exceptionSignal[]={4,31,11,11,2,6,8,0,6,7,0,0,7,7,7,7};
-       int i=0;
-       gdbPacketStart();
-       gdbPacketChar('T');
-       i=gdbRegFile.expstate&0x7f;
-       if (i<sizeof(exceptionSignal)) {
-               gdbPacketHex(exceptionSignal[i], 8);
-       } else {
-               gdbPacketHex(11, 8);
-       }
-       gdbPacketEnd();
-}
-
-static int sendPacket(const char * text) {
-       gdbPacketStart();
-       if (text != NULL) gdbPacketStr(text);
-       gdbPacketEnd();
-       return ST_OK;
-}
-
-#if CONFIG_ESP_GDBSTUB_SUPPORT_TASKS
-
-#define STUB_TASKS_NUM  CONFIG_ESP_GDBSTUB_MAX_TASKS
-
-//Remember the exception frame that caused panic since it's not saved in TCB
-static XtExcFrame paniced_frame;
-
-//Allows GDBStub to disable task support after a crash
-//(e.g. if GDBStub crashes while trying to get task list, e.g. due to corrupted list structures)
-static enum {
-       HANDLER_NOT_STARTED,
-       HANDLER_STARTED,
-       HANDLER_TASK_SUPPORT_DISABLED
-} handlerState;
-
-static void dumpTaskToRegfile(XtSolFrame *frame) {
-       int i;
-       long *frameAregs=&frame->a0;
-       gdbRegFile.pc=(frame->pc & 0x3fffffffU)|0x40000000U;
-       for (i=0; i<4; i++) gdbRegFile.a[i]=frameAregs[i];
-       for (i=4; i<64; i++) gdbRegFile.a[i]=0xDEADBEEF;
-       gdbRegFile.lbeg=0;
-       gdbRegFile.lend=0;
-       gdbRegFile.lcount=0;
-       gdbRegFile.ps=(frame->ps & PS_UM) ? (frame->ps & ~PS_EXCM) : frame->ps;
-       //All windows have been spilled to the stack by the ISR routines. The following values should indicate that.
-       gdbRegFile.sar=0;
-       commonRegfile();
-       gdbRegFile.expstate=0; //ToDo
-}
-
-// Fetch the task status. Returns the total number of tasks.
-static unsigned getTaskInfo(unsigned index, unsigned * handle, const char ** name, unsigned * coreId) {
-       static unsigned taskCount = 0;
-       static TaskSnapshot_t tasks[STUB_TASKS_NUM];
-
-       if (!taskCount) {
-               unsigned tcbSize = 0;
-               taskCount = uxTaskGetSnapshotAll(tasks, STUB_TASKS_NUM, &tcbSize);
-       }
-       if (index < taskCount) {
-               TaskHandle_t h = (TaskHandle_t)tasks[index].pxTCB;
-               if (handle) *handle = (unsigned)h;
-               if (name) *name = pcTaskGetTaskName(h);
-               if (coreId) *coreId = xTaskGetAffinity(h);
-       }
-       return taskCount;
-}
-
-typedef struct
-{
-       uint8_t * topOfStack;
-} DumpTCB;
-
-
-static void dumpTCBToRegFile(unsigned handle) {
-       // A task handle is a pointer to a TCB in FreeRTOS
-       DumpTCB * tcb = (DumpTCB*)handle;
-       uint8_t * pxTopOfStack = tcb->topOfStack;
-
-       //Deduced from coredump code
-       XtExcFrame * frame = (XtExcFrame*)pxTopOfStack;
-       if (frame->exit) {
-               // It's an exception frame
-               dumpHwToRegfile(frame);
-       } else {
-               XtSolFrame * taskFrame = (XtSolFrame*)pxTopOfStack;
-               dumpTaskToRegfile(taskFrame);
-       }
-}
-
-#define CUR_TASK_INDEX_NOT_SET -2
-#define CUR_TASK_INDEX_UNKNOWN -1
-
-// Get the index of the task currently running on the current CPU, and cache the result
-static int findCurrentTaskIndex() {
-       static int curTaskIndex = CUR_TASK_INDEX_NOT_SET;
-       if (curTaskIndex == CUR_TASK_INDEX_NOT_SET) {
-               unsigned curHandle = (unsigned)xTaskGetCurrentTaskHandleForCPU(xPortGetCoreID());
-               unsigned handle;
-               unsigned count = getTaskInfo(0, 0, 0, 0);
-               for(int k=0; k<(int)count; k++) {
-                       if (getTaskInfo(k, &handle, 0, 0) && curHandle == handle) {
-                               curTaskIndex = k;
-                               return curTaskIndex;
-                       }
-               }
-               curTaskIndex = CUR_TASK_INDEX_UNKNOWN;
-       }
-       return curTaskIndex;
-}
-
-#endif // CONFIG_ESP_GDBSTUB_SUPPORT_TASKS
-
-//Handle a command as received from GDB.
-static int 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
-               int *p=(int*)&gdbRegFile;
-               gdbPacketStart();
-               for (i=0; i<sizeof(GdbRegFile)/4; i++) gdbPacketHex(iswap(*p++), 32);
-               gdbPacketEnd();
-       } else if (cmd[0]=='G') {       //receive content for all registers from gdb
-               int *p=(int*)&gdbRegFile;
-               for (i=0; i<sizeof(GdbRegFile)/4; i++) *p++=iswap(gdbGetHexVal(&data, 32));
-               sendPacket("OK");
-       } else if (cmd[0]=='m') {       //read memory to gdb
-               i=gdbGetHexVal(&data, -1);
-               data++;
-               j=gdbGetHexVal(&data, -1);
-               gdbPacketStart();
-               for (k=0; k<j; k++) {
-                       gdbPacketHex(readbyte(i++), 8);
-               }
-               gdbPacketEnd();
-       } else if (cmd[0]=='?') {       //Reply with stop reason
-               sendReason();
-#if CONFIG_ESP_GDBSTUB_SUPPORT_TASKS
-       } else if (handlerState != HANDLER_TASK_SUPPORT_DISABLED) {
-               if (cmd[0]=='H') { //Continue with task
-                       if (cmd[1]=='g' || cmd[1]=='c') {
-                               const char * ret = "OK";
-                               data++;
-                               i=gdbGetHexVal(&data, -1);
-                               handlerState = HANDLER_STARTED; //Hg0 is the first packet received after connect
-                               j = findCurrentTaskIndex();
-                               if (i == j || (j == CUR_TASK_INDEX_UNKNOWN && i == 0)) {
-                                       //GDB has asked us for the current task on this CPU.
-                                       //This task either was executing when we have entered the panic handler,
-                                       //or was already switched out and we have paniced during the context switch.
-                                       //Either way we are interested in the stack frame where panic has happened,
-                                       //so obtain the state from the exception frame rather than the TCB.
-                                       dumpHwToRegfile(&paniced_frame);
-                               } else {
-                                       unsigned handle, count;
-                                       //Get the handle for that task
-                                       count = getTaskInfo(i, &handle, 0, 0);
-                                       //Then extract TCB and gdbRegFile from it
-                                       if (i < count) dumpTCBToRegFile(handle);
-                                       else ret = "E00";
-                               }
-                               return sendPacket(ret);
-                       }
-                       return sendPacket(NULL);
-               } else if (cmd[0]=='T') {       //Task alive check
-                       unsigned count;
-                       data++;
-                       i=gdbGetHexVal(&data, -1);
-                       count = getTaskInfo(i, 0, 0, 0);
-                       return sendPacket(i < count ? "OK": "E00");
-               } else if (cmd[0]=='q') {       //Extended query
-                       // React to qThreadExtraInfo or qfThreadInfo or qsThreadInfo or qC, without using strcmp
-                       if (len > 16 && cmd[1] == 'T' && cmd[2] == 'h' && cmd[3] == 'r' && cmd[7] == 'E' && cmd[12] == 'I' && cmd[16] == ',') {
-                               data=&cmd[17];
-                               i=gdbGetHexVal(&data, -1);
-
-                               unsigned handle = 0, coreId = 3;
-                               const char * name = 0;
-                               // Extract the task name and CPU from freeRTOS
-                               unsigned tCount = getTaskInfo(i, &handle, &name, &coreId);
-                               if (i < tCount) {
-                                       gdbPacketStart();
-                                       for(k=0; name[k]; k++)  gdbPacketHex(name[k], 8);
-                                       gdbPacketStr("20435055"); // CPU
-                                       gdbPacketStr(coreId == 0 ? "30": coreId == 1 ? "31" : "78"); // 0 or 1 or x
-                                       gdbPacketEnd();
-                                       return ST_OK;
-                               }
-                       } else if (len >= 12 && (cmd[1] == 'f' || cmd[1] == 's') && (cmd[2] == 'T' && cmd[3] == 'h' && cmd[4] == 'r' && cmd[5] == 'e' && cmd[6] == 'a' && cmd[7] == 'd' && cmd[8] == 'I')) {
-                               // Only react to qfThreadInfo and qsThreadInfo, not using strcmp here since it can be in ROM
-                               // Extract the number of task from freeRTOS
-                               static int taskIndex = 0;
-                               unsigned tCount = 0;
-                               if (cmd[1] == 'f') {
-                                       taskIndex = 0;
-                                       handlerState = HANDLER_STARTED; //It seems it's the first request GDB is sending
-                               }
-                               tCount = getTaskInfo(0, 0, 0, 0);
-                               if (taskIndex < tCount) {
-                                       gdbPacketStart();
-                                       gdbPacketStr("m");
-                                       gdbPacketHex(taskIndex, 32);
-                                       gdbPacketEnd();
-                                       taskIndex++;
-                               } else return sendPacket("l");
-                       } else if (len >= 2 && cmd[1] == 'C') {
-                               // Get current task id
-                               gdbPacketStart();
-                               k = findCurrentTaskIndex();
-                               if (k != CUR_TASK_INDEX_UNKNOWN) {
-                                       gdbPacketStr("QC");
-                                       gdbPacketHex(k, 32);
-                               } else gdbPacketStr("bad");
-                               gdbPacketEnd();
-                               return ST_OK;
-                       }
-                       return sendPacket(NULL);
-               }
-#endif // CONFIG_ESP_GDBSTUB_SUPPORT_TASKS
-       } else {
-               //We don't recognize or support whatever GDB just sent us.
-               return sendPacket(NULL);
-       }
-       return ST_OK;
-}
-
-
-//Lower layer: grab a command packet and check the checksum
-//Calls gdbHandleCommand on the packet if the checksum is OK
-//Returns ST_OK on success, ST_ERR when checksum fails, a
-//character if it is received instead of the GDB packet
-//start char.
-static int gdbReadCommand() {
-       unsigned char c;
-       unsigned char chsum=0, rchsum;
-       unsigned char sentchs[2];
-       int p=0;
-       unsigned char *ptr;
-       c=gdbRecvChar();
-       if (c!='$') return c;
-       while(1) {
-               c=gdbRecvChar();
-               if (c=='#') {   //end of packet, checksum follows
-                       cmd[p]=0;
-                       break;
-               }
-               chsum+=c;
-               if (c=='$') {
-                       //Wut, restart packet?
-                       chsum=0;
-                       p=0;
-                       continue;
-               }
-               if (c=='}') {           //escape the next char
-                       c=gdbRecvChar();
-                       chsum+=c;
-                       c^=0x20;
-               }
-               cmd[p++]=c;
-               if (p>=PBUFLEN) return ST_ERR;
-       }
-       //A # has been received. Get and check the received chsum.
-       sentchs[0]=gdbRecvChar();
-       sentchs[1]=gdbRecvChar();
-       ptr=&sentchs[0];
-       rchsum=gdbGetHexVal(&ptr, 8);
-       if (rchsum!=chsum) {
-               gdbSendChar('-');
-               return ST_ERR;
-       } else {
-               gdbSendChar('+');
-               return gdbHandleCommand(cmd, p);
-       }
-}
-
-
-void esp_gdbstub_panic_handler(XtExcFrame *frame) {
-#if CONFIG_ESP_GDBSTUB_SUPPORT_TASKS
-       if (handlerState == HANDLER_STARTED) {
-               //We have re-entered GDB Stub. Try disabling task support.
-               handlerState = HANDLER_TASK_SUPPORT_DISABLED;
-               gdbPacketEnd(); // Ends up any pending GDB packet (this creates a garbage value)
-       } else if (handlerState == HANDLER_NOT_STARTED) {
-               //Need to remember the frame that panic'd since gdb will ask for all threads before ours
-               memcpy(&paniced_frame, frame, sizeof(paniced_frame));
-               dumpHwToRegfile(&paniced_frame);
-       }
-#else // CONFIG_ESP_GDBSTUB_SUPPORT_TASKS
-       dumpHwToRegfile(frame);
-#endif // CONFIG_ESP_GDBSTUB_SUPPORT_TASKS
-
-       //Make sure txd/rxd are enabled
-       gpio_pullup_dis(1);
-       PIN_FUNC_SELECT(PERIPHS_IO_MUX_U0RXD_U, FUNC_U0RXD_U0RXD);
-       PIN_FUNC_SELECT(PERIPHS_IO_MUX_U0TXD_U, FUNC_U0TXD_U0TXD);
-
-       sendReason();
-       while(gdbReadCommand()!=ST_CONT);
-       while(1);
-}
index 80f20e606948c8533d63e3ed84a850f704f9404f..ca5608b8e4ae73042b55362c2947ff94db9153aa 100644 (file)
@@ -127,24 +127,6 @@ menu "Common ESP-related"
         range 1200 4000000
 
 
-    config ESP_GDBSTUB_SUPPORT_TASKS
-        bool "GDBStub: enable listing FreeRTOS tasks"
-        default y
-        depends on ESP32_PANIC_GDBSTUB
-        help
-            If enabled, GDBStub can supply the list of FreeRTOS tasks to GDB.
-            Thread list can be queried from GDB using 'info threads' command.
-            Note that if GDB task lists were corrupted, this feature may not work.
-            If GDBStub fails, try disabling this feature.
-
-    config ESP_GDBSTUB_MAX_TASKS
-        int "GDBStub: maximum number of tasks supported"
-        default 32
-        depends on ESP_GDBSTUB_SUPPORT_TASKS
-        help
-            Set the number of tasks which GDB Stub will support.
-
-
     config ESP_INT_WDT
         bool "Interrupt watchdog"
         default y
diff --git a/components/esp_gdbstub/CMakeLists.txt b/components/esp_gdbstub/CMakeLists.txt
new file mode 100644 (file)
index 0000000..14528a4
--- /dev/null
@@ -0,0 +1,13 @@
+idf_build_get_property(target IDF_TARGET)
+
+set(esp_gdbstub_srcs "src/gdbstub.c"
+                     "src/packet.c"
+                     "${target}/gdbstub_${target}.c"
+                     "xtensa/gdbstub_xtensa.c")
+
+idf_component_register(SRCS "${esp_gdbstub_srcs}"
+                       INCLUDE_DIRS "include"
+                       PRIV_INCLUDE_DIRS "private_include" "${target}" "xtensa"
+                       LDFRAGMENTS "linker.lf"
+                       REQUIRES "freertos"
+                       PRIV_REQUIRES "soc" "xtensa" "esp_rom")
diff --git a/components/esp_gdbstub/Kconfig b/components/esp_gdbstub/Kconfig
new file mode 100644 (file)
index 0000000..14e7d85
--- /dev/null
@@ -0,0 +1,25 @@
+menu "GDB Stub"
+
+    # Hidden option which is selected from the "Panic handler behavior"
+    # menu in the target component.
+    config ESP_GDBSTUB_ENABLED
+        bool
+
+    config ESP_GDBSTUB_SUPPORT_TASKS
+        bool "Enable listing FreeRTOS tasks through GDB Stub"
+        depends on ESP_GDBSTUB_ENABLED
+        default y
+        help
+            If enabled, GDBStub can supply the list of FreeRTOS tasks to GDB.
+            Thread list can be queried from GDB using 'info threads' command.
+            Note that if GDB task lists were corrupted, this feature may not work.
+            If GDBStub fails, try disabling this feature.
+
+    config ESP_GDBSTUB_MAX_TASKS
+        int "Maximum number of tasks supported by GDB Stub"
+        default 32
+        depends on ESP_GDBSTUB_SUPPORT_TASKS
+        help
+            Set the number of tasks which GDB Stub will support.
+
+endmenu
diff --git a/components/esp_gdbstub/component.mk b/components/esp_gdbstub/component.mk
new file mode 100644 (file)
index 0000000..97dfce2
--- /dev/null
@@ -0,0 +1,4 @@
+COMPONENT_ADD_INCLUDEDIRS := include
+COMPONENT_PRIV_INCLUDEDIRS := private_include esp32 xtensa
+COMPONENT_SRCDIRS := src esp32 xtensa
+COMPONENT_ADD_LDFRAGMENTS += linker.lf
diff --git a/components/esp_gdbstub/esp32/gdbstub_esp32.c b/components/esp_gdbstub/esp32/gdbstub_esp32.c
new file mode 100644 (file)
index 0000000..3fe393a
--- /dev/null
@@ -0,0 +1,51 @@
+// Copyright 2015-2019 Espressif Systems (Shanghai) PTE LTD
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "soc/uart_periph.h"
+#include "soc/gpio_periph.h"
+#include "esp_gdbstub_common.h"
+#include "sdkconfig.h"
+
+#define UART_NUM CONFIG_CONSOLE_UART_NUM
+
+void esp_gdbstub_target_init()
+{
+}
+
+int esp_gdbstub_getchar()
+{
+    while (REG_GET_FIELD(UART_STATUS_REG(UART_NUM), UART_RXFIFO_CNT) == 0) {
+        ;
+    }
+    return REG_READ(UART_FIFO_REG(UART_NUM));
+}
+
+void esp_gdbstub_putchar(int c)
+{
+    while (REG_GET_FIELD(UART_STATUS_REG(UART_NUM), UART_TXFIFO_CNT) >= 126) {
+        ;
+    }
+    REG_WRITE(UART_FIFO_REG(UART_NUM), c);
+}
+
+int esp_gdbstub_readmem(intptr_t addr)
+{
+    if (addr < 0x20000000 || addr >= 0x80000000) {
+        /* see cpu_configure_region_protection */
+        return -1;
+    }
+    uint32_t val_aligned = *(uint32_t *)(addr & (~3));
+    uint32_t shift = (addr & 3) * 8;
+    return (val_aligned >> shift) & 0xff;
+}
diff --git a/components/esp_gdbstub/esp32/gdbstub_target_config.h b/components/esp_gdbstub/esp32/gdbstub_target_config.h
new file mode 100644 (file)
index 0000000..ae31ae9
--- /dev/null
@@ -0,0 +1,18 @@
+// Copyright 2019 Espressif Systems (Shanghai) PTE LTD
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#pragma once
+
+/* Number of extra TIE defined registers, not listed in the XCHAL */
+#define GDBSTUB_EXTRA_TIE_SIZE    0
diff --git a/components/esp_gdbstub/include/esp_gdbstub.h b/components/esp_gdbstub/include/esp_gdbstub.h
new file mode 100644 (file)
index 0000000..02fda63
--- /dev/null
@@ -0,0 +1,27 @@
+// Copyright 2015-2019 Espressif Systems (Shanghai) PTE LTD
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#pragma once
+
+#include "esp_gdbstub_arch.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+void esp_gdbstub_panic_handler(esp_gdbstub_frame_t *frame) __attribute__((noreturn));
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/components/esp_gdbstub/linker.lf b/components/esp_gdbstub/linker.lf
new file mode 100644 (file)
index 0000000..b5d88a2
--- /dev/null
@@ -0,0 +1,4 @@
+[mapping:esp_gdbstub]
+archive: libesp_gdbstub.a
+entries:
+    * (noflash)
diff --git a/components/esp_gdbstub/private_include/esp_gdbstub_common.h b/components/esp_gdbstub/private_include/esp_gdbstub_common.h
new file mode 100644 (file)
index 0000000..75674ff
--- /dev/null
@@ -0,0 +1,151 @@
+// Copyright 2015-2019 Espressif Systems (Shanghai) PTE LTD
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#pragma once
+
+#include <stdint.h>
+#include <stddef.h>
+#include <stdbool.h>
+
+#include "esp_gdbstub.h"
+#include "sdkconfig.h"
+
+#ifdef CONFIG_ESP_GDBSTUB_SUPPORT_TASKS
+#include "freertos/FreeRTOS.h"
+#include "freertos/task.h"
+#endif // CONFIG_ESP_GDBSTUB_SUPPORT_TASKS
+
+/* Internal error codes used by the routines that parse the incoming gdb packet */
+#define GDBSTUB_ST_ENDPACKET -1
+#define GDBSTUB_ST_ERR -2
+#define GDBSTUB_ST_OK -3
+
+/* Special task index values */
+#define GDBSTUB_CUR_TASK_INDEX_UNKNOWN -1
+
+/* Cab be set to a lower value in gdbstub_target_config.h */
+#ifndef GDBSTUB_CMD_BUFLEN
+#define GDBSTUB_CMD_BUFLEN 512
+#endif
+
+#if CONFIG_ESP_GDBSTUB_SUPPORT_TASKS
+typedef enum {
+    GDBSTUB_NOT_STARTED,
+    GDBSTUB_STARTED,
+    GDBSTUB_TASK_SUPPORT_DISABLED
+} esp_gdbstub_state_t;
+
+#define GDBSTUB_TASKS_NUM  CONFIG_ESP_GDBSTUB_MAX_TASKS
+
+#endif // CONFIG_ESP_GDBSTUB_SUPPORT_TASKS
+
+/* gdbstub temporary run-time data, stored in .bss to reduce stack usage */
+typedef struct {
+    esp_gdbstub_gdb_regfile_t regfile;
+    int signal;
+#if CONFIG_ESP_GDBSTUB_SUPPORT_TASKS
+    esp_gdbstub_state_t state;
+    int task_count;
+    int paniced_task_index;
+    int current_task_index;
+    int thread_info_index;                             //!< index of the last task passed to qsThreadInfo
+    esp_gdbstub_frame_t paniced_frame;
+    TaskSnapshot_t tasks[GDBSTUB_TASKS_NUM];           // TODO: add an API to get snapshots one by one
+#endif // CONFIG_ESP_GDBSTUB_SUPPORT_TASKS
+} esp_gdbstub_scratch_t;
+
+
+/**** Functions provided by the architecture specific part ****/
+
+/**
+ * @param frame exception frame pointer
+ * @return the appropriate "signal" number for the given exception cause
+ */
+int esp_gdbstub_get_signal(const esp_gdbstub_frame_t *frame);
+
+/**
+ * Write registers from the exception frame to the GDB register file
+ * @param frame  exception frame to parse
+ * @param dst  pointer to the GDB register file
+ */
+void esp_gdbstub_frame_to_regfile(const esp_gdbstub_frame_t *frame, esp_gdbstub_gdb_regfile_t *dst);
+
+#if CONFIG_ESP_GDBSTUB_SUPPORT_TASKS
+/**
+ * Write registers from the saved frame of a given task to the GDB register file
+ * @param tcb  pointer to the TCB of the task
+ * @param dst  pointer to the GDB register file
+ */
+void esp_gdbstub_tcb_to_regfile(TaskHandle_t tcb, esp_gdbstub_gdb_regfile_t *dst);
+#endif // CONFIG_ESP_GDBSTUB_SUPPORT_TASKS
+
+
+
+/**** Functions provided by the target specific part ****/
+
+/**
+ * Do target-specific initialization before gdbstub can start communicating.
+ * This may involve, for example, configuring the UART.
+ */
+void esp_gdbstub_target_init();
+
+/**
+ * Receive a byte from the GDB client. Blocks until a byte is available.
+ * @return received byte
+ */
+int esp_gdbstub_getchar();
+
+/**
+ * Send a byte to the GDB client
+ * @param c  byte to send
+ */
+void esp_gdbstub_putchar(int c);
+
+/**
+ * Read a byte from target memory
+ * @param ptr  address
+ * @return  byte value, or GDBSTUB_ST_ERR if the address is not readable
+ */
+int esp_gdbstub_readmem(intptr_t addr);
+
+
+/**** GDB packet related functions ****/
+
+/** Begin a packet */
+void esp_gdbstub_send_start();
+
+/** Send a character as part of the packet */
+void esp_gdbstub_send_char(char c);
+
+/** Send a string as part of the packet */
+void esp_gdbstub_send_str(const char *s);
+
+/** Send a hex value as part of the packet */
+void esp_gdbstub_send_hex(int val, int bits);
+
+/** Finish sending the packet */
+void esp_gdbstub_send_end();
+
+/** Send a packet with a string as content */
+void esp_gdbstub_send_str_packet(const char* str);
+
+/** Get a hex value from the gdb packet */
+uint32_t esp_gdbstub_gethex(const unsigned char **ptr, int bits);
+
+/** Read, unescape, and validate the incoming GDB command */
+int esp_gdbstub_read_command(unsigned char **out_cmd, size_t *out_size);
+
+/** Handle a command received from gdb */
+int esp_gdbstub_handle_command(unsigned char *cmd, int len);
+
diff --git a/components/esp_gdbstub/src/gdbstub.c b/components/esp_gdbstub/src/gdbstub.c
new file mode 100644 (file)
index 0000000..4d1f4cf
--- /dev/null
@@ -0,0 +1,343 @@
+// Copyright 2015-2019 Espressif Systems (Shanghai) PTE LTD
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include <string.h>
+#include "esp_gdbstub.h"
+#include "esp_gdbstub_common.h"
+#include "sdkconfig.h"
+
+
+#ifdef CONFIG_ESP_GDBSTUB_SUPPORT_TASKS
+static void init_task_info();
+static void find_paniced_task_index();
+static int handle_task_commands(unsigned char *cmd, int len);
+#endif
+
+static void send_reason();
+
+
+static esp_gdbstub_scratch_t s_scratch;
+
+
+void esp_gdbstub_panic_handler(esp_gdbstub_frame_t *frame)
+{
+#ifndef CONFIG_ESP_GDBSTUB_SUPPORT_TASKS
+    esp_gdbstub_frame_to_regfile(frame, &s_scratch.regfile);
+#else
+    if (s_scratch.state == GDBSTUB_STARTED) {
+        /* We have re-entered GDB Stub. Try disabling task support. */
+        s_scratch.state = GDBSTUB_TASK_SUPPORT_DISABLED;
+        /* Flush any pending GDB packet (this creates a garbage value) */
+        esp_gdbstub_send_end();
+    } else if (s_scratch.state == GDBSTUB_NOT_STARTED) {
+        s_scratch.state = GDBSTUB_STARTED;
+        /* Save the paniced frame and get the list of tasks */
+        memcpy(&s_scratch.paniced_frame, frame, sizeof(*frame));
+        esp_gdbstub_frame_to_regfile(frame, &s_scratch.regfile);
+        init_task_info();
+        find_paniced_task_index();
+        /* Current task is the paniced task */
+        if (s_scratch.paniced_task_index == GDBSTUB_CUR_TASK_INDEX_UNKNOWN) {
+            s_scratch.current_task_index = 0;
+        }
+    }
+#endif // CONFIG_ESP_GDBSTUB_SUPPORT_TASKS
+
+    esp_gdbstub_target_init();
+    s_scratch.signal = esp_gdbstub_get_signal(frame);
+    send_reason();
+    while (true) {
+        unsigned char *cmd;
+        size_t size;
+        int res = esp_gdbstub_read_command(&cmd, &size);
+        if (res > 0) {
+            /* character received instead of a command */
+            continue;
+        }
+        if (res == GDBSTUB_ST_ERR) {
+            esp_gdbstub_send_str_packet("E01");
+            continue;
+        }
+        res = esp_gdbstub_handle_command(cmd, size);
+        if (res == GDBSTUB_ST_ERR) {
+            esp_gdbstub_send_str_packet(NULL);
+        }
+    }
+}
+
+
+static void send_reason()
+{
+    esp_gdbstub_send_start();
+    esp_gdbstub_send_char('T');
+    esp_gdbstub_send_hex(s_scratch.signal, 8);
+    esp_gdbstub_send_end();
+}
+
+static uint32_t gdbstub_hton(uint32_t i)
+{
+    return __builtin_bswap32(i);
+}
+
+/** Send all registers to gdb */
+static void handle_g_command(const unsigned char* cmd, int len)
+{
+    uint32_t *p = (uint32_t *) &s_scratch.regfile;
+    esp_gdbstub_send_start();
+    for (int i = 0; i < sizeof(s_scratch.regfile) / sizeof(*p); ++i) {
+        esp_gdbstub_send_hex(gdbstub_hton(*p++), 32);
+    }
+    esp_gdbstub_send_end();
+}
+
+/** Receive register values from gdb */
+static void handle_G_command(const unsigned char* cmd, int len)
+{
+    uint32_t *p = (uint32_t *) &s_scratch.regfile;
+    for (int i = 0; i < sizeof(s_scratch.regfile) / sizeof(*p); ++i) {
+        *p++ = gdbstub_hton(esp_gdbstub_gethex(&cmd, 32));
+    }
+    esp_gdbstub_send_str_packet("OK");
+}
+
+/** Read memory to gdb */
+static void handle_m_command(const unsigned char* cmd, int len)
+{
+    intptr_t addr = (intptr_t) esp_gdbstub_gethex(&cmd, -1);
+    cmd++;
+    uint32_t size = esp_gdbstub_gethex(&cmd, -1);
+
+    if (esp_gdbstub_readmem(addr) < 0 || esp_gdbstub_readmem(addr + size - 1) < 0) {
+        esp_gdbstub_send_str_packet("E01");
+        return;
+    }
+
+    esp_gdbstub_send_start();
+    for (int i = 0; i < size; ++i) {
+        int b = esp_gdbstub_readmem(addr++);
+        esp_gdbstub_send_hex(b, 8);
+    }
+    esp_gdbstub_send_end();
+}
+
+/** Handle a command received from gdb */
+int esp_gdbstub_handle_command(unsigned char *cmd, int len)
+{
+    unsigned char *data = cmd + 1;
+    if (cmd[0] == 'g') 
+    {
+        handle_g_command(data, len - 1);
+    } else if (cmd[0] == 'G') {
+        /* receive content for all registers from gdb */
+        handle_G_command(data, len - 1);
+    } else if (cmd[0] == 'm') {
+        /* read memory to gdb */
+        handle_m_command(data, len - 1);
+    } else if (cmd[0] == '?') {
+        /* Reply with stop reason */
+        send_reason();
+#if CONFIG_ESP_GDBSTUB_SUPPORT_TASKS
+    } else if (s_scratch.state != GDBSTUB_TASK_SUPPORT_DISABLED) {
+        return handle_task_commands(cmd, len);
+#endif // CONFIG_ESP_GDBSTUB_SUPPORT_TASKS
+    } else {
+        /* Unrecognized command */
+        return GDBSTUB_ST_ERR;
+    }
+    return GDBSTUB_ST_OK;
+}
+
+/* Everything below is related to the support for listing FreeRTOS tasks as threads in GDB */
+
+#ifdef CONFIG_ESP_GDBSTUB_SUPPORT_TASKS
+
+static void init_task_info()
+{
+    unsigned tcb_size;
+    s_scratch.task_count = uxTaskGetSnapshotAll(s_scratch.tasks, GDBSTUB_TASKS_NUM, &tcb_size);
+}
+
+static bool get_task_handle(size_t index, TaskHandle_t *handle)
+{
+    if (index >= s_scratch.task_count) {
+        return false;
+    }
+    *handle = (TaskHandle_t) s_scratch.tasks[index].pxTCB;
+    return true;
+}
+
+/** Get the index of the task running on the current CPU, and save the result */
+static void find_paniced_task_index()
+{
+    TaskHandle_t cur_handle = xTaskGetCurrentTaskHandleForCPU(xPortGetCoreID());
+    TaskHandle_t handle;
+    for (int i = 0; i < s_scratch.task_count; i++) {
+        if (get_task_handle(i, &handle) && cur_handle == handle) {
+            s_scratch.paniced_task_index = i;
+            return;
+        }
+    }
+    s_scratch.paniced_task_index = GDBSTUB_CUR_TASK_INDEX_UNKNOWN;
+}
+
+/** H command sets the "current task" for the purpose of further commands */
+static void handle_H_command(const unsigned char* cmd, int len)
+{
+    if (cmd[1] == 'g' || cmd[1] == 'c') {
+        const char *ret = "OK";
+        cmd += 2;
+        int requested_task_index = esp_gdbstub_gethex(&cmd, -1);
+        if (requested_task_index == s_scratch.paniced_task_index ||
+                (requested_task_index == 0 && s_scratch.current_task_index == GDBSTUB_CUR_TASK_INDEX_UNKNOWN)) {
+            /* Get the registers of the paniced task */
+            esp_gdbstub_frame_to_regfile(&s_scratch.paniced_frame, &s_scratch.regfile);
+        } else if (requested_task_index > s_scratch.task_count) {
+            ret = "E00";
+        } else {
+            TaskHandle_t handle;
+            get_task_handle(requested_task_index, &handle);
+            /* FIXME: for the task currently running on the other CPU, extracting the registers from TCB
+             * isn't valid. Need to use some IPC mechanism to obtain the registers of the other CPU
+             */
+            esp_gdbstub_tcb_to_regfile(handle, &s_scratch.regfile);
+        }
+        esp_gdbstub_send_str_packet(ret);
+    } else {
+        esp_gdbstub_send_str_packet(NULL);
+    }
+}
+
+/** qC returns the current thread ID */
+static void handle_qC_command(const unsigned char* cmd, int len)
+{
+    esp_gdbstub_send_start();
+    esp_gdbstub_send_str("QC");
+    esp_gdbstub_send_hex(s_scratch.current_task_index, 32);
+    esp_gdbstub_send_end();
+}
+
+/** T command checks if the task is alive.
+ *  Since GDB isn't going to ask about the tasks which haven't been listed by q*ThreadInfo,
+ *  and the state of tasks can not change (no stepping allowed), simply return "OK" here.
+ */
+static void handle_T_command(const unsigned char* cmd, int len)
+{
+    esp_gdbstub_send_str_packet("OK");
+}
+
+/** qfThreadInfo requests the start of the thread list, qsThreadInfo (below) is repeated to
+ *  get the subsequent threads.
+ */
+static void handle_qfThreadInfo_command(const unsigned char* cmd, int len)
+{
+    /* The first task in qfThreadInfo reply is going to be the one which GDB will request to stop.
+     * Therefore it has to be the paniced task.
+     * Reply with the paniced task index, and later skip over this index while handling qsThreadInfo
+     */
+    esp_gdbstub_send_start();
+    esp_gdbstub_send_str("m");
+    esp_gdbstub_send_hex(s_scratch.paniced_task_index, 32);
+    esp_gdbstub_send_end();
+
+    s_scratch.thread_info_index = 0;
+}
+
+static void handle_qsThreadInfo_command(const unsigned char* cmd, int len)
+{
+    int next_task_index = ++s_scratch.thread_info_index;
+
+    if (next_task_index == s_scratch.task_count) {
+        /* No more tasks */
+        esp_gdbstub_send_str_packet("l");
+        return;
+    }
+
+    if (next_task_index == s_scratch.paniced_task_index) {
+        /* Have already sent this one in the reply to qfThreadInfo, skip over it */
+        handle_qsThreadInfo_command(cmd, len);
+        return;
+    }
+
+    esp_gdbstub_send_start();
+    esp_gdbstub_send_str("m");
+    esp_gdbstub_send_hex(next_task_index, 32);
+    esp_gdbstub_send_end();
+}
+
+/** qThreadExtraInfo requests the thread name */
+static void handle_qThreadExtraInfo_command(const unsigned char* cmd, int len)
+{
+    cmd += sizeof("qThreadExtraInfo,") - 1;
+    int task_index = esp_gdbstub_gethex(&cmd, -1);
+    TaskHandle_t handle;
+    if (!get_task_handle(task_index, &handle)) {
+        esp_gdbstub_send_str_packet("E01");
+        return;
+    }
+    esp_gdbstub_send_start();
+    const char* task_name = pcTaskGetTaskName(handle);
+    while (*task_name) {
+        esp_gdbstub_send_hex(*task_name, 8);
+        task_name++;
+    }
+    /** TODO: add "Running" or "Suspended" and "CPU0" or "CPU1" */
+    esp_gdbstub_send_end();
+}
+
+bool command_name_matches(const char* pattern, const unsigned char* ucmd, int len)
+{
+    const char* cmd = (const char*) ucmd;
+    const char* end = cmd + len;
+    for (; *pattern && cmd < end; ++cmd, ++pattern) {
+        if (*pattern == '?') {
+            continue;
+        }
+        if (*pattern != *cmd) {
+            return false;
+        }
+    }
+    return *pattern == 0 && (cmd == end || *cmd == ',');
+}
+
+/** Handle all the thread-related commands */
+static int handle_task_commands(unsigned char *cmd, int len)
+{
+    if (cmd[0] == 'H') {
+        /* Continue with task */
+        handle_H_command(cmd, len);
+    } else if (cmd[0] == 'T') {
+        /* Task alive check */
+        handle_T_command(cmd, len);
+    } else if (cmd[0] == 'q') {
+        if (command_name_matches("qfThreadInfo", cmd, len)) {
+            handle_qfThreadInfo_command(cmd, len);
+        } else if (command_name_matches("qsThreadInfo", cmd, len)) {
+            handle_qsThreadInfo_command(cmd, len);
+        } else if (command_name_matches("qC", cmd, len)) {
+            handle_qC_command(cmd, len);
+        } else if (command_name_matches("qThreadExtraInfo", cmd, len)) {
+            handle_qThreadExtraInfo_command(cmd, len);
+        } else {
+            /* Unrecognized command */
+            return GDBSTUB_ST_ERR;
+        }
+    } else {
+        /* Unrecognized command */
+        return GDBSTUB_ST_ERR;
+    }
+    return GDBSTUB_ST_OK;
+}
+
+#endif // CONFIG_ESP_GDBSTUB_SUPPORT_TASKS
+
diff --git a/components/esp_gdbstub/src/packet.c b/components/esp_gdbstub/src/packet.c
new file mode 100644 (file)
index 0000000..9bf0c5d
--- /dev/null
@@ -0,0 +1,177 @@
+// Copyright 2015-2019 Espressif Systems (Shanghai) PTE LTD
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "esp_gdbstub_common.h"
+
+// GDB command input buffer
+static unsigned char s_cmd[GDBSTUB_CMD_BUFLEN];
+
+// Running checksum of the output packet
+static char s_chsum;
+
+// Send the start of a packet; reset checksum calculation.
+void esp_gdbstub_send_start()
+{
+    s_chsum = 0;
+    esp_gdbstub_putchar('$');
+}
+
+// Send a char as part of a packet
+void esp_gdbstub_send_char(char c)
+{
+    if (c == '#' || c == '$' || c == '}' || c == '*') {
+        esp_gdbstub_putchar('}');
+        esp_gdbstub_putchar(c ^ 0x20);
+        s_chsum += (c ^ 0x20) + '}';
+    } else {
+        esp_gdbstub_putchar(c);
+        s_chsum += c;
+    }
+}
+
+// Send a string as part of a packet
+void esp_gdbstub_send_str(const char *c)
+{
+    while (*c != 0) {
+        esp_gdbstub_send_char(*c);
+        c++;
+    }
+}
+
+// Send a hex val as part of a packet.
+// 'bits'/4 dictates the number of hex chars sent.
+void esp_gdbstub_send_hex(int val, int bits)
+{
+    const char* hex_chars = "0123456789abcdef";
+    for (int i = bits; i > 0; i -= 4) {
+        esp_gdbstub_send_char(hex_chars[(val >> (i - 4)) & 0xf]);
+    }
+}
+
+// Finish sending a packet.
+void esp_gdbstub_send_end()
+{
+    esp_gdbstub_putchar('#');
+    esp_gdbstub_send_hex(s_chsum, 8);
+}
+
+// Send a packet with a string as content
+void esp_gdbstub_send_str_packet(const char* str)
+{
+    esp_gdbstub_send_start();
+    if (str != NULL) {
+        esp_gdbstub_send_str(str);
+    }
+    esp_gdbstub_send_end();
+}
+
+// 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.
+uint32_t esp_gdbstub_gethex(const unsigned char **ptr, int bits)
+{
+    int i;
+    int no;
+    uint32_t 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 GDBSTUB_ST_ENDPACKET;
+        } else {
+            if (bits == -1) {
+                (*ptr)--;
+                return v;
+            }
+            return GDBSTUB_ST_ERR;
+        }
+    }
+    return v;
+}
+
+
+// Lower layer: grab a command packet and check the checksum
+// Calls gdbHandleCommand on the packet if the checksum is OK
+// Returns GDBSTUB_ST_OK on success, GDBSTUB_ST_ERR when checksum fails, a
+// character if it is received instead of the GDB packet
+// start char.
+int esp_gdbstub_read_command(unsigned char **out_cmd, size_t *out_size)
+{
+    unsigned char c;
+    unsigned char chsum = 0;
+    unsigned char sentchs[2];
+    int p = 0;
+    c = esp_gdbstub_getchar();
+    if (c != '$') {
+        return c;
+    }
+    while (1) {
+        c = esp_gdbstub_getchar();
+        if (c == '#') {
+            // end of packet, checksum follows
+            s_cmd[p] = 0;
+            break;
+        }
+        chsum += c;
+        if (c == '$') {
+            // restart packet?
+            chsum = 0;
+            p = 0;
+            continue;
+        }
+        if (c == '}') {
+            //escape the next char
+            c = esp_gdbstub_getchar();
+            chsum += c;
+            c ^= 0x20;
+        }
+        s_cmd[p++] = c;
+        if (p >= GDBSTUB_CMD_BUFLEN) {
+            return GDBSTUB_ST_ERR;
+        }
+    }
+    // A # has been received. Get and check the received chsum.
+    sentchs[0] = esp_gdbstub_getchar();
+    sentchs[1] = esp_gdbstub_getchar();
+    const unsigned char* c_ptr = &sentchs[0];
+    unsigned char rchsum = esp_gdbstub_gethex(&c_ptr, 8);
+    if (rchsum != chsum) {
+        esp_gdbstub_putchar('-');
+        return GDBSTUB_ST_ERR;
+    } else {
+        esp_gdbstub_putchar('+');
+        *out_cmd = s_cmd;
+        *out_size = p;
+        return GDBSTUB_ST_OK;
+    }
+}
diff --git a/components/esp_gdbstub/xtensa/esp_gdbstub_arch.h b/components/esp_gdbstub/xtensa/esp_gdbstub_arch.h
new file mode 100644 (file)
index 0000000..18b119c
--- /dev/null
@@ -0,0 +1,91 @@
+// Copyright 2015-2019 Espressif Systems (Shanghai) PTE LTD
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#pragma once
+#include <stdint.h>
+#include "freertos/xtensa_context.h"
+#include "gdbstub_target_config.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef XtExcFrame esp_gdbstub_frame_t;
+
+/* GDB regfile structure, configuration dependent */
+typedef struct {
+    uint32_t pc;
+    uint32_t a[XCHAL_NUM_AREGS];
+
+#if XCHAL_HAVE_LOOPS
+    uint32_t lbeg;
+    uint32_t lend;
+    uint32_t lcount;
+#endif
+
+    uint32_t sar;
+
+#if XCHAL_HAVE_WINDOWED
+    uint32_t windowbase;
+    uint32_t windowstart;
+#endif
+
+    uint32_t configid0;
+    uint32_t configid1;
+    uint32_t ps;
+
+#if XCHAL_HAVE_THREADPTR
+    uint32_t threadptr;
+#endif
+
+#if XCHAL_HAVE_BOOLEANS
+    uint32_t br;
+#endif
+
+#if XCHAL_HAVE_S32C1I
+    uint32_t scompare1;
+#endif
+
+#if XCHAL_HAVE_MAC16
+    uint32_t acclo;
+    uint32_t acchi;
+    uint32_t m0;
+    uint32_t m1;
+    uint32_t m2;
+    uint32_t m3;
+#endif
+
+#if XCHAL_HAVE_DFP_ACCEL
+    uint32_t expstate;
+    uint32_t f64r_lo;
+    uint32_t f64r_hi;
+    uint32_t f64s;
+#endif
+
+#if XCHAL_HAVE_FP
+    uint32_t f[16];
+    uint32_t fcr;
+    uint32_t fsr;
+#endif
+
+#if GDBSTUB_EXTRA_TIE_SIZE > 0
+    uint32_t tie[GDBSTUB_EXTRA_TIE_SIZE];
+#endif
+
+} esp_gdbstub_gdb_regfile_t;
+
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/components/esp_gdbstub/xtensa/gdbstub_xtensa.c b/components/esp_gdbstub/xtensa/gdbstub_xtensa.c
new file mode 100644 (file)
index 0000000..853b1ba
--- /dev/null
@@ -0,0 +1,118 @@
+// Copyright 2015-2019 Espressif Systems (Shanghai) PTE LTD
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include <string.h>
+#include "esp_gdbstub.h"
+#include "esp_gdbstub_common.h"
+#include "soc/cpu.h"
+#include "soc/soc_memory_layout.h"
+#include "sdkconfig.h"
+
+#if !XCHAL_HAVE_WINDOWED
+#warning "gdbstub_xtensa: revisit the implementation for Call0 ABI"
+#endif
+
+static void init_regfile(esp_gdbstub_gdb_regfile_t *dst)
+{
+    memset(dst, 0, sizeof(*dst));
+}
+
+static void update_regfile_common(esp_gdbstub_gdb_regfile_t *dst)
+{
+    if (dst->a[0] & 0x8000000U) {
+        dst->a[0] = (dst->a[0] & 0x3fffffffU) | 0x40000000U;
+    }
+    if (!esp_stack_ptr_is_sane(dst->a[1])) {
+        dst->a[1] = 0xDEADBEEF;
+    }
+    dst->windowbase = 0;
+    dst->windowstart = 0x1;
+    RSR(CONFIGID0, dst->configid0);
+    RSR(CONFIGID1, dst->configid1);
+}
+
+void esp_gdbstub_frame_to_regfile(const esp_gdbstub_frame_t *frame, esp_gdbstub_gdb_regfile_t *dst)
+{
+    init_regfile(dst);
+    const uint32_t *a_regs = (const uint32_t *) &frame->a0;
+    dst->pc = (frame->pc & 0x3fffffffU) | 0x40000000U;
+
+    for (int i = 0; i < 16; i++) {
+        dst->a[i] = a_regs[i];
+    }
+    for (int i = 16; i < 64; i++) {
+        dst->a[i] = 0xDEADBEEF;
+    }
+
+#if XCHAL_HAVE_LOOPS
+    dst->lbeg = frame->lbeg;
+    dst->lend = frame->lend;
+    dst->lcount = frame->lcount;
+#endif
+
+    dst->ps = (frame->ps & PS_UM) ? (frame->ps & ~PS_EXCM) : frame->ps;
+    dst->sar = frame->sar;
+    update_regfile_common(dst);
+}
+
+#ifdef CONFIG_ESP_GDBSTUB_SUPPORT_TASKS
+
+static void solicited_frame_to_regfile(const XtSolFrame *frame, esp_gdbstub_gdb_regfile_t *dst)
+{
+    init_regfile(dst);
+    const uint32_t *a_regs = (const uint32_t *) &frame->a0;
+    dst->pc = (frame->pc & 0x3fffffffU) | 0x40000000U;
+
+    /* only 4 registers saved in the solicited frame */
+    for (int i = 0; i < 4; i++) {
+        dst->a[i] = a_regs[i];
+    }
+    for (int i = 4; i < 64; i++) {
+        dst->a[i] = 0xDEADBEEF;
+    }
+
+    dst->ps = (frame->ps & PS_UM) ? (frame->ps & ~PS_EXCM) : frame->ps;
+    update_regfile_common(dst);
+}
+
+/* Represents FreeRTOS TCB structure */
+typedef struct {
+    uint8_t *top_of_stack;
+    /* Other members aren't needed */
+} dummy_tcb_t;
+
+
+void esp_gdbstub_tcb_to_regfile(TaskHandle_t tcb, esp_gdbstub_gdb_regfile_t *dst)
+{
+    const dummy_tcb_t *dummy_tcb = (const dummy_tcb_t *) tcb;
+
+    const XtExcFrame *frame = (XtExcFrame *) dummy_tcb->top_of_stack;
+    if (frame->exit != 0) {
+        esp_gdbstub_frame_to_regfile(frame, dst);
+    } else {
+        const XtSolFrame *taskFrame = (const XtSolFrame *) dummy_tcb->top_of_stack;
+        solicited_frame_to_regfile(taskFrame, dst);
+    }
+}
+
+#endif // CONFIG_ESP_GDBSTUB_SUPPORT_TASKS
+
+int esp_gdbstub_get_signal(const esp_gdbstub_frame_t *frame)
+{
+    const char exccause_to_signal[] = {4, 31, 11, 11, 2, 6, 8, 0, 6, 7, 0, 0, 7, 7, 7, 7};
+    if (frame->exccause > sizeof(exccause_to_signal)) {
+        return 11;
+    }
+    return (int) exccause_to_signal[frame->exccause];
+}