diff --git a/components/esp32/Kconfig b/components/esp32/Kconfig index 051c73326..ab73fbada 100644 --- a/components/esp32/Kconfig +++ b/components/esp32/Kconfig @@ -544,6 +544,23 @@ menu "ESP32-specific" of the crash. endchoice + config 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 GDBSTUB_MAX_TASKS + int "GDBStub: maximum number of tasks supported" + default 32 + depends on GDBSTUB_SUPPORT_TASKS + help + Set the number of tasks which GDB Stub will support. + config ESP32_DEBUG_OCDAWARE bool "Make exception and panic handlers JTAG/OCD aware" default y diff --git a/components/esp32/gdbstub.c b/components/esp32/gdbstub.c index df04c9199..a93b45992 100644 --- a/components/esp32/gdbstub.c +++ b/components/esp32/gdbstub.c @@ -13,16 +13,21 @@ // 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 #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" +#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. @@ -187,7 +192,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 +206,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) @@ -211,24 +216,13 @@ STRUCT_FIELD (long, 4, XT_STK_OVLY, ovly) 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; +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 +232,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 +259,114 @@ static void sendReason() { gdbPacketChar('T'); i=gdbRegFile.expstate&0x7f; if (ia0; + 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_GDBSTUB_SUPPORT_TASKS + //Handle a command as received from GDB. static int gdbHandleCommand(unsigned char *cmd, int len) { //Handle a command @@ -270,10 +379,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 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_GDBSTUB_SUPPORT_TASKS } else { //We don't recognize or support whatever GDB just sent us. - gdbPacketStart(); - gdbPacketEnd(); - return ST_ERR; + return sendPacket(NULL); } return ST_OK; } @@ -297,7 +484,7 @@ static int gdbHandleCommand(unsigned char *cmd, int len) { //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() { @@ -344,9 +531,21 @@ static int gdbReadCommand() { } - void esp_gdbstub_panic_handler(XtExcFrame *frame) { +#if CONFIG_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_GDBSTUB_SUPPORT_TASKS dumpHwToRegfile(frame); +#endif // CONFIG_GDBSTUB_SUPPORT_TASKS + //Make sure txd/rxd are enabled gpio_pullup_dis(1); PIN_FUNC_SELECT(PERIPHS_IO_MUX_U0RXD_U, FUNC_U0RXD_U0RXD); @@ -356,4 +555,3 @@ void esp_gdbstub_panic_handler(XtExcFrame *frame) { while(gdbReadCommand()!=ST_CONT); while(1); } -