v0.6: state.stack, until(loc), finish(), prelude show.* helpers
- StackRegion from /proc/<pid>/maps, exposed as state.stack
({top, usable_base, guard_size} BigInts per spec)
- until(loc) one-shot bp wrapper that cleans up via try/finally,
works even if a different stop fires first
- finish() reads return address from [rbp+8] and uses until()
- updated js.rs module doc; allow(dead_code) on the kept-for-future
BreakpointSet::find_by_name
This commit is contained in:
@@ -69,6 +69,7 @@ impl BreakpointSet {
|
||||
self.by_addr.get(&addr).and_then(|id| self.get(*id))
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn find_by_name(&self, name: &str) -> Option<&Breakpoint> {
|
||||
self.by_name.get(name).and_then(|id| self.get(*id))
|
||||
}
|
||||
|
||||
@@ -1,15 +1,22 @@
|
||||
//! QuickJS REPL: persistent context, ptrace target lives behind an Rc<RefCell>,
|
||||
//! globals are installed once at construction.
|
||||
//!
|
||||
//! v0.1 surface:
|
||||
//! Native globals installed here:
|
||||
//! spawn(path, ...args) -> { pid, status }
|
||||
//! step(n=1) -> stop-reason string
|
||||
//! continue() -> stop-reason string (exposed as `continue` and `cont`)
|
||||
//! print(...args) -> writes to stdout
|
||||
//! ctx -> persistent plain object
|
||||
//! state.pc / .sp -> BigInt
|
||||
//! step(n=1) / cont() -> stop-reason string
|
||||
//! print(...args), ctx (persistent object)
|
||||
//! state.pc, state.sp -> BigInt (live ptrace)
|
||||
//! state.registers -> object of BigInts
|
||||
//! state.memory(addr) -> { u8(), u32(), u64(), i64(), bytes(n) }
|
||||
//! state.memory(addr) -> { u8/u32/u64/i64/bytes }
|
||||
//! state.disasm(n=8) -> [{addr, bytes, text, target?, target_sym?}]
|
||||
//! state.stack -> { top, usable_base, guard_size }
|
||||
//! prev -> register snapshot from before last resume
|
||||
//! sym(addr), addr(name), symbols()
|
||||
//! disasm(addr, n=8)
|
||||
//! breakpoint / bp: .set, .remove, .list
|
||||
//!
|
||||
//! JS-side helpers (in prelude.js): hex, unhex, dump, repr, addrstr, here,
|
||||
//! until, finish, show.{disasm,bp,regs}.
|
||||
|
||||
use crate::target::Target;
|
||||
use rquickjs::function::Rest;
|
||||
@@ -314,6 +321,24 @@ fn install_globals<'js>(
|
||||
state_obj.set("disasm", f)?;
|
||||
}
|
||||
|
||||
// state.stack — accessor returning {top, usable_base, guard_size} as BigInts
|
||||
{
|
||||
let target = target.clone();
|
||||
state_obj.prop(
|
||||
"stack",
|
||||
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 s = t.stack().ok_or_else(|| throw(&c, "no stack info"))?;
|
||||
let obj = Object::new(c.clone())?;
|
||||
obj.set("top", u64_to_bigint(&c, s.high)?)?;
|
||||
obj.set("usable_base", u64_to_bigint(&c, s.low)?)?;
|
||||
obj.set("guard_size", u64_to_bigint(&c, s.guard_size)?)?;
|
||||
Ok(obj.into_value())
|
||||
}),
|
||||
)?;
|
||||
}
|
||||
|
||||
globals.set("state", state_obj)?;
|
||||
}
|
||||
|
||||
|
||||
+32
-1
@@ -111,7 +111,38 @@ globalThis.here = function (label) {
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// show.disasm(n=8, [addr])
|
||||
// until(addr_or_sym) — run until pc reaches `loc`. Sets a one-shot bp, conts,
|
||||
// removes the bp. Returns the stop reason. If the stop wasn't our bp (the
|
||||
// program exited or hit another bp first), we still clean up our bp.
|
||||
//
|
||||
// `loc` accepts whatever bp.set accepts: a BigInt/Number address or a
|
||||
// symbol name string.
|
||||
|
||||
globalThis.until = function (loc) {
|
||||
const id = bp.set(loc);
|
||||
let reason;
|
||||
try {
|
||||
reason = cont();
|
||||
} finally {
|
||||
// Remove our bp; ignore errors (target may have exited).
|
||||
try { bp.remove(id); } catch (_) {}
|
||||
}
|
||||
return reason;
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// finish() — run until the current function returns. Reads the return
|
||||
// address from [rbp+8] (assumes frame pointer present, which is the case
|
||||
// for code compiled without -fomit-frame-pointer).
|
||||
|
||||
globalThis.finish = function () {
|
||||
if (state.registers.rbp === 0n) {
|
||||
throw new Error("finish(): rbp is 0 — no frame pointer to follow");
|
||||
}
|
||||
const ret_addr = state.memory(state.registers.rbp + 8n).u64();
|
||||
return until(ret_addr);
|
||||
};
|
||||
|
||||
// Pretty-print N instructions starting at addr (default: pc).
|
||||
// Each line: ADDR BYTES MNEMONIC ARGS [-> sym]
|
||||
//
|
||||
|
||||
@@ -190,3 +190,48 @@ fn is_pie(elf: &object::File) -> bool {
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Stack region info read from /proc/<pid>/maps.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct StackRegion {
|
||||
/// Lowest mapped stack address (anonymous mapping start). The kernel
|
||||
/// places a guard page below this on the main thread.
|
||||
pub low: u64,
|
||||
/// Address one past the highest stack byte (mapping end).
|
||||
pub high: u64,
|
||||
/// Guard page size (typically 4 KiB). 0 if no guard could be found.
|
||||
pub guard_size: u64,
|
||||
}
|
||||
|
||||
impl StackRegion {
|
||||
/// Find `[stack]` for the main thread in /proc/<pid>/maps.
|
||||
pub fn from_pid(pid: i32) -> Result<StackRegion, String> {
|
||||
let maps = std::fs::read_to_string(format!("/proc/{}/maps", pid))
|
||||
.map_err(|e| format!("read maps: {}", e))?;
|
||||
let mut prev_high: Option<u64> = None;
|
||||
let mut prev_path: &str = "";
|
||||
for line in maps.lines() {
|
||||
let mut it = line.splitn(6, ' ').filter(|s| !s.is_empty());
|
||||
let range = match it.next() { Some(s) => s, None => continue };
|
||||
let _perms = match it.next() { Some(s) => s, None => continue };
|
||||
let _off = it.next();
|
||||
let _dev = it.next();
|
||||
let _inode = it.next();
|
||||
let path = it.next().unwrap_or("").trim();
|
||||
let mut parts = range.split('-');
|
||||
let lo = parts.next().ok_or_else(|| "bad maps".to_string())?;
|
||||
let hi = parts.next().ok_or_else(|| "bad maps".to_string())?;
|
||||
let lo = u64::from_str_radix(lo, 16).map_err(|e| format!("bad lo: {}", e))?;
|
||||
let hi = u64::from_str_radix(hi, 16).map_err(|e| format!("bad hi: {}", e))?;
|
||||
if path == "[stack]" {
|
||||
let guard_size = if let Some(ph) = prev_high {
|
||||
if ph < lo && prev_path == "" { lo - ph } else { 0 }
|
||||
} else { 0 };
|
||||
return Ok(StackRegion { low: lo, high: hi, guard_size });
|
||||
}
|
||||
prev_high = Some(hi);
|
||||
prev_path = if path.is_empty() { "" } else { "named" };
|
||||
}
|
||||
Err("no [stack] mapping found".into())
|
||||
}
|
||||
}
|
||||
|
||||
+9
-1
@@ -12,7 +12,7 @@ use std::ffi::CString;
|
||||
use std::io::IoSliceMut;
|
||||
|
||||
use crate::breakpoint::BreakpointSet;
|
||||
use crate::symbols::SymbolTable;
|
||||
use crate::symbols::{StackRegion, SymbolTable};
|
||||
|
||||
/// Why the target stopped after a step/continue, or that it has exited.
|
||||
#[derive(Debug, Clone)]
|
||||
@@ -66,6 +66,8 @@ pub struct Target {
|
||||
symbols: Option<SymbolTable>,
|
||||
/// Software (INT3) breakpoints.
|
||||
breakpoints: BreakpointSet,
|
||||
/// Stack region of the main thread, parsed once at spawn.
|
||||
stack: Option<StackRegion>,
|
||||
}
|
||||
|
||||
impl Target {
|
||||
@@ -115,12 +117,14 @@ impl Target {
|
||||
match status {
|
||||
WaitStatus::Stopped(_, Signal::SIGTRAP) => {
|
||||
let symbols = SymbolTable::from_pid(child.as_raw()).ok();
|
||||
let stack = StackRegion::from_pid(child.as_raw()).ok();
|
||||
Ok(Target {
|
||||
pid: child,
|
||||
state: State::Stopped(StopReason::Step),
|
||||
prev_regs: None,
|
||||
symbols,
|
||||
breakpoints: BreakpointSet::new(),
|
||||
stack,
|
||||
})
|
||||
}
|
||||
WaitStatus::Exited(_, code) => {
|
||||
@@ -144,6 +148,10 @@ impl Target {
|
||||
&self.breakpoints
|
||||
}
|
||||
|
||||
pub fn stack(&self) -> Option<&StackRegion> {
|
||||
self.stack.as_ref()
|
||||
}
|
||||
|
||||
// ----- breakpoint ops -------------------------------------------------
|
||||
|
||||
/// Install a software breakpoint at `addr`. Reads the original byte,
|
||||
|
||||
Reference in New Issue
Block a user