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