forked from microsoft/pxt
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
starting on-chip software debugger (microsoft#2057)
This implements a debugger interface over HID running on interrupts in user space. It can step through the code and show simple values. It also has the following fixes: * only build natively tsprj/blockprj, not all the libraries * fixes in re-connecting to HID * support for events sent over HID
- Loading branch information
Showing
30 changed files
with
908 additions
and
654 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
import * as hid from './hid'; | ||
import * as fs from "fs"; | ||
|
||
import Cloud = pxt.Cloud; | ||
import U = pxt.Util; | ||
import D = pxt.HWDBG; | ||
|
||
|
||
export function startAsync(compileRes: pxtc.CompileResult) { | ||
return hid.initAsync() | ||
.then(d => { | ||
hid.connectSerial(d) | ||
|
||
D.postMessage = msg => { | ||
if (msg.subtype != "breakpoint") { | ||
console.log(msg) | ||
return | ||
} | ||
let bmsg = msg as pxsim.DebuggerBreakpointMessage | ||
|
||
console.log("GLOBALS", bmsg.globals) | ||
for (let s of bmsg.stackframes) | ||
console.log(s.funcInfo.functionName, s.locals) | ||
|
||
let brkMatch = compileRes.breakpoints.filter(b => b.id == bmsg.breakpointId)[0] | ||
if (!brkMatch) { | ||
console.log("Invalid breakpoint ID", msg) | ||
return | ||
} | ||
let lines = fs.readFileSync(brkMatch.fileName, "utf8").split(/\n/) | ||
|
||
console.log(">>>", lines.slice(brkMatch.line, brkMatch.endLine + 1).join(" ;; ")) | ||
Promise.delay(500) | ||
.then(() => D.resumeAsync(false)) | ||
} | ||
|
||
return D.startDebugAsync(compileRes, d) | ||
}) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,104 @@ | ||
# On-device Software Debugger | ||
|
||
PXT supports debugging the generated TypeScript code. This is done via | ||
a USB HID interface implemented in software and doesn't require an interface | ||
(debugging) chip. The USB HID interface uses | ||
[HF2 protocol](https://github.com/Microsoft/uf2/blob/master/hf2.md). | ||
Messages from the host (computer connected to the device being debugged) | ||
are handled in the USB interrupt of the device. | ||
|
||
Debugging needs to be enabled during compilation. The switch will cause breakpoint | ||
code to be inserted before every statement. It's possible to enable debug for all | ||
TypeScript code, or only user code (i.e, excluding everything under `pxt_modules/**`). | ||
|
||
## Debugger features (TODO) | ||
|
||
* [x] inspecting globals (including `uint16` and friends) | ||
* [x] inspecting tagged values, doubles, strings, buffers | ||
* [x] `debugger` statement support | ||
* [x] step-into support | ||
* [x] step-over support | ||
* [x] continue support | ||
* [x] inspecting locals in current function, and up the call stack | ||
|
||
* [ ] stack-walking with first-class functions | ||
* [ ] inspecting contents of records | ||
* [ ] inspecting contents of arrays | ||
* [ ] inspecting contents of maps | ||
* [ ] inspecting contents of reference-locals (captured ones) | ||
* [ ] inspecting active threads | ||
* [ ] user-defined breakpoints (other than `debugger` statement) | ||
* [ ] handling of non-intentional faults (right now we get infinite loop; not sure how useful this is without GDB) | ||
|
||
## Debugger implementation | ||
|
||
The debug mode is controlled by the first global variable. In C++ code it's | ||
referred to as `globals[0]`, while in assembly it's `[r6, #0]` (`r6` holds `globals` | ||
while TypeScript code is executing). | ||
|
||
The C++ side of the code sits in `hf2.cpp` file in `core` library (in `pxt-common-packages`). | ||
Current link: https://github.com/Microsoft/pxt-common-packages/blob/dbg/libs/core/hf2.cpp#L182 | ||
|
||
There are currently 4 possible values of `globals[0]` | ||
* a valid heap address, somewhere in the middle - normal execution | ||
* `0` - debugger connected, no single stepping mode | ||
* `1` - debugger connected, step-into mode | ||
* `3` - debugger connected, step-over mode (replaced with `0` on first hit) | ||
|
||
The host will start by resetting the device into debug mode with a specific HF2 | ||
message. This causes `globals[0]` to be set to `1`. | ||
After that, the device will send a message to the host saying it's halted. | ||
Then the host will eventually set `globals[0]` to `0`, `1` or `3` and | ||
resume execution via another HF2 message. | ||
|
||
TS `debugger` statement is translated into a word fetch from `globals[0] - 4`. | ||
In any mode other than the normal execution, it will cause a fault - either alignment fault | ||
or accessing memory at `0xfffffffc`. | ||
|
||
``` | ||
ldr r0, [r6, #0] | ||
subs r0, r0, #4 | ||
ldr r0, [r0, #0] | ||
``` | ||
|
||
Additionally, in front of all other statements, provided debugging is enabled, | ||
we try to load memory location pointed to by `globals[0]`. If `globals[0]` | ||
is `1` or `3` (but not `0` or valid heap address), this will cause alignment fault. | ||
|
||
``` | ||
ldr r0, [r6, #0] | ||
ldr r0, [r0, #0] | ||
``` | ||
|
||
All hard faults are caught by a single handler. It then looks at | ||
the instruction causing fault and if it is `ldr r0, [r0, #0]`, | ||
it will return to a function which pauses all other user | ||
fibers and executes `fiber_sleep()` in a loop. | ||
It also sends a HF2 message to the host stating that the device | ||
if halted. At some point, a HF2 message from the host clears | ||
a flag causing the loop to stop and the device to resume execution after the | ||
`ldr r0, [r0, #0]` instruction (after unpausing all other user fibers). | ||
|
||
This is enough to support step-into and continue modes of operation. | ||
To support step-over, a instructions sequence similar to the one above | ||
is used at the entry of every procedure, before even the `push {lr}`: | ||
|
||
``` | ||
ldr r0, [r6, #0] | ||
ldr r0, [r0, #4] | ||
``` | ||
|
||
If the fault handler detects the fault instruction to be `ldr r0, [r0, #4]` | ||
and `globals[0]` is `3` it sets `globals[0]` to `0` and modifies the | ||
contents of `lr` register by setting its highest bit. In all cases, when it's done, | ||
it just resumes execution after the `ldr r0, [r0, #4]` instruction | ||
(without entering the pause loop). | ||
|
||
Thus, in step-over mode, entry to any user procedure will set `globals[0]` to | ||
`0` preventing further instruction breakpoints from firing. When this particular | ||
function tries to return, we get another fault, where the `pc` is set to | ||
an address with highest bit set. This is detected, the `globals[0]` is set | ||
back to `3`, the `pc` is corrected and execution continues. | ||
|
||
|
||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.