Browse Source

Fix Teensy 3

pull/2/head
Fernando Trias 4 years ago
parent
commit
abe14e0fc4
  1. 6
      README.md
  2. 152
      TeensyDebug.cpp
  3. 9
      TeensyDebug.h
  4. 18
      gdbstub.cpp
  5. 3
      teensy_debug

6
README.md

@ -2,11 +2,11 @@ Live debugging on Teensy & GDB support
=========================================== ===========================================
By Fernando Trias, June 2020 By Fernando Trias, June 2020
This module provides breakpoint support for the Teensy 4 (and 3.x in the future) platform from PJRC without need for an external debug interface. The module provides: This module provides breakpoint support for the Teensy 3.x and 4.x platform from PJRC without need for an external debug interface. The module provides:
1. GDB Remote Serial Protocol stub so that GDB can connect to the Teensy and perform live debugging. 1. GDB Remote Serial Protocol stub so that GDB can connect to the Teensy and perform live debugging.
2. Ability to set/clear breakpoints and query registers and memory. 2. Ability to set/clear breakpoints and query registers and memory on running and paused programs.
3. Catch hard crashes and display diagnosics. 3. Catch hard crashes and display diagnosics.
@ -251,7 +251,7 @@ Bugs
1. Because stepping is implemented by putting a `SVC` in the next instruction, there are a number of bugs related to `step` and `next`. 1. Because stepping is implemented by putting a `SVC` in the next instruction, there are a number of bugs related to `step` and `next`.
2. `step` may not always step into functions. Stepping won't always work over a return. TeensyDebug traps `bx lr`, `pop {Rmmm, pc}`, `mov pc, Rm` and will usually step properly over these instruction if using gdb `stepi` command. Branch instructions are also interpreted properly most of the time. However gdb `step` and `next` may occasionally get confused and stop stepping. 2. `step` may not always step into functions. Stepping won't always work over a return. TeensyDebug traps `bx lr`, `pop {Rmmm, pc}`, `mov pc, Rm` and will usually step properly over these instruction if using gdb `stepi` command. Branch instructions are also interpreted properly most of the time. However gdb `step` and `next` may occasionally get confused and stop stepping. Returning from functions on Teensy 3 is very limited.
Future considerations Future considerations
------------------------------------------- -------------------------------------------

152
TeensyDebug.cpp

