use std::panic::{AssertUnwindSafe, catch_unwind};
use dbgscope::dbgeng::{DebugEngine, Scope};
const MOVERS: &[&str] = &[".frame 3", ".ecxr", ".frame 1"];
fn move_the_scope(e: &DebugEngine) -> Option<&'static str> {
let before = e.scope().ok()?;
for command in MOVERS {
let _ = e.execute_command(command);
if e.scope().ok()? != before {
return Some(command);
}
}
None
}
fn frame(e: &DebugEngine) -> String {
e.execute_command(".frame")
.unwrap_or_else(|err| format!("ERR: {err}"))
.trim()
.replace('\n', " / ")
}
fn describe(scope: &Scope) -> String {
format!(
"ip={:#x} frame={} context={}",
scope.instruction_offset(),
scope.frame().FrameNumber,
if scope.has_context() { "yes" } else { "none" }
)
}
fn main() {
let path = std::env::args()
.nth(1)
.expect("usage: scope_restore <dump-or-trace>");
let e = DebugEngine::new();
println!("=== 0. no target ===");
match e.scope() {
Ok(scope) => println!("unexpectedly read a scope: {}", describe(&scope)),
Err(err) => println!("scope() with no target: {err}"),
}
e.open_dump(&path).expect("open failed");
e.wait_for_event(120_000).expect("load failed");
let has_analyze = e.execute_command(".load ext").is_ok_and(|_| {
e.execute_command("!analyze -v")
.is_ok_and(|out| out.len() > 1000)
});
println!("\ntarget: {path}\nanalysis available: {has_analyze}");
println!("\n=== 1. !analyze -v with no guard ===");
match move_the_scope(&e) {
Some(command) => println!(
"moved off the default scope with `{command}`: {}",
frame(&e)
),
None => println!("nothing moved this target's scope — sections 1-3 prove nothing here"),
}
let before = e.scope().expect("scope() failed");
println!("before: {}", describe(&before));
let _ = e.execute_command("!analyze -v");
let after = e.scope().expect("scope() failed");
println!("after : {} [{}]", describe(&after), frame(&e));
println!("moved : {}", if after == before { "no" } else { "YES" });
println!("\n=== 2. !analyze -v inside a scope guard ===");
move_the_scope(&e);
let before = e.scope().expect("scope() failed");
println!("before: {} [{}]", describe(&before), frame(&e));
{
let guard = e.scope_guard().expect("scope_guard() failed");
let _ = e.execute_command("!analyze -v");
let inside = e.scope().expect("scope() failed");
println!(
"inside: {} (analysis moved it: {})",
describe(&inside),
if inside == before { "no" } else { "yes" }
);
drop(guard);
}
let after = e.scope().expect("scope() failed");
println!("after : {} [{}]", describe(&after), frame(&e));
println!("restored: {}", if after == before { "YES" } else { "no" });
println!("\n=== 3. a panic unwinding through the guard ===");
move_the_scope(&e);
let before = e.scope().expect("scope() failed");
println!("before: {}", describe(&before));
let panicked = catch_unwind(AssertUnwindSafe(|| {
let _guard = e.scope_guard().expect("scope_guard() failed");
let _ = e.execute_command("!analyze -v");
let _ = e.execute_command(".frame 0");
println!(
"inside: {} (moved: {})",
describe(&e.scope().expect("scope() failed")),
e.scope().expect("scope() failed") != before
);
panic!("the command's caller gave up here");
}))
.is_err();
let after = e.scope().expect("scope() failed");
println!("panicked: {panicked}");
println!("after : {} [{}]", describe(&after), frame(&e));
println!("restored: {}", if after == before { "YES" } else { "no" });
println!("\n=== 4. restoring a scope after the target is gone ===");
let stale = e.scope().expect("scope() failed");
let _ = e.end_session();
e.open_dump(&path).expect("reopen failed");
e.wait_for_event(120_000).expect("reload failed");
match e.set_scope(&stale) {
Ok(()) => println!("applied a stale scope — that should not happen"),
Err(err) => println!("refused: {err}"),
}
let fresh = e.scope().expect("scope() failed");
move_the_scope(&e);
e.set_scope(&fresh).expect("set_scope failed");
println!(
"fresh target round-trips: {}",
e.scope().expect("scope() failed") == fresh
);
let _ = e.end_session();
}