diff --git a/objdiff-core/src/arch/mod.rs b/objdiff-core/src/arch/mod.rs index fa41a744..3d54ed1d 100644 --- a/objdiff-core/src/arch/mod.rs +++ b/objdiff-core/src/arch/mod.rs @@ -420,6 +420,16 @@ pub trait Arch: Any + Debug + Send + Sync { cb: &mut dyn FnMut(InstructionPart) -> Result<()>, ) -> Result<()>; + /// Return formatting-token structure that must match for two instructions to be considered equal. + /// Architectures that encode operand structure in their formatted output can override this. + fn instruction_comparison_key( + &self, + _resolved: ResolvedInstructionRef, + _diff_config: &DiffObjConfig, + ) -> Result>> { + Ok(None) + } + /// Generate a list of fake relocations from the given code that represent pooled data accesses. fn generate_pooled_relocations( &self, diff --git a/objdiff-core/src/arch/x86.rs b/objdiff-core/src/arch/x86.rs index b02bd0ee..0ce75b9d 100644 --- a/objdiff-core/src/arch/x86.rs +++ b/objdiff-core/src/arch/x86.rs @@ -1,4 +1,4 @@ -use alloc::{boxed::Box, format, vec::Vec}; +use alloc::{boxed::Box, format, string::String, vec::Vec}; use core::cmp::Ordering; use anyhow::{Context, Result, anyhow, bail}; @@ -102,6 +102,25 @@ impl ArchX86 { } impl Arch for ArchX86 { + fn instruction_comparison_key( + &self, + resolved: ResolvedInstructionRef, + diff_config: &DiffObjConfig, + ) -> Result>> { + let mut key = Vec::new(); + self.display_instruction(resolved, diff_config, &mut |part| { + match part { + InstructionPart::Basic(text) if !text.trim().is_empty() => { + key.push(format!("basic:{text}")); + } + InstructionPart::Arg(_) => key.push("arg".into()), + _ => {} + } + Ok(()) + })?; + Ok(Some(key)) + } + fn scan_instructions_internal( &self, address: u64, @@ -891,4 +910,31 @@ mod test { InstructionPart::unsigned(0xABu64), ]); } + + #[test] + fn test_reported_mov_operand_pair_has_distinct_argument_structure() { + let concrete_arch = + ArchX86 { arch: Architecture::X86, endianness: object::Endianness::Little }; + let arch: &dyn Arch = &concrete_arch; + let left_code = [0x8b, 0x03]; // mov eax, [ebx] + let right_code = [0x89, 0x18]; // mov [eax], ebx + let comparison_key = |code: &[u8]| { + arch.instruction_comparison_key( + ResolvedInstructionRef { + ins_ref: InstructionRef { + address: 0, + size: 2, + opcode: iced_x86::Mnemonic::Mov as u16, + branch_dest: None, + }, + code, + ..Default::default() + }, + &DiffObjConfig::default(), + ) + .unwrap() + .unwrap() + }; + assert_ne!(comparison_key(&left_code), comparison_key(&right_code)); + } } diff --git a/objdiff-core/src/diff/code.rs b/objdiff-core/src/diff/code.rs index 9cd5ea56..169f112d 100644 --- a/objdiff-core/src/diff/code.rs +++ b/objdiff-core/src/diff/code.rs @@ -513,6 +513,15 @@ fn diff_instruction( result.right_args_diff.push(InstructionArgDiffIndex::new(b_diff)); } } + if let (Some(left_key), Some(right_key)) = ( + left_obj.arch.instruction_comparison_key(left_resolved, diff_config)?, + right_obj.arch.instruction_comparison_key(right_resolved, diff_config)?, + ) && left_key != right_key + && result.kind == InstructionDiffKind::None + { + state.diff_score += PENALTY_REG_DIFF; + result.kind = InstructionDiffKind::ArgMismatch; + } if result.kind == InstructionDiffKind::None && left_resolved.code.len() != right_resolved.code.len() {