v0.2: prev snapshot of regs, refactored state/prev to share build_register_view
This commit is contained in:
@@ -218,60 +218,15 @@ fn install_globals<'js>(
|
|||||||
|
|
||||||
// ---------- state ----------
|
// ---------- state ----------
|
||||||
{
|
{
|
||||||
let state_obj = Object::new(c.clone())?;
|
let target_for_state = target.clone();
|
||||||
|
let state_obj = build_register_view(
|
||||||
// state.pc — accessor returning BigInt
|
&c,
|
||||||
{
|
move |c| -> rquickjs::Result<libc::user_regs_struct> {
|
||||||
let target = target.clone();
|
let slot = target_for_state.borrow();
|
||||||
state_obj.prop(
|
let t = slot.as_ref().ok_or_else(|| throw(c, "no target attached"))?;
|
||||||
"pc",
|
t.regs().map_err(|e| throw(c, &e))
|
||||||
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())
|
|
||||||
}),
|
|
||||||
)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
// state.memory(addr) → object with u8/u32/u64/i64/bytes
|
// state.memory(addr) → object with u8/u32/u64/i64/bytes
|
||||||
{
|
{
|
||||||
@@ -346,6 +301,23 @@ fn install_globals<'js>(
|
|||||||
globals.set("state", state_obj)?;
|
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 ---------------------------------------------
|
// ---------- JS-side prelude ---------------------------------------------
|
||||||
// Loaded last, so it can reference any of the native globals above.
|
// Loaded last, so it can reference any of the native globals above.
|
||||||
// Lives in src/prelude.js, embedded at compile time.
|
// Lives in src/prelude.js, embedded at compile time.
|
||||||
@@ -357,6 +329,60 @@ fn install_globals<'js>(
|
|||||||
|
|
||||||
// --- helpers --------------------------------------------------------------
|
// --- 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
|
/// 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
|
/// in rquickjs for "throw an error from a Rust callback" is to call
|
||||||
/// `Exception::throw_*(ctx, msg)`.
|
/// `Exception::throw_*(ctx, msg)`.
|
||||||
|
|||||||
@@ -52,6 +52,9 @@ pub enum State {
|
|||||||
pub struct Target {
|
pub struct Target {
|
||||||
pid: Pid,
|
pid: Pid,
|
||||||
state: State,
|
state: State,
|
||||||
|
/// Snapshot of registers from the previous stop. None until at least one
|
||||||
|
/// step/cont has happened after spawn.
|
||||||
|
prev_regs: Option<libc::user_regs_struct>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Target {
|
impl Target {
|
||||||
@@ -102,6 +105,7 @@ impl Target {
|
|||||||
WaitStatus::Stopped(_, Signal::SIGTRAP) => Ok(Target {
|
WaitStatus::Stopped(_, Signal::SIGTRAP) => Ok(Target {
|
||||||
pid: child,
|
pid: child,
|
||||||
state: State::Stopped(StopReason::Step),
|
state: State::Stopped(StopReason::Step),
|
||||||
|
prev_regs: None,
|
||||||
}),
|
}),
|
||||||
WaitStatus::Exited(_, code) => {
|
WaitStatus::Exited(_, code) => {
|
||||||
Err(format!("child exited immediately with code {} (exec failed?)", code))
|
Err(format!("child exited immediately with code {} (exec failed?)", code))
|
||||||
@@ -117,6 +121,8 @@ impl Target {
|
|||||||
if !self.is_stopped() {
|
if !self.is_stopped() {
|
||||||
return Err("target is not stopped".into());
|
return Err("target is not stopped".into());
|
||||||
}
|
}
|
||||||
|
// Snapshot regs before resuming so `prev` reflects the pre-step state.
|
||||||
|
self.prev_regs = ptrace::getregs(self.pid).ok();
|
||||||
ptrace::step(self.pid, None).map_err(|e| format!("ptrace step: {}", e))?;
|
ptrace::step(self.pid, None).map_err(|e| format!("ptrace step: {}", e))?;
|
||||||
let reason = self.wait_for_stop()?;
|
let reason = self.wait_for_stop()?;
|
||||||
Ok(reason)
|
Ok(reason)
|
||||||
@@ -127,11 +133,16 @@ impl Target {
|
|||||||
if !self.is_stopped() {
|
if !self.is_stopped() {
|
||||||
return Err("target is not stopped".into());
|
return Err("target is not stopped".into());
|
||||||
}
|
}
|
||||||
|
self.prev_regs = ptrace::getregs(self.pid).ok();
|
||||||
ptrace::cont(self.pid, None).map_err(|e| format!("ptrace cont: {}", e))?;
|
ptrace::cont(self.pid, None).map_err(|e| format!("ptrace cont: {}", e))?;
|
||||||
let reason = self.wait_for_stop()?;
|
let reason = self.wait_for_stop()?;
|
||||||
Ok(reason)
|
Ok(reason)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn prev_regs(&self) -> Option<libc::user_regs_struct> {
|
||||||
|
self.prev_regs
|
||||||
|
}
|
||||||
|
|
||||||
fn wait_for_stop(&mut self) -> Result<StopReason, String> {
|
fn wait_for_stop(&mut self) -> Result<StopReason, String> {
|
||||||
let status = waitpid(self.pid, None).map_err(|e| format!("waitpid: {}", e))?;
|
let status = waitpid(self.pid, None).map_err(|e| format!("waitpid: {}", e))?;
|
||||||
let reason = match status {
|
let reason = match status {
|
||||||
|
|||||||
Reference in New Issue
Block a user