Add support for listing tasks to gdb in gdbstub

…and changing the active task and fetching each task's stack

Merges https://github.com/espressif/esp-idf/pull/2828
This commit is contained in:
X-Ryl669 2018-12-11 23:14:15 +01:00 committed by Ivan Grokhotkov
parent e053fdafc6
commit b797f05558

View File

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