struct_audit/
loader.rs

1use crate::error::{Error, Result};
2use gimli::{Dwarf, EndianSlice, RunTimeEndian, SectionId};
3use memmap2::Mmap;
4use object::{Object, ObjectSection};
5use std::borrow::Cow;
6use std::collections::HashMap;
7use std::fs::File;
8use std::path::Path;
9
10pub struct BinaryData {
11    pub mmap: Mmap,
12}
13
14pub type DwarfSlice<'a> = EndianSlice<'a, RunTimeEndian>;
15
16pub struct LoadedDwarf<'a> {
17    pub dwarf: Dwarf<DwarfSlice<'a>>,
18    pub address_size: u8,
19    #[allow(dead_code)]
20    decompressed_sections: HashMap<&'static str, Vec<u8>>,
21}
22
23impl BinaryData {
24    pub fn load(path: &Path) -> Result<Self> {
25        let file = File::open(path)?;
26        let mmap = unsafe { Mmap::map(&file)? };
27        Ok(Self { mmap })
28    }
29
30    pub fn load_dwarf(&self) -> Result<LoadedDwarf<'_>> {
31        let object = object::File::parse(&*self.mmap)?;
32
33        if !matches!(
34            object.format(),
35            object::BinaryFormat::Elf | object::BinaryFormat::MachO | object::BinaryFormat::Pe
36        ) {
37            return Err(Error::UnsupportedFormat);
38        }
39
40        let endian =
41            if object.is_little_endian() { RunTimeEndian::Little } else { RunTimeEndian::Big };
42
43        let mut decompressed_sections: HashMap<&'static str, Vec<u8>> = HashMap::new();
44
45        let section_names: &[&'static str] = &[
46            ".debug_abbrev",
47            ".debug_addr",
48            ".debug_aranges",
49            ".debug_info",
50            ".debug_line",
51            ".debug_line_str",
52            ".debug_loc",
53            ".debug_loclists",
54            ".debug_ranges",
55            ".debug_rnglists",
56            ".debug_str",
57            ".debug_str_offsets",
58            ".debug_types",
59            ".zdebug_abbrev",
60            ".zdebug_addr",
61            ".zdebug_aranges",
62            ".zdebug_info",
63            ".zdebug_line",
64            ".zdebug_line_str",
65            ".zdebug_loc",
66            ".zdebug_loclists",
67            ".zdebug_ranges",
68            ".zdebug_rnglists",
69            ".zdebug_str",
70            ".zdebug_str_offsets",
71            ".zdebug_types",
72        ];
73
74        for &name in section_names {
75            if let Some(section) = object.section_by_name(name) {
76                if let Ok(Cow::Owned(vec)) = section.uncompressed_data() {
77                    decompressed_sections.insert(name, vec);
78                }
79            }
80        }
81
82        // SAFETY: We need raw pointer here because:
83        // 1. load_section closure returns slices pointing into decompressed_sections
84        // 2. These slices are stored in Dwarf by Dwarf::load
85        // 3. We then move decompressed_sections into LoadedDwarf
86        // 4. Vec heap data doesn't move when HashMap is moved, so slices remain valid
87        // 5. LoadedDwarf keeps decompressed_sections alive for dwarf's lifetime
88        let decompressed_ptr = &decompressed_sections as *const HashMap<&'static str, Vec<u8>>;
89
90        let load_section = |id: SectionId| -> std::result::Result<DwarfSlice<'_>, gimli::Error> {
91            let section_name = id.name();
92            let zdebug_name = section_name.replace(".debug_", ".zdebug_");
93
94            let try_load = |name: &str| -> Option<&[u8]> {
95                let decompressed = unsafe { &*decompressed_ptr };
96                if let Some(vec) = decompressed.get(name) {
97                    return Some(vec.as_slice());
98                }
99
100                object.section_by_name(name).and_then(|s| s.uncompressed_data().ok()).and_then(
101                    |data| match data {
102                        Cow::Borrowed(b) => Some(b),
103                        Cow::Owned(_) => None,
104                    },
105                )
106            };
107
108            let slice = try_load(section_name).or_else(|| try_load(&zdebug_name)).unwrap_or(&[]);
109
110            Ok(EndianSlice::new(slice, endian))
111        };
112
113        let dwarf = Dwarf::load(load_section).map_err(|e| Error::Dwarf(e.to_string()))?;
114
115        let mut units = dwarf.units();
116        if units.next().map_err(|e| Error::Dwarf(e.to_string()))?.is_none() {
117            return Err(Error::NoDebugInfo);
118        }
119
120        Ok(LoadedDwarf {
121            dwarf,
122            address_size: if object.is_64() { 8 } else { 4 },
123            decompressed_sections,
124        })
125    }
126}