The esp8266 portion of the project
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

926 lines
27 KiB

/******************************************************************************
* Copyright 2015 Espressif Systems
*
* Description: A stub to make the ESP8266 debuggable by GDB over the serial
* port.
*
* License: ESPRESSIF MIT License
*******************************************************************************/
#include <GDBStub.h>
#include <stddef.h>
#include <Arduino.h>
#include "ets_sys.h"
#include "eagle_soc.h"
#include "c_types.h"
#include "gpio.h"
#include "xtensa/corebits.h"
#include "uart_register.h"
#include "gdbstub-entry.h"
#include "gdbstub-cfg.h"
//From xtruntime-frames.h
struct XTensa_exception_frame_s {
uint32_t pc;
uint32_t ps;
uint32_t sar;
uint32_t vpri;
uint32_t a[16]; //a0..a15
//These are added manually by the exception code; the HAL doesn't set these on an exception.
uint32_t litbase;
uint32_t sr176;
uint32_t sr208;
//'reason' is abused for both the debug and the exception vector: if bit 7 is set,
//this contains an exception reason, otherwise it contains a debug vector bitmap.
uint32_t reason;
};
#if GDBSTUB_FREERTOS
struct XTensa_rtos_int_frame_s {
uint32_t exitPtr;
uint32_t pc;
uint32_t ps;
uint32_t a[16];
uint32_t sar;
};
/*
Definitions for FreeRTOS. This redefines some os_* functions to use their non-os* counterparts. It
also sets up some function pointers for ROM functions that aren't in the FreeRTOS ld files.
*/
#include <string.h>
#include <stdio.h>
void os_isr_attach(int inum, void *fn);
void os_install_putc1(void (*p)(char c));
#define os_printf(...) printf(__VA_ARGS__)
#define os_strncmp(...) strncmp(__VA_ARGS__)
typedef void wdtfntype();
static wdtfntype *ets_wdt_disable = (wdtfntype *)0x400030f0;
static wdtfntype *ets_wdt_enable = (wdtfntype *)0x40002fa0;
#else
/*
OS-less SDK defines. Defines some headers for things that aren't in the include files, plus
the xthal stack frame struct.
*/
#include "osapi.h"
#include "user_interface.h"
void _xtos_set_exception_handler(int cause, void (exhandler)(struct XTensa_exception_frame_s *frame));
#endif
#define EXCEPTION_GDB_SP_OFFSET 0x100
//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 190 bytes.
#define PBUFLEN 256
//The asm stub saves the Xtensa registers here when a debugging exception happens.
struct XTensa_exception_frame_s gdbstub_savedRegs;
#if GDBSTUB_USE_OWN_STACK
//This is the debugging exception stack.
int exceptionStack[256];
#endif
static bool gdb_attached = false;
static unsigned char cmd[PBUFLEN]; //GDB command input buffer
static char chsum; //Running checksum of the output packet
#if GDBSTUB_CTRLC_BREAK
static void (*uart_isr_callback)(void*, uint8_t) = NULL;
static void* uart_isr_arg = NULL;
#endif
#if GDBSTUB_REDIRECT_CONSOLE_OUTPUT
static void (*uart_putc1_callback)(char) = NULL;
#endif
//Stores ps when single-stepping instruction. -1 when not in use.
static int32_t singleStepPs = -1;
//Uart libs can reference these to see if gdb is attaching to them
bool gdbstub_has_putc1_control() {
#if GDBSTUB_REDIRECT_CONSOLE_OUTPUT
return true;
#else
return false;
#endif
}
bool gdbstub_has_uart_isr_control() {
#if GDBSTUB_CTRLC_BREAK
return true;
#else
return false;
#endif
}
//Small function to feed the hardware watchdog. Needed to stop the ESP from resetting
//due to a watchdog timeout while reading a command.
static void ATTR_GDBFN keepWDTalive() {
uint64_t *wdtval = (uint64_t*)0x3ff21048;
uint64_t *wdtovf = (uint64_t*)0x3ff210cc;
int *wdtctl = (int*)0x3ff210c8;
*wdtovf = *wdtval + 1600000;
*wdtctl |= 1 << 31;
}
//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
#define ST_DETACH -5
//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 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 iswap(int i) {
return ((i >> 24) & 0xff)
| (((i >> 16) & 0xff) << 8)
| (((i >> 8) & 0xff) << 16)
| (((i >> 0) & 0xff) << 24);
}
//Read a byte from the ESP8266 memory.
static unsigned char readbyte(unsigned int p) {
if (p < 0x20000000 || p >= 0x60000000) return -1;
int *i = (int*)(p & ~3);
return *i >> ((p & 3) * 8);
}
//Write a byte to the ESP8266 memory.
static void writeByte(unsigned int p, unsigned char d) {
if (p < 0x20000000 || p >= 0x60000000) return;
int *i = (int*)(p & ~3);
if ((p & 3) == 0) *i = (*i & 0xffffff00) | (d << 0);
else if ((p & 3) == 1) *i = (*i & 0xffff00ff) | (d << 8);
else if ((p & 3) == 2) *i = (*i & 0xff00ffff) | (d << 16);
else if ((p & 3) == 3) *i = (*i & 0x00ffffff) | (d << 24);
}
//Returns 1 if it makes sense to write to addr p
static int validWrAddr(int p) {
return (p >= 0x3ff00000 && p < 0x40000000)
|| (p >= 0x40100000 && p < 0x40140000)
|| (p >= 0x60000000 && p < 0x60002000);
}
static inline bool ATTR_GDBFN gdbRxFifoIsEmpty() {
return ((READ_PERI_REG(UART_STATUS(0)) >> UART_RXFIFO_CNT_S) & UART_RXFIFO_CNT) == 0;
}
static inline bool ATTR_GDBFN gdbTxFifoIsFull() {
return ((READ_PERI_REG(UART_STATUS(0)) >> UART_TXFIFO_CNT_S) & UART_TXFIFO_CNT) >= 126;
}
//Receive a char from the uart. Uses polling and feeds the watchdog.
static inline int gdbRecvChar() {
while (gdbRxFifoIsEmpty()) {
keepWDTalive();
}
return READ_PERI_REG(UART_FIFO(0));
}
//Send a char to the uart.
static void gdbSendChar(char c) {
while (gdbTxFifoIsFull())
;
WRITE_PERI_REG(UART_FIFO(0), c);
}
//Send the start of a packet; reset checksum calculation.
static void gdbPacketStart() {
chsum = 0;
gdbSendChar('$');
}
//Send a char as part of a packet
static void gdbPacketChar(char c) {
if (c == '#' || c == '$' || c == '}' || c == '*') {
gdbSendChar('}');
chsum += '}';
c ^= 0x20;
}
gdbSendChar(c);
chsum += c;
}
//Send a hex val as part of a packet. 'bits'/4 dictates the number of hex chars sent.
static void gdbPacketHex(int val, int bits) {
static const char hexChars[] = "0123456789abcdef";
int i;
for (i = bits; i > 0; i -= 4) {
gdbPacketChar(hexChars[(val >> (i - 4)) & 0xf]);
}
}
//Send a hex val as part of a packet. 'bits'/4 dictates the number of hex chars sent.
static void gdbPacketSwappedHexInt(int val) {
gdbPacketHex(iswap(val), 32);
}
static void gdbPacketXXXXInt() {
for (int i=0; i<8; i++) gdbPacketChar('x');
}
//Finish sending a packet.
static void gdbPacketEnd() {
gdbSendChar('#');
//Ok to use packet version here since hex char can never be an
//excape-requiring character
gdbPacketHex(chsum, 8);
}
// Send a complete packet containing str
static void gdbSendPacketStr(const char *c) {
gdbPacketStart();
while (*c != 0) {
gdbPacketChar(*c);
c++;
}
gdbPacketEnd();
}
// Send a complete packet containing str as an output message
static inline void ATTR_GDBEXTERNFN gdbSendOutputPacketStr(const unsigned char* buf, size_t size) {
size_t i;
gdbPacketStart();
gdbPacketChar('O');
for (i = 0; i < size; i++)
gdbPacketHex(buf[i], 8);
gdbPacketEnd();
}
// Send a complete packet containing c as an output message
static inline void ATTR_GDBEXTERNFN gdbSendOutputPacketChar(unsigned char c) {
gdbPacketStart();
gdbPacketChar('O');
gdbPacketHex(c, 8);
gdbPacketEnd();
}
static long gdbGetSwappedHexInt(unsigned char **ptr) {
return iswap(gdbGetHexVal(ptr, 32));
}
//Send the reason execution is stopped to GDB.
static void sendReason() {
static const char exceptionSignal[] = {4,31,11,11,2,6,8,0,6,7,0,0,7,7,7,7};
#if 0
char *reason=""; //default
#endif
//exception-to-signal mapping
size_t i;
gdbPacketStart();
gdbPacketChar('T');
if (gdbstub_savedRegs.reason == 0xff) {
gdbPacketHex(2, 8); //sigint
} else if (gdbstub_savedRegs.reason & 0x80) {
//We stopped because of an exception. Convert exception code to a signal number and send it.
i = gdbstub_savedRegs.reason & 0x7f;
if (i < sizeof(exceptionSignal))
gdbPacketHex(exceptionSignal[i], 8);
else
gdbPacketHex(11, 8);
} else {
//We stopped because of a debugging exception.
gdbPacketHex(5, 8); //sigtrap
//Current Xtensa GDB versions don't seem to request this, so let's leave it off.
#if 0
if (gdbstub_savedRegs.reason&(1<<0)) reason="break";
if (gdbstub_savedRegs.reason&(1<<1)) reason="hwbreak";
if (gdbstub_savedRegs.reason&(1<<2)) reason="watch";
if (gdbstub_savedRegs.reason&(1<<3)) reason="swbreak";
if (gdbstub_savedRegs.reason&(1<<4)) reason="swbreak";
gdbPacketStr(reason);
gdbPacketChar(':');
//ToDo: watch: send address
#endif
}
gdbPacketEnd();
}
static inline void ATTR_GDBFN gdbSendPacketOK() {
gdbSendPacketStr("OK");
}
static inline void ATTR_GDBFN gdbSendPacketE01() {
gdbSendPacketStr("E01");
}
static inline void ATTR_GDBFN gdbSendEmptyPacket() {
gdbPacketStart();
gdbPacketEnd();
}
void ATTR_GDBEXTERNFN gdbstub_write_char(char c) {
if (gdb_attached) {
ETS_UART_INTR_DISABLE();
gdbSendOutputPacketChar(c);
ETS_UART_INTR_ENABLE();
} else {
gdbSendChar(c);
}
}
void ATTR_GDBEXTERNFN gdbstub_write(const char* buf, size_t size) {
size_t i;
if (gdb_attached) {
ETS_UART_INTR_DISABLE();
gdbSendOutputPacketStr((const unsigned char *)buf, size);
ETS_UART_INTR_ENABLE();
} else {
for (i = 0; i < size; i++) {
gdbSendChar(buf[i]);
}
}
}
/*
Register file in the format lx106 gdb port expects it.
Inspired by gdb/regformats/reg-xtensa.dat from
https://github.com/jcmvbkbc/crosstool-NG/blob/lx106-g%2B%2B/overlays/xtensa_lx106.tar
As decoded by Cesanta.
struct regfile {
uint32_t a[16];
uint32_t pc;
uint32_t sar;
uint32_t litbase;
uint32_t sr176;
uint32_t sr208;
uint32_t ps;
};
*/
//Handle a command as received from GDB.
static inline int gdbHandleCommand() {
//Handle a command
int i, j, k;
unsigned char *data = cmd + 1;
if (cmd[0]=='g') { //send all registers to gdb
gdbPacketStart();
gdbPacketSwappedHexInt(gdbstub_savedRegs.pc);
for (int i=1; i<=35; i++) gdbPacketXXXXInt();
gdbPacketSwappedHexInt(gdbstub_savedRegs.sar);
gdbPacketSwappedHexInt(gdbstub_savedRegs.litbase);
for (int i=38; i<=39; i++) gdbPacketXXXXInt();
gdbPacketSwappedHexInt(gdbstub_savedRegs.sr176);
for (int i=41; i<=41; i++) gdbPacketXXXXInt();
gdbPacketSwappedHexInt(gdbstub_savedRegs.ps);
for (int i=43; i<=96; i++) gdbPacketXXXXInt();
for (i=0; i<16; i++) gdbPacketSwappedHexInt(gdbstub_savedRegs.a[i]);
gdbPacketEnd();
} else if (cmd[0]=='G') { //receive content for all registers from gdb
gdbstub_savedRegs.pc=gdbGetSwappedHexInt(&data);
for (int i=1; i<=35; i++) gdbGetHexVal(&data, 32);
gdbstub_savedRegs.sar=gdbGetSwappedHexInt(&data);
gdbstub_savedRegs.litbase=gdbGetSwappedHexInt(&data);
for (int i=38; i<=39; i++) gdbGetHexVal(&data, 32);
gdbstub_savedRegs.sr176=gdbGetSwappedHexInt(&data);
for (int i=41; i<=41; i++) gdbGetHexVal(&data, 32);
gdbstub_savedRegs.ps=gdbGetSwappedHexInt(&data);
for (int i=43; i<=96; i++) gdbGetHexVal(&data, 32);
for (i=0; i<16; i++) gdbstub_savedRegs.a[i]=gdbGetSwappedHexInt(&data);
gdbSendPacketOK();
} else if ((cmd[0] | 0x20) == 'm') { //read/write memory to gdb
i = gdbGetHexVal(&data, -1); //addr
data++;
j = gdbGetHexVal(&data, -1); //length
if (cmd[0] == 'm') { //read memory to gdb
gdbPacketStart();
for (k = 0; k < j; k++) {
gdbPacketHex(readbyte(i++), 8);
}
gdbPacketEnd();
} else { //write memory from gdb
if (validWrAddr(i) && validWrAddr(i + j)) {
data++; //skip :
for (k = 0; k < j; k++, i++) {
writeByte(i, gdbGetHexVal(&data, 8));
}
//Make sure caches are up-to-date. Procedure according to Xtensa ISA document, ISYNC inst desc.
asm volatile("ISYNC\nISYNC\n");
gdbSendPacketOK();
} else {
//Trying to do a software breakpoint on a flash proc, perhaps?
gdbSendPacketE01();
}
}
} else if (cmd[0] == '?') { //Reply with stop reason
sendReason();
} else if (cmd[0] == 'c') { //continue execution
return ST_CONT;
} else if (cmd[0] == 's') { //single-step instruction
//Single-stepping can go wrong if an interrupt is pending, especially when it is e.g. a task switch:
//the ICOUNT register will overflow in the task switch code. That is why we disable interupts when
//doing single-instruction stepping.
singleStepPs=gdbstub_savedRegs.ps;
gdbstub_savedRegs.ps=(gdbstub_savedRegs.ps & ~0xf) | (XCHAL_DEBUGLEVEL - 1);
gdbstub_icount_ena_single_step();
return ST_CONT;
} else if (cmd[0] == 'D') { //detach
gdbSendPacketOK();
return ST_DETACH;
} else if (cmd[0] == 'k') { //kill
system_restart_core();
} else if (cmd[0] == 'q') { //Extended query
if (os_strncmp((char*)&cmd[1], "Supported", 9) == 0) { //Capabilities query
gdbSendPacketStr("swbreak+;hwbreak+;PacketSize=FF"); //PacketSize is in hex
} else if (os_strncmp((char*)&cmd[1], "Attached", 8) == 0) {
//Let gdb know that it is attaching to a running program
//In general that just means it detaches instead of killing when it exits
gdbSendPacketStr("1");
} else {
//We don't support other queries.
gdbSendEmptyPacket();
}
// case insensitive compare matches 'Z' or 'z'
} else if ((cmd[0] | 0x20) == 'z' && cmd[1] >= '1' && cmd[2] <= '4') { //hardware break/watchpoint
int result;
data += 2; //skip 'x,'
i = gdbGetHexVal(&data, -1);
data++; //skip ','
j = gdbGetHexVal(&data, -1);
if (cmd[0] == 'Z') { //Set hardware break/watchpoint
if (cmd[1] == '1') { //Set breakpoint
result = gdbstub_set_hw_breakpoint(i, j);
} else { //Set watchpoint
int access;
unsigned int mask = 0;
if (cmd[1] == '2') access = 2; //write
if (cmd[1] == '3') access = 1; //read
if (cmd[1] == '4') access = 3; //access
if (j == 1) mask = 0x3F;
if (j == 2) mask = 0x3E;
if (j == 4) mask = 0x3C;
if (j == 8) mask = 0x38;
if (j == 16) mask = 0x30;
if (j == 32) mask = 0x20;
result = mask != 0 && gdbstub_set_hw_watchpoint(i, mask, access);
}
} else { //Clear hardware break/watchpoint
if (cmd[1] == '1') { //hardware breakpoint
result = gdbstub_del_hw_breakpoint(i);
} else { //hardware watchpoint
result = gdbstub_del_hw_watchpoint(i);
}
}
if (result) {
gdbSendPacketOK();
} else {
gdbSendPacketE01();
}
} else {
//We don't recognize or support whatever GDB just sent us.
gdbSendEmptyPacket();
}
return ST_OK;
}
//Lower layer: grab a command packet and check the checksum
//Calls gdbHandleCommand on the packet if the checksum is OK
//Returns only if execution of the user program should continue
//Otherwise keeps reading uart data and executing commands
//Flags that gdb has been attached whenever a gdb formatted
// packet is received
//While gdb is attached, checks for ctl-c (\x03) if it's not
// already paused
//Keeps reading commands if it is paused, until either a
// continue, detach, or kill command is received
//It is not necessary for gdb to be attached for it to be paused
//For example, during an exception break, the program is
// paused but gdb might not be attached yet
static int gdbReadCommand() {
unsigned char chsum;
unsigned char sentchs[2];
size_t p;
unsigned char c;
unsigned char *ptr;
int result;
ETS_UART_INTR_DISABLE();
ets_wdt_disable();
sendReason();
while (true) {
gdbReadCommand_start:
while (gdbRecvChar() != '$')
;
gdbReadCommand_packetBegin:
chsum = 0;
p = 0;
while ((c = gdbRecvChar()) != '#') { //end of packet, checksum follows
if (c == '$') {
//Wut, restart packet?
goto gdbReadCommand_packetBegin;
}
if (c == '}') { //escape the next char
c = gdbRecvChar() ^ 0x20;
}
chsum += c;
cmd[p++] = c;
if (p >= PBUFLEN) {
//Received more than the size of the command buffer
goto gdbReadCommand_start;
}
}
cmd[p] = 0;
sentchs[0] = gdbRecvChar();
sentchs[1] = gdbRecvChar();
ptr = &sentchs[0];
if (gdbGetHexVal(&ptr, 8) == chsum) {
gdb_attached = true;
gdbSendChar('+');
result = gdbHandleCommand();
if (result != ST_OK) {
break;
}
} else {
gdbSendChar('-');
}
}
if (result == ST_DETACH) {
gdb_attached = false;
}
ets_wdt_enable();
ETS_UART_INTR_ENABLE();
return result;
}
//Get the value of one of the A registers
static unsigned int ATTR_GDBFN getaregval(int reg) {
return gdbstub_savedRegs.a[reg];
}
//Set the value of one of the A registers
static inline void ATTR_GDBFN setaregval(int reg, unsigned int val) {
// os_printf("%x -> %x\n", val, reg);
gdbstub_savedRegs.a[reg] = val;
}
//Emulate the l32i/s32i instruction we're stopped at.
static inline void emulLdSt() {
unsigned char i0 = readbyte(gdbstub_savedRegs.pc);
unsigned char i1 = readbyte(gdbstub_savedRegs.pc + 1);
unsigned char i2;
int *p;
if ((i0 & 0xf) == 2 && (i1 & 0xb0) == 0x20) {
//l32i or s32i
i2 = readbyte(gdbstub_savedRegs.pc + 2);
p = (int*)getaregval(i1 & 0xf) + (i2 * 4);
i0 >>= 4;
if ((i1 & 0xf0) == 0x20) { //l32i
setaregval(i0, *p);
} else { //s32i
*p = getaregval(i0);
}
gdbstub_savedRegs.pc += 3;
} else if ((i0 & 0xe) == 0x8) {
//l32i.n or s32i.n
p = (int*)getaregval(i1 & 0xf) + ((i1 >> 4) * 4);
if ((i0 & 0xf) == 0x8) { //l32i.n
setaregval(i0 >> 4, *p);
} else {
*p = getaregval(i0 >> 4);
}
gdbstub_savedRegs.pc += 2;
// } else {
// os_printf("GDBSTUB: No l32i/s32i instruction: %x %x. Huh?", i1, i0);
}
}
//We just caught a debug exception and need to handle it. This is called from an assembly
//routine in gdbstub-entry.S
static void gdbstub_handle_debug_exception_flash();
void ATTR_GDBFN gdbstub_handle_debug_exception() {
Cache_Read_Enable_New();
gdbstub_handle_debug_exception_flash();
}
static void __attribute__((noinline)) gdbstub_handle_debug_exception_flash() {
if (singleStepPs != -1) {
//We come here after single-stepping an instruction. Interrupts are disabled
//for the single step. Re-enable them here.
gdbstub_savedRegs.ps = (gdbstub_savedRegs.ps & ~0xf) | (singleStepPs & 0xf);
singleStepPs =- 1;
}
gdbReadCommand();
if ((gdbstub_savedRegs.reason & 0x80) == 0) { //Watchpoint/BREAK/BREAK.N
if ((gdbstub_savedRegs.reason & 0x4) != 0) {
//We stopped due to a watchpoint. We can't re-execute the current instruction
//because it will happily re-trigger the same watchpoint, so we emulate it
//while we're still in debugger space.
emulLdSt();
} else if ((((gdbstub_savedRegs.reason & 0x8) != 0)
//We stopped due to a BREAK instruction. Skip over it.
//Check the instruction first; gdb may have replaced it with the original instruction
//if it's one of the breakpoints it set.
&& readbyte(gdbstub_savedRegs.pc + 2) == 0
&& (readbyte(gdbstub_savedRegs.pc + 1) & 0xf0) == 0x40
&& (readbyte(gdbstub_savedRegs.pc) & 0x0f) == 0x00)
|| (((gdbstub_savedRegs.reason & 0x10) != 0)
//We stopped due to a BREAK.N instruction. Skip over it, after making sure the instruction
//actually is a BREAK.N
&& (readbyte(gdbstub_savedRegs.pc + 1) & 0xf0) == 0xf0
&& readbyte(gdbstub_savedRegs.pc) == 0x2d)) {
gdbstub_savedRegs.pc += 3;
}
}
}
#if GDBSTUB_BREAK_ON_EXCEPTION || GDBSTUB_CTRLC_BREAK
#if !GDBSTUB_FREERTOS
static inline int gdbReadCommandWithFrame(void* frame) {
//Copy registers the Xtensa HAL did save to gdbstub_savedRegs
os_memcpy(&gdbstub_savedRegs, frame, 5 * 4);
os_memcpy(&gdbstub_savedRegs.a[2], ((uint32_t*)frame) + 5, 14 * 4);
//Credits go to Cesanta for this trick. A1 seems to be destroyed, but because it
//has a fixed offset from the address of the passed frame, we can recover it.
gdbstub_savedRegs.a[1] = (uint32_t)frame + EXCEPTION_GDB_SP_OFFSET;
int result = gdbReadCommand();
//Copy any changed registers back to the frame the Xtensa HAL uses.
os_memcpy(frame, &gdbstub_savedRegs, 5 * 4);
os_memcpy(((uint32_t*)frame) + 5, &gdbstub_savedRegs.a[2], 14 * 4);
return result;
}
#endif
#endif
#if GDBSTUB_BREAK_ON_EXCEPTION
#if GDBSTUB_FREERTOS
//Freertos exception. This routine is called by an assembly routine in gdbstub-entry.S
void ATTR_GDBFN gdbstub_handle_user_exception() {
gdbstub_savedRegs.reason |= 0x80; //mark as an exception reason
while (gdbReadCommand() != ST_CONT)
;
}
//FreeRTOS doesn't use the Xtensa HAL for exceptions, but uses its own fatal exception handler.
//We use a small hack to replace that with a jump to our own handler, which then has the task of
//decyphering and re-instating the registers the FreeRTOS code left.
extern void user_fatal_exception_handler();
extern void gdbstub_user_exception_entry();
static void ATTR_GDBINIT install_exceptions() {
//Replace the user_fatal_exception_handler by a jump to our own code
int *ufe = (int*)user_fatal_exception_handler;
//This mess encodes as a relative jump instruction to user_fatal_exception_handler
*ufe = ((((int)gdbstub_user_exception_entry - (int)user_fatal_exception_handler) - 4) << 6) | 6;
}
#else
//Non-OS exception handler. Gets called by the Xtensa HAL.
static void gdbstub_exception_handler_flash(struct XTensa_exception_frame_s *frame);
static void ATTR_GDBFN gdbstub_exception_handler(struct XTensa_exception_frame_s *frame) {
//Save the extra registers the Xtensa HAL doesn't save
gdbstub_save_extra_sfrs_for_exception();
Cache_Read_Enable_New();
gdbstub_exception_handler_flash(frame);
}
static void __attribute__((noinline)) gdbstub_exception_handler_flash(struct XTensa_exception_frame_s *frame) {
gdbstub_savedRegs.reason |= 0x80; //mark as an exception reason
while (gdbReadCommandWithFrame((void*)frame) != ST_CONT)
;
}
//The OS-less SDK uses the Xtensa HAL to handle exceptions. We can use those functions to catch any
//fatal exceptions and invoke the debugger when this happens.
static void ATTR_GDBINIT install_exceptions() {
static int exno[] = {EXCCAUSE_ILLEGAL, EXCCAUSE_SYSCALL, EXCCAUSE_INSTR_ERROR, EXCCAUSE_LOAD_STORE_ERROR,
EXCCAUSE_DIVIDE_BY_ZERO, EXCCAUSE_UNALIGNED, EXCCAUSE_INSTR_DATA_ERROR, EXCCAUSE_LOAD_STORE_DATA_ERROR,
EXCCAUSE_INSTR_ADDR_ERROR, EXCCAUSE_LOAD_STORE_ADDR_ERROR, EXCCAUSE_INSTR_PROHIBITED,
EXCCAUSE_LOAD_PROHIBITED, EXCCAUSE_STORE_PROHIBITED};
unsigned int i;
for (i = 0; i < (sizeof(exno) / sizeof(exno[0])); i++) {
_xtos_set_exception_handler(exno[i], gdbstub_exception_handler);
}
}
#endif
#endif
#if GDBSTUB_REDIRECT_CONSOLE_OUTPUT
//Replacement putchar1 routine. Instead of spitting out the character directly, it will buffer up to
//OBUFLEN characters (or up to a \n, whichever comes earlier) and send it out as a gdb stdout packet.
static void ATTR_GDBEXTERNFN gdbstub_semihost_putchar1(char c) {
if (!gdb_attached && uart_putc1_callback != NULL) {
uart_putc1_callback(c);
} else {
gdbstub_write_char(c);
}
}
void ATTR_GDBINIT gdbstub_set_putc1_callback(void (*func)(char)) {
uart_putc1_callback = func;
}
#endif
#if GDBSTUB_FREERTOS
static void ATTR_GDBINIT configure_uart() {}
#else
static void ATTR_GDBINIT configure_uart() {
#ifdef ARDUINO
// Set the UART input/output pins to TX=1, RX=3
pinMode(3, SPECIAL);
pinMode(1, FUNCTION_0);
#endif
WRITE_PERI_REG(UART_CONF0(0), 0b00011100); //8N1
SET_PERI_REG_MASK(UART_CONF0(0), UART_RXFIFO_RST | UART_TXFIFO_RST); //RESET FIFO
CLEAR_PERI_REG_MASK(UART_CONF0(0), UART_RXFIFO_RST | UART_TXFIFO_RST);
}
#endif
#if GDBSTUB_CTRLC_BREAK
#if GDBSTUB_FREERTOS
void ATTR_GDBFN gdbstub_handle_uart_int(struct XTensa_rtos_int_frame_s *frame) {
int doDebug = 0, fifolen, x;
fifolen = (READ_PERI_REG(UART_STATUS(0)) >> UART_RXFIFO_CNT_S)&UART_RXFIFO_CNT;
while (fifolen != 0) {
//Check if any of the chars is control-C. Throw away rest.
if ((READ_PERI_REG(UART_FIFO(0)) & 0xFF) == 0x3)
doDebug = 1;
fifolen--;
}
WRITE_PERI_REG(UART_INT_CLR(0), UART_RXFIFO_FULL_INT_CLR | UART_RXFIFO_TOUT_INT_CLR);
if (doDebug) {
//Copy registers the Xtensa HAL did save to gdbstub_savedRegs
gdbstub_savedRegs.pc = frame->pc;
gdbstub_savedRegs.ps = frame->ps;
gdbstub_savedRegs.sar = frame->sar;
for (x = 0; x < 16; x++)
gdbstub_savedRegs.a[x] = frame->a[x];
// gdbstub_savedRegs.a1=(uint32_t)frame+EXCEPTION_GDB_SP_OFFSET;
gdbstub_savedRegs.reason = 0xff; //mark as user break reason
gdbReadCommand();
//Copy any changed registers back to the frame the Xtensa HAL uses.
frame->pc = gdbstub_savedRegs.pc;
frame->ps = gdbstub_savedRegs.ps;
frame->sar = gdbstub_savedRegs.sar;
for (x = 0; x < 16; x++)
frame->a[x] = gdbstub_savedRegs.a[x];
}
}
static void ATTR_GDBINIT install_uart_hdlr() {
os_isr_attach(ETS_UART_INUM, gdbstub_uart_entry);
SET_PERI_REG_MASK(UART_INT_ENA(0), UART_RXFIFO_FULL_INT_ENA | UART_RXFIFO_TOUT_INT_ENA);
ETS_UART_INTR_ENABLE();
}
#else
static void ATTR_GDBFN gdbstub_uart_hdlr(void* arg, void* frame) {
(void) arg;
unsigned char c;
//Save the extra registers the Xtensa HAL doesn't save
gdbstub_save_extra_sfrs_for_exception();
ETS_UART_INTR_DISABLE();
WRITE_PERI_REG(UART_INT_CLR(0), UART_RXFIFO_FULL_INT_CLR | UART_RXFIFO_TOUT_INT_CLR);
int fifolen = (READ_PERI_REG(UART_STATUS(0)) >> UART_RXFIFO_CNT_S)&UART_RXFIFO_CNT;
while (true) {
if (fifolen == 0) {
ETS_UART_INTR_ENABLE();
return;
}
c = READ_PERI_REG(UART_FIFO(0)) & 0xFF;
//Check if any of the chars is control-C
if (c == 0x3) {
break;
}
#if GDBSTUB_CTRLC_BREAK
if (!gdb_attached && uart_isr_callback != NULL) {
uart_isr_callback(uart_isr_arg, c);
}
#endif
fifolen--;
}
gdbstub_savedRegs.reason = 0xff; //mark as user break reason
gdbReadCommandWithFrame(frame);
}
static void ATTR_GDBINIT install_uart_hdlr() {
ETS_UART_INTR_DISABLE();
ETS_UART_INTR_ATTACH(gdbstub_uart_hdlr, NULL);
configure_uart();
WRITE_PERI_REG(UART_CONF1(0),
((16 & UART_RXFIFO_FULL_THRHD) << UART_RXFIFO_FULL_THRHD_S) |
((0x02 & UART_RX_TOUT_THRHD) << UART_RX_TOUT_THRHD_S) |
UART_RX_TOUT_EN);
WRITE_PERI_REG(UART_INT_CLR(0), 0xffff);
SET_PERI_REG_MASK(UART_INT_ENA(0), UART_RXFIFO_FULL_INT_ENA | UART_RXFIFO_TOUT_INT_ENA);
ETS_UART_INTR_ENABLE();
}
void ATTR_GDBINIT gdbstub_set_uart_isr_callback(void (*func)(void*, uint8_t), void* arg) {
ETS_UART_INTR_DISABLE();
uart_isr_callback = func;
uart_isr_arg = arg;
ETS_UART_INTR_ENABLE();
}
#endif
#endif
//gdbstub initialization routine.
void gdbstub_init() {
#if GDBSTUB_REDIRECT_CONSOLE_OUTPUT
os_install_putc1(gdbstub_semihost_putchar1);
#endif
#if GDBSTUB_CTRLC_BREAK
install_uart_hdlr();
#else
configure_uart();
#endif
#if GDBSTUB_BREAK_ON_EXCEPTION
install_exceptions();
#endif
gdbstub_init_debug_entry();
#if GDBSTUB_BREAK_ON_INIT
gdbstub_do_break();
#endif
}
bool ATTR_GDBEXTERNFN gdb_present() {
return true;
}
void ATTR_GDBFN gdb_do_break() { gdbstub_do_break(); }
void gdb_init() __attribute__((alias("gdbstub_init")));