diff options
| author | iximeow <me@iximeow.net> | 2026-03-28 01:07:37 +0000 |
|---|---|---|
| committer | iximeow <me@iximeow.net> | 2026-03-28 01:07:37 +0000 |
| commit | 511ebed3f774186e5f2626d03302dd1b198dd587 (patch) | |
| tree | 8e02ad6a8346d3b8db1a9ac2329db728efb203d5 /test/long_mode | |
| parent | 75f8b64b0f3af293721108255009fe3c0fb04cb3 (diff) | |
handle instructions that read and write different parts of the same instruction
the motivating case is `xchg ah, al`, where both register writes
independently "don't match" the overall register diff of the low 16
bits. the diff-checking code was too narrow: we really have to collect
all allowed diffs on a register for an instruction and compare the
actual diff to that unification.
the implementation goes the other way though: compute the diff, and
remove parts of the diff that are unaccounted for. if any diff remains,
that is by definition unexpected and an error.
Diffstat (limited to 'test/long_mode')
| -rw-r--r-- | test/long_mode/behavior.rs | 38 |
1 files changed, 19 insertions, 19 deletions
diff --git a/test/long_mode/behavior.rs b/test/long_mode/behavior.rs index 6bbca05..e707b5d 100644 --- a/test/long_mode/behavior.rs +++ b/test/long_mode/behavior.rs @@ -957,7 +957,7 @@ mod kvm { } } } - fn write_matches_reg(reg: RegSpec, diff: u64) -> bool { + fn reg_mask(reg: RegSpec) -> u64 { match reg.class() { register_class::B => { // non-rex byte regs are al, cl, dl, bl, ah, ch, dh, bh @@ -966,17 +966,17 @@ mod kvm { } else { 0xff00 }; - (diff & !mask) == 0 + mask }, // but rex byte regs are all low-byte - register_class::RB => (diff & !0xff) == 0, - register_class::W => (diff & !0xffff) == 0, + register_class::RB => 0xff, + register_class::W => 0xffff, // x86_64 zero-extends 32-bit writes to 64-bit, so writes to "32-bit" registers still // are fully-clobbers. - register_class::D => (diff & !0xffffffff_ffffffff) == 0, - register_class::Q => (diff & !0xffffffff_ffffffff) == 0, - register_class::RFLAGS => (diff & !0xffffffff_ffffffff) == 0, - register_class::S => (diff & !0xffff) == 0, + register_class::D => 0xffffffff_ffffffff, + register_class::Q => 0xffffffff_ffffffff, + register_class::RFLAGS => 0xffffffff_ffffffff, + register_class::S => 0xffff, other => { panic!("unhandled register class: {:?}", other); } @@ -994,24 +994,24 @@ mod kvm { unexpected_regs: &mut Vec<UnexpectedRegChange>, expected_regs: &[ExpectedRegAccess], changed_reg: RegSpec, before: u64, after: u64, ) { - let diff = before ^ after; + // the same GPR may appear by different names in `expected_regs`, like as in `xchg ah, al`. + // so, compute a diff here and poke out bits as the diff can be accounted for. + let mut diff = before ^ after; if diff != 0 { // could be a write. full write? maybe! - let position = expected_regs.iter().position(|e| { + for e in expected_regs.iter() { if !e.write { - return false; + continue; } if !check_contains(changed_reg, e.reg) { - return false; + continue; } - write_matches_reg(e.reg, diff) - }); + diff &= !reg_mask(e.reg); + } - if let Some(_position) = position { - // nothing to do with it right now - } else { + if diff != 0 { unexpected_regs.push(UnexpectedRegChange { reg: changed_reg, before, @@ -1066,7 +1066,7 @@ mod kvm { Some(reg.num()) } register_class::B => { - Some(reg.num() & 0b111) + Some(reg.num() & 0b11) } _ => { None @@ -1119,7 +1119,7 @@ mod kvm { Some(reg.num()) } register_class::B => { - Some(reg.num() & 0b111) + Some(reg.num() & 0b11) } _ => { None |
