1use std::rc::Rc;
2
3use addr2line::{fallible_iterator::FallibleIterator, Context, LookupResult};
4use elf::{abi::STT_FUNC, endian::LittleEndian, ElfBytes};
5use eyre::Result;
6use gimli::{EndianRcSlice, RunTimeEndian};
7use object::File;
8use rustc_demangle::demangle;
9use rustc_hash::FxHashMap;
10
11type GimliReader = gimli::EndianRcSlice<gimli::RunTimeEndian>;
12
13fn demangle_name(name: String) -> String {
14 if let Some(index) = name.rfind("::") {
15 let truncated = &name[0..index];
16 truncated.to_string()
17 } else {
18 name
19 }
20}
21
22fn build_symbol_table(elf_data: &[u8]) -> Result<FxHashMap<u64, String>> {
23 let mut symbol_table = FxHashMap::default();
24
25 let elf = ElfBytes::<LittleEndian>::minimal_parse(elf_data)?;
26 if let Some((symtab, strtab)) = elf.symbol_table()? {
27 for sym in symtab {
28 if sym.st_symtype() == STT_FUNC {
29 let name = strtab.get(sym.st_name as usize)?;
30 symbol_table.insert(sym.st_value, demangle(name).to_string());
31 }
32 }
33 }
34
35 Ok(symbol_table)
36}
37
38fn lookup_pc_in_dwarf(pc: u32, ctx: &Context<GimliReader>) -> Vec<Frame> {
39 let frames = match ctx.find_frames(pc as u64) {
40 LookupResult::Output(result) => result.unwrap(),
41 LookupResult::Load {
42 load: _,
43 continuation: _,
44 } => unimplemented!(),
45 };
46 frames
47 .filter_map(|frame| Ok(decode_frame(frame)))
48 .collect()
49 .unwrap()
50}
51
52fn decode_frame(fr: addr2line::Frame<EndianRcSlice<RunTimeEndian>>) -> Option<Frame> {
53 Some(Frame {
54 name: fr.function.as_ref()?.demangle().ok()?.to_string(),
55 lineno: fr.location.as_ref()?.line? as i64,
56 filename: fr.location.as_ref()?.file?.to_string(),
57 })
58}
59
60fn load_dwarf<'data, O: object::Object<'data>>(file: &O) -> Result<gimli::Dwarf<GimliReader>> {
61 let endian = if file.is_little_endian() {
62 gimli::RunTimeEndian::Little
63 } else {
64 gimli::RunTimeEndian::Big
65 };
66
67 fn load_section<'data, O, Endian>(
68 id: gimli::SectionId,
69 file: &O,
70 endian: Endian,
71 ) -> std::result::Result<gimli::EndianRcSlice<Endian>, gimli::Error>
72 where
73 O: object::Object<'data>,
74 Endian: gimli::Endianity,
75 {
76 use object::ObjectSection as _;
77
78 let data = file
79 .section_by_name(id.name())
80 .and_then(|section| section.uncompressed_data().ok())
81 .unwrap_or(std::borrow::Cow::Borrowed(&[]));
82 Ok(gimli::EndianRcSlice::new(Rc::from(&*data), endian))
83 }
84
85 Ok(gimli::Dwarf::load(|id| load_section(id, file, endian))?)
86}
87
88#[derive(Clone, Debug, PartialEq, Eq)]
90pub struct Frame {
91 pub name: String,
93
94 pub lineno: i64,
96
97 pub filename: String,
99}
100
101pub trait LookupPc {
102 fn lookup_pc(&self, pc: u32) -> Option<Frame>;
103}
104
105pub struct Addr2Frame {
106 ctx: Context<GimliReader>,
107 symbol_table: FxHashMap<u64, String>,
108}
109
110impl Addr2Frame {
111 pub fn new(input: &[u8]) -> Result<Self> {
112 let file = File::parse(input)?;
113 let dwarf = load_dwarf(&file)?;
114 let ctx = Context::from_dwarf(dwarf)?;
115 let symbol_table = build_symbol_table(input)?;
116 Ok(Self { ctx, symbol_table })
117 }
118}
119
120impl LookupPc for Addr2Frame {
121 fn lookup_pc(&self, pc: u32) -> Option<Frame> {
122 let dwarf_frames = lookup_pc_in_dwarf(pc, &self.ctx);
123 let symbol = self.symbol_table.get(&(pc as u64)).cloned();
124
125 if !dwarf_frames.is_empty() {
126 Some(dwarf_frames.last().unwrap().clone())
127 } else {
128 symbol.map(|symbol| Frame {
129 name: demangle_name(symbol).replace('&', ""),
130 lineno: 0,
131 filename: "unknown".into(),
132 })
133 }
134 }
135}