v0.7: event-listener system (addEventListener/step/breakpoint/signal/exit)

- Native step/cont moved to __step_native/__cont_native; public step()/cont()
  defined in prelude.js wrap them and dispatch JS listeners on each stop.
- addEventListener/removeEventListener/clearListeners/listeners() registry on
  globalThis, persistent for the daemon lifetime (independent of ctx.reset()).
- Event object carries {state, prev, stop_reason} plus breakpoint_id/signal/
  exit_code as appropriate. Implements the SPEC 'Event Listeners' + 'Core Loop'.
- No-listener fast path keeps existing scripts zero-cost.
- examples/smarm_ctx_switch.js: worked session inspecting smarm's naked-asm
  context switch; README updated (status table, listener API, deferred list).
This commit is contained in:
llmdbg-dev
2026-05-28 05:50:09 +00:00
parent 98e3347b4c
commit ce96a98829
4 changed files with 276 additions and 10 deletions
+34 -4
View File
@@ -20,6 +20,7 @@ each step" is the natural interface. The full motivation is in `SPEC.md`.
| v0.4 | INT3 software breakpoints with by-name/by-id ops; auto disarm-step-rearm; RIP rewind on hit |
| v0.5 | x86-64 disassembly via `iced-x86`; `read_mem_pristine()` overlays original bytes through active INT3s; branch targets get symbolised |
| v0.6 | `state.stack`; `until(loc)`; `finish()`; `show.{disasm,bp,regs}` prelude helpers |
| v0.7 | Event-listener system: `addEventListener('step'\|'breakpoint'\|'signal'\|'exit', ...)`, `removeEventListener`, `clearListeners`, `listeners()`; `step`/`cont` fire listeners per stop with `{state, prev, stop_reason, ...}` events; native steppers moved to `__step_native`/`__cont_native` |
Each `vN` corresponds to one git commit. See `git log --oneline`.
@@ -27,17 +28,20 @@ Each `vN` corresponds to one git commit. See `git log --oneline`.
The spec calls for these; they're deliberately deferred:
- Event listener system (`addEventListener('step', ...)` etc.) — current model
is synchronous send-script-get-output; events come next.
- `attach(pid)` — only `spawn` works today.
- Hardware watchpoints.
- Hardware watchpoints. (The `watchpoint` event type exists in the listener
registry but never fires until hardware watchpoints land.)
- Source-level debugging via DWARF (`gimli`).
- Multi-threaded targets; shared-library symbol resolution.
- Browser visualisation / DOM API.
- Conditional breakpoints.
- Conditional breakpoints. (Now trivial to express as a guard inside a
`breakpoint` listener, but native `--condition` support is still deferred.)
- The `--summary` mode for batch stepping.
- Reverse execution.
The event-listener system (`addEventListener('step', ...)` etc.) landed in
v0.7 — see the status table.
The complete "what not yet" list is in `SPEC.md`.
## Build
@@ -174,8 +178,34 @@ finish() // until([rbp+8]) — assumes frame pointer
show.disasm(n=8, [at])
show.bp()
show.regs([keys])
// Event listeners (v0.7). Handlers fire on each stop during step()/cont().
addEventListener(type, handler) // type: 'step'|'breakpoint'|'signal'|'exit'
removeEventListener(type, handler)
clearListeners([type]) // drop one type, or all if omitted
listeners() // -> { step: n, breakpoint: n, ... } counts
```
### Event listeners
The event object passed to each handler:
```js
e.state // live state (re-reads via ptrace), same as the `state` global
e.prev // pre-resume snapshot, or null on the very first event
e.stop_reason // "step" | "breakpoint:N" | "signal:SIGSEGV" | "exited:0" | ...
e.breakpoint_id // number — only on 'breakpoint' events
e.signal // string — only on 'signal' events
e.exit_code // number — only on 'exit' events
```
`step(n)` single-steps one instruction at a time, firing listeners after each
stop; `cont()` fires after the stop it lands on. When no listeners are
registered, both take the native fast path and pay nothing. Handler exceptions
are caught and printed, so one bad listener won't abort a long step loop.
A worked example against smarm's context switch is in
`examples/smarm_ctx_switch.js`.
## Architecture
```