Skip to main content

binate_core/
dwarf.rs

1use crate::binary::BinaryProvider;
2use crate::error::Result;
3use gimli::{EndianSlice, RunTimeEndian};
4use serde::Serialize;
5use std::sync::Arc;
6
7#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize)]
8pub struct SourceLocation {
9    pub file: String,
10    pub line: Option<u32>,
11    pub column: Option<u32>,
12}
13
14type Slice = EndianSlice<'static, RunTimeEndian>;
15
16/// Holds owned DWARF section bytes and a `gimli::Dwarf` view over them.
17pub struct DwarfIndex {
18    _sections: Arc<DwarfSections>,
19    dwarf: gimli::Dwarf<Slice>,
20}
21
22struct DwarfSections {
23    debug_info:        Vec<u8>,
24    debug_abbrev:      Vec<u8>,
25    debug_str:         Vec<u8>,
26    debug_str_offsets: Vec<u8>,
27    debug_line:        Vec<u8>,
28    debug_line_str:    Vec<u8>,
29    debug_ranges:      Vec<u8>,
30    debug_rnglists:    Vec<u8>,
31    debug_addr:        Vec<u8>,
32}
33
34impl DwarfIndex {
35    pub fn load(provider: &dyn BinaryProvider) -> Result<Self> {
36        let endian = if is_big_endian(provider.architecture()) {
37            RunTimeEndian::Big
38        } else {
39            RunTimeEndian::Little
40        };
41
42        let load = |name: &str| -> Vec<u8> {
43            provider
44                .section_data(name)
45                .map(|(data, _, _)| data.to_vec())
46                .unwrap_or_default()
47        };
48
49        let sections = Arc::new(DwarfSections {
50            debug_info:        load(".debug_info"),
51            debug_abbrev:      load(".debug_abbrev"),
52            debug_str:         load(".debug_str"),
53            debug_str_offsets: load(".debug_str_offsets"),
54            debug_line:        load(".debug_line"),
55            debug_line_str:    load(".debug_line_str"),
56            debug_ranges:      load(".debug_ranges"),
57            debug_rnglists:    load(".debug_rnglists"),
58            debug_addr:        load(".debug_addr"),
59        });
60
61        // SAFETY: `_sections` keeps the Vec<u8> buffers alive for the entire
62        // lifetime of `DwarfIndex`. We transmute a lifetime-bound slice to
63        // `'static` so gimli can borrow it. This is the standard pattern used
64        // by gimli's own examples when combining mmap/owned data with Dwarf<>.
65        let dwarf = unsafe {
66            let s = &*Arc::as_ptr(&sections);
67            let mk = |v: &Vec<u8>| -> Slice {
68                let raw: &'static [u8] = std::slice::from_raw_parts(v.as_ptr(), v.len());
69                EndianSlice::new(raw, endian)
70            };
71
72            let loader = |section: gimli::SectionId| -> std::result::Result<Slice, gimli::Error> {
73                let slice = match section {
74                    gimli::SectionId::DebugInfo       => mk(&s.debug_info),
75                    gimli::SectionId::DebugAbbrev     => mk(&s.debug_abbrev),
76                    gimli::SectionId::DebugStr        => mk(&s.debug_str),
77                    gimli::SectionId::DebugStrOffsets => mk(&s.debug_str_offsets),
78                    gimli::SectionId::DebugLine       => mk(&s.debug_line),
79                    gimli::SectionId::DebugLineStr    => mk(&s.debug_line_str),
80                    gimli::SectionId::DebugRanges     => mk(&s.debug_ranges),
81                    gimli::SectionId::DebugRngLists   => mk(&s.debug_rnglists),
82                    gimli::SectionId::DebugAddr       => mk(&s.debug_addr),
83                    _ => EndianSlice::new(&[], endian),
84                };
85                Ok(slice)
86            };
87
88            gimli::Dwarf::load(loader)?
89        };
90
91        Ok(Self { _sections: sections, dwarf })
92    }
93
94    /// Map a virtual address to a source location via DWARF line programs.
95    pub fn resolve(&self, address: u64) -> Result<Option<SourceLocation>> {
96        let mut units = self.dwarf.units();
97        while let Some(header) = units.next()? {
98            let unit = self.dwarf.unit(header)?;
99
100            if !unit_may_contain(&self.dwarf, &unit, address)? {
101                continue;
102            }
103
104            if let Some(program) = unit.line_program.clone() {
105                let comp_dir = unit.comp_dir.clone();
106                let (program, sequences) = program.sequences()?;
107                for seq in &sequences {
108                    if address < seq.start || address >= seq.end {
109                        continue;
110                    }
111                    let mut rows = program.resume_from(seq);
112                    let mut best: Option<SourceLocation> = None;
113                    while let Some((header, row)) = rows.next_row()? {
114                        if row.address() > address {
115                            break;
116                        }
117                        if let Some(file) = row.file(header) {
118                            if let Ok(filename) = file_to_string(&self.dwarf, &unit, file, comp_dir.as_ref()) {
119                                best = Some(SourceLocation {
120                                    file: filename,
121                                    line: row.line().map(|n| n.get() as u32),
122                                    column: match row.column() {
123                                        gimli::ColumnType::LeftEdge => Some(0),
124                                        gimli::ColumnType::Column(c) => Some(c.get() as u32),
125                                    },
126                                });
127                            }
128                        }
129                    }
130                    if let Some(loc) = best {
131                        return Ok(Some(loc));
132                    }
133                }
134            }
135        }
136        Ok(None)
137    }
138}
139
140fn unit_may_contain(
141    dwarf: &gimli::Dwarf<Slice>,
142    unit: &gimli::Unit<Slice>,
143    address: u64,
144) -> Result<bool> {
145    let mut cursor = unit.entries();
146    if let Some((_, entry)) = cursor.next_dfs()? {
147        // DW_AT_ranges
148        if let Some(val) = entry.attr_value(gimli::DW_AT_ranges)? {
149            let _offset = match val {
150                gimli::AttributeValue::RangeListsRef(o) => {
151                    let mut ranges = dwarf.ranges(unit, gimli::RangeListsOffset(o.0))?;
152                    while let Some(range) = ranges.next()? {
153                        if address >= range.begin && address < range.end {
154                            return Ok(true);
155                        }
156                    }
157                    return Ok(false);
158                }
159                gimli::AttributeValue::SecOffset(o) => {
160                    let offset = gimli::RangeListsOffset(o as usize);
161                    let mut ranges = dwarf.ranges(unit, offset)?;
162                    while let Some(range) = ranges.next()? {
163                        if address >= range.begin && address < range.end {
164                            return Ok(true);
165                        }
166                    }
167                    return Ok(false);
168                }
169                _ => return Ok(true),
170            };
171        }
172
173        // DW_AT_low_pc / DW_AT_high_pc fallback
174        let low  = entry.attr_value(gimli::DW_AT_low_pc)?;
175        let high = entry.attr_value(gimli::DW_AT_high_pc)?;
176        if let Some(gimli::AttributeValue::Addr(lo)) = low {
177            let hi = match high {
178                Some(gimli::AttributeValue::Addr(a)) => a,
179                Some(gimli::AttributeValue::Udata(offset)) => lo + offset,
180                _ => return Ok(true),
181            };
182            return Ok(address >= lo && address < hi);
183        }
184    }
185    Ok(true)
186}
187
188fn file_to_string(
189    dwarf: &gimli::Dwarf<Slice>,
190    unit: &gimli::Unit<Slice>,
191    file: &gimli::FileEntry<Slice, usize>,
192    comp_dir: Option<&Slice>,
193) -> std::result::Result<String, gimli::Error> {
194    let mut path = String::new();
195
196    if let Some(dir) = comp_dir {
197        let s = dir.to_string_lossy();
198        if !s.is_empty() {
199            path.push_str(&s);
200            if !path.ends_with('/') {
201                path.push('/');
202            }
203        }
204    }
205
206    if let Some(lp) = unit.line_program.as_ref() {
207        if let Some(dir) = file.directory(lp.header()) {
208            let dir_str = dwarf.attr_string(unit, dir)?;
209            let s = dir_str.to_string_lossy();
210            let s = s.trim_end_matches('/');
211            if !s.is_empty() && !path.trim_end_matches('/').ends_with(s) {
212                path.push_str(s);
213                path.push('/');
214            }
215        }
216    }
217
218    let name = dwarf.attr_string(unit, file.path_name())?;
219    path.push_str(&name.to_string_lossy());
220    Ok(path)
221}
222
223fn is_big_endian(arch: object::Architecture) -> bool {
224    matches!(
225        arch,
226        object::Architecture::PowerPc
227            | object::Architecture::PowerPc64
228            | object::Architecture::Mips
229            | object::Architecture::Sparc64
230    )
231}