v0.2: prev snapshot of regs, refactored state/prev to share build_register_view

This commit is contained in:
claude
2026-05-27 21:10:23 +00:00
parent 912f5003fe
commit 19c6154831
2 changed files with 91 additions and 54 deletions
+80 -54
View File
@@ -218,60 +218,15 @@ fn install_globals<'js>(
// ---------- state ----------
{
let state_obj = Object::new(c.clone())?;
// state.pc — accessor returning BigInt
{
let target = target.clone();
state_obj.prop(
"pc",
Accessor::from(move |c: Ctx<'js>| -> rquickjs::Result<Value<'js>> {
let slot = target.borrow();
let t = slot.as_ref().ok_or_else(|| throw(&c, "no target attached"))?;
let r = t.regs().map_err(|e| throw(&c, &e))?;
u64_to_bigint(&c, r.rip)
}),
)?;
}
// state.sp
{
let target = target.clone();
state_obj.prop(
"sp",
Accessor::from(move |c: Ctx<'js>| -> rquickjs::Result<Value<'js>> {
let slot = target.borrow();
let t = slot.as_ref().ok_or_else(|| throw(&c, "no target attached"))?;
let r = t.regs().map_err(|e| throw(&c, &e))?;
u64_to_bigint(&c, r.rsp)
}),
)?;
}
// state.registers — accessor returning a fresh object each time
{
let target = target.clone();
state_obj.prop(
"registers",
Accessor::from(move |c: Ctx<'js>| -> rquickjs::Result<Value<'js>> {
let slot = target.borrow();
let t = slot.as_ref().ok_or_else(|| throw(&c, "no target attached"))?;
let r = t.regs().map_err(|e| throw(&c, &e))?;
let obj = Object::new(c.clone())?;
let set = |name: &str, val: u64| -> rquickjs::Result<()> {
obj.set(name, u64_to_bigint(&c, val)?)
};
set("rax", r.rax)?; set("rbx", r.rbx)?; set("rcx", r.rcx)?;
set("rdx", r.rdx)?; set("rsi", r.rsi)?; set("rdi", r.rdi)?;
set("rbp", r.rbp)?; set("rsp", r.rsp)?; set("rip", r.rip)?;
set("r8", r.r8)?; set("r9", r.r9)?; set("r10", r.r10)?;
set("r11", r.r11)?; set("r12", r.r12)?; set("r13", r.r13)?;
set("r14", r.r14)?; set("r15", r.r15)?;
set("eflags", r.eflags)?;
Ok(obj.into_value())
}),
)?;
}
let target_for_state = target.clone();
let state_obj = build_register_view(
&c,
move |c| -> rquickjs::Result<libc::user_regs_struct> {
let slot = target_for_state.borrow();
let t = slot.as_ref().ok_or_else(|| throw(c, "no target attached"))?;
t.regs().map_err(|e| throw(c, &e))
},
)?;
// state.memory(addr) → object with u8/u32/u64/i64/bytes
{
@@ -346,6 +301,23 @@ fn install_globals<'js>(
globals.set("state", state_obj)?;
}
// ---------- prev ----------
// Mirror of state for register-only data, backed by the pre-resume
// snapshot. Property accessors throw if no snapshot is available yet
// (i.e. immediately after spawn, before any step/cont).
{
let target_for_prev = target.clone();
let prev_obj = build_register_view(
&c,
move |c| -> rquickjs::Result<libc::user_regs_struct> {
let slot = target_for_prev.borrow();
let t = slot.as_ref().ok_or_else(|| throw(c, "no target attached"))?;
t.prev_regs().ok_or_else(|| throw(c, "no prev snapshot yet (step/cont first)"))
},
)?;
globals.set("prev", prev_obj)?;
}
// ---------- JS-side prelude ---------------------------------------------
// Loaded last, so it can reference any of the native globals above.
// Lives in src/prelude.js, embedded at compile time.
@@ -357,6 +329,60 @@ fn install_globals<'js>(
// --- helpers --------------------------------------------------------------
/// Build a JS object with `pc`, `sp`, and `registers` accessor properties,
/// each backed by a closure that fetches a `user_regs_struct` on demand.
/// Used for both `state` (live regs via ptrace) and `prev` (snapshot).
fn build_register_view<'js, F>(c: &Ctx<'js>, fetch: F) -> rquickjs::Result<Object<'js>>
where
F: Fn(&Ctx<'js>) -> rquickjs::Result<libc::user_regs_struct> + Clone + 'static,
{
let obj = Object::new(c.clone())?;
{
let fetch = fetch.clone();
obj.prop(
"pc",
Accessor::from(move |c: Ctx<'js>| -> rquickjs::Result<Value<'js>> {
let r = fetch(&c)?;
u64_to_bigint(&c, r.rip)
}),
)?;
}
{
let fetch = fetch.clone();
obj.prop(
"sp",
Accessor::from(move |c: Ctx<'js>| -> rquickjs::Result<Value<'js>> {
let r = fetch(&c)?;
u64_to_bigint(&c, r.rsp)
}),
)?;
}
{
let fetch = fetch;
obj.prop(
"registers",
Accessor::from(move |c: Ctx<'js>| -> rquickjs::Result<Value<'js>> {
let r = fetch(&c)?;
let inner = Object::new(c.clone())?;
let set = |name: &str, val: u64| -> rquickjs::Result<()> {
inner.set(name, u64_to_bigint(&c, val)?)
};
set("rax", r.rax)?; set("rbx", r.rbx)?; set("rcx", r.rcx)?;
set("rdx", r.rdx)?; set("rsi", r.rsi)?; set("rdi", r.rdi)?;
set("rbp", r.rbp)?; set("rsp", r.rsp)?; set("rip", r.rip)?;
set("r8", r.r8)?; set("r9", r.r9)?; set("r10", r.r10)?;
set("r11", r.r11)?; set("r12", r.r12)?; set("r13", r.r13)?;
set("r14", r.r14)?; set("r15", r.r15)?;
set("eflags", r.eflags)?;
Ok(inner.into_value())
}),
)?;
}
Ok(obj)
}
/// Create a JS Error and turn it into an rquickjs::Error::Exception. The idiom
/// in rquickjs for "throw an error from a Rust callback" is to call
/// `Exception::throw_*(ctx, msg)`.