mirror of
https://github.com/espressif/esp-idf.git
synced 2024-10-05 20:47:46 -04:00
Merge branch 'feature/esp32s2beta_gdbstub' into 'feature/esp32s2beta'
esp32s2beta: add gdbstub support See merge request espressif/esp-idf!5568
This commit is contained in:
commit
3f49d71258
@ -19,7 +19,6 @@ elseif(CONFIG_IDF_TARGET_ESP32)
|
||||
"esp_adapter.c"
|
||||
"esp_timer_esp32.c"
|
||||
"esp_himem.c"
|
||||
"gdbstub.c"
|
||||
"hw_random.c"
|
||||
"int_wdt.c"
|
||||
"intr_alloc.c"
|
||||
|
@ -342,6 +342,7 @@ menu "ESP32-specific"
|
||||
|
||||
config ESP32_PANIC_GDBSTUB
|
||||
bool "Invoke GDBStub"
|
||||
select ESP_GDBSTUB_ENABLED
|
||||
help
|
||||
Invoke gdbstub on the serial port, allowing for gdb to attach to it to do a postmortem
|
||||
of the crash.
|
||||
|
@ -1,557 +0,0 @@
|
||||
// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
/******************************************************************************
|
||||
* 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
|
||||
*******************************************************************************/
|
||||
|
||||
#include <string.h>
|
||||
#include "esp32/rom/ets_sys.h"
|
||||
#include "soc/uart_periph.h"
|
||||
#include "soc/gpio_periph.h"
|
||||
#include "esp_private/gdbstub.h"
|
||||
#include "esp_debug_helpers.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.
|
||||
#define PBUFLEN 512
|
||||
|
||||
static unsigned char cmd[PBUFLEN]; //GDB command input buffer
|
||||
static char chsum; //Running checksum of the output packet
|
||||
|
||||
#define ATTR_GDBFN
|
||||
|
||||
//Receive a char from the uart. Uses polling and feeds the watchdog.
|
||||
static int ATTR_GDBFN gdbRecvChar() {
|
||||
int i;
|
||||
while (((READ_PERI_REG(UART_STATUS_REG(0))>>UART_RXFIFO_CNT_S)&UART_RXFIFO_CNT)==0) ;
|
||||
i=READ_PERI_REG(UART_FIFO_REG(0));
|
||||
return i;
|
||||
}
|
||||
|
||||
//Send a char to the uart.
|
||||
static void ATTR_GDBFN gdbSendChar(char c) {
|
||||
while (((READ_PERI_REG(UART_STATUS_REG(0))>>UART_TXFIFO_CNT_S)&UART_TXFIFO_CNT)>=126) ;
|
||||
WRITE_PERI_REG(UART_FIFO_REG(0), c);
|
||||
}
|
||||
|
||||
//Send the start of a packet; reset checksum calculation.
|
||||
static void ATTR_GDBFN gdbPacketStart() {
|
||||
chsum=0;
|
||||
gdbSendChar('$');
|
||||
}
|
||||
|
||||
//Send a char as part of a packet
|
||||
static void ATTR_GDBFN gdbPacketChar(char c) {
|
||||
if (c=='#' || c=='$' || c=='}' || c=='*') {
|
||||
gdbSendChar('}');
|
||||
gdbSendChar(c^0x20);
|
||||
chsum+=(c^0x20)+'}';
|
||||
} else {
|
||||
gdbSendChar(c);
|
||||
chsum+=c;
|
||||
}
|
||||
}
|
||||
|
||||
//Send a string as part of a packet
|
||||
static void ATTR_GDBFN gdbPacketStr(const char *c) {
|
||||
while (*c!=0) {
|
||||
gdbPacketChar(*c);
|
||||
c++;
|
||||
}
|
||||
}
|
||||
|
||||
//Send a hex val as part of a packet. 'bits'/4 dictates the number of hex chars sent.
|
||||
static void ATTR_GDBFN gdbPacketHex(int val, int bits) {
|
||||
char hexChars[]="0123456789abcdef";
|
||||
int i;
|
||||
for (i=bits; i>0; i-=4) {
|
||||
gdbPacketChar(hexChars[(val>>(i-4))&0xf]);
|
||||
}
|
||||
}
|
||||
|
||||
//Finish sending a packet.
|
||||
static void ATTR_GDBFN gdbPacketEnd() {
|
||||
gdbSendChar('#');
|
||||
gdbPacketHex(chsum, 8);
|
||||
}
|
||||
|
||||
//Error states used by the routines that grab stuff from the incoming gdb packet
|
||||
#define ST_ENDPACKET -1
|
||||
#define ST_ERR -2
|
||||
#define ST_OK -3
|
||||
#define ST_CONT -4
|
||||
|
||||
//Grab a hex value from the gdb packet. Ptr will get positioned on the end
|
||||
//of the hex string, as far as the routine has read into it. Bits/4 indicates
|
||||
//the max amount of hex chars it gobbles up. Bits can be -1 to eat up as much
|
||||
//hex chars as possible.
|
||||
static long ATTR_GDBFN gdbGetHexVal(unsigned char **ptr, int bits) {
|
||||
int i;
|
||||
int no;
|
||||
unsigned int v=0;
|
||||
char c;
|
||||
no=bits/4;
|
||||
if (bits==-1) no=64;
|
||||
for (i=0; i<no; i++) {
|
||||
c=**ptr;
|
||||
(*ptr)++;
|
||||
if (c>='0' && c<='9') {
|
||||
v<<=4;
|
||||
v|=(c-'0');
|
||||
} else if (c>='A' && c<='F') {
|
||||
v<<=4;
|
||||
v|=(c-'A')+10;
|
||||
} else if (c>='a' && c<='f') {
|
||||
v<<=4;
|
||||
v|=(c-'a')+10;
|
||||
} else if (c=='#') {
|
||||
if (bits==-1) {
|
||||
(*ptr)--;
|
||||
return v;
|
||||
}
|
||||
return ST_ENDPACKET;
|
||||
} else {
|
||||
if (bits==-1) {
|
||||
(*ptr)--;
|
||||
return v;
|
||||
}
|
||||
return ST_ERR;
|
||||
}
|
||||
}
|
||||
return v;
|
||||
}
|
||||
|
||||
//Swap an int into the form gdb wants it
|
||||
static int ATTR_GDBFN iswap(int i) {
|
||||
int r;
|
||||
r=((i>>24)&0xff);
|
||||
r|=((i>>16)&0xff)<<8;
|
||||
r|=((i>>8)&0xff)<<16;
|
||||
r|=((i>>0)&0xff)<<24;
|
||||
return r;
|
||||
}
|
||||
|
||||
//Read a byte from ESP32 memory.
|
||||
static unsigned char ATTR_GDBFN readbyte(unsigned int p) {
|
||||
int *i=(int*)(p&(~3));
|
||||
if (p<0x20000000 || p>=0x80000000) return -1;
|
||||
return *i>>((p&3)*8);
|
||||
}
|
||||
|
||||
|
||||
//Register file in the format exp108 gdb port expects it.
|
||||
//Inspired by gdb/regformats/reg-xtensa.dat
|
||||
typedef struct {
|
||||
uint32_t pc;
|
||||
uint32_t a[64];
|
||||
uint32_t lbeg;
|
||||
uint32_t lend;
|
||||
uint32_t lcount;
|
||||
uint32_t sar;
|
||||
uint32_t windowbase;
|
||||
uint32_t windowstart;
|
||||
uint32_t configid0;
|
||||
uint32_t configid1;
|
||||
uint32_t ps;
|
||||
uint32_t threadptr;
|
||||
uint32_t br;
|
||||
uint32_t scompare1;
|
||||
uint32_t acclo;
|
||||
uint32_t acchi;
|
||||
uint32_t m0;
|
||||
uint32_t m1;
|
||||
uint32_t m2;
|
||||
uint32_t m3;
|
||||
uint32_t expstate; //I'm going to assume this is exccause...
|
||||
uint32_t f64r_lo;
|
||||
uint32_t f64r_hi;
|
||||
uint32_t f64s;
|
||||
uint32_t f[16];
|
||||
uint32_t fcr;
|
||||
uint32_t fsr;
|
||||
} GdbRegFile;
|
||||
|
||||
|
||||
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)
|
||||
STRUCT_FIELD (long, 4, XT_STK_PS, ps)
|
||||
STRUCT_FIELD (long, 4, XT_STK_A0, a0)
|
||||
[..]
|
||||
STRUCT_FIELD (long, 4, XT_STK_A15, a15)
|
||||
STRUCT_FIELD (long, 4, XT_STK_SAR, sar)
|
||||
STRUCT_FIELD (long, 4, XT_STK_EXCCAUSE, exccause)
|
||||
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
|
||||
STRUCT_FIELD (long, 4, XT_STK_TMP0, tmp0)
|
||||
STRUCT_FIELD (long, 4, XT_STK_TMP1, tmp1)
|
||||
STRUCT_FIELD (long, 4, XT_STK_TMP2, tmp2)
|
||||
STRUCT_FIELD (long, 4, XT_STK_VPRI, vpri)
|
||||
STRUCT_FIELD (long, 4, XT_STK_OVLY, ovly)
|
||||
#endif
|
||||
STRUCT_END(XtExcFrame)
|
||||
*/
|
||||
|
||||
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.threadptr=0xdeadbeef; //ToDo
|
||||
gdbRegFile.br=0xdeadbeef; //ToDo
|
||||
gdbRegFile.scompare1=0xdeadbeef; //ToDo
|
||||
gdbRegFile.acclo=0xdeadbeef; //ToDo
|
||||
gdbRegFile.acchi=0xdeadbeef; //ToDo
|
||||
gdbRegFile.m0=0xdeadbeef; //ToDo
|
||||
gdbRegFile.m1=0xdeadbeef; //ToDo
|
||||
gdbRegFile.m2=0xdeadbeef; //ToDo
|
||||
gdbRegFile.m3=0xdeadbeef; //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() {
|
||||
//exception-to-signal mapping
|
||||
char exceptionSignal[]={4,31,11,11,2,6,8,0,6,7,0,0,7,7,7,7};
|
||||
int i=0;
|
||||
gdbPacketStart();
|
||||
gdbPacketChar('T');
|
||||
i=gdbRegFile.expstate&0x7f;
|
||||
if (i<sizeof(exceptionSignal)) {
|
||||
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_ESP_GDBSTUB_SUPPORT_TASKS
|
||||
|
||||
#define STUB_TASKS_NUM CONFIG_ESP_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_ESP_GDBSTUB_SUPPORT_TASKS
|
||||
|
||||
//Handle a command as received from GDB.
|
||||
static int gdbHandleCommand(unsigned char *cmd, int len) {
|
||||
//Handle a command
|
||||
int i, j, k;
|
||||
unsigned char *data=cmd+1;
|
||||
if (cmd[0]=='g') { //send all registers to gdb
|
||||
int *p=(int*)&gdbRegFile;
|
||||
gdbPacketStart();
|
||||
for (i=0; i<sizeof(GdbRegFile)/4; i++) gdbPacketHex(iswap(*p++), 32);
|
||||
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));
|
||||
sendPacket("OK");
|
||||
} else if (cmd[0]=='m') { //read memory to gdb
|
||||
i=gdbGetHexVal(&data, -1);
|
||||
data++;
|
||||
j=gdbGetHexVal(&data, -1);
|
||||
gdbPacketStart();
|
||||
for (k=0; k<j; k++) {
|
||||
gdbPacketHex(readbyte(i++), 8);
|
||||
}
|
||||
gdbPacketEnd();
|
||||
} else if (cmd[0]=='?') { //Reply with stop reason
|
||||
sendReason();
|
||||
#if CONFIG_ESP_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_ESP_GDBSTUB_SUPPORT_TASKS
|
||||
} else {
|
||||
//We don't recognize or support whatever GDB just sent us.
|
||||
return sendPacket(NULL);
|
||||
}
|
||||
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
|
||||
//character if it is received instead of the GDB packet
|
||||
//start char.
|
||||
static int gdbReadCommand() {
|
||||
unsigned char c;
|
||||
unsigned char chsum=0, rchsum;
|
||||
unsigned char sentchs[2];
|
||||
int p=0;
|
||||
unsigned char *ptr;
|
||||
c=gdbRecvChar();
|
||||
if (c!='$') return c;
|
||||
while(1) {
|
||||
c=gdbRecvChar();
|
||||
if (c=='#') { //end of packet, checksum follows
|
||||
cmd[p]=0;
|
||||
break;
|
||||
}
|
||||
chsum+=c;
|
||||
if (c=='$') {
|
||||
//Wut, restart packet?
|
||||
chsum=0;
|
||||
p=0;
|
||||
continue;
|
||||
}
|
||||
if (c=='}') { //escape the next char
|
||||
c=gdbRecvChar();
|
||||
chsum+=c;
|
||||
c^=0x20;
|
||||
}
|
||||
cmd[p++]=c;
|
||||
if (p>=PBUFLEN) return ST_ERR;
|
||||
}
|
||||
//A # has been received. Get and check the received chsum.
|
||||
sentchs[0]=gdbRecvChar();
|
||||
sentchs[1]=gdbRecvChar();
|
||||
ptr=&sentchs[0];
|
||||
rchsum=gdbGetHexVal(&ptr, 8);
|
||||
if (rchsum!=chsum) {
|
||||
gdbSendChar('-');
|
||||
return ST_ERR;
|
||||
} else {
|
||||
gdbSendChar('+');
|
||||
return gdbHandleCommand(cmd, p);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void esp_gdbstub_panic_handler(XtExcFrame *frame) {
|
||||
#if CONFIG_ESP_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_ESP_GDBSTUB_SUPPORT_TASKS
|
||||
dumpHwToRegfile(frame);
|
||||
#endif // CONFIG_ESP_GDBSTUB_SUPPORT_TASKS
|
||||
|
||||
//Make sure txd/rxd are enabled
|
||||
gpio_pullup_dis(1);
|
||||
PIN_FUNC_SELECT(PERIPHS_IO_MUX_U0RXD_U, FUNC_U0RXD_U0RXD);
|
||||
PIN_FUNC_SELECT(PERIPHS_IO_MUX_U0TXD_U, FUNC_U0TXD_U0TXD);
|
||||
|
||||
sendReason();
|
||||
while(gdbReadCommand()!=ST_CONT);
|
||||
while(1);
|
||||
}
|
@ -19,7 +19,6 @@ elseif(CONFIG_IDF_TARGET_ESP32S2BETA)
|
||||
"dport_panic_highint_hdl.S"
|
||||
"esp_adapter.c"
|
||||
"esp_timer_esp32s2beta.c"
|
||||
"gdbstub.c"
|
||||
"hw_random.c"
|
||||
"int_wdt.c"
|
||||
"intr_alloc.c"
|
||||
|
@ -317,6 +317,7 @@ menu "ESP32S2-specific"
|
||||
|
||||
config ESP32S2_PANIC_GDBSTUB
|
||||
bool "Invoke GDBStub"
|
||||
select ESP_GDBSTUB_ENABLED
|
||||
help
|
||||
Invoke gdbstub on the serial port, allowing for gdb to attach to it to do a postmortem
|
||||
of the crash.
|
||||
|
@ -1,356 +0,0 @@
|
||||
// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
/******************************************************************************
|
||||
* 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
|
||||
*******************************************************************************/
|
||||
|
||||
#include "esp32s2beta/rom/ets_sys.h"
|
||||
#include "soc/uart_reg.h"
|
||||
#include "soc/io_mux_reg.h"
|
||||
#include "esp_private/gdbstub.h"
|
||||
#include "driver/gpio.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.
|
||||
#define PBUFLEN 512
|
||||
|
||||
static unsigned char cmd[PBUFLEN]; //GDB command input buffer
|
||||
static char chsum; //Running checksum of the output packet
|
||||
|
||||
#define ATTR_GDBFN
|
||||
|
||||
//Receive a char from the uart. Uses polling and feeds the watchdog.
|
||||
static int ATTR_GDBFN gdbRecvChar() {
|
||||
int i;
|
||||
while (((READ_PERI_REG(UART_STATUS_REG(0))>>UART_RXFIFO_CNT_S)&UART_RXFIFO_CNT)==0) ;
|
||||
i=READ_PERI_REG(UART_FIFO_AHB_REG(0));
|
||||
return i;
|
||||
}
|
||||
|
||||
//Send a char to the uart.
|
||||
static void ATTR_GDBFN gdbSendChar(char c) {
|
||||
while (((READ_PERI_REG(UART_STATUS_REG(0))>>UART_TXFIFO_CNT_S)&UART_TXFIFO_CNT)>=126) ;
|
||||
WRITE_PERI_REG(UART_FIFO_AHB_REG(0), c);
|
||||
}
|
||||
|
||||
//Send the start of a packet; reset checksum calculation.
|
||||
static void ATTR_GDBFN gdbPacketStart() {
|
||||
chsum=0;
|
||||
gdbSendChar('$');
|
||||
}
|
||||
|
||||
//Send a char as part of a packet
|
||||
static void ATTR_GDBFN gdbPacketChar(char c) {
|
||||
if (c=='#' || c=='$' || c=='}' || c=='*') {
|
||||
gdbSendChar('}');
|
||||
gdbSendChar(c^0x20);
|
||||
chsum+=(c^0x20)+'}';
|
||||
} else {
|
||||
gdbSendChar(c);
|
||||
chsum+=c;
|
||||
}
|
||||
}
|
||||
|
||||
//Send a string as part of a packet
|
||||
static void ATTR_GDBFN gdbPacketStr(const char *c) {
|
||||
while (*c!=0) {
|
||||
gdbPacketChar(*c);
|
||||
c++;
|
||||
}
|
||||
}
|
||||
|
||||
//Send a hex val as part of a packet. 'bits'/4 dictates the number of hex chars sent.
|
||||
static void ATTR_GDBFN gdbPacketHex(int val, int bits) {
|
||||
char hexChars[]="0123456789abcdef";
|
||||
int i;
|
||||
for (i=bits; i>0; i-=4) {
|
||||
gdbPacketChar(hexChars[(val>>(i-4))&0xf]);
|
||||
}
|
||||
}
|
||||
|
||||
//Finish sending a packet.
|
||||
static void ATTR_GDBFN gdbPacketEnd() {
|
||||
gdbSendChar('#');
|
||||
gdbPacketHex(chsum, 8);
|
||||
}
|
||||
|
||||
//Error states used by the routines that grab stuff from the incoming gdb packet
|
||||
#define ST_ENDPACKET -1
|
||||
#define ST_ERR -2
|
||||
#define ST_OK -3
|
||||
#define ST_CONT -4
|
||||
|
||||
//Grab a hex value from the gdb packet. Ptr will get positioned on the end
|
||||
//of the hex string, as far as the routine has read into it. Bits/4 indicates
|
||||
//the max amount of hex chars it gobbles up. Bits can be -1 to eat up as much
|
||||
//hex chars as possible.
|
||||
static long ATTR_GDBFN gdbGetHexVal(unsigned char **ptr, int bits) {
|
||||
int i;
|
||||
int no;
|
||||
unsigned int v=0;
|
||||
char c;
|
||||
no=bits/4;
|
||||
if (bits==-1) no=64;
|
||||
for (i=0; i<no; i++) {
|
||||
c=**ptr;
|
||||
(*ptr)++;
|
||||
if (c>='0' && c<='9') {
|
||||
v<<=4;
|
||||
v|=(c-'0');
|
||||
} else if (c>='A' && c<='F') {
|
||||
v<<=4;
|
||||
v|=(c-'A')+10;
|
||||
} else if (c>='a' && c<='f') {
|
||||
v<<=4;
|
||||
v|=(c-'a')+10;
|
||||
} else if (c=='#') {
|
||||
if (bits==-1) {
|
||||
(*ptr)--;
|
||||
return v;
|
||||
}
|
||||
return ST_ENDPACKET;
|
||||
} else {
|
||||
if (bits==-1) {
|
||||
(*ptr)--;
|
||||
return v;
|
||||
}
|
||||
return ST_ERR;
|
||||
}
|
||||
}
|
||||
return v;
|
||||
}
|
||||
|
||||
//Swap an int into the form gdb wants it
|
||||
static int ATTR_GDBFN iswap(int i) {
|
||||
int r;
|
||||
r=((i>>24)&0xff);
|
||||
r|=((i>>16)&0xff)<<8;
|
||||
r|=((i>>8)&0xff)<<16;
|
||||
r|=((i>>0)&0xff)<<24;
|
||||
return r;
|
||||
}
|
||||
|
||||
//Read a byte from ESP32 memory.
|
||||
static unsigned char ATTR_GDBFN readbyte(unsigned int p) {
|
||||
int *i=(int*)(p&(~3));
|
||||
if (p<0x20000000 || p>=0x80000000) return -1;
|
||||
return *i>>((p&3)*8);
|
||||
}
|
||||
|
||||
|
||||
//Register file in the format exp108 gdb port expects it.
|
||||
//Inspired by gdb/regformats/reg-xtensa.dat
|
||||
typedef struct {
|
||||
uint32_t pc;
|
||||
uint32_t a[64];
|
||||
uint32_t lbeg;
|
||||
uint32_t lend;
|
||||
uint32_t lcount;
|
||||
uint32_t sar;
|
||||
uint32_t windowbase;
|
||||
uint32_t windowstart;
|
||||
uint32_t configid0;
|
||||
uint32_t configid1;
|
||||
uint32_t ps;
|
||||
uint32_t threadptr;
|
||||
uint32_t br;
|
||||
uint32_t scompare1;
|
||||
uint32_t acclo;
|
||||
uint32_t acchi;
|
||||
uint32_t m0;
|
||||
uint32_t m1;
|
||||
uint32_t m2;
|
||||
uint32_t m3;
|
||||
uint32_t expstate; //I'm going to assume this is exccause...
|
||||
uint32_t f64r_lo;
|
||||
uint32_t f64r_hi;
|
||||
uint32_t f64s;
|
||||
uint32_t f[16];
|
||||
uint32_t fcr;
|
||||
uint32_t fsr;
|
||||
} GdbRegFile;
|
||||
|
||||
|
||||
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)
|
||||
STRUCT_FIELD (long, 4, XT_STK_PS, ps)
|
||||
STRUCT_FIELD (long, 4, XT_STK_A0, a0)
|
||||
[..]
|
||||
STRUCT_FIELD (long, 4, XT_STK_A15, a15)
|
||||
STRUCT_FIELD (long, 4, XT_STK_SAR, sar)
|
||||
STRUCT_FIELD (long, 4, XT_STK_EXCCAUSE, exccause)
|
||||
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
|
||||
STRUCT_FIELD (long, 4, XT_STK_TMP0, tmp0)
|
||||
STRUCT_FIELD (long, 4, XT_STK_TMP1, tmp1)
|
||||
STRUCT_FIELD (long, 4, XT_STK_TMP2, tmp2)
|
||||
STRUCT_FIELD (long, 4, XT_STK_VPRI, vpri)
|
||||
STRUCT_FIELD (long, 4, XT_STK_OVLY, ovly)
|
||||
#endif
|
||||
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.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.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
|
||||
gdbRegFile.acclo=0xdeadbeef; //ToDo
|
||||
gdbRegFile.acchi=0xdeadbeef; //ToDo
|
||||
gdbRegFile.m0=0xdeadbeef; //ToDo
|
||||
gdbRegFile.m1=0xdeadbeef; //ToDo
|
||||
gdbRegFile.m2=0xdeadbeef; //ToDo
|
||||
gdbRegFile.m3=0xdeadbeef; //ToDo
|
||||
gdbRegFile.expstate=frame->exccause; //ToDo
|
||||
}
|
||||
|
||||
|
||||
//Send the reason execution is stopped to GDB.
|
||||
static void sendReason() {
|
||||
//exception-to-signal mapping
|
||||
char exceptionSignal[]={4,31,11,11,2,6,8,0,6,7,0,0,7,7,7,7};
|
||||
int i=0;
|
||||
gdbPacketStart();
|
||||
gdbPacketChar('T');
|
||||
i=gdbRegFile.expstate&0x7f;
|
||||
if (i<sizeof(exceptionSignal)) {
|
||||
gdbPacketHex(exceptionSignal[i], 8);
|
||||
} else {
|
||||
gdbPacketHex(11, 8);
|
||||
}
|
||||
gdbPacketEnd();
|
||||
}
|
||||
|
||||
//Handle a command as received from GDB.
|
||||
static int gdbHandleCommand(unsigned char *cmd, int len) {
|
||||
//Handle a command
|
||||
int i, j, k;
|
||||
unsigned char *data=cmd+1;
|
||||
if (cmd[0]=='g') { //send all registers to gdb
|
||||
int *p=(int*)&gdbRegFile;
|
||||
gdbPacketStart();
|
||||
for (i=0; i<sizeof(GdbRegFile)/4; i++) gdbPacketHex(iswap(*p++), 32);
|
||||
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();
|
||||
} else if (cmd[0]=='m') { //read memory to gdb
|
||||
i=gdbGetHexVal(&data, -1);
|
||||
data++;
|
||||
j=gdbGetHexVal(&data, -1);
|
||||
gdbPacketStart();
|
||||
for (k=0; k<j; k++) {
|
||||
gdbPacketHex(readbyte(i++), 8);
|
||||
}
|
||||
gdbPacketEnd();
|
||||
} else if (cmd[0]=='?') { //Reply with stop reason
|
||||
sendReason();
|
||||
} else {
|
||||
//We don't recognize or support whatever GDB just sent us.
|
||||
gdbPacketStart();
|
||||
gdbPacketEnd();
|
||||
return ST_ERR;
|
||||
}
|
||||
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
|
||||
//character if it is received instead of the GDB packet
|
||||
//start char.
|
||||
static int gdbReadCommand() {
|
||||
unsigned char c;
|
||||
unsigned char chsum=0, rchsum;
|
||||
unsigned char sentchs[2];
|
||||
int p=0;
|
||||
unsigned char *ptr;
|
||||
c=gdbRecvChar();
|
||||
if (c!='$') return c;
|
||||
while(1) {
|
||||
c=gdbRecvChar();
|
||||
if (c=='#') { //end of packet, checksum follows
|
||||
cmd[p]=0;
|
||||
break;
|
||||
}
|
||||
chsum+=c;
|
||||
if (c=='$') {
|
||||
//Wut, restart packet?
|
||||
chsum=0;
|
||||
p=0;
|
||||
continue;
|
||||
}
|
||||
if (c=='}') { //escape the next char
|
||||
c=gdbRecvChar();
|
||||
chsum+=c;
|
||||
c^=0x20;
|
||||
}
|
||||
cmd[p++]=c;
|
||||
if (p>=PBUFLEN) return ST_ERR;
|
||||
}
|
||||
//A # has been received. Get and check the received chsum.
|
||||
sentchs[0]=gdbRecvChar();
|
||||
sentchs[1]=gdbRecvChar();
|
||||
ptr=&sentchs[0];
|
||||
rchsum=gdbGetHexVal(&ptr, 8);
|
||||
if (rchsum!=chsum) {
|
||||
gdbSendChar('-');
|
||||
return ST_ERR;
|
||||
} else {
|
||||
gdbSendChar('+');
|
||||
return gdbHandleCommand(cmd, p);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
void esp_gdbstub_panic_handler(XtExcFrame *frame) {
|
||||
dumpHwToRegfile(frame);
|
||||
//Make sure txd/rxd are enabled
|
||||
gpio_pullup_dis(1);
|
||||
PIN_FUNC_SELECT(PERIPHS_IO_MUX_U0RXD_U, FUNC_U0RXD_U0RXD);
|
||||
PIN_FUNC_SELECT(PERIPHS_IO_MUX_U0TXD_U, FUNC_U0TXD_U0TXD);
|
||||
|
||||
sendReason();
|
||||
while(gdbReadCommand()!=ST_CONT);
|
||||
while(1);
|
||||
}
|
||||
|
@ -127,24 +127,6 @@ menu "Common ESP-related"
|
||||
range 1200 4000000
|
||||
|
||||
|
||||
config ESP_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 ESP_GDBSTUB_MAX_TASKS
|
||||
int "GDBStub: maximum number of tasks supported"
|
||||
default 32
|
||||
depends on ESP_GDBSTUB_SUPPORT_TASKS
|
||||
help
|
||||
Set the number of tasks which GDB Stub will support.
|
||||
|
||||
|
||||
config ESP_INT_WDT
|
||||
bool "Interrupt watchdog"
|
||||
default y
|
||||
|
13
components/esp_gdbstub/CMakeLists.txt
Normal file
13
components/esp_gdbstub/CMakeLists.txt
Normal file
@ -0,0 +1,13 @@
|
||||
idf_build_get_property(target IDF_TARGET)
|
||||
|
||||
set(esp_gdbstub_srcs "src/gdbstub.c"
|
||||
"src/packet.c"
|
||||
"${target}/gdbstub_${target}.c"
|
||||
"xtensa/gdbstub_xtensa.c")
|
||||
|
||||
idf_component_register(SRCS "${esp_gdbstub_srcs}"
|
||||
INCLUDE_DIRS "include"
|
||||
PRIV_INCLUDE_DIRS "private_include" "${target}" "xtensa"
|
||||
LDFRAGMENTS "linker.lf"
|
||||
REQUIRES "freertos"
|
||||
PRIV_REQUIRES "soc" "xtensa" "esp_rom")
|
25
components/esp_gdbstub/Kconfig
Normal file
25
components/esp_gdbstub/Kconfig
Normal file
@ -0,0 +1,25 @@
|
||||
menu "GDB Stub"
|
||||
|
||||
# Hidden option which is selected from the "Panic handler behavior"
|
||||
# menu in the target component.
|
||||
config ESP_GDBSTUB_ENABLED
|
||||
bool
|
||||
|
||||
config ESP_GDBSTUB_SUPPORT_TASKS
|
||||
bool "Enable listing FreeRTOS tasks through GDB Stub"
|
||||
depends on ESP_GDBSTUB_ENABLED
|
||||
default y
|
||||
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 ESP_GDBSTUB_MAX_TASKS
|
||||
int "Maximum number of tasks supported by GDB Stub"
|
||||
default 32
|
||||
depends on ESP_GDBSTUB_SUPPORT_TASKS
|
||||
help
|
||||
Set the number of tasks which GDB Stub will support.
|
||||
|
||||
endmenu
|
4
components/esp_gdbstub/component.mk
Normal file
4
components/esp_gdbstub/component.mk
Normal file
@ -0,0 +1,4 @@
|
||||
COMPONENT_ADD_INCLUDEDIRS := include
|
||||
COMPONENT_PRIV_INCLUDEDIRS := private_include esp32 xtensa
|
||||
COMPONENT_SRCDIRS := src esp32 xtensa
|
||||
COMPONENT_ADD_LDFRAGMENTS += linker.lf
|
51
components/esp_gdbstub/esp32/gdbstub_esp32.c
Normal file
51
components/esp_gdbstub/esp32/gdbstub_esp32.c
Normal file
@ -0,0 +1,51 @@
|
||||
// Copyright 2015-2019 Espressif Systems (Shanghai) PTE LTD
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include "soc/uart_periph.h"
|
||||
#include "soc/gpio_periph.h"
|
||||
#include "esp_gdbstub_common.h"
|
||||
#include "sdkconfig.h"
|
||||
|
||||
#define UART_NUM CONFIG_ESP_CONSOLE_UART_NUM
|
||||
|
||||
void esp_gdbstub_target_init()
|
||||
{
|
||||
}
|
||||
|
||||
int esp_gdbstub_getchar()
|
||||
{
|
||||
while (REG_GET_FIELD(UART_STATUS_REG(UART_NUM), UART_RXFIFO_CNT) == 0) {
|
||||
;
|
||||
}
|
||||
return REG_READ(UART_FIFO_REG(UART_NUM));
|
||||
}
|
||||
|
||||
void esp_gdbstub_putchar(int c)
|
||||
{
|
||||
while (REG_GET_FIELD(UART_STATUS_REG(UART_NUM), UART_TXFIFO_CNT) >= 126) {
|
||||
;
|
||||
}
|
||||
REG_WRITE(UART_FIFO_REG(UART_NUM), c);
|
||||
}
|
||||
|
||||
int esp_gdbstub_readmem(intptr_t addr)
|
||||
{
|
||||
if (addr < 0x20000000 || addr >= 0x80000000) {
|
||||
/* see cpu_configure_region_protection */
|
||||
return -1;
|
||||
}
|
||||
uint32_t val_aligned = *(uint32_t *)(addr & (~3));
|
||||
uint32_t shift = (addr & 3) * 8;
|
||||
return (val_aligned >> shift) & 0xff;
|
||||
}
|
18
components/esp_gdbstub/esp32/gdbstub_target_config.h
Normal file
18
components/esp_gdbstub/esp32/gdbstub_target_config.h
Normal file
@ -0,0 +1,18 @@
|
||||
// Copyright 2019 Espressif Systems (Shanghai) PTE LTD
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#pragma once
|
||||
|
||||
/* Number of extra TIE defined registers, not listed in the XCHAL */
|
||||
#define GDBSTUB_EXTRA_TIE_SIZE 0
|
51
components/esp_gdbstub/esp32s2beta/gdbstub_esp32s2beta.c
Normal file
51
components/esp_gdbstub/esp32s2beta/gdbstub_esp32s2beta.c
Normal file
@ -0,0 +1,51 @@
|
||||
// Copyright 2015-2019 Espressif Systems (Shanghai) PTE LTD
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include "soc/uart_periph.h"
|
||||
#include "soc/gpio_periph.h"
|
||||
#include "esp_gdbstub_common.h"
|
||||
#include "sdkconfig.h"
|
||||
|
||||
#define UART_NUM CONFIG_ESP_CONSOLE_UART_NUM
|
||||
|
||||
void esp_gdbstub_target_init()
|
||||
{
|
||||
}
|
||||
|
||||
int esp_gdbstub_getchar()
|
||||
{
|
||||
while (REG_GET_FIELD(UART_STATUS_REG(UART_NUM), UART_RXFIFO_CNT) == 0) {
|
||||
;
|
||||
}
|
||||
return REG_READ(UART_FIFO_AHB_REG(UART_NUM));
|
||||
}
|
||||
|
||||
void esp_gdbstub_putchar(int c)
|
||||
{
|
||||
while (REG_GET_FIELD(UART_STATUS_REG(UART_NUM), UART_TXFIFO_CNT) >= 126) {
|
||||
;
|
||||
}
|
||||
REG_WRITE(UART_FIFO_AHB_REG(UART_NUM), c);
|
||||
}
|
||||
|
||||
int esp_gdbstub_readmem(intptr_t addr)
|
||||
{
|
||||
if (addr < 0x20000000 || addr >= 0x80000000) {
|
||||
/* see cpu_configure_region_protection */
|
||||
return -1;
|
||||
}
|
||||
uint32_t val_aligned = *(uint32_t *)(addr & (~3));
|
||||
uint32_t shift = (addr & 3) * 8;
|
||||
return (val_aligned >> shift) & 0xff;
|
||||
}
|
18
components/esp_gdbstub/esp32s2beta/gdbstub_target_config.h
Normal file
18
components/esp_gdbstub/esp32s2beta/gdbstub_target_config.h
Normal file
@ -0,0 +1,18 @@
|
||||
// Copyright 2019 Espressif Systems (Shanghai) PTE LTD
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#pragma once
|
||||
|
||||
/* Number of extra TIE defined registers, not listed in the XCHAL */
|
||||
#define GDBSTUB_EXTRA_TIE_SIZE 1
|
27
components/esp_gdbstub/include/esp_gdbstub.h
Normal file
27
components/esp_gdbstub/include/esp_gdbstub.h
Normal file
@ -0,0 +1,27 @@
|
||||
// Copyright 2015-2019 Espressif Systems (Shanghai) PTE LTD
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "esp_gdbstub_arch.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
void esp_gdbstub_panic_handler(esp_gdbstub_frame_t *frame) __attribute__((noreturn));
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
4
components/esp_gdbstub/linker.lf
Normal file
4
components/esp_gdbstub/linker.lf
Normal file
@ -0,0 +1,4 @@
|
||||
[mapping:esp_gdbstub]
|
||||
archive: libesp_gdbstub.a
|
||||
entries:
|
||||
* (noflash)
|
151
components/esp_gdbstub/private_include/esp_gdbstub_common.h
Normal file
151
components/esp_gdbstub/private_include/esp_gdbstub_common.h
Normal file
@ -0,0 +1,151 @@
|
||||
// Copyright 2015-2019 Espressif Systems (Shanghai) PTE LTD
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#include "esp_gdbstub.h"
|
||||
#include "sdkconfig.h"
|
||||
|
||||
#ifdef CONFIG_ESP_GDBSTUB_SUPPORT_TASKS
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#endif // CONFIG_ESP_GDBSTUB_SUPPORT_TASKS
|
||||
|
||||
/* Internal error codes used by the routines that parse the incoming gdb packet */
|
||||
#define GDBSTUB_ST_ENDPACKET -1
|
||||
#define GDBSTUB_ST_ERR -2
|
||||
#define GDBSTUB_ST_OK -3
|
||||
|
||||
/* Special task index values */
|
||||
#define GDBSTUB_CUR_TASK_INDEX_UNKNOWN -1
|
||||
|
||||
/* Cab be set to a lower value in gdbstub_target_config.h */
|
||||
#ifndef GDBSTUB_CMD_BUFLEN
|
||||
#define GDBSTUB_CMD_BUFLEN 512
|
||||
#endif
|
||||
|
||||
#if CONFIG_ESP_GDBSTUB_SUPPORT_TASKS
|
||||
typedef enum {
|
||||
GDBSTUB_NOT_STARTED,
|
||||
GDBSTUB_STARTED,
|
||||
GDBSTUB_TASK_SUPPORT_DISABLED
|
||||
} esp_gdbstub_state_t;
|
||||
|
||||
#define GDBSTUB_TASKS_NUM CONFIG_ESP_GDBSTUB_MAX_TASKS
|
||||
|
||||
#endif // CONFIG_ESP_GDBSTUB_SUPPORT_TASKS
|
||||
|
||||
/* gdbstub temporary run-time data, stored in .bss to reduce stack usage */
|
||||
typedef struct {
|
||||
esp_gdbstub_gdb_regfile_t regfile;
|
||||
int signal;
|
||||
#if CONFIG_ESP_GDBSTUB_SUPPORT_TASKS
|
||||
esp_gdbstub_state_t state;
|
||||
int task_count;
|
||||
int paniced_task_index;
|
||||
int current_task_index;
|
||||
int thread_info_index; //!< index of the last task passed to qsThreadInfo
|
||||
esp_gdbstub_frame_t paniced_frame;
|
||||
TaskSnapshot_t tasks[GDBSTUB_TASKS_NUM]; // TODO: add an API to get snapshots one by one
|
||||
#endif // CONFIG_ESP_GDBSTUB_SUPPORT_TASKS
|
||||
} esp_gdbstub_scratch_t;
|
||||
|
||||
|
||||
/**** Functions provided by the architecture specific part ****/
|
||||
|
||||
/**
|
||||
* @param frame exception frame pointer
|
||||
* @return the appropriate "signal" number for the given exception cause
|
||||
*/
|
||||
int esp_gdbstub_get_signal(const esp_gdbstub_frame_t *frame);
|
||||
|
||||
/**
|
||||
* Write registers from the exception frame to the GDB register file
|
||||
* @param frame exception frame to parse
|
||||
* @param dst pointer to the GDB register file
|
||||
*/
|
||||
void esp_gdbstub_frame_to_regfile(const esp_gdbstub_frame_t *frame, esp_gdbstub_gdb_regfile_t *dst);
|
||||
|
||||
#if CONFIG_ESP_GDBSTUB_SUPPORT_TASKS
|
||||
/**
|
||||
* Write registers from the saved frame of a given task to the GDB register file
|
||||
* @param tcb pointer to the TCB of the task
|
||||
* @param dst pointer to the GDB register file
|
||||
*/
|
||||
void esp_gdbstub_tcb_to_regfile(TaskHandle_t tcb, esp_gdbstub_gdb_regfile_t *dst);
|
||||
#endif // CONFIG_ESP_GDBSTUB_SUPPORT_TASKS
|
||||
|
||||
|
||||
|
||||
/**** Functions provided by the target specific part ****/
|
||||
|
||||
/**
|
||||
* Do target-specific initialization before gdbstub can start communicating.
|
||||
* This may involve, for example, configuring the UART.
|
||||
*/
|
||||
void esp_gdbstub_target_init();
|
||||
|
||||
/**
|
||||
* Receive a byte from the GDB client. Blocks until a byte is available.
|
||||
* @return received byte
|
||||
*/
|
||||
int esp_gdbstub_getchar();
|
||||
|
||||
/**
|
||||
* Send a byte to the GDB client
|
||||
* @param c byte to send
|
||||
*/
|
||||
void esp_gdbstub_putchar(int c);
|
||||
|
||||
/**
|
||||
* Read a byte from target memory
|
||||
* @param ptr address
|
||||
* @return byte value, or GDBSTUB_ST_ERR if the address is not readable
|
||||
*/
|
||||
int esp_gdbstub_readmem(intptr_t addr);
|
||||
|
||||
|
||||
/**** GDB packet related functions ****/
|
||||
|
||||
/** Begin a packet */
|
||||
void esp_gdbstub_send_start();
|
||||
|
||||
/** Send a character as part of the packet */
|
||||
void esp_gdbstub_send_char(char c);
|
||||
|
||||
/** Send a string as part of the packet */
|
||||
void esp_gdbstub_send_str(const char *s);
|
||||
|
||||
/** Send a hex value as part of the packet */
|
||||
void esp_gdbstub_send_hex(int val, int bits);
|
||||
|
||||
/** Finish sending the packet */
|
||||
void esp_gdbstub_send_end();
|
||||
|
||||
/** Send a packet with a string as content */
|
||||
void esp_gdbstub_send_str_packet(const char* str);
|
||||
|
||||
/** Get a hex value from the gdb packet */
|
||||
uint32_t esp_gdbstub_gethex(const unsigned char **ptr, int bits);
|
||||
|
||||
/** Read, unescape, and validate the incoming GDB command */
|
||||
int esp_gdbstub_read_command(unsigned char **out_cmd, size_t *out_size);
|
||||
|
||||
/** Handle a command received from gdb */
|
||||
int esp_gdbstub_handle_command(unsigned char *cmd, int len);
|
||||
|
343
components/esp_gdbstub/src/gdbstub.c
Normal file
343
components/esp_gdbstub/src/gdbstub.c
Normal file
@ -0,0 +1,343 @@
|
||||
// Copyright 2015-2019 Espressif Systems (Shanghai) PTE LTD
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include <string.h>
|
||||
#include "esp_gdbstub.h"
|
||||
#include "esp_gdbstub_common.h"
|
||||
#include "sdkconfig.h"
|
||||
|
||||
|
||||
#ifdef CONFIG_ESP_GDBSTUB_SUPPORT_TASKS
|
||||
static void init_task_info();
|
||||
static void find_paniced_task_index();
|
||||
static int handle_task_commands(unsigned char *cmd, int len);
|
||||
#endif
|
||||
|
||||
static void send_reason();
|
||||
|
||||
|
||||
static esp_gdbstub_scratch_t s_scratch;
|
||||
|
||||
|
||||
void esp_gdbstub_panic_handler(esp_gdbstub_frame_t *frame)
|
||||
{
|
||||
#ifndef CONFIG_ESP_GDBSTUB_SUPPORT_TASKS
|
||||
esp_gdbstub_frame_to_regfile(frame, &s_scratch.regfile);
|
||||
#else
|
||||
if (s_scratch.state == GDBSTUB_STARTED) {
|
||||
/* We have re-entered GDB Stub. Try disabling task support. */
|
||||
s_scratch.state = GDBSTUB_TASK_SUPPORT_DISABLED;
|
||||
/* Flush any pending GDB packet (this creates a garbage value) */
|
||||
esp_gdbstub_send_end();
|
||||
} else if (s_scratch.state == GDBSTUB_NOT_STARTED) {
|
||||
s_scratch.state = GDBSTUB_STARTED;
|
||||
/* Save the paniced frame and get the list of tasks */
|
||||
memcpy(&s_scratch.paniced_frame, frame, sizeof(*frame));
|
||||
esp_gdbstub_frame_to_regfile(frame, &s_scratch.regfile);
|
||||
init_task_info();
|
||||
find_paniced_task_index();
|
||||
/* Current task is the paniced task */
|
||||
if (s_scratch.paniced_task_index == GDBSTUB_CUR_TASK_INDEX_UNKNOWN) {
|
||||
s_scratch.current_task_index = 0;
|
||||
}
|
||||
}
|
||||
#endif // CONFIG_ESP_GDBSTUB_SUPPORT_TASKS
|
||||
|
||||
esp_gdbstub_target_init();
|
||||
s_scratch.signal = esp_gdbstub_get_signal(frame);
|
||||
send_reason();
|
||||
while (true) {
|
||||
unsigned char *cmd;
|
||||
size_t size;
|
||||
int res = esp_gdbstub_read_command(&cmd, &size);
|
||||
if (res > 0) {
|
||||
/* character received instead of a command */
|
||||
continue;
|
||||
}
|
||||
if (res == GDBSTUB_ST_ERR) {
|
||||
esp_gdbstub_send_str_packet("E01");
|
||||
continue;
|
||||
}
|
||||
res = esp_gdbstub_handle_command(cmd, size);
|
||||
if (res == GDBSTUB_ST_ERR) {
|
||||
esp_gdbstub_send_str_packet(NULL);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static void send_reason()
|
||||
{
|
||||
esp_gdbstub_send_start();
|
||||
esp_gdbstub_send_char('T');
|
||||
esp_gdbstub_send_hex(s_scratch.signal, 8);
|
||||
esp_gdbstub_send_end();
|
||||
}
|
||||
|
||||
static uint32_t gdbstub_hton(uint32_t i)
|
||||
{
|
||||
return __builtin_bswap32(i);
|
||||
}
|
||||
|
||||
/** Send all registers to gdb */
|
||||
static void handle_g_command(const unsigned char* cmd, int len)
|
||||
{
|
||||
uint32_t *p = (uint32_t *) &s_scratch.regfile;
|
||||
esp_gdbstub_send_start();
|
||||
for (int i = 0; i < sizeof(s_scratch.regfile) / sizeof(*p); ++i) {
|
||||
esp_gdbstub_send_hex(gdbstub_hton(*p++), 32);
|
||||
}
|
||||
esp_gdbstub_send_end();
|
||||
}
|
||||
|
||||
/** Receive register values from gdb */
|
||||
static void handle_G_command(const unsigned char* cmd, int len)
|
||||
{
|
||||
uint32_t *p = (uint32_t *) &s_scratch.regfile;
|
||||
for (int i = 0; i < sizeof(s_scratch.regfile) / sizeof(*p); ++i) {
|
||||
*p++ = gdbstub_hton(esp_gdbstub_gethex(&cmd, 32));
|
||||
}
|
||||
esp_gdbstub_send_str_packet("OK");
|
||||
}
|
||||
|
||||
/** Read memory to gdb */
|
||||
static void handle_m_command(const unsigned char* cmd, int len)
|
||||
{
|
||||
intptr_t addr = (intptr_t) esp_gdbstub_gethex(&cmd, -1);
|
||||
cmd++;
|
||||
uint32_t size = esp_gdbstub_gethex(&cmd, -1);
|
||||
|
||||
if (esp_gdbstub_readmem(addr) < 0 || esp_gdbstub_readmem(addr + size - 1) < 0) {
|
||||
esp_gdbstub_send_str_packet("E01");
|
||||
return;
|
||||
}
|
||||
|
||||
esp_gdbstub_send_start();
|
||||
for (int i = 0; i < size; ++i) {
|
||||
int b = esp_gdbstub_readmem(addr++);
|
||||
esp_gdbstub_send_hex(b, 8);
|
||||
}
|
||||
esp_gdbstub_send_end();
|
||||
}
|
||||
|
||||
/** Handle a command received from gdb */
|
||||
int esp_gdbstub_handle_command(unsigned char *cmd, int len)
|
||||
{
|
||||
unsigned char *data = cmd + 1;
|
||||
if (cmd[0] == 'g')
|
||||
{
|
||||
handle_g_command(data, len - 1);
|
||||
} else if (cmd[0] == 'G') {
|
||||
/* receive content for all registers from gdb */
|
||||
handle_G_command(data, len - 1);
|
||||
} else if (cmd[0] == 'm') {
|
||||
/* read memory to gdb */
|
||||
handle_m_command(data, len - 1);
|
||||
} else if (cmd[0] == '?') {
|
||||
/* Reply with stop reason */
|
||||
send_reason();
|
||||
#if CONFIG_ESP_GDBSTUB_SUPPORT_TASKS
|
||||
} else if (s_scratch.state != GDBSTUB_TASK_SUPPORT_DISABLED) {
|
||||
return handle_task_commands(cmd, len);
|
||||
#endif // CONFIG_ESP_GDBSTUB_SUPPORT_TASKS
|
||||
} else {
|
||||
/* Unrecognized command */
|
||||
return GDBSTUB_ST_ERR;
|
||||
}
|
||||
return GDBSTUB_ST_OK;
|
||||
}
|
||||
|
||||
/* Everything below is related to the support for listing FreeRTOS tasks as threads in GDB */
|
||||
|
||||
#ifdef CONFIG_ESP_GDBSTUB_SUPPORT_TASKS
|
||||
|
||||
static void init_task_info()
|
||||
{
|
||||
unsigned tcb_size;
|
||||
s_scratch.task_count = uxTaskGetSnapshotAll(s_scratch.tasks, GDBSTUB_TASKS_NUM, &tcb_size);
|
||||
}
|
||||
|
||||
static bool get_task_handle(size_t index, TaskHandle_t *handle)
|
||||
{
|
||||
if (index >= s_scratch.task_count) {
|
||||
return false;
|
||||
}
|
||||
*handle = (TaskHandle_t) s_scratch.tasks[index].pxTCB;
|
||||
return true;
|
||||
}
|
||||
|
||||
/** Get the index of the task running on the current CPU, and save the result */
|
||||
static void find_paniced_task_index()
|
||||
{
|
||||
TaskHandle_t cur_handle = xTaskGetCurrentTaskHandleForCPU(xPortGetCoreID());
|
||||
TaskHandle_t handle;
|
||||
for (int i = 0; i < s_scratch.task_count; i++) {
|
||||
if (get_task_handle(i, &handle) && cur_handle == handle) {
|
||||
s_scratch.paniced_task_index = i;
|
||||
return;
|
||||
}
|
||||
}
|
||||
s_scratch.paniced_task_index = GDBSTUB_CUR_TASK_INDEX_UNKNOWN;
|
||||
}
|
||||
|
||||
/** H command sets the "current task" for the purpose of further commands */
|
||||
static void handle_H_command(const unsigned char* cmd, int len)
|
||||
{
|
||||
if (cmd[1] == 'g' || cmd[1] == 'c') {
|
||||
const char *ret = "OK";
|
||||
cmd += 2;
|
||||
int requested_task_index = esp_gdbstub_gethex(&cmd, -1);
|
||||
if (requested_task_index == s_scratch.paniced_task_index ||
|
||||
(requested_task_index == 0 && s_scratch.current_task_index == GDBSTUB_CUR_TASK_INDEX_UNKNOWN)) {
|
||||
/* Get the registers of the paniced task */
|
||||
esp_gdbstub_frame_to_regfile(&s_scratch.paniced_frame, &s_scratch.regfile);
|
||||
} else if (requested_task_index > s_scratch.task_count) {
|
||||
ret = "E00";
|
||||
} else {
|
||||
TaskHandle_t handle;
|
||||
get_task_handle(requested_task_index, &handle);
|
||||
/* FIXME: for the task currently running on the other CPU, extracting the registers from TCB
|
||||
* isn't valid. Need to use some IPC mechanism to obtain the registers of the other CPU
|
||||
*/
|
||||
esp_gdbstub_tcb_to_regfile(handle, &s_scratch.regfile);
|
||||
}
|
||||
esp_gdbstub_send_str_packet(ret);
|
||||
} else {
|
||||
esp_gdbstub_send_str_packet(NULL);
|
||||
}
|
||||
}
|
||||
|
||||
/** qC returns the current thread ID */
|
||||
static void handle_qC_command(const unsigned char* cmd, int len)
|
||||
{
|
||||
esp_gdbstub_send_start();
|
||||
esp_gdbstub_send_str("QC");
|
||||
esp_gdbstub_send_hex(s_scratch.current_task_index, 32);
|
||||
esp_gdbstub_send_end();
|
||||
}
|
||||
|
||||
/** T command checks if the task is alive.
|
||||
* Since GDB isn't going to ask about the tasks which haven't been listed by q*ThreadInfo,
|
||||
* and the state of tasks can not change (no stepping allowed), simply return "OK" here.
|
||||
*/
|
||||
static void handle_T_command(const unsigned char* cmd, int len)
|
||||
{
|
||||
esp_gdbstub_send_str_packet("OK");
|
||||
}
|
||||
|
||||
/** qfThreadInfo requests the start of the thread list, qsThreadInfo (below) is repeated to
|
||||
* get the subsequent threads.
|
||||
*/
|
||||
static void handle_qfThreadInfo_command(const unsigned char* cmd, int len)
|
||||
{
|
||||
/* The first task in qfThreadInfo reply is going to be the one which GDB will request to stop.
|
||||
* Therefore it has to be the paniced task.
|
||||
* Reply with the paniced task index, and later skip over this index while handling qsThreadInfo
|
||||
*/
|
||||
esp_gdbstub_send_start();
|
||||
esp_gdbstub_send_str("m");
|
||||
esp_gdbstub_send_hex(s_scratch.paniced_task_index, 32);
|
||||
esp_gdbstub_send_end();
|
||||
|
||||
s_scratch.thread_info_index = 0;
|
||||
}
|
||||
|
||||
static void handle_qsThreadInfo_command(const unsigned char* cmd, int len)
|
||||
{
|
||||
int next_task_index = ++s_scratch.thread_info_index;
|
||||
|
||||
if (next_task_index == s_scratch.task_count) {
|
||||
/* No more tasks */
|
||||
esp_gdbstub_send_str_packet("l");
|
||||
return;
|
||||
}
|
||||
|
||||
if (next_task_index == s_scratch.paniced_task_index) {
|
||||
/* Have already sent this one in the reply to qfThreadInfo, skip over it */
|
||||
handle_qsThreadInfo_command(cmd, len);
|
||||
return;
|
||||
}
|
||||
|
||||
esp_gdbstub_send_start();
|
||||
esp_gdbstub_send_str("m");
|
||||
esp_gdbstub_send_hex(next_task_index, 32);
|
||||
esp_gdbstub_send_end();
|
||||
}
|
||||
|
||||
/** qThreadExtraInfo requests the thread name */
|
||||
static void handle_qThreadExtraInfo_command(const unsigned char* cmd, int len)
|
||||
{
|
||||
cmd += sizeof("qThreadExtraInfo,") - 1;
|
||||
int task_index = esp_gdbstub_gethex(&cmd, -1);
|
||||
TaskHandle_t handle;
|
||||
if (!get_task_handle(task_index, &handle)) {
|
||||
esp_gdbstub_send_str_packet("E01");
|
||||
return;
|
||||
}
|
||||
esp_gdbstub_send_start();
|
||||
const char* task_name = pcTaskGetTaskName(handle);
|
||||
while (*task_name) {
|
||||
esp_gdbstub_send_hex(*task_name, 8);
|
||||
task_name++;
|
||||
}
|
||||
/** TODO: add "Running" or "Suspended" and "CPU0" or "CPU1" */
|
||||
esp_gdbstub_send_end();
|
||||
}
|
||||
|
||||
bool command_name_matches(const char* pattern, const unsigned char* ucmd, int len)
|
||||
{
|
||||
const char* cmd = (const char*) ucmd;
|
||||
const char* end = cmd + len;
|
||||
for (; *pattern && cmd < end; ++cmd, ++pattern) {
|
||||
if (*pattern == '?') {
|
||||
continue;
|
||||
}
|
||||
if (*pattern != *cmd) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return *pattern == 0 && (cmd == end || *cmd == ',');
|
||||
}
|
||||
|
||||
/** Handle all the thread-related commands */
|
||||
static int handle_task_commands(unsigned char *cmd, int len)
|
||||
{
|
||||
if (cmd[0] == 'H') {
|
||||
/* Continue with task */
|
||||
handle_H_command(cmd, len);
|
||||
} else if (cmd[0] == 'T') {
|
||||
/* Task alive check */
|
||||
handle_T_command(cmd, len);
|
||||
} else if (cmd[0] == 'q') {
|
||||
if (command_name_matches("qfThreadInfo", cmd, len)) {
|
||||
handle_qfThreadInfo_command(cmd, len);
|
||||
} else if (command_name_matches("qsThreadInfo", cmd, len)) {
|
||||
handle_qsThreadInfo_command(cmd, len);
|
||||
} else if (command_name_matches("qC", cmd, len)) {
|
||||
handle_qC_command(cmd, len);
|
||||
} else if (command_name_matches("qThreadExtraInfo", cmd, len)) {
|
||||
handle_qThreadExtraInfo_command(cmd, len);
|
||||
} else {
|
||||
/* Unrecognized command */
|
||||
return GDBSTUB_ST_ERR;
|
||||
}
|
||||
} else {
|
||||
/* Unrecognized command */
|
||||
return GDBSTUB_ST_ERR;
|
||||
}
|
||||
return GDBSTUB_ST_OK;
|
||||
}
|
||||
|
||||
#endif // CONFIG_ESP_GDBSTUB_SUPPORT_TASKS
|
||||
|
177
components/esp_gdbstub/src/packet.c
Normal file
177
components/esp_gdbstub/src/packet.c
Normal file
@ -0,0 +1,177 @@
|
||||
// Copyright 2015-2019 Espressif Systems (Shanghai) PTE LTD
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include "esp_gdbstub_common.h"
|
||||
|
||||
// GDB command input buffer
|
||||
static unsigned char s_cmd[GDBSTUB_CMD_BUFLEN];
|
||||
|
||||
// Running checksum of the output packet
|
||||
static char s_chsum;
|
||||
|
||||
// Send the start of a packet; reset checksum calculation.
|
||||
void esp_gdbstub_send_start()
|
||||
{
|
||||
s_chsum = 0;
|
||||
esp_gdbstub_putchar('$');
|
||||
}
|
||||
|
||||
// Send a char as part of a packet
|
||||
void esp_gdbstub_send_char(char c)
|
||||
{
|
||||
if (c == '#' || c == '$' || c == '}' || c == '*') {
|
||||
esp_gdbstub_putchar('}');
|
||||
esp_gdbstub_putchar(c ^ 0x20);
|
||||
s_chsum += (c ^ 0x20) + '}';
|
||||
} else {
|
||||
esp_gdbstub_putchar(c);
|
||||
s_chsum += c;
|
||||
}
|
||||
}
|
||||
|
||||
// Send a string as part of a packet
|
||||
void esp_gdbstub_send_str(const char *c)
|
||||
{
|
||||
while (*c != 0) {
|
||||
esp_gdbstub_send_char(*c);
|
||||
c++;
|
||||
}
|
||||
}
|
||||
|
||||
// Send a hex val as part of a packet.
|
||||
// 'bits'/4 dictates the number of hex chars sent.
|
||||
void esp_gdbstub_send_hex(int val, int bits)
|
||||
{
|
||||
const char* hex_chars = "0123456789abcdef";
|
||||
for (int i = bits; i > 0; i -= 4) {
|
||||
esp_gdbstub_send_char(hex_chars[(val >> (i - 4)) & 0xf]);
|
||||
}
|
||||
}
|
||||
|
||||
// Finish sending a packet.
|
||||
void esp_gdbstub_send_end()
|
||||
{
|
||||
esp_gdbstub_putchar('#');
|
||||
esp_gdbstub_send_hex(s_chsum, 8);
|
||||
}
|
||||
|
||||
// Send a packet with a string as content
|
||||
void esp_gdbstub_send_str_packet(const char* str)
|
||||
{
|
||||
esp_gdbstub_send_start();
|
||||
if (str != NULL) {
|
||||
esp_gdbstub_send_str(str);
|
||||
}
|
||||
esp_gdbstub_send_end();
|
||||
}
|
||||
|
||||
// Grab a hex value from the gdb packet. Ptr will get positioned on the end
|
||||
// of the hex string, as far as the routine has read into it. Bits/4 indicates
|
||||
// the max amount of hex chars it gobbles up. Bits can be -1 to eat up as much
|
||||
// hex chars as possible.
|
||||
uint32_t esp_gdbstub_gethex(const unsigned char **ptr, int bits)
|
||||
{
|
||||
int i;
|
||||
int no;
|
||||
uint32_t v = 0;
|
||||
char c;
|
||||
no = bits / 4;
|
||||
if (bits == -1) {
|
||||
no = 64;
|
||||
}
|
||||
for (i = 0; i < no; i++) {
|
||||
c = **ptr;
|
||||
(*ptr)++;
|
||||
if (c >= '0' && c <= '9') {
|
||||
v <<= 4;
|
||||
v |= (c - '0');
|
||||
} else if (c >= 'A' && c <= 'F') {
|
||||
v <<= 4;
|
||||
v |= (c - 'A') + 10;
|
||||
} else if (c >= 'a' && c <= 'f') {
|
||||
v <<= 4;
|
||||
v |= (c - 'a') + 10;
|
||||
} else if (c == '#') {
|
||||
if (bits == -1) {
|
||||
(*ptr)--;
|
||||
return v;
|
||||
}
|
||||
return GDBSTUB_ST_ENDPACKET;
|
||||
} else {
|
||||
if (bits == -1) {
|
||||
(*ptr)--;
|
||||
return v;
|
||||
}
|
||||
return GDBSTUB_ST_ERR;
|
||||
}
|
||||
}
|
||||
return v;
|
||||
}
|
||||
|
||||
|
||||
// Lower layer: grab a command packet and check the checksum
|
||||
// Calls gdbHandleCommand on the packet if the checksum is OK
|
||||
// Returns GDBSTUB_ST_OK on success, GDBSTUB_ST_ERR when checksum fails, a
|
||||
// character if it is received instead of the GDB packet
|
||||
// start char.
|
||||
int esp_gdbstub_read_command(unsigned char **out_cmd, size_t *out_size)
|
||||
{
|
||||
unsigned char c;
|
||||
unsigned char chsum = 0;
|
||||
unsigned char sentchs[2];
|
||||
int p = 0;
|
||||
c = esp_gdbstub_getchar();
|
||||
if (c != '$') {
|
||||
return c;
|
||||
}
|
||||
while (1) {
|
||||
c = esp_gdbstub_getchar();
|
||||
if (c == '#') {
|
||||
// end of packet, checksum follows
|
||||
s_cmd[p] = 0;
|
||||
break;
|
||||
}
|
||||
chsum += c;
|
||||
if (c == '$') {
|
||||
// restart packet?
|
||||
chsum = 0;
|
||||
p = 0;
|
||||
continue;
|
||||
}
|
||||
if (c == '}') {
|
||||
//escape the next char
|
||||
c = esp_gdbstub_getchar();
|
||||
chsum += c;
|
||||
c ^= 0x20;
|
||||
}
|
||||
s_cmd[p++] = c;
|
||||
if (p >= GDBSTUB_CMD_BUFLEN) {
|
||||
return GDBSTUB_ST_ERR;
|
||||
}
|
||||
}
|
||||
// A # has been received. Get and check the received chsum.
|
||||
sentchs[0] = esp_gdbstub_getchar();
|
||||
sentchs[1] = esp_gdbstub_getchar();
|
||||
const unsigned char* c_ptr = &sentchs[0];
|
||||
unsigned char rchsum = esp_gdbstub_gethex(&c_ptr, 8);
|
||||
if (rchsum != chsum) {
|
||||
esp_gdbstub_putchar('-');
|
||||
return GDBSTUB_ST_ERR;
|
||||
} else {
|
||||
esp_gdbstub_putchar('+');
|
||||
*out_cmd = s_cmd;
|
||||
*out_size = p;
|
||||
return GDBSTUB_ST_OK;
|
||||
}
|
||||
}
|
91
components/esp_gdbstub/xtensa/esp_gdbstub_arch.h
Normal file
91
components/esp_gdbstub/xtensa/esp_gdbstub_arch.h
Normal file
@ -0,0 +1,91 @@
|
||||
// Copyright 2015-2019 Espressif Systems (Shanghai) PTE LTD
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#pragma once
|
||||
#include <stdint.h>
|
||||
#include "freertos/xtensa_context.h"
|
||||
#include "gdbstub_target_config.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef XtExcFrame esp_gdbstub_frame_t;
|
||||
|
||||
/* GDB regfile structure, configuration dependent */
|
||||
typedef struct {
|
||||
uint32_t pc;
|
||||
uint32_t a[XCHAL_NUM_AREGS];
|
||||
|
||||
#if XCHAL_HAVE_LOOPS
|
||||
uint32_t lbeg;
|
||||
uint32_t lend;
|
||||
uint32_t lcount;
|
||||
#endif
|
||||
|
||||
uint32_t sar;
|
||||
|
||||
#if XCHAL_HAVE_WINDOWED
|
||||
uint32_t windowbase;
|
||||
uint32_t windowstart;
|
||||
#endif
|
||||
|
||||
uint32_t configid0;
|
||||
uint32_t configid1;
|
||||
uint32_t ps;
|
||||
|
||||
#if XCHAL_HAVE_THREADPTR
|
||||
uint32_t threadptr;
|
||||
#endif
|
||||
|
||||
#if XCHAL_HAVE_BOOLEANS
|
||||
uint32_t br;
|
||||
#endif
|
||||
|
||||
#if XCHAL_HAVE_S32C1I
|
||||
uint32_t scompare1;
|
||||
#endif
|
||||
|
||||
#if XCHAL_HAVE_MAC16
|
||||
uint32_t acclo;
|
||||
uint32_t acchi;
|
||||
uint32_t m0;
|
||||
uint32_t m1;
|
||||
uint32_t m2;
|
||||
uint32_t m3;
|
||||
#endif
|
||||
|
||||
#if XCHAL_HAVE_DFP_ACCEL
|
||||
uint32_t expstate;
|
||||
uint32_t f64r_lo;
|
||||
uint32_t f64r_hi;
|
||||
uint32_t f64s;
|
||||
#endif
|
||||
|
||||
#if XCHAL_HAVE_FP
|
||||
uint32_t f[16];
|
||||
uint32_t fcr;
|
||||
uint32_t fsr;
|
||||
#endif
|
||||
|
||||
#if GDBSTUB_EXTRA_TIE_SIZE > 0
|
||||
uint32_t tie[GDBSTUB_EXTRA_TIE_SIZE];
|
||||
#endif
|
||||
|
||||
} esp_gdbstub_gdb_regfile_t;
|
||||
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
119
components/esp_gdbstub/xtensa/gdbstub_xtensa.c
Normal file
119
components/esp_gdbstub/xtensa/gdbstub_xtensa.c
Normal file
@ -0,0 +1,119 @@
|
||||
// Copyright 2015-2019 Espressif Systems (Shanghai) PTE LTD
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include <string.h>
|
||||
#include "esp_gdbstub.h"
|
||||
#include "esp_gdbstub_common.h"
|
||||
#include "soc/cpu.h"
|
||||
#include "soc/soc_memory_layout.h"
|
||||
#include "sdkconfig.h"
|
||||
#include "esp_debug_helpers.h"
|
||||
|
||||
#if !XCHAL_HAVE_WINDOWED
|
||||
#warning "gdbstub_xtensa: revisit the implementation for Call0 ABI"
|
||||
#endif
|
||||
|
||||
static void init_regfile(esp_gdbstub_gdb_regfile_t *dst)
|
||||
{
|
||||
memset(dst, 0, sizeof(*dst));
|
||||
}
|
||||
|
||||
static void update_regfile_common(esp_gdbstub_gdb_regfile_t *dst)
|
||||
{
|
||||
if (dst->a[0] & 0x8000000U) {
|
||||
dst->a[0] = (dst->a[0] & 0x3fffffffU) | 0x40000000U;
|
||||
}
|
||||
if (!esp_stack_ptr_is_sane(dst->a[1])) {
|
||||
dst->a[1] = 0xDEADBEEF;
|
||||
}
|
||||
dst->windowbase = 0;
|
||||
dst->windowstart = 0x1;
|
||||
RSR(CONFIGID0, dst->configid0);
|
||||
RSR(CONFIGID1, dst->configid1);
|
||||
}
|
||||
|
||||
void esp_gdbstub_frame_to_regfile(const esp_gdbstub_frame_t *frame, esp_gdbstub_gdb_regfile_t *dst)
|
||||
{
|
||||
init_regfile(dst);
|
||||
const uint32_t *a_regs = (const uint32_t *) &frame->a0;
|
||||
dst->pc = (frame->pc & 0x3fffffffU) | 0x40000000U;
|
||||
|
||||
for (int i = 0; i < 16; i++) {
|
||||
dst->a[i] = a_regs[i];
|
||||
}
|
||||
for (int i = 16; i < 64; i++) {
|
||||
dst->a[i] = 0xDEADBEEF;
|
||||
}
|
||||
|
||||
#if XCHAL_HAVE_LOOPS
|
||||
dst->lbeg = frame->lbeg;
|
||||
dst->lend = frame->lend;
|
||||
dst->lcount = frame->lcount;
|
||||
#endif
|
||||
|
||||
dst->ps = (frame->ps & PS_UM) ? (frame->ps & ~PS_EXCM) : frame->ps;
|
||||
dst->sar = frame->sar;
|
||||
update_regfile_common(dst);
|
||||
}
|
||||
|
||||
#ifdef CONFIG_ESP_GDBSTUB_SUPPORT_TASKS
|
||||
|
||||
static void solicited_frame_to_regfile(const XtSolFrame *frame, esp_gdbstub_gdb_regfile_t *dst)
|
||||
{
|
||||
init_regfile(dst);
|
||||
const uint32_t *a_regs = (const uint32_t *) &frame->a0;
|
||||
dst->pc = (frame->pc & 0x3fffffffU) | 0x40000000U;
|
||||
|
||||
/* only 4 registers saved in the solicited frame */
|
||||
for (int i = 0; i < 4; i++) {
|
||||
dst->a[i] = a_regs[i];
|
||||
}
|
||||
for (int i = 4; i < 64; i++) {
|
||||
dst->a[i] = 0xDEADBEEF;
|
||||
}
|
||||
|
||||
dst->ps = (frame->ps & PS_UM) ? (frame->ps & ~PS_EXCM) : frame->ps;
|
||||
update_regfile_common(dst);
|
||||
}
|
||||
|
||||
/* Represents FreeRTOS TCB structure */
|
||||
typedef struct {
|
||||
uint8_t *top_of_stack;
|
||||
/* Other members aren't needed */
|
||||
} dummy_tcb_t;
|
||||
|
||||
|
||||
void esp_gdbstub_tcb_to_regfile(TaskHandle_t tcb, esp_gdbstub_gdb_regfile_t *dst)
|
||||
{
|
||||
const dummy_tcb_t *dummy_tcb = (const dummy_tcb_t *) tcb;
|
||||
|
||||
const XtExcFrame *frame = (XtExcFrame *) dummy_tcb->top_of_stack;
|
||||
if (frame->exit != 0) {
|
||||
esp_gdbstub_frame_to_regfile(frame, dst);
|
||||
} else {
|
||||
const XtSolFrame *taskFrame = (const XtSolFrame *) dummy_tcb->top_of_stack;
|
||||
solicited_frame_to_regfile(taskFrame, dst);
|
||||
}
|
||||
}
|
||||
|
||||
#endif // CONFIG_ESP_GDBSTUB_SUPPORT_TASKS
|
||||
|
||||
int esp_gdbstub_get_signal(const esp_gdbstub_frame_t *frame)
|
||||
{
|
||||
const char exccause_to_signal[] = {4, 31, 11, 11, 2, 6, 8, 0, 6, 7, 0, 0, 7, 7, 7, 7};
|
||||
if (frame->exccause > sizeof(exccause_to_signal)) {
|
||||
return 11;
|
||||
}
|
||||
return (int) exccause_to_signal[frame->exccause];
|
||||
}
|
Loading…
Reference in New Issue
Block a user