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:
Ivan Grokhotkov 2019-03-06 11:12:46 +08:00
commit d474018725
2 changed files with 245 additions and 30 deletions

View file

@ -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

View file

@ -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);
}