]> granicus.if.org Git - esp-idf/commitdiff
Add support for listing tasks to gdb in gdbstub
authorX-Ryl669 <boite.pour.spam@gmail.com>
Tue, 11 Dec 2018 22:14:15 +0000 (23:14 +0100)
committerIvan Grokhotkov <ivan@espressif.com>
Tue, 5 Mar 2019 04:00:22 +0000 (12:00 +0800)
…and changing the active task and fetching each task's stack

Merges https://github.com/espressif/esp-idf/pull/2828

components/esp32/gdbstub.c

index df04c91992e8043a3d335ede300f7e68d5133228..500ecca15024a06ae31a72d885f3997a896e2260 100644 (file)
 // limitations under the License.
 
 /******************************************************************************
- * Description: A stub to make the ESP32 debuggable by GDB over the serial 
+ * 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 
+ * it allows inspecting the ESP32 state
  *******************************************************************************/
 
 #include "rom/ets_sys.h"
 #include "soc/uart_reg.h"
 #include "soc/io_mux_reg.h"
 #include "esp_gdbstub.h"
+#include "esp_panic.h"
 #include "driver/gpio.h"
+#include "freertos/FreeRTOS.h"
+#include "freertos/task.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.
@@ -33,6 +36,15 @@ static char chsum;                                           //Running checksum of the output packet
 
 #define ATTR_GDBFN
 
+//If you want more than (default) 32 tasks to be dumped, you'll need to enable the coredump feature and specify
+//here your amount.
+#ifdef CONFIG_ESP32_CORE_DUMP_MAX_TASKS_NUM
+       #define STUB_TASKS_NUM CONFIG_ESP32_CORE_DUMP_MAX_TASKS_NUM
+#else
+       #define STUB_TASKS_NUM 32
+#endif
+
+
 //Receive a char from the uart. Uses polling and feeds the watchdog.
 static int ATTR_GDBFN gdbRecvChar() {
        int i;
@@ -187,7 +199,7 @@ typedef struct {
 
 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)
@@ -201,7 +213,7 @@ 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 
+// 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)
@@ -212,23 +224,16 @@ STRUCT_END(XtExcFrame)
 */
 
 
-static void dumpHwToRegfile(XtExcFrame *frame) {
-       int i;
-       long *frameAregs=&frame->a0;
-       gdbRegFile.pc=frame->pc;
-       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.sar=frame->sar;
-       //All windows have been spilled to the stack by the ISR routines. The following values should indicate that.
-       gdbRegFile.sar=frame->sar;
+//Remember the exception frame that caused panic since it's not saved in TCB
+static XtExcFrame paniced_frame;
+
+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.ps=frame->ps-PS_EXCM_MASK;
        gdbRegFile.threadptr=0xdeadbeef; //ToDo
        gdbRegFile.br=0xdeadbeef; //ToDo
        gdbRegFile.scompare1=0xdeadbeef; //ToDo
@@ -238,9 +243,23 @@ static void dumpHwToRegfile(XtExcFrame *frame) {
        gdbRegFile.m1=0xdeadbeef; //ToDo
        gdbRegFile.m2=0xdeadbeef; //ToDo
        gdbRegFile.m3=0xdeadbeef; //ToDo
-       gdbRegFile.expstate=frame->exccause; //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() {
@@ -251,13 +270,90 @@ static void sendReason() {
        gdbPacketChar('T');
        i=gdbRegFile.expstate&0x7f;
        if (i<sizeof(exceptionSignal)) {
-               gdbPacketHex(exceptionSignal[i], 8); 
+               gdbPacketHex(exceptionSignal[i], 8);
        } else {
                gdbPacketHex(11, 8);
        }
        gdbPacketEnd();
 }
 
+static int reenteredHandler = 0;
+static int sendPacket(const char * text) {
+       gdbPacketStart();
+       if (text != NULL) gdbPacketStr(text);
+       gdbPacketEnd();
+       return ST_OK;
+}
+
+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
+static unsigned getAllTasksHandle(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);
+       }
+}
+
+static int findCurrentTaskIndex() {
+       static int curTaskIndex = -2;
+    if (curTaskIndex == -2) {
+               unsigned curHandle = (unsigned)xTaskGetCurrentTaskHandleForCPU(xPortGetCoreID()), handle, count = getAllTasksHandle(0, 0, 0, 0);
+               for(int k=0; k<(int)count; k++) {
+                       if (getAllTasksHandle(k, &handle, 0, 0) && curHandle == handle)
+                               return curTaskIndex = k;
+               }
+               curTaskIndex = -1;
+       }
+       return curTaskIndex;
+}
+
 //Handle a command as received from GDB.
 static int gdbHandleCommand(unsigned char *cmd, int len) {
        //Handle a command
@@ -270,10 +366,8 @@ static int gdbHandleCommand(unsigned char *cmd, int len) {
                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));;
-               gdbPacketStart();
-               gdbPacketStr("OK");
-               gdbPacketEnd();
+               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++;
@@ -285,19 +379,91 @@ static int gdbHandleCommand(unsigned char *cmd, int len) {
                gdbPacketEnd();
        } else if (cmd[0]=='?') {       //Reply with stop reason
                sendReason();
-       } else {
+       } else if (reenteredHandler != -1) {
+               if (cmd[0]=='H') { //Continue with task
+                       if (cmd[1]=='g' || cmd[1]=='c') {
+                               const char * ret = "OK";
+                               data++;
+                               i=gdbGetHexVal(&data, -1);
+                               reenteredHandler = 1; //Hg0 is the first packet received after connect
+                               j = findCurrentTaskIndex();
+                               if (i == j || (j == -1 && !i)) { //The first task requested must be the one that crashed and it does not have a valid TCB anyway since it's not saved yet
+                                       dumpHwToRegfile(&paniced_frame);
+                               } else {
+                                       unsigned handle, count;
+                                       //Get the handle for that task
+                                       count = getAllTasksHandle(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 = getAllTasksHandle(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 = getAllTasksHandle(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;
+                                       reenteredHandler = 1;   //It seems it's the first request GDB is sending
+                               }
+                               tCount = getAllTasksHandle(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 != -1) {
+                                       gdbPacketStr("QC");
+                                       gdbPacketHex(k, 32);
+                               } else gdbPacketStr("bad");
+                               gdbPacketEnd();
+                               return ST_OK;
+                       }
+                       return sendPacket(0);
+               }
+       } else
                //We don't recognize or support whatever GDB just sent us.
-               gdbPacketStart();
-               gdbPacketEnd();
-               return ST_ERR;
-       }
+               return sendPacket(0);
        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 
+//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() {
@@ -346,7 +512,19 @@ static int gdbReadCommand() {
 
 
 void esp_gdbstub_panic_handler(XtExcFrame *frame) {
-       dumpHwToRegfile(frame);
+       if (reenteredHandler == 1) {
+               //Prevent using advanced FreeRTOS features since they seems to crash
+               gdbPacketEnd(); // Ends up any pending GDB packet (this creates a garbage value)
+               reenteredHandler = -1;
+       } else {
+               //Need to remember the frame that panic'd since gdb will ask for all thread before ours
+           uint8_t * pf = (uint8_t*)&paniced_frame, *f = (uint8_t*)frame;
+               //Could not use memcpy here
+           for(int i = 0; i < sizeof(*frame); i++) pf[i] = f[i];
+       }
+       //Let's start from something
+       dumpHwToRegfile(&paniced_frame);
+
        //Make sure txd/rxd are enabled
        gpio_pullup_dis(1);
        PIN_FUNC_SELECT(PERIPHS_IO_MUX_U0RXD_U, FUNC_U0RXD_U0RXD);
@@ -356,4 +534,3 @@ void esp_gdbstub_panic_handler(XtExcFrame *frame) {
        while(gdbReadCommand()!=ST_CONT);
        while(1);
 }
-