From 53440fe27e2f7150a86742fbd9f628b450270ba8 Mon Sep 17 00:00:00 2001 From: Fabian Lippold Date: Fri, 4 Sep 2026 05:29:04 +0200 Subject: [PATCH] Scope one-shot diffs to the requested symbol Use a symbol-specific core diff path when a positional symbol is provided. This avoids processing unrelated symbols and sections, preventing malformed data symbols from aborting output and substantially reducing work for large binaries while preserving object-wide indexes. --- objdiff-cli/src/cmd/diff.rs | 13 ++++- objdiff-core/src/diff/mod.rs | 94 ++++++++++++++++++++++++++++++ objdiff-core/tests/arch_x86.rs | 101 +++++++++++++++++++++++++++++++++ 3 files changed, 206 insertions(+), 2 deletions(-) diff --git a/objdiff-cli/src/cmd/diff.rs b/objdiff-cli/src/cmd/diff.rs index 96fb454f..15d43d43 100644 --- a/objdiff-cli/src/cmd/diff.rs +++ b/objdiff-cli/src/cmd/diff.rs @@ -212,8 +212,17 @@ fn run_oneshot( .with_context(|| format!("Loading {p}")) }) .transpose()?; - let result = - diff::diff_objs(target.as_ref(), base.as_ref(), None, &diff_config, &mapping_config)?; + let result = if let Some(symbol_name) = args.symbol.as_deref() { + diff::diff_objs_for_symbol( + target.as_ref(), + base.as_ref(), + symbol_name, + &diff_config, + &mapping_config, + )? + } else { + diff::diff_objs(target.as_ref(), base.as_ref(), None, &diff_config, &mapping_config)? + }; let left = target.as_ref().zip(result.left.as_ref()); let right = base.as_ref().zip(result.right.as_ref()); let diff_result = DiffResult::new(left, right, &diff_config)?; diff --git a/objdiff-core/src/diff/mod.rs b/objdiff-core/src/diff/mod.rs index f82242df..ff49c2c6 100644 --- a/objdiff-core/src/diff/mod.rs +++ b/objdiff-core/src/diff/mod.rs @@ -197,6 +197,100 @@ pub struct DiffObjsResult { pub prev: Option, } +fn diffable_symbol_by_name(obj: &Object, name: &str) -> Option { + obj.symbols.iter().position(|symbol| { + symbol.name == name + && (symbol.section.is_some() || symbol.flags.contains(SymbolFlag::Common)) + }) +} + +/// Diff a single named symbol while preserving the object-wide symbol and section indexes. +/// +/// This is intended for consumers that only need one symbol's detailed diff. The returned +/// [`ObjectDiff`] values still contain placeholder entries for every symbol and section so that +/// indexes in relocations and `target_symbol` remain valid, but no unrelated symbols or sections +/// are diffed. +pub fn diff_objs_for_symbol( + left: Option<&Object>, + right: Option<&Object>, + symbol_name: &str, + diff_config: &DiffObjConfig, + mapping_config: &MappingConfig, +) -> Result { + let left_symbol_idx = left.and_then(|obj| diffable_symbol_by_name(obj, symbol_name)); + let right_symbol_idx = match (left, right, left_symbol_idx) { + (Some(left_obj), Some(right_obj), Some(left_idx)) => mapping_config + .mappings + .get(symbol_name) + .and_then(|right_name| diffable_symbol_by_name(right_obj, right_name)) + .or_else(|| find_symbol(Some(right_obj), left_obj, left_idx, None, false)), + (_, Some(right_obj), _) => diffable_symbol_by_name(right_obj, symbol_name), + _ => None, + }; + + if left_symbol_idx.is_none() && right_symbol_idx.is_none() { + return Err(anyhow!("Symbol not found: {symbol_name}")); + } + + let left_kind = + left_symbol_idx.map(|idx| symbol_section_kind(left.unwrap(), &left.unwrap().symbols[idx])); + let right_kind = right_symbol_idx + .map(|idx| symbol_section_kind(right.unwrap(), &right.unwrap().symbols[idx])); + if let (Some(left_kind), Some(right_kind)) = (left_kind, right_kind) + && left_kind != right_kind + { + return Err(anyhow!( + "Symbol section kind mismatch: {symbol_name} ({left_kind:?} vs {right_kind:?})" + )); + } + let section_kind = left_kind.or(right_kind).unwrap_or(SectionKind::Unknown); + if section_kind == SectionKind::Unknown { + return Err(anyhow!("Symbol has no diffable section: {symbol_name}")); + } + + let mut left_diff = left.map(ObjectDiff::new_from_obj); + let mut right_diff = right.map(ObjectDiff::new_from_obj); + match (left_symbol_idx, right_symbol_idx) { + (Some(left_idx), Some(right_idx)) => { + let (left_symbol_diff, right_symbol_diff) = match section_kind { + SectionKind::Code => { + diff_code(left.unwrap(), right.unwrap(), left_idx, right_idx, diff_config) + } + SectionKind::Data => { + diff_data_symbol(left.unwrap(), right.unwrap(), left_idx, right_idx) + } + SectionKind::Bss | SectionKind::Common => { + diff_bss_symbol(left.unwrap(), right.unwrap(), left_idx, right_idx) + } + SectionKind::Unknown => unreachable!(), + }?; + left_diff.as_mut().unwrap().symbols[left_idx] = left_symbol_diff; + right_diff.as_mut().unwrap().symbols[right_idx] = right_symbol_diff; + } + (Some(left_idx), None) => { + let symbol_diff = match section_kind { + SectionKind::Code => no_diff_code(left.unwrap(), left_idx, diff_config), + SectionKind::Data => no_diff_data_symbol(left.unwrap(), left_idx), + SectionKind::Bss | SectionKind::Common => Ok(SymbolDiff::default()), + SectionKind::Unknown => unreachable!(), + }?; + left_diff.as_mut().unwrap().symbols[left_idx] = symbol_diff; + } + (None, Some(right_idx)) => { + let symbol_diff = match section_kind { + SectionKind::Code => no_diff_code(right.unwrap(), right_idx, diff_config), + SectionKind::Data => no_diff_data_symbol(right.unwrap(), right_idx), + SectionKind::Bss | SectionKind::Common => Ok(SymbolDiff::default()), + SectionKind::Unknown => unreachable!(), + }?; + right_diff.as_mut().unwrap().symbols[right_idx] = symbol_diff; + } + (None, None) => unreachable!(), + } + + Ok(DiffObjsResult { left: left_diff, right: right_diff, prev: None }) +} + pub fn diff_objs( left: Option<&Object>, right: Option<&Object>, diff --git a/objdiff-core/tests/arch_x86.rs b/objdiff-core/tests/arch_x86.rs index e5eb6e80..75097605 100644 --- a/objdiff-core/tests/arch_x86.rs +++ b/objdiff-core/tests/arch_x86.rs @@ -20,6 +20,107 @@ fn read_x86() { insta::assert_snapshot!(output); } +#[test] +#[cfg(feature = "x86")] +fn diff_single_x86_symbol() { + let diff_config = diff::DiffObjConfig::default(); + let obj = obj::read::parse( + include_object!("data/x86/staticdebug.obj"), + &diff_config, + diff::DiffSide::Target, + ) + .unwrap(); + let symbol_name = "?PrintThing@@YAXXZ"; + let symbol_idx = obj.symbol_by_name(symbol_name).unwrap(); + + let result = diff::diff_objs_for_symbol( + Some(&obj), + Some(&obj), + symbol_name, + &diff_config, + &diff::MappingConfig::default(), + ) + .unwrap(); + let left = result.left.unwrap(); + let right = result.right.unwrap(); + + assert_eq!(left.symbols.len(), obj.symbols.len()); + assert_eq!(right.symbols.len(), obj.symbols.len()); + assert_eq!(left.symbols[symbol_idx].target_symbol, Some(symbol_idx)); + assert_eq!(right.symbols[symbol_idx].target_symbol, Some(symbol_idx)); + assert!(!left.symbols[symbol_idx].instruction_rows.is_empty()); + assert!(!right.symbols[symbol_idx].instruction_rows.is_empty()); + assert!(left.sections.iter().all(|section| section.data_diff.is_empty())); + assert!(right.sections.iter().all(|section| section.data_diff.is_empty())); + assert!( + left.symbols + .iter() + .enumerate() + .filter(|(idx, _)| *idx != symbol_idx) + .all(|(_, symbol)| symbol.instruction_rows.is_empty()) + ); + assert!( + right + .symbols + .iter() + .enumerate() + .filter(|(idx, _)| *idx != symbol_idx) + .all(|(_, symbol)| symbol.instruction_rows.is_empty()) + ); +} + +#[test] +#[cfg(feature = "x86")] +fn diff_single_symbol_falls_back_from_missing_mapping() { + let diff_config = diff::DiffObjConfig::default(); + let obj = obj::read::parse( + include_object!("data/x86/staticdebug.obj"), + &diff_config, + diff::DiffSide::Target, + ) + .unwrap(); + let symbol_name = "?PrintThing@@YAXXZ"; + let symbol_idx = obj.symbol_by_name(symbol_name).unwrap(); + let mut mapping_config = diff::MappingConfig::default(); + mapping_config.mappings.insert(symbol_name.into(), "missing".into()); + + let result = diff::diff_objs_for_symbol( + Some(&obj), + Some(&obj), + symbol_name, + &diff_config, + &mapping_config, + ) + .unwrap(); + + assert_eq!(result.left.unwrap().symbols[symbol_idx].target_symbol, Some(symbol_idx)); + assert_eq!(result.right.unwrap().symbols[symbol_idx].target_symbol, Some(symbol_idx)); +} + +#[test] +fn diff_single_common_symbol() { + let common_symbol = obj::Symbol { + name: "common".into(), + size: 4, + flags: obj::SymbolFlag::Common.into(), + ..Default::default() + }; + let left = obj::Object { symbols: vec![common_symbol.clone()], ..Default::default() }; + let right = obj::Object { symbols: vec![common_symbol], ..Default::default() }; + + let result = diff::diff_objs_for_symbol( + Some(&left), + Some(&right), + "common", + &diff::DiffObjConfig::default(), + &diff::MappingConfig::default(), + ) + .unwrap(); + + assert_eq!(result.left.unwrap().symbols[0].target_symbol, Some(0)); + assert_eq!(result.right.unwrap().symbols[0].target_symbol, Some(0)); +} + #[test] #[cfg(feature = "x86")] fn read_x86_combine_sections() {