Browse Source

Handle branches during stepping

pull/2/head
Fernando Trias 4 years ago
parent
commit
4009773b31
  1. 6
      README.md
  2. 78
      TeensyDebug.cpp
  3. 16
      TeensyDebug.h
  4. 6
      gdbstub.cpp
  5. 9
      notes.md

6
README.md

@ -186,7 +186,11 @@ TODO / Future considerations
Bugs
-------------------------------------------
1. `step` and `next` don't work completely. `step` will not step into functions. Both won't work over a return. TeensyDebug traps `bx lr`, `pop {Rmmm, pc}`, `mov pc, Rm` and will actually step properly over these instruction if using gdb `stepi` command. However gdb `step` and `next` get confused and don't stop stepping once the function returns.
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` will not step into functions. Stepping won't work over a return. TeensyDebug traps `bx lr`, `pop {Rmmm, pc}`, `mov pc, Rm` and will actually step properly over these instruction if using gdb `stepi` command. However gdb `step` and `next` get confused and don't stop stepping once the function returns.
3. Stepping fails over branches. To fix, this will have to implement decoding of branches, which can be a bit complicated.
Future considerations
-------------------------------------------

78
TeensyDebug.cpp

@ -66,21 +66,6 @@ https://web.eecs.umich.edu/~prabal/teaching/eecs373-f10/readings/ARMv7-M_ARM.pdf
#define FP_LAR (*(unsigned int*) 0xE0000FB0)
#define FP_LSR (*(unsigned int*) 0xE0000FB4)
//
// Need to know where RAM starts/stops so we know where
// software breakpoints are possible
//
#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
#define ADDRESS_MASK 0xFFFFFFFE
#define ADDRESS_MASK2 0xFFFFFFFC
@ -495,6 +480,37 @@ void *instructionReturn(void *p) {
return 0;
}
void *instructionBranch(void *p) {
uint16_t inst = *(uint16_t*)p;
// B conditional
if ((inst & 0xF000) == 0xD000) {
int8_t offset = inst & 0xFF;
return (void*)(save_registers.pc + offset);
}
// B
else if ((inst & 0xF800) == 0xC000) {
int16_t offset = inst & 0x7F;
if (offset & 0x400) { // sign extend negative number
offset |= 0xF800;
}
return (void*)(save_registers.pc + offset);
}
// BL or BLX 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("offset ");Serial.println(offset, HEX);
return (void*)(save_registers.pc + offset + 2);
}
return 0;
}
/**
* @brief Default debug callback
*
@ -508,7 +524,7 @@ void debug_action() {
// Saved address to restore original breakpoint
uint32_t debugreset = 0;
uint32_t temp_breakpoint = 0;
// uint32_t caller_breakpoint = 0;
uint32_t temp_breakpoint2 = 0;
/**
* @brief Called by software interrupt to perform breakpoint manipulation
@ -518,7 +534,7 @@ uint32_t temp_breakpoint = 0;
void debug_monitor() {
uint32_t breakaddr = save_registers.pc - 2;
// Serial.print("break at ");Serial.println(breakaddr, HEX);
Serial.print("break at ");Serial.println(breakaddr, HEX);
// is this the first breakpoint or are we in a sequence?
if (debugactive == 0) {
@ -546,8 +562,6 @@ void debug_monitor() {
// break at next instruction
temp_breakpoint = save_registers.pc;
debug_setBreakpoint((void*)(temp_breakpoint), 0);
// caller_breakpoint = save_registers.lr;
// debug_setBreakpoint((void*)caller_breakpoint, 2);
// set to rerun current instruction
stack->pc = breakaddr;
// we need to process the next breakpoint differently
@ -559,9 +573,10 @@ void debug_monitor() {
else {
// clear the temporary breakpoint
debug_clearBreakpoint((void*)temp_breakpoint, 0);
// if (caller_breakpoint) {
// debug_clearBreakpoint((void*)caller_breakpoint, 2);
// }
if (temp_breakpoint2) {
debug_clearBreakpoint((void*)temp_breakpoint2, 2);
temp_breakpoint2 = 0;
}
// reset to re-run the instruction
stack->pc = breakaddr;
@ -589,24 +604,23 @@ void debug_monitor() {
temp_breakpoint = (uint32_t)ret;
// Serial.print("return to ");Serial.println(temp_breakpoint, HEX);
}
else {
void *b = instructionBranch((void*)breakaddr);
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?
else if (instructionWidth((void*)breakaddr) == 2) {
if (instructionWidth((void*)breakaddr) == 2) {
// Serial.print("32-bit instruction at ");Serial.println(breakaddr, HEX);
temp_breakpoint = save_registers.pc + 2;
}
else {
temp_breakpoint = save_registers.pc;
}
}
debug_setBreakpoint((void*)temp_breakpoint, 0);
// caller_breakpoint = save_registers.lr & ADDRESS_MASK;
// if (caller_breakpoint == breakaddr) {
// // we just returned from a call, LR has not been restored, so this
// // LR is invalid.
// caller_breakpoint = 0;
// }
// else {
// debug_setBreakpoint((void*)caller_breakpoint, 2);
// }
}
else {
// we're not stepping so reset mode

16
TeensyDebug.h

@ -18,6 +18,22 @@
#define HAS_FP_MAP
#endif
//
// Need to know where RAM starts/stops so we know where
// software breakpoints are possible
//
#ifdef __MK20DX256__
#define RAM_START ((void*)0x1FFF8000)
#define RAM_END ((void*)0x2FFFFFFF)
#endif
#ifdef __IMXRT1062__
#define RAM_START ((void*)0x00000020)
#define RAM_END ((void*)0x5FFFFFFF)
#endif
#if defined(GDB_DUAL_SERIAL) && ! defined(CDC2_DATA_INTERFACE)
#error "You must use Dual Serial or Triple Serial to enable GDB on Dual Serial."
#endif

6
gdbstub.cpp

@ -343,10 +343,10 @@ int process_G(const char *cmd, char *result) {
* @return int 1 = valid; 0 = invalid
*/
int isValidAddress(uint32_t addr) {
if (addr == 0) {
if (addr <= 0x20) {
return 0;
}
if (addr > 0xF0000000) {
else if (addr > 0xF0000000) {
return 0;
}
return 1;
@ -743,7 +743,7 @@ void processGDBinput() {
}
*pcmd = 0;
// Serial.print("got command:");Serial.println(cmd);
Serial.print("got command:");Serial.println(cmd);
c = getDebugChar();
checksum = hex(c) << 4;

9
notes.md

@ -1,23 +1,20 @@
20200612
Pulled together some codethat allows GDB to perform source-level debugging on the Teensy without an external debug interface (no need for SWD, etc). It uses GDB's Remote Serial Protocol to communicate with Teensy over a Serial connection (hardware or USB). This is a beta release for comments and testing. It works on Teensy 4 and 3.2. Tested on Mac. Highlights:
I put together some code that allows GDB to perform source-level debugging on the Teensy without an external debug interface (no need for SWD, etc). It uses GDB's Remote Serial Protocol to communicate with Teensy over a Serial connection (hardware or USB). This is a beta release for comments and testing. It works (mostly) on Teensy 4 and 3.2. Tested on Mac. Highlights:
* Set/remove breakpoints on most places in your code
* Examine and change memory and registers
* View call stack
* Halt code at any time
* Step with next/step working minimally, but not across functions.
In the past, doing this has been very challenging because the debugging features of the Teensy are permanently disabled (see https://forum.pjrc.com/threads/26358-Software-Debugger-Stack and https://forum.pjrc.com/threads/61262-Sleeping-to-disable-C_DEBUGEN?p=242721#post242721). But I'm using a trick that doesn't involve the debugging features.
In the past, debugging like this has been very challenging because the debugging features of the Teensy are permanently disabled (see https://forum.pjrc.com/threads/26358-Software-Debugger-Stack and https://forum.pjrc.com/threads/61262-Sleeping-to-disable-C_DEBUGEN?p=242721#post242721). But I'm using a trick that doesn't involve the debugging features.
To emulate breakpoints the library first takes over the SVC interrupt. The Teensy 4 places most code in RAM so to activate a breakpoint it just replaces the original instructions with SVC calls. On Teensy 3.2, setting breakpoints uses the Cortex-M4's features to "patch" parts of flash by pointing it to RAM.
The library is enabled by including the header. On Macs, there is a python script that adds a menu option and configures Arduino to open GDB automatically after uploading your program. On other platforms, you run GDB manually and use `target remote` to connect to the serial port used by the GDB interface. However, the python script is simple and could easily be ported to Windows (or rewritten in C).
```C
#include "TeensyDebug.h"
```
To read more and try it out visit: http://github.com/ftrias/TeensyDebug
20200609

Loading…
Cancel
Save