use yaxpeax_arch::Decoder; use yaxpeax_power::{InstDecoder, DecodeError}; use crate::{ParsedInstr, ParsedOperand}; use crate::tools; fn yax_decode(bytes: &[u8]) -> String { let decoder = InstDecoder::default() .with_be(false); let mut reader = yaxpeax_arch::U8Reader::new(&bytes); let decode_res = decoder.decode(&mut reader); format!("{}", decode_res.unwrap()) } fn compare_objdump(bytes: &[u8]) { let objdump_text = tools::objdump(bytes).unwrap(); eprintln!("objdump says: {:?}", objdump_text); let yax_text = yax_decode(bytes); eprintln!("yax says: {:?}", yax_text); assert_eq!( ParsedInstr::from_str(objdump_text.as_str()).unwrap(), ParsedInstr::from_str(yax_text.as_str()).unwrap(), ); } #[test] fn test_general() { // this is `bdzflr+ 4*cr7+gt`. on gekko, there was no bh field, but objdump prints one anyway. // yax doesn't, so the operand count mismatches, and objdump is sad. also, how does this get a // + in the modern hinting scheme? // compare_objdump(&[0x20, 0x18, 0x7d, 0x4c]); compare_objdump(&[0x04, 0x06, 0xe0, 0x13]); compare_objdump(&[0x44, 0xfe, 0x00, 0x10]); compare_objdump(&[0x64, 0x01, 0x00, 0x7f]); compare_objdump(&[0x24, 0x01, 0x60, 0x7d]); compare_objdump(&[0x82, 0x13, 0xc6, 0x4c]); compare_objdump(&[0x3d, 0x60, 0x82, 0x70]); compare_objdump(&[0x21, 0x04, 0x06, 0x4e]); } // a blanket ppc64le test won't run because it will decode vsx instructions which yax doesn't // know.. #[ignore] #[test] fn test_vs_objdump() { eprintln!("objdump says {}", tools::objdump(&[0x82, 0x51, 0x0d, 0x4c]).unwrap()); let decoder = InstDecoder::default() .with_be(false); let mut reader = yaxpeax_arch::U8Reader::new(&[0x82, 0x51, 0x0d, 0x4c]); let decode_res = decoder.decode(&mut reader).unwrap(); eprintln!("i says {}", decode_res); } // a blanket ppc64le test won't run because it will decode vsx instructions which yax doesn't // know.. #[ignore] #[test] fn differential_objdump_ppc64le() { use crate::ParsedInstr; let decoder = InstDecoder::default() .with_be(false); let ranges: [std::ops::Range; 3] = [ 0x7c_00_00_00..0x7d_00_00_00, 0xec_00_00_00..0xed_00_00_00, 0xfc_00_00_00..0xfd_00_00_00, ]; for range in ranges { for word in range { // u32::MAX { let bytes = word.to_le_bytes(); if word & 0x0000_1fff == 0 { eprintln!("{:#x?}", bytes); } let objdump_text = tools::objdump(&bytes).unwrap(); let mut reader = yaxpeax_arch::U8Reader::new(&bytes); let decode_res = decoder.decode(&mut reader); let instr = match decode_res { Err(e) => { // if objdump failed to decode and we failed to decode, move on; we agree! if objdump_text.contains("\t.long ") { continue; } let parsed_objdump = match ParsedInstr::from_str(&objdump_text) { Ok(parse) => parse, Err(e) => { panic!("couldn't parse objdump ({}), because {}", objdump_text, e); } }; eprintln!("objdump says: {:?}", parsed_objdump); if e == DecodeError::IncompleteDecoder { // attn and friends continue; } panic!("failed to decode {:02x}{:02x}{:02x}{:02x}: {}", bytes[0], bytes[1], bytes[2], bytes[3], e); }, Ok(instr) => instr, }; let displayed = format!("{}", instr); if objdump_text.contains("\t.long ") { // objdump rejected but we did not. the vast majority of this is objdump being picky // about the low bit (rc, lk etc) of the word, but manuals say it's a don't-care. // that's not *All* cases, so this is overly broad to try helping the overall SNR... continue; } let parsed_objdump = ParsedInstr::from_str(&objdump_text).expect("can parse objdump"); let parsed_yax = ParsedInstr::from_str(&displayed).expect("can parse decoder output"); if !acceptable_match(&parsed_objdump, &parsed_yax) { eprintln!("miss {:02x}{:02x}{:02x}{:02x}: {} vs {}", bytes[0], bytes[1], bytes[2], bytes[3], objdump_text, displayed); // missed += 1; } else { // eprintln!("you: {}, me: {}", objdump_text, displayed); } } } } fn acceptable_match(objdump: &ParsedInstr, yax: &ParsedInstr) -> bool { if objdump == yax { return true; } if objdump.mnemonic == yax.mnemonic && (yax.mnemonic == "mtfsfi" || yax.mnemonic == "mtfsfi.") { // TDOO: for the first operand, yax prints "fpscrf", but objdump just prints an immediate.. // so just compare the second. would be better to assert the same numeric value at least? if objdump.operands[1] == yax.operands[1] { return true; } } if objdump.mnemonic == yax.mnemonic && ["fre", "frsqrte", "fre.", "frsqrte."].contains(&yax.mnemonic.as_str()) { // TODO: objdump prints a third immediate sometimes if bit 15 is set, an immediate 0 or 1 // which yax ignores. if yax.operands == objdump.operands[..2] { return true; } } if objdump.operands.len() == yax.operands.len() { static LEGACY_MNEMONICS: &'static [(&'static str, &'static str)] = &[ ("lswi", "lsi"), ("lswx", "lsx"), ("lwbrx", "lbrx"), ("l", "lwz"), ("lu", "lwzu"), ("lux", "lwzux"), ("stsi", "stswi"), ("stsx", "stswx"), ("stu", "stwu"), ("stux", "stwux"), ("stx", "stwx"), ("lsx", "lswx"), ("lsi", "lswi"), ]; for (old, new) in LEGACY_MNEMONICS.iter() { if objdump.mnemonic == *old && yax.mnemonic == *new { return true; } } } if objdump.mnemonic == yax.mnemonic && objdump.operands.len() == yax.operands.len() { if objdump.operands.len() == 2 { if objdump.operands[0] == yax.operands[0] && objdump.operands[1] == ParsedOperand::Immediate(0) && yax.operands[1] == ParsedOperand::Reg(0) { return true; } else if objdump.operands[1] == yax.operands[1] && objdump.operands[0] == ParsedOperand::Immediate(0) && yax.operands[0] == ParsedOperand::Reg(0) { return true; } } else if yax.operands.len() == 4 { // objdump is at *least* 3 operands ... if objdump.operands[0] == yax.operands[0] && objdump.operands[2] == yax.operands[2] && objdump.operands[3] == yax.operands[3] { if objdump.operands[1] == ParsedOperand::Immediate(0) && yax.operands[1] == ParsedOperand::Reg(0) { return true; } } } else if objdump.operands.len() == 3 { if objdump.operands[0] == yax.operands[0] && objdump.operands[2] == yax.operands[2] && objdump.operands[1] == ParsedOperand::Immediate(0) && yax.operands[1] == ParsedOperand::Reg(0) { return true; } } } if yax.mnemonic == objdump.mnemonic && ["lwarx", "ldarx"].contains(&yax.mnemonic.as_str()) { // objdump is at *least* 3 operands ... if objdump.operands[0] == yax.operands[0] && objdump.operands[2] == yax.operands[2] && objdump.operands.len() == 3 && yax.operands[3] == ParsedOperand::Immediate(0) { if objdump.operands[1] == ParsedOperand::Immediate(0) && yax.operands[1] == ParsedOperand::Reg(0) { return true; } } } if yax.mnemonic == objdump.mnemonic && yax.mnemonic == "dcbtls" && yax.operands[1] == ParsedOperand::Reg(0) && yax.operands[0] == objdump.operands[0] && yax.operands[2] == objdump.operands[1] { return true; } if (yax.mnemonic == objdump.mnemonic && yax.mnemonic == "mbar") || (yax.mnemonic == "eieio" && objdump.mnemonic == "mbar") { // yax prints TH, objdump doesn't print it if objdump.operands.len() == 0 && yax.operands.len() <= 1 { return true; } } false } #[test] fn compare_objdump_acceptable_match() { assert!(acceptable_match( &ParsedInstr::from_str("stvewx v0,0,r0").unwrap(), &ParsedInstr::from_str("stvewx vr0, r0, r0").unwrap(), )); assert!(acceptable_match( &ParsedInstr::from_str("fre f0,f13,1").unwrap(), &ParsedInstr::from_str("fre fr0, fr13").unwrap(), )); assert!(acceptable_match( &ParsedInstr::from_str("mbar").unwrap(), &ParsedInstr::from_str("eieio").unwrap(), )); assert!(acceptable_match( &ParsedInstr::from_str("mbar").unwrap(), &ParsedInstr::from_str("mbar 0").unwrap(), )); }