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