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.
1184 lines
31 KiB
1184 lines
31 KiB
/**
|
|
* @file TeensyDebug.cpp
|
|
* @author Fernando Trias
|
|
* @brief Implement GDB stub using TeensyDebug interface
|
|
* @version 0.1
|
|
* @date 2020-06-09
|
|
*
|
|
* @copyright Copyright (c) 2020 Fernando Trias
|
|
*
|
|
*/
|
|
|
|
/** 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
|
|
https://sourceware.org/gdb/current/onlinedocs/gdb/Remote-Protocol.html#Remote-Protocol
|
|
http://engold.ui.ac.ir/~nikmehr/Appendix_B2.pdf
|
|
http://wcours.gel.ulaval.ca/GIF1001/docs/arm-instructionset.pdf
|
|
https://web.eecs.umich.edu/~prabal/teaching/eecs373-f10/readings/ARMv7-M_ARM.pdf
|
|
|
|
*/
|
|
|
|
#include <Arduino.h>
|
|
|
|
#define GDB_DEBUG_INTERNAL
|
|
#include "TeensyDebug.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)
|
|
|
|
#define ADDRESS_MASK 0xFFFFFFFE
|
|
#define ADDRESS_MASK2 0xFFFFFFFC
|
|
|
|
#define CPU_FLAG_FPCA 2
|
|
|
|
#ifdef __ARM_PCS_VFP
|
|
// reserve space for 8 register, 16 floats, float stat and spacer
|
|
#define ISR_STACK_SIZE ((8+16+1+1)*4)
|
|
#else
|
|
#define ISR_STACK_SIZE (8*4)
|
|
#endif
|
|
|
|
/***********************************************
|
|
*
|
|
* Breakpoint setup
|
|
*
|
|
*/
|
|
|
|
/**
|
|
* @brief Software RAM breakpoints
|
|
*
|
|
*/
|
|
|
|
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) & ADDRESS_MASK;
|
|
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("clear bkpt; 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) & ADDRESS_MASK;
|
|
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("set brkt; 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) & ADDRESS_MASK;
|
|
for(int i=0; i<sw_breakpoint_count; i++) {
|
|
if (sw_breakpoint_addr[i] == (void*)addr) {
|
|
return 1;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
#ifdef HAS_FP_MAP
|
|
|
|
/**
|
|
* @brief Hardware breakpoints, including memory remapping
|
|
* Teensy doesn't support disabling C_DEBUGEN so unfortunately
|
|
* it is not possible to use Cortex debugging features.
|
|
*
|
|
*/
|
|
|
|
const int hw_breakpoint_count = 6;
|
|
void *hw_breakpoints[hw_breakpoint_count];
|
|
uint16_t *hw_remap_table;
|
|
|
|
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) & 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;
|
|
}
|
|
|
|
void hwdebug_disableBreakpoint(int n) {
|
|
FP_COMP(n) &= ADDRESS_MASK;
|
|
// 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
|
|
|
|
/**
|
|
* @brief Hard coded breakpoints
|
|
*
|
|
*/
|
|
|
|
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
|
|
// SVC 0x12 is Ctrl-C trap
|
|
if (p[0] == 0xdf11 || p[0] == 0xdf12) {
|
|
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;
|
|
}
|
|
|
|
/**
|
|
* Wrapper functions that call corresponding breakpoint functions.
|
|
*
|
|
*/
|
|
|
|
/**
|
|
* @brief Initialize the breakpoint system and clear storage
|
|
*
|
|
*/
|
|
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
|
|
}
|
|
|
|
|
|
/**
|
|
* @brief Clear the given breakpoint
|
|
*
|
|
* @param p Pointer to location with breakpoint
|
|
* @param n Optional slot for hardware breakpoints
|
|
* @return int 0 if success; -1 failure
|
|
*/
|
|
int debug_clearBreakpoint(void *p, int n) {
|
|
// Serial.print("clear ");Serial.println((int)p,HEX);
|
|
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
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @brief Set a new breakpoint
|
|
*
|
|
* @param p Pointer to location to set breakpoint
|
|
* @param n Optional slot for hardware breakpoints
|
|
* @return int 0 = success; -1 = failure
|
|
*/
|
|
int debug_setBreakpoint(void *p, int n) {
|
|
// Serial.print("set ");Serial.println((int)p,HEX);
|
|
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
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @brief Return if there is a breakpoint at location
|
|
*
|
|
* @param p Pointer to location
|
|
* @return int 1 = there is one; 0 = there isn't one
|
|
*/
|
|
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
|
|
*
|
|
* During a breakpoint:
|
|
* 1. The interrupt is initiated and registers are saved.
|
|
* 2. The breakpoint instruction is replaced with the original code.
|
|
* 3. The handler callback is called.
|
|
* 4. A new temporary breakpoint is put in the next instruction.
|
|
* 5. Execution continues and breaks at next instruction.
|
|
* 6. The temporary breakpoint is replaced with origianl code.
|
|
* 7. The original breakpoint is restored.
|
|
* 8. Execution continues.
|
|
*/
|
|
|
|
// breakpoint handler pointer
|
|
void (*callback)() = NULL;
|
|
|
|
// Counter for debugging; counts number of breakpoint calls
|
|
int debugcount = 0;
|
|
|
|
// Debug system is enabled?
|
|
int debugenabled = 0;
|
|
|
|
// Are we in a breakpoint or step instruction?
|
|
int debugstep = 0;
|
|
|
|
// Restore registers before returning?
|
|
int debugrestore = 0;
|
|
|
|
// Pretty names for breakpoint and fault types
|
|
const char *hard_fault_debug_text[] = {
|
|
"debug", "break", "nmi", "hard", "mem", "bus", "usage"
|
|
};
|
|
|
|
// The interrupt call
|
|
// 0 = breakpoint
|
|
// 1 = nmi
|
|
// 2 = hard fault, etc.
|
|
uint32_t debug_id = 0;
|
|
|
|
// Debug tracing - not used by code
|
|
int debug_trace = 0;
|
|
|
|
// Copy of the registers at breakpoint
|
|
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;
|
|
};
|
|
// Live pointer to stack of ISR. We use this to modify the
|
|
// return address and other things
|
|
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);
|
|
}
|
|
|
|
int instructionWidth(void *p) {
|
|
uint16_t prefix = (*(uint16_t*)p) & 0xF800;
|
|
if (prefix == 0xF000) return 2;
|
|
if (prefix == 0xE800) return 2;
|
|
if (prefix == 0xF000) return 2;
|
|
if (prefix == 0xF800) return 2;
|
|
return 1;
|
|
}
|
|
|
|
uint32_t getRegisterNum(int x) {
|
|
switch(x) {
|
|
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;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int countBits(int x) {
|
|
return
|
|
((x & (1<<0)) >> 0) +
|
|
((x & (1<<1)) >> 1) +
|
|
((x & (1<<2)) >> 2) +
|
|
((x & (1<<3)) >> 3) +
|
|
((x & (1<<4)) >> 4) +
|
|
((x & (1<<5)) >> 5) +
|
|
((x & (1<<6)) >> 6) +
|
|
((x & (1<<7)) >> 7);
|
|
}
|
|
|
|
void *instructionReturn(void *p) {
|
|
uint16_t inst = *(uint16_t*)p;
|
|
uint16_t prefix = inst >> 8;
|
|
// mov pc, Rx
|
|
// Serial.print("ret? prefix ");Serial.println(prefix);
|
|
if (prefix == 0b01000110 && ((inst & 0b111) == 0b111)) {
|
|
uint32_t reg = getRegisterNum((inst >> 3) & 0b111);
|
|
// Serial.print("mov pc ");Serial.println(reg);
|
|
return (void*)reg;
|
|
}
|
|
if (prefix == 0b10111101) { // pop {Rxxx, pc}
|
|
int regs = countBits(inst & 0xFF);
|
|
uint32_t *memory = (uint32_t*)save_registers.sp;
|
|
// Serial.print("pop pc instr ");Serial.println(inst, HEX);
|
|
// Serial.print("regs ");Serial.println(regs);
|
|
// Serial.print("pop pc at ");Serial.println(memory[regs], HEX);
|
|
return (void*)memory[regs];
|
|
}
|
|
if (inst == 0x4770) { // bx lr
|
|
// Serial.print("bx lr=");Serial.println(save_registers.lr);
|
|
return (void*)save_registers.lr;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
void *instructionBranch(void *p, int *bx) {
|
|
*bx = 0;
|
|
uint16_t inst = *(uint16_t*)p;
|
|
|
|
// BX or BLX
|
|
if ((inst & 0xFF00) == 0x4700) {
|
|
int reg = (inst >> 3) & 0b1111;
|
|
uint32_t addr = getRegisterNum(reg);
|
|
*bx = 1;
|
|
// Serial.print("BX ");Serial.println(addr, HEX);
|
|
return (void*)(addr);
|
|
}
|
|
// B
|
|
if ((inst & 0xF800) == 0xC000) {
|
|
int16_t offset = inst & 0x7F;
|
|
if (offset & 0x400) { // sign extend negative number
|
|
offset |= 0xF800;
|
|
}
|
|
// Serial.print("B ");Serial.println(save_registers.pc + offset, HEX);
|
|
return (void*)(save_registers.pc + offset);
|
|
}
|
|
// BL prefix
|
|
else if ((inst & 0xF800) == 0xF000) {
|
|
int offset1 = inst & 0x3F;
|
|
uint16_t inst2 = ((uint16_t*)p)[1];
|
|
int offset2 = inst2 & 0x7FF;
|
|
int32_t offset = (offset1 << 11) + offset2;
|
|
if (offset & 0x10000) {
|
|
offset |= 0xFFFE0000;
|
|
}
|
|
offset <<= 1;
|
|
// Serial.print("BL prefix ");Serial.println(save_registers.pc + offset + 4, HEX);
|
|
return (void*)(save_registers.pc + offset + 4);
|
|
}
|
|
// B conditional
|
|
else if ((inst & 0xF000) == 0xD000) {
|
|
int8_t offset = inst & 0xFF;
|
|
// Serial.print("B cond ");Serial.println(save_registers.pc + offset, HEX);
|
|
return (void*)(save_registers.pc + offset);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* @brief Default debug callback
|
|
*
|
|
*/
|
|
void debug_action() {
|
|
Serial.println("****DEBUG");
|
|
print_registers();
|
|
Serial.println("****");
|
|
}
|
|
|
|
// Saved address to restore original breakpoint
|
|
uint32_t debugreset = 0;
|
|
uint32_t temp_breakpoint = 0;
|
|
uint32_t temp_breakpoint2 = 0;
|
|
|
|
void setBreakPointNext(uint32_t breakaddr, uint32_t nextaddr) {
|
|
void *ret = instructionReturn((void*)breakaddr);
|
|
// is this a return of some sort?
|
|
if (ret) {
|
|
temp_breakpoint = (uint32_t)ret;
|
|
// Serial.print("return to ");Serial.println(temp_breakpoint, HEX);
|
|
}
|
|
else {
|
|
int bx;
|
|
void *b = instructionBranch((void*)breakaddr, &bx);
|
|
if (b) {
|
|
temp_breakpoint2 = (uint32_t)b;
|
|
// Serial.print("branch to ");Serial.println(temp_breakpoint2, HEX);
|
|
debug_setBreakpoint((void*)temp_breakpoint2, 0);
|
|
}
|
|
// is 32 bits wide?
|
|
if (instructionWidth((void*)breakaddr) == 2) {
|
|
// Serial.print("32-bit instruction at ");Serial.println(breakaddr, HEX);
|
|
temp_breakpoint = nextaddr + 2;
|
|
}
|
|
else {
|
|
temp_breakpoint = nextaddr;
|
|
}
|
|
}
|
|
debug_setBreakpoint((void*)temp_breakpoint, 0);
|
|
}
|
|
|
|
/**
|
|
* @brief Called by software interrupt to perform breakpoint manipulation
|
|
* during execution and to call the callback.
|
|
*
|
|
*/
|
|
void debug_monitor() {
|
|
uint32_t nextaddr = save_registers.pc;
|
|
uint32_t breakaddr = save_registers.pc - 2;
|
|
|
|
// Serial.print("break at ");Serial.println(breakaddr, HEX);
|
|
// print_registers();
|
|
|
|
if (debug_isHardcoded((void*)breakaddr)) {
|
|
// do nothing for hardcoded interrupts; we continue on next instruction
|
|
}
|
|
// else if (debug_id == 3) { // break cause by Ctrl-C
|
|
// // Serial.print("manual breakpoint at ");Serial.println(breakaddr, HEX);
|
|
// // do nothing because we continue on to next instruction
|
|
// }
|
|
// regular breakpoint
|
|
else if (debug_id == 0) {
|
|
save_registers.pc = breakaddr; // gdb expects address at breakpoint in pc
|
|
// set to rerun current instruction
|
|
stack->pc = breakaddr;
|
|
|
|
// gdb will clear the current breakpoint but we do this first so disassembly holds
|
|
// original code
|
|
debug_clearBreakpoint((void*)breakaddr, 1);
|
|
// clear the temporary breakpoints
|
|
if (temp_breakpoint) {
|
|
debug_clearBreakpoint((void*)temp_breakpoint, 0);
|
|
temp_breakpoint = 0;
|
|
}
|
|
if (temp_breakpoint2) {
|
|
debug_clearBreakpoint((void*)temp_breakpoint2, 2);
|
|
temp_breakpoint2 = 0;
|
|
}
|
|
}
|
|
|
|
// Adjust original SP to before the interrupt call to remove ISR's stack entries
|
|
// so GDB has correct stack. The actual stack pointer will get restored
|
|
// to this value when the interrupt returns.
|
|
save_registers.sp += ISR_STACK_SIZE;
|
|
|
|
if (callback) {
|
|
callback();
|
|
}
|
|
else {
|
|
debug_action();
|
|
}
|
|
|
|
debug_id = 0;
|
|
|
|
if (debugstep) {
|
|
// break at next instruction
|
|
setBreakPointNext(breakaddr, nextaddr);
|
|
debugstep = 0;
|
|
// the original breakpoint needs to be put back after next break
|
|
// debugreset = breakaddr;
|
|
}
|
|
|
|
// if we need to reset the original, do so
|
|
if (debugreset) {
|
|
debug_setBreakpoint((void*)debugreset, 1);
|
|
debugreset = 0;
|
|
}
|
|
}
|
|
|
|
#define SAVE_STACK \
|
|
"ldr r0, =stack \n" \
|
|
"str sp, [r0] \n"
|
|
|
|
// Save registers within an interrupt. Changes R0 register
|
|
#define SAVE_REGISTERS \
|
|
"ldr r0, =stack \n" \
|
|
"ldr r1, [r0] \n " \
|
|
"ldr r0, =save_registers \n" \
|
|
"ldr r2, [r1, #0] \n" \
|
|
"str r2, [r0, #0] \n" \
|
|
"ldr r2, [r1, #4] \n" \
|
|
"str r2, [r0, #4] \n" \
|
|
"ldr r2, [r1, #8] \n" \
|
|
"str r2, [r0, #8] \n" \
|
|
"ldr r2, [r1, #12] \n" \
|
|
"str r2, [r0, #12] \n" \
|
|
"ldr r2, [r1, #16] \n" \
|
|
"str r2, [r0, #16] \n" \
|
|
\
|
|
"ldr r2, [r1, #20] \n" \
|
|
"str r2, [r0, #20] \n" \
|
|
"ldr r2, [r1, #24] \n" \
|
|
"str r2, [r0, #24] \n" \
|
|
"ldr r2, [r1, #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 r1, [r0, #64] \n"
|
|
|
|
#define RESTORE_REGISTERS \
|
|
"ldr r0, =stack \n" \
|
|
"ldr r1, [r0] \n " \
|
|
"ldr r0, =save_registers \n" \
|
|
"ldr r2, [r0, #0] \n" \
|
|
"str r2, [r1, #0] \n" \
|
|
"ldr r2, [r0, #4] \n" \
|
|
"str r2, [r1, #4] \n" \
|
|
"ldr r2, [r0, #8] \n" \
|
|
"str r2, [r1, #8] \n" \
|
|
"ldr r2, [r0, #12] \n" \
|
|
"str r2, [r1, #12] \n" \
|
|
"ldr r2, [r0, #16] \n" \
|
|
"str r2, [r1, #16] \n" \
|
|
\
|
|
"ldr r2, [r0, #20] \n" \
|
|
"str r2, [r1, #20] \n" \
|
|
"ldr r2, [r0, #24] \n" \
|
|
"str r2, [r1, #24] \n" \
|
|
"ldr r2, [r0, #28] \n" \
|
|
"str r2, [r1, #28] \n" \
|
|
\
|
|
"ldr r4, [r0, #32] \n" \
|
|
"ldr r5, [r0, #36] \n" \
|
|
"ldr r6, [r0, #40] \n" \
|
|
"ldr r7, [r0, #44] \n" \
|
|
"ldr r8, [r0, #48] \n" \
|
|
"ldr r9, [r0, #52] \n" \
|
|
"ldr r10, [r0, #56] \n" \
|
|
"ldr r11, [r0, #60] \n"
|
|
|
|
#pragma GCC push_options
|
|
#pragma GCC optimize ("O0")
|
|
|
|
void (*original_software_isr)() = NULL;
|
|
void (*original_svc_isr)() = NULL;
|
|
|
|
/**
|
|
* @brief Called by software interrupt. Perform chaining or
|
|
* call handler.
|
|
*
|
|
*/
|
|
__attribute__((noinline, naked))
|
|
void debug_call_isr() {
|
|
__disable_irq();
|
|
asm volatile(SAVE_STACK);
|
|
asm volatile(SAVE_REGISTERS);
|
|
__enable_irq();
|
|
asm volatile("push {lr}");
|
|
NVIC_CLEAR_PENDING(IRQ_SOFTWARE);
|
|
|
|
// Are we in debug mode? If not, just jump to original ISR
|
|
if (debugenabled == 0) {
|
|
#if 0
|
|
if (original_software_isr) {
|
|
// asm volatile("ldr r0, =original_software_isr");
|
|
// asm volatile("ldr r0, [r0]");
|
|
// asm volatile("mov pc, r0");
|
|
asm volatile("pop {lr}");
|
|
asm volatile("mov pc, %0" : : "r" (original_software_isr));
|
|
}
|
|
#endif
|
|
return;
|
|
}
|
|
|
|
if (debugenabled == 2) { // halt permenantly
|
|
while(1) { yield(); }
|
|
}
|
|
|
|
debug_monitor(); // process the debug event
|
|
debugenabled = 0;
|
|
// Serial.print("restore regs=");Serial.println(debugrestore);
|
|
|
|
if (debugrestore) {
|
|
debugrestore = 0;
|
|
asm volatile("pop {r12}");
|
|
__disable_irq();
|
|
asm volatile(RESTORE_REGISTERS);
|
|
__enable_irq();
|
|
asm volatile("mov lr, r12");
|
|
asm volatile("bx lr");
|
|
}
|
|
else {
|
|
asm volatile("pop {pc}");
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @brief Called by SVC ISR to trigger software interrupt
|
|
*
|
|
*/
|
|
void debug_call_isr_setup() {
|
|
debugcount++;
|
|
debugenabled = 1;
|
|
// process in lower priority so services can keep running
|
|
NVIC_SET_PENDING(IRQ_SOFTWARE);
|
|
}
|
|
|
|
#if 1
|
|
uint32_t lastpc;
|
|
|
|
int testOurSVC() {
|
|
uint16_t *memory = (uint16_t*)(lastpc);
|
|
if (((*memory) & 0xFFF0) == 0xdf10 || debug_isBreakpoint(memory)) {
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
#endif
|
|
|
|
/**
|
|
* @brief SVC handler. Save registers and handle breakpoint.
|
|
*
|
|
*/
|
|
__attribute__((noinline, naked))
|
|
void svcall_isr() {
|
|
#if 1
|
|
// get the PC that triggered this
|
|
// subtract width of svc instruction (which is 2)
|
|
// is it one of our svcs?
|
|
asm volatile(
|
|
"ldr r0, [sp, #24] \n"
|
|
"sub r0, #2 \n"
|
|
"ldr r1, =lastpc \n"
|
|
"str r0, [r1] \n"
|
|
"push {lr}"
|
|
);
|
|
if (testOurSVC()) {
|
|
debug_call_isr_setup();
|
|
asm volatile("pop {pc}");
|
|
}
|
|
else {
|
|
if (original_svc_isr) {
|
|
asm volatile("pop {lr}");
|
|
asm volatile("mov pc, %0" : : "r" (original_svc_isr));
|
|
}
|
|
asm volatile("pop {pc}");
|
|
}
|
|
#else
|
|
asm volatile("push {lr}");
|
|
debug_call_isr_setup();
|
|
asm volatile("pop {pc}");
|
|
#endif
|
|
}
|
|
|
|
/**
|
|
* @brief Table used by FP_MAP to map memory to breakpoints
|
|
*
|
|
*/
|
|
__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
|
|
|
|
/**
|
|
* @brief Return register value for a text representation.
|
|
*
|
|
* @param reg Text representation 'r0', 'r1', etc.
|
|
* @return uint32_t Value of register
|
|
*/
|
|
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[2]) {
|
|
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;
|
|
}
|
|
|
|
int debug_setRegister(const char *reg, uint32_t value) {
|
|
debugrestore = 1;
|
|
if (reg[0] == 'r') {
|
|
if (reg[2] == 0) { // r0-r9
|
|
switch(reg[1]) {
|
|
case '0': save_registers.r0 = value; return 1;
|
|
case '1': save_registers.r1 = value; return 1;
|
|
case '2': save_registers.r2 = value; return 1;
|
|
case '3': save_registers.r3 = value; return 1;
|
|
case '4': save_registers.r4 = value; return 1;
|
|
case '5': save_registers.r5 = value; return 1;
|
|
case '6': save_registers.r6 = value; return 1;
|
|
case '7': save_registers.r7 = value; return 1;
|
|
case '8': save_registers.r8 = value; return 1;
|
|
case '9': save_registers.r9 = value; return 1;
|
|
default: return 0;
|
|
}
|
|
}
|
|
else if (reg[1] == '1') { // r10-r12
|
|
switch(reg[2]) {
|
|
case '0': save_registers.r10 = value; return 1;
|
|
case '1': save_registers.r11 = value; return 1;
|
|
case '2': save_registers.r12 = value; return 1;
|
|
default: return 0;
|
|
}
|
|
}
|
|
}
|
|
else if (strcmp(reg, "lr")==0) save_registers.lr = value;
|
|
else if (strcmp(reg, "pc")==0) save_registers.pc = value;
|
|
else if (strcmp(reg, "sp")==0) save_registers.sp = value;
|
|
else if (strcmp(reg, "cpsr")==0) save_registers.xPSR = value;
|
|
else {
|
|
return 0;
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
/**
|
|
* @brief TODO, flag to restore registers on return
|
|
*
|
|
* @return int 0 = success
|
|
*/
|
|
int debug_restoreRunMode() {
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* 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;
|
|
|
|
/**
|
|
* @brief Default handler for faults
|
|
*
|
|
* @param n
|
|
*/
|
|
void hard_fault_debug(int n) {
|
|
// if (debug_crash) flash_blink(n);
|
|
Serial.print("****FAULT ");
|
|
Serial.println(hard_fault_debug_text[n]);
|
|
Serial.print("r0=");Serial.println(stack->r0, HEX);
|
|
Serial.print("r1=");Serial.println(stack->r1, HEX);
|
|
Serial.print("r2=");Serial.println(stack->r2, HEX);
|
|
Serial.print("r3=");Serial.println(stack->r3, HEX);
|
|
Serial.print("r12=");Serial.println(stack->r12, HEX);
|
|
Serial.print("lr=0x");Serial.println(stack->lr, HEX);
|
|
Serial.print("pc=0x");Serial.println(stack->pc, HEX);
|
|
Serial.println("****************");
|
|
debug_crash = 1;
|
|
stack->pc += 2; // if we continue, skip faulty instruction
|
|
}
|
|
|
|
extern "C"
|
|
__attribute__((noinline, naked))
|
|
void fault_halt() {
|
|
while(1) { asm volatile("wfi"); }
|
|
}
|
|
|
|
// uint32_t hard_fault_debug_addr = (uint32_t)hard_fault_debug;
|
|
|
|
#pragma GCC push_options
|
|
#pragma GCC optimize ("O0")
|
|
|
|
// Save registers during fault and call default handler
|
|
#define xfault_isr_stack(fault) \
|
|
asm volatile("ldr r0, =stack \n str sp, [r0]"); \
|
|
asm volatile("push {lr}"); \
|
|
hard_fault_debug(fault); \
|
|
asm volatile("pop {pc}")
|
|
|
|
#define fault_isr_stack(fault) \
|
|
asm volatile(SAVE_STACK); \
|
|
asm volatile(SAVE_REGISTERS); \
|
|
asm volatile("push {lr}"); \
|
|
debug_crash = 1; \
|
|
debug_id = fault; \
|
|
debug_call_isr_setup(); \
|
|
asm volatile("pop {pc}")
|
|
|
|
/**
|
|
* @brief Trap faults
|
|
*
|
|
*/
|
|
__attribute__((noinline, naked)) void call_nmi_isr(void) { fault_isr_stack(2); }
|
|
__attribute__((noinline, naked)) void call_hard_fault_isr(void) { fault_isr_stack(3); }
|
|
__attribute__((noinline, naked)) void call_memmanage_fault_isr(void) { fault_isr_stack(4); }
|
|
__attribute__((noinline, naked)) void call_bus_fault_isr(void) { fault_isr_stack(5); }
|
|
__attribute__((noinline, naked)) void call_usage_fault_isr(void) { fault_isr_stack(6); }
|
|
|
|
#pragma GCC pop_options
|
|
|
|
/**
|
|
* 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;
|
|
|
|
#ifdef __IMXRT1062__
|
|
extern "C" void unused_interrupt_vector(void);
|
|
#else
|
|
extern "C" void unused_isr(void);
|
|
#endif
|
|
|
|
/**
|
|
* @brief Initialize debugging system.
|
|
*
|
|
*/
|
|
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
|
|
|
|
debug_trace = 1;
|
|
|
|
_VectorsRam[2] = call_nmi_isr;
|
|
_VectorsRam[3] = call_hard_fault_isr;
|
|
_VectorsRam[4] = call_memmanage_fault_isr;
|
|
_VectorsRam[5] = call_bus_fault_isr;
|
|
_VectorsRam[6] = call_usage_fault_isr;
|
|
|
|
#ifdef __IMXRT1062__
|
|
if (_VectorsRam[11] == unused_interrupt_vector) {
|
|
#else
|
|
if (_VectorsRam[11] == unused_isr) {
|
|
#endif
|
|
original_svc_isr = 0;
|
|
}
|
|
else {
|
|
original_svc_isr = _VectorsRam[11];
|
|
}
|
|
_VectorsRam[11] = svcall_isr;
|
|
|
|
// chain 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();
|
|
}
|
|
|
|
void gdb_init(Stream *device);
|
|
|
|
/**
|
|
* @brief Initialize both debugger and GDB
|
|
*
|
|
* @param device Device to use; inherits from Stream; if NULL use Serial
|
|
* @return int
|
|
*/
|
|
int debug_begin(Stream *device) {
|
|
debug_init(); // perform debug initialization
|
|
gdb_init(device);
|
|
return 1;
|
|
}
|
|
|
|
#ifdef REMAP_SETUP
|
|
|
|
// We will rename the original setup() to this by using a #define
|
|
void setup_main();
|
|
|
|
void debug_setup_auto() {
|
|
// delay(3000);
|
|
// Serial.println("Auto setup debug serial");
|
|
// delay(3000);
|
|
// return;
|
|
#if defined(GDB_DUAL_SERIAL)
|
|
debug_begin(&SerialUSB1);
|
|
#elif defined(GDB_TAKE_OVER_SERIAL)
|
|
debug_begin(&Serial);
|
|
#else
|
|
debug_begin(NULL);
|
|
#endif
|
|
}
|
|
|
|
/*
|
|
* 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 {r0-r4,lr}"); // save the return address
|
|
debug_setup_auto();
|
|
setup_main(); // call the "real" setup function
|
|
asm volatile("pop {r0-r4,pc}"); // get original return address
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
/**
|
|
* Class
|
|
*/
|
|
|
|
int Debug::begin(Stream *device) { return debug_begin(device); }
|
|
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); }
|
|
int Debug::setRegister(const char *reg, uint32_t value) { return debug_setRegister(reg, value); }
|
|
// int Debug::restoreRunMode() { return debug_restoreRunMode(); }
|
|
|
|
Debug debug;
|
|
|