diff options
| author | iximeow <me@iximeow.net> | 2026-02-22 23:10:57 +0000 |
|---|---|---|
| committer | iximeow <me@iximeow.net> | 2026-02-23 00:03:13 +0000 |
| commit | 1822c7d0de9b14d87d937b89ec63b17f6b485718 (patch) | |
| tree | 25782db86cf3d4127db32843164ae12dc765e645 | |
| parent | c6a3341a67fd99e47a51976a168fe2c977a0d793 (diff) | |
more expansive access behavior validation, start on implicit op lists
| -rw-r--r-- | Cargo.toml | 2 | ||||
| -rw-r--r-- | src/long_mode/behavior.rs | 197 | ||||
| -rw-r--r-- | test/long_mode/behavior.rs | 290 | ||||
| -rw-r--r-- | test/long_mode/reuse_test.rs | 8 |
4 files changed, 458 insertions, 39 deletions
@@ -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(®s).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(®s).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(®s).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); |
