mirror of
https://github.com/espressif/esp-idf.git
synced 2024-09-20 00:36:01 -04:00
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:
parent
e053fdafc6
commit
b797f05558
@ -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);
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user