use super::{StackAnchor, StackAnchors};
fn analyze(bytes: &[u8], frame_pointer: bool) -> StackAnchors {
let mut insns = Vec::new();
crate::scan::decode(bytes, 0x1000, |ins| insns.push(*ins));
StackAnchors::analyze(&insns, frame_pointer)
}
#[test]
fn dominating_anchor_survives_guard_but_branch_local_anchor_does_not() -> Result<(), Box<dyn std::error::Error>> {
let dominant = analyze(
&[0x48, 0x8d, 0x7c, 0x24, 0x40, 0x85, 0xff, 0x74, 0x05, 0xb8, 1, 0, 0, 0, 0x48, 0x8b, 0x44, 0x24, 0x58, 0xc3],
false,
);
let access = dominant.access(0x100e).ok_or("missing reachable access")?;
assert_eq!(access.coordinate, 0x58);
assert_eq!(access.anchors.len(), 1);
assert_eq!(access.anchors[0].frame_offset, 0x40);
let partial =
analyze(&[0x85, 0xff, 0x74, 0x05, 0x48, 0x8d, 0x7c, 0x24, 0x40, 0x48, 0x8b, 0x44, 0x24, 0x58, 0xc3], false);
assert_eq!(partial.access(0x1009).ok_or("missing joined access")?.anchors, Vec::<StackAnchor>::new());
Ok(())
}
#[test]
fn conflicting_stack_coordinates_and_unreachable_islands_are_not_reused() {
let conflict = analyze(
&[0x48, 0x8d, 0x7c, 0x24, 0x40, 0x85, 0xff, 0x74, 0x01, 0x50, 0x48, 0x8b, 0x44, 0x24, 0x58, 0xc3],
false,
);
assert!(conflict.access(0x100a).is_none());
let cycle = analyze(
&[
0x48, 0x8d, 0x7c, 0x24, 0x40, 0x50, 0xeb, 0xfd, 0x48, 0x8b, 0x44, 0x24, 0x58, 0xc3,
],
false,
);
assert!(cycle.access(0x1008).is_none());
let indirect = analyze(&[0xff, 0xe0, 0x48, 0x8b, 0x44, 0x24, 0x58], false);
assert!(indirect.access(0x1002).is_none());
assert!(StackAnchors::analyze(&[], false).access(0x1000).is_none());
}
#[test]
fn stack_pointer_copies_widths_restoration_and_clobbers() -> Result<(), Box<dyn std::error::Error>> {
let copied = analyze(&[0x48, 0x89, 0xe7, 0x48, 0x8b, 0x44, 0x24, 0x18, 0xc3], false);
let access = copied.access(0x1003).ok_or("missing copied-SP access")?;
assert_eq!(access.coordinate, 0x18);
assert_eq!(access.anchors[0].frame_offset, 0);
let narrow = analyze(
&[
0x66, 0x50, 0x48, 0x8d, 0x7c, 0x24, 0x40, 0x66, 0x58, 0x48, 0x8b, 0x44, 0x24, 0x58, 0xc3,
],
false,
);
let access = narrow.access(0x1009).ok_or("missing word push/pop access")?;
assert_eq!(access.coordinate, 0x58);
assert_eq!(access.anchors[0].frame_offset, 0x3e);
let restored =
analyze(&[0x55, 0x48, 0x89, 0xe5, 0x48, 0x83, 0xec, 0x20, 0xc9, 0x48, 0x8b, 0x44, 0x24, 0x18, 0xc3], true);
assert_eq!(restored.access(0x1009).ok_or("missing leave restoration")?.coordinate, 0x18);
let aligned =
analyze(&[0x48, 0x8d, 0x7c, 0x24, 0x40, 0x48, 0x83, 0xe4, 0xf0, 0x48, 0x8b, 0x44, 0x24, 0x58, 0xc3], false);
assert!(aligned.access(0x1009).is_none());
Ok(())
}
#[test]
fn indexed_leas_and_frame_less_rbp_do_not_establish_anchors() -> Result<(), Box<dyn std::error::Error>> {
let indexed = analyze(&[0x48, 0x8d, 0x7c, 0xcc, 0x40, 0x48, 0x8b, 0x44, 0x24, 0x58, 0xc3], false);
assert_eq!(indexed.access(0x1005).ok_or("missing literal indexed test access")?.anchors, Vec::<StackAnchor>::new());
let frameless = analyze(&[0x48, 0x8d, 0x7d, 0x40, 0x48, 0x8b, 0x45, 0x58, 0xc3], false);
assert!(frameless.access(0x1000).is_none());
assert!(frameless.access(0x1004).is_none());
Ok(())
}
#[test]
fn frame_pointer_setup_is_not_a_buffer_anchor() -> Result<(), Box<dyn std::error::Error>> {
let frame = analyze(&[0x55, 0x48, 0x89, 0xe5, 0x8b, 0x45, 0xe8, 0xc3], true);
let local = frame.access(0x1004).ok_or("missing frame-local access")?;
assert_eq!(local.coordinate, -0x20);
assert_eq!(local.anchors, Vec::<StackAnchor>::new());
let explicit = analyze(
&[
0x55, 0x48, 0x89, 0xe5, 0x48, 0x8d, 0x7d, 0xc0, 0x48, 0x8b, 0x45, 0xd8, 0xc3,
],
true,
);
let field = explicit.access(0x1008).ok_or("missing explicitly anchored field")?;
assert_eq!(field.anchors.len(), 1);
assert_eq!(field.anchors[0].frame_offset, -0x48);
let frameless = analyze(
&[
0x48, 0x8d, 0x6c, 0x24, 0x40, 0x48, 0x8b, 0x44, 0x24, 0x58, 0xc3,
],
false,
);
assert_eq!(frameless.access(0x1005).ok_or("missing frameless RBP destination")?.anchors.len(), 1);
Ok(())
}