From b797f05558f5c902f40ea4b9f24e31e1ffdf8106 Mon Sep 17 00:00:00 2001 From: X-Ryl669 Date: Tue, 11 Dec 2018 23:14:15 +0100 Subject: [PATCH] Add support for listing tasks to gdb in gdbstub MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit …and changing the active task and fetching each task's stack Merges https://github.com/espressif/esp-idf/pull/2828 --- components/esp32/gdbstub.c | 239 ++++++++++++++++++++++++++++++++----- 1 file changed, 208 insertions(+), 31 deletions(-) diff --git a/components/esp32/gdbstub.c b/components/esp32/gdbstub.c index df04c91992..500ecca150 100644 --- a/components/esp32/gdbstub.c +++ b/components/esp32/gdbstub.c @@ -13,16 +13,19 @@ // 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 (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 +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 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); } - -- 2.40.0