linker_diff/
lib.rs

1//! This crate finds differences between two ELF files. It's intended use is where the files were
2//! produced by different linkers, or different versions of the same linker. So the input files
3//! should be the same except for where the linkers make different decisions such as layout.
4//!
5//! Because the intended use is verifying the correct functioning of linkers, the focus is on
6//! avoiding false positives rather than avoiding false negatives. i.e. we'd much rather fail to
7//! report a difference than report a difference that doesn't matter. Ideally a reported difference
8//! should indicate a bug or missing feature of the linker.
9//!
10//! Right now, performance of this library is not a priority, so there's quite a bit of heap
11//! allocation going on that with a little work could be avoided. If we end up using this library as
12//! part of a fuzzer this may need to be optimised.
13
14#![allow(clippy::too_many_arguments)]
15
16use anyhow::Context as _;
17use anyhow::bail;
18use asm_diff::AddressIndex;
19use clap::Parser;
20use clap::ValueEnum;
21use itertools::Itertools as _;
22#[allow(clippy::wildcard_imports)]
23use linker_utils::elf::secnames::*;
24use object::LittleEndian;
25use object::Object as _;
26use object::ObjectSection;
27use object::ObjectSymbol as _;
28use object::read::elf::ElfSection64;
29use section_map::IndexedLayout;
30use section_map::LayoutAndFiles;
31use std::collections::HashMap;
32use std::fmt::Display;
33use std::path::Path;
34use std::path::PathBuf;
35
36mod aarch64;
37mod arch;
38mod asm_diff;
39mod debug_info_diff;
40mod diagnostics;
41mod eh_frame_diff;
42mod gnu_hash;
43mod header_diff;
44pub(crate) mod section_map;
45mod symtab;
46mod trace;
47mod x86_64;
48
49type Result<T = (), E = anyhow::Error> = core::result::Result<T, E>;
50type ElfFile64<'data> = object::read::elf::ElfFile64<'data, LittleEndian>;
51type ElfSymbol64<'data, 'file> = object::read::elf::ElfSymbol64<'data, 'file, LittleEndian>;
52
53use colored::Colorize;
54pub use diagnostics::enable_diagnostics;
55use section_map::InputSectionId;
56use section_map::OwnedFileIdentifier;
57
58#[non_exhaustive]
59#[derive(Parser, Default, Clone)]
60pub struct Config {
61    /// Keys to ignore.
62    #[arg(long, value_delimiter = ',')]
63    pub ignore: Vec<String>,
64
65    /// Show only the specified keys.
66    #[arg(long, value_delimiter = ',')]
67    pub only: Vec<String>,
68
69    /// Treat the sections with the specified names as equivalent. e.g. ".got.plt=.got"
70    #[arg(long, value_delimiter = ',', value_parser = parse_string_equality)]
71    pub equiv: Vec<(String, String)>,
72
73    /// Apply defaults for things that should be ignored currently for Wild. These defaults are
74    /// subject to change as Wild changes.
75    #[arg(long)]
76    pub wild_defaults: bool,
77
78    /// Print information about what sections did and didn't get diffed.
79    #[arg(long)]
80    pub coverage: bool,
81
82    /// Display names for input files.
83    #[arg(long, value_delimiter = ',', value_name = "NAME,NAME...")]
84    pub display_names: Vec<String>,
85
86    /// Files to compare against
87    #[arg(long = "ref", value_name = "FILE")]
88    pub references: Vec<PathBuf>,
89
90    #[arg(long, alias = "color", default_value = "auto")]
91    pub colour: Colour,
92
93    /// Primary file that we're validating against the reference file(s)
94    pub file: PathBuf,
95}
96
97#[derive(ValueEnum, Copy, Clone, Default)]
98pub enum Colour {
99    #[default]
100    Auto,
101    Never,
102    Always,
103}
104
105/// An output binary such as an executable or shared object.
106pub struct Binary<'data> {
107    name: String,
108    path: PathBuf,
109    elf_file: &'data ElfFile64<'data>,
110    address_index: AddressIndex<'data>,
111    name_index: NameIndex<'data>,
112    indexed_layout: Option<IndexedLayout<'data>>,
113    trace: trace::Trace,
114    sections_by_name: HashMap<&'data [u8], SectionInfo>,
115}
116
117#[derive(Clone, Copy)]
118struct SectionInfo {
119    index: object::SectionIndex,
120    size: u64,
121}
122
123struct NameIndex<'data> {
124    globals_by_name: HashMap<&'data [u8], Vec<object::SymbolIndex>>,
125    locals_by_name: HashMap<&'data [u8], Vec<object::SymbolIndex>>,
126    dynamic_by_name: HashMap<&'data [u8], Vec<object::SymbolIndex>>,
127}
128
129impl Config {
130    #[must_use]
131    pub fn from_env() -> Self {
132        Self::parse()
133    }
134
135    fn apply_wild_defaults(&mut self) {
136        self.ignore.extend(
137            [
138                // We don't currently support allocating space except in sections, so we have sections
139                // to hold the section and program headers. We then need to ignore them because GNU ld
140                // doesn't define such sections.
141                "section.shdr",
142                "section.phdr",
143                // We don't yet support these sections.
144                "section.data.rel.ro",
145                "section.debug*",
146                "section.stapsdt.base",
147                "section.note.gnu.build-id",
148                "section.note.gnu.property",
149                "section.note.stapsdt",
150                "section.hash",
151                // We set this to 8. GNU ld sometimes does too, but sometimes to 0.
152                "section.got.entsize",
153                "section.plt.got.entsize",
154                "section.plt.entsize",
155                // GNU ld sometimes sets this differently that we do.
156                "section.plt",
157                "section.plt.alignment",
158                "section.bss.alignment",
159                "section.gnu.build.attributes",
160                "section.annobin.notes.entsize",
161                // We don't yet group .lrodata sections separately.
162                "section.lrodata",
163                // We currently output version info when linking against the interpreter
164                // (ld-linux-x86-64.so.2). GNU ld doesn't.
165                ".dynamic.DT_VERNEEDNUM",
166                // We currently handle these dynamic tags differently
167                ".dynamic.DT_JMPREL",
168                ".dynamic.DT_PLTGOT",
169                ".dynamic.DT_PLTREL",
170                // We currently produce a .got.plt whenever we produce .plt, but GNU ld doesn't
171                "section.got.plt",
172                GOT_PLT_SECTION_NAME_STR,
173                // We don't currently produce a separate .plt.sec section.
174                "section.plt.sec",
175                // We don't yet write this.
176                ".dynamic.DT_HASH",
177                // We do support this. TODO: Should definitely look into why we're seeing this missing
178                // in our output.
179                "section.rela.plt",
180                // We currently write 10 byte PLT entries in some cases where GNU ld writes 8 byte ones.
181                "section.plt.got.alignment",
182                // GNU ld sometimes makes this writable sometimes not. Presumably this depends on
183                // whether there are relocations or some flags.
184                "section.eh_frame.flags",
185                // A package note section used by Ubuntu: https://systemd.io/ELF_PACKAGE_METADATA/
186                "section.note.package",
187                // TLSDESC relaxations aren't yet implemented.
188                "rel.match_failed.R_X86_64_GOTPC32_TLSDESC",
189                "rel.missing-opt.R_X86_64_TLSDESC_CALL.SkipTlsDescCall.*",
190                // Wild eliminates GOTPCRELX in statically linked executables even for undefined
191                // symbols, whereas other linkers don't. This is a valid optimisation that other
192                // linkers don't currently do.
193                "rel.extra-opt.R_X86_64_GOTPCRELX.CallIndirectToRelative.static-*",
194                // We don't yet support emitting warnings.
195                "section.gnu.warning",
196                // GNU ld sometimes applies relaxations that we don't yet.
197                "rel.missing-opt.R_AARCH64_CALL26.ReplaceWithNop.static-non-pie",
198                "rel.missing-opt.R_AARCH64_JUMP26.ReplaceWithNop.static-non-pie",
199                "rel.match_failed.R_AARCH64_TLSDESC_ADR_PAGE21",
200                "rel.match_failed.R_AARCH64_TLSDESC_LD64_LO12",
201                "rel.match_failed.R_AARCH64_TLSGD_ADD_LO12_NC",
202                "rel.missing-opt.R_X86_64_TLSGD.TlsGdToInitialExec.shared-object",
203                // We seem to do an optimisation here where GNU ld doesn't. TODO: Look into if this
204                // is OK.
205                "rel.extra-opt.R_AARCH64_TLSIE_ADR_GOTTPREL_PAGE21.MovzXnLsl16.*",
206                // GNU ld sometimes relaxes an adrp instruction to an adr instruction when the
207                // address is known and within +/-1MB. We don't as yet.
208                "rel.missing-opt.R_AARCH64_ADR_GOT_PAGE.AdrpToAdr.*",
209                "rel.missing-opt.R_AARCH64_ADR_PREL_PG_HI21.AdrpToAdr.*",
210                // The other linkers set properties on sections if all input sections have that
211                // property. For sections like .rodata, this seems like an unimportant behaviour to
212                // replicate.
213                "section.rodata.entsize",
214                "section.rodata.flags",
215            ]
216            .into_iter()
217            .map(ToOwned::to_owned),
218        );
219
220        #[cfg(target_arch = "aarch64")]
221        {
222            self.ignore.extend(
223                [
224                    // Other linkers have a bigger initial PLT entry, thus the entsize is set to zero:
225                    // https://sourceware.org/bugzilla/show_bug.cgi?id=26312
226                    "section.plt.entsize",
227                ]
228                .into_iter()
229                .map(ToOwned::to_owned),
230            );
231        }
232
233        self.equiv.push((
234            GOT_SECTION_NAME_STR.to_owned(),
235            GOT_PLT_SECTION_NAME_STR.to_owned(),
236        ));
237        // We don't currently define .plt.got and .plt.sec, we just put everything into .plt.
238        self.equiv.push((
239            PLT_SECTION_NAME_STR.to_owned(),
240            PLT_GOT_SECTION_NAME_STR.to_owned(),
241        ));
242        self.equiv.push((
243            PLT_SECTION_NAME_STR.to_owned(),
244            PLT_SEC_SECTION_NAME_STR.to_owned(),
245        ));
246    }
247
248    #[must_use]
249    pub fn to_arg_string(&self) -> String {
250        let mut out = String::new();
251        if self.wild_defaults {
252            out.push_str("--wild-defaults ");
253        }
254        if !self.ignore.is_empty() {
255            out.push_str("--ignore '");
256            out.push_str(&self.ignore.join(","));
257            out.push_str("' ");
258        }
259        if !self.equiv.is_empty() {
260            out.push_str("--equiv '");
261            let parts = self
262                .equiv
263                .iter()
264                .map(|(k, v)| format!("{k}={v}"))
265                .collect_vec();
266            out.push_str(&parts.join(","));
267            out.push_str("' ");
268        }
269        if !self.display_names.is_empty() {
270            out.push_str("--display-names ");
271            out.push_str(&self.display_names.join(","));
272            out.push(' ');
273        }
274        for file in &self.references {
275            out.push_str("--ref ");
276            out.push_str(&file.to_string_lossy());
277            out.push(' ');
278        }
279        out.push_str(&self.file.to_string_lossy());
280        out
281    }
282
283    fn filenames(&self) -> impl Iterator<Item = &PathBuf> {
284        // We always put our file first, since it makes it easier to treat it differently. e.g. when
285        // we compare a value from our file against each of the values from the other files.
286        std::iter::once(&self.file).chain(&self.references)
287    }
288}
289
290impl<'data> Binary<'data> {
291    pub(crate) fn new(
292        elf_file: &'data ElfFile64<'data>,
293        name: String,
294        path: PathBuf,
295        layout_and_files: Option<&'data LayoutAndFiles>,
296    ) -> Result<Self> {
297        let address_index = AddressIndex::new(elf_file);
298        let indexed_layout = layout_and_files.map(IndexedLayout::new).transpose()?;
299        let trace = trace::Trace::for_path(&path)?;
300
301        let sections_by_name = elf_file
302            .sections()
303            .map(|section| {
304                Ok((
305                    section.name_bytes()?,
306                    SectionInfo {
307                        index: section.index(),
308                        size: section.size(),
309                    },
310                ))
311            })
312            .collect::<Result<HashMap<&[u8], SectionInfo>>>()?;
313
314        Ok(Self {
315            name,
316            elf_file,
317            path,
318            address_index,
319            name_index: NameIndex::new(elf_file),
320            indexed_layout,
321            trace,
322            sections_by_name,
323        })
324    }
325
326    /// Looks up a symbol, first trying to get a global, or failing that a local. If multiple
327    /// symbols have the same name, then `hint_address` is used to select which one to return.
328    pub(crate) fn symbol_by_name(&self, name: &[u8], hint_address: u64) -> NameLookupResult {
329        match self.lookup_symbol(&self.name_index.globals_by_name, name, hint_address) {
330            NameLookupResult::Undefined => {
331                self.lookup_symbol(&self.name_index.locals_by_name, name, hint_address)
332            }
333            other => other,
334        }
335    }
336
337    fn lookup_symbol(
338        &self,
339        symbol_map: &HashMap<&[u8], Vec<object::SymbolIndex>>,
340        name: &[u8],
341        hint_address: u64,
342    ) -> NameLookupResult {
343        let indexes = symbol_map.get(name).map(Vec::as_slice).unwrap_or_default();
344
345        if indexes.len() >= 2 {
346            for sym_index in indexes {
347                if let Ok(sym) = self.elf_file.symbol_by_index(*sym_index) {
348                    if sym.address() == hint_address {
349                        return NameLookupResult::Defined(sym);
350                    }
351                }
352            }
353
354            // We didn't find a symbol with exactly the address hinted at.
355            return NameLookupResult::Duplicate;
356        }
357
358        if let Some(symbol_index) = indexes.first() {
359            if let Ok(sym) = self.elf_file.symbol_by_index(*symbol_index) {
360                NameLookupResult::Defined(sym)
361            } else {
362                NameLookupResult::Undefined
363            }
364        } else {
365            NameLookupResult::Undefined
366        }
367    }
368
369    fn has_symbols(&self) -> bool {
370        !self.name_index.globals_by_name.is_empty()
371    }
372
373    fn section_by_name(&self, name: &str) -> Option<ElfSection64<LittleEndian>> {
374        self.section_by_name_bytes(name.as_bytes())
375    }
376
377    fn section_by_name_bytes(&self, name: &[u8]) -> Option<ElfSection64<LittleEndian>> {
378        let index = self.sections_by_name.get(name)?.index;
379        self.elf_file.section_by_index(index).ok()
380    }
381
382    /// Returns the name of the section that contains the supplied address. Does a linear scan, so
383    /// should only be used for error reporting.
384    fn section_containing_address(&self, address: u64) -> Option<&str> {
385        self.elf_file
386            .sections()
387            .find(|sec| (sec.address()..sec.address() + sec.size()).contains(&address))
388            .and_then(|sec| sec.name().ok())
389    }
390}
391
392#[derive(Debug)]
393enum NameLookupResult<'data, 'file> {
394    Undefined,
395    Duplicate,
396    Defined(ElfSymbol64<'data, 'file>),
397}
398
399fn validate_objects(
400    report: &mut Report,
401    objects: &[Binary],
402    validation_name: &str,
403    validation_fn: impl Fn(&Binary) -> Result,
404) {
405    let values = objects
406        .iter()
407        .map(|obj| match validation_fn(obj) {
408            Ok(_) => "OK".to_owned(),
409            Err(e) => e.to_string(),
410        })
411        .collect_vec();
412    if first_equals_any(values.iter()) {
413        return;
414    }
415    report.add_diff(Diff {
416        key: validation_name.to_owned(),
417        values: DiffValues::PerObject(values),
418    });
419}
420
421pub struct Report {
422    /// The names of each of our binaries. These should be short, not a full path, since we often
423    /// prefix lines with these names.
424    names: Vec<String>,
425
426    /// The full path of each of our binaries.
427    paths: Vec<PathBuf>,
428
429    /// The differences that were detected.
430    diffs: Vec<Diff>,
431
432    /// The configuration that was used.
433    config: Config,
434
435    coverage: Option<Coverage>,
436}
437
438#[derive(Default)]
439struct Coverage {
440    sections: HashMap<InputSectionId, SectionCoverage>,
441}
442
443struct SectionCoverage {
444    /// The original input file from which the section came.
445    original_file: OwnedFileIdentifier,
446
447    /// The name of the section.
448    name: String,
449
450    /// Whether we diffed this section at all.
451    diffed: bool,
452
453    /// The size of the section in bytes.
454    num_bytes: u64,
455}
456
457impl Report {
458    pub fn from_config(mut config: Config) -> Result<Report> {
459        // This changes mutable global state, which isn't an ideal thing to be doing from a library.
460        // It's expedient though, and we don't really expect linker-diff to get used as a library
461        // anywhere except the linker-diff binary and wild's integration tests, so this probably
462        // isn't a big deal.
463        match config.colour {
464            Colour::Auto => colored::control::unset_override(),
465            Colour::Never => colored::control::set_override(false),
466            Colour::Always => colored::control::set_override(true),
467        }
468
469        if config.wild_defaults {
470            config.apply_wild_defaults();
471        }
472        let display_names = short_file_display_names(&config)?;
473
474        let file_bytes = config
475            .filenames()
476            .map(|filename| -> Result<Vec<u8>> {
477                let bytes = std::fs::read(filename)
478                    .with_context(|| format!("Failed to read `{}`", filename.display()))?;
479                Ok(bytes)
480            })
481            .collect::<Result<Vec<Vec<u8>>>>()?;
482
483        let elf_files = file_bytes
484            .iter()
485            .map(|bytes| -> Result<ElfFile64> { Ok(ElfFile64::parse(bytes.as_slice())?) })
486            .collect::<Result<Vec<_>>>()?;
487
488        let layouts = config
489            .filenames()
490            .map(|p| LayoutAndFiles::from_base_path(p))
491            .collect::<Result<Vec<_>>>()?;
492
493        let objects = elf_files
494            .iter()
495            .zip(display_names)
496            .zip(config.filenames())
497            .zip(&layouts)
498            .map(|(((elf_file, name), path), layout)| -> Result<Binary> {
499                Binary::new(elf_file, name, path.clone(), layout.as_ref())
500            })
501            .collect::<Result<Vec<_>>>()?;
502
503        let mut report = Report {
504            names: objects.iter().map(|o| o.name.clone()).collect(),
505            paths: objects.iter().map(|o| o.path.clone()).collect(),
506            diffs: Default::default(),
507            coverage: config.coverage.then(Coverage::default),
508            config,
509        };
510        report.run_on_objects(&objects);
511        Ok(report)
512    }
513
514    fn run_on_objects(&mut self, objects: &[Binary]) {
515        validate_objects(
516            self,
517            objects,
518            GNU_HASH_SECTION_NAME_STR,
519            gnu_hash::check_object,
520        );
521        validate_objects(self, objects, "index", asm_diff::validate_indexes);
522        validate_objects(
523            self,
524            objects,
525            GOT_PLT_SECTION_NAME_STR,
526            asm_diff::validate_got_plt,
527        );
528        validate_objects(
529            self,
530            objects,
531            SYMTAB_SECTION_NAME_STR,
532            symtab::validate_debug,
533        );
534        validate_objects(
535            self,
536            objects,
537            DYNSYM_SECTION_NAME_STR,
538            symtab::validate_dynamic,
539        );
540        header_diff::check_dynamic_headers(self, objects);
541        header_diff::check_file_headers(self, objects);
542        asm_diff::report_section_diffs(self, objects);
543        header_diff::report_section_diffs(self, objects);
544        eh_frame_diff::report_diffs(self, objects);
545        debug_info_diff::check_debug_info(self, objects);
546    }
547
548    fn add_diff(&mut self, diff: Diff) {
549        if self.should_ignore(&diff.key) {
550            return;
551        }
552        self.diffs.push(diff);
553    }
554
555    fn add_diffs(&mut self, new_diffs: Vec<Diff>) {
556        for diff in new_diffs {
557            self.add_diff(diff);
558        }
559    }
560
561    #[must_use]
562    pub fn has_problems(&self) -> bool {
563        !self.diffs.is_empty()
564    }
565
566    fn should_ignore(&self, key: &str) -> bool {
567        if !self.config.only.is_empty() {
568            return !self.config.only.iter().any(|i| {
569                if let Some(prefix) = i.strip_suffix('*') {
570                    key.starts_with(prefix)
571                } else {
572                    key == *i
573                }
574            });
575        }
576        self.config.ignore.iter().any(|i| {
577            if let Some(prefix) = i.strip_suffix('*') {
578                key.starts_with(prefix)
579            } else {
580                key == *i
581            }
582        })
583    }
584
585    fn add_error(&mut self, error: impl Into<String>) {
586        self.diffs.push(Diff {
587            key: "error".to_owned(),
588            values: DiffValues::PreFormatted(error.into()),
589        });
590    }
591}
592
593struct Diff {
594    key: String,
595    values: DiffValues,
596}
597
598enum DiffValues {
599    PerObject(Vec<String>),
600    PreFormatted(String),
601}
602
603impl Display for Report {
604    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
605        for (name, path) in self.names.iter().zip(&self.paths) {
606            writeln!(f, "{name}: {}", path.display())?;
607        }
608
609        for diff in &self.diffs {
610            writeln!(f, "{}", diff.key)?;
611
612            match &diff.values {
613                DiffValues::PerObject(values) => {
614                    for (filename, result) in self.names.iter().zip(values) {
615                        writeln!(f, "  {filename} {result}")?;
616                    }
617                }
618                DiffValues::PreFormatted(values) => {
619                    for line in values.lines() {
620                        writeln!(f, "  {line}")?;
621                    }
622                }
623            }
624
625            writeln!(f)?;
626        }
627
628        if let Some(coverage) = self.coverage.as_ref() {
629            Display::fmt(coverage, f)?;
630        }
631
632        Ok(())
633    }
634}
635
636impl Display for Binary<'_> {
637    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
638        self.name.fmt(f)
639    }
640}
641
642impl Display for Coverage {
643    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
644        writeln!(f, "Diffed sections:")?;
645
646        let mut total_bytes = 0;
647        let mut total_diffed = 0;
648
649        for sec in self.sections.values() {
650            writeln!(
651                f,
652                "  {} {}: {}",
653                sec.original_file,
654                sec.name,
655                if sec.diffed {
656                    "true".green()
657                } else {
658                    "false".red()
659                }
660            )?;
661
662            if sec.diffed {
663                total_diffed += sec.num_bytes;
664            }
665
666            total_bytes += sec.num_bytes;
667        }
668
669        writeln!(
670            f,
671            "Diffed {total_diffed} of {total_bytes} section bytes ({}%)",
672            total_diffed * 100 / total_bytes
673        )?;
674
675        Ok(())
676    }
677}
678
679fn short_file_display_names(config: &Config) -> Result<Vec<String>> {
680    let paths: Vec<&PathBuf> = config.filenames().collect();
681    if !config.display_names.is_empty() {
682        if config.display_names.len() != paths.len() {
683            bail!(
684                "--display-names has {} names, but {} filenames were provided",
685                config.display_names.len(),
686                paths.len()
687            );
688        }
689        return Ok(config.display_names.clone());
690    }
691    if paths.is_empty() {
692        return Ok(vec![]);
693    }
694    let mut names = paths
695        .iter()
696        .map(|p| p.to_string_lossy().into_owned())
697        .collect_vec();
698    if names.iter().all(|name| {
699        Path::new(name)
700            .extension()
701            .is_some_and(|ext| ext.eq_ignore_ascii_case("so"))
702    }) {
703        names = names
704            .into_iter()
705            .map(|n| n.strip_suffix(".so").unwrap().to_owned())
706            .collect();
707    }
708
709    if names.len() > 1 {
710        // This is not quite right, since we might split in the middle of a multibyte character.
711        // But this is a dev tool, so we'll punt on that for now.
712        let mut iterators = names.iter().map(|n| n.bytes()).collect_vec();
713        let mut n = 0;
714        while first_equals_all(iterators.iter_mut().map(Iterator::next)) {
715            n += 1;
716        }
717        names = names
718            .iter()
719            .map(|name| String::from_utf8_lossy(&name.bytes().skip(n).collect_vec()).into_owned())
720            .collect_vec();
721    }
722    Ok(names)
723}
724
725fn first_equals_all<T: PartialEq>(mut inputs: impl Iterator<Item = T>) -> bool {
726    let Some(first) = inputs.next() else {
727        return true;
728    };
729    for next in inputs {
730        if next != first {
731            return false;
732        }
733    }
734    true
735}
736
737/// Returns whether the first input is equal to at least one of the remaining values.
738fn first_equals_any<T: PartialEq>(mut inputs: impl Iterator<Item = T>) -> bool {
739    let Some(first) = inputs.next() else {
740        return true;
741    };
742    for next in inputs {
743        if next == first {
744            return true;
745        }
746    }
747    false
748}
749
750impl<'data> NameIndex<'data> {
751    fn new(elf_file: &ElfFile64<'data>) -> NameIndex<'data> {
752        let mut globals_by_name: HashMap<&[u8], Vec<object::SymbolIndex>> = HashMap::new();
753        let mut locals_by_name: HashMap<&[u8], Vec<object::SymbolIndex>> = HashMap::new();
754        let mut dynamic_by_name: HashMap<&[u8], Vec<object::SymbolIndex>> = HashMap::new();
755
756        for sym in elf_file.symbols() {
757            // We only index symbols that have a section. Note this is different than the object
758            // crate's `is_defined`, which imposes additional requirements that we don't want.
759            if sym.section_index().is_none() {
760                continue;
761            }
762
763            if let Ok(mut name) = sym.name_bytes() {
764                // Wild doesn't emit local symbols that start with ".L". The other linkers mostly do
765                // the same. However, GNU ld and lld, if they encounter a GOT-forming relocation to
766                // such a symbol, even if they then optimise away the GOT-forming relocation, will
767                // emit the symbol. This behaviour seems weird and not worth replicating, so we just
768                // ignore all just symbols.
769                if name.starts_with(b".L") {
770                    continue;
771                }
772
773                // GNU ld sometimes emits symbols that contain the symbol version. This causes
774                // problems when we go to look those symbols up, since they no longer match the name
775                // of the symbol in the original input file. So for now at least, we get rid of the
776                // version.
777                if let Some(at_pos) = name.iter().position(|b| *b == b'@') {
778                    name = &name[..at_pos];
779                }
780
781                if sym.is_global() {
782                    globals_by_name.entry(name).or_default().push(sym.index());
783                } else {
784                    locals_by_name.entry(name).or_default().push(sym.index());
785                }
786            }
787        }
788
789        for sym in elf_file.dynamic_symbols() {
790            if let Ok(name) = sym.name_bytes() {
791                dynamic_by_name.entry(name).or_default().push(sym.index());
792            }
793        }
794
795        NameIndex {
796            globals_by_name,
797            locals_by_name,
798            dynamic_by_name,
799        }
800    }
801}
802
803fn slice_from_all_bytes<T: object::Pod>(data: &[u8]) -> &[T] {
804    object::slice_from_bytes(data, data.len() / size_of::<T>())
805        .unwrap()
806        .0
807}
808
809fn parse_string_equality(
810    s: &str,
811) -> Result<(String, String), Box<dyn std::error::Error + Send + Sync + 'static>> {
812    let (a, b) = s
813        .split_once('=')
814        .ok_or_else(|| format!("invalid key-value pair. No '=' found in `{s}`"))?;
815    Ok((a.to_owned(), b.to_owned()))
816}