diff options
Diffstat (limited to 'test/long_mode')
| -rw-r--r-- | test/long_mode/behavior.rs | 294 |
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(®s); -// 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(®s).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"); } } } |
