From 7d21ece78b41a1624860b52a414aeb2198010e68 Mon Sep 17 00:00:00 2001 From: Ivan Grokhotkov Date: Fri, 14 Jun 2019 00:43:49 +0800 Subject: [PATCH] gdbstub: move to a separate component, support multiple targets --- components/esp32/CMakeLists.txt | 1 - components/esp32/Kconfig | 1 + components/esp32/gdbstub.c | 557 ------------------ components/esp_common/Kconfig | 18 - components/esp_gdbstub/CMakeLists.txt | 13 + components/esp_gdbstub/Kconfig | 25 + components/esp_gdbstub/component.mk | 4 + components/esp_gdbstub/esp32/gdbstub_esp32.c | 51 ++ .../esp_gdbstub/esp32/gdbstub_target_config.h | 18 + components/esp_gdbstub/include/esp_gdbstub.h | 27 + components/esp_gdbstub/linker.lf | 4 + .../private_include/esp_gdbstub_common.h | 151 +++++ components/esp_gdbstub/src/gdbstub.c | 343 +++++++++++ components/esp_gdbstub/src/packet.c | 177 ++++++ .../esp_gdbstub/xtensa/esp_gdbstub_arch.h | 91 +++ .../esp_gdbstub/xtensa/gdbstub_xtensa.c | 118 ++++ 16 files changed, 1023 insertions(+), 576 deletions(-) delete mode 100644 components/esp32/gdbstub.c create mode 100644 components/esp_gdbstub/CMakeLists.txt create mode 100644 components/esp_gdbstub/Kconfig create mode 100644 components/esp_gdbstub/component.mk create mode 100644 components/esp_gdbstub/esp32/gdbstub_esp32.c create mode 100644 components/esp_gdbstub/esp32/gdbstub_target_config.h create mode 100644 components/esp_gdbstub/include/esp_gdbstub.h create mode 100644 components/esp_gdbstub/linker.lf create mode 100644 components/esp_gdbstub/private_include/esp_gdbstub_common.h create mode 100644 components/esp_gdbstub/src/gdbstub.c create mode 100644 components/esp_gdbstub/src/packet.c create mode 100644 components/esp_gdbstub/xtensa/esp_gdbstub_arch.h create mode 100644 components/esp_gdbstub/xtensa/gdbstub_xtensa.c diff --git a/components/esp32/CMakeLists.txt b/components/esp32/CMakeLists.txt index 8c25a3f38a..701075be99 100644 --- a/components/esp32/CMakeLists.txt +++ b/components/esp32/CMakeLists.txt @@ -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" diff --git a/components/esp32/Kconfig b/components/esp32/Kconfig index e5b5dee5b3..d8441d7738 100644 --- a/components/esp32/Kconfig +++ b/components/esp32/Kconfig @@ -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. diff --git a/components/esp32/gdbstub.c b/components/esp32/gdbstub.c deleted file mode 100644 index 08623dce4c..0000000000 --- a/components/esp32/gdbstub.c +++ /dev/null @@ -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 -#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='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 (ia0; - 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 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); -} diff --git a/components/esp_common/Kconfig b/components/esp_common/Kconfig index 80f20e6069..ca5608b8e4 100644 --- a/components/esp_common/Kconfig +++ b/components/esp_common/Kconfig @@ -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 diff --git a/components/esp_gdbstub/CMakeLists.txt b/components/esp_gdbstub/CMakeLists.txt new file mode 100644 index 0000000000..14528a4b52 --- /dev/null +++ b/components/esp_gdbstub/CMakeLists.txt @@ -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") diff --git a/components/esp_gdbstub/Kconfig b/components/esp_gdbstub/Kconfig new file mode 100644 index 0000000000..14e7d859c2 --- /dev/null +++ b/components/esp_gdbstub/Kconfig @@ -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 diff --git a/components/esp_gdbstub/component.mk b/components/esp_gdbstub/component.mk new file mode 100644 index 0000000000..97dfce265f --- /dev/null +++ b/components/esp_gdbstub/component.mk @@ -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 diff --git a/components/esp_gdbstub/esp32/gdbstub_esp32.c b/components/esp_gdbstub/esp32/gdbstub_esp32.c new file mode 100644 index 0000000000..3fe393a0db --- /dev/null +++ b/components/esp_gdbstub/esp32/gdbstub_esp32.c @@ -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_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; +} diff --git a/components/esp_gdbstub/esp32/gdbstub_target_config.h b/components/esp_gdbstub/esp32/gdbstub_target_config.h new file mode 100644 index 0000000000..ae31ae9dae --- /dev/null +++ b/components/esp_gdbstub/esp32/gdbstub_target_config.h @@ -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 diff --git a/components/esp_gdbstub/include/esp_gdbstub.h b/components/esp_gdbstub/include/esp_gdbstub.h new file mode 100644 index 0000000000..02fda63e58 --- /dev/null +++ b/components/esp_gdbstub/include/esp_gdbstub.h @@ -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 diff --git a/components/esp_gdbstub/linker.lf b/components/esp_gdbstub/linker.lf new file mode 100644 index 0000000000..b5d88a2675 --- /dev/null +++ b/components/esp_gdbstub/linker.lf @@ -0,0 +1,4 @@ +[mapping:esp_gdbstub] +archive: libesp_gdbstub.a +entries: + * (noflash) diff --git a/components/esp_gdbstub/private_include/esp_gdbstub_common.h b/components/esp_gdbstub/private_include/esp_gdbstub_common.h new file mode 100644 index 0000000000..75674ff026 --- /dev/null +++ b/components/esp_gdbstub/private_include/esp_gdbstub_common.h @@ -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 +#include +#include + +#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); + diff --git a/components/esp_gdbstub/src/gdbstub.c b/components/esp_gdbstub/src/gdbstub.c new file mode 100644 index 0000000000..4d1f4cf54c --- /dev/null +++ b/components/esp_gdbstub/src/gdbstub.c @@ -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 +#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 + diff --git a/components/esp_gdbstub/src/packet.c b/components/esp_gdbstub/src/packet.c new file mode 100644 index 0000000000..9bf0c5d5a7 --- /dev/null +++ b/components/esp_gdbstub/src/packet.c @@ -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; + } +} diff --git a/components/esp_gdbstub/xtensa/esp_gdbstub_arch.h b/components/esp_gdbstub/xtensa/esp_gdbstub_arch.h new file mode 100644 index 0000000000..18b119cb3f --- /dev/null +++ b/components/esp_gdbstub/xtensa/esp_gdbstub_arch.h @@ -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 +#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 diff --git a/components/esp_gdbstub/xtensa/gdbstub_xtensa.c b/components/esp_gdbstub/xtensa/gdbstub_xtensa.c new file mode 100644 index 0000000000..853b1ba085 --- /dev/null +++ b/components/esp_gdbstub/xtensa/gdbstub_xtensa.c @@ -0,0 +1,118 @@ +// 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 +#include "esp_gdbstub.h" +#include "esp_gdbstub_common.h" +#include "soc/cpu.h" +#include "soc/soc_memory_layout.h" +#include "sdkconfig.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]; +}