forked from espressif/esp-idf
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:
committed by
Ivan Grokhotkov
parent
e053fdafc6
commit
b797f05558
@@ -13,16 +13,19 @@
|
|||||||
// limitations under the License.
|
// 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:
|
* 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 "rom/ets_sys.h"
|
||||||
#include "soc/uart_reg.h"
|
#include "soc/uart_reg.h"
|
||||||
#include "soc/io_mux_reg.h"
|
#include "soc/io_mux_reg.h"
|
||||||
#include "esp_gdbstub.h"
|
#include "esp_gdbstub.h"
|
||||||
|
#include "esp_panic.h"
|
||||||
#include "driver/gpio.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
|
//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.
|
//implies a minimum size of about 320 bytes.
|
||||||
@@ -33,6 +36,15 @@ static char chsum; //Running checksum of the output packet
|
|||||||
|
|
||||||
#define ATTR_GDBFN
|
#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.
|
//Receive a char from the uart. Uses polling and feeds the watchdog.
|
||||||
static int ATTR_GDBFN gdbRecvChar() {
|
static int ATTR_GDBFN gdbRecvChar() {
|
||||||
int i;
|
int i;
|
||||||
@@ -187,7 +199,7 @@ typedef struct {
|
|||||||
|
|
||||||
GdbRegFile gdbRegFile;
|
GdbRegFile gdbRegFile;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
//Register format as the Xtensa HAL has it:
|
//Register format as the Xtensa HAL has it:
|
||||||
STRUCT_FIELD (long, 4, XT_STK_EXIT, exit)
|
STRUCT_FIELD (long, 4, XT_STK_EXIT, exit)
|
||||||
STRUCT_FIELD (long, 4, XT_STK_PC, pc)
|
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_LBEG, lbeg)
|
||||||
STRUCT_FIELD (long, 4, XT_STK_LEND, lend)
|
STRUCT_FIELD (long, 4, XT_STK_LEND, lend)
|
||||||
STRUCT_FIELD (long, 4, XT_STK_LCOUNT, lcount)
|
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_TMP0, tmp0)
|
||||||
STRUCT_FIELD (long, 4, XT_STK_TMP1, tmp1)
|
STRUCT_FIELD (long, 4, XT_STK_TMP1, tmp1)
|
||||||
STRUCT_FIELD (long, 4, XT_STK_TMP2, tmp2)
|
STRUCT_FIELD (long, 4, XT_STK_TMP2, tmp2)
|
||||||
@@ -212,23 +224,16 @@ STRUCT_END(XtExcFrame)
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
static void dumpHwToRegfile(XtExcFrame *frame) {
|
//Remember the exception frame that caused panic since it's not saved in TCB
|
||||||
int i;
|
static XtExcFrame paniced_frame;
|
||||||
long *frameAregs=&frame->a0;
|
|
||||||
gdbRegFile.pc=frame->pc;
|
static void commonRegfile() {
|
||||||
for (i=0; i<16; i++) gdbRegFile.a[i]=frameAregs[i];
|
if (gdbRegFile.a[0] & 0x8000000U) gdbRegFile.a[0] = (gdbRegFile.a[0] & 0x3fffffffU) | 0x40000000U;
|
||||||
for (i=16; i<64; i++) gdbRegFile.a[i]=0xDEADBEEF;
|
if (!esp_stack_ptr_is_sane(gdbRegFile.a[1])) gdbRegFile.a[1] = 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;
|
|
||||||
gdbRegFile.windowbase=0; //0
|
gdbRegFile.windowbase=0; //0
|
||||||
gdbRegFile.windowstart=0x1; //1
|
gdbRegFile.windowstart=0x1; //1
|
||||||
gdbRegFile.configid0=0xdeadbeef; //ToDo
|
gdbRegFile.configid0=0xdeadbeef; //ToDo
|
||||||
gdbRegFile.configid1=0xdeadbeef; //ToDo
|
gdbRegFile.configid1=0xdeadbeef; //ToDo
|
||||||
gdbRegFile.ps=frame->ps-PS_EXCM_MASK;
|
|
||||||
gdbRegFile.threadptr=0xdeadbeef; //ToDo
|
gdbRegFile.threadptr=0xdeadbeef; //ToDo
|
||||||
gdbRegFile.br=0xdeadbeef; //ToDo
|
gdbRegFile.br=0xdeadbeef; //ToDo
|
||||||
gdbRegFile.scompare1=0xdeadbeef; //ToDo
|
gdbRegFile.scompare1=0xdeadbeef; //ToDo
|
||||||
@@ -238,9 +243,23 @@ static void dumpHwToRegfile(XtExcFrame *frame) {
|
|||||||
gdbRegFile.m1=0xdeadbeef; //ToDo
|
gdbRegFile.m1=0xdeadbeef; //ToDo
|
||||||
gdbRegFile.m2=0xdeadbeef; //ToDo
|
gdbRegFile.m2=0xdeadbeef; //ToDo
|
||||||
gdbRegFile.m3=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.
|
//Send the reason execution is stopped to GDB.
|
||||||
static void sendReason() {
|
static void sendReason() {
|
||||||
@@ -251,13 +270,90 @@ static void sendReason() {
|
|||||||
gdbPacketChar('T');
|
gdbPacketChar('T');
|
||||||
i=gdbRegFile.expstate&0x7f;
|
i=gdbRegFile.expstate&0x7f;
|
||||||
if (i<sizeof(exceptionSignal)) {
|
if (i<sizeof(exceptionSignal)) {
|
||||||
gdbPacketHex(exceptionSignal[i], 8);
|
gdbPacketHex(exceptionSignal[i], 8);
|
||||||
} else {
|
} else {
|
||||||
gdbPacketHex(11, 8);
|
gdbPacketHex(11, 8);
|
||||||
}
|
}
|
||||||
gdbPacketEnd();
|
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.
|
//Handle a command as received from GDB.
|
||||||
static int gdbHandleCommand(unsigned char *cmd, int len) {
|
static int gdbHandleCommand(unsigned char *cmd, int len) {
|
||||||
//Handle a command
|
//Handle a command
|
||||||
@@ -270,10 +366,8 @@ static int gdbHandleCommand(unsigned char *cmd, int len) {
|
|||||||
gdbPacketEnd();
|
gdbPacketEnd();
|
||||||
} else if (cmd[0]=='G') { //receive content for all registers from gdb
|
} else if (cmd[0]=='G') { //receive content for all registers from gdb
|
||||||
int *p=(int*)&gdbRegFile;
|
int *p=(int*)&gdbRegFile;
|
||||||
for (i=0; i<sizeof(GdbRegFile)/4; i++) *p++=iswap(gdbGetHexVal(&data, 32));;
|
for (i=0; i<sizeof(GdbRegFile)/4; i++) *p++=iswap(gdbGetHexVal(&data, 32));
|
||||||
gdbPacketStart();
|
sendPacket("OK");
|
||||||
gdbPacketStr("OK");
|
|
||||||
gdbPacketEnd();
|
|
||||||
} else if (cmd[0]=='m') { //read memory to gdb
|
} else if (cmd[0]=='m') { //read memory to gdb
|
||||||
i=gdbGetHexVal(&data, -1);
|
i=gdbGetHexVal(&data, -1);
|
||||||
data++;
|
data++;
|
||||||
@@ -285,19 +379,91 @@ static int gdbHandleCommand(unsigned char *cmd, int len) {
|
|||||||
gdbPacketEnd();
|
gdbPacketEnd();
|
||||||
} else if (cmd[0]=='?') { //Reply with stop reason
|
} else if (cmd[0]=='?') { //Reply with stop reason
|
||||||
sendReason();
|
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.
|
//We don't recognize or support whatever GDB just sent us.
|
||||||
gdbPacketStart();
|
return sendPacket(0);
|
||||||
gdbPacketEnd();
|
|
||||||
return ST_ERR;
|
|
||||||
}
|
|
||||||
return ST_OK;
|
return ST_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
//Lower layer: grab a command packet and check the checksum
|
//Lower layer: grab a command packet and check the checksum
|
||||||
//Calls gdbHandleCommand on the packet if the checksum is OK
|
//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
|
//character if it is received instead of the GDB packet
|
||||||
//start char.
|
//start char.
|
||||||
static int gdbReadCommand() {
|
static int gdbReadCommand() {
|
||||||
@@ -346,7 +512,19 @@ static int gdbReadCommand() {
|
|||||||
|
|
||||||
|
|
||||||
void esp_gdbstub_panic_handler(XtExcFrame *frame) {
|
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
|
//Make sure txd/rxd are enabled
|
||||||
gpio_pullup_dis(1);
|
gpio_pullup_dis(1);
|
||||||
PIN_FUNC_SELECT(PERIPHS_IO_MUX_U0RXD_U, FUNC_U0RXD_U0RXD);
|
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(gdbReadCommand()!=ST_CONT);
|
||||||
while(1);
|
while(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user