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:
@@ -0,0 +1,68 @@
|
||||
// examples/smarm_ctx_switch.js
|
||||
//
|
||||
// Demonstrates the v0.7 event-listener system by inspecting smarm's naked-asm
|
||||
// context switch. smarm (https://git.kalsbeek.dev/Markk116/smarm) is a green-
|
||||
// thread actor runtime whose core is two `#[naked]` switch shims that swap rsp
|
||||
// between a scheduler stack and an actor's mmap'd stack.
|
||||
//
|
||||
// Build the probe binary (frame pointers on, single scheduler thread):
|
||||
// cd smarm
|
||||
// RUSTFLAGS="-C force-frame-pointers=yes" cargo build --example ctx_switch_probe
|
||||
//
|
||||
// Then pipe this script into llmdbg, substituting:
|
||||
// - SPAWN_PATH: absolute path to the built ctx_switch_probe-<hash> binary
|
||||
// - ACTOR_ASM: mangled name of switch_to_actor_asm (from `nm BIN | grep`)
|
||||
// - SCHED: mangled name of switch_to_scheduler
|
||||
//
|
||||
// What it shows:
|
||||
// 1. A 'breakpoint' listener that logs each switch with rsp + 16-alignment.
|
||||
// 2. A 'step' listener that single-steps the shim's tail (mov rsp,rax; the
|
||||
// six pops; ret) and proves the actor entry lands with rsp % 16 == 8 —
|
||||
// the x86-64 ABI state expected at a function entry reached via `call`.
|
||||
//
|
||||
// This was the session that caught the off-by-one-slot error in
|
||||
// init_actor_stack's layout comment (entry is at aligned_top-16, not -8).
|
||||
|
||||
spawn("SPAWN_PATH")
|
||||
|
||||
// --- (1) per-switch alignment trace via a breakpoint listener --------------
|
||||
ctx.sched = addr("SCHED")
|
||||
ctx.actor_asm = addr("ACTOR_ASM")
|
||||
|
||||
addEventListener('breakpoint', e => {
|
||||
const sp = e.state.sp;
|
||||
const where = (e.state.pc === ctx.sched) ? "to_sched" : "to_actor";
|
||||
ctx.hits = (ctx.hits ?? 0) + 1;
|
||||
print(`#${ctx.hits} ${where} rsp=${hex(sp)} rsp%16=${sp % 16n}`);
|
||||
});
|
||||
bp.set(ctx.sched, "to_sched")
|
||||
bp.set(ctx.actor_asm, "to_actor")
|
||||
---END---
|
||||
|
||||
// Drive through the first several switches. Each cont() fires the listener.
|
||||
for (let i = 0; i < 6; i++) {
|
||||
let r = cont();
|
||||
if (!r.startsWith("breakpoint")) { print("stop: " + r); break; }
|
||||
}
|
||||
---END---
|
||||
|
||||
// --- (2) prove the ret lands at rsp%16==8 in the actor entry ---------------
|
||||
// Remove the breakpoint listener; add a step listener for the tail trace.
|
||||
clearListeners('breakpoint')
|
||||
|
||||
// Reach `mov rsp,rax` (offset 0x17 in the shim) on the next switch_to_actor.
|
||||
ctx.movrsp = ctx.actor_asm + 0x17n
|
||||
bp.remove("to_sched")
|
||||
bp.remove("to_actor")
|
||||
bp.set(ctx.movrsp, "movrsp")
|
||||
cont()
|
||||
---END---
|
||||
|
||||
clearListeners()
|
||||
addEventListener('step', e => {
|
||||
const ins = e.state.disasm(1)[0];
|
||||
print(`${hex(e.state.pc)} rsp=${hex(e.state.sp)} %16=${e.state.sp % 16n} ${ins.text}`);
|
||||
});
|
||||
// mov rsp,rax ; pop r15..rbx (6) ; ret ; first insn of entry
|
||||
step(8)
|
||||
---END---
|
||||
Reference in New Issue
Block a user