use std::collections::HashSet; use anyhow::Result; use crate::{ config::SymbolMappings, diff::{ code::{diff_code, no_diff_code, process_code_symbol}, data::{ diff_bss_section, diff_bss_symbol, diff_data_section, diff_data_symbol, diff_generic_section, no_diff_symbol, }, }, obj::{ ObjInfo, ObjIns, ObjReloc, ObjSection, ObjSectionKind, ObjSymbol, SymbolRef, SECTION_COMMON, }, }; pub mod code; pub mod data; pub mod display; include!(concat!(env!("OUT_DIR"), "/config.gen.rs")); impl DiffObjConfig { pub fn separator(&self) -> &'static str { if self.space_between_args { ", " } else { "," } } } #[derive(Debug, Clone)] pub struct ObjSectionDiff { pub symbols: Vec, pub data_diff: Vec, pub match_percent: Option, } impl ObjSectionDiff { fn merge(&mut self, other: ObjSectionDiff) { // symbols ignored self.data_diff = other.data_diff; self.match_percent = other.match_percent; } } #[derive(Debug, Clone, Default)] pub struct ObjSymbolDiff { /// The symbol ref this object pub symbol_ref: SymbolRef, /// The symbol ref in the _other_ object that this symbol was diffed against pub target_symbol: Option, pub instructions: Vec, pub match_percent: Option, } #[derive(Debug, Clone, Default)] pub struct ObjInsDiff { pub ins: Option, /// Diff kind pub kind: ObjInsDiffKind, /// Branches from instruction pub branch_from: Option, /// Branches to instruction pub branch_to: Option, /// Arg diffs (only contains non-PlainText args) pub arg_diff: Vec>, } #[derive(Debug, Copy, Clone, Eq, PartialEq, Default)] pub enum ObjInsDiffKind { #[default] None, OpMismatch, ArgMismatch, Replace, Delete, Insert, } #[derive(Debug, Clone, Default)] pub struct ObjDataDiff { pub data: Vec, pub kind: ObjDataDiffKind, pub len: usize, pub symbol: String, pub reloc: Option, } #[derive(Debug, Copy, Clone, Eq, PartialEq, Default)] pub enum ObjDataDiffKind { #[default] None, Replace, Delete, Insert, } #[derive(Debug, Copy, Clone)] pub struct ObjInsArgDiff { /// Incrementing index for coloring pub idx: usize, } #[derive(Debug, Clone)] pub struct ObjInsBranchFrom { /// Source instruction indices pub ins_idx: Vec, /// Incrementing index for coloring pub branch_idx: usize, } #[derive(Debug, Clone)] pub struct ObjInsBranchTo { /// Target instruction index pub ins_idx: usize, /// Incrementing index for coloring pub branch_idx: usize, } #[derive(Default)] pub struct ObjDiff { /// A list of all section diffs in the object. pub sections: Vec, /// Common BSS symbols don't live in a section, so they're stored separately. pub common: Vec, /// If `selecting_left` or `selecting_right` is set, this is the list of symbols /// that are being mapped to the other object. pub mapping_symbols: Vec, } impl ObjDiff { pub fn new_from_obj(obj: &ObjInfo) -> Self { let mut result = Self { sections: Vec::with_capacity(obj.sections.len()), common: Vec::with_capacity(obj.common.len()), mapping_symbols: vec![], }; for (section_idx, section) in obj.sections.iter().enumerate() { let mut symbols = Vec::with_capacity(section.symbols.len()); for (symbol_idx, _) in section.symbols.iter().enumerate() { symbols.push(ObjSymbolDiff { symbol_ref: SymbolRef { section_idx, symbol_idx }, target_symbol: None, instructions: vec![], match_percent: None, }); } result.sections.push(ObjSectionDiff { symbols, data_diff: vec![ObjDataDiff { data: section.data.clone(), kind: ObjDataDiffKind::None, len: section.data.len(), symbol: section.name.clone(), ..Default::default() }], match_percent: None, }); } for (symbol_idx, _) in obj.common.iter().enumerate() { result.common.push(ObjSymbolDiff { symbol_ref: SymbolRef { section_idx: SECTION_COMMON, symbol_idx }, target_symbol: None, instructions: vec![], match_percent: None, }); } result } #[inline] pub fn section_diff(&self, section_idx: usize) -> &ObjSectionDiff { &self.sections[section_idx] } #[inline] pub fn section_diff_mut(&mut self, section_idx: usize) -> &mut ObjSectionDiff { &mut self.sections[section_idx] } #[inline] pub fn symbol_diff(&self, symbol_ref: SymbolRef) -> &ObjSymbolDiff { if symbol_ref.section_idx == SECTION_COMMON { &self.common[symbol_ref.symbol_idx] } else { &self.section_diff(symbol_ref.section_idx).symbols[symbol_ref.symbol_idx] } } #[inline] pub fn symbol_diff_mut(&mut self, symbol_ref: SymbolRef) -> &mut ObjSymbolDiff { if symbol_ref.section_idx == SECTION_COMMON { &mut self.common[symbol_ref.symbol_idx] } else { &mut self.section_diff_mut(symbol_ref.section_idx).symbols[symbol_ref.symbol_idx] } } } #[derive(Default)] pub struct DiffObjsResult { pub left: Option, pub right: Option, pub prev: Option, } pub fn diff_objs( diff_config: &DiffObjConfig, mapping_config: &MappingConfig, left: Option<&ObjInfo>, right: Option<&ObjInfo>, prev: Option<&ObjInfo>, ) -> Result { let symbol_matches = matching_symbols(left, right, prev, mapping_config)?; let section_matches = matching_sections(left, right)?; let mut left = left.map(|p| (p, ObjDiff::new_from_obj(p))); let mut right = right.map(|p| (p, ObjDiff::new_from_obj(p))); let mut prev = prev.map(|p| (p, ObjDiff::new_from_obj(p))); for symbol_match in symbol_matches { match symbol_match { SymbolMatch { left: Some(left_symbol_ref), right: Some(right_symbol_ref), prev: prev_symbol_ref, section_kind, } => { let (left_obj, left_out) = left.as_mut().unwrap(); let (right_obj, right_out) = right.as_mut().unwrap(); match section_kind { ObjSectionKind::Code => { let left_code = process_code_symbol(left_obj, left_symbol_ref, diff_config)?; let right_code = process_code_symbol(right_obj, right_symbol_ref, diff_config)?; let (left_diff, right_diff) = diff_code( left_obj, right_obj, &left_code, &right_code, left_symbol_ref, right_symbol_ref, diff_config, )?; *left_out.symbol_diff_mut(left_symbol_ref) = left_diff; *right_out.symbol_diff_mut(right_symbol_ref) = right_diff; if let Some(prev_symbol_ref) = prev_symbol_ref { let (prev_obj, prev_out) = prev.as_mut().unwrap(); let prev_code = process_code_symbol(prev_obj, prev_symbol_ref, diff_config)?; let (_, prev_diff) = diff_code( left_obj, right_obj, &right_code, &prev_code, right_symbol_ref, prev_symbol_ref, diff_config, )?; *prev_out.symbol_diff_mut(prev_symbol_ref) = prev_diff; } } ObjSectionKind::Data => { let (left_diff, right_diff) = diff_data_symbol( left_obj, right_obj, left_symbol_ref, right_symbol_ref, )?; *left_out.symbol_diff_mut(left_symbol_ref) = left_diff; *right_out.symbol_diff_mut(right_symbol_ref) = right_diff; } ObjSectionKind::Bss => { let (left_diff, right_diff) = diff_bss_symbol( left_obj, right_obj, left_symbol_ref, right_symbol_ref, )?; *left_out.symbol_diff_mut(left_symbol_ref) = left_diff; *right_out.symbol_diff_mut(right_symbol_ref) = right_diff; } } } SymbolMatch { left: Some(left_symbol_ref), right: None, prev: _, section_kind } => { let (left_obj, left_out) = left.as_mut().unwrap(); match section_kind { ObjSectionKind::Code => { let code = process_code_symbol(left_obj, left_symbol_ref, diff_config)?; *left_out.symbol_diff_mut(left_symbol_ref) = no_diff_code(&code, left_symbol_ref)?; } ObjSectionKind::Data | ObjSectionKind::Bss => { *left_out.symbol_diff_mut(left_symbol_ref) = no_diff_symbol(left_obj, left_symbol_ref); } } } SymbolMatch { left: None, right: Some(right_symbol_ref), prev: _, section_kind } => { let (right_obj, right_out) = right.as_mut().unwrap(); match section_kind { ObjSectionKind::Code => { let code = process_code_symbol(right_obj, right_symbol_ref, diff_config)?; *right_out.symbol_diff_mut(right_symbol_ref) = no_diff_code(&code, right_symbol_ref)?; } ObjSectionKind::Data | ObjSectionKind::Bss => { *right_out.symbol_diff_mut(right_symbol_ref) = no_diff_symbol(right_obj, right_symbol_ref); } } } SymbolMatch { left: None, right: None, .. } => { // Should not happen } } } for section_match in section_matches { if let SectionMatch { left: Some(left_section_idx), right: Some(right_section_idx), section_kind, } = section_match { let (left_obj, left_out) = left.as_mut().unwrap(); let (right_obj, right_out) = right.as_mut().unwrap(); let left_section = &left_obj.sections[left_section_idx]; let right_section = &right_obj.sections[right_section_idx]; match section_kind { ObjSectionKind::Code => { let left_section_diff = left_out.section_diff(left_section_idx); let right_section_diff = right_out.section_diff(right_section_idx); let (left_diff, right_diff) = diff_generic_section( left_section, right_section, left_section_diff, right_section_diff, )?; left_out.section_diff_mut(left_section_idx).merge(left_diff); right_out.section_diff_mut(right_section_idx).merge(right_diff); } ObjSectionKind::Data => { let left_section_diff = left_out.section_diff(left_section_idx); let right_section_diff = right_out.section_diff(right_section_idx); let (left_diff, right_diff) = diff_data_section( left_obj, right_obj, left_section, right_section, left_section_diff, right_section_diff, )?; left_out.section_diff_mut(left_section_idx).merge(left_diff); right_out.section_diff_mut(right_section_idx).merge(right_diff); } ObjSectionKind::Bss => { let left_section_diff = left_out.section_diff(left_section_idx); let right_section_diff = right_out.section_diff(right_section_idx); let (left_diff, right_diff) = diff_bss_section( left_section, right_section, left_section_diff, right_section_diff, )?; left_out.section_diff_mut(left_section_idx).merge(left_diff); right_out.section_diff_mut(right_section_idx).merge(right_diff); } } } } if let (Some((right_obj, right_out)), Some((left_obj, left_out))) = (right.as_mut(), left.as_mut()) { if let Some(right_name) = &mapping_config.selecting_left { generate_mapping_symbols(right_obj, right_name, left_obj, left_out, diff_config)?; } if let Some(left_name) = &mapping_config.selecting_right { generate_mapping_symbols(left_obj, left_name, right_obj, right_out, diff_config)?; } } Ok(DiffObjsResult { left: left.map(|(_, o)| o), right: right.map(|(_, o)| o), prev: prev.map(|(_, o)| o), }) } /// When we're selecting a symbol to use as a comparison, we'll create comparisons for all /// symbols in the other object that match the selected symbol's section and kind. This allows /// us to display match percentages for all symbols in the other object that could be selected. fn generate_mapping_symbols( base_obj: &ObjInfo, base_name: &str, target_obj: &ObjInfo, target_out: &mut ObjDiff, config: &DiffObjConfig, ) -> Result<()> { let Some(base_symbol_ref) = symbol_ref_by_name(base_obj, base_name) else { return Ok(()); }; let (base_section, _base_symbol) = base_obj.section_symbol(base_symbol_ref); let Some(base_section) = base_section else { return Ok(()); }; let base_code = match base_section.kind { ObjSectionKind::Code => Some(process_code_symbol(base_obj, base_symbol_ref, config)?), _ => None, }; for (target_section_index, target_section) in target_obj.sections.iter().enumerate().filter(|(_, s)| s.kind == base_section.kind) { for (target_symbol_index, _target_symbol) in target_section.symbols.iter().enumerate() { let target_symbol_ref = SymbolRef { section_idx: target_section_index, symbol_idx: target_symbol_index }; match base_section.kind { ObjSectionKind::Code => { let target_code = process_code_symbol(target_obj, target_symbol_ref, config)?; let (left_diff, _right_diff) = diff_code( target_obj, base_obj, &target_code, base_code.as_ref().unwrap(), target_symbol_ref, base_symbol_ref, config, )?; target_out.mapping_symbols.push(left_diff); } ObjSectionKind::Data => { let (left_diff, _right_diff) = diff_data_symbol(target_obj, base_obj, target_symbol_ref, base_symbol_ref)?; target_out.mapping_symbols.push(left_diff); } ObjSectionKind::Bss => { let (left_diff, _right_diff) = diff_bss_symbol(target_obj, base_obj, target_symbol_ref, base_symbol_ref)?; target_out.mapping_symbols.push(left_diff); } } } } Ok(()) } #[derive(Copy, Clone, Eq, PartialEq)] struct SymbolMatch { left: Option, right: Option, prev: Option, section_kind: ObjSectionKind, } #[derive(Copy, Clone, Eq, PartialEq)] struct SectionMatch { left: Option, right: Option, section_kind: ObjSectionKind, } #[derive(Debug, Clone, Default, serde::Deserialize, serde::Serialize)] #[cfg_attr(feature = "wasm", derive(tsify_next::Tsify), tsify(from_wasm_abi))] #[serde(default)] pub struct MappingConfig { /// Manual symbol mappings pub mappings: SymbolMappings, /// The right object symbol name that we're selecting a left symbol for pub selecting_left: Option, /// The left object symbol name that we're selecting a right symbol for pub selecting_right: Option, } fn symbol_ref_by_name(obj: &ObjInfo, name: &str) -> Option { for (section_idx, section) in obj.sections.iter().enumerate() { for (symbol_idx, symbol) in section.symbols.iter().enumerate() { if symbol.name == name { return Some(SymbolRef { section_idx, symbol_idx }); } } } None } fn apply_symbol_mappings( left: &ObjInfo, right: &ObjInfo, mapping_config: &MappingConfig, left_used: &mut HashSet, right_used: &mut HashSet, matches: &mut Vec, ) -> Result<()> { // If we're selecting a symbol to use as a comparison, mark it as used // This ensures that we don't match it to another symbol at any point if let Some(left_name) = &mapping_config.selecting_left { if let Some(left_symbol) = symbol_ref_by_name(left, left_name) { left_used.insert(left_symbol); } } if let Some(right_name) = &mapping_config.selecting_right { if let Some(right_symbol) = symbol_ref_by_name(right, right_name) { right_used.insert(right_symbol); } } // Apply manual symbol mappings for (left_name, right_name) in &mapping_config.mappings { let Some(left_symbol) = symbol_ref_by_name(left, left_name) else { continue; }; if left_used.contains(&left_symbol) { continue; } let Some(right_symbol) = symbol_ref_by_name(right, right_name) else { continue; }; if right_used.contains(&right_symbol) { continue; } let left_section = &left.sections[left_symbol.section_idx]; let right_section = &right.sections[right_symbol.section_idx]; if left_section.kind != right_section.kind { log::warn!( "Symbol section kind mismatch: {} ({:?}) vs {} ({:?})", left_name, left_section.kind, right_name, right_section.kind ); continue; } matches.push(SymbolMatch { left: Some(left_symbol), right: Some(right_symbol), prev: None, // TODO section_kind: left_section.kind, }); left_used.insert(left_symbol); right_used.insert(right_symbol); } Ok(()) } /// Find matching symbols between each object. fn matching_symbols( left: Option<&ObjInfo>, right: Option<&ObjInfo>, prev: Option<&ObjInfo>, mappings: &MappingConfig, ) -> Result> { let mut matches = Vec::new(); let mut left_used = HashSet::new(); let mut right_used = HashSet::new(); if let Some(left) = left { if let Some(right) = right { apply_symbol_mappings( left, right, mappings, &mut left_used, &mut right_used, &mut matches, )?; } for (section_idx, section) in left.sections.iter().enumerate() { for (symbol_idx, symbol) in section.symbols.iter().enumerate() { let symbol_ref = SymbolRef { section_idx, symbol_idx }; if left_used.contains(&symbol_ref) { continue; } let symbol_match = SymbolMatch { left: Some(symbol_ref), right: find_symbol(right, symbol, section, Some(&right_used)), prev: find_symbol(prev, symbol, section, None), section_kind: section.kind, }; matches.push(symbol_match); if let Some(right) = symbol_match.right { right_used.insert(right); } } } for (symbol_idx, symbol) in left.common.iter().enumerate() { let symbol_ref = SymbolRef { section_idx: SECTION_COMMON, symbol_idx }; if left_used.contains(&symbol_ref) { continue; } let symbol_match = SymbolMatch { left: Some(symbol_ref), right: find_common_symbol(right, symbol), prev: find_common_symbol(prev, symbol), section_kind: ObjSectionKind::Bss, }; matches.push(symbol_match); if let Some(right) = symbol_match.right { right_used.insert(right); } } } if let Some(right) = right { for (section_idx, section) in right.sections.iter().enumerate() { for (symbol_idx, symbol) in section.symbols.iter().enumerate() { let symbol_ref = SymbolRef { section_idx, symbol_idx }; if right_used.contains(&symbol_ref) { continue; } matches.push(SymbolMatch { left: None, right: Some(symbol_ref), prev: find_symbol(prev, symbol, section, None), section_kind: section.kind, }); } } for (symbol_idx, symbol) in right.common.iter().enumerate() { let symbol_ref = SymbolRef { section_idx: SECTION_COMMON, symbol_idx }; if right_used.contains(&symbol_ref) { continue; } matches.push(SymbolMatch { left: None, right: Some(symbol_ref), prev: find_common_symbol(prev, symbol), section_kind: ObjSectionKind::Bss, }); } } Ok(matches) } fn unmatched_symbols<'section, 'used>( section: &'section ObjSection, section_idx: usize, used: Option<&'used HashSet>, ) -> impl Iterator + 'used where 'section: 'used, { section.symbols.iter().enumerate().filter(move |&(symbol_idx, _)| { // Skip symbols that have already been matched !used.map(|u| u.contains(&SymbolRef { section_idx, symbol_idx })).unwrap_or(false) }) } fn find_symbol( obj: Option<&ObjInfo>, in_symbol: &ObjSymbol, in_section: &ObjSection, used: Option<&HashSet>, ) -> Option { let obj = obj?; // Try to find an exact name match for (section_idx, section) in obj.sections.iter().enumerate() { if section.kind != in_section.kind { continue; } if let Some((symbol_idx, _)) = unmatched_symbols(section, section_idx, used) .find(|(_, symbol)| symbol.name == in_symbol.name) { return Some(SymbolRef { section_idx, symbol_idx }); } } // Match compiler-generated symbols against each other (e.g. @251 -> @60) // If they are at the same address in the same section if in_symbol.name.starts_with('@') && matches!(in_section.kind, ObjSectionKind::Data | ObjSectionKind::Bss) { if let Some((section_idx, section)) = obj.sections.iter().enumerate().find(|(_, s)| s.name == in_section.name) { if let Some((symbol_idx, _)) = unmatched_symbols(section, section_idx, used).find(|(_, symbol)| { symbol.address == in_symbol.address && symbol.name.starts_with('@') }) { return Some(SymbolRef { section_idx, symbol_idx }); } } } // Match Metrowerks symbol$1234 against symbol$2345 if let Some((prefix, suffix)) = in_symbol.name.split_once('$') { if !suffix.chars().all(char::is_numeric) { return None; } for (section_idx, section) in obj.sections.iter().enumerate() { if section.kind != in_section.kind { continue; } if let Some((symbol_idx, _)) = unmatched_symbols(section, section_idx, used).find(|&(_, symbol)| { if let Some((p, s)) = symbol.name.split_once('$') { prefix == p && s.chars().all(char::is_numeric) } else { false } }) { return Some(SymbolRef { section_idx, symbol_idx }); } } } None } fn find_common_symbol(obj: Option<&ObjInfo>, in_symbol: &ObjSymbol) -> Option { let obj = obj?; for (symbol_idx, symbol) in obj.common.iter().enumerate() { if symbol.name == in_symbol.name { return Some(SymbolRef { section_idx: SECTION_COMMON, symbol_idx }); } } None } /// Find matching sections between each object. fn matching_sections(left: Option<&ObjInfo>, right: Option<&ObjInfo>) -> Result> { let mut matches = Vec::new(); if let Some(left) = left { for (section_idx, section) in left.sections.iter().enumerate() { matches.push(SectionMatch { left: Some(section_idx), right: find_section(right, §ion.name, section.kind), section_kind: section.kind, }); } } if let Some(right) = right { for (section_idx, section) in right.sections.iter().enumerate() { if matches.iter().any(|m| m.right == Some(section_idx)) { continue; } matches.push(SectionMatch { left: None, right: Some(section_idx), section_kind: section.kind, }); } } Ok(matches) } fn find_section(obj: Option<&ObjInfo>, name: &str, section_kind: ObjSectionKind) -> Option { for (section_idx, section) in obj?.sections.iter().enumerate() { if section.kind != section_kind { continue; } if section.name == name { return Some(section_idx); } } None }