Merge branch 'feature/esp32s2beta_gdbstub' into 'feature/esp32s2beta'

esp32s2beta: add gdbstub support

See merge request espressif/esp-idf!5568
This commit is contained in:
Ivan Grokhotkov 2019-07-29 19:00:18 +08:00
commit 3f49d71258
21 changed files with 1094 additions and 933 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View 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")

View 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

View 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

View 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;
}

View 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

View 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;
}

View 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

View 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

View File

@ -0,0 +1,4 @@
[mapping:esp_gdbstub]
archive: libesp_gdbstub.a
entries:
* (noflash)

View 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);

View 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

View 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;
}
}

View 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

View 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];
}