1use addr2line::Context;
4use anyhow::{Context as _, Result, bail};
5use gimli::EndianSlice;
6use std::collections::HashMap;
7use std::ops::Range;
8use wasmparser::{Encoding, Parser, Payload};
9
10pub struct Addr2lineModules<'a> {
11 modules: Vec<Module<'a>>,
12}
13
14struct Module<'a> {
15 range: Range<u64>,
16 code_start: Option<u64>,
17 custom_sections: HashMap<&'a str, &'a [u8]>,
18 context: Option<Context<EndianSlice<'a, gimli::LittleEndian>>>,
19}
20
21impl<'a> Addr2lineModules<'a> {
22 pub fn parse(wasm: &'a [u8]) -> Result<Self> {
23 let mut modules = Vec::new();
24 let mut cur_module = None;
25 for payload in Parser::new(0).parse_all(wasm) {
26 match payload? {
27 Payload::Version {
28 encoding: Encoding::Module,
29 range,
30 ..
31 } => {
32 assert!(cur_module.is_none());
33 cur_module = Some(Module {
34 range: range.start as u64..0,
35 code_start: None,
36 custom_sections: HashMap::new(),
37 context: None,
38 });
39 }
40
41 Payload::CustomSection(s) => {
42 if let Some(cur) = &mut cur_module {
43 cur.custom_sections.insert(s.name(), s.data());
44 }
45 }
46 Payload::CodeSectionStart { range, .. } => {
47 assert!(cur_module.is_some());
48 cur_module.as_mut().unwrap().code_start = Some(range.start as u64);
49 }
50
51 Payload::End(offset) => {
52 if let Some(mut module) = cur_module.take() {
53 module.range.end = offset as u64;
54 modules.push(module);
55 }
56 }
57 _ => {}
58 }
59 }
60 Ok(Addr2lineModules { modules })
61 }
62
63 pub fn context(
64 &mut self,
65 addr: u64,
66 code_section_relative: bool,
67 ) -> Result<Option<(&mut Context<EndianSlice<'a, gimli::LittleEndian>>, u64)>> {
68 let module = if code_section_relative {
69 if self.modules.len() == 1 {
70 &mut self.modules[0]
71 } else {
72 bail!("cannot use `--code-section-relative` with more than one module")
73 }
74 } else {
75 match self
76 .modules
77 .iter_mut()
78 .find(|module| module.range.start <= addr && addr <= module.range.end)
79 {
80 Some(module) => module,
81 None => return Ok(None),
82 }
83 };
84
85 let dwarf = gimli::Dwarf::load(|id| -> Result<_> {
86 let data = module
87 .custom_sections
88 .get(id.name())
89 .copied()
90 .unwrap_or(&[]);
91 Ok(EndianSlice::new(data, gimli::LittleEndian))
92 })?;
93 if module.context.is_none() {
94 module.context = Some(
95 Context::from_dwarf(dwarf)
96 .context("failed to create addr2line dwarf mapping context")?,
97 );
98 }
99 let context = module.context.as_mut().unwrap();
100
101 let text_relative_addr = if code_section_relative {
104 addr
105 } else {
106 match module.code_start.and_then(|start| addr.checked_sub(start)) {
107 Some(rel) => rel,
108 None => return Ok(None),
109 }
110 };
111
112 Ok(Some((context, text_relative_addr)))
113 }
114}