Skip to main content

objdiff_core/obj/
mod.rs

1pub mod comment;
2#[cfg(feature = "dwarf")]
3mod dwarf2;
4mod mdebug;
5pub mod read;
6pub mod split_meta;
7
8use alloc::{
9    borrow::Cow,
10    boxed::Box,
11    collections::BTreeMap,
12    string::{String, ToString},
13    vec,
14    vec::Vec,
15};
16use core::{
17    fmt,
18    num::{NonZeroU32, NonZeroU64},
19};
20
21use flagset::{FlagSet, flags};
22
23use crate::{
24    arch::{Arch, ArchDummy},
25    obj::split_meta::SplitMeta,
26    util::ReallySigned,
27};
28
29#[derive(Debug, Eq, PartialEq, Copy, Clone, Default)]
30pub enum SectionKind {
31    #[default]
32    Unknown = -1,
33    Code,
34    Data,
35    Bss,
36    Common,
37}
38
39flags! {
40    #[derive(Hash)]
41    pub enum SymbolFlag: u16 {
42        Global,
43        Local,
44        Weak,
45        Common,
46        Hidden,
47        /// Has extra data associated with the symbol
48        /// (e.g. exception table entry)
49        HasExtra,
50        /// Symbol size was missing and was inferred
51        SizeInferred,
52        /// Symbol should be ignored by any diffing
53        Ignored,
54        /// Symbol name is compiler-generated; compare by value instead of name
55        CompilerGenerated,
56    }
57}
58
59pub type SymbolFlagSet = FlagSet<SymbolFlag>;
60
61flags! {
62    #[derive(Hash)]
63    pub enum SectionFlag: u8 {
64        /// Section combined from multiple input sections
65        Combined,
66    }
67}
68
69pub type SectionFlagSet = FlagSet<SectionFlag>;
70
71#[derive(Debug, Clone, Default)]
72pub struct Section {
73    /// Unique section ID
74    pub id: String,
75    pub name: String,
76    pub address: u64,
77    pub size: u64,
78    pub kind: SectionKind,
79    pub data: SectionData,
80    pub flags: SectionFlagSet,
81    pub align: Option<NonZeroU64>,
82    pub relocations: Vec<Relocation>,
83    /// Line number info (.line or .debug_line section)
84    pub line_info: BTreeMap<u64, u32>,
85    /// Original virtual address (from .note.split section)
86    pub virtual_address: Option<u64>,
87}
88
89#[derive(Clone, Default)]
90#[repr(transparent)]
91pub struct SectionData(pub Vec<u8>);
92
93impl core::ops::Deref for SectionData {
94    type Target = Vec<u8>;
95
96    fn deref(&self) -> &Self::Target { &self.0 }
97}
98
99impl fmt::Debug for SectionData {
100    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
101        f.debug_tuple("SectionData").field(&self.0.len()).finish()
102    }
103}
104
105impl Section {
106    pub fn data_range(&self, address: u64, size: usize) -> Option<&[u8]> {
107        let offset = address.checked_sub(self.address)?;
108        self.data.get(offset as usize..offset as usize + size)
109    }
110
111    // The alignment to use when "Combine data/text sections" is enabled.
112    pub fn combined_alignment(&self) -> u64 {
113        const MIN_ALIGNMENT: u64 = 4;
114        self.align.map_or(MIN_ALIGNMENT, |align| align.get().max(MIN_ALIGNMENT))
115    }
116
117    pub fn relocation_at(&self, address: u64, size: u8) -> Option<&Relocation> {
118        match self.relocations.binary_search_by_key(&address, |r| r.address) {
119            Ok(mut i) => {
120                // Find the first relocation at the address
121                while i
122                    .checked_sub(1)
123                    .and_then(|n| self.relocations.get(n))
124                    .is_some_and(|r| r.address == address)
125                {
126                    i -= 1;
127                }
128                self.relocations.get(i)
129            }
130            Err(i) => self.relocations.get(i).filter(|r| r.address < address + size as u64),
131        }
132    }
133
134    pub fn resolve_relocation_at<'obj>(
135        &'obj self,
136        obj: &'obj Object,
137        address: u64,
138        size: u8,
139    ) -> Option<ResolvedRelocation<'obj>> {
140        self.relocation_at(address, size).and_then(|relocation| {
141            let symbol = obj.symbols.get(relocation.target_symbol)?;
142            Some(ResolvedRelocation { relocation, symbol })
143        })
144    }
145}
146
147#[derive(Debug, Clone, Eq, PartialEq)]
148pub enum InstructionArgValue<'a> {
149    Signed(i64),
150    Unsigned(u64),
151    Opaque(Cow<'a, str>),
152}
153
154impl InstructionArgValue<'_> {
155    pub fn loose_eq(&self, other: &InstructionArgValue) -> bool {
156        match (self, other) {
157            (InstructionArgValue::Signed(a), InstructionArgValue::Signed(b)) => a == b,
158            (InstructionArgValue::Unsigned(a), InstructionArgValue::Unsigned(b)) => a == b,
159            (InstructionArgValue::Signed(a), InstructionArgValue::Unsigned(b))
160            | (InstructionArgValue::Unsigned(b), InstructionArgValue::Signed(a)) => *a as u64 == *b,
161            (InstructionArgValue::Opaque(a), InstructionArgValue::Opaque(b)) => a == b,
162            _ => false,
163        }
164    }
165
166    pub fn to_static(&self) -> InstructionArgValue<'static> {
167        match self {
168            InstructionArgValue::Signed(v) => InstructionArgValue::Signed(*v),
169            InstructionArgValue::Unsigned(v) => InstructionArgValue::Unsigned(*v),
170            InstructionArgValue::Opaque(v) => InstructionArgValue::Opaque(v.to_string().into()),
171        }
172    }
173
174    pub fn into_static(self) -> InstructionArgValue<'static> {
175        match self {
176            InstructionArgValue::Signed(v) => InstructionArgValue::Signed(v),
177            InstructionArgValue::Unsigned(v) => InstructionArgValue::Unsigned(v),
178            InstructionArgValue::Opaque(v) => {
179                InstructionArgValue::Opaque(Cow::Owned(v.into_owned()))
180            }
181        }
182    }
183}
184
185impl fmt::Display for InstructionArgValue<'_> {
186    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
187        match self {
188            InstructionArgValue::Signed(v) => write!(f, "{:#x}", ReallySigned(*v)),
189            InstructionArgValue::Unsigned(v) => write!(f, "{v:#x}"),
190            InstructionArgValue::Opaque(v) => write!(f, "{v}"),
191        }
192    }
193}
194
195#[derive(Debug, Clone, Eq, PartialEq)]
196pub enum InstructionArg<'a> {
197    Value(InstructionArgValue<'a>),
198    Reloc,
199    BranchDest(u64),
200}
201
202impl InstructionArg<'_> {
203    pub fn loose_eq(&self, other: &InstructionArg) -> bool {
204        match (self, other) {
205            (InstructionArg::Value(a), InstructionArg::Value(b)) => a.loose_eq(b),
206            (InstructionArg::Reloc, InstructionArg::Reloc) => true,
207            (InstructionArg::BranchDest(a), InstructionArg::BranchDest(b)) => a == b,
208            _ => false,
209        }
210    }
211
212    pub fn to_static(&self) -> InstructionArg<'static> {
213        match self {
214            InstructionArg::Value(v) => InstructionArg::Value(v.to_static()),
215            InstructionArg::Reloc => InstructionArg::Reloc,
216            InstructionArg::BranchDest(v) => InstructionArg::BranchDest(*v),
217        }
218    }
219
220    pub fn into_static(self) -> InstructionArg<'static> {
221        match self {
222            InstructionArg::Value(v) => InstructionArg::Value(v.into_static()),
223            InstructionArg::Reloc => InstructionArg::Reloc,
224            InstructionArg::BranchDest(v) => InstructionArg::BranchDest(v),
225        }
226    }
227}
228
229#[derive(Copy, Clone, Debug, Default)]
230pub struct InstructionRef {
231    pub address: u64,
232    pub size: u8,
233    pub opcode: u16,
234    pub branch_dest: Option<u64>,
235}
236
237#[derive(Debug, Clone)]
238pub struct ParsedInstruction {
239    pub ins_ref: InstructionRef,
240    pub mnemonic: Cow<'static, str>,
241    pub args: Vec<InstructionArg<'static>>,
242}
243
244#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Default)]
245pub enum SymbolKind {
246    #[default]
247    Unknown,
248    Function,
249    Object,
250    Section,
251}
252
253#[derive(Debug)]
254pub enum FlowAnalysisValue {
255    Text(String),
256}
257
258pub trait FlowAnalysisResult: core::fmt::Debug + Send {
259    fn get_argument_value_at_address(
260        &self,
261        address: u64,
262        argument: u8,
263    ) -> Option<&FlowAnalysisValue>;
264}
265
266#[derive(Debug, Clone, Eq, PartialEq, Hash, Default)]
267pub struct Symbol {
268    pub name: String,
269    pub demangled_name: Option<String>,
270    pub normalized_name: Option<String>,
271    pub address: u64,
272    pub size: u64,
273    pub kind: SymbolKind,
274    pub section: Option<usize>,
275    pub flags: SymbolFlagSet,
276    /// Alignment (from Metrowerks .comment section)
277    pub align: Option<NonZeroU32>,
278    /// Original virtual address (from .note.split section)
279    pub virtual_address: Option<u64>,
280}
281
282#[derive(Debug)]
283pub struct Object {
284    pub arch: Box<dyn Arch>,
285    pub endianness: object::Endianness,
286    pub symbols: Vec<Symbol>,
287    pub sections: Vec<Section>,
288    /// Split object metadata (.note.split section)
289    pub split_meta: Option<SplitMeta>,
290    #[cfg(feature = "std")]
291    pub path: Option<std::path::PathBuf>,
292    #[cfg(feature = "std")]
293    pub timestamp: Option<filetime::FileTime>,
294    pub flow_analysis_results: BTreeMap<u64, Box<dyn FlowAnalysisResult>>,
295}
296
297impl Default for Object {
298    fn default() -> Self {
299        Self {
300            arch: ArchDummy::new(),
301            endianness: object::Endianness::Little,
302            symbols: vec![],
303            sections: vec![],
304            split_meta: None,
305            #[cfg(feature = "std")]
306            path: None,
307            #[cfg(feature = "std")]
308            timestamp: None,
309            flow_analysis_results: BTreeMap::<u64, Box<dyn FlowAnalysisResult>>::new(),
310        }
311    }
312}
313
314impl Object {
315    pub fn resolve_instruction_ref(
316        &self,
317        symbol_index: usize,
318        ins_ref: InstructionRef,
319    ) -> Option<ResolvedInstructionRef<'_>> {
320        let symbol = self.symbols.get(symbol_index)?;
321        let section_index = symbol.section?;
322        let section = self.sections.get(section_index)?;
323        let offset = ins_ref.address.checked_sub(section.address)?;
324        let code = section.data.get(offset as usize..offset as usize + ins_ref.size as usize)?;
325        let relocation = section.resolve_relocation_at(self, ins_ref.address, ins_ref.size);
326        Some(ResolvedInstructionRef {
327            ins_ref,
328            symbol_index,
329            symbol,
330            section,
331            section_index,
332            code,
333            relocation,
334        })
335    }
336
337    pub fn symbol_data(&self, symbol_index: usize) -> Option<&[u8]> {
338        let symbol = self.symbols.get(symbol_index)?;
339        let section_index = symbol.section?;
340        let section = self.sections.get(section_index)?;
341        let offset = symbol.address.checked_sub(section.address)?;
342        section.data.get(offset as usize..offset as usize + symbol.size as usize)
343    }
344
345    pub fn symbol_by_name(&self, name: &str) -> Option<usize> {
346        self.symbols.iter().position(|symbol| symbol.section.is_some() && symbol.name == name)
347    }
348
349    pub fn get_flow_analysis_result(&self, symbol: &Symbol) -> Option<&dyn FlowAnalysisResult> {
350        let key = symbol.section.unwrap_or_default() as u64 * 1024 * 1024 * 1024 + symbol.address;
351        self.flow_analysis_results.get(&key).map(|result| result.as_ref())
352    }
353
354    pub fn add_flow_analysis_result(
355        &mut self,
356        symbol: &Symbol,
357        result: Box<dyn FlowAnalysisResult>,
358    ) {
359        let key = symbol.section.unwrap_or_default() as u64 * 1024 * 1024 * 1024 + symbol.address;
360        self.flow_analysis_results.insert(key, result);
361    }
362
363    pub fn has_flow_analysis_result(&self) -> bool { !self.flow_analysis_results.is_empty() }
364}
365
366#[derive(Debug, Clone, Eq, PartialEq, Hash)]
367pub struct Relocation {
368    pub flags: RelocationFlags,
369    pub address: u64,
370    pub target_symbol: usize,
371    pub addend: i64,
372}
373
374#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
375pub enum RelocationFlags {
376    Elf(u32),
377    Coff(u16),
378}
379
380#[derive(Debug, Copy, Clone)]
381pub struct ResolvedRelocation<'a> {
382    pub relocation: &'a Relocation,
383    pub symbol: &'a Symbol,
384}
385
386#[derive(Debug, Copy, Clone)]
387pub struct ResolvedSymbol<'obj> {
388    pub obj: &'obj Object,
389    pub symbol_index: usize,
390    pub symbol: &'obj Symbol,
391    pub section_index: usize,
392    pub section: &'obj Section,
393    pub data: &'obj [u8],
394}
395
396#[derive(Debug, Copy, Clone)]
397pub struct ResolvedInstructionRef<'obj> {
398    pub ins_ref: InstructionRef,
399    pub symbol_index: usize,
400    pub symbol: &'obj Symbol,
401    pub section_index: usize,
402    pub section: &'obj Section,
403    pub code: &'obj [u8],
404    pub relocation: Option<ResolvedRelocation<'obj>>,
405}
406
407static DUMMY_SYMBOL: Symbol = Symbol {
408    name: String::new(),
409    demangled_name: None,
410    normalized_name: None,
411    address: 0,
412    size: 0,
413    kind: SymbolKind::Unknown,
414    section: None,
415    flags: SymbolFlagSet::empty(),
416    align: None,
417    virtual_address: None,
418};
419
420static DUMMY_SECTION: Section = Section {
421    id: String::new(),
422    name: String::new(),
423    address: 0,
424    size: 0,
425    kind: SectionKind::Unknown,
426    data: SectionData(Vec::new()),
427    flags: SectionFlagSet::empty(),
428    align: None,
429    relocations: Vec::new(),
430    line_info: BTreeMap::new(),
431    virtual_address: None,
432};
433
434impl Default for ResolvedInstructionRef<'_> {
435    fn default() -> Self {
436        Self {
437            ins_ref: InstructionRef::default(),
438            symbol_index: 0,
439            symbol: &DUMMY_SYMBOL,
440            section_index: 0,
441            section: &DUMMY_SECTION,
442            code: &[],
443            relocation: None,
444        }
445    }
446}