v0.5: x86-64 disassembly via iced-x86

- state.disasm(n=8), disasm(addr, n=8) -> [{addr, bytes, text, target?, target_sym?}]
- read_mem_pristine() overlays orig bytes through active INT3 breakpoints
- branch/call targets get symbolized when they land in known symbols
- show.disasm / show.bp / show.regs prelude helpers for ergonomic printing
This commit is contained in:
claude
2026-05-27 21:24:01 +00:00
parent 8ae6103ce7
commit 2ed17f0286
7 changed files with 217 additions and 0 deletions
+60
View File
@@ -298,9 +298,41 @@ fn install_globals<'js>(
state_obj.set("memory", mem_fn)?;
}
// state.disasm(n=8) — disassemble n instructions starting at pc.
{
let target = target.clone();
let f = Function::new(c.clone(), move |c: Ctx<'js>, n: rquickjs::function::Opt<usize>| -> rquickjs::Result<Value<'js>> {
let n = n.0.unwrap_or(8).max(1);
let slot = target.borrow();
let t = slot.as_ref().ok_or_else(|| throw(&c, "no target attached"))?;
let pc = t.regs().map_err(|e| throw(&c, &e))?.rip;
let bytes = t.read_mem_pristine(pc, n.saturating_mul(15))
.map_err(|e| throw(&c, &e))?;
let decoded = crate::disasm::decode(pc, &bytes, n);
decoded_to_array(&c, &decoded, t.symbols())
})?;
state_obj.set("disasm", f)?;
}
globals.set("state", state_obj)?;
}
// ---------- disasm(addr, n=8) ----------
{
let target = target.clone();
let f = Function::new(c.clone(), move |c: Ctx<'js>, addr: Value<'js>, n: rquickjs::function::Opt<usize>| -> rquickjs::Result<Value<'js>> {
let addr = coerce_addr(&c, &addr)?;
let n = n.0.unwrap_or(8).max(1);
let slot = target.borrow();
let t = slot.as_ref().ok_or_else(|| throw(&c, "no target attached"))?;
let bytes = t.read_mem_pristine(addr, n.saturating_mul(15))
.map_err(|e| throw(&c, &e))?;
let decoded = crate::disasm::decode(addr, &bytes, n);
decoded_to_array(&c, &decoded, t.symbols())
})?;
globals.set("disasm", f)?;
}
// ---------- prev ----------
// Mirror of state for register-only data, backed by the pre-resume
// snapshot. Property accessors throw if no snapshot is available yet
@@ -457,6 +489,34 @@ fn install_globals<'js>(
// --- helpers --------------------------------------------------------------
/// Convert a Vec<Decoded> from the disassembler into a JS Array of records:
/// [{addr: BigInt, bytes: "ffce..", text: "mov rax, ...", target?: BigInt, target_sym?: "name"}]
/// `bytes` is a hex string for compactness; `target` and `target_sym` are
/// only present for branch/call instructions.
fn decoded_to_array<'js>(
c: &Ctx<'js>,
decoded: &[crate::disasm::Decoded],
symbols: Option<&crate::symbols::SymbolTable>,
) -> rquickjs::Result<Value<'js>> {
let arr = rquickjs::Array::new(c.clone())?;
for (i, d) in decoded.iter().enumerate() {
let obj = Object::new(c.clone())?;
obj.set("addr", u64_to_bigint(c, d.addr)?)?;
let mut bytes_hex = String::with_capacity(d.bytes.len() * 2);
for b in &d.bytes { bytes_hex.push_str(&format!("{:02x}", b)); }
obj.set("bytes", bytes_hex)?;
obj.set("text", d.text.as_str())?;
if let Some(t) = d.branch_target {
obj.set("target", u64_to_bigint(c, t)?)?;
if let Some(s) = symbols.and_then(|s| s.name_for_addr(t)) {
obj.set("target_sym", s)?;
}
}
arr.set(i, obj)?;
}
Ok(arr.into_value())
}
/// 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).