Skip to main content

objdiff_core/obj/
mod.rs

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