1use gimli;
2use goblin::elf::header::{ELFDATA2LSB, ELFDATA2MSB};
3use nix::errno::Errno;
4use std::{
5 collections::HashMap,
6 fmt,
7 fs::File,
8 io::{BufRead, BufReader},
9 path::PathBuf,
10};
11
12use crate::{
13 diag::{Error, Result},
14 process,
15};
16
17const FIRST_UNSUPPORTED_DWARF_VERSION: u16 = 5;
18
19pub struct LineInfo {
25 addr: u64,
26 path: PathBuf,
27 line: usize,
28}
29
30impl LineInfo {
31 pub fn new(addr: u64, path: PathBuf, line: u64) -> Result<Self> {
50 Ok(Self {
51 addr,
52 path,
53 line: usize::try_from(line)?,
54 })
55 }
56
57 #[must_use]
58 pub fn path(&self) -> String {
64 self.path.display().to_string()
65 }
66
67 #[must_use]
68 pub fn line(&self) -> usize {
74 self.line
75 }
76
77 fn read(&self) -> Result<String> {
78 let file = File::open(&self.path)?;
79 let reader = BufReader::new(file);
80
81 let mut lines = reader.lines();
82
83 let line = lines
84 .nth(self.line - 1)
85 .ok_or_else(|| Error::from(Errno::ENODATA))??;
86 Ok(line)
87 }
88}
89
90impl fmt::Display for LineInfo {
91 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
92 let line = self.read().map_err(|_| fmt::Error)?;
93 write!(
94 f,
95 "{:#x}: {}:{} | {}",
96 self.addr,
97 self.path.display(),
98 self.line,
99 line
100 )
101 }
102}
103
104type AddressRange = Vec<(u64, u64)>;
105type DebugArangesMap = HashMap<gimli::DebugInfoOffset, AddressRange>;
106type SectionData<'a> = gimli::EndianSlice<'a, gimli::RunTimeEndian>;
107
108pub struct Dwarf<'a> {
115 data: gimli::Dwarf<SectionData<'a>>,
116 aranges: DebugArangesMap,
117 offset: u64,
118}
119
120impl<'a> Dwarf<'a> {
121 pub fn build(process: &'a process::Info) -> Result<Self> {
135 let endianness = match process.endianness() {
136 ELFDATA2LSB => Ok(gimli::RunTimeEndian::Little),
137 ELFDATA2MSB => Ok(gimli::RunTimeEndian::Big),
138 _ => Err(Error::from(Errno::ENOEXEC)),
139 }?;
140
141 let debug_ranges = gimli::DebugRanges::new(
142 Self::get_section(".debug_ranges", process).unwrap_or(&[]),
143 endianness,
144 );
145 let debug_rnglists = gimli::DebugRngLists::new(
146 Self::get_section(".debug_rnglists", process).unwrap_or(&[]),
147 endianness,
148 );
149 let ranges = gimli::RangeLists::new(debug_ranges, debug_rnglists);
150 let data = gimli::Dwarf {
151 debug_abbrev: gimli::DebugAbbrev::new(
152 Self::get_section(".debug_abbrev", process)?,
153 endianness,
154 ),
155 debug_info: gimli::DebugInfo::new(
156 Self::get_section(".debug_info", process)?,
157 endianness,
158 ),
159 debug_line: gimli::DebugLine::new(
160 Self::get_section(".debug_line", process)?,
161 endianness,
162 ),
163 debug_str: gimli::DebugStr::new(
164 Self::get_section(".debug_str", process)?,
165 endianness,
166 ),
167 ranges,
168 ..Default::default()
169 };
170
171 let aranges = Self::build_aranges(&data)?;
172 let offset = process.offset();
173
174 Ok(Self {
175 data,
176 aranges,
177 offset,
178 })
179 }
180
181 fn get_section(
182 section_name: &'a str,
183 process: &'a process::Info,
184 ) -> Result<&'a [u8]> {
185 process
186 .get_section_data(section_name)?
187 .ok_or_else(|| Error::from(Errno::ENODATA))
188 }
189
190 fn build_aranges(
191 dwarf: &gimli::Dwarf<gimli::EndianSlice<gimli::RunTimeEndian>>,
192 ) -> Result<HashMap<gimli::DebugInfoOffset, Vec<(u64, u64)>>> {
193 let mut aranges = HashMap::new();
194 let mut iter = dwarf.units();
195 while let Some(unit_header) = iter.next()? {
196 if unit_header.version() >= FIRST_UNSUPPORTED_DWARF_VERSION {
197 Err(Errno::ENOEXEC)?;
198 }
199
200 let mut unit_ranges = Vec::new();
201 let unit = dwarf.unit(unit_header)?;
202 let mut entries = unit.entries();
203 while let Some((_, entry)) = entries.next_dfs()? {
204 let mut attrs = entry.attrs();
205 let mut low_pc = None;
206 let mut high_pc = None;
207 let mut high_pc_offset = None;
208 let mut ranges_offset = None;
209 while let Some(attr) = attrs.next()? {
210 match attr.name() {
211 gimli::DW_AT_low_pc => {
212 if let gimli::AttributeValue::Addr(addr) = attr.value() {
213 low_pc = Some(addr);
214 }
215 }
216 gimli::DW_AT_high_pc => match attr.value() {
217 gimli::AttributeValue::Addr(val) => high_pc = Some(val),
218 gimli::AttributeValue::Udata(val) => {
219 high_pc_offset = Some(val);
220 }
221 _ => Err(Error::from(Errno::ENODATA))?,
222 },
223 gimli::DW_AT_ranges => {
224 if let gimli::AttributeValue::RangeListsRef(val) =
225 attr.value()
226 {
227 ranges_offset = Some(val);
228 }
229 }
230 _ => {}
231 }
232 }
233
234 if let (Some(low_pc), Some(high_pc)) = (low_pc, high_pc) {
235 unit_ranges.push((low_pc, high_pc));
236 } else if let (Some(low_pc), Some(high_pc_offset)) =
237 (low_pc, high_pc_offset)
238 {
239 unit_ranges.push((low_pc, (low_pc + high_pc_offset)));
240 } else if let Some(ranges_offset) = ranges_offset {
241 let offset = dwarf.ranges_offset_from_raw(&unit, ranges_offset);
242 let mut iter = dwarf.ranges(&unit, offset)?;
243 while let Some(range) = iter.next()? {
244 unit_ranges.push((range.begin, range.end));
245 }
246 }
247 }
248
249 let offset = unit_header
250 .offset()
251 .as_debug_info_offset()
252 .ok_or_else(|| Error::from(Errno::ENODATA))?;
253 aranges.insert(offset, unit_ranges);
254 }
255
256 Ok(aranges)
257 }
258
259 fn is_addr_in_unit<R: gimli::Reader<Offset = usize>>(
260 &self,
261 addr: u64,
262 unit_header: &gimli::UnitHeader<R>,
263 ) -> Result<bool> {
264 let offset = unit_header
265 .offset()
266 .as_debug_info_offset()
267 .ok_or_else(|| Error::from(Errno::ENODATA))?;
268
269 self.aranges
270 .get(&offset)
271 .map(|ranges| {
272 ranges
273 .iter()
274 .any(|(start, end)| (*start..*end).contains(&addr))
275 })
276 .ok_or_else(|| Error::from(Errno::ENODATA))
277 }
278
279 fn path_from_row(
280 &self,
281 unit_header: &gimli::UnitHeader<SectionData<'_>>,
282 program_header: &gimli::LineProgramHeader<SectionData<'_>>,
283 row: &gimli::LineRow,
284 ) -> Result<PathBuf> {
285 let mut path = PathBuf::new();
286
287 let unit = self.data.unit(*unit_header)?;
288 if let Some(dir) = unit.comp_dir {
289 path.push(dir.to_string_lossy().into_owned());
290 }
291
292 let file = row
293 .file(program_header)
294 .ok_or_else(|| Error::from(Errno::ENODATA))?;
295 if file.directory_index() != 0 {
296 if let Some(dir) = file.directory(program_header) {
297 let dir_path = self
298 .data
299 .attr_string(&unit, dir)?
300 .to_string_lossy()
301 .into_owned();
302 path.push(dir_path);
303 }
304 }
305
306 let file_path = self
307 .data
308 .attr_string(&unit, file.path_name())?
309 .to_string_lossy()
310 .into_owned();
311 path.push(file_path);
312
313 Ok(path)
314 }
315
316 fn info_from_row(
317 &self,
318 unit_header: &gimli::UnitHeader<SectionData<'_>>,
319 program_header: &gimli::LineProgramHeader<SectionData<'_>>,
320 row: &gimli::LineRow,
321 ) -> Result<LineInfo> {
322 let line = row.line().ok_or_else(|| Error::from(Errno::ENODATA))?;
323
324 let line = line.get();
325 let path = self.path_from_row(unit_header, program_header, row)?;
326 LineInfo::new(row.address() + self.offset, path, line)
327 }
328
329 fn info_from_unit(
330 &self,
331 addr: u64,
332 unit_header: &gimli::UnitHeader<SectionData<'_>>,
333 ) -> Result<Option<LineInfo>> {
334 let mut info = None;
335
336 let unit = self.data.unit(*unit_header)?;
337 if let Some(program) = unit.line_program {
338 let mut rows = program.rows();
339 while let Some((program_header, row)) = rows.next_row()? {
340 if !row.is_stmt() {
341 continue;
342 }
343
344 if addr != row.address() {
345 continue;
346 }
347
348 info = Some(self.info_from_row(unit_header, program_header, row)?);
349 break;
350 }
351 }
352
353 Ok(info)
354 }
355
356 pub fn addr2line(&self, addr: u64) -> Result<Option<LineInfo>> {
372 let addr = addr - self.offset;
373
374 let mut info: Option<LineInfo> = None;
375 let mut iter = self.data.units();
376 while let Some(unit_header) = iter.next()? {
377 if !self.is_addr_in_unit(addr, &unit_header)? {
378 continue;
379 }
380
381 info = self.info_from_unit(addr, &unit_header)?;
382 if info.is_some() {
383 break;
384 }
385 }
386
387 Ok(info)
388 }
389}
390
391#[cfg(test)]
392mod tests {
393 use super::*;
394
395 use gimli::{
396 DebugAbbrev, DebugInfo, DebugLine, DebugRanges, DebugRngLists, DebugStr,
397 RangeLists, RunTimeEndian,
398 };
399 use std::{collections::HashMap, io::Write};
400
401 use crate::diag::Result;
402
403 #[test]
404 fn test_line_info_new() {
405 let path = PathBuf::from("/path/to/file.rs");
406 let line_info = LineInfo::new(0x1234, path.clone(), 42)
407 .expect("Failed to create LineInfo");
408 assert_eq!(line_info.addr, 0x1234);
409 assert_eq!(line_info.path, path);
410 assert_eq!(line_info.line, 42);
411 }
412
413 #[test]
414 fn test_line_info_path() {
415 let path = PathBuf::from("/path/to/file.rs");
416 let line_info = LineInfo::new(0x1234, path.clone(), 42)
417 .expect("Failed to create LineInfo");
418 assert_eq!(line_info.path(), "/path/to/file.rs");
419 }
420
421 #[test]
422 fn test_line_info_line() {
423 let path = PathBuf::from("/path/to/file.rs");
424 let line_info =
425 LineInfo::new(0x1234, path, 42).expect("Failed to create LineInfo");
426 assert_eq!(line_info.line(), 42);
427 }
428
429 #[test]
430 fn test_line_info_display() {
431 let mut tmpfile =
432 tempfile::NamedTempFile::new().expect("Failed to create temp file");
433 for i in 1..100 {
434 writeln!(tmpfile, "line {}", i).expect("Failed to write to temp file");
435 }
436 let path = tmpfile.path().to_path_buf();
437 let line_info = LineInfo::new(0x1234, path.clone(), 42)
438 .expect("Failed to create LineInfo");
439 let display = format!("{}", line_info);
440 assert!(display.contains("0x1234"));
441 assert!(
442 display.contains(path.to_str().expect("Failed to convert path to str"))
443 );
444 assert!(display.contains("42"));
445 }
446
447 #[test]
448 fn test_build_aranges_empty() -> Result<()> {
449 let endian = RunTimeEndian::Little;
450 let data = gimli::Dwarf {
451 debug_abbrev: DebugAbbrev::new(&[], endian),
452 debug_info: DebugInfo::new(&[], endian),
453 debug_line: DebugLine::new(&[], endian),
454 debug_str: DebugStr::new(&[], endian),
455 ranges: RangeLists::new(
456 DebugRanges::new(&[], endian),
457 DebugRngLists::new(&[], endian),
458 ),
459 ..Default::default()
460 };
461
462 let map = Dwarf::build_aranges(&data)?;
463 assert!(map.is_empty());
464 Ok(())
465 }
466
467 #[test]
468 fn test_addr2line_empty_returns_none() -> Result<()> {
469 let endian = RunTimeEndian::Little;
470 let data = gimli::Dwarf {
471 debug_abbrev: DebugAbbrev::new(&[], endian),
472 debug_info: DebugInfo::new(&[], endian),
473 debug_line: DebugLine::new(&[], endian),
474 debug_str: DebugStr::new(&[], endian),
475 ranges: RangeLists::new(
476 DebugRanges::new(&[], endian),
477 DebugRngLists::new(&[], endian),
478 ),
479 ..Default::default()
480 };
481
482 let dwarf = Dwarf {
483 data,
484 aranges: HashMap::new(),
485 offset: 0,
486 };
487 let res = dwarf.addr2line(0x1000)?;
488 assert!(res.is_none());
489 Ok(())
490 }
491}