Skip to main content

objdiff_core/diff/
mod.rs

1use alloc::{
2    collections::{BTreeMap, BTreeSet},
3    string::String,
4    vec,
5    vec::Vec,
6};
7use core::{cmp::Ordering, num::NonZeroU32, ops::Range};
8
9use anyhow::{Result, anyhow};
10
11use crate::{
12    diff::{
13        code::{diff_code, no_diff_code},
14        data::{
15            diff_bss_section, diff_bss_symbol, diff_data_section, diff_data_symbol,
16            diff_generic_section, no_diff_bss_section, no_diff_data_section, no_diff_data_symbol,
17        },
18    },
19    obj::{
20        InstructionRef, Object, Relocation, ResolvedRelocation, SectionKind, Symbol, SymbolFlag,
21        SymbolKind,
22    },
23};
24
25pub mod code;
26pub mod data;
27pub mod demangler;
28pub mod display;
29
30include!(concat!(env!("OUT_DIR"), "/config.gen.rs"));
31
32impl DiffObjConfig {
33    pub fn separator(&self) -> &'static str { if self.space_between_args { ", " } else { "," } }
34}
35
36#[derive(Debug, Clone)]
37pub struct SectionDiff {
38    // pub target_section: Option<usize>,
39    pub match_percent: Option<f32>,
40    pub data_diff: Vec<DataDiff>,
41    pub reloc_diff: Vec<DataRelocationDiff>,
42}
43
44#[derive(Debug, Clone, Default)]
45pub struct SymbolDiff {
46    /// The symbol index in the _other_ object that this symbol was diffed against
47    pub target_symbol: Option<usize>,
48    pub match_percent: Option<f32>,
49    pub diff_score: Option<(u64, u64)>,
50    pub instruction_rows: Vec<InstructionDiffRow>,
51    pub data_rows: Vec<DataDiffRow>,
52    pub order: Option<Ordering>,
53}
54
55#[derive(Debug, Clone, Default)]
56pub struct MappingSymbolDiff {
57    pub symbol_index: usize,
58    pub symbol_diff: SymbolDiff,
59}
60
61#[derive(Debug, Clone, Default)]
62pub struct InstructionDiffRow {
63    /// Instruction reference
64    pub ins_ref: Option<InstructionRef>,
65    /// Diff kind
66    pub kind: InstructionDiffKind,
67    /// Branches from instruction(s)
68    pub branch_from: Option<InstructionBranchFrom>,
69    /// Branches to instruction
70    pub branch_to: Option<InstructionBranchTo>,
71    /// Arg diffs
72    pub arg_diff: Vec<InstructionArgDiffIndex>,
73}
74
75#[derive(Debug, Copy, Clone, Eq, PartialEq, Default)]
76pub enum InstructionDiffKind {
77    #[default]
78    None,
79    OpMismatch,
80    ArgMismatch,
81    Replace,
82    Delete,
83    Insert,
84}
85
86#[derive(Debug, Clone, Default)]
87pub struct DataDiff {
88    pub data: Vec<u8>,
89    pub size: usize,
90    pub kind: DataDiffKind,
91}
92
93#[derive(Debug, Clone)]
94pub struct DataRelocationDiff {
95    pub reloc: Relocation,
96    pub range: Range<u64>,
97    pub kind: DataDiffKind,
98}
99
100#[derive(Debug, Copy, Clone, Eq, PartialEq, Default)]
101pub enum DataDiffKind {
102    #[default]
103    None,
104    Replace,
105    Delete,
106    Insert,
107}
108
109#[derive(Debug, Clone, Default)]
110pub struct DataDiffRow {
111    pub address: u64,
112    pub segments: Vec<DataDiff>,
113    pub relocations: Vec<DataRelocationDiff>,
114}
115
116/// Index of the argument diff for coloring.
117#[repr(transparent)]
118#[derive(Debug, Copy, Clone, Default)]
119pub struct InstructionArgDiffIndex(pub Option<NonZeroU32>);
120
121impl InstructionArgDiffIndex {
122    pub const NONE: Self = Self(None);
123
124    #[inline(always)]
125    pub fn new(idx: u32) -> Self {
126        Self(Some(unsafe { NonZeroU32::new_unchecked(idx.saturating_add(1)) }))
127    }
128
129    #[inline(always)]
130    pub fn get(&self) -> Option<u32> { self.0.map(|idx| idx.get() - 1) }
131
132    #[inline(always)]
133    pub fn is_some(&self) -> bool { self.0.is_some() }
134
135    #[inline(always)]
136    pub fn is_none(&self) -> bool { self.0.is_none() }
137}
138
139#[derive(Debug, Clone)]
140pub struct InstructionBranchFrom {
141    /// Source instruction indices
142    pub ins_idx: Vec<u32>,
143    /// Incrementing index for coloring
144    pub branch_idx: u32,
145}
146
147#[derive(Debug, Clone)]
148pub struct InstructionBranchTo {
149    /// Target instruction index
150    pub ins_idx: u32,
151    /// Incrementing index for coloring
152    pub branch_idx: u32,
153}
154
155#[derive(Debug, Default)]
156pub struct ObjectDiff {
157    /// A list of all symbol diffs in the object.
158    pub symbols: Vec<SymbolDiff>,
159    /// A list of all section diffs in the object.
160    pub sections: Vec<SectionDiff>,
161    /// If `selecting_left` or `selecting_right` is set, this is the list of symbols
162    /// that are being mapped to the other object.
163    pub mapping_symbols: Vec<MappingSymbolDiff>,
164}
165
166impl ObjectDiff {
167    pub fn new_from_obj(obj: &Object) -> Self {
168        let mut result = Self {
169            symbols: Vec::with_capacity(obj.symbols.len()),
170            sections: Vec::with_capacity(obj.sections.len()),
171            mapping_symbols: vec![],
172        };
173        for _ in obj.symbols.iter() {
174            result.symbols.push(SymbolDiff {
175                target_symbol: None,
176                match_percent: None,
177                diff_score: None,
178                ..Default::default()
179            });
180        }
181        for _ in obj.sections.iter() {
182            result.sections.push(SectionDiff {
183                // target_section: None,
184                match_percent: None,
185                data_diff: vec![],
186                reloc_diff: vec![],
187            });
188        }
189        result
190    }
191}
192
193#[derive(Debug, Default)]
194pub struct DiffObjsResult {
195    pub left: Option<ObjectDiff>,
196    pub right: Option<ObjectDiff>,
197    pub prev: Option<ObjectDiff>,
198}
199
200pub fn diff_objs(
201    left: Option<&Object>,
202    right: Option<&Object>,
203    prev: Option<&Object>,
204    diff_config: &DiffObjConfig,
205    mapping_config: &MappingConfig,
206) -> Result<DiffObjsResult> {
207    let symbol_matches = matching_symbols(left, right, prev, mapping_config)?;
208    let section_matches = matching_sections(left, right)?;
209    let mut left = left.map(|p| (p, ObjectDiff::new_from_obj(p)));
210    let mut right = right.map(|p| (p, ObjectDiff::new_from_obj(p)));
211    let mut prev = prev.map(|p| (p, ObjectDiff::new_from_obj(p)));
212
213    for symbol_match in &symbol_matches {
214        match *symbol_match {
215            SymbolMatch {
216                left: Some(left_symbol_ref),
217                right: Some(right_symbol_ref),
218                prev: prev_symbol_ref,
219                section_kind,
220            } => {
221                let (left_obj, left_out) = left.as_mut().unwrap();
222                let (right_obj, right_out) = right.as_mut().unwrap();
223                match section_kind {
224                    SectionKind::Code => {
225                        let (left_diff, right_diff) = diff_code(
226                            left_obj,
227                            right_obj,
228                            left_symbol_ref,
229                            right_symbol_ref,
230                            diff_config,
231                        )?;
232                        left_out.symbols[left_symbol_ref] = left_diff;
233                        right_out.symbols[right_symbol_ref] = right_diff;
234
235                        if let Some(prev_symbol_ref) = prev_symbol_ref {
236                            let (_prev_obj, prev_out) = prev.as_mut().unwrap();
237                            let (_, prev_diff) = diff_code(
238                                left_obj,
239                                right_obj,
240                                right_symbol_ref,
241                                prev_symbol_ref,
242                                diff_config,
243                            )?;
244                            prev_out.symbols[prev_symbol_ref] = prev_diff;
245                        }
246                    }
247                    SectionKind::Data => {
248                        let (left_diff, right_diff) = diff_data_symbol(
249                            left_obj,
250                            right_obj,
251                            left_symbol_ref,
252                            right_symbol_ref,
253                        )?;
254                        left_out.symbols[left_symbol_ref] = left_diff;
255                        right_out.symbols[right_symbol_ref] = right_diff;
256                    }
257                    SectionKind::Bss | SectionKind::Common => {
258                        let (left_diff, right_diff) = diff_bss_symbol(
259                            left_obj,
260                            right_obj,
261                            left_symbol_ref,
262                            right_symbol_ref,
263                        )?;
264                        left_out.symbols[left_symbol_ref] = left_diff;
265                        right_out.symbols[right_symbol_ref] = right_diff;
266                    }
267                    SectionKind::Unknown => unreachable!(),
268                }
269            }
270            SymbolMatch { left: Some(left_symbol_ref), right: None, prev: _, section_kind } => {
271                let (left_obj, left_out) = left.as_mut().unwrap();
272                match section_kind {
273                    SectionKind::Code => {
274                        left_out.symbols[left_symbol_ref] =
275                            no_diff_code(left_obj, left_symbol_ref, diff_config)?;
276                    }
277                    SectionKind::Data => {
278                        left_out.symbols[left_symbol_ref] =
279                            no_diff_data_symbol(left_obj, left_symbol_ref)?;
280                    }
281                    SectionKind::Bss | SectionKind::Common => {
282                        // Nothing needs to be done
283                    }
284                    SectionKind::Unknown => unreachable!(),
285                }
286            }
287            SymbolMatch { left: None, right: Some(right_symbol_ref), prev: _, section_kind } => {
288                let (right_obj, right_out) = right.as_mut().unwrap();
289                match section_kind {
290                    SectionKind::Code => {
291                        right_out.symbols[right_symbol_ref] =
292                            no_diff_code(right_obj, right_symbol_ref, diff_config)?;
293                    }
294                    SectionKind::Data => {
295                        right_out.symbols[right_symbol_ref] =
296                            no_diff_data_symbol(right_obj, right_symbol_ref)?;
297                    }
298                    SectionKind::Bss | SectionKind::Common => {
299                        // Nothing needs to be done
300                    }
301                    SectionKind::Unknown => unreachable!(),
302                }
303            }
304            SymbolMatch { left: None, right: None, .. } => {
305                // Should not happen
306            }
307        }
308    }
309
310    for section_match in section_matches {
311        match section_match {
312            SectionMatch {
313                left: Some(left_section_idx),
314                right: Some(right_section_idx),
315                section_kind,
316            } => {
317                let (left_obj, left_out) = left.as_mut().unwrap();
318                let (right_obj, right_out) = right.as_mut().unwrap();
319                match section_kind {
320                    SectionKind::Code => {
321                        let (left_diff, right_diff) = diff_generic_section(
322                            left_obj,
323                            right_obj,
324                            left_out,
325                            right_out,
326                            left_section_idx,
327                            right_section_idx,
328                        )?;
329                        left_out.sections[left_section_idx] = left_diff;
330                        right_out.sections[right_section_idx] = right_diff;
331                    }
332                    SectionKind::Data => {
333                        let (left_diff, right_diff) = diff_data_section(
334                            left_obj,
335                            right_obj,
336                            left_out,
337                            right_out,
338                            left_section_idx,
339                            right_section_idx,
340                        )?;
341                        left_out.sections[left_section_idx] = left_diff;
342                        right_out.sections[right_section_idx] = right_diff;
343                    }
344                    SectionKind::Bss | SectionKind::Common => {
345                        let (left_diff, right_diff) = diff_bss_section(
346                            left_obj,
347                            right_obj,
348                            left_out,
349                            right_out,
350                            left_section_idx,
351                            right_section_idx,
352                        )?;
353                        left_out.sections[left_section_idx] = left_diff;
354                        right_out.sections[right_section_idx] = right_diff;
355                    }
356                    SectionKind::Unknown => unreachable!(),
357                }
358            }
359            SectionMatch { left: Some(left_section_idx), right: None, section_kind } => {
360                let (left_obj, left_out) = left.as_mut().unwrap();
361                match section_kind {
362                    SectionKind::Code => {}
363                    SectionKind::Data => {
364                        left_out.sections[left_section_idx] =
365                            no_diff_data_section(left_obj, left_section_idx)?;
366                    }
367                    SectionKind::Bss | SectionKind::Common => {
368                        left_out.sections[left_section_idx] = no_diff_bss_section()?;
369                    }
370                    SectionKind::Unknown => unreachable!(),
371                }
372            }
373            SectionMatch { left: None, right: Some(right_section_idx), section_kind } => {
374                let (right_obj, right_out) = right.as_mut().unwrap();
375                match section_kind {
376                    SectionKind::Code => {}
377                    SectionKind::Data => {
378                        right_out.sections[right_section_idx] =
379                            no_diff_data_section(right_obj, right_section_idx)?;
380                    }
381                    SectionKind::Bss | SectionKind::Common => {
382                        right_out.sections[right_section_idx] = no_diff_bss_section()?;
383                    }
384                    SectionKind::Unknown => unreachable!(),
385                }
386            }
387            SectionMatch { left: None, right: None, .. } => {
388                // Should not happen
389            }
390        }
391    }
392
393    if let (Some((right_obj, right_out)), Some((left_obj, left_out))) =
394        (right.as_mut(), left.as_mut())
395    {
396        if let Some(right_name) = mapping_config.selecting_left.as_deref() {
397            generate_mapping_symbols(
398                left_obj,
399                left_out,
400                right_obj,
401                right_out,
402                MappingSymbol::Right(right_name),
403                diff_config,
404            )?;
405        }
406        if let Some(left_name) = mapping_config.selecting_right.as_deref() {
407            generate_mapping_symbols(
408                left_obj,
409                left_out,
410                right_obj,
411                right_out,
412                MappingSymbol::Left(left_name),
413                diff_config,
414            )?;
415        }
416    }
417
418    if let Some((left_obj, left_out)) = left.as_mut()
419        && let Some((right_obj, right_out)) = right.as_mut()
420    {
421        let mut done_section_names = BTreeSet::new();
422        for left_section in left_obj.sections.iter() {
423            if done_section_names.contains(&left_section.name) {
424                continue;
425            }
426            done_section_names.insert(&left_section.name);
427            diff_order_for_section_name(
428                left_obj,
429                right_obj,
430                left_out,
431                right_out,
432                &left_section.name,
433                &symbol_matches,
434            )?;
435        }
436    }
437
438    Ok(DiffObjsResult {
439        left: left.map(|(_, o)| o),
440        right: right.map(|(_, o)| o),
441        prev: prev.map(|(_, o)| o),
442    })
443}
444
445fn symbols_matching_section_name<'obj>(
446    obj: &'obj Object,
447    section_name: &str,
448) -> impl Iterator<Item = (usize, &'obj Symbol)> {
449    obj.symbols.iter().enumerate().filter(move |(_, s)| {
450        let curr_section_name = symbol_section(obj, s).map(|(n, _)| n);
451        curr_section_name == Some(section_name)
452            && s.kind != SymbolKind::Section
453            && s.size > 0
454            && !s.flags.contains(SymbolFlag::Hidden)
455            && !s.flags.contains(SymbolFlag::Ignored)
456    })
457}
458
459fn diff_order_for_section_name(
460    left_obj: &Object,
461    right_obj: &Object,
462    left_diff: &mut ObjectDiff,
463    right_diff: &mut ObjectDiff,
464    section_name: &str,
465    symbol_matches: &Vec<SymbolMatch>,
466) -> Result<()> {
467    let mut left_paired_symbol_idxs = BTreeSet::new();
468    let mut right_paired_symbol_idxs = BTreeSet::new();
469    let mut left_sym_idx_to_right_sym_idx = BTreeMap::new();
470    for symbol_match in symbol_matches {
471        let Some(left_symbol_idx) = symbol_match.left else {
472            continue;
473        };
474        let Some(right_symbol_idx) = symbol_match.right else {
475            continue;
476        };
477        left_paired_symbol_idxs.insert(left_symbol_idx);
478        right_paired_symbol_idxs.insert(right_symbol_idx);
479        left_sym_idx_to_right_sym_idx.insert(left_symbol_idx, right_symbol_idx);
480    }
481
482    let left_paired_symbols: Vec<_> = symbols_matching_section_name(left_obj, section_name)
483        .filter(|(sym_idx, _)| left_paired_symbol_idxs.contains(sym_idx))
484        .collect();
485    let right_paired_symbols: Vec<_> = symbols_matching_section_name(right_obj, section_name)
486        .filter(|(sym_idx, _)| right_paired_symbol_idxs.contains(sym_idx))
487        .collect();
488
489    let mut expected_right_order_idx = 0;
490    for (left_order_idx, (left_symbol_idx, _left_symbol)) in left_paired_symbols.iter().enumerate()
491    {
492        let right_symbol_idx = left_sym_idx_to_right_sym_idx.get(left_symbol_idx).unwrap();
493        let right_order_idx = right_paired_symbols
494            .iter()
495            .position(|(sym_idx, _)| sym_idx == right_symbol_idx)
496            .ok_or_else(|| {
497                anyhow!("Failed to find right side symbol for paired left side symbol")
498            })?;
499        if right_order_idx == left_order_idx {
500            // In the correct spot.
501            left_diff.symbols[*left_symbol_idx].order = Some(Ordering::Equal);
502            right_diff.symbols[*right_symbol_idx].order = Some(Ordering::Equal);
503            expected_right_order_idx = left_order_idx + 1
504        } else if right_order_idx == expected_right_order_idx {
505            // In the wrong spot, but correct relative to the symbol before it.
506            // Don't show this as a diff to reduce noise.
507            left_diff.symbols[*left_symbol_idx].order = Some(Ordering::Equal);
508            right_diff.symbols[*right_symbol_idx].order = Some(Ordering::Equal);
509        } else {
510            // In the wrong spot.
511            left_diff.symbols[*left_symbol_idx].order =
512                Some(expected_right_order_idx.cmp(&right_order_idx));
513            right_diff.symbols[*right_symbol_idx].order =
514                Some(right_order_idx.cmp(&expected_right_order_idx));
515            expected_right_order_idx = right_order_idx;
516        }
517        expected_right_order_idx += 1;
518    }
519    Ok(())
520}
521
522/// Score entry for a candidate symbol when searching for similar functions.
523#[derive(Debug, Clone)]
524pub struct SimilarSymbol {
525    pub symbol_idx: usize,
526    pub match_percent: f32,
527}
528
529/// Find all code symbols in `target_obj` that are similar to a given symbol in `source_obj`,
530/// sorted descending by similarity score. Symbols that fail to score are silently skipped.
531pub fn find_similar_code_symbols(
532    source_obj: &Object,
533    source_symbol_idx: usize,
534    target_obj: &Object,
535    diff_config: &DiffObjConfig,
536) -> Vec<SimilarSymbol> {
537    let source_symbol = &source_obj.symbols[source_symbol_idx];
538    let source_section_kind = symbol_section_kind(source_obj, source_symbol);
539    if source_section_kind != SectionKind::Code {
540        return vec![];
541    }
542
543    let mut results = Vec::new();
544    for (target_idx, target_symbol) in target_obj.symbols.iter().enumerate() {
545        if target_symbol.size == 0 || target_symbol.flags.contains(SymbolFlag::Ignored) {
546            continue;
547        }
548        if symbol_section_kind(target_obj, target_symbol) != SectionKind::Code {
549            continue;
550        }
551        let Ok((left_diff, _)) =
552            diff_code(source_obj, target_obj, source_symbol_idx, target_idx, diff_config)
553        else {
554            continue;
555        };
556        let Some(match_percent) = left_diff.match_percent else { continue };
557        results.push(SimilarSymbol { symbol_idx: target_idx, match_percent });
558    }
559    results.sort_by(|a, b| {
560        b.match_percent.partial_cmp(&a.match_percent).unwrap_or(core::cmp::Ordering::Equal)
561    });
562    results
563}
564
565#[derive(Clone, Copy)]
566enum MappingSymbol<'a> {
567    Left(&'a str),
568    Right(&'a str),
569}
570
571/// When we're selecting a symbol to use as a comparison, we'll create comparisons for all
572/// symbols in the other object that match the selected symbol's section and kind. This allows
573/// us to display match percentages for all symbols in the other object that could be selected.
574fn generate_mapping_symbols(
575    left_obj: &Object,
576    left_out: &mut ObjectDiff,
577    right_obj: &Object,
578    right_out: &mut ObjectDiff,
579    mapping_symbol: MappingSymbol,
580    config: &DiffObjConfig,
581) -> Result<()> {
582    let (base_obj, base_name, target_obj) = match mapping_symbol {
583        MappingSymbol::Left(name) => (left_obj, name, right_obj),
584        MappingSymbol::Right(name) => (right_obj, name, left_obj),
585    };
586    let Some(base_symbol_ref) = base_obj.symbol_by_name(base_name) else {
587        return Ok(());
588    };
589    let base_section_kind = symbol_section_kind(base_obj, &base_obj.symbols[base_symbol_ref]);
590    for (target_symbol_index, target_symbol) in target_obj.symbols.iter().enumerate() {
591        if target_symbol.size == 0
592            || target_symbol.flags.contains(SymbolFlag::Ignored)
593            || symbol_section_kind(target_obj, target_symbol) != base_section_kind
594        {
595            continue;
596        }
597        let (left_symbol_idx, right_symbol_idx) = match mapping_symbol {
598            MappingSymbol::Left(_) => (base_symbol_ref, target_symbol_index),
599            MappingSymbol::Right(_) => (target_symbol_index, base_symbol_ref),
600        };
601        let (left_diff, right_diff) = match base_section_kind {
602            SectionKind::Code => {
603                diff_code(left_obj, right_obj, left_symbol_idx, right_symbol_idx, config)
604            }
605            SectionKind::Data => {
606                diff_data_symbol(left_obj, right_obj, left_symbol_idx, right_symbol_idx)
607            }
608            SectionKind::Bss | SectionKind::Common => {
609                diff_bss_symbol(left_obj, right_obj, left_symbol_idx, right_symbol_idx)
610            }
611            SectionKind::Unknown => continue,
612        }?;
613        match mapping_symbol {
614            MappingSymbol::Left(_) => right_out.mapping_symbols.push(MappingSymbolDiff {
615                symbol_index: right_symbol_idx,
616                symbol_diff: right_diff,
617            }),
618            MappingSymbol::Right(_) => left_out
619                .mapping_symbols
620                .push(MappingSymbolDiff { symbol_index: left_symbol_idx, symbol_diff: left_diff }),
621        }
622    }
623    Ok(())
624}
625
626#[derive(Copy, Clone, Eq, PartialEq)]
627struct SymbolMatch {
628    left: Option<usize>,
629    right: Option<usize>,
630    prev: Option<usize>,
631    section_kind: SectionKind,
632}
633
634#[derive(Copy, Clone, Eq, PartialEq)]
635struct SectionMatch {
636    left: Option<usize>,
637    right: Option<usize>,
638    section_kind: SectionKind,
639}
640
641#[derive(Debug, Clone, Default)]
642#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize), serde(default))]
643pub struct MappingConfig {
644    /// Manual symbol mappings
645    pub mappings: BTreeMap<String, String>,
646    /// The right object symbol name that we're selecting a left symbol for
647    pub selecting_left: Option<String>,
648    /// The left object symbol name that we're selecting a right symbol for
649    pub selecting_right: Option<String>,
650}
651
652fn apply_symbol_mappings(
653    left: &Object,
654    right: &Object,
655    mapping_config: &MappingConfig,
656    left_used: &mut BTreeSet<usize>,
657    right_used: &mut BTreeSet<usize>,
658    matches: &mut Vec<SymbolMatch>,
659) -> Result<()> {
660    // If we're selecting a symbol to use as a comparison, mark it as used
661    // This ensures that we don't match it to another symbol at any point
662    if let Some(left_name) = &mapping_config.selecting_left
663        && let Some(left_symbol) = left.symbol_by_name(left_name)
664    {
665        left_used.insert(left_symbol);
666    }
667    if let Some(right_name) = &mapping_config.selecting_right
668        && let Some(right_symbol) = right.symbol_by_name(right_name)
669    {
670        right_used.insert(right_symbol);
671    }
672
673    // Apply manual symbol mappings
674    for (left_name, right_name) in &mapping_config.mappings {
675        let Some(left_symbol_index) = left.symbol_by_name(left_name) else {
676            continue;
677        };
678        if left_used.contains(&left_symbol_index) {
679            continue;
680        }
681        let Some(right_symbol_index) = right.symbol_by_name(right_name) else {
682            continue;
683        };
684        if right_used.contains(&right_symbol_index) {
685            continue;
686        }
687        let left_section_kind = left
688            .symbols
689            .get(left_symbol_index)
690            .and_then(|s| s.section)
691            .and_then(|section_index| left.sections.get(section_index))
692            .map_or(SectionKind::Unknown, |s| s.kind);
693        let right_section_kind = right
694            .symbols
695            .get(right_symbol_index)
696            .and_then(|s| s.section)
697            .and_then(|section_index| right.sections.get(section_index))
698            .map_or(SectionKind::Unknown, |s| s.kind);
699        if left_section_kind != right_section_kind {
700            log::warn!(
701                "Symbol section kind mismatch: {left_name} ({left_section_kind:?}) vs {right_name} ({right_section_kind:?})"
702            );
703            continue;
704        }
705        matches.push(SymbolMatch {
706            left: Some(left_symbol_index),
707            right: Some(right_symbol_index),
708            prev: None, // TODO
709            section_kind: left_section_kind,
710        });
711        left_used.insert(left_symbol_index);
712        right_used.insert(right_symbol_index);
713    }
714    Ok(())
715}
716
717/// Find matching symbols between each object.
718fn matching_symbols(
719    left: Option<&Object>,
720    right: Option<&Object>,
721    prev: Option<&Object>,
722    mappings: &MappingConfig,
723) -> Result<Vec<SymbolMatch>> {
724    let mut matches = Vec::new();
725    let mut left_used = BTreeSet::new();
726    let mut right_used = BTreeSet::new();
727    if let Some(left) = left {
728        if let Some(right) = right {
729            apply_symbol_mappings(
730                left,
731                right,
732                mappings,
733                &mut left_used,
734                &mut right_used,
735                &mut matches,
736            )?;
737        }
738        // Do two passes for nameless literals. The first only pairs up perfect matches to ensure
739        // those are correct first, while the second pass catches near matches.
740        for fuzzy_literals in [false, true] {
741            for (symbol_idx, symbol) in left.symbols.iter().enumerate() {
742                if symbol.size == 0 || symbol.flags.contains(SymbolFlag::Ignored) {
743                    continue;
744                }
745                let section_kind = symbol_section_kind(left, symbol);
746                if section_kind == SectionKind::Unknown {
747                    continue;
748                }
749                if left_used.contains(&symbol_idx) {
750                    continue;
751                }
752                let symbol_match = SymbolMatch {
753                    left: Some(symbol_idx),
754                    right: find_symbol(right, left, symbol_idx, Some(&right_used), fuzzy_literals),
755                    prev: find_symbol(prev, left, symbol_idx, None, fuzzy_literals),
756                    section_kind,
757                };
758                matches.push(symbol_match);
759                if let Some(right) = symbol_match.right {
760                    left_used.insert(symbol_idx);
761                    right_used.insert(right);
762                }
763            }
764        }
765    }
766    if let Some(right) = right {
767        // Do two passes for nameless literals. The first only pairs up perfect matches to ensure
768        // those are correct first, while the second pass catches near matches.
769        for fuzzy_literals in [false, true] {
770            for (symbol_idx, symbol) in right.symbols.iter().enumerate() {
771                if symbol.size == 0 || symbol.flags.contains(SymbolFlag::Ignored) {
772                    continue;
773                }
774                let section_kind = symbol_section_kind(right, symbol);
775                if section_kind == SectionKind::Unknown {
776                    continue;
777                }
778                if right_used.contains(&symbol_idx) {
779                    continue;
780                }
781                let symbol_match = SymbolMatch {
782                    left: None,
783                    right: Some(symbol_idx),
784                    prev: find_symbol(prev, right, symbol_idx, None, fuzzy_literals),
785                    section_kind,
786                };
787                matches.push(symbol_match);
788                if symbol_match.prev.is_some() {
789                    right_used.insert(symbol_idx);
790                }
791            }
792        }
793    }
794    Ok(matches)
795}
796
797fn unmatched_symbols<'obj, 'used>(
798    obj: &'obj Object,
799    used: Option<&'used BTreeSet<usize>>,
800) -> impl Iterator<Item = (usize, &'obj Symbol)> + 'used
801where
802    'obj: 'used,
803{
804    obj.symbols.iter().enumerate().filter(move |&(symbol_idx, symbol)| {
805        !symbol.flags.contains(SymbolFlag::Ignored)
806            // Skip symbols that have already been matched
807            && !used.is_some_and(|u| u.contains(&symbol_idx))
808    })
809}
810
811fn symbol_section<'obj>(obj: &'obj Object, symbol: &Symbol) -> Option<(&'obj str, SectionKind)> {
812    if let Some(section) = symbol.section.and_then(|section_idx| obj.sections.get(section_idx)) {
813        // Match x86 .rdata$r against .rdata$rs
814        let section_name =
815            section.name.split_once('$').map_or(section.name.as_str(), |(prefix, _)| prefix);
816        Some((section_name, section.kind))
817    } else if symbol.flags.contains(SymbolFlag::Common) {
818        Some((".comm", SectionKind::Common))
819    } else {
820        None
821    }
822}
823
824fn symbol_section_kind(obj: &Object, symbol: &Symbol) -> SectionKind {
825    match symbol.section {
826        Some(section_index) => obj.sections[section_index].kind,
827        None if symbol.flags.contains(SymbolFlag::Common) => SectionKind::Common,
828        None => SectionKind::Unknown,
829    }
830}
831
832fn find_symbol(
833    obj: Option<&Object>,
834    in_obj: &Object,
835    in_symbol_idx: usize,
836    used: Option<&BTreeSet<usize>>,
837    fuzzy_literals: bool,
838) -> Option<usize> {
839    let in_symbol = &in_obj.symbols[in_symbol_idx];
840    let obj = obj?;
841    let (section_name, section_kind) = symbol_section(in_obj, in_symbol)?;
842
843    // Match compiler-generated symbols against each other (e.g. @251 -> @60)
844    // If they are in the same section and have the same value
845    if in_symbol.flags.contains(SymbolFlag::CompilerGenerated)
846        && matches!(section_kind, SectionKind::Code | SectionKind::Data | SectionKind::Bss)
847    {
848        let mut closest_match_symbol_idx = None;
849        let mut closest_match_percent = 0.0;
850        for (symbol_idx, symbol) in unmatched_symbols(obj, used) {
851            let Some(section_index) = symbol.section else {
852                continue;
853            };
854            if obj.sections[section_index].name != section_name {
855                continue;
856            }
857            if !symbol.flags.contains(SymbolFlag::CompilerGenerated) {
858                continue;
859            }
860            match section_kind {
861                SectionKind::Data | SectionKind::Code => {
862                    // For code or data, pick the first symbol with exactly matching bytes and relocations.
863                    // If no symbols match exactly, and `fuzzy_literals` is true, pick the closest
864                    // plausible match instead.
865                    if let Ok((left_diff, _right_diff)) =
866                        diff_data_symbol(in_obj, obj, in_symbol_idx, symbol_idx)
867                        && let Some(match_percent) = left_diff.match_percent
868                        && (match_percent == 100.0
869                            || (fuzzy_literals
870                                && match_percent >= 50.0
871                                && match_percent > closest_match_percent))
872                    {
873                        closest_match_symbol_idx = Some(symbol_idx);
874                        closest_match_percent = match_percent;
875                        if match_percent == 100.0 {
876                            break;
877                        }
878                    }
879                }
880                SectionKind::Bss => {
881                    // For BSS, pick the first symbol that has the exact matching size.
882                    if in_symbol.size == symbol.size {
883                        closest_match_symbol_idx = Some(symbol_idx);
884                        break;
885                    }
886                }
887                _ => unreachable!(),
888            }
889        }
890        return closest_match_symbol_idx;
891    }
892
893    // Try to find a symbol with a matching name
894    if let Some((symbol_idx, _)) = unmatched_symbols(obj, used).find(|&(_, symbol)| {
895        symbol_name_matches(in_symbol, symbol)
896            && symbol_section_kind(obj, symbol) == section_kind
897            && symbol_section(obj, symbol).is_some_and(|(name, _)| name == section_name)
898    }) {
899        return Some(symbol_idx);
900    }
901
902    None
903}
904
905/// Find matching sections between each object.
906fn matching_sections(left: Option<&Object>, right: Option<&Object>) -> Result<Vec<SectionMatch>> {
907    let mut matches = Vec::with_capacity(
908        left.as_ref()
909            .map_or(0, |o| o.sections.len())
910            .max(right.as_ref().map_or(0, |o| o.sections.len())),
911    );
912    if let Some(left) = left {
913        for (section_idx, section) in left.sections.iter().enumerate() {
914            if section.kind == SectionKind::Unknown {
915                continue;
916            }
917            matches.push(SectionMatch {
918                left: Some(section_idx),
919                right: find_section(right, &section.name, section.kind, &matches),
920                section_kind: section.kind,
921            });
922        }
923    }
924    if let Some(right) = right {
925        for (section_idx, section) in right.sections.iter().enumerate() {
926            if section.kind == SectionKind::Unknown {
927                continue;
928            }
929            if matches.iter().any(|m| m.right == Some(section_idx)) {
930                continue;
931            }
932            matches.push(SectionMatch {
933                left: None,
934                right: Some(section_idx),
935                section_kind: section.kind,
936            });
937        }
938    }
939    Ok(matches)
940}
941
942fn find_section(
943    obj: Option<&Object>,
944    name: &str,
945    section_kind: SectionKind,
946    matches: &[SectionMatch],
947) -> Option<usize> {
948    obj?.sections.iter().enumerate().position(|(i, s)| {
949        s.kind == section_kind && s.name == name && !matches.iter().any(|m| m.right == Some(i))
950    })
951}
952
953#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
954pub enum DiffSide {
955    /// The target/expected side of the diff.
956    Target,
957    /// The base side of the diff.
958    Base,
959}
960
961pub(crate) fn address_eq(left: ResolvedRelocation, right: ResolvedRelocation) -> bool {
962    if right.symbol.size == 0 && left.symbol.size != 0 {
963        // The base relocation is against a pool but the target relocation isn't.
964        // This can happen in rare cases where the compiler will generate a pool+addend relocation
965        // in the base's data, but the one detected in the target is direct with no addend.
966        // Just check that the final address is the same so these count as a match.
967        left.symbol.address as i64 + left.relocation.addend
968            == right.symbol.address as i64 + right.relocation.addend
969    } else {
970        // But otherwise, if the compiler isn't using a pool, we're more strict and check that the
971        // target symbol address and relocation addend both match exactly.
972        left.symbol.address == right.symbol.address
973            && left.relocation.addend == right.relocation.addend
974    }
975}
976
977pub(crate) fn section_name_eq(
978    left_obj: &Object,
979    right_obj: &Object,
980    left_section_index: usize,
981    right_section_index: usize,
982) -> bool {
983    left_obj.sections.get(left_section_index).is_some_and(|left_section| {
984        right_obj
985            .sections
986            .get(right_section_index)
987            .is_some_and(|right_section| left_section.name == right_section.name)
988    })
989}
990
991pub(crate) fn symbol_name_matches(left: &Symbol, right: &Symbol) -> bool {
992    if let Some(left_name) = &left.normalized_name
993        && let Some(right_name) = &right.normalized_name
994    {
995        left_name == right_name
996    } else {
997        left.name == right.name
998    }
999}