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.
 
 
 
 

1153 lines
28 KiB

/**
* @file gdbstubs.cpp
* @author Fernando Trias
* @brief Implement GDB stub using TeensyDebug interface
* @version 0.1
* @date 2020-06-09
*
* @copyright Copyright (c) 2020 Fernando Trias
*
*/
/*
* Notes on 'p':
*
* Calling 'p' with a function as in `p func(1,2,3)` doesn't work without
* this hack. The reason it fails is that GDB 7 performs the
* call by:
*
* 1. Setting a breakpoint at 0x60001000 (the ResetHandler)
* 2. Setting LR to 0x60001000
* 3. Setting PC to the funtion we want to call
* 4. Continuing, which causes function to execute
* 5. Upon finishing, the function returns to LR
* 6. The breakpoint is hit and GDB takes over
* 7. GDB unrolls the stack, etc. and gets result
*
* This doesn't work because 0x60001000 is in Flash. I first tried to do a soft
* remap of 0x60001000 to a point in RAM, but that doesn't work. It fails
* with a "Assertion `get_frame_type (frame) == DUMMY_FRAME' failed."
* I think the reason is because GDB changes SP by subtracting 4 or 8 before
* calling the function. Unfortunately, this library doesn't support
* GDB changing SP. So I keep track of a "fakesp" whenever SP is changed
* by GDB and just return that number instead of the real SP. See
* process_G and process_P.
*
* Newer versions of GDB choose a different breakpoint location. Instead
* of setting the breakpoint on the ResetHandler, they will set it on
* the stack. If that's the case, then this hack will stop working
* sinces it relies on the address of 0x60001000.
*
*/
#include <Arduino.h>
#define CPU_RESTART_ADDR (uint32_t *)0xE000ED0C
#define CPU_RESTART_VAL 0x5FA0004
#define CPU_RESTART (*CPU_RESTART_ADDR = CPU_RESTART_VAL);
#define GDB_DEBUG_INTERNAL
#include "TeensyDebug.h"
// #define GDB_DEBUG_COMMANDS
#define GDB_POLL_INTERVAL_MICROSEC 500
#define MAP_DUMMY_BREAKPOINT 0x60001000
/**
* Code to communicate with GDB. Use standard nomenclature.
* devInit() is not standard. It must be called at initialization.
*
*/
Stream *dev = NULL;
/**
* @brief Get the next character from the serial
*
* @return int Character or -1 if error
*/
int getDebugChar() {
unsigned int timeout = millis() + 1000;
while(dev->available() <= 0) {
delay(1);
if (millis() > timeout) {
// Serial.println("{timeout}");
return -1;
}
}
char c = dev->read();
// Serial.print("{");Serial.print(c);Serial.print("}");
return c;
}
/**
* @brief Send a character to the serial
*
* @param c Character to send (one 8-bit byte)
*/
void putDebugChar(int c) {
// Serial.print("[");Serial.print((char)c);Serial.print("]");
dev->write(c);
}
/**
* @brief Return 1 or more if there is data to read
*
* @return int Number of characters to read
*/
int hasDebugChar() {
// Serial.println("has?");
return dev->available();
}
/**
* @brief Initialize serial
*
*/
void devInit(Stream *device = NULL) {
if (device) {
dev = device;
}
else if (dev == NULL) {
dev = &Serial;
Serial.begin(9600);
}
}
// Signal codes for ARM faults; corresponds to debug_id
const char *signal_text[] = {
"S05", "S02", "S06", "S0B", "S07", "S07", "S04"
};
/**
* @brief Calculate checksum for message
*
* @param c Packet
* @return int Checksum
*/
int calcChecksum(const char *c) {
uint8_t sum = 0;
while(*c) {
sum += *c++;
}
return sum;
}
// constants for hex conversions
char int2hex[] = { '0','1','2','3','4','5','6','7','8','9','a','b','c','d','e','f' };
/**
* @brief Convert a hex char to a number
*
* @param ch Hex char '0', '1', etc.
* @return int Number 0-15
*/
int hex(unsigned char ch) {
if (ch >= 'a' && ch <= 'f')
return ch - 'a' + 10;
if (ch >= '0' && ch <= '9')
return ch - '0';
if (ch >= 'A' && ch <= 'F')
return ch - 'A' + 10;
return -1;
}
/**
* @brief Take a memory span and format it in hexadecimal
*
* @param buff Output in ascii hexadecimal
* @param addr Memory address to convert
* @param sz Number of bytes to convert
* @return char* The address of the last character (a \0) in buff
*/
char *mem2hex(char *buff, const void *addr, int sz = -1) {
if (sz < 0) sz = strlen((char*)addr);
for (int i = 0; i < sz; i++) {
uint8_t b = ((uint8_t*)addr)[i];
*buff++ = int2hex[b >> 4];
*buff++ = int2hex[b & 0x0F];
}
*buff = 0;
return buff;
}
/**
* @brief Convert hex string to actual string
*
* @param buff Store string here
* @param hexstr Hexadeciamal representation
* @return char* Pointer to \0 at end of buff
*/
char *hex2str(char *buff, const char *hexstr) {
while (*hexstr) {
int c_high = hex(*hexstr++);
int c_low = hex(*hexstr++);
*buff++ = (c_high << 4) + c_low;
}
*buff = 0;
return buff;
}
/**
* While we find nice hex chars, build an int.
* Return number of chars processed.
*/
/**
* @brief Convert ascii hex into an integer. Used when parsing commands.
*
* @param ptr Pointer to (char*) with text. Updated as text is parsed.
* @param intValue Pointer to (int) that holds value parsed
* @return int Number of characters parsed
*/
static int hexToInt(const char **ptr, int *intValue)
{
int numChars = 0;
int hexValue;
*intValue = 0;
while (**ptr) {
hexValue = hex(**ptr);
if (hexValue < 0)
break;
*intValue = (*intValue << 4) | hexValue;
numChars++;
(*ptr)++;
}
return (numChars);
}
static int hex32ToInt(const char **ptr)
{
const char *p = *ptr;
uint32_t intValue;
uint8_t *i = (uint8_t*)&intValue;
i[0] = (hex(p[0]) << 4) + hex(p[1]);
i[1] = (hex(p[2]) << 4) + hex(p[3]);
i[2] = (hex(p[4]) << 4) + hex(p[5]);
i[3] = (hex(p[6]) << 4) + hex(p[7]);
// Serial.print("parse ");Serial.println(intValue);
*ptr += 8;
return intValue;
}
static int strToInt(const char *str) {
if (str[0] == '0' && str[1] == 'x') {
int ret;
str += 2;
hexToInt(&str, &ret);
return ret;
}
else {
return atoi(str);
}
}
/**
* @brief Send result text to GDB (formatting and calculating checksum)
*
* @param result String of message to send
*/
void sendResult(const char *result) {
#ifdef GDB_DEBUG_COMMANDS
Serial.print("target reply:");Serial.println(result);
#endif
int checksum = calcChecksum(result);
const char *presult = result;
putDebugChar('$');
while (*presult) {
putDebugChar(*presult++);
}
putDebugChar('#');
putDebugChar(int2hex[checksum >> 4]);
putDebugChar(int2hex[checksum & 0x0F]);
// Serial.println(result);
}
// global flag to enabling or suspending debug system
volatile int debug_active = 1;
// global flag indicating debug should is simulating a "halt"; set to 0 to continue
volatile int halt_state = 0;
// global flag indicating Ctrl-C, causing system to "break" as soon as possible
volatile int cause_break = 0;
// main routine for processing GDB commands and states
void processGDB();
// from debug class indicating a fault
extern int debug_id;
// from debug indicting we are "stepping" instructions
extern int debugstep;
extern int debugenabled;
// for messages that are sent seperately (like 'O', print)
char send_message[256];
/**
* @brief Output to the GDB console using 'O' command
*
* @param msg Message to display
* @param len Number of characters
* @return size_t Number of characters sent
*/
size_t gdb_out_write(const uint8_t *msg, size_t len) {
if (send_message[0]) {
int lx = strlen(send_message);
char *p = send_message + lx;
mem2hex(p, (const char *)msg, len);
}
else {
send_message[0] = 'O';
mem2hex(send_message+1, (const char *)msg, len);
}
return len;
}
/**
* @brief Output string to GDB console using 'O'
*
* @param msg String to print
* @return size_t Number of characters sent
*/
size_t gdb_out_print(const char *msg) {
return gdb_out_write((const uint8_t *)msg, strlen(msg));
}
int gdb_wait_for_flag(volatile int *flag, int timeout) {
unsigned int endtime = millis()+1000;
while(*flag) {
if (timeout && millis() > endtime) {
return -1;
}
delay(1);
yield();
}
return 0;
}
int file_io_result;
int file_io_errno;
volatile int file_io_pending = 0;
int gdb_file_io(const char *cmd) {
// Serial.println(cmd);
file_io_pending = 1;
sendResult(cmd);
gdb_wait_for_flag(&file_io_pending, 1000);
// Serial.println(file_io_result);
return file_io_result;
}
/**
* @brief Routing for processing breakpoints
*
*/
#pragma GCC push_options
#pragma GCC optimize ("O0")
void process_onbreak() {
// send the signal
halt_state = 1;
sendResult(signal_text[debug_id]);
// go into halt state and stay until flag is cleared
gdb_wait_for_flag(&halt_state, 0);
debug_id = 0;
}
uint32_t fakesp;
__attribute__((noinline, naked))
void fake_breakpoint() {
// asm volatile("ldr r0, =fakesp");
// asm volatile("str sp, [r0]");
asm volatile("svc 0x10");
}
#pragma GCC pop_options
/**
* @brief Add 32-bit ascii hexadecimal to string buffer
*
* @param p Buffer to hold ascii hex
* @param n Number to encode
* @return char* Pointer last item (a \0) so you can continue appending
*/
char *append32(char *p, uint32_t n) {
uint8_t *x = (uint8_t *) &n;
for(int i=0; i<4; i++) {
int c = x[i];
*p++ = int2hex[c >> 4];
*p++ = int2hex[c & 0x0F];
}
return p;
}
// extern void print_registers();
/**
* @brief Process 'g' command to return registers
*
* @param cmd Original command
* @param result String with encoded text to return
* @return int 0 = success
*/
int process_g(const char *cmd, char *result) {
// print_registers();
// See Notes above for explanation
uint32_t pc = debug.getRegister("pc");
uint32_t sp = debug.getRegister("sp");
if ((pc|1) == (uint32_t)&fake_breakpoint) {
pc = MAP_DUMMY_BREAKPOINT;
// Serial.print("sp fake:");Serial.println(fakesp, HEX);
// Serial.print("sp:");Serial.println(sp, HEX);
// delay(1000);
sp = fakesp;
// Serial.print("Fake breakpoint sp=");Serial.println(sp, HEX);
}
result = append32(result, debug.getRegister("r0"));
result = append32(result, debug.getRegister("r1"));
result = append32(result, debug.getRegister("r2"));
result = append32(result, debug.getRegister("r3"));
result = append32(result, debug.getRegister("r4"));
result = append32(result, debug.getRegister("r5"));
result = append32(result, debug.getRegister("r6"));
result = append32(result, debug.getRegister("r7"));
result = append32(result, debug.getRegister("r8"));
result = append32(result, debug.getRegister("r9"));
result = append32(result, debug.getRegister("r10"));
result = append32(result, debug.getRegister("r11"));
result = append32(result, debug.getRegister("r12"));
result = append32(result, sp);
result = append32(result, debug.getRegister("lr"));
result = append32(result, pc);
result = append32(result, debug.getRegister("cpsr"));
*result = 0;
return 0;
}
/**
* @brief Process 'G' to write registers. Not supported.
*
* @param cmd
* @param result
* @return int
*/
int process_G(const char *cmd, char *result) {
// Not fully supported; enable when restore*() works
// strcpy(result, "E01");
// return 0;
cmd++;
debug.setRegister("r0", hex32ToInt(&cmd));
debug.setRegister("r1", hex32ToInt(&cmd));
debug.setRegister("r2", hex32ToInt(&cmd));
debug.setRegister("r3", hex32ToInt(&cmd));
debug.setRegister("r4", hex32ToInt(&cmd));
debug.setRegister("r5", hex32ToInt(&cmd));
debug.setRegister("r6", hex32ToInt(&cmd));
debug.setRegister("r7", hex32ToInt(&cmd));
debug.setRegister("r8", hex32ToInt(&cmd));
debug.setRegister("r9", hex32ToInt(&cmd));
debug.setRegister("r10", hex32ToInt(&cmd));
debug.setRegister("r11", hex32ToInt(&cmd));
debug.setRegister("r12", hex32ToInt(&cmd));
debug.setRegister("sp", hex32ToInt(&cmd));
debug.setRegister("lr", hex32ToInt(&cmd));
debug.setRegister("pc", hex32ToInt(&cmd));
debug.setRegister("cspr", hex32ToInt(&cmd));
strcpy(result, "OK");
return 0;
}
int process_P(const char *cmd, char *result) {
// Not fully supported; enable when restore*() works
// strcpy(result, "E01");
// return 0;
cmd++;
int reg = hex(*cmd++);
cmd++; // skip '='
uint32_t val = hex32ToInt(&cmd);
// Serial.print("Reg ");Serial.print(reg);Serial.print("=");Serial.println(val, HEX);
switch(reg) {
case 0: debug.setRegister("r0", val); break;
case 1: debug.setRegister("r1", val); break;
case 2: debug.setRegister("r2", val); break;
case 3: debug.setRegister("r3", val); break;
case 4: debug.setRegister("r4", val); break;
case 5: debug.setRegister("r5", val); break;
case 6: debug.setRegister("r6", val); break;
case 7: debug.setRegister("r7", val); break;
case 8: debug.setRegister("r8", val); break;
case 9: debug.setRegister("r9", val); break;
case 10: debug.setRegister("r10", val); break;
case 11: debug.setRegister("r11", val); break;
case 12: debug.setRegister("r12", val); break;
case 13:
if (debug.getRegister("lr") == (uint32_t)&fake_breakpoint) {
fakesp = val; // gdb changes sp, and we need to return it later
}
debug.setRegister("sp", val); break;
case 14:
if (val == (MAP_DUMMY_BREAKPOINT|1)) { // special breakpoint
debug.setRegister("lr", (uint32_t)&fake_breakpoint);
fakesp = debug.getRegister("sp"); // just in case not set later
}
else {
debug.setRegister("lr", val);
}
break;
case 15: debug.setRegister("pc", val); break;
case 16: debug.setRegister("cspr", val); break;
// case 13: strcpy(result, "E01"); return 0;
// case 14: strcpy(result, "E01"); return 0;
// case 15: strcpy(result, "E01"); return 0;
// case 16: strcpy(result, "E01"); return 0;
}
strcpy(result, "OK");
return 0;
}
/**
* @brief Test is requested address is valid. GDB sometimes sends
* invalid memory requests that will cause a fault. Should test more
* accurately, but this takes care of most problems.
*
* @param addr Address to check
* @return int 1 = valid; 0 = invalid
*/
int isValidAddress(uint32_t addr) {
if (addr <= 0x20) {
return 0;
}
else if (addr >= 0xF0000000) {
return 0;
}
return 1;
}
/**
* @brief Process 'm' to read memory
*
* @param cmd Original command
* @param result String to return
* @return int 0 for success
*/
int process_m(const char *cmd, char *result) {
int addr, sz=4;
cmd++; // skip cmd
hexToInt(&cmd, &addr);
if (*cmd == ',') {
cmd++; // skip comma
hexToInt(&cmd, &sz);
}
// Serial.print("read at ");Serial.println(addr, HEX);
if (isValidAddress(addr+sz-1) == 0) {
strcpy(result, "E01");
return 0;
}
// if (addr == MAP_DUMMY_BREAKPOINT) {
// if (sz == 2) {
// strcpy(result, "fbbe");
// return 0;
// }
// else if (sz == 4) {
// strcpy(result, "fbbe0000");
// return 0;
// }
// }
uint8_t *m = (uint8_t *)addr;
for (int i=0; i<sz; i++) {
uint8_t d = m[i];
*(result++) = int2hex[d >> 4];
*(result++) = int2hex[d & 0x0F];
}
*result = 0;
return 0;
}
/**
* @brief Process 'M' to write memory
*
* @param cmd Original command
* @param result Results 'OK'
* @return int
*/
int process_M(const char *cmd, char *result) {
int addr, sz;
cmd++; // skip command
hexToInt(&cmd, &addr);
cmd++; // skip comma
hexToInt(&cmd, &sz);
cmd++;
uint8_t *memory = (uint8_t*)addr;
for(int i=0; i<sz; i++) {
int c_high = hex(*cmd++);
int c_low = hex(*cmd++);
memory[i] = (c_high << 4) + c_low;
}
strcpy(result, "OK");
return 0;
}
/**
* @brief Process 'c' continue
*
* @param cmd Original command
* @param result String result
* @return int 1 to signal caller
*/
int process_c(const char *cmd, char *result) {
halt_state = 0; // not halted
debugstep = 0; // not stepping
strcpy(result, "");
return 1;
}
/**
* @brief Process 's' step command to step a single instruction.
* Arguments to start in a different address are not supported.
*
* @param cmd Original command
* @param result String with ENN or blank
* @return int 1 to continue; 0 if errors
*/
int process_s(const char *cmd, char *result) {
cmd++;
if (*cmd) {
// int addr;
// hexToInt(&cmd, &addr);
// we don't support starting in a different address
strcpy(result, "E01"); // SNN
return 0;
}
debugstep = 1; // just step
halt_state = 0;
strcpy(result, ""); // return comes from the actual break
return 1;
}
/**
* @brief Process '?' query for last stop reason. TODO.
*
* @param cmd
* @param result
* @return int
*/
int process_question(const char *cmd, char *result) {
// sprintf(result, "S0%d", debug_id);
strcpy(result, signal_text[debug_id]);
return 0;
}
/**
* @brief 'B' deprecated and not supported
*
* @param cmd
* @param result
* @return int
*/
int process_B(const char *cmd, char *result) {
strcpy(result, "E10");
return 0;
}
/**
* @brief Process 'z' clear breakpoint at address
*
* @param cmd Original command
* @param result Result is ENN or OK
* @return int 0 if success
*/
int process_z(const char *cmd, char *result) {
int btype, addr;
cmd++;
hexToInt(&cmd, &btype);
cmd++;
hexToInt(&cmd, &addr);
// cmd++;
// hexToInt(&cmd, &sz);
if (addr == 0) {
strcpy(result, "E01");
}
else if (addr == MAP_DUMMY_BREAKPOINT) { // hard-coded breakpoint
strcpy(result, "OK");
}
else if (debug.clearBreakpoint((void*)addr)) {
strcpy(result, "E01");
strcpy(result, "OK");
}
else {
strcpy(result, "OK");
}
return 0;
}
/**
* @brief Process 'Z' set breakpoint at address
*
* @param cmd Original command
* @param result Result is ENN or OK
* @return int
*/
int process_Z(const char *cmd, char *result) {
int btype, addr;
cmd++;
hexToInt(&cmd, &btype); // don't care. We figure out what's best.
cmd++;
hexToInt(&cmd, &addr);
// optional size not used because we only support Thumb
// cmd++;
// hexToInt(&cmd, &sz);
if (addr == 0) {
strcpy(result, "E01");
}
else if (addr == MAP_DUMMY_BREAKPOINT) { // hard-coded breakpoint
strcpy(result, "OK");
}
else if (debug.setBreakpoint((void*)addr)) {
#ifdef GDB_DEBUG_COMMANDS
Serial.print("Breakpoint failed on ");Serial.println(addr);
#endif
strcpy(result, "E01");
}
else {
strcpy(result, "OK");
}
return 0;
}
char *getNextWord(char **text) {
char *orig = *text;
char *p = orig;
while(*p) {
if (*p == ' ' || *p == '(' || *p == ')' || *p == ',') {
*p++ = 0;
while(*p == ' ') p++;
*text = p;
return orig;
}
p++;
}
*text = 0;
return orig;
}
char *getNextToken(char **text, char token) {
char *orig = *text;
char *p = orig;
while(*p) {
if (*p == token) {
*p++ = 0;
*text = 0;
return orig;
}
p++;
}
*text = 0;
return orig;
}
int (*call0)();
int (*call1)(int p1);
int (*call2)(int p1, int p2);
int (*call3)(int p1, int p2, int p3);
int process_monitor(char *cmd, char *result) {
char *place = cmd;
char *word;
word = getNextWord(&place);
// Serial.print("command :");Serial.print(word);Serial.println(":");
if (stricmp(word, "digitalWrite") == 0) {
char *pin = getNextWord(&place);
char *state = getNextWord(&place);
int ipin = strToInt(pin);
int istate = strToInt(state);
if (stricmp(state, "high")==0) istate = 1;
pinMode(ipin, OUTPUT);
digitalWrite(ipin, istate);
strcpy(result, "OK");
return 0;
}
else if (stricmp(word, "digitalRead") == 0) {
char *pin = getNextWord(&place);
int ipin = strToInt(pin);
pinMode(ipin, INPUT);
int v = digitalRead(ipin);
char x[6];
sprintf(x, "%d\n", v);
mem2hex(result, (const char *)x, strlen(x));
return 0;
}
else if (stricmp(word, "analogWrite") == 0) {
char *pin = getNextWord(&place);
char *state = getNextWord(&place);
int ipin = strToInt(pin);
int istate = strToInt(state);
pinMode(ipin, OUTPUT);
analogWrite(ipin, istate);
strcpy(result, "OK");
return 0;
}
else if (stricmp(word, "analogRead") == 0) {
char *pin = getNextWord(&place);
int ipin = strToInt(pin);
pinMode(ipin, INPUT);
int v = analogRead(ipin);
char x[6];
sprintf(x, "%d\n", v);
mem2hex(result, (const char *)x, strlen(x));
return 0;
}
else if (stricmp(word, "call") == 0) {
int args = 0, p[4], ret;
char *arg = getNextWord(&place);
uint32_t addr = strToInt(arg);
// Serial.print("addr ");Serial.println(addr);
if (addr == 0) {
mem2hex(result, "E Invalid address\n");
return 0;
}
addr |= 1; // set the exchange bit
for(int i=0; i<4; i++) {
if (*place == 0) break;
arg = getNextWord(&place);
p[args] = strToInt(arg);
// Serial.print("parameter ");Serial.println(p[args]);
args++;
}
// Serial.print("arguments ");Serial.println(args);
switch(args) {
case 0:
call0 = (int (*)())addr;
ret = call0();
break;
case 1:
call1 = (int (*)(int))addr;
ret = call1(p[0]);
break;
case 2:
call2 = (int (*)(int,int))addr;
ret = call2(p[0], p[1]);
break;
case 3:
call3 = (int (*)(int,int,int))addr;
ret = call3(p[0], p[1], p[2]);
break;
default:
mem2hex(result, "E Too many parameters (max=3)\n");
return 0;
}
char x[40];
sprintf(x, "%d\n", ret);
mem2hex(result, (const char *)x, strlen(x));
return 0;
}
else if (stricmp(word, "restart") == 0) {
CPU_RESTART;
strcpy(result, "");
return 0;
}
strcpy(result, "");
return 0;
}
/**
* @brief Process 'q' query command. For now report back PacketSize.
* Handle 'monitor' commands.
*
* @param cmd Original command
* @param result Results or ""
* @return int 0
*/
int process_q(const char *cmd, char *result) {
if (strncmp(cmd, "qSupported", 10) == 0) {
strcpy(result, "PacketSize=1024");
return 0;
}
else if (strncmp(cmd, "qRcmd", 5) == 0) {
char x[256];
hex2str(x, cmd+6);
return process_monitor(x, result);
}
strcpy(result, "");
return 0;
}
/**
* @brief Functions are not supported, but would be very useful in the future
*
* @param cmd Function specification
* @param result
* @return int
*/
int process_F(const char *cmd, char *result) {
// Serial.println(cmd);
cmd++;
// error
if (*cmd == '-') {
cmd++;
hexToInt(&cmd, &file_io_result); // don't care. We figure out what's best.
file_io_result = -file_io_result;
}
else {
hexToInt(&cmd, &file_io_result); // don't care. We figure out what's best.
}
if (*cmd == ',') {
cmd++;
hexToInt(&cmd, &file_io_errno);
}
else {
file_io_errno = 0;
}
file_io_pending = 0;
strcpy(result, "OK");
return 0;
}
/**
* @brief Process 'R' restart
*
* @param cmd
* @param result
* @return int
*/
int process_R(const char *cmd, char *result) {
// _reboot_Teensyduino_();
// _restart_Teensyduino_();
CPU_RESTART;
strcpy(result, "OK");
return 0;
}
int process_k(const char *cmd, char *result) {
// _reboot_Teensyduino_();
// _restart_Teensyduino_();
CPU_RESTART;
strcpy(result, "OK");
return 0;
}
int process_v(char *cmd, char *result) {
char *work = getNextToken(&cmd, ';');
// Serial.print("v:");Serial.println(work);
if (strcmp(work, "vKill") == 0) {
strcpy(result, "OK");
}
else {
result[0] = 0;
}
return 0;
}
int gdb_active_flag = 0;
/**
* @brief Process a command by calling appropriate delegation function
*
* @param cmd Command and parameters
* @param result Result to send back
* @return int 0
*/
int processCommand(char *cmd, char *result) {
gdb_active_flag = 1;
switch(cmd[0]) {
case 'g': return process_g(cmd, result);
case 'G': return process_G(cmd, result);
case 'P': return process_P(cmd, result);
case 'm': return process_m(cmd, result);
case 'M': return process_M(cmd, result);
case 'c': return process_c(cmd, result);
case 's': return process_s(cmd, result);
case 'F': return process_F(cmd, result);
case 'R': return process_R(cmd, result);
case 'r': return process_R(cmd, result);
case 'k': return process_k(cmd, result);
case 'v': return process_v(cmd, result);
case '?': return process_question(cmd, result);
// case 'B': return process_B(cmd, result);
case 'z': return process_z(cmd, result);
case 'Z': return process_Z(cmd, result);
case 'q': return process_q(cmd, result);
}
// if it's not listed above, it's not supported
result[0] = 0;
return 0;
}
/**
* @brief Read input, if available and process any commands
*
*/
void processGDBinput() {
char result[1024];
// no data? do nothing
if (! hasDebugChar()) return;
int c = getDebugChar();
if (c < 0) {
// Serial.println("Error reading");
return;
}
// GDB ack'd our last command; don't do anything yet with this
if (c == '+') {
// Serial.println("ACK");
return;
}
// GDB had a problem with last command; should resend. TODO
if (c == '-') {
// Serial.println("NAK");
if (file_io_pending) {
file_io_result = -1;
file_io_pending = 0;
}
return;
}
// User hit Ctrl-C or other break
if (c == 0x03) {
// Serial.println("Ctrl-C");
cause_break = 1; // cause break later so we don't break internals
return;
}
// If we don't have a valid start command, then something went wrong so
// we just ignore it.
if (c != '$') {
// Serial.print("Bad char: ");
// Serial.println((char)c, HEX);
return; // wait for start char
}
// buffer to read command; matches our PacketSize
const int cmd_max = 1024;
char cmd[cmd_max]; // buffer
char *pcmd = cmd; // pointer to last char
// int sum;
int checksum = 0; // to store checksum
// 2-second timeout
while(1) {
// read next char or timeout
c = getDebugChar();
if (c == -1) {
// Serial.println("read error");
putDebugChar('-');
return;
}
if (c == '#') break; // checksum follows
*pcmd++ = c;
if (pcmd >= cmd+cmd_max) { // overrun
pcmd = cmd;
}
}
*pcmd = 0;
#ifdef GDB_DEBUG_COMMANDS
Serial.print("gdb command:");Serial.println(cmd);
#endif
c = getDebugChar();
checksum = hex(c) << 4;
c = getDebugChar();
checksum += hex(c);
if (checksum != calcChecksum(cmd)) {
// Serial.println("bad checksum");
// Serial.println(calcChecksum(cmd), HEX);
putDebugChar('-');
return;
}
// all good, so ACK
putDebugChar('+');
int r = processCommand(cmd, result);
// r == 1 means there are no results for now. A step or continue
// don't return immediate results. Results are returned upon
// hitting the break or successful step
if (r==1) return;
// toss results back to GDB
sendResult(result);
}
/**
* @brief Process GDB messages, including Break
*
*/
void processGDB() {
#if 0
static unsigned int nexttick = millis() + 1000;
if (millis() > nexttick) {
// Serial.println("tick");
gdb_out_print("ping");
nexttick += 1000;
}
#endif
if (! debug_active) return;
while (hasDebugChar()) {
processGDBinput();
}
if (send_message[0]) {
// Serial.print("send ");Serial.println(send_message);
sendResult(send_message);
send_message[0] = 0;
}
if (cause_break) {
// Serial.println("BREAK!!");
cause_break = 0;
debug_id = 1;
// NVIC_SET_PENDING(IRQ_SOFTWARE);
asm volatile("svc 0x12");
}
}
// void setup_main();
// Check for GDB commands periodically
IntervalTimer gdb_timer;
/**
* @brief Initialize debug system
*
* @param device Optional device that inherits from Stream; default is Serial
*/
void gdb_init(Stream *device) {
send_message[0] = 0;
devInit(device);
gdb_timer.begin(processGDB, GDB_POLL_INTERVAL_MICROSEC);
debug.setCallback(process_onbreak);
debug_active = 1;
#ifdef GDB_HALT_ON_STARTUP
#ifdef REMAP_SETUP
debug.setBreakpoint(setup_main, 1);
#else
debug.setBreakpoint(setup, 1);
#endif
#endif
}