Fernando Trias
4 years ago
commit
08765b363b
9 changed files with 1498 additions and 0 deletions
@ -0,0 +1 @@ |
|||
.vscode |
@ -0,0 +1,55 @@ |
|||
GDB stubs on Teensy |
|||
=================== |
|||
|
|||
I gave this some thought over the weekend and I think live debugging is possible even if C_DEBUGEN cannot be changed. The idea is to use a GDB stub to remotely debug the Teensy and instead of using BKPT, using SVC. This proposal will enable limited breakpoints and live examination of data. |
|||
|
|||
First, I'll explain GDB stubs a little. Then I'll discuss how to use it. I'm writing this here to see if this has already been done and to get feedback. |
|||
|
|||
GDB stubs |
|||
--------- |
|||
|
|||
GDB stubs provides a simple interface between GDB and a remote target via a serial interface, such as a socket, pipe, UART, USB, etc. It works like this: |
|||
|
|||
``` |
|||
[PC running GDB] <--(serial)--> [Teensy GDB stub] |
|||
``` |
|||
|
|||
Teensyduino comes with a GDB executable that supports ARM. The ELF file contains debugging info if compiled with the "Debug" option that Teensyduino provides. Thus, GDB loads up the ELF and knows all the symbols and sees the source code. GDB queries Teensy for information like memory data and registers as needed. It also sends Teensy breaking and execution commands. |
|||
|
|||
Process |
|||
--------- |
|||
|
|||
This is how it would work with Teensy: |
|||
|
|||
1. By using interrupts or the EventResponder, the Teensy listens to GDB commands from a serial device. |
|||
|
|||
2. When it gets commands like memory queries, memory sets and things that don't require halting, it responds with the data requested. |
|||
|
|||
3. When it receives a halt command, Teensy will just go into a loop querying for commands and responding. It won't return to it's caller until GDB tells it to do so. Thus, execution of the main thread will stop but interrupts will continue. Because interrupts continue, on the plus side, the Teensy won't die and USB and other features will stay active. On the other hand, sometimes you just the want the system to halt. Perhaps there could be an option to halt all interrupts as well or change the priority. Keeping interrupts going is probably easier for beginners and models what desktop apps do (when an apps stops, the OS keeps going). |
|||
|
|||
So far, this will enable us to do simple live debugging. You can't set breakpoints yet, but you'll be able to stop execution and examine the state. |
|||
|
|||
4. Provide a special "breakpoint" instruction that you can insert into your code. Each breakpoint will have a flag in RAM to determine if it is enabled or not. If enabled, when execution reaches it, it will execute an interrupt (software or SVC). If disabled, execution just keeps going. Breakpoints are enabled/disabled based on commands received from GDB. |
|||
|
|||
So far, this provides fixed hardcoded breakpoints. You can stop the code at any place and examine/change variables, stack, etc. |
|||
|
|||
5. If a function is placed in RAM with the FASTRAM option, you can dynamically insert/remove SVC calls in the code, in the same way that standard debuggers work. Thus, if you want arbitrary breakpoints in certain code, just specify FASTRAM. Again, breakpoints like this can be set and enabled/disabled by GDB. |
|||
|
|||
In theory, most code can be placed in RAM by changing the gcc compile options, or by linking with an alternative LD file. Perhaps Teensyduino can provide a feature to do this in the future? The Teensy 4 has plenty of RAM to store most programs. You can store some parts, like core code, in flash and all user code in RAM. |
|||
|
|||
6. On the Teensy 3.1/3.2, we could as well use the Flash Patch Block to set and remove SVC calls using patching. Thus, you can dynamically set breakpoints in flash. Teensy 4 doesn't support this. |
|||
|
|||
Other considerations |
|||
--------- |
|||
|
|||
The serial connection can be anything. To start it's probably best to use a UART but in theory it could be USB Serial, CAN, network, Raw connection, etc. |
|||
|
|||
Right now, running GDB will have to be done manually. But in the future, GDB could be piped to Arduino's serial monitor. Both GDB's output and Teensy's serial output could be sent to the display. GDB can receive commands from the Send window. If, in addition to this, we use USB for the serial connection to GDB, then Teensy will have onboard live debugging available with no special setup or hardware required. |
|||
|
|||
``` |
|||
[Arduino] [ s1] <-- [Teensy] |
|||
[Serial ] <-> [gdb proxy ] |
|||
[Monitor] [ s2] <-> [GDB] |
|||
``` |
|||
|
|||
If C_DEBUGEN can be disabled, then the Teensy 4 can also have dynamic breakpoints in flash and everything else becomes somewhat simpler to implement. |
@ -0,0 +1,714 @@ |
|||
/**
|
|||
* @brief TeensyDebug library. Used by gdb stub and also stand-alone. |
|||
* |
|||
*/ |
|||
|
|||
/** References
|
|||
|
|||
https://forum.pjrc.com/threads/26358-Software-Debugger-Stack
|
|||
https://forum.pjrc.com/threads/28058-Teensyduino-1-22-Features?highlight=C_DEBUGEN
|
|||
http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.ddi0344k/Cegjcacg.html
|
|||
https://web.eecs.umich.edu/~prabal/teaching/resources/eecs373/ARMv7-M_ARM.pdf
|
|||
https://os.mbed.com/users/va009039/code/lpcterm2/annotate/5e4107edcbdb/Target2.cpp/
|
|||
https://sourceforge.net/p/openocd/code/ci/master/tree/src/target/cortex_m.c#l303
|
|||
https://docs.google.com/viewer?a=v&pid=forums&srcid=MTQ3NDIxMjYyNTI1NDYxNDY0MzcBMTQyMjM2NDMzNTU1NzM4ODY4NjEBQzAtUlFoeFFJMGNKATAuMQEBdjI&authuser=0
|
|||
https://developer.apple.com/library/archive/documentation/DeveloperTools/gdb/gdb/gdb_33.html
|
|||
https://elixir.bootlin.com/linux/v3.3/source/arch/mn10300/kernel/gdb-stub.c
|
|||
https://ftp.gnu.org/old-gnu/Manuals/gdb/html_node/gdb_129.html#SEC134
|
|||
https://github.com/turboscrew/rpi_stub
|
|||
https://www.embecosm.com/appnotes/ean4/embecosm-howto-rsp-server-ean4-issue-2.html
|
|||
https://github.com/redox-os/binutils-gdb/blob/master/gdb/arm-tdep.c
|
|||
https://github.com/redox-os/binutils-gdb/blob/master/gdb/stubs/i386-stub.c
|
|||
*/ |
|||
|
|||
#include <Arduino.h> |
|||
|
|||
/**
|
|||
* @brief Memory maps missing from headers |
|||
* |
|||
*/ |
|||
|
|||
#define FP_CTRL (*(uint32_t*)0xE0002000) |
|||
#define FP_REMAP (*(uint32_t*)0xE0002004) |
|||
#define FP_COMP(n) (((uint32_t*)0xE0002008)[n]) |
|||
#define FP_COMP0 (*(uint32_t*)0xE0002008) |
|||
#define FP_COMP1 (*(uint32_t*)0xE000200C) |
|||
#define FP_COMP2 (*(uint32_t*)0xE0002010) |
|||
#define FP_COMP3 (*(uint32_t*)0xE0002014) |
|||
#define FP_COMP4 (*(uint32_t*)0xE0002018) |
|||
#define FP_COMP5 (*(uint32_t*)0xE000201C) |
|||
#define FP_COMP6 (*(uint32_t*)0xE0002020) |
|||
#define FP_COMP7 (*(uint32_t*)0xE0002024) |
|||
#define FP_COMP_MASK 0x1FFFFFFC |
|||
#define FP_REMAP_MASK 0x1FFFFFF0 |
|||
#define FP_REMAP_RMPSPT (1<<29) |
|||
|
|||
#define ARM_DHCSR (*(uint32_t*)0xE000EDF0) |
|||
#define ARM_DCRSR (*(uint32_t*)0xE000EDF4) |
|||
#define ARM_DCRDR (*(uint32_t*)0xE000EDF8) |
|||
//#define ARM_DEMCR (*(uint32_t*)0xE000EDFC) // defined in header
|
|||
|
|||
#define FP_LAR_UNLOCK_KEY 0xC5ACCE55 |
|||
#define FP_LAR (*(unsigned int*) 0xE0000FB0) |
|||
#define FP_LSR (*(unsigned int*) 0xE0000FB4) |
|||
|
|||
|
|||
#ifdef __MK20DX256__ |
|||
#define RAM_START ((void*)0x1FFF8000) |
|||
#define RAM_END ((void*)0x2FFFFFFF) |
|||
#endif |
|||
|
|||
#ifdef __IMXRT1062__ |
|||
#define RAM_START ((void*)0x00000000) |
|||
#define RAM_END ((void*)0x5FFFFFFF) |
|||
#endif |
|||
|
|||
/*
|
|||
* Breakpoint setup |
|||
*/ |
|||
|
|||
void *breakpoints[32]; |
|||
uint16_t *remap_table; |
|||
|
|||
const int sw_breakpoint_count = 32; |
|||
void *sw_breakpoint_addr[sw_breakpoint_count]; |
|||
uint16_t sw_breakpoint_code[sw_breakpoint_count]; |
|||
|
|||
int swdebug_clearBreakpoint(void *p) { |
|||
uint32_t addr = ((uint32_t)p) & 0x1FFFFFFE; |
|||
for(int i=0; i<sw_breakpoint_count; i++) { |
|||
if (sw_breakpoint_addr[i] == (void*)addr) { |
|||
sw_breakpoint_addr[i] = 0; |
|||
uint16_t *memory = (uint16_t*)addr; |
|||
*memory = sw_breakpoint_code[i]; |
|||
Serial.print("restore ");Serial.print(addr, HEX);Serial.print("=");Serial.println(*memory, HEX); |
|||
return 0; |
|||
} |
|||
} |
|||
return -1; |
|||
} |
|||
|
|||
int swdebug_setBreakpoint(void *p) { |
|||
uint32_t addr = ((uint32_t)p) & 0x1FFFFFFE; |
|||
for(int i=0; i<sw_breakpoint_count; i++) { |
|||
if (sw_breakpoint_addr[i] == 0) { |
|||
sw_breakpoint_addr[i] = (void*)addr; |
|||
uint16_t *memory = (uint16_t*)addr; |
|||
sw_breakpoint_code[i] = *memory; |
|||
Serial.print("overwrite ");Serial.print(addr, HEX);Serial.print(" from ");Serial.println(*memory, HEX); |
|||
*memory = 0xdf10; // SVC 10
|
|||
return 0; |
|||
} |
|||
} |
|||
return -1; |
|||
} |
|||
|
|||
int swdebug_isBreakpoint(void *p) { |
|||
uint32_t addr = ((uint32_t)p) & 0x1FFFFFFE; |
|||
for(int i=0; i<sw_breakpoint_count; i++) { |
|||
if (sw_breakpoint_addr[i] == (void*)addr) { |
|||
return 1; |
|||
} |
|||
} |
|||
return 0; |
|||
} |
|||
|
|||
#ifdef HAS_FP_MAP |
|||
|
|||
int hwdebug_clearBreakpoint(void *p, int n) { |
|||
FP_COMP(n) = 0; |
|||
return 0; |
|||
} |
|||
|
|||
int hwdebug_setBreakpoint(void *p, int n) { |
|||
if (p == 0) { |
|||
FP_COMP(n) = 0; |
|||
// Serial.print("break0 ");Serial.println(n);
|
|||
} |
|||
else { |
|||
uint32_t pc = ((uint32_t)p) & 0x1FFFFFFE; |
|||
if (pc & 0b10) { // must be aligned, so go to next instruction
|
|||
// store the first instruction
|
|||
pc -= 2; |
|||
remap_table[(n<<1) + 0] = ((uint16_t*)pc)[0]; |
|||
remap_table[(n<<1) + 1] = 0xdf10; // svc 10 instruction
|
|||
} |
|||
else { |
|||
// store the next instruction
|
|||
remap_table[(n<<1) + 0] = 0xdf10; // svc 10 instruction
|
|||
remap_table[(n<<1) + 1] = ((uint16_t*)pc)[1]; |
|||
} |
|||
|
|||
uint32_t addr = pc & 0x1FFFFFFC; |
|||
FP_COMP(n) = addr | 1; |
|||
breakpoints[n] = p; |
|||
// Serial.print("break ");Serial.print(n);Serial.print(" at ");Serial.print(pc, HEX);Serial.print("=");Serial.println(addr, HEX);
|
|||
} |
|||
return 0; |
|||
} |
|||
|
|||
void hwdebug_disableBreakpoint(int n) { |
|||
FP_COMP(n) &= 0xFFFFFFFE; |
|||
Serial.print("break ");Serial.print(n);Serial.println(" disable"); |
|||
} |
|||
|
|||
void hwdebug_enableBreakpoint(int n) { |
|||
FP_COMP(n) |= 1; |
|||
Serial.print("break ");Serial.print(n);Serial.println(" enable"); |
|||
} |
|||
|
|||
int hwdebug_getBreakpoint(void *p) { |
|||
for (int n=1; n<6; n++) { |
|||
if (breakpoints[n]== p) { |
|||
return n; |
|||
} |
|||
} |
|||
return -1; |
|||
} |
|||
|
|||
int hwdebug_isBreakpoint(void *p) { |
|||
for (int n=1; n<6; n++) { |
|||
if (breakpoints[n]== p) { |
|||
return 1; |
|||
} |
|||
} |
|||
return 0; |
|||
} |
|||
|
|||
#endif |
|||
|
|||
const int hc_breakpoint_count = 32; |
|||
void *hc_breakpoint_addr[hc_breakpoint_count]; |
|||
int hc_breakpoint_enabled[hc_breakpoint_count]; |
|||
int hc_breakpoint_trip = -1; |
|||
|
|||
int debug_isHardcoded(void *addr) { |
|||
// if (addr >= RAM_START && addr <= RAM_END) {
|
|||
// return 0;
|
|||
// }
|
|||
uint16_t *p = (uint16_t*)addr; |
|||
// SVC 0x11 is reserved for hardcoded breaks
|
|||
if (p[0] == 0xdf11) { |
|||
return 1; |
|||
} |
|||
return 0; |
|||
} |
|||
|
|||
int hcdebug_clearBreakpoint(int n) { |
|||
hc_breakpoint_enabled[n] = 0; |
|||
return 0; |
|||
} |
|||
|
|||
int hcdebug_setBreakpoint(int n) { |
|||
hc_breakpoint_enabled[n] = 1; |
|||
return 0; |
|||
} |
|||
|
|||
int hcdebug_isEnabled(int n) { |
|||
return hc_breakpoint_enabled[n]; |
|||
} |
|||
|
|||
int hcdebug_isBreakpoint(int n) { |
|||
return hc_breakpoint_enabled[n]; |
|||
} |
|||
|
|||
void hcdebug_tripBreakpoint(int n) { |
|||
hc_breakpoint_trip = n; |
|||
} |
|||
|
|||
void debug_initBreakpoints() { |
|||
for(int i=0; i<sw_breakpoint_count; i++) { |
|||
sw_breakpoint_addr[i] = 0; |
|||
} |
|||
for(int i=0; i<hc_breakpoint_count; i++) { |
|||
hc_breakpoint_enabled[i] = 0; |
|||
} |
|||
#ifdef HAS_FP_MAP |
|||
hwdebug_clearBreakpoint(0, 0); |
|||
hwdebug_clearBreakpoint(0, 1); |
|||
hwdebug_clearBreakpoint(0, 2); |
|||
hwdebug_clearBreakpoint(0, 3); |
|||
hwdebug_clearBreakpoint(0, 4); |
|||
hwdebug_clearBreakpoint(0, 5); |
|||
#endif |
|||
} |
|||
|
|||
int debug_clearBreakpoint(void *p, int n) { |
|||
if (p >= RAM_START && p <= RAM_END) { |
|||
return swdebug_clearBreakpoint(p); |
|||
} |
|||
else if (p < (void*)0xF) { |
|||
return hcdebug_clearBreakpoint((int)p); |
|||
} |
|||
else { |
|||
#ifdef HAS_FP_MAP |
|||
return hwdebug_clearBreakpoint(p, n); |
|||
#else |
|||
return -1; |
|||
#endif |
|||
} |
|||
} |
|||
|
|||
int debug_setBreakpoint(void *p, int n) { |
|||
if (p >= RAM_START && p <= RAM_END) { |
|||
return swdebug_setBreakpoint(p); |
|||
} |
|||
else if (p < (void*)0xF) { |
|||
return hcdebug_setBreakpoint((int)p); |
|||
} |
|||
else { |
|||
#ifdef HAS_FP_MAP |
|||
return hwdebug_setBreakpoint(p, n); |
|||
#else |
|||
return -1; |
|||
#endif |
|||
} |
|||
} |
|||
|
|||
int debug_isBreakpoint(void *p) { |
|||
if (p >= RAM_START && p <= RAM_END) { |
|||
return swdebug_isBreakpoint(p); |
|||
} |
|||
else if (p < (void*)0xF) { |
|||
return hcdebug_isBreakpoint((int)p); |
|||
} |
|||
else { |
|||
#ifdef HAS_FP_MAP |
|||
return hwdebug_isBreakpoint(p); |
|||
#else |
|||
return -1; |
|||
#endif |
|||
} |
|||
} |
|||
|
|||
/*
|
|||
* Breakpint handlers |
|||
*/ |
|||
|
|||
|
|||
void (*callback)() = NULL; |
|||
|
|||
// If debugactive=0, this means the actual breakpoint. If debugactive=1, this is the followon breakpoint.
|
|||
int debugactive = 0; |
|||
int debugreset = 0; |
|||
int debugcount = 0; |
|||
int debugenabled = 0; |
|||
int debugstep = 0; |
|||
|
|||
// During the initial breakpoint, the next address to break on
|
|||
uint32_t nextpc = 0; |
|||
|
|||
const char *hard_fault_debug_text[] = { |
|||
"debug", "nmi", "hard", "mem", "bus", "usage" |
|||
}; |
|||
uint32_t debug_id = 0; |
|||
|
|||
struct save_registers_struct { |
|||
uint32_t r0; |
|||
uint32_t r1; |
|||
uint32_t r2; |
|||
uint32_t r3; |
|||
uint32_t r12; |
|||
uint32_t lr; |
|||
uint32_t pc; |
|||
uint32_t xPSR; |
|||
|
|||
uint32_t r4; |
|||
uint32_t r5; |
|||
uint32_t r6; |
|||
uint32_t r7; |
|||
uint32_t r8; |
|||
uint32_t r9; |
|||
uint32_t r10; |
|||
uint32_t r11; |
|||
uint32_t sp; |
|||
} save_registers; |
|||
|
|||
// Structure of ISR stack
|
|||
struct stack_isr { |
|||
uint32_t r0; |
|||
uint32_t r1; |
|||
uint32_t r2; |
|||
uint32_t r3; |
|||
uint32_t r12; |
|||
uint32_t lr; |
|||
uint32_t pc; |
|||
uint32_t xPSR; |
|||
}; |
|||
struct stack_isr *stack; |
|||
|
|||
void print_registers() { |
|||
Serial.print("r0=");Serial.println(save_registers.r0); |
|||
Serial.print("r1=");Serial.println(save_registers.r1); |
|||
Serial.print("r2=");Serial.println(save_registers.r2); |
|||
Serial.print("r3=");Serial.println(save_registers.r3); |
|||
Serial.print("r12=");Serial.println(save_registers.r12); |
|||
Serial.print("lr=0x");Serial.println(save_registers.lr, HEX); |
|||
Serial.print("pc=0x");Serial.println(save_registers.pc, HEX); |
|||
Serial.print("r4=");Serial.println(save_registers.r4); |
|||
Serial.print("r5=");Serial.println(save_registers.r5); |
|||
Serial.print("r6=");Serial.println(save_registers.r6); |
|||
Serial.print("r7=");Serial.println(save_registers.r7); |
|||
Serial.print("r8=");Serial.println(save_registers.r8); |
|||
Serial.print("r9=");Serial.println(save_registers.r9); |
|||
Serial.print("r10=");Serial.println(save_registers.r10); |
|||
Serial.print("r11=");Serial.println(save_registers.r11); |
|||
Serial.print("sp=0x");Serial.println(save_registers.sp,HEX); |
|||
} |
|||
|
|||
void debug_action() { |
|||
Serial.println("****DEBUG"); |
|||
print_registers(); |
|||
Serial.println("****"); |
|||
} |
|||
|
|||
void debug_monitor() { |
|||
uint32_t breakaddr = save_registers.pc - 2; |
|||
|
|||
// is this the first breakpoint or are we in a sequence?
|
|||
if (debugactive == 0) { |
|||
// Adjust original SP to before the interrupt call
|
|||
save_registers.sp += 20; |
|||
|
|||
// Serial.print("break at ");Serial.println(breakaddr, HEX);
|
|||
|
|||
if (callback) { |
|||
callback(); |
|||
} |
|||
else { |
|||
debug_action(); |
|||
} |
|||
|
|||
if (debug_isHardcoded((void*)breakaddr)) { |
|||
// Serial.print("hard coded at ");Serial.println(breakaddr, HEX);
|
|||
// do nothing because we continue on to next instruction
|
|||
} |
|||
else if (debug_isBreakpoint((void*)breakaddr)) { |
|||
// our location is a breakpoint so we need to return it to the original and rerun
|
|||
// the instruction; to prevent a problem, set breakpoint to next instruction
|
|||
// and at next break, set it back
|
|||
debug_clearBreakpoint((void*)breakaddr, 1); |
|||
// break at next instruction
|
|||
debug_setBreakpoint((void*)(save_registers.pc), 0); |
|||
// set to rerun current instruction
|
|||
stack->pc = breakaddr; |
|||
// we need to process the next breakpoint differently
|
|||
debugactive = 1; |
|||
// the original breakpoint needs to be put back after next break
|
|||
debugreset = breakaddr; |
|||
} |
|||
} |
|||
else { |
|||
// clear the temporary breakpoint
|
|||
debug_clearBreakpoint((void*)breakaddr, 0); |
|||
// reset to re-run the instruction
|
|||
stack->pc = breakaddr; |
|||
|
|||
// if we need to reset the original, do so
|
|||
if (debugreset) { |
|||
debug_setBreakpoint((void*)debugreset, 1); |
|||
debugreset = 0; |
|||
} |
|||
|
|||
// are we stepping instruction by instruction?
|
|||
if (debugstep) { |
|||
// we're stepping so process any commands and break at next
|
|||
// and stay in this mode
|
|||
if (callback) { |
|||
callback(); |
|||
} |
|||
else { |
|||
debug_action(); |
|||
} |
|||
debug_setBreakpoint((void*)save_registers.pc, 0); |
|||
} |
|||
else { |
|||
// we're not stepping so reset mode
|
|||
debugactive = 0; |
|||
} |
|||
} |
|||
} |
|||
|
|||
#define SAVE_REGISTERS \ |
|||
"ldr r0, =stack \n" \ |
|||
"str sp, [r0] \n" \ |
|||
"ldr r0, =save_registers \n" \ |
|||
"ldr r2, [sp, #0] \n" \ |
|||
"str r2, [r0, #0] \n" \ |
|||
"ldr r2, [sp, #4] \n" \ |
|||
"str r2, [r0, #4] \n" \ |
|||
"ldr r2, [sp, #8] \n" \ |
|||
"str r2, [r0, #8] \n" \ |
|||
"ldr r2, [sp, #12] \n" \ |
|||
"str r2, [r0, #12] \n" \ |
|||
"ldr r2, [sp, #16] \n" \ |
|||
"str r2, [r0, #16] \n" \ |
|||
\ |
|||
"ldr r2, [sp, #20] \n" \ |
|||
"str r2, [r0, #20] \n" \ |
|||
"ldr r2, [sp, #24] \n" \ |
|||
"str r2, [r0, #24] \n" \ |
|||
"ldr r2, [sp, #28] \n" \ |
|||
"str r2, [r0, #28] \n" \ |
|||
\ |
|||
"str r4, [r0, #32] \n" \ |
|||
"str r5, [r0, #36] \n" \ |
|||
"str r6, [r0, #40] \n" \ |
|||
"str r7, [r0, #44] \n" \ |
|||
"str r8, [r0, #48] \n" \ |
|||
"str r9, [r0, #52] \n" \ |
|||
"str r10, [r0, #56] \n" \ |
|||
"str r11, [r0, #60] \n" \ |
|||
"str sp, [r0, #64] \n" |
|||
|
|||
void (*original_software_isr)() = NULL; |
|||
|
|||
__attribute__((noinline, naked)) |
|||
void debug_call_isr() { |
|||
// Are we in debug mode? If not, just jump to original ISR
|
|||
if (debugenabled == 0) { |
|||
if (original_software_isr) { |
|||
asm volatile("mov pc,%0" : : "r" (original_software_isr)); |
|||
} |
|||
return; |
|||
} |
|||
asm volatile( |
|||
"ldr r0, =stack \n" |
|||
"str sp, [r0] \n" |
|||
"push {lr} \n"); |
|||
debug_monitor(); // process the debug event
|
|||
asm volatile("pop {pc}"); |
|||
} |
|||
|
|||
void debug_call_isr_setup() { |
|||
debugcount++; |
|||
debugenabled = 1; |
|||
// process in lower priority so services can keep running
|
|||
NVIC_SET_PENDING(IRQ_SOFTWARE); |
|||
} |
|||
|
|||
__attribute__((noinline, naked)) |
|||
void svcall_isr() { |
|||
asm volatile(SAVE_REGISTERS); |
|||
asm volatile("push {lr}"); |
|||
debug_call_isr_setup(); |
|||
asm volatile("pop {pc}"); |
|||
} |
|||
|
|||
#pragma GCC push_options |
|||
#pragma GCC optimize ("O0") |
|||
__attribute__((naked)) |
|||
|
|||
void svc_call_table() { |
|||
asm volatile( |
|||
"svc #0x10 \n" |
|||
"nop \n" |
|||
"svc #0x10 \n" |
|||
"nop \n" |
|||
"svc #0x10 \n" |
|||
"nop \n" |
|||
"svc #0x10 \n" |
|||
"nop \n" |
|||
"svc #0x10 \n" |
|||
"nop \n" |
|||
"svc #0x10 \n" |
|||
"nop \n" |
|||
"svc #0x10 \n" |
|||
"nop \n" |
|||
); |
|||
} |
|||
|
|||
#pragma GCC pop_options |
|||
|
|||
uint32_t debug_getRegister(const char *reg) { |
|||
if (reg[0] == 'r') { |
|||
if (reg[2] == 0) { // r0-r9
|
|||
switch(reg[1]) { |
|||
case '0': return save_registers.r0; |
|||
case '1': return save_registers.r1; |
|||
case '2': return save_registers.r2; |
|||
case '3': return save_registers.r3; |
|||
case '4': return save_registers.r4; |
|||
case '5': return save_registers.r5; |
|||
case '6': return save_registers.r6; |
|||
case '7': return save_registers.r7; |
|||
case '8': return save_registers.r8; |
|||
case '9': return save_registers.r9; |
|||
} |
|||
} |
|||
else if (reg[1] == '1') { // r10-r12
|
|||
switch(reg[1]) { |
|||
case '0': return save_registers.r10; |
|||
case '1': return save_registers.r11; |
|||
case '2': return save_registers.r12; |
|||
} |
|||
} |
|||
} |
|||
else if (strcmp(reg, "lr")==0) return save_registers.lr; |
|||
else if (strcmp(reg, "pc")==0) return save_registers.pc; |
|||
else if (strcmp(reg, "sp")==0) return save_registers.sp; |
|||
else if (strcmp(reg, "cpsr")==0) return save_registers.xPSR; |
|||
return -1; |
|||
} |
|||
|
|||
/**
|
|||
* Fault debug messages |
|||
*/ |
|||
|
|||
void flash_blink(int n) { |
|||
volatile int p = 0; |
|||
pinMode(13, OUTPUT); |
|||
while(1) { |
|||
for(int c=0; c<n; c++) { |
|||
for(int i=0; i<20000000; i++) {p++;} |
|||
digitalWrite(13, HIGH); |
|||
for(int i=0; i<20000000; i++) {p++;} |
|||
digitalWrite(13, LOW); |
|||
} |
|||
for(int i=0; i<100000000; i++) {p++;} |
|||
} |
|||
} |
|||
|
|||
int debug_crash = 0; |
|||
void hard_fault_debug(int n) { |
|||
// if (debug_crash) flash_blink(n);
|
|||
Serial1.print("****FAULT "); |
|||
Serial1.println(hard_fault_debug_text[n]); |
|||
Serial1.print("r0=");Serial1.println(stack->r0, HEX); |
|||
Serial1.print("r1=");Serial1.println(stack->r1, HEX); |
|||
Serial1.print("r2=");Serial1.println(stack->r2, HEX); |
|||
Serial1.print("r3=");Serial1.println(stack->r3, HEX); |
|||
Serial1.print("r12=");Serial1.println(stack->r12, HEX); |
|||
Serial1.print("lr=0x");Serial1.println(stack->lr, HEX); |
|||
Serial1.print("pc=0x");Serial1.println(stack->pc, HEX); |
|||
stack->pc += 2; |
|||
debug_crash = 1; |
|||
} |
|||
|
|||
// uint32_t hard_fault_debug_addr = (uint32_t)hard_fault_debug;
|
|||
|
|||
#define xfault_isr_stack(fault) \ |
|||
asm volatile(SAVE_REGISTERS); \ |
|||
debug_id = fault; \ |
|||
NVIC_SET_PENDING(IRQ_SOFTWARE); \ |
|||
asm volatile("bx lr") |
|||
|
|||
#define fault_isr_stack(fault) \ |
|||
asm volatile("ldr r0, =stack \n str sp, [r0]"); \ |
|||
asm volatile("push {lr}"); \ |
|||
hard_fault_debug(fault); \ |
|||
asm volatile("pop {pc}") |
|||
|
|||
__attribute__((noinline, naked)) void nmi_isr(void) { fault_isr_stack(1); } |
|||
__attribute__((noinline, naked)) void hard_fault_isr(void) { fault_isr_stack(2); } |
|||
__attribute__((noinline, naked)) void memmanage_fault_isr(void) { fault_isr_stack(3); } |
|||
__attribute__((noinline, naked)) void bus_fault_isr(void) { fault_isr_stack(4); } |
|||
__attribute__((noinline, naked)) void usage_fault_isr(void) { fault_isr_stack(5); } |
|||
|
|||
/**
|
|||
* Initialization code |
|||
* |
|||
*/ |
|||
|
|||
void dumpmem(void *mem, int sz) { |
|||
Serial.print((uint32_t)mem, HEX); |
|||
Serial.print("="); |
|||
for(int i=0; i<sz; i++) { |
|||
Serial.print(((uint8_t*)mem)[i], HEX); |
|||
Serial.print(":"); |
|||
} |
|||
Serial.println(); |
|||
} |
|||
|
|||
// store the address of the stack pointer where we pre-allocate space since the remap
|
|||
// table must be in ram above 0x20000000 and this ram is in the stack area.
|
|||
uint32_t save_stack; |
|||
|
|||
void debug_init() { |
|||
|
|||
#ifdef HAS_FP_MAP |
|||
// find next aligned block in the stack
|
|||
uint32_t xtable = (save_stack + 0x100) & 0xFFFFFFC0; |
|||
// copy table
|
|||
uint32_t *sourcemem = (uint32_t*)(((uint32_t)svc_call_table & 0xFFFFFFFE)); |
|||
uint32_t *destmem = (uint32_t*)xtable; |
|||
for(int i=0; i<6; i++) { |
|||
destmem[i] = sourcemem[i]; |
|||
} |
|||
// enable the remap, but don't assign any yet
|
|||
FP_LAR = FP_LAR_UNLOCK_KEY; // doesn't do anything, but might in some other processors
|
|||
FP_REMAP = xtable; |
|||
remap_table = (uint16_t *)xtable; |
|||
FP_CTRL = 0b11; |
|||
|
|||
// delay(3000);
|
|||
// Serial.println(FP_CTRL, HEX);
|
|||
// dumpmem(sourcemem, 32);
|
|||
// dumpmem(destmem, 32);
|
|||
// dumpmem(xtable, 32);
|
|||
#endif |
|||
|
|||
_VectorsRam[2] = nmi_isr; |
|||
_VectorsRam[3] = hard_fault_isr; |
|||
_VectorsRam[4] = memmanage_fault_isr; |
|||
_VectorsRam[5] = bus_fault_isr; |
|||
_VectorsRam[6] = usage_fault_isr; |
|||
|
|||
_VectorsRam[11] = svcall_isr; |
|||
|
|||
// chaing the software ISR handler
|
|||
original_software_isr = _VectorsRam[IRQ_SOFTWARE + 16]; |
|||
_VectorsRam[IRQ_SOFTWARE + 16] = debug_call_isr; |
|||
NVIC_SET_PRIORITY(IRQ_SOFTWARE, 208); // 255 = lowest priority
|
|||
NVIC_ENABLE_IRQ(IRQ_SOFTWARE); |
|||
|
|||
debug_initBreakpoints(); |
|||
} |
|||
|
|||
// We will rename the original setup() to this by using a #define
|
|||
void setup_main(); |
|||
void gdb_init(); |
|||
|
|||
#ifdef HAS_FP_MAP |
|||
|
|||
/*
|
|||
* The setup function must allocate space on the stack for the remap table; this space must |
|||
* reside above 0x20000000 and this area is reserved in the stack. This is OK because the function |
|||
* calling setup() is main(), which never returns. So taking a chunk of stack won't affect it. If |
|||
* main() does ever want to return, it will have to dealloate this memory. |
|||
*/ |
|||
void __attribute__((naked)) setup() { |
|||
|
|||
#ifdef HAS_FP_MAP |
|||
asm volatile("sub sp, #512"); // allocate 512 bytes so we have room to align data
|
|||
asm volatile("mov %0, sp" : "=r" (save_stack) ); // save the location
|
|||
#endif |
|||
asm volatile("push {lr}"); // save the return address
|
|||
debug_init(); // perform debug initialization
|
|||
gdb_init(); |
|||
setup_main(); // call the "real" setup function
|
|||
asm volatile("pop {pc}"); // get original return address
|
|||
} |
|||
|
|||
#else |
|||
|
|||
void setup() { |
|||
debug_init(); // perform debug initialization
|
|||
gdb_init(); |
|||
setup_main(); // call the "real" setup function
|
|||
} |
|||
|
|||
#endif |
|||
|
|||
/**
|
|||
* Class |
|||
*/ |
|||
|
|||
#include "TeensyDebug.h" |
|||
|
|||
int Debug::setBreakpoint(void *p, int n) { return debug_setBreakpoint(p, n); } |
|||
int Debug::clearBreakpoint(void *p, int n) { return debug_clearBreakpoint(p, n); } |
|||
void Debug::setCallback(void (*c)()) { callback = c; } |
|||
uint32_t Debug::getRegister(const char *reg) { return debug_getRegister(reg); } |
|||
|
|||
Debug debug; |
@ -0,0 +1,43 @@ |
|||
#ifndef TEENSY_DEBUG_H |
|||
#define TEENSY_DEBUG_H |
|||
|
|||
// is this a Teensy 3.2? If so, we can support hardware interrupts
|
|||
// and should hijack setup() to set that up.
|
|||
#ifdef __MK20DX256__ |
|||
#define HAS_FP_MAP |
|||
#endif |
|||
|
|||
// If this is used internally, not need to remap
|
|||
#ifndef DEBUG_INTERNAL |
|||
// rename the original setup() because we need to hijack it
|
|||
#define setup setup_main |
|||
#endif |
|||
|
|||
int hcdebug_isEnabled(int n); |
|||
int hcdebug_setBreakpoint(int n); |
|||
|
|||
int debug_setBreakpoint(void *p, int n); |
|||
int debug_clearBreakpoint(void *p, int n); |
|||
void debug_setCallback(void (*c)()); |
|||
uint32_t debug_getRegister(const char *reg); |
|||
|
|||
class Debug { |
|||
public: |
|||
int setBreakpoint(void *p, int n=1); |
|||
int clearBreakpoint(void *p, int n=1); |
|||
void setCallback(void (*c)()); |
|||
uint32_t getRegister(const char *reg); |
|||
}; |
|||
extern Debug debug; |
|||
|
|||
#define breakpoint(n) {if (hcdebug_isEnabled(n)) {asm volatile("svc #0x11");}} |
|||
#define breakpoint_enable(n) {hcdebug_setBreakpoint(n);} |
|||
#define halt() {asm volatile("svc #0x11");} |
|||
|
|||
#define DEBUGRUN FASTRUN |
|||
|
|||
// set optimizations to 0 so that instructions will match
|
|||
// C statements; otherwise, gcc will reorg code
|
|||
#pragma GCC optimize("O0") |
|||
|
|||
#endif |
@ -0,0 +1,53 @@ |
|||
#include "TeensyDebug.h" |
|||
|
|||
volatile int mark = 45; |
|||
volatile int mark2 = 55; |
|||
|
|||
void break_me() { |
|||
Serial.println("BREAK!"); |
|||
Serial.println(debug.getRegister("pc"), HEX); |
|||
} |
|||
|
|||
//DEBUGRUN
|
|||
void testme() { |
|||
int local1=1; |
|||
int local2=2; |
|||
// halt();
|
|||
mark = 5; |
|||
mark2++; |
|||
mark2++; |
|||
} |
|||
|
|||
void level2() { |
|||
int local1b=15; |
|||
int local2b=25; |
|||
testme(); |
|||
} |
|||
|
|||
void level1() { |
|||
int local1a=45; |
|||
int local2a=65; |
|||
level2(); |
|||
} |
|||
|
|||
void setup() { |
|||
delay(3000); |
|||
Serial.begin(115200); |
|||
|
|||
// debug.setCallback(break_me);
|
|||
// debug.setBreakpoint(testme, 1);
|
|||
|
|||
*(int*)0 = 0; |
|||
|
|||
// breakpoint_enable(0);
|
|||
// breakpoint(0);
|
|||
} |
|||
|
|||
extern int debugcount; |
|||
|
|||
void loop() { |
|||
// level1();
|
|||
Serial.print("act=");Serial.println(debugcount); |
|||
Serial.print("mark=");Serial.println(mark); |
|||
delay(2000); |
|||
} |
@ -0,0 +1,603 @@ |
|||
/**
|
|||
* @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 |
|||
* |
|||
*/ |
|||
#include <Arduino.h> |
|||
#include <HardwareSerial.h> |
|||
|
|||
#define DEBUG_INTERNAL |
|||
|
|||
#include "TeensyDebug.h" |
|||
|
|||
/**
|
|||
* Code to communicate with GDB. Use standard nomenclature. |
|||
* devInit() is not standard. It must be called at initialization. |
|||
* |
|||
*/ |
|||
#define dev Serial1 |
|||
|
|||
/**
|
|||
* @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 Initialize serial |
|||
* |
|||
*/ |
|||
void devInit() { |
|||
dev.begin(9600); |
|||
} |
|||
|
|||
// Signal codes for ARM faults
|
|||
const char *signal_text[] = { |
|||
"S05", "S10", "S11", "S11", "S10", "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, void *addr, int sz) { |
|||
for (int i = 0; i < sz; i++) { |
|||
uint8_t b = ((uint8_t*)addr)[i]; |
|||
*buff++ = int2hex[b]; |
|||
} |
|||
*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); |
|||
} |
|||
|
|||
/**
|
|||
* @brief Send result text to GDB (formatting and calculating checksum) |
|||
* |
|||
* @param result String of message to send |
|||
*/ |
|||
void sendResult(const char *result) { |
|||
int checksum = calcChecksum(result); |
|||
const char *presult = result; |
|||
putDebugChar('$'); |
|||
while (*presult) { |
|||
putDebugChar(*presult++); |
|||
} |
|||
putDebugChar('#'); |
|||
putDebugChar(int2hex[checksum >> 4]); |
|||
putDebugChar(int2hex[checksum & 0x0F]); |
|||
// dev.flush();
|
|||
} |
|||
|
|||
// 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; |
|||
|
|||
/**
|
|||
* @brief Routing for processing breakpoints |
|||
* |
|||
*/ |
|||
#pragma GCC push_options |
|||
#pragma GCC optimize ("O0") |
|||
void process_onbreak() { |
|||
// send the signal
|
|||
sendResult(signal_text[debug_id]); |
|||
// go into halt state and stay until flag is cleared
|
|||
halt_state = 1; |
|||
while(halt_state) { |
|||
delay(10); |
|||
yield(); |
|||
} |
|||
} |
|||
#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, int 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; |
|||
} |
|||
|
|||
/**
|
|||
* @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) { |
|||
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, debug.getRegister("sp"));; |
|||
result = append32(result, debug.getRegister("lr")); |
|||
result = append32(result, debug.getRegister("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) { |
|||
strcpy(result, "E01"); |
|||
return 0; |
|||
} |
|||
|
|||
/**
|
|||
* @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 (addr == 0) { |
|||
strcpy(result, "E01"); |
|||
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 suppor 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, "S00");
|
|||
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 (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 (debug.setBreakpoint((void*)addr)) { |
|||
strcpy(result, "E01"); |
|||
} |
|||
else { |
|||
strcpy(result, "OK"); |
|||
} |
|||
return 0; |
|||
} |
|||
|
|||
/**
|
|||
* @brief Process 'q' query command. For now report back only PacketSize. |
|||
* |
|||
* @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; |
|||
} |
|||
strcpy(result, ""); |
|||
return 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(const char *cmd, char *result) { |
|||
switch(cmd[0]) { |
|||
case 'g': return process_g(cmd, result); |
|||
case 'G': return process_G(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 '?': 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); |
|||
} |
|||
result[0] = 0; |
|||
return 0; |
|||
} |
|||
|
|||
/**
|
|||
* @brief Read input, if available and process any commands |
|||
* |
|||
*/ |
|||
void processGDBinput() { |
|||
char result[1024]; |
|||
|
|||
// no data? do nothing
|
|||
if (dev.available() <= 0) 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"); |
|||
return; |
|||
} |
|||
|
|||
// User hit Ctrl-C or other break
|
|||
if (c == 0x03) { |
|||
Serial.println("Ctrl-C"); |
|||
cause_break = 1; // later?
|
|||
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; |
|||
|
|||
Serial.print("got command:"); |
|||
Serial.println(cmd); |
|||
|
|||
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() { |
|||
// static unsigned int nexttick = millis() + 1000;
|
|||
// if (millis() > nexttick) {
|
|||
// Serial.println("tick");
|
|||
// nexttick += 1000;
|
|||
// }
|
|||
if (! debug_active) return; |
|||
processGDBinput(); |
|||
if (cause_break) { |
|||
cause_break = 0; |
|||
asm volatile("svc 0x12"); |
|||
} |
|||
} |
|||
|
|||
// void setup_main();
|
|||
|
|||
IntervalTimer gdb_timer; |
|||
void gdb_init() { |
|||
if (debug_active) { |
|||
devInit(); |
|||
gdb_timer.begin(processGDB, 1000); |
|||
debug.setCallback(process_onbreak); |
|||
// We could halt at startup, but maybe it's best to let user
|
|||
// explicitly do so with a breakpoint(n)
|
|||
// debug.setBreakpoint(setup_main, 1);
|
|||
} |
|||
} |
@ -0,0 +1,3 @@ |
|||
debug KEYWORD1 |
|||
gdb KEYWORD2 |
|||
Teensy KEYWORD3 |
@ -0,0 +1,10 @@ |
|||
name=TeensyDebug |
|||
version=0.0.1 |
|||
author=Fernando Trias |
|||
maintainer=Fernando Trias |
|||
sentence=Debugging using GDB on PJRC Teensy. |
|||
paragraph=This debugging library uses GDB stub to enable remote debugging of live programs running on the Teensy. |
|||
category=Other |
|||
url=https://github.com/ftrias/TeensyDebug |
|||
architectures=* |
|||
includes=TeensyDebug.h |
@ -0,0 +1,16 @@ |
|||
Copyright 2020 by Fernando Trias |
|||
|
|||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software |
|||
and associated documentation files (the "Software"), to deal in the Software without restriction, |
|||
including without limitation the rights to use, copy, modify, merge, publish, distribute, |
|||
sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is |
|||
furnished to do so, subject to the following conditions: |
|||
|
|||
The above copyright notice and this permission notice shall be included in all copies or |
|||
substantial portions of the Software. |
|||
|
|||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING |
|||
BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND |
|||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, |
|||
DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
|||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. |
Loading…
Reference in new issue