aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Cargo.toml2
-rw-r--r--src/long_mode/behavior.rs197
-rw-r--r--test/long_mode/behavior.rs290
-rw-r--r--test/long_mode/reuse_test.rs8
4 files changed, 458 insertions, 39 deletions
diff --git a/Cargo.toml b/Cargo.toml
index 41e8a11..a8d2c86 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -18,7 +18,7 @@ yaxpeax-arch = { version = "0.3.1", default-features = false, features = [] }
"cfg-if" = "1.0.0"
[dev-dependencies]
-rand = "0.8.4"
+rand = { version = "0.10.0", features = ["thread_rng"] }
"kvm-ioctls" = "0.24.0"
"kvm-bindings" = "0.14.0"
"libc" = "*"
diff --git a/src/long_mode/behavior.rs b/src/long_mode/behavior.rs
index 1626948..12886c2 100644
--- a/src/long_mode/behavior.rs
+++ b/src/long_mode/behavior.rs
@@ -256,6 +256,18 @@ pub struct OperandIter<'inst> {
inner: AccessIter<'inst>,
}
+/// enough structure to describe any implicitly-present operand in an x86_64 instruction.
+///
+/// this is (maybe surprisingly, compared to the rest of the isa) relatively tiny: the only
+/// implicit operands to date are register reads/writes, and simple dereference of a register (such
+/// as `[rsp - 8] = ...` in a push).
+struct ImplicitOperand {
+ spec: OperandSpec,
+ reg: RegSpec,
+ disp: i32,
+ write: bool,
+}
+
impl<'inst> Iterator for OperandIter<'inst> {
type Item = Operand;
@@ -320,6 +332,16 @@ impl<'inst> InstBehavior<'inst> {
Access::from_bits(flag_acc)
}
+ pub fn implicit_oplist(&self) -> Option<&'static [ImplicitOperand]> {
+ let ops_idx = self.behavior.extra;
+ if ops_idx == 0 {
+ return None;
+ }
+
+ // TODO: ops_idx cannot be out of bounds, so maybe kinda-unchecked here..?
+ Some(&IMPLICIT_OPS_LIST[ops_idx as usize])
+ }
+
pub fn operand_access(&self, idx: u8) -> Option<Access> {
if idx >= 4 {
return None;
@@ -329,7 +351,6 @@ impl<'inst> InstBehavior<'inst> {
Access::from_bits(op_acc)
}
- // TODO: this should visit implicit operand lists, flags, same as operand iter.
pub fn visit_accesses<T: AccessVisitor>(&self, v: &mut T) -> Result<(), ComplexOp> {
if self.inst.opcode == Opcode::WRMSR {
return Err(ComplexOp::WRMSR);
@@ -337,7 +358,9 @@ impl<'inst> InstBehavior<'inst> {
fn compute_addr<T: AccessVisitor>(v: &mut T, inst: &Instruction, op_spec: OperandSpec) -> Option<u64> {
// TODO: test assertions feature?
- assert!(op_spec.is_memory());
+ if !op_spec.is_memory() {
+ panic!("expected memory operand but got {:?}", op_spec);
+ }
match op_spec {
OperandSpec::Deref => {
@@ -349,6 +372,43 @@ impl<'inst> InstBehavior<'inst> {
}
}
+ if let Some(implicit_oplist) = self.implicit_oplist() {
+ for op in implicit_oplist.iter() {
+ if op.spec == OperandSpec::RegRRR {
+ if op.write {
+ v.register_write(op.reg);
+ } else {
+ v.register_read(op.reg);
+ }
+ } else {
+ let addr = match op.spec {
+ OperandSpec::Deref => {
+ v.get_register(op.reg)
+ },
+ OperandSpec::Disp => {
+ if let Some(base) = v.get_register(op.reg) {
+ Some(base.wrapping_add(op.disp as i64 as u64))
+ } else {
+ None
+ }
+ }
+ other => {
+ panic!("impossible operand spec {:?}", other);
+ }
+ };
+
+ let size = self.inst.mem_size().expect("memory operand implies memory access size")
+ .bytes_size().expect("non-complex instructions have well-defined bytes_size()");
+
+ if op.write {
+ v.memory_write(addr, size as u32);
+ } else {
+ v.memory_read(addr, size as u32);
+ }
+ }
+ }
+ }
+
if let Some(acc) = self.flags_access() {
if acc.is_read() {
v.register_read(RegSpec::rflags());
@@ -370,26 +430,66 @@ impl<'inst> InstBehavior<'inst> {
OperandSpec::RegRRR => {
v.register_read(self.inst.regs[0]);
}
+ OperandSpec::RegMMM => {
+ v.register_read(self.inst.regs[1]);
+ }
+ OperandSpec::ImmI8 |
+ OperandSpec::ImmU8 |
+ OperandSpec::ImmI16 |
+ OperandSpec::ImmU16 |
+ OperandSpec::ImmI32 |
+ OperandSpec::ImmI64 |
+ OperandSpec::ImmInDispField => {
+ // no register/memory access to report.
+ }
other => {
// compute effective address...
let addr = compute_addr(v, &self.inst, op_spec);
let size = self.inst.mem_size().expect("memory operand implies memory access size")
.bytes_size().expect("non-complex instructions have well-defined bytes_size()");
- v.memory_read(addr, size as u32);
+ // `lea` *just* computes the effective address, which we've done above.
+ // othrwise, the instruction will actually read this memory operand.
+ if self.inst.opcode != Opcode::LEA {
+ v.memory_read(addr, size as u32);
+ }
}
}
}
if access.is_write() {
+ // given a register `reg` that an instruction writes, expand it for the purposes of
+ // tracking register writes. x86 zero-extends writes to 32-bit GPRs into 64-bit GPR
+ // writes, so replicate that here.
+ fn apply_x86_zext(mut reg: RegSpec) -> RegSpec {
+ use super::RegisterBank;
+ if reg.bank == RegisterBank::D {
+ reg.bank = RegisterBank::Q;
+ }
+ reg
+ }
match op_spec {
OperandSpec::RegRRR => {
- v.register_write(self.inst.regs[0]);
+ v.register_write(apply_x86_zext(self.inst.regs[0]));
+ }
+ OperandSpec::RegMMM => {
+ v.register_write(apply_x86_zext(self.inst.regs[1]));
+ }
+ OperandSpec::ImmI8 |
+ OperandSpec::ImmU8 |
+ OperandSpec::ImmI16 |
+ OperandSpec::ImmU16 |
+ OperandSpec::ImmI32 |
+ OperandSpec::ImmI64 |
+ OperandSpec::ImmInDispField => {
+ // no register/memory access to report.
}
other => {
// compute effective address...
let addr = compute_addr(v, &self.inst, op_spec);
let size = self.inst.mem_size().expect("memory operand implies memory access size")
.bytes_size().expect("non-complex instructions have well-defined bytes_size()");
+ // no lea check necessary: the memory access is coded as a read and no
+ // instruction has a similar "fake" memory write.
v.memory_write(addr, size as u32);
}
}
@@ -435,7 +535,7 @@ pub struct BehaviorDigest {
// laid out like:
//
// |7 6|5 4|3 2|1 0|
- // |imp_ops| |FL |PL |
+ // |imp_ops|FL |PL |
//
// imp_ops: selector for a `&'static [Operand]` of additional "implicit" operands for the
// instruction.
@@ -452,6 +552,7 @@ pub struct BehaviorDigest {
// describes validity of these bits: fields left `00` must not have a corresponding operand at
// that offset. fields with no corresponding operand may have bits set.
operand_access: u8,
+ extra: u16,
}
impl BehaviorDigest {
@@ -459,6 +560,7 @@ impl BehaviorDigest {
BehaviorDigest {
behavior: 0,
operand_access: 0,
+ extra: 0
}
}
@@ -493,6 +595,18 @@ impl BehaviorDigest {
self.operand_access |= (access as u8) << offset;
self
}
+
+ const fn set_implicit_ops(mut self, ops_idx: u16) -> Self {
+ // TODO: this needs much less than a full u16 (much less than |Opcode| even)
+ self.extra = ops_idx;
+ self
+ }
+
+ const fn set_complex(mut self, state: bool) -> Self {
+ self.behavior &= 0b11_10_11_11;
+ self.behavior |= (state as u8) << 4;
+ self
+ }
}
/// a subset of [`Opcode`] where access patterns cannot be expressed as a simple stream of reads or
@@ -711,9 +825,9 @@ mod test {
behavior.visit_accesses(&mut ctx).expect("xor eax, [rcx] is not complex");
assert_eq!(ctx.accesses, vec![
+ (RegSpec::rflags(), Access::Write),
(RegSpec::eax(), Access::Read),
- // TODO: should this be `rax`? given that x86 zero-extends eax up...
- (RegSpec::eax(), Access::Write),
+ (RegSpec::rax(), Access::Write),
(RegSpec::rcx(), Access::Read)
]);
assert_eq!(ctx.mem_accesses, vec![((Some(0x10000), 4), Access::Read)]);
@@ -803,7 +917,8 @@ const GENERAL_RW_R_FLAGREAD: BehaviorDigest = GENERAL_RW_FLAGREAD
/// `inc`, `dec`, and `neg` have one operand and modify flags.
const GENERAL_RW_FLAGWRITE: BehaviorDigest = BehaviorDigest::empty()
.set_pl_any()
- .set_operand(0, Access::ReadWrite);
+ .set_operand(0, Access::ReadWrite)
+ .set_flags_access(Access::Write);
/// `inc`, `dec`, and `neg` have one operand and modify flags.
const GENERAL_RW: BehaviorDigest = BehaviorDigest::empty()
@@ -823,6 +938,57 @@ const GENERAL_RW_RW: BehaviorDigest = GENERAL_RW_R
const GENERAL_RW_RW_FLAGWRITE: BehaviorDigest = GENERAL_RW_RW
.set_flags_access(Access::Write);
+static PUSH_OPS: &'static [ImplicitOperand] = &[
+ ImplicitOperand {
+ spec: OperandSpec::Disp,
+ reg: RegSpec::rsp(),
+ disp: -8i32,
+ write: true,
+ },
+ // push.. pushes the value (above), then does a RMW on rsp.
+ ImplicitOperand {
+ spec: OperandSpec::RegRRR,
+ reg: RegSpec::rsp(),
+ disp: 0,
+ write: false,
+ },
+ ImplicitOperand {
+ spec: OperandSpec::RegRRR,
+ reg: RegSpec::rsp(),
+ disp: 0,
+ write: true,
+ }
+];
+
+static POP_OPS: &'static [ImplicitOperand] = &[
+ ImplicitOperand {
+ spec: OperandSpec::Deref,
+ reg: RegSpec::rsp(),
+ disp: 0i32,
+ write: false,
+ },
+ ImplicitOperand {
+ spec: OperandSpec::RegRRR,
+ reg: RegSpec::rsp(),
+ disp: 0,
+ write: false,
+ },
+ ImplicitOperand {
+ spec: OperandSpec::RegRRR,
+ reg: RegSpec::rsp(),
+ disp: 0,
+ write: true,
+ }
+];
+
+const PUSH_OPS_IDX: u16 = 1;
+const POP_OPS_IDX: u16 = 2;
+
+static IMPLICIT_OPS_LIST: [&[ImplicitOperand]; 3] = [
+ &[], // implicit ops list 0 is not used
+ PUSH_OPS,
+ POP_OPS,
+];
fn opcode2behavior(opc: &Opcode) -> BehaviorDigest {
use Opcode::*;
@@ -915,10 +1081,17 @@ fn opcode2behavior(opc: &Opcode) -> BehaviorDigest {
CALLF => { panic!("todo: callf"); },
JMP => { panic!("todo: jmp"); },
JMPF => { panic!("todo: jmpf"); },
- PUSH => { panic!("todo: push"); },
- POP => { panic!("todo: pop"); },
- LEA => { panic!("todo: lea"); },
- NOP => { panic!("todo: nop"); },
+ PUSH => BehaviorDigest::empty()
+ .set_implicit_ops(PUSH_OPS_IDX)
+ .set_pl_any()
+ .set_operand(0, Access::Read),
+ POP => BehaviorDigest::empty()
+ .set_implicit_ops(POP_OPS_IDX)
+ .set_pl_any()
+ .set_operand(0, Access::Write),
+ LEA => GENERAL_W_R,
+ NOP => BehaviorDigest::empty()
+ .set_pl_any(),
PREFETCHNTA => { panic!("todo: prefetchnta"); },
PREFETCH0 => { panic!("todo: prefetch0"); },
PREFETCH1 => { panic!("todo: prefetch1"); },
diff --git a/test/long_mode/behavior.rs b/test/long_mode/behavior.rs
index d300214..4cabe28 100644
--- a/test/long_mode/behavior.rs
+++ b/test/long_mode/behavior.rs
@@ -7,6 +7,8 @@ mod kvm {
KVM_GUESTDBG_ENABLE, KVM_GUESTDBG_SINGLESTEP,
};
+ use rand::prelude::*;
+
/// a test VM for running arbitrary instructions.
///
/// there is one CPU which is configured for long-mode execution. all memory is
@@ -332,19 +334,26 @@ mod kvm {
}
}
- #[derive(Debug)]
+ #[derive(Debug, Copy, Clone)]
struct ExpectedMemAccess {
write: bool,
addr: u64,
size: u32,
}
- #[derive(Debug)]
+ #[derive(Debug, Copy, Clone)]
struct ExpectedRegAccess {
write: bool,
reg: RegSpec,
}
+ #[derive(Debug, Copy, Clone)]
+ struct UnexpectedRegChange {
+ reg: RegSpec,
+ before: u64,
+ after: u64,
+ }
+
struct AccessTestCtx<'regs> {
regs: &'regs mut kvm_regs,
used_regs: [bool; 16],
@@ -397,7 +406,18 @@ mod kvm {
};
Some(value)
} else {
- let value = (kvm_reg_nr as u64 + 1) * 0x100_0000;
+ // register value allocation is done carefully to keep memory accesses out
+ // of the first 1G of memory. that keeps test instructions from clobbering
+ // page tables. registers used for memory access are set to at least to 8G,
+ // so that disp32 offsets can cause an instruction to access only as early
+ // as 4G. SIB addressing means the highest address that may be accessed
+ // could be 8G + 0xf00_0000 + (8G + 0x1000_0000) * 4 + 2G, or somewhere
+ // around 42G.
+ //
+ // since the highest accessible address is (probably) 2^48 or 256T, even in
+ // the most convoluted case we're always going to be forming canonical
+ // (lower-half) addresses.
+ let value = 0x2_0000_0000 + (kvm_reg_nr as u64 + 1) * 0x100_0000;
unsafe {
(self.regs as *mut _ as *mut u64).offset(kvm_reg_nr as isize).write(value);
}
@@ -428,7 +448,8 @@ mod kvm {
}
}
- fn run_with_mem_checks(vm: &mut TestVm, expected_end: u64, mut expected_mem: Vec<ExpectedMemAccess>) {
+ fn run_with_mem_checks(vm: &mut TestVm, expected_end: u64, expected_mem: &[ExpectedMemAccess]) {
+ let mut expected_mem = expected_mem.to_vec();
let mut unexpected_mem = Vec::new();
let mut exits = 0;
let end_pc = loop {
@@ -465,7 +486,7 @@ mod kvm {
break info.pc;
}
VcpuExit::Hlt => {
- let mut regs = vm.vcpu.get_regs().unwrap();
+ let regs = vm.vcpu.get_regs().unwrap();
break regs.rip;
}
other => {
@@ -479,7 +500,22 @@ mod kvm {
}
if !unexpected_mem.is_empty() {
- panic!("unexpected mem accesses: {:?}", unexpected_mem);
+ eprintln!("memory access surprise!");
+ if expected_mem.is_empty() {
+ eprintln!("expected none");
+ } else {
+ eprintln!("expected:");
+ for acc in expected_mem.iter() {
+ let rw = if acc.write { "write:" } else { " read:" };
+ eprintln!(" {} {} bytes at {:08x}", rw, acc.size, acc.addr);
+ }
+ }
+ eprintln!("unexpected:");
+ for (write, addr, size) in unexpected_mem {
+ let rw = if write { "write:" } else { " read:" };
+ eprintln!(" {} {} bytes at {:08x}", rw, size, addr);
+ }
+ panic!("stop");
}
return;
}
@@ -544,7 +580,7 @@ mod kvm {
}
fn verify_reg(
- unexpected_regs: &mut Vec<RegSpec>, expected_regs: &[ExpectedRegAccess],
+ unexpected_regs: &mut Vec<UnexpectedRegChange>, expected_regs: &[ExpectedRegAccess],
changed_reg: RegSpec, before: u64, after: u64,
) {
let diff = before ^ after;
@@ -562,24 +598,57 @@ mod kvm {
write_matches_reg(e.reg, diff)
});
- if let Some(position) = position {
+ if let Some(_position) = position {
// nothing to do with it right now
} else {
- eprintln!("register {} changed unexpectedly: {:08x} -> {:08x}", changed_reg.name(), before, after);
- unexpected_regs.push(changed_reg);
+ unexpected_regs.push(UnexpectedRegChange {
+ reg: changed_reg,
+ before,
+ after,
+ });
}
}
}
+ fn verify_dontcares(written_regs: &[RegSpec], initial_after_regs: &kvm_regs, now_after_regs: &kvm_regs) {
+ let mut bad = false;
+
+ for reg in written_regs.iter() {
+ assert_eq!(reg.class(), register_class::Q);
+
+ static KVM_REG_LUT: [usize; 16] = [
+ 0, 2, 3, 1, 6, 7, 4, 5,
+ 8, 9, 10, 11, 12, 13, 14, 15,
+ ];
+ let kvm_reg_nr = KVM_REG_LUT[reg.num() as usize];
+
+ let initial_after = unsafe {
+ (initial_after_regs as *const _ as *const u64).offset(kvm_reg_nr as isize).read()
+ };
+
+ let now_after = unsafe {
+ (now_after_regs as *const _ as *const u64).offset(kvm_reg_nr as isize).read()
+ };
+
+ if initial_after != now_after {
+ eprintln!("register {} changed after permuting dontcares: {:016x} => {:016x}",
+ reg, initial_after, now_after);
+ bad = true;
+ }
+ }
+
+ if bad {
+ panic!("cared about dontcares");
+ }
+ }
+
fn verify_reg_changes(
- expected_regs: Vec<ExpectedRegAccess>,
+ expected_regs: &[ExpectedRegAccess],
before_regs: kvm_regs, after_regs: kvm_regs,
before_sregs: kvm_sregs, after_sregs: kvm_sregs
) {
let mut unexpected_regs = Vec::new();
- eprintln!("expecting reg changes: {:?}", expected_regs);
-
verify_reg(&mut unexpected_regs, &expected_regs, RegSpec::rax(), before_regs.rax, after_regs.rax);
verify_reg(&mut unexpected_regs, &expected_regs, RegSpec::rcx(), before_regs.rcx, after_regs.rcx);
verify_reg(&mut unexpected_regs, &expected_regs, RegSpec::rdx(), before_regs.rdx, after_regs.rdx);
@@ -599,7 +668,11 @@ mod kvm {
verify_reg(&mut unexpected_regs, &expected_regs, RegSpec::rflags(), before_regs.rflags, after_regs.rflags);
if !unexpected_regs.is_empty() {
- panic!("unexpected reg changes: {:?}", unexpected_regs);
+ eprintln!("unexpected reg changes:");
+ for change in unexpected_regs {
+ eprintln!(" {}: {:08x} -> {:08x}", change.reg.name(), change.before, change.after);
+ }
+ panic!("stop");
}
}
@@ -620,31 +693,174 @@ mod kvm {
insts.push(0xf4);
let decoded = yaxpeax_x86::long_mode::InstDecoder::default()
.decode_slice(inst).expect("can decode");
+ use yaxpeax_arch::LengthedInstruction;
+ assert_eq!(insts.len(), 0.wrapping_offset(decoded.len()) as usize + 1);
let behavior = decoded.behavior();
+ eprintln!("checking behavior of {}", decoded);
let before_sregs = vm.vcpu.get_sregs().unwrap();
let mut regs = vm.vcpu.get_regs().unwrap();
+ vm.set_single_step(true);
vm.program(insts.as_slice(), &mut regs);
+ let mut rng = rand::rng();
+
+ regs.rax = rng.next_u64();
+ regs.rbx = rng.next_u64();
+ regs.rcx = rng.next_u64();
+ regs.rdx = rng.next_u64();
+ regs.rsp = rng.next_u64();
+ regs.rbp = rng.next_u64();
+ regs.rsi = rng.next_u64();
+ regs.rdi = rng.next_u64();
+
+ regs.r8 = rng.next_u64();
+ regs.r9 = rng.next_u64();
+ regs.r10 = rng.next_u64();
+ regs.r11 = rng.next_u64();
+ regs.r12 = rng.next_u64();
+ regs.r13 = rng.next_u64();
+ regs.r14 = rng.next_u64();
+ regs.r15 = rng.next_u64();
+
let mut ctx = AccessTestCtx {
regs: &mut regs,
used_regs: [false; 16],
expected_reg: Vec::new(),
expected_mem: Vec::new(),
};
- let accesses = behavior.visit_accesses(&mut ctx).expect("can visit accesses");
+ behavior.visit_accesses(&mut ctx).expect("can visit accesses");
let (expected_reg, expected_mem) = ctx.into_expectations();
- vm.vcpu.set_regs(&regs).unwrap();
+ fn compute_dontcares(accesses: &[ExpectedRegAccess]) -> Vec<RegSpec> {
+ // use a bitmap for dontcares, mask out bits as registers are seen to be read.
+ let mut reg_bitmap: u32 = 0xffffffff;
+
+ fn reg_to_gpr(reg: RegSpec) -> Option<u8> {
+ match reg.class() {
+ register_class::Q |
+ register_class::D |
+ register_class::W |
+ register_class::RB => {
+ Some(reg.num())
+ }
+ register_class::B => {
+ Some(reg.num() & 0b111)
+ }
+ _ => {
+ None
+ }
+ }
+ }
- vm.set_single_step(true);
+ for acc in accesses.iter() {
+ if acc.write {
+ continue;
+ }
- run_with_mem_checks(vm, regs.rip + insts.len() as u64, expected_mem);
+ if let Some(gpr_num) = reg_to_gpr(acc.reg) {
+ reg_bitmap &= !(1 << gpr_num);
+ }
+ }
- let after_regs = vm.vcpu.get_regs().unwrap();
+ let mut regs = Vec::new();
+
+ for i in 0..16 {
+ if reg_bitmap & (1 << i) != 0 {
+ regs.push(RegSpec::q(i));
+ }
+ }
+
+ regs
+ }
+
+ fn compute_writes(accesses: &[ExpectedRegAccess]) -> Vec<RegSpec> {
+ // same as dontcares, isk
+ let mut reg_bitmap: u32 = 0x00000000;
+
+ fn reg_to_gpr(reg: RegSpec) -> Option<u8> {
+ match reg.class() {
+ register_class::Q |
+ register_class::D |
+ register_class::W |
+ register_class::RB => {
+ Some(reg.num())
+ }
+ register_class::B => {
+ Some(reg.num() & 0b111)
+ }
+ _ => {
+ None
+ }
+ }
+ }
+
+ for acc in accesses.iter() {
+ if !acc.write {
+ continue;
+ }
+
+ if let Some(gpr_num) = reg_to_gpr(acc.reg) {
+ reg_bitmap |= 1 << gpr_num;
+ }
+ }
+
+ let mut regs = Vec::new();
+
+ for i in 0..16 {
+ if reg_bitmap & (1 << i) != 0 {
+ regs.push(RegSpec::q(i));
+ }
+ }
+
+ regs
+ }
+
+ let dontcare_regs = compute_dontcares(&expected_reg);
+ let written_regs = compute_writes(&expected_reg);
+
+ fn permute_dontcares(dontcare_regs: &[RegSpec], regs: &mut kvm_regs) {
+ let mut rng = rand::rng();
+
+ for reg in dontcare_regs {
+ assert_eq!(reg.class(), register_class::Q);
+
+ static KVM_REG_LUT: [usize; 16] = [
+ 0, 2, 3, 1, 6, 7, 4, 5,
+ 8, 9, 10, 11, 12, 13, 14, 15,
+ ];
+ let kvm_reg_nr = KVM_REG_LUT[reg.num() as usize];
+ let rand = rng.next_u64();
+ unsafe {
+ (regs as *mut _ as *mut u64).offset(kvm_reg_nr as isize).write(rand);
+ }
+ }
+ }
+
+ permute_dontcares(dontcare_regs.as_slice(), &mut regs);
+
+ vm.vcpu.set_regs(&regs).unwrap();
+
+ run_with_mem_checks(vm, regs.rip + insts.len() as u64, expected_mem.as_slice());
+
+ let initial_after_regs = vm.vcpu.get_regs().unwrap();
let after_sregs = vm.vcpu.get_sregs().unwrap();
- verify_reg_changes(expected_reg, regs, after_regs, before_sregs, after_sregs);
+ verify_reg_changes(&expected_reg, regs, initial_after_regs, before_sregs, after_sregs);
+
+ for _ in 0..4 {
+ permute_dontcares(dontcare_regs.as_slice(), &mut regs);
+
+ vm.vcpu.set_regs(&regs).unwrap();
+
+ run_with_mem_checks(vm, regs.rip + insts.len() as u64, expected_mem.as_slice());
+
+ let after_regs = vm.vcpu.get_regs().unwrap();
+ let after_sregs = vm.vcpu.get_sregs().unwrap();
+
+ verify_reg_changes(&expected_reg, regs, after_regs, before_sregs, after_sregs);
+ verify_dontcares(written_regs.as_slice(), &initial_after_regs, &after_regs);
+ }
}
#[test]
@@ -666,6 +882,28 @@ mod kvm {
}
#[test]
+ fn kvm_verify_inc() {
+ let mut vm = TestVm::create();
+
+ // `inc eax`
+ let inst: &'static [u8] = &[0xff, 0xc0];
+ check_behavior(&mut vm, inst);
+
+ // `inc dword [rax]`
+ let inst: &'static [u8] = &[0xff, 0x00];
+ check_behavior(&mut vm, inst);
+ }
+
+ #[test]
+ fn kvm_verify_push() {
+ let mut vm = TestVm::create();
+
+ // `push rax`
+ let inst: &'static [u8] = &[0x50];
+ check_behavior(&mut vm, inst);
+ }
+
+ #[test]
fn behavior_verify_kvm() {
use yaxpeax_arch::{Decoder, U8Reader};
use yaxpeax_x86::long_mode::{Instruction, InstDecoder};
@@ -679,8 +917,16 @@ mod kvm {
let inst = word.to_le_bytes();
let mut reader = U8Reader::new(&inst);
if decoder.decode_into(&mut buf, &mut reader).is_ok() {
- eprintln!("checking behavior of {:02x} {:02x}: {}", inst[0], inst[1], buf);
- check_behavior(&mut vm, &inst);
+ // some instructions may just be one byte, so figure out the length and only check
+ // that many bytes of instructions for specific behavior..
+ use yaxpeax_arch::LengthedInstruction;
+ let inst_len = 0.wrapping_offset(buf.len()) as usize;
+ if inst_len == 1 {
+ eprintln!("checking behavior of {:02x}: {}", inst[0], buf);
+ } else {
+ eprintln!("checking behavior of {:02x} {:02x}: {}", inst[0], inst[1], buf);
+ }
+ check_behavior(&mut vm, &inst[..inst_len]);
}
}
}
diff --git a/test/long_mode/reuse_test.rs b/test/long_mode/reuse_test.rs
index ad8e890..8742041 100644
--- a/test/long_mode/reuse_test.rs
+++ b/test/long_mode/reuse_test.rs
@@ -1981,18 +1981,18 @@ const INSTRUCTIONS: [&'static [u8]; 1975] = [
#[test]
fn test_against_leftover_data() {
- use super::rand::{thread_rng, Rng};
+ use super::rand::{rngs::ThreadRng, RngExt};
use yaxpeax_arch::U8Reader;
- let mut rng = thread_rng();
+ let mut rng = ThreadRng::default();
let decoder = InstDecoder::default();
for _ in 0..100000 {
- let first_vec = INSTRUCTIONS[rng.gen_range(0..INSTRUCTIONS.len())];
+ let first_vec = INSTRUCTIONS[rng.random_range(0..INSTRUCTIONS.len())];
let mut first_reader = U8Reader::new(first_vec);
let first_decode = decoder.decode(&mut first_reader).unwrap();
- let second_vec = INSTRUCTIONS[rng.gen_range(0..INSTRUCTIONS.len())];
+ let second_vec = INSTRUCTIONS[rng.random_range(0..INSTRUCTIONS.len())];
let mut second_reader = U8Reader::new(second_vec);
let mut reused_decode = decoder.decode(&mut second_reader).unwrap();
let mut first_reader = U8Reader::new(first_vec);