Skip to main content

wasm_tools/
addr2line.rs

1//! Shared support for `addr2line` and `validate` to parse DWARF sections.
2
3use 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        // Addresses in DWARF are relative to the start of the text section, so
102        // factor that in here.
103        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}