aboutsummaryrefslogtreecommitdiff
path: root/test/long_mode/behavior.rs
diff options
context:
space:
mode:
Diffstat (limited to 'test/long_mode/behavior.rs')
-rw-r--r--test/long_mode/behavior.rs294
1 files changed, 162 insertions, 132 deletions
diff --git a/test/long_mode/behavior.rs b/test/long_mode/behavior.rs
index 986b5f3..b6bd8c2 100644
--- a/test/long_mode/behavior.rs
+++ b/test/long_mode/behavior.rs
@@ -47,6 +47,9 @@ mod kvm {
reg: RegSpec,
}
+ // this is only actually *used* when printing detailed failure information. otherwise the fact
+ // that any of these exist is all that's used to fail tests.
+ #[cfg_attr(not(feature = "fmt"), allow(dead_code))]
#[derive(Debug, Copy, Clone)]
struct UnexpectedRegChange {
reg: RegSpec,
@@ -252,17 +255,12 @@ mod kvm {
}
let mut exits = 0;
let end_pc = loop {
-// eprintln!("about to run! here's some state:");
-// let regs = vm.get_regs().unwrap();
-// dump_regs(&regs);
-// let sregs = vm.get_sregs().unwrap();
-// eprintln!("sregs: {:?}", sregs);
let exit = vm.run().expect("can run vcpu");
exits += 1;
match exit {
- VcpuExit::MmioRead { .. } |
- VcpuExit::MmioWrite { .. } => {
- panic!("shoud not be mmio accesses anymore");
+ VcpuExit::MmioRead { addr, .. } |
+ VcpuExit::MmioWrite { addr, .. } => {
+ panic!("should not be mmio accesses anymore, but one at {:08x}", addr);
}
VcpuExit::Debug { pc, info: _ } => {
break pc;
@@ -719,6 +717,7 @@ mod kvm {
if !unexpected_regs.is_empty() {
eprintln!("unexpected reg changes:");
for change in unexpected_regs {
+ let _ = change;
#[cfg(feature = "fmt")]
eprintln!(" {}: {:08x} -> {:08x}", change.reg.name(), change.before, change.after);
}
@@ -795,14 +794,9 @@ mod kvm {
let decoded = yaxpeax_x86::long_mode::InstDecoder::default()
.decode_slice(inst).expect("can decode");
- let mut printed = false;
eprint!("checking behavior of ");
for b in inst.iter() {
- if printed {
- eprint!(" ");
- }
eprint!("{:02x}", b);
- printed = true;
}
#[cfg(feature = "fmt")]
eprint!(": {}", decoded);
@@ -895,7 +889,6 @@ mod kvm {
permute_dontcares(dontcare_regs.as_slice(), &mut regs);
- eprintln!("setting regs to: {:?}", regs);
vm.set_regs(&regs).unwrap();
let expected_end = regs.rip + insts.len() as u64;
@@ -1094,8 +1087,115 @@ mod kvm {
}
*/
+ fn is_generic(inst: &Instruction) -> bool {
+ if [Opcode::FLDENV, Opcode::FNSTENV, Opcode::FRSTOR, Opcode::FNSAVE, Opcode::FNSTCW, Opcode::FNSTSW].contains(&inst.opcode()) {
+ // this needs a more targeted test
+ return false;
+ }
+
+ if [Opcode::INS, Opcode::MOVS, Opcode::OUTS, Opcode::LODS, Opcode::STOS, Opcode::CMPS, Opcode::SCAS].contains(&inst.opcode()) {
+ if inst.prefixes.rep_any() {
+ // `repnz cmps` will carry on for however long memory allows,
+ // `rep movs` runs `rcx`-many times, etc
+ return false;
+ }
+ }
+
+ if inst.opcode() == Opcode::RSM {
+ // SMM is kinda not our problem for now..
+ return false;
+ }
+
+ if inst.opcode() == Opcode::GETSEC {
+ // oh dear
+ return false;
+ }
+
+ if inst.opcode() == Opcode::RDPID {
+ // rdpid is a specialized rdmsr
+ return false;
+ }
+
+ if inst.opcode() == Opcode::RDTSC {
+ // the TSC keeps ticking so eax will change across runs and trip the
+ // "cared about dontcares" check.
+ return false;
+ }
+
+ if inst.opcode() == Opcode::RDPMC {
+ // reading a bogus PMC will just #GP so this needs a more specific test.
+ return false;
+ }
+
+ if inst.opcode() == Opcode::DIV || inst.opcode() == Opcode::IDIV {
+ // if the operand is in memory we're not currently permuting memory, so it
+ // might be zero and just #DE.
+ return false;
+ }
+
+ if inst.opcode() == Opcode::WRMSR || inst.opcode() == Opcode::RDMSR {
+ // TODO: ... okay come on.
+ return false;
+ }
+ if inst.opcode() == Opcode::RETURN {
+ // hard to handle generically here; see `verify_ret`.
+ return false;
+ }
+ if inst.opcode() == Opcode::LEAVE {
+ // TODO: trying to generically handle leave typically gets #SS from popping a
+ // non-canonical address. needs more specific test.
+ return false;
+ }
+ if inst.opcode() == Opcode::JMPF || inst.opcode() == Opcode::RETF || inst.opcode() == Opcode::CALLF {
+ // TODO: trying to is harder. needs more specific test.
+ return false;
+ }
+ if inst.opcode() == Opcode::INT {
+ // TODO: int is complex, but check_behavior() does not tolerate those yet
+ return false;
+ }
+ if inst.opcode() == Opcode::JMP || inst.opcode() == Opcode::CALL {
+ // TODO: needs more specific testing
+ return false;
+ }
+ if inst.opcode() == Opcode::JRCXZ || inst.opcode() == Opcode::LOOP || inst.opcode() == Opcode::LOOPZ || inst.opcode() == Opcode::LOOPNZ {
+ // TODO: also complex
+ return false;
+ }
+ if inst.opcode() == Opcode::IRET || inst.opcode() == Opcode::IRETD || inst.opcode() == Opcode::IRETQ {
+ // TODO: oh dear
+ return false;
+ }
+ if [Opcode::JO, Opcode::JNO, Opcode::JB, Opcode::JNB, Opcode::JZ, Opcode::JNZ, Opcode::JA, Opcode::JNA, Opcode::JS, Opcode::JNS, Opcode::JP, Opcode::JNP, Opcode::JL, Opcode::JGE, Opcode::JLE, Opcode::JG].contains(&inst.opcode()) {
+ // TODO: jmp-related tests that tolerate rip changing.
+ return false;
+ }
+ if [Opcode::SYSCALL, Opcode::SYSRET, Opcode::SYSENTER, Opcode::SYSEXIT].contains(&inst.opcode()) {
+ // TODO: syscall tests
+ return false;
+ }
+
+ if undef::OPCODES.contains(&inst.opcode()) {
+ // TODO: ud tests, etc
+ return false;
+ }
+
+ if inst.opcode() == Opcode::CLTS {
+ // what happens here, access 0xff000?
+ return false;
+ }
+
+ // mov es, word [rax]
+ // does an inf loop too...?
+ if [Opcode::INS, Opcode::OUTS, Opcode::IN, Opcode::OUT].contains(&inst.opcode()) {
+ return false;
+ }
+
+ true
+ }
+
#[test]
- fn behavior_verify_kvm() {
+ fn behavior_verify_kvm_general() {
use yaxpeax_arch::{Decoder, U8Reader};
use yaxpeax_x86::long_mode::Instruction;
@@ -1103,130 +1203,60 @@ mod kvm {
vm.set_single_step(true).expect("can enable single-step");
let decoder = host_decoder();
- let mut buf = Instruction::default();
+ let mut inst = Instruction::default();
let initial_regs = vm.get_regs().unwrap();
- for word in 0..u16::MAX {
- let inst = word.to_le_bytes();
- let mut reader = U8Reader::new(&inst);
- if decoder.decode_into(&mut buf, &mut reader).is_ok() {
- if [Opcode::FLDENV, Opcode::FNSTENV, Opcode::FRSTOR, Opcode::FNSAVE, Opcode::FNSTCW, Opcode::FNSTSW].contains(&buf.opcode()) {
- // this needs a more targeted test
- continue;
- }
-
- if [Opcode::INS, Opcode::MOVS, Opcode::OUTS, Opcode::LODS, Opcode::STOS, Opcode::CMPS, Opcode::SCAS].contains(&buf.opcode()) {
- if buf.prefixes.rep_any() {
- // `repnz cmps` will carry on for however long memory allows,
- // `rep movs` runs `rcx`-many times, etc
- continue;
- }
- }
-
- if buf.opcode() == Opcode::RSM {
- // SMM is kinda not our problem for now..
- continue;
- }
-
- if buf.opcode() == Opcode::GETSEC {
- // oh dear
- continue;
- }
-
- if buf.opcode() == Opcode::RDPID {
- // rdpid is a specialized rdmsr
- continue;
- }
-
- if buf.opcode() == Opcode::RDTSC {
- // the TSC keeps ticking so eax will change across runs and trip the
- // "cared about dontcares" check.
- continue;
- }
-
- if buf.opcode() == Opcode::RDPMC {
- // reading a bogus PMC will just #GP so this needs a more specific test.
- continue;
- }
+ for prefix in [0x00, 0x66] {
+ for opc in 0..=u8::MAX {
+ for opers in [0x00,
+ 0x01, 0x09, 0x11, 0x19, 0x21, 0x29, 0x31, 0x39,
+ 0xc1, 0xc9, 0xd1, 0xd9, 0xe1, 0xe9, 0xf1, 0xf9] {
+ for imm in [0x00, 0x01, 0x80, 0x81, 0xc0, 0xc1] {
+ let mut inst_len = 0;
+ let mut buf = [0u8; 8];
+
+ match prefix {
+ 0x00 => {},
+ o => {
+ buf[inst_len] = o;
+ inst_len += 1;
+ }
+ }
- if buf.opcode() == Opcode::DIV || buf.opcode() == Opcode::IDIV {
- // if the operand is in memory we're not currently permuting memory, so it
- // might be zero and just #DE.
- continue;
- }
+ buf[inst_len] = opc;
+ inst_len += 1;
- if buf.opcode() == Opcode::WRMSR || buf.opcode() == Opcode::RDMSR {
- // TODO: ... okay come on.
- continue;
- }
- if buf.opcode() == Opcode::RETURN {
- // hard to handle generically here; see `verify_ret`.
- continue;
- }
- if buf.opcode() == Opcode::LEAVE {
- // TODO: trying to generically handle leave typically gets #SS from popping a
- // non-canonical address. needs more specific test.
- continue;
- }
- if buf.opcode() == Opcode::JMPF || buf.opcode() == Opcode::RETF || buf.opcode() == Opcode::CALLF {
- // TODO: trying to is harder. needs more specific test.
- continue;
- }
- if buf.opcode() == Opcode::INT {
- // TODO: int is complex, but check_behavior() does not tolerate those yet
- continue;
- }
- if buf.opcode() == Opcode::JMP || buf.opcode() == Opcode::CALL {
- // TODO: needs more specific testing
- continue;
- }
- if buf.opcode() == Opcode::JRCXZ || buf.opcode() == Opcode::LOOP || buf.opcode() == Opcode::LOOPZ || buf.opcode() == Opcode::LOOPNZ {
- // TODO: also complex
- continue;
- }
- if buf.opcode() == Opcode::IRET || buf.opcode() == Opcode::IRETD || buf.opcode() == Opcode::IRETQ {
- // TODO: oh dear
- continue;
- }
- if [Opcode::JO, Opcode::JNO, Opcode::JB, Opcode::JNB, Opcode::JZ, Opcode::JNZ, Opcode::JA, Opcode::JNA, Opcode::JS, Opcode::JNS, Opcode::JP, Opcode::JNP, Opcode::JL, Opcode::JGE, Opcode::JLE, Opcode::JG].contains(&buf.opcode()) {
- // TODO: jmp-related tests that tolerate rip changing.
- continue;
- }
- if [Opcode::SYSCALL, Opcode::SYSRET, Opcode::SYSENTER, Opcode::SYSEXIT].contains(&buf.opcode()) {
- // TODO: syscall tests
- continue;
- }
+ if opers != 0x00 {
+ buf[inst_len] = opers;
+ inst_len += 1;
+ }
- if undef::OPCODES.contains(&buf.opcode()) {
- // TODO: ud tests, etc
- continue;
- }
+ if imm != 0x00 {
+ buf[inst_len] = imm;
+ inst_len += 1;
+ }
- if buf.opcode() == Opcode::CLTS {
- // what happens here, access 0xff000?
- continue;
- }
- // 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 {
- #[cfg(feature = "fmt")]
- eprintln!("checking behavior of {:02x}: {}", inst[0], buf);
- } else {
- #[cfg(feature = "fmt")]
- eprintln!("checking behavior of {:02x} {:02x}: {}", inst[0], inst[1], buf);
- }
- use yaxpeax_x86::long_mode::Opcode;
- // mov es, word [rax]
- // does an inf loop too...?
- if [Opcode::INS, Opcode::OUTS, Opcode::IN, Opcode::OUT].contains(&buf.opcode()) {
- #[cfg(feature = "fmt")]
- eprintln!("skipping {}", buf.opcode());
- continue;
+ let mut reader = U8Reader::new(&buf[..inst_len]);
+ if decoder.decode_into(&mut inst, &mut reader).is_ok() {
+ if !is_generic(&inst) {
+ continue;
+ }
+ // in a truly strange turn, running this test under build-o-tron gets
+ // an mmio to 0x14f4faf4 for `add word gs:[rcx], ax`?? but not when run
+ // again without tests. so i guess we just deny gs/fs prefixes?? wtf
+ if inst.prefixes.gs() || inst.prefixes.fs() {
+ continue;
+ }
+ vm.set_regs(&initial_regs).unwrap();
+ use yaxpeax_arch::LengthedInstruction;
+ let inst_len = 0.wrapping_offset(inst.len()) as usize;
+ let res = check_behavior(&mut vm, &buf[..inst_len]);
+ if matches!(res, Err(CheckErr::ComplexOp(_))) {
+ continue;
+ }
+ }
+ }
}
- vm.set_regs(&initial_regs).unwrap();
- check_behavior(&mut vm, &inst[..inst_len]).expect("behavior check is ok");
}
}
}