diff options
Diffstat (limited to 'test')
| -rw-r--r-- | test/long_mode/descriptions.rs | 335 | ||||
| -rw-r--r-- | test/long_mode/mod.rs | 2 | 
2 files changed, 337 insertions, 0 deletions
diff --git a/test/long_mode/descriptions.rs b/test/long_mode/descriptions.rs new file mode 100644 index 0000000..fc53acb --- /dev/null +++ b/test/long_mode/descriptions.rs @@ -0,0 +1,335 @@ +use std::fmt::Write; + +use yaxpeax_arch::{AddressBase, LengthedInstruction}; +use yaxpeax_arch::annotation::FieldDescription; +use yaxpeax_x86::long_mode::Opcode; +use yaxpeax_x86::long_mode::RegSpec; +use yaxpeax_x86::long_mode::InstDecoder; +use yaxpeax_x86::long_mode::Instruction; +use yaxpeax_x86::long_mode::InnerDescription; +use yaxpeax_arch::annotation::AnnotatingDecoder; + +fn test_annotations(data: &[u8], expected: &'static str, checks: &[AnnotationCheck]) { +    test_annotations_under(&InstDecoder::default(), data, expected, checks); +} + +// pair up field descriptions and the check that matched them. we'll use this for +// reporting errors if checks don't match up. +#[derive(PartialEq, Eq, Copy, Clone)] +enum CheckResult { +    Matched, +    Failed, +    Ignored, +} + +impl CheckResult { +    fn consumed_check(&self) -> bool { +        *self != CheckResult::Ignored +    } +} + +struct MatchResult { +    check: AnnotationCheck, +    result: CheckResult, +} + +#[derive(Clone)] +enum AnnotationCheck { +    // does not match any description; intended to assert that there should be no extra annotations +    // after the last check. +    NoExtra, +    Exact { +        // check the reported annotation matches this description +        desc: InnerDescription, +        start_bit: u32, +        end_bit: u32, +    }, +    Approximate { +        check: fn(&InnerDescription) -> bool, +        start_bit: u32, +        end_bit: u32, +    } +} + +impl std::fmt::Display for AnnotationCheck { +    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { +        match self { +            AnnotationCheck::NoExtra => { +                write!(f, "no further field descriptions expected") +            } +            AnnotationCheck::Exact { desc, start_bit, end_bit } => { +                write!(f, "bit {}:{}; {}", start_bit, end_bit, desc) +            } +            AnnotationCheck::Approximate { +                start_bit, end_bit, .. +            } => { +                write!(f, "bit {}:{}; (fn-based match)", start_bit, end_bit) +            } +        } +    } +} + +impl AnnotationCheck { +    fn exact(start: u32, end: u32, desc: InnerDescription) -> AnnotationCheck { +        AnnotationCheck::Exact { +            desc, +            start_bit: start, +            end_bit: end, +        } +    } + +    fn approximate(start: u32, end: u32, check: fn(&InnerDescription) -> bool) -> AnnotationCheck { +        AnnotationCheck::Approximate { +            check, +            start_bit: start, +            end_bit: end, +        } +    } + +    fn no_extra() -> AnnotationCheck { +        AnnotationCheck::NoExtra +    } + +    fn matches(&self, actual_start: u32, actual_end: u32, actual_desc: InnerDescription) -> CheckResult { +        match self { +            AnnotationCheck::NoExtra => { +                CheckResult::Failed +            }, +            AnnotationCheck::Exact { start_bit, end_bit, desc } => { +                let bits_match = *start_bit == actual_start && *end_bit == actual_end; +                let desc_match = desc == &actual_desc; +                let fail_anyway = match (desc, &actual_desc) { +                    // expect that there's only one `Number` field with a given name, so if the +                    // bits are wrong or the value is wrong, that's a guaranteed fail. +                    (InnerDescription::Number(expected_name, _), InnerDescription::Number(actual_name, _)) => { +                        if expected_name == actual_name { +                            true +                        } else { +                            false +                        } +                    } +                    // expect that there's only one opcode field. there won't be a second one that +                    // we might match on later. +                    (InnerDescription::Opcode(_), InnerDescription::Opcode(_)) => { +                        true +                    } +                    (_expected, _actual) => false +                }; + +                if (!bits_match && !desc_match) && !fail_anyway { +                   return CheckResult::Ignored; +                } + +                if bits_match && desc_match { +                    CheckResult::Matched +                } else { +                    CheckResult::Failed +                } +            }, +            AnnotationCheck::Approximate { start_bit, end_bit, check } => { +                let bits_match = *start_bit == actual_start && *end_bit == actual_end; +                let desc_match = check(&actual_desc); + +                if !bits_match && !desc_match { +                   return CheckResult::Ignored; +                } + +                if bits_match && desc_match { +                    CheckResult::Matched +                } else { +                    CheckResult::Failed +                } +            } +        } +    } +} + +impl std::fmt::Display for CheckResult { +    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { +        let s = match self { +            CheckResult::Matched => "\x1b[32mmatched\x1b[0m   ", +            CheckResult::Failed  => "\x1b[31mfailed\x1b[0m    ", +            CheckResult::Ignored => "\x1b[33mignored\x1b[0m", +        }; +        f.write_str(s) +    } +} + +fn test_annotations_under(decoder: &InstDecoder, data: &[u8], expected: &'static str, checks: &[AnnotationCheck]) { +    let mut hex = String::new(); +    for b in data { +        write!(hex, "{:02x}", b).unwrap(); +    } +    let mut reader = yaxpeax_arch::U8Reader::new(data); +    let mut sink = yaxpeax_arch::annotation::VecSink::new(); +    let mut inst = Instruction::default(); +    match decoder.decode_with_annotation(&mut inst, &mut reader, &mut sink) { +        Ok(()) => { +            let text = format!("{}", inst); +            assert!( +                text == expected, +                "display error for {}:\n  decoded: {:?} under decoder {}\n displayed: {}\n expected: {}\n", +                hex, +                inst, +                decoder, +                text, +                expected +            ); + +            let mut matches: Vec<((u32, u32, InnerDescription), Option<MatchResult>)> = Vec::new(); +            let mut extra_checks: Vec<AnnotationCheck> = Vec::new(); + +            sink.records.sort_by_key(|x| x.2.id()); +            let mut rec_iter = sink.records.iter(); +            let mut check_iter = checks.iter(); + +            let mut check = check_iter.next(); + +            let mut failed = false; + +            while let Some((bit_start, bit_end, desc)) = rec_iter.next().cloned() { +                if let Some(curr_check) = check { +                    if let AnnotationCheck::NoExtra = curr_check { +                        failed = true; +                    } + +                    let check_result = curr_check.matches(bit_start, bit_end, desc.desc().clone()); +                    if check_result == CheckResult::Failed { +                        failed = true; +                    } + +                    matches.push(((bit_start, bit_end, desc.desc().clone()), Some(MatchResult { +                        check: curr_check.clone(), +                        result: check_result +                    }))); + +                    if check_result.consumed_check() { +                        check = check_iter.next(); +                    } +                } else { +                    // no more checks, so we'll have passed the test at least. continue scooping up +                    // field descriptions into `matches` with no checks. +                    matches.push(((bit_start, bit_end, desc.desc().clone()), None)); +                } +            } + +            while let Some(missed_check) = check { +                check = check_iter.next(); +                if let AnnotationCheck::NoExtra = missed_check { +                    // "no extra" will be "missed" in that nothing matches it above. in the success +                    // case, it's a leftover check, and should be the only one remaining if the +                    // test is written correctly. so skip it here, and see if we've exhausted the +                    // list of checks.. +                    continue; +                } +                extra_checks.push(missed_check.clone()); +            } + +            if extra_checks.len() > 0 { +                failed = true; +            } + +            if failed { +                eprintln!("[!] annotation check for {}, `{}`, failed:", hex, inst); +                for ((bit_start, bit_end, desc), check) in matches { +                    let mut desc = format!("bit {}:{}; {}", bit_start, bit_end, desc); +                    while desc.len() < 60 { +                        desc.push(' '); +                    } +                    desc.push(' '); +                    let comment = match check { +                        None => { +                            "\x1b[34mno check\x1b[0m".to_owned() +                        } +                        Some(MatchResult { +                            result, +                            check +                        }) => { +                            if result == CheckResult::Ignored { +                                result.to_string() +                            } else { +                                format!("{}{}", result, check) +                            } +                        } +                    }; +                    eprintln!(" - {}{}", desc, comment); +                } +                for check in extra_checks { +                    eprintln!(" !  \x1b[31mextra check\x1b[0m: {}", check); +                } +            } +            assert!(!failed); + +            // while we're at it, test that the instruction is as long, and no longer, than its +            // input +            assert_eq!((0u64.wrapping_offset(inst.len()).to_linear()) as usize, data.len(), "instruction length is incorrect, wanted instruction {}", expected); +        }, +        Err(e) => { +            cfg_if::cfg_if! { +                if #[cfg(feature="fmt")] { +                    assert!(false, "decode error ({}) for {} under decoder {}:\n  expected: {}\n", e, hex, decoder, expected); +                } else { +                    // avoid the unused `e` warning +                    let _ = e; +                    assert!(false, "decode error (<non-fmt build>) for {} under decoder <non-fmt build>:\n  expected: {}\n", hex, expected); +                } +            } +        } +    } +} + +#[test] +fn test_modrm_decode() { +    test_annotations(&[0xff, 0xc0], "inc eax", &[ +        AnnotationCheck::exact(11, 13, InnerDescription::Opcode(Opcode::INC)), +        AnnotationCheck::approximate(0, 7, |desc| { desc.to_string().contains("ModRM_0xff_Ev") }), +        AnnotationCheck::approximate(14, 15, |desc| { +            desc.to_string().contains("mmm") && +            desc.to_string().contains("register number") && +            desc.to_string().contains("mod bits: 11") +        }), +//        AnnotationCheck::exact(8, 10, InnerDescription::RegisterNumber("mmm", 0, RegSpec::eax())), +//        AnnotationCheck::no_extra(), +    ]); +    test_annotations(&[0xc1, 0xe0, 0x03], "shl eax, 0x3", &[ +        AnnotationCheck::exact(11, 13, InnerDescription::Opcode(Opcode::SHL)), +        AnnotationCheck::exact(16, 23, InnerDescription::Number("imm", 3)), +        AnnotationCheck::approximate(0, 7, |desc| { desc.to_string().contains("ModRM_0xc1_Ev_Ib") }), +        AnnotationCheck::approximate(14, 15, |desc| { +            desc.to_string().contains("mmm") && +            desc.to_string().contains("register number") && +            desc.to_string().contains("mod bits: 11") +        }), +        AnnotationCheck::exact(8, 10, InnerDescription::RegisterNumber("mmm", 0, RegSpec::eax())), +        AnnotationCheck::no_extra(), +    ]); +    // just modrm +    test_annotations(&[0x33, 0x08], "xor ecx, dword [rax]", &[ +        AnnotationCheck::exact(0, 7, InnerDescription::Opcode(Opcode::XOR)), +        AnnotationCheck::approximate(0, 7, |desc| { desc.to_string() == "operand code `Gv_Ev`" }), +        AnnotationCheck::approximate(7, 7, |desc| { desc.to_string().contains("operands begin") }), +        AnnotationCheck::approximate(14, 15, |desc| { +            desc.to_string().contains("memory operand is [reg]") && +            desc.to_string().contains("mod bits: 00") +        }), +        AnnotationCheck::exact(11, 13, InnerDescription::RegisterNumber("rrr", 1, RegSpec::ecx())), +        AnnotationCheck::exact(8, 10, InnerDescription::RegisterNumber("mmm", 0, RegSpec::rax())), +        AnnotationCheck::no_extra(), +    ]); + +    // modrm + rex.w +    test_annotations(&[0x48, 0x33, 0x08], "xor rcx, qword [rax]", &[]); +    test_annotations(&[0x48, 0x33, 0x20], "xor rsp, qword [rax]", &[]); +    test_annotations(&[0x48, 0x33, 0x05, 0x78, 0x56, 0x34, 0x12], "xor rax, qword [rip + 0x12345678]", &[]); + +    // specifically sib with base == 0b101 +    // mod bits 00 +    test_annotations(&[0x42, 0x33, 0x34, 0x25, 0x20, 0x30, 0x40, 0x50], "xor esi, dword [r12 * 1 + 0x50403020]", &[]); +    test_annotations(&[0x43, 0x33, 0x34, 0x25, 0x20, 0x30, 0x40, 0x50], "xor esi, dword [r12 * 1 + 0x50403020]", &[]); +    // mod bits 01 +    test_annotations(&[0x42, 0x33, 0x74, 0x25, 0x20], "xor esi, dword [rbp + r12 * 1 + 0x20]", &[]); +    test_annotations(&[0x43, 0x33, 0x74, 0x25, 0x20], "xor esi, dword [r13 + r12 * 1 + 0x20]", &[]); +    // mod bits 10 +    test_annotations(&[0x42, 0x33, 0xb4, 0x25, 0x20, 0x30, 0x40, 0x50], "xor esi, dword [rbp + r12 * 1 + 0x50403020]", &[]); +    test_annotations(&[0x43, 0x33, 0xb4, 0x25, 0x20, 0x30, 0x40, 0x50], "xor esi, dword [r13 + r12 * 1 + 0x50403020]", &[]); +} diff --git a/test/long_mode/mod.rs b/test/long_mode/mod.rs index 2a11b79..eeee155 100644 --- a/test/long_mode/mod.rs +++ b/test/long_mode/mod.rs @@ -4,6 +4,8 @@ mod regspec;  mod operand;  #[cfg(feature="fmt")]  mod display; +#[cfg(feature="std")] +mod descriptions;  mod evex_generated;  mod reuse_test;  | 