@ -37,6 +37,8 @@ https://web.eecs.umich.edu/~prabal/teaching/eecs373-f10/readings/ARMv7-M_ARM.pdf
#define GDB_DEBUG_INTERNAL #define GDB_DEBUG_INTERNAL
#include "TeensyDebug.h" #include "TeensyDebug.h"
// #define DISPLAY_HARD_FAULT
/** /**
* @brief Memory maps missing from headers * @brief Memory maps missing from headers
* *
@ -145,51 +147,26 @@ const int hw_breakpoint_count = 6;
void *hw_breakpoints[hw_breakpoint_count]; void *hw_breakpoints[hw_breakpoint_count];
uint16_t *hw_remap_table; uint16_t *hw_remap_table;
int hwdebug_clearBreakpoint(void *p, int n) { int hwdebug_clearAllBreakpoints() {
FP_COMP(n) = 0; for (int n=1; n<6; n++) {
return 0; hw_breakpoints[n] = 0;
}
int hwdebug_setBreakpoint(void *p, int n) {
if (p == 0) {
FP_COMP(n) = 0; FP_COMP(n) = 0;
// Serial.print("break0 ");Serial.println(n);
}
else {
uint32_t pc = ((uint32_t)p) & ADDRESS_MASK;
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 & ADDRESS_MASK2;
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; return 0;
} }
void hwdebug_disableBreakpoint(int n) { int hwdebug_getBreakpoint(void *p) {
FP_COMP(n) &= ADDRESS_MASK; for (int n=1; n<6; n++) {
// Serial.print("break ");Serial.print(n);Serial.println(" disable"); if (hw_breakpoints[n] == p) {
} return n;
}
void hwdebug_enableBreakpoint(int n) { }
FP_COMP(n) |= 1; return -1;
// Serial.print("break ");Serial.print(n);Serial.println(" enable");
} }
int hwdebug_getBreakpoint(void *p) { int hwdebug_nextBreakpoint() {
for (int n=1; n<6; n++) { for (int n=1; n<6; n++) {
if (breakpoints[n]== p) { if (hw_breakpoints[n] == 0) {
return n; return n;
} }
} }
@ -198,13 +175,56 @@ int hwdebug_getBreakpoint(void *p) {
int hwdebug_isBreakpoint(void *p) { int hwdebug_isBreakpoint(void *p) {
for (int n=1; n<6; n++) { for (int n=1; n<6; n++) {
if (breakpoints[n]== p) { if (hw_breakpoints[n] == p) {
return 1; return 1;
} }
} }
return 0; return 0;
} }
int hwdebug_clearBreakpoint(void *p) {
int n = hwdebug_getBreakpoint(p);
if (n < 0) return -1;
FP_COMP(n) = 0;
hw_breakpoints[n] = 0;
// Serial.print("hw clear ");Serial.print(n);Serial.print(" at ");Serial.println((int)p, HEX);
return 0;
}
int hwdebug_setBreakpoint(void *p) {
int n = hwdebug_getBreakpoint(p);
if (n > 0) {
// Serial.print("set existing hw breakpoint at ");Serial.println((int)p, HEX);
return 0;
}
n = hwdebug_nextBreakpoint();
if (n < 0) {
// Serial.print("out of breakpoints for ");Serial.println((int)p, HEX);
return -1;
}
uint32_t pc = ((uint32_t)p) & ADDRESS_MASK;
if (pc & 0b10) { // must be aligned, so go to next instruction
// store the first instruction
pc -= 2;
hw_remap_table[(n<<1) + 0] = ((uint16_t*)pc)[0];
hw_remap_table[(n<<1) + 1] = 0xdf10; // svc 10 instruction
}
else {
// store the next instruction
hw_remap_table[(n<<1) + 0] = 0xdf10; // svc 10 instruction
hw_remap_table[(n<<1) + 1] = ((uint16_t*)pc)[1];
}
uint32_t addr = pc & ADDRESS_MASK2;
FP_COMP(n) = addr | 1;
hw_breakpoints[n] = p;
// Serial.print("hw break ");Serial.print(n);Serial.print(" at ");Serial.print(pc, HEX);Serial.print("=");Serial.println(addr, HEX);
return 0;
}
#endif #endif
/** /**
@ -269,12 +289,7 @@ void debug_initBreakpoints() {
hc_breakpoint_enabled[i] = 0; hc_breakpoint_enabled[i] = 0;
} }
#ifdef HAS_FP_MAP #ifdef HAS_FP_MAP
hwdebug_clearBreakpoint(0, 0); hwdebug_clearAllBreakpoints();
hwdebug_clearBreakpoint(0, 1);
hwdebug_clearBreakpoint(0, 2);
hwdebug_clearBreakpoint(0, 3);
hwdebug_clearBreakpoint(0, 4);
hwdebug_clearBreakpoint(0, 5);
#endif #endif
} }
@ -286,7 +301,7 @@ void debug_initBreakpoints() {
* @param n Optional slot for hardware breakpoints * @param n Optional slot for hardware breakpoints
* @return int 0 if success; -1 failure * @return int 0 if success; -1 failure
*/ */
int debug_clearBreakpoint(void *p, int n) { int debug_clearBreakpoint(void *p) {
// Serial.print("clear ");Serial.println((int)p,HEX); // Serial.print("clear ");Serial.println((int)p,HEX);
if (p >= RAM_START && p <= RAM_END) { if (p >= RAM_START && p <= RAM_END) {
return swdebug_clearBreakpoint(p); return swdebug_clearBreakpoint(p);
@ -296,7 +311,7 @@ int debug_clearBreakpoint(void *p, int n) {
} }
else { else {
#ifdef HAS_FP_MAP #ifdef HAS_FP_MAP
return hwdebug_clearBreakpoint(p, n); return hwdebug_clearBreakpoint(p);
#else #else
return -1; return -1;
#endif #endif
@ -310,7 +325,7 @@ int debug_clearBreakpoint(void *p, int n) {
* @param n Optional slot for hardware breakpoints * @param n Optional slot for hardware breakpoints
* @return int 0 = success; -1 = failure * @return int 0 = success; -1 = failure
*/ */
int debug_setBreakpoint(void *p, int n) { int debug_setBreakpoint(void *p) {
// Serial.print("set ");Serial.println((int)p,HEX); // Serial.print("set ");Serial.println((int)p,HEX);
if (p >= RAM_START && p <= RAM_END) { if (p >= RAM_START && p <= RAM_END) {
return swdebug_setBreakpoint(p); return swdebug_setBreakpoint(p);
@ -320,7 +335,7 @@ int debug_setBreakpoint(void *p, int n) {
} }
else { else {
#ifdef HAS_FP_MAP #ifdef HAS_FP_MAP
return hwdebug_setBreakpoint(p, n); return hwdebug_setBreakpoint(p);
#else #else
return -1; return -1;
#endif #endif
@ -499,6 +514,7 @@ void *instructionReturn(void *p) {
// Serial.print("pop pc instr ");Serial.println(inst, HEX); // Serial.print("pop pc instr ");Serial.println(inst, HEX);
// Serial.print("regs ");Serial.println(regs); // Serial.print("regs ");Serial.println(regs);
// Serial.print("pop pc at ");Serial.println(memory[regs], HEX); // Serial.print("pop pc at ");Serial.println(memory[regs], HEX);
// return 0;
return (void*)memory[regs]; return (void*)memory[regs];
} }
if (inst == 0x4770) { // bx lr if (inst == 0x4770) { // bx lr
@ -567,19 +583,25 @@ uint32_t temp_breakpoint = 0;
uint32_t temp_breakpoint2 = 0; uint32_t temp_breakpoint2 = 0;
void setBreakPointNext(uint32_t breakaddr, uint32_t nextaddr) { void setBreakPointNext(uint32_t breakaddr, uint32_t nextaddr) {
#ifdef HAS_FP_MAP
// For some unknown reason, FP doesn't work across returns like pop {pc},
// etc.
if (0) { }
#else
void *ret = instructionReturn((void*)breakaddr); void *ret = instructionReturn((void*)breakaddr);
// is this a return of some sort? // is this a return of some sort?
if (ret) { if (ret) {
temp_breakpoint = (uint32_t)ret; temp_breakpoint = (uint32_t)ret;
// Serial.print("return to ");Serial.println(temp_breakpoint, HEX); // Serial.print("return to ");Serial.println(temp_breakpoint, HEX);
} }
#endif
else { else {
int bx; int bx;
void *b = instructionBranch((void*)breakaddr, &bx); void *b = instructionBranch((void*)breakaddr, &bx);
if (b) { if (b) {
temp_breakpoint2 = (uint32_t)b; temp_breakpoint2 = (uint32_t)b;
// Serial.print("branch to ");Serial.println(temp_breakpoint2, HEX); // Serial.print("branch to ");Serial.println(temp_breakpoint2, HEX);
debug_setBreakpoint((void*)temp_breakpoint2, 0); debug_setBreakpoint((void*)temp_breakpoint2);
} }
// is 32 bits wide? // is 32 bits wide?
if (instructionWidth((void*)breakaddr) == 2) { if (instructionWidth((void*)breakaddr) == 2) {
@ -590,7 +612,7 @@ void setBreakPointNext(uint32_t breakaddr, uint32_t nextaddr) {
temp_breakpoint = nextaddr; temp_breakpoint = nextaddr;
} }
} }
debug_setBreakpoint((void*)temp_breakpoint, 0); debug_setBreakpoint((void*)temp_breakpoint);
} }
/** /**
@ -620,14 +642,14 @@ void debug_monitor() {
// gdb will clear the current breakpoint but we do this first so disassembly holds // gdb will clear the current breakpoint but we do this first so disassembly holds
// original code // original code
debug_clearBreakpoint((void*)breakaddr, 1); debug_clearBreakpoint((void*)breakaddr);
// clear the temporary breakpoints // clear the temporary breakpoints
if (temp_breakpoint) { if (temp_breakpoint) {
debug_clearBreakpoint((void*)temp_breakpoint, 0); debug_clearBreakpoint((void*)temp_breakpoint);
temp_breakpoint = 0; temp_breakpoint = 0;
} }
if (temp_breakpoint2) { if (temp_breakpoint2) {
debug_clearBreakpoint((void*)temp_breakpoint2, 2); debug_clearBreakpoint((void*)temp_breakpoint2);
temp_breakpoint2 = 0; temp_breakpoint2 = 0;
} }
} }
@ -656,7 +678,7 @@ void debug_monitor() {
// if we need to reset the original, do so // if we need to reset the original, do so
if (debugreset) { if (debugreset) {
debug_setBreakpoint((void*)debugreset, 1); debug_setBreakpoint((void*)debugreset);
debugreset = 0; debugreset = 0;
} }
} }
@ -961,12 +983,12 @@ void flash_blink(int n) {
pinMode(13, OUTPUT); pinMode(13, OUTPUT);
while(1) { while(1) {
for(int c=0; c<n; c++) { for(int c=0; c<n; c++) {
for(int i=0; i<20000000; i++) {p++;} for(int i=0; i<20000; i++) {p++;}
digitalWrite(13, HIGH); digitalWrite(13, HIGH);
for(int i=0; i<20000000; i++) {p++;} for(int i=0; i<20000; i++) {p++;}
digitalWrite(13, LOW); digitalWrite(13, LOW);
} }
for(int i=0; i<100000000; i++) {p++;} for(int i=0; i<100000; i++) {p++;}
} }
} }
@ -978,7 +1000,6 @@ int debug_crash = 0;
* @param n * @param n
*/ */
void hard_fault_debug(int n) { void hard_fault_debug(int n) {
// if (debug_crash) flash_blink(n);
Serial.print("****FAULT "); Serial.print("****FAULT ");
Serial.println(hard_fault_debug_text[n]); Serial.println(hard_fault_debug_text[n]);
Serial.print("r0=");Serial.println(stack->r0, HEX); Serial.print("r0=");Serial.println(stack->r0, HEX);
@ -991,6 +1012,7 @@ void hard_fault_debug(int n) {
Serial.println("****************"); Serial.println("****************");
debug_crash = 1; debug_crash = 1;
stack->pc += 2; // if we continue, skip faulty instruction stack->pc += 2; // if we continue, skip faulty instruction
flash_blink(n);
} }
extern "C" extern "C"
@ -1004,13 +1026,17 @@ void fault_halt() {
#pragma GCC push_options #pragma GCC push_options
#pragma GCC optimize ("O0") #pragma GCC optimize ("O0")
#ifdef DISPLAY_HARD_FAULT
// Save registers during fault and call default handler // Save registers during fault and call default handler
#define xfault_isr_stack(fault) \ #define fault_isr_stack(fault) \
asm volatile("ldr r0, =stack \n str sp, [r0]"); \ asm volatile("ldr r0, =stack \n str sp, [r0]"); \
asm volatile("push {lr}"); \ asm volatile("push {lr}"); \
hard_fault_debug(fault); \ hard_fault_debug(fault); \
asm volatile("pop {pc}") asm volatile("pop {pc}")
#else
#define fault_isr_stack(fault) \ #define fault_isr_stack(fault) \
asm volatile(SAVE_STACK); \ asm volatile(SAVE_STACK); \
asm volatile(SAVE_REGISTERS); \ asm volatile(SAVE_REGISTERS); \
@ -1020,6 +1046,8 @@ void fault_halt() {
debug_call_isr_setup(); \ debug_call_isr_setup(); \
asm volatile("pop {pc}") asm volatile("pop {pc}")
#endif
/** /**
* @brief Trap faults * @brief Trap faults
* *
@ -1075,7 +1103,7 @@ void debug_init() {
// enable the remap, but don't assign any yet // 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_LAR = FP_LAR_UNLOCK_KEY; // doesn't do anything, but might in some other processors
FP_REMAP = xtable; FP_REMAP = xtable;
remap_table = (uint16_t *)xtable; hw_remap_table = (uint16_t *)xtable;
FP_CTRL = 0b11; FP_CTRL = 0b11;
// delay(3000); // delay(3000);
@ -1174,8 +1202,8 @@ void __attribute__((naked)) setup() {
*/ */
int Debug::begin(Stream *device) { return debug_begin(device); } int Debug::begin(Stream *device) { return debug_begin(device); }
int Debug::setBreakpoint(void *p, int n) { return debug_setBreakpoint(p, n); } int Debug::setBreakpoint(void *p) { return debug_setBreakpoint(p); }
int Debug::clearBreakpoint(void *p, int n) { return debug_clearBreakpoint(p, n); } int Debug::clearBreakpoint(void *p) { return debug_clearBreakpoint(p); }
void Debug::setCallback(void (*c)()) { callback = c; } void Debug::setCallback(void (*c)()) { callback = c; }
uint32_t Debug::getRegister(const char *reg) { return debug_getRegister(reg); } uint32_t Debug::getRegister(const char *reg) { return debug_getRegister(reg); }
int Debug::setRegister(const char *reg, uint32_t value) { return debug_setRegister(reg, value); } int Debug::setRegister(const char *reg, uint32_t value) { return debug_setRegister(reg, value); }

9
TeensyDebug.h

@ -24,15 +24,20 @@
// //
#ifdef __MK20DX256__ #ifdef __MK20DX256__
#define FLASH_START ((void*)0x0)
#define FLASH_END ((void*)0x00040000)
#define RAM_START ((void*)0x1FFF8000) #define RAM_START ((void*)0x1FFF8000)
#define RAM_END ((void*)0x2FFFFFFF) #define RAM_END ((void*)0x2FFFFFFF)
#endif #endif
#ifdef __IMXRT1062__ #ifdef __IMXRT1062__
#define FLASH_START ((void*)0x60000000)
#define FLASH_END ((void*)0x601f0000)
#define RAM_START ((void*)0x00000020) #define RAM_START ((void*)0x00000020)
#define RAM_END ((void*)0x20280000) #define RAM_END ((void*)0x20280000)
#endif #endif
#include <usb_desc.h>
#if defined(GDB_DUAL_SERIAL) && ! defined(CDC2_DATA_INTERFACE) #if defined(GDB_DUAL_SERIAL) && ! defined(CDC2_DATA_INTERFACE)
#error "You must use Dual Serial or Triple Serial to enable GDB on Dual Serial." #error "You must use Dual Serial or Triple Serial to enable GDB on Dual Serial."
@ -121,8 +126,8 @@ class Debug : public Print, public DebugFileIO {
public: public:
int begin(Stream *device = NULL); int begin(Stream *device = NULL);
int begin(Stream &device) { return begin(&device); } int begin(Stream &device) { return begin(&device); }
int setBreakpoint(void *p, int n=1); int setBreakpoint(void *p);
int clearBreakpoint(void *p, int n=1); int clearBreakpoint(void *p);
void setCallback(void (*c)()); void setCallback(void (*c)());
uint32_t getRegister(const char *reg); uint32_t getRegister(const char *reg);
int setRegister(const char *reg, uint32_t value); int setRegister(const char *reg, uint32_t value);

18
gdbstub.cpp

@ -525,14 +525,18 @@ int process_P(const char *cmd, char *result) {
* @param addr Address to check * @param addr Address to check
* @return int 1 = valid; 0 = invalid * @return int 1 = valid; 0 = invalid
*/ */
int isValidAddress(uint32_t addr) { int isValidAddress(uint32_t addr, int sz=0) {
if (addr <= 0x20) { if (addr >= RAM_START && addr <= RAM_END) {
return 0; if (addr+sz-1 >= RAM_START && addr+sz-1 <= RAM_END) {
return 1;
}
} }
else if (addr >= 0xF0000000) { else if (addr >= FLASH_START && addr <= FLASH_END) {
return 0; if (addr+sz-1 >= FLASH_START && addr+sz-1 <= FLASH_END) {
return 1;
}
} }
return 1; return 0;
} }
/** /**
@ -554,7 +558,7 @@ int process_m(const char *cmd, char *result) {
// Serial.print("read at ");Serial.println(addr, HEX); // Serial.print("read at ");Serial.println(addr, HEX);
if (isValidAddress(addr+sz-1) == 0) { if (isValidAddress(addr, sz) == 0) {
strcpy(result, "E01"); strcpy(result, "E01");
return 0; return 0;
} }

3
teensy_debug

@ -1,4 +1,5 @@
#!/usr/bin/env python #!/usr/bin/env python
# Copyright 2020 by Fernando Trias
# #
# Program Teensy, wait for it to come back online and start GDB. # Program Teensy, wait for it to come back online and start GDB.
@ -212,7 +213,7 @@ def createFiles(AVR):
print("Create %s" % (board)) print("Create %s" % (board))
with open(board, mode) as f: with open(board, mode) as f:
f.write("menu.gdb=GDB\n") f.write("menu.gdb=GDB\n")
for ver in ('41','40'): for ver in ('41','40','31'):
f.write(""" f.write("""
teensy%s.menu.gdb.serial=Take over Serial teensy%s.menu.gdb.serial=Take over Serial
teensy%s.menu.gdb.serial.build.gdb=2 teensy%s.menu.gdb.serial.build.gdb=2

Loading…
Cancel
Save