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