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