Skip to main content

objdiff_core/obj/
read.rs

1use alloc::{
2    boxed::Box,
3    collections::BTreeMap,
4    format,
5    string::{String, ToString},
6    vec,
7    vec::Vec,
8};
9use core::{
10    cmp::Ordering,
11    num::{NonZeroU32, NonZeroU64},
12};
13
14use anyhow::{Context, Result, anyhow, bail, ensure};
15use object::{Architecture, Object as _, ObjectSection as _, ObjectSymbol as _};
16
17use crate::{
18    arch::{Arch, RelocationOverride, RelocationOverrideTarget, new_arch},
19    diff::{DiffObjConfig, DiffSide},
20    obj::{
21        FlowAnalysisResult, Object, Relocation, RelocationFlags, Section, SectionData, SectionFlag,
22        SectionKind, Symbol, SymbolFlag, SymbolFlagSet, SymbolKind,
23        comment::{COMMENT_SECTION, CommentSym, MWComment},
24        split_meta::{SPLITMETA_SECTION, SplitMeta},
25    },
26    util::{align_data_slice_to, align_u64_to, read_u16, read_u32},
27};
28
29fn map_section_kind(section: &object::Section) -> SectionKind {
30    match section.kind() {
31        object::SectionKind::Text => SectionKind::Code,
32        object::SectionKind::Data
33        | object::SectionKind::ReadOnlyData
34        | object::SectionKind::ReadOnlyString
35        | object::SectionKind::Tls => SectionKind::Data,
36        object::SectionKind::UninitializedData
37        | object::SectionKind::UninitializedTls
38        | object::SectionKind::Common => SectionKind::Bss,
39        _ => SectionKind::Unknown,
40    }
41}
42
43/// Check if a symbol's name is partially compiler-generated, and if so normalize it for pairing.
44/// e.g. symbol$1234 and symbol$2345 will both be replaced with symbol$0000 internally.
45fn get_normalized_symbol_name(name: &str) -> Option<String> {
46    const DUMMY_UNIQUE_ID: &str = "0000";
47    const DUMMY_UNIQUE_MSVC_ID: &str = "00000000";
48    if let Some((prefix, suffix)) = name.split_once("@class$")
49        && let Some(idx) = suffix.chars().position(|c| !c.is_numeric())
50        && idx > 0
51    {
52        // Match Metrowerks anonymous class symbol names, ignoring the unique ID.
53        // e.g. __dt__Q29dCamera_c23@class$3665d_camera_cppFv
54        // and: __dt__Q29dCamera_c23@class$1727d_camera_cppFv
55        let suffix = &suffix[idx..];
56        Some(format!("{prefix}@class${DUMMY_UNIQUE_ID}{suffix}"))
57    } else if let Some((prefix, suffix)) = name.split_once('$')
58        && suffix.chars().all(char::is_numeric)
59    {
60        // Match Metrowerks symbol$1234 against symbol$2345
61        Some(format!("{prefix}${DUMMY_UNIQUE_ID}"))
62    } else if let Some((prefix, suffix)) = name.split_once('.')
63        && suffix.chars().all(char::is_numeric)
64    {
65        // Match GCC symbol.1234 against symbol.2345
66        Some(format!("{prefix}.{DUMMY_UNIQUE_ID}"))
67    } else if name.starts_with('?') {
68        // Match MSVC anonymous class symbol names, ignoring the unique ID.
69        // e.g. ?CheckContextOr@?A0x24773155@@YA_NPBVDataArray@@@Z
70        // and: ?CheckContextOr@?A0xddf6240c@@YA_NPBVDataArray@@@Z
71        let mut name_str = String::from(name);
72        let anon_indices: Vec<usize> = name_str.match_indices("?A0x").map(|(idx, _)| idx).collect();
73        if !anon_indices.is_empty() {
74            for idx in anon_indices {
75                // the str sequence we're looking for is: ?A0xXXXXXXXX@@
76                if u32::from_str_radix(&name_str[idx + 4..idx + 12], 16).is_ok()
77                    && &name_str[idx + 12..idx + 14] == "@@"
78                {
79                    // if the two above checks passed, we're good to replace the hash
80                    name_str.replace_range(idx + 4..idx + 12, DUMMY_UNIQUE_MSVC_ID);
81                }
82            }
83            Some(name_str)
84        } else {
85            None
86        }
87    } else {
88        None
89    }
90}
91
92/// Check if a symbol's name is entirely compiler-generated (e.g. for a literal).
93/// This enables pairing these symbols up by their value instead of their name.
94fn is_symbol_name_compiler_generated(name: &str) -> bool {
95    if name.starts_with('@') && name[1..].chars().all(char::is_numeric) {
96        // Match Metrowerks @1234 against @2345
97        return true;
98    } else if name.starts_with("_$E") && name[3..].chars().all(char::is_numeric) {
99        // Match MSVC _$E1234 against _$E2345
100        return true;
101    } else if name.starts_with("$LC") && name[3..].chars().all(char::is_numeric) {
102        // Match GCC $LC1234 against $LC2345
103        return true;
104    }
105    false
106}
107
108fn map_symbol(
109    arch: &dyn Arch,
110    file: &object::File,
111    symbol: &object::Symbol,
112    section_indices: &[usize],
113    split_meta: Option<&SplitMeta>,
114    comment_syms: Option<&Vec<CommentSym>>,
115    config: &DiffObjConfig,
116) -> Result<Symbol> {
117    let mut name = symbol.name().context("Failed to process symbol name")?.to_string();
118    let mut size = symbol.size();
119    if let (object::SymbolKind::Section, Some(section)) =
120        (symbol.kind(), symbol.section_index().and_then(|i| file.section_by_index(i).ok()))
121    {
122        let section_name = section.name().context("Failed to process section name")?;
123        name = format!("[{section_name}]");
124        // For section symbols, set the size to zero. If the size is non-zero, it will be included
125        // in the diff. Most of the time, this is duplicative, given that we'll have function or
126        // object symbols that cover the same range. In the case of an empty section, the size
127        // inference logic below will set the size back to the section size, thus acting as a
128        // placeholder symbol.
129        size = 0;
130    }
131
132    let mut flags = arch.extra_symbol_flags(symbol);
133    if symbol.is_global() {
134        flags |= SymbolFlag::Global;
135    }
136    if symbol.is_local() {
137        flags |= SymbolFlag::Local;
138    }
139    if symbol.is_common() {
140        flags |= SymbolFlag::Common;
141    }
142    if symbol.is_weak() {
143        flags |= SymbolFlag::Weak;
144    }
145    if file.format() == object::BinaryFormat::Elf
146        && symbol.scope() == object::SymbolScope::Linkage
147        && (file.architecture() != Architecture::Arm || !symbol.is_global())
148    {
149        flags |= SymbolFlag::Hidden;
150    }
151    if file.format() == object::BinaryFormat::Coff
152        && let Ok(name) = symbol.name()
153        && (name.starts_with("except_data_")
154            || name.starts_with("__unwind")
155            || name.starts_with("__catch"))
156    {
157        flags |= SymbolFlag::Hidden;
158    }
159
160    let kind = match symbol.kind() {
161        object::SymbolKind::Text => SymbolKind::Function,
162        object::SymbolKind::Data => SymbolKind::Object,
163        object::SymbolKind::Section => SymbolKind::Section,
164        _ => SymbolKind::Unknown,
165    };
166    let address = arch.symbol_address(symbol.address(), kind);
167    let demangled_name = config.demangler.demangle(&name);
168    // Find the alignment for the symbol if available
169    let comment_sym = comment_syms.map(|vec| vec[symbol.index().0 - 1]);
170    let align = comment_sym.and_then(|c| NonZeroU32::new(c.align));
171    // Find the virtual address for the symbol if available
172    let virtual_address = split_meta
173        .and_then(|m| m.virtual_addresses.as_ref())
174        .and_then(|v| v.get(symbol.index().0).cloned());
175    let section = symbol.section_index().and_then(|i| section_indices.get(i.0).copied());
176    let normalized_name = get_normalized_symbol_name(&name);
177    if is_symbol_name_compiler_generated(&name) {
178        flags |= SymbolFlag::CompilerGenerated;
179    }
180
181    Ok(Symbol {
182        name,
183        demangled_name,
184        normalized_name,
185        address,
186        size,
187        kind,
188        section,
189        flags,
190        align,
191        virtual_address,
192    })
193}
194
195fn map_symbols(
196    arch: &dyn Arch,
197    obj_file: &object::File,
198    section_indices: &[usize],
199    split_meta: Option<&SplitMeta>,
200    comment_syms: Option<&Vec<CommentSym>>,
201    config: &DiffObjConfig,
202) -> Result<(Vec<Symbol>, Vec<usize>)> {
203    // symbols() is not guaranteed to be sorted by address.
204    // We sort it here to fix pairing bugs with diff algorithms that assume the symbols are ordered.
205    // Sorting everything here once is less expensive than sorting subsets later in expensive loops.
206    let mut max_index = 0;
207    let mut obj_symbols = obj_file
208        .symbols()
209        .filter(|s| s.kind() != object::SymbolKind::File)
210        .inspect(|sym| max_index = max_index.max(sym.index().0))
211        .collect::<Vec<_>>();
212    obj_symbols.sort_by(|a, b| {
213        // First sort symbols by their section names lexicographically (for COFF section groups)
214        if let Some(a_section_index) = a.section_index()
215            && let Some(b_section_index) = b.section_index()
216            && let Ok(a_section) = obj_file.section_by_index(a_section_index)
217            && let Ok(b_section) = obj_file.section_by_index(b_section_index)
218            && let Ok(a_section_name) = a_section.name()
219            && let Ok(b_section_name) = b_section.name()
220        {
221            if a_section_name.contains('$') && !b_section_name.contains('$') {
222                return Ordering::Greater;
223            } else if !a_section_name.contains('$') && b_section_name.contains('$') {
224                return Ordering::Less;
225            }
226        }
227        // Sort symbols by section index, placing absolute symbols last
228        a.section_index()
229            .map_or(usize::MAX, |s| s.0)
230            .cmp(&b.section_index().map_or(usize::MAX, |s| s.0))
231            .then_with(|| {
232                // Sort section symbols first in a section
233                if a.kind() == object::SymbolKind::Section {
234                    Ordering::Less
235                } else if b.kind() == object::SymbolKind::Section {
236                    Ordering::Greater
237                } else {
238                    Ordering::Equal
239                }
240            })
241            // Sort by address within section
242            .then_with(|| a.address().cmp(&b.address()))
243            // If there are multiple symbols with the same address, smaller symbol first
244            .then_with(|| a.size().cmp(&b.size()))
245    });
246    let mut symbols = Vec::<Symbol>::with_capacity(obj_symbols.len() + obj_file.sections().count());
247    let mut symbol_indices = vec![usize::MAX; max_index + 1];
248    for obj_symbol in obj_symbols {
249        let symbol = map_symbol(
250            arch,
251            obj_file,
252            &obj_symbol,
253            section_indices,
254            split_meta,
255            comment_syms,
256            config,
257        )?;
258        symbol_indices[obj_symbol.index().0] = symbols.len();
259        symbols.push(symbol);
260    }
261
262    Ok((symbols, symbol_indices))
263}
264
265/// Add an extra fake symbol to the start of each data section in order to allow the user to diff
266/// all of the data in the section at once by clicking on this fake symbol at the top of the list.
267fn add_section_symbols(sections: &[Section], symbols: &mut Vec<Symbol>) {
268    for (section_idx, section) in sections.iter().enumerate() {
269        if section.kind != SectionKind::Data {
270            continue;
271        }
272
273        // Instead of naming the fake section symbol after `section.name` (e.g. ".data") we use
274        // `section.id` (e.g. ".data-0") so that it is unique when multiple sections with the same
275        // name exist and it also doesn't conflict with any real section symbols from the object.
276        let name = if section.flags.contains(SectionFlag::Combined) {
277            // For combined sections, `section.id` (e.g. ".data-combined") is inconsistent with
278            // uncombined section IDs, so we add the "-0" suffix to the name to enable proper
279            // pairing when one side had multiple sections combined and the other only had one
280            // section to begin with.
281            format!("[{}-0]", section.name)
282        } else {
283            format!("[{}]", section.id)
284        };
285
286        // `section.size` can include extra padding, so instead prefer using the address that the
287        // last symbol ends at when there are any symbols in the section.
288        let size = symbols
289            .iter()
290            .filter(|s| {
291                s.section == Some(section_idx) && s.kind == SymbolKind::Object && s.size > 0
292            })
293            .map(|s| s.address + s.size)
294            .max()
295            .unwrap_or(section.size);
296
297        symbols.push(Symbol {
298            name,
299            demangled_name: None,
300            normalized_name: None,
301            address: 0,
302            size,
303            kind: SymbolKind::Section,
304            section: Some(section_idx),
305            flags: SymbolFlagSet::default() | SymbolFlag::Local,
306            align: None,
307            virtual_address: None,
308        });
309    }
310}
311
312/// When inferring a symbol's size, we ignore symbols that start with specific prefixes. They are
313/// usually emitted as branch targets and do not represent the start of a function or object.
314fn is_local_label(symbol: &Symbol) -> bool {
315    const LABEL_PREFIXES: &[&str] = &[".L", "LAB_", "switchD_"];
316    symbol.size == 0
317        && symbol.flags.contains(SymbolFlag::Local)
318        && LABEL_PREFIXES.iter().any(|p| symbol.name.starts_with(p))
319}
320
321fn infer_symbol_sizes(arch: &dyn Arch, symbols: &mut [Symbol], sections: &[Section]) -> Result<()> {
322    // Above, we've sorted the symbols by section and then by address, and also mapped section relocations.
323
324    // Set symbol sizes based on the next symbol's address
325    let mut iter_idx = 0;
326    let mut last_end = (0, 0);
327    while iter_idx < symbols.len() {
328        let symbol_idx = iter_idx;
329        let symbol = &symbols[symbol_idx];
330        let Some(section_idx) = symbol.section else {
331            // Start of absolute symbols
332            break;
333        };
334        iter_idx += 1;
335        if symbol.size != 0 {
336            if symbol.kind != SymbolKind::Section {
337                last_end = (section_idx, symbol.address + symbol.size);
338            }
339            continue;
340        }
341        // Skip over symbols that are contained within the previous symbol
342        if last_end.0 == section_idx && last_end.1 > symbol.address {
343            continue;
344        }
345        let next_symbol = loop {
346            let Some(next_symbol) = symbols.get(iter_idx) else {
347                break None;
348            };
349            if next_symbol.section != Some(section_idx) {
350                break None;
351            }
352            if match symbol.kind {
353                SymbolKind::Function
354                    if next_symbol.kind == SymbolKind::Function
355                        && (next_symbol.name.starts_with("__unwind$")
356                            || next_symbol.name.starts_with("__catch$")) =>
357                {
358                    // MSVC unwinds/catches count as functions, but shouldn't cut off the function they're from.
359                    false
360                }
361                SymbolKind::Function | SymbolKind::Object => {
362                    // For function/object symbols, find the next function/object
363                    matches!(next_symbol.kind, SymbolKind::Function | SymbolKind::Object)
364                }
365                SymbolKind::Unknown | SymbolKind::Section => {
366                    // For labels (or anything else), stop at any symbol
367                    true
368                }
369            } && !is_local_label(next_symbol)
370            {
371                break Some(next_symbol);
372            }
373            iter_idx += 1;
374        };
375        let section = &sections[section_idx];
376        let next_address =
377            next_symbol.map(|s| s.address).unwrap_or_else(|| section.address + section.size);
378        let new_size = if symbol.kind == SymbolKind::Section && section.kind == SectionKind::Data {
379            // Data sections already have always-visible section symbols created by objdiff to allow
380            // diffing them, so no need to unhide these.
381            0
382        } else if section.kind == SectionKind::Code {
383            arch.infer_function_size(symbol, section, next_address)?
384        } else {
385            next_address.saturating_sub(symbol.address)
386        };
387        if new_size > 0 {
388            let symbol = &mut symbols[symbol_idx];
389            symbol.size = new_size;
390            if symbol.kind != SymbolKind::Section {
391                symbol.flags |= SymbolFlag::SizeInferred;
392            }
393            // Set symbol kind if unknown and size is non-zero
394            if symbol.kind == SymbolKind::Unknown {
395                symbol.kind = match section.kind {
396                    SectionKind::Code => SymbolKind::Function,
397                    SectionKind::Data | SectionKind::Bss => SymbolKind::Object,
398                    _ => SymbolKind::Unknown,
399                };
400            }
401        }
402    }
403    Ok(())
404}
405
406fn map_sections(
407    _arch: &dyn Arch,
408    obj_file: &object::File,
409    split_meta: Option<&SplitMeta>,
410) -> Result<(Vec<Section>, Vec<usize>)> {
411    let mut section_names = BTreeMap::<String, usize>::new();
412    let mut max_index = 0;
413    let section_count =
414        obj_file.sections().inspect(|s| max_index = max_index.max(s.index().0)).count();
415    let mut result = Vec::<Section>::with_capacity(section_count);
416    let mut section_indices = vec![usize::MAX; max_index + 1];
417    for section in obj_file.sections() {
418        let name = section.name().context("Failed to process section name")?;
419        let kind = map_section_kind(&section);
420        let data = if kind == SectionKind::Unknown {
421            // Don't need to read data for unknown sections
422            Vec::new()
423        } else {
424            section.uncompressed_data().context("Failed to read section data")?.into_owned()
425        };
426
427        // Find the virtual address for the section symbol if available
428        let section_symbol = obj_file.symbols().find(|s| {
429            s.kind() == object::SymbolKind::Section && s.section_index() == Some(section.index())
430        });
431        let virtual_address = section_symbol.and_then(|s| {
432            split_meta
433                .and_then(|m| m.virtual_addresses.as_ref())
434                .and_then(|v| v.get(s.index().0).cloned())
435        });
436
437        let unique_id = section_names.entry(name.to_string()).or_insert(0);
438        let id = format!("{name}-{unique_id}");
439        *unique_id += 1;
440
441        section_indices[section.index().0] = result.len();
442        result.push(Section {
443            id,
444            name: name.to_string(),
445            address: section.address(),
446            size: section.size(),
447            kind,
448            data: SectionData(data),
449            flags: Default::default(),
450            align: NonZeroU64::new(section.align()),
451            relocations: Default::default(),
452            virtual_address,
453            line_info: Default::default(),
454        });
455    }
456    Ok((result, section_indices))
457}
458
459const LOW_PRIORITY_SYMBOLS: &[&str] =
460    &["__gnu_compiled_c", "__gnu_compiled_cplusplus", "gcc2_compiled."];
461
462fn best_symbol<'r, 'data, 'file>(
463    symbols: &'r [object::Symbol<'data, 'file>],
464    address: u64,
465) -> Option<(object::SymbolIndex, u64)> {
466    let mut closest_symbol_index = match symbols.binary_search_by_key(&address, |s| s.address()) {
467        Ok(index) => Some(index),
468        Err(index) => index.checked_sub(1),
469    }?;
470    // The binary search may not find the first symbol at the address, so work backwards
471    let target_address = symbols[closest_symbol_index].address();
472    while let Some(prev_index) = closest_symbol_index.checked_sub(1) {
473        if symbols[prev_index].address() != target_address {
474            break;
475        }
476        closest_symbol_index = prev_index;
477    }
478    let mut best_symbol: Option<&'r object::Symbol<'data, 'file>> = None;
479    for symbol in symbols.iter().skip(closest_symbol_index) {
480        if symbol.address() > address {
481            break;
482        }
483        if symbol.kind() == object::SymbolKind::Section
484            || (symbol.size() > 0 && (symbol.address() + symbol.size()) <= address)
485        {
486            continue;
487        }
488        // TODO priority ranking with visibility, etc
489        if let Some(best) = best_symbol {
490            if LOW_PRIORITY_SYMBOLS.contains(&best.name().unwrap_or_default())
491                && !LOW_PRIORITY_SYMBOLS.contains(&symbol.name().unwrap_or_default())
492            {
493                best_symbol = Some(symbol);
494            }
495        } else {
496            best_symbol = Some(symbol);
497        }
498    }
499    best_symbol.map(|s| (s.index(), s.address()))
500}
501
502fn map_section_relocations(
503    arch: &dyn Arch,
504    obj_file: &object::File,
505    obj_section: &object::Section,
506    symbol_indices: &[usize],
507    ordered_symbols: &[Vec<object::Symbol>],
508) -> Result<Vec<Relocation>> {
509    let mut relocations = Vec::<Relocation>::with_capacity(obj_section.relocations().count());
510    for (address, reloc) in obj_section.relocations() {
511        let mut target_reloc = RelocationOverride {
512            target: match reloc.target() {
513                object::RelocationTarget::Symbol(symbol) => {
514                    RelocationOverrideTarget::Symbol(symbol)
515                }
516                object::RelocationTarget::Section(section) => {
517                    RelocationOverrideTarget::Section(section)
518                }
519                _ => RelocationOverrideTarget::Skip,
520            },
521            addend: reloc.addend(),
522        };
523
524        // Allow the architecture to override the relocation target and addend
525        match arch.relocation_override(obj_file, obj_section, address, &reloc)? {
526            Some(reloc_override) => {
527                match reloc_override.target {
528                    RelocationOverrideTarget::Keep => {}
529                    target => {
530                        target_reloc.target = target;
531                    }
532                }
533                target_reloc.addend = reloc_override.addend;
534            }
535            None => {
536                ensure!(
537                    !reloc.has_implicit_addend(),
538                    "Unsupported {:?} implicit relocation {:?}",
539                    obj_file.architecture(),
540                    reloc.flags()
541                );
542            }
543        }
544
545        // Resolve the relocation target symbol
546        let (symbol_index, addend) = match target_reloc.target {
547            RelocationOverrideTarget::Keep => unreachable!(),
548            RelocationOverrideTarget::Skip => continue,
549            RelocationOverrideTarget::Symbol(symbol_index) => {
550                // Sometimes used to indicate "absolute"
551                if symbol_index.0 == u32::MAX as usize {
552                    continue;
553                }
554
555                // If the target is a section symbol, try to resolve a better symbol as the target
556                if let Some(section_symbol) = obj_file
557                    .symbol_by_index(symbol_index)
558                    .ok()
559                    .filter(|s| s.kind() == object::SymbolKind::Section)
560                {
561                    let section_index =
562                        section_symbol.section_index().context("Section symbol without section")?;
563                    let target_address =
564                        section_symbol.address().wrapping_add_signed(target_reloc.addend);
565                    if let Some((new_idx, addr)) = ordered_symbols
566                        .get(section_index.0)
567                        .and_then(|symbols| best_symbol(symbols, target_address))
568                    {
569                        (new_idx, target_address.wrapping_sub(addr) as i64)
570                    } else {
571                        (symbol_index, target_reloc.addend)
572                    }
573                } else {
574                    (symbol_index, target_reloc.addend)
575                }
576            }
577            RelocationOverrideTarget::Section(section_index) => {
578                let section = match obj_file.section_by_index(section_index) {
579                    Ok(section) => section,
580                    Err(e) => {
581                        log::warn!("Invalid relocation section: {e}");
582                        continue;
583                    }
584                };
585                let Ok(target_address) = u64::try_from(target_reloc.addend) else {
586                    log::warn!(
587                        "Negative section relocation addend: {}{}",
588                        section.name()?,
589                        target_reloc.addend
590                    );
591                    continue;
592                };
593                let Some(symbols) = ordered_symbols.get(section_index.0) else {
594                    log::warn!(
595                        "Couldn't resolve relocation target symbol for section {} (no symbols)",
596                        section.name()?
597                    );
598                    continue;
599                };
600                // Attempt to resolve a target symbol for the relocation
601                if let Some((new_idx, addr)) = best_symbol(symbols, target_address) {
602                    (new_idx, target_address.wrapping_sub(addr) as i64)
603                } else if let Some(section_symbol) =
604                    symbols.iter().find(|s| s.kind() == object::SymbolKind::Section)
605                {
606                    (
607                        section_symbol.index(),
608                        target_address.wrapping_sub(section_symbol.address()) as i64,
609                    )
610                } else {
611                    log::warn!(
612                        "Couldn't resolve relocation target symbol for section {}",
613                        section.name()?
614                    );
615                    continue;
616                }
617            }
618        };
619
620        let flags = match reloc.flags() {
621            object::RelocationFlags::Elf { r_type } => RelocationFlags::Elf(r_type),
622            object::RelocationFlags::Coff { typ } => RelocationFlags::Coff(typ),
623            flags => bail!("Unhandled relocation flags: {:?}", flags),
624        };
625        let target_symbol = match symbol_indices.get(symbol_index.0).copied() {
626            Some(i) => i,
627            None => {
628                log::warn!("Invalid symbol index {}", symbol_index.0);
629                continue;
630            }
631        };
632        relocations.push(Relocation { address, flags, target_symbol, addend });
633    }
634    relocations.sort_by_key(|r| r.address);
635    Ok(relocations)
636}
637
638fn map_relocations(
639    arch: &dyn Arch,
640    obj_file: &object::File,
641    sections: &mut [Section],
642    section_indices: &[usize],
643    symbol_indices: &[usize],
644) -> Result<()> {
645    // Generate a list of symbols for each section
646    let mut ordered_symbols =
647        Vec::<Vec<object::Symbol>>::with_capacity(obj_file.sections().count() + 1);
648    for symbol in obj_file.symbols() {
649        let Some(section_index) = symbol.section_index() else {
650            continue;
651        };
652        if symbol.kind() == object::SymbolKind::Section {
653            continue;
654        }
655        if section_index.0 >= ordered_symbols.len() {
656            ordered_symbols.resize_with(section_index.0 + 1, Vec::new);
657        }
658        ordered_symbols[section_index.0].push(symbol);
659    }
660    // Sort symbols by address and size
661    for vec in &mut ordered_symbols {
662        vec.sort_by(|a, b| a.address().cmp(&b.address()).then(a.size().cmp(&b.size())));
663    }
664    // Map relocations for each section. Section-relative relocations use the ordered symbols list
665    // to find a better target symbol, if available.
666    for obj_section in obj_file.sections() {
667        let section = &mut sections[section_indices[obj_section.index().0]];
668        if section.kind != SectionKind::Unknown {
669            section.relocations = map_section_relocations(
670                arch,
671                obj_file,
672                &obj_section,
673                symbol_indices,
674                &ordered_symbols,
675            )?;
676        }
677    }
678    Ok(())
679}
680
681fn perform_data_flow_analysis(obj: &mut Object, config: &DiffObjConfig) -> Result<()> {
682    // If neither of these settings are on, no flow analysis to perform
683    if !config.analyze_data_flow && !config.ppc_calculate_pool_relocations {
684        return Ok(());
685    }
686
687    let mut generated_relocations = Vec::<(usize, Vec<Relocation>)>::new();
688    let mut generated_flow_results = Vec::<(Symbol, Box<dyn FlowAnalysisResult>)>::new();
689    for (section_index, section) in obj.sections.iter().enumerate() {
690        if section.kind != SectionKind::Code {
691            continue;
692        }
693        for symbol in obj.symbols.iter() {
694            if symbol.section != Some(section_index) {
695                continue;
696            }
697            if symbol.kind != SymbolKind::Function {
698                continue;
699            }
700            let code =
701                section.data_range(symbol.address, symbol.size as usize).ok_or_else(|| {
702                    anyhow!(
703                        "Symbol data out of bounds: {:#x}..{:#x}",
704                        symbol.address,
705                        symbol.address + symbol.size
706                    )
707                })?;
708
709            // Optional pooled relocation computation
710            // Long view: This could be replaced by the full data flow analysis
711            // once that feature has stabilized.
712            if config.ppc_calculate_pool_relocations {
713                let relocations = obj.arch.generate_pooled_relocations(
714                    symbol.address,
715                    code,
716                    &section.relocations,
717                    &obj.symbols,
718                );
719                generated_relocations.push((section_index, relocations));
720            }
721
722            // Optional full data flow analysis
723            if config.analyze_data_flow
724                && let Some(flow_result) =
725                    obj.arch.data_flow_analysis(obj, symbol, code, &section.relocations)
726            {
727                generated_flow_results.push((symbol.clone(), flow_result));
728            }
729        }
730    }
731    for (symbol, flow_result) in generated_flow_results {
732        obj.add_flow_analysis_result(&symbol, flow_result);
733    }
734    for (section_index, mut relocations) in generated_relocations {
735        obj.sections[section_index].relocations.append(&mut relocations);
736    }
737    for section in obj.sections.iter_mut() {
738        section.relocations.sort_by_key(|r| r.address);
739    }
740    Ok(())
741}
742
743fn parse_line_info(
744    obj_file: &object::File,
745    sections: &mut [Section],
746    section_indices: &[usize],
747    obj_data: &[u8],
748) -> Result<()> {
749    // DWARF 1.1
750    if let Err(e) = parse_line_info_dwarf1(obj_file, sections) {
751        log::warn!("Failed to parse DWARF 1.1 line info: {e}");
752    }
753
754    // DWARF 2+
755    #[cfg(feature = "dwarf")]
756    if let Err(e) = super::dwarf2::parse_line_info_dwarf2(obj_file, sections) {
757        log::warn!("Failed to parse DWARF 2+ line info: {e}");
758    }
759
760    // COFF
761    if let object::File::Coff(coff) = obj_file
762        && let Err(e) = parse_line_info_coff(coff, sections, section_indices, obj_data)
763    {
764        log::warn!("Failed to parse COFF line info: {e}");
765    }
766
767    if let Err(e) = super::mdebug::parse_line_info_mdebug(obj_file, sections) {
768        log::warn!("Failed to parse MIPS mdebug line info: {e}");
769    }
770
771    Ok(())
772}
773
774/// Parse .line section from DWARF 1.1 format.
775fn parse_line_info_dwarf1(obj_file: &object::File, sections: &mut [Section]) -> Result<()> {
776    let mut text_sections = sections.iter_mut().filter(|s| s.kind == SectionKind::Code);
777    for section in obj_file.sections().filter(|s| s.name().is_ok_and(|n| n == ".line")) {
778        let data = section.uncompressed_data()?;
779        let mut reader: &[u8] = data.as_ref();
780
781        while !reader.is_empty() {
782            let mut section_data = reader;
783            let size = read_u32(obj_file, &mut section_data)? as usize;
784            if size > reader.len() {
785                bail!("Line info size {size} exceeds remaining size {}", reader.len());
786            }
787            (section_data, reader) = reader.split_at(size);
788
789            section_data = &section_data[4..]; // Skip the size field
790            let base_address = read_u32(obj_file, &mut section_data)? as u64;
791            let out_section = text_sections.next().context("No text section for line info")?;
792            while !section_data.is_empty() {
793                let line_number = read_u32(obj_file, &mut section_data)?;
794                let statement_pos = read_u16(obj_file, &mut section_data)?;
795                if statement_pos != 0xFFFF {
796                    log::warn!("Unhandled statement pos {statement_pos}");
797                }
798                let address_delta = read_u32(obj_file, &mut section_data)? as u64;
799                out_section.line_info.insert(base_address + address_delta, line_number);
800            }
801        }
802    }
803    Ok(())
804}
805
806fn parse_line_info_coff(
807    coff: &object::coff::CoffFile,
808    sections: &mut [Section],
809    section_indices: &[usize],
810    obj_data: &[u8],
811) -> Result<()> {
812    use object::{
813        coff::{CoffHeader as _, ImageSymbol as _},
814        endian::LittleEndian as LE,
815    };
816    let symbol_table = coff.coff_header().symbols(obj_data)?;
817
818    // Enumerate over all sections.
819    for sect in coff.sections() {
820        let ptr_linenums = sect.coff_section().pointer_to_linenumbers.get(LE) as usize;
821        let num_linenums = sect.coff_section().number_of_linenumbers.get(LE) as usize;
822
823        // If we have no line number, skip this section.
824        if num_linenums == 0 {
825            continue;
826        }
827
828        // Find this section in our out_section. If it's not in out_section,
829        // skip it.
830        let Some(out_section) =
831            section_indices.get(sect.index().0).and_then(|&i| sections.get_mut(i))
832        else {
833            continue;
834        };
835
836        // Turn the line numbers into an ImageLinenumber slice.
837        let Some(linenums) = &obj_data.get(
838            ptr_linenums..ptr_linenums + num_linenums * size_of::<object::pe::ImageLinenumber>(),
839        ) else {
840            continue;
841        };
842        let Ok(linenums) =
843            object::pod::slice_from_all_bytes::<object::pe::ImageLinenumber>(linenums)
844        else {
845            continue;
846        };
847
848        // In COFF, the line numbers are stored relative to the start of the
849        // function. Because of this, we need to know the line number where the
850        // function starts, so we can sum the two and get the line number
851        // relative to the start of the file.
852        //
853        // This variable stores the line number where the function currently
854        // being processed starts. It is set to None when we failed to find the
855        // line number of the start of the function.
856        let mut cur_fun_start_linenumber = None;
857        for linenum in linenums {
858            let line_number = linenum.linenumber.get(LE);
859            if line_number == 0 {
860                // Starting a new function. We need to find the line where that
861                // function is located in the file. To do this, we need to find
862                // the `.bf` symbol "associated" with this function. The .bf
863                // symbol will have a Function Begin/End Auxillary Record, which
864                // contains the line number of the start of the function.
865
866                // First, set cur_fun_start_linenumber to None. If we fail to
867                // find the start of the function, this will make sure the
868                // subsequent line numbers will be ignored until the next start
869                // of function.
870                cur_fun_start_linenumber = None;
871
872                // Get the symbol associated with this function. We'll need it
873                // for logging purposes, but also to acquire its Function
874                // Auxillary Record, which tells us where to find our .bf symbol.
875                let symtable_entry = linenum.symbol_table_index_or_virtual_address.get(LE);
876                let Ok(symbol) = symbol_table.symbol(object::SymbolIndex(symtable_entry as usize))
877                else {
878                    continue;
879                };
880                let Ok(aux_fun) =
881                    symbol_table.aux_function(object::SymbolIndex(symtable_entry as usize))
882                else {
883                    continue;
884                };
885
886                // Get the .bf symbol associated with this symbol. To do so, we
887                // look at the Function Auxillary Record's tag_index, which is
888                // an index in the symbol table pointing to our .bf symbol.
889                if aux_fun.tag_index.get(LE) == 0 {
890                    continue;
891                }
892                let Ok(bf_symbol) =
893                    symbol_table.symbol(object::SymbolIndex(aux_fun.tag_index.get(LE) as usize))
894                else {
895                    continue;
896                };
897                // Do some sanity checks that we are, indeed, looking at a .bf
898                // symbol.
899                if bf_symbol.name(symbol_table.strings()) != Ok(b".bf") {
900                    continue;
901                }
902                // Get the Function Begin/End Auxillary Record associated with
903                // our .bf symbol, where we'll fine the linenumber of the start
904                // of our function.
905                let Ok(bf_aux) = symbol_table.get::<object::pe::ImageAuxSymbolFunctionBeginEnd>(
906                    object::SymbolIndex(aux_fun.tag_index.get(LE) as usize),
907                    1,
908                ) else {
909                    continue;
910                };
911                // Set cur_fun_start_linenumber so the following linenumber
912                // records will know at what line the current function start.
913                cur_fun_start_linenumber = Some(bf_aux.linenumber.get(LE) as u32);
914                // Let's also synthesize a line number record from the start of
915                // the function, as the linenumber records don't always cover it.
916                out_section.line_info.insert(
917                    sect.address() + symbol.value() as u64,
918                    bf_aux.linenumber.get(LE) as u32,
919                );
920            } else if let Some(cur_linenumber) = cur_fun_start_linenumber {
921                let vaddr = linenum.symbol_table_index_or_virtual_address.get(LE);
922                out_section
923                    .line_info
924                    .insert(sect.address() + vaddr as u64, cur_linenumber + line_number as u32);
925            }
926        }
927    }
928    Ok(())
929}
930
931pub fn get_section_base_name(section: &Section) -> &str {
932    // Match MSVC x86 .rdata$r against .rdata$rs
933    // Match GCC i._ZN14class_00acb578C1Ev against i [combined]
934    section
935        .name
936        .get(1..)
937        .and_then(|s| s.rfind(['$', '.']))
938        .and_then(|i| section.name.get(..i + 1))
939        .unwrap_or(&section.name)
940}
941
942fn combine_sections(
943    sections: &mut [Section],
944    symbols: &mut [Symbol],
945    config: &DiffObjConfig,
946) -> Result<()> {
947    let mut data_sections = BTreeMap::<String, Vec<usize>>::new();
948    let mut text_sections = BTreeMap::<String, Vec<usize>>::new();
949    for (i, section) in sections.iter().enumerate() {
950        let base_name = get_section_base_name(section);
951        match section.kind {
952            SectionKind::Data | SectionKind::Bss => {
953                data_sections.entry(base_name.to_string()).or_default().push(i);
954            }
955            SectionKind::Code => {
956                text_sections.entry(base_name.to_string()).or_default().push(i);
957            }
958            _ => {}
959        }
960    }
961    if config.combine_data_sections {
962        for (combined_name, mut section_indices) in data_sections {
963            do_combine_sections(sections, symbols, &mut section_indices, combined_name)?;
964        }
965    }
966    if config.combine_text_sections {
967        for (combined_name, mut section_indices) in text_sections {
968            do_combine_sections(sections, symbols, &mut section_indices, combined_name)?;
969        }
970    }
971    Ok(())
972}
973
974fn do_combine_sections(
975    sections: &mut [Section],
976    symbols: &mut [Symbol],
977    section_indices: &mut [usize],
978    combined_name: String,
979) -> Result<()> {
980    if section_indices.len() < 2 {
981        return Ok(());
982    }
983    // Sort sections lexicographically by name (for COFF section groups)
984    section_indices.sort_by(|&a, &b| {
985        let a_name = &sections[a].name;
986        let b_name = &sections[b].name;
987        // .text$di < .text$mn < .text
988        if a_name.contains('$') && !b_name.contains('$') {
989            return Ordering::Greater;
990        } else if !a_name.contains('$') && b_name.contains('$') {
991            return Ordering::Less;
992        }
993        a_name.cmp(b_name)
994    });
995    let first_section_idx = section_indices[0];
996
997    // Calculate the new offset for each section
998    let mut offsets = Vec::<u64>::with_capacity(section_indices.len());
999    let mut current_offset = 0;
1000    let mut data_size = 0;
1001    let mut num_relocations = 0;
1002    for i in section_indices.iter().copied() {
1003        let section = &sections[i];
1004        if section.address != 0 {
1005            bail!("Section {} ({}) has non-zero address", i, section.name);
1006        }
1007        offsets.push(current_offset);
1008        current_offset += section.size;
1009        let align = section.combined_alignment();
1010        current_offset = align_u64_to(current_offset, align);
1011        data_size += section.data.len();
1012        data_size = align_u64_to(data_size as u64, align) as usize;
1013        num_relocations += section.relocations.len();
1014    }
1015    if data_size > 0 {
1016        ensure!(data_size == current_offset as usize, "Data size mismatch");
1017    }
1018
1019    // Combine section data
1020    let mut data = Vec::<u8>::with_capacity(data_size);
1021    let mut relocations = Vec::<Relocation>::with_capacity(num_relocations);
1022    let mut line_info = BTreeMap::<u64, u32>::new();
1023    for (&i, &offset) in section_indices.iter().zip(&offsets) {
1024        let section = &mut sections[i];
1025        section.size = 0;
1026        data.append(&mut section.data.0);
1027        align_data_slice_to(&mut data, section.combined_alignment());
1028        section.relocations.iter_mut().for_each(|r| r.address += offset);
1029        relocations.append(&mut section.relocations);
1030        line_info.append(&mut section.line_info.iter().map(|(&a, &l)| (a + offset, l)).collect());
1031        section.line_info.clear();
1032        if offset > 0 {
1033            section.kind = SectionKind::Unknown;
1034        }
1035    }
1036    {
1037        let first_section = &mut sections[first_section_idx];
1038        first_section.id = format!("{combined_name}-combined");
1039        first_section.name = combined_name;
1040        first_section.size = current_offset;
1041        first_section.data = SectionData(data);
1042        first_section.flags |= SectionFlag::Combined;
1043        first_section.relocations = relocations;
1044        first_section.line_info = line_info;
1045    }
1046
1047    // Find all section symbols for the merged sections
1048    let mut section_symbols = symbols
1049        .iter()
1050        .enumerate()
1051        .filter(|&(_, s)| {
1052            s.kind == SymbolKind::Section && s.section.is_some_and(|i| section_indices.contains(&i))
1053        })
1054        .map(|(i, _)| i)
1055        .collect::<Vec<_>>();
1056    section_symbols.sort_by_key(|&i| symbols[i].section.unwrap());
1057    let target_section_symbol = section_symbols.first().copied();
1058
1059    // Adjust symbol addresses and section indices
1060    for symbol in symbols.iter_mut() {
1061        let Some(section_index) = symbol.section else {
1062            continue;
1063        };
1064        let Some(merge_index) = section_indices.iter().position(|&i| i == section_index) else {
1065            continue;
1066        };
1067        symbol.address += offsets[merge_index];
1068        symbol.section = Some(first_section_idx);
1069    }
1070
1071    // Adjust relocations to section symbols
1072    for relocation in sections.iter_mut().flat_map(|s| s.relocations.iter_mut()) {
1073        let target_symbol = &symbols[relocation.target_symbol];
1074        if target_symbol.kind != SymbolKind::Section {
1075            continue;
1076        }
1077        if !target_symbol.section.is_some_and(|i| section_indices.contains(&i)) {
1078            continue;
1079        }
1080        // The section symbol's address will have the offset applied
1081        relocation.target_symbol = target_section_symbol.context("No target section symbol")?;
1082        relocation.addend = relocation
1083            .addend
1084            .checked_add_unsigned(target_symbol.address)
1085            .context("Relocation addend overflow")?;
1086    }
1087
1088    // Reset section symbols
1089    for (i, &symbol_index) in section_symbols.iter().enumerate() {
1090        let symbol = &mut symbols[symbol_index];
1091        symbol.address = 0;
1092        if i > 0 {
1093            // Remove the section symbol
1094            symbol.kind = SymbolKind::Unknown;
1095            symbol.section = None;
1096        }
1097    }
1098
1099    Ok(())
1100}
1101
1102#[cfg(feature = "std")]
1103pub fn read(
1104    obj_path: &std::path::Path,
1105    config: &DiffObjConfig,
1106    diff_side: DiffSide,
1107) -> Result<Object> {
1108    let (data, timestamp) = {
1109        let file = std::fs::File::open(obj_path)?;
1110        let timestamp = filetime::FileTime::from_last_modification_time(&file.metadata()?);
1111        (unsafe { memmap2::Mmap::map(&file) }?, timestamp)
1112    };
1113    let mut obj = parse(&data, config, diff_side)?;
1114    obj.path = Some(obj_path.to_path_buf());
1115    obj.timestamp = Some(timestamp);
1116    Ok(obj)
1117}
1118
1119pub fn parse(data: &[u8], config: &DiffObjConfig, diff_side: DiffSide) -> Result<Object> {
1120    let obj_file = object::File::parse(data)?;
1121    let mut arch = new_arch(&obj_file, diff_side)?;
1122    let split_meta = parse_split_meta(&obj_file)?;
1123    let comment_syms = parse_mw_comment_syms(&obj_file)?;
1124    let (mut sections, section_indices) =
1125        map_sections(arch.as_ref(), &obj_file, split_meta.as_ref())?;
1126    let (mut symbols, symbol_indices) = map_symbols(
1127        arch.as_ref(),
1128        &obj_file,
1129        &section_indices,
1130        split_meta.as_ref(),
1131        comment_syms.as_ref(),
1132        config,
1133    )?;
1134    map_relocations(arch.as_ref(), &obj_file, &mut sections, &section_indices, &symbol_indices)?;
1135    // Infer symbol sizes for 0-size symbols (must be done after map_relocations is called)
1136    infer_symbol_sizes(arch.as_ref(), &mut symbols, &sections)?;
1137    parse_line_info(&obj_file, &mut sections, &section_indices, data)?;
1138    if config.combine_data_sections || config.combine_text_sections {
1139        combine_sections(&mut sections, &mut symbols, config)?;
1140    }
1141    add_section_symbols(&sections, &mut symbols);
1142    arch.post_init(&sections, &symbols, &symbol_indices);
1143    let mut obj = Object {
1144        arch,
1145        endianness: obj_file.endianness(),
1146        symbols,
1147        sections,
1148        split_meta,
1149        #[cfg(feature = "std")]
1150        path: None,
1151        #[cfg(feature = "std")]
1152        timestamp: None,
1153        flow_analysis_results: Default::default(),
1154    };
1155
1156    // Need to construct the obj first so that we have a convinient package to
1157    // pass to flow analysis. Then the flow analysis will mutate obj adding
1158    // additional data to it.
1159    perform_data_flow_analysis(&mut obj, config)?;
1160    Ok(obj)
1161}
1162
1163#[cfg(feature = "std")]
1164pub fn has_function(obj_path: &std::path::Path, symbol_name: &str) -> Result<bool> {
1165    let data = {
1166        let file = std::fs::File::open(obj_path)?;
1167        unsafe { memmap2::Mmap::map(&file) }?
1168    };
1169    Ok(object::File::parse(&*data)?
1170        .symbol_by_name(symbol_name)
1171        .filter(|o| o.kind() == object::SymbolKind::Text)
1172        .is_some())
1173}
1174
1175fn parse_split_meta(obj_file: &object::File) -> Result<Option<SplitMeta>> {
1176    Ok(if let Some(section) = obj_file.section_by_name(SPLITMETA_SECTION) {
1177        Some(SplitMeta::from_section(section, obj_file.endianness(), obj_file.is_64())?)
1178    } else {
1179        None
1180    })
1181}
1182
1183fn parse_mw_comment_syms(obj_file: &object::File) -> Result<Option<Vec<CommentSym>>> {
1184    Ok(if let Some(section) = obj_file.section_by_name(COMMENT_SECTION) {
1185        let data = section.uncompressed_data()?;
1186        let mut reader: &[u8] = data.as_ref();
1187        if let Ok(_header) = MWComment::from_reader(obj_file, &mut reader) {
1188            CommentSym::from_reader(obj_file, &mut reader)?; // Null symbol
1189            let mut comment_syms = Vec::with_capacity(obj_file.symbols().count());
1190            for _symbol in obj_file.symbols() {
1191                let comment_sym = CommentSym::from_reader(obj_file, &mut reader)?;
1192                comment_syms.push(comment_sym);
1193            }
1194            Some(comment_syms)
1195        } else {
1196            // .comment section exists but the header failed to parse, likely an unsupported compiler version (e.g. mwccarm)
1197            None
1198        }
1199    } else {
1200        None
1201    })
1202}
1203
1204#[cfg(test)]
1205mod test {
1206    use super::*;
1207
1208    #[test]
1209    fn test_combine_sections() {
1210        let mut sections = vec![
1211            Section {
1212                id: ".text-0".to_string(),
1213                name: ".text".to_string(),
1214                size: 8,
1215                kind: SectionKind::Code,
1216                data: SectionData(vec![0; 8]),
1217                relocations: vec![
1218                    Relocation {
1219                        address: 0,
1220                        flags: RelocationFlags::Elf(0),
1221                        target_symbol: 0,
1222                        addend: 0,
1223                    },
1224                    Relocation {
1225                        address: 2,
1226                        flags: RelocationFlags::Elf(0),
1227                        target_symbol: 1,
1228                        addend: 0,
1229                    },
1230                    Relocation {
1231                        address: 4,
1232                        flags: RelocationFlags::Elf(0),
1233                        target_symbol: 3,
1234                        addend: 2,
1235                    },
1236                ],
1237                ..Default::default()
1238            },
1239            Section {
1240                id: ".data-0".to_string(),
1241                name: ".data".to_string(),
1242                size: 4,
1243                kind: SectionKind::Data,
1244                data: SectionData(vec![1, 2, 3, 4]),
1245                relocations: vec![Relocation {
1246                    address: 0,
1247                    flags: RelocationFlags::Elf(0),
1248                    target_symbol: 2,
1249                    addend: 0,
1250                }],
1251                line_info: [(0, 1)].into_iter().collect(),
1252                ..Default::default()
1253            },
1254            Section {
1255                id: ".data-1".to_string(),
1256                name: ".data".to_string(),
1257                size: 4,
1258                kind: SectionKind::Data,
1259                data: SectionData(vec![5, 6, 7, 8]),
1260                relocations: vec![Relocation {
1261                    address: 0,
1262                    flags: RelocationFlags::Elf(0),
1263                    target_symbol: 2,
1264                    addend: 0,
1265                }],
1266                ..Default::default()
1267            },
1268            Section {
1269                id: ".data-2".to_string(),
1270                name: ".data".to_string(),
1271                size: 4,
1272                kind: SectionKind::Data,
1273                data: SectionData(vec![9, 10, 11, 12]),
1274                line_info: [(0, 2)].into_iter().collect(),
1275                ..Default::default()
1276            },
1277        ];
1278        let mut symbols = vec![
1279            Symbol {
1280                name: ".data".to_string(),
1281                address: 0,
1282                kind: SymbolKind::Section,
1283                section: Some(2),
1284                ..Default::default()
1285            },
1286            Symbol {
1287                name: "symbol".to_string(),
1288                address: 0,
1289                kind: SymbolKind::Object,
1290                size: 4,
1291                section: Some(2),
1292                ..Default::default()
1293            },
1294            Symbol {
1295                name: "function".to_string(),
1296                address: 0,
1297                size: 8,
1298                kind: SymbolKind::Function,
1299                section: Some(0),
1300                ..Default::default()
1301            },
1302            Symbol {
1303                name: ".data".to_string(),
1304                address: 0,
1305                kind: SymbolKind::Section,
1306                section: Some(3),
1307                ..Default::default()
1308            },
1309        ];
1310        do_combine_sections(&mut sections, &mut symbols, &mut [1, 2, 3], ".data".to_string())
1311            .unwrap();
1312        assert_eq!(sections[1].data.0, (1..=12).collect::<Vec<_>>());
1313        insta::assert_debug_snapshot!((sections, symbols));
1314    }
1315}