Merge branch 'feature/gdbstub_task_list' into 'master'
Add support for listing tasks to gdb in gdbstub Closes IDFGH-498 See merge request idf/esp-idf!4357
This commit is contained in:
commit
d474018725
2 changed files with 245 additions and 30 deletions
|
@ -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
|
||||
|
|
|
@ -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 <string.h>
|
||||
#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 (i<sizeof(exceptionSignal)) {
|
||||
gdbPacketHex(exceptionSignal[i], 8);
|
||||
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_GDBSTUB_SUPPORT_TASKS
|
||||
|
||||
#define STUB_TASKS_NUM CONFIG_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_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<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,11 +392,91 @@ static int gdbHandleCommand(unsigned char *cmd, int len) {
|
|||
gdbPacketEnd();
|
||||
} else if (cmd[0]=='?') { //Reply with stop reason
|
||||
sendReason();
|
||||
#if CONFIG_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_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);
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue