Skip to content

Commit fe363f0

Browse files
authored
Add component support to addr2line (#1312)
Previously this didn't take into account components so components with multiple modules internally wouldn't work with `addr2line`.
1 parent 39a6029 commit fe363f0

File tree

1 file changed

+58
-29
lines changed

1 file changed

+58
-29
lines changed

src/bin/wasm-tools/addr2line.rs

Lines changed: 58 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
use addr2line::{Context, LookupResult};
2-
use anyhow::{bail, Context as _, Result};
2+
use anyhow::{anyhow, bail, Context as _, Result};
33
use gimli::EndianSlice;
44
use std::collections::HashMap;
55
use std::io::Write;
6+
use std::ops::Range;
67
use std::u64;
7-
use wasmparser::{Parser, Payload};
8+
use wasmparser::{Encoding, Parser, Payload};
89

910
/// Translate a WebAssembly address to a filename and line number using DWARF
1011
/// debugging information.
@@ -40,6 +41,12 @@ pub struct Opts {
4041
code_section_relative: bool,
4142
}
4243

44+
struct Module<'a> {
45+
range: Range<u64>,
46+
code_start: Option<u64>,
47+
custom_sections: HashMap<&'a str, &'a [u8]>,
48+
}
49+
4350
impl Opts {
4451
pub fn general_opts(&self) -> &wasm_tools::GeneralOpts {
4552
self.io.general_opts()
@@ -48,54 +55,60 @@ impl Opts {
4855
pub fn run(&self) -> Result<()> {
4956
let wasm = self.io.parse_input_wasm()?;
5057

51-
let (code_start, custom_sections) = self
58+
let modules = self
5259
.parse_custom_sections(&wasm)
5360
.context("failed to parse input and read custom sections")?;
54-
55-
let dwarf = gimli::Dwarf::load(|id| -> Result<_> {
56-
let data = custom_sections.get(id.name()).copied().unwrap_or(&[]);
57-
Ok(EndianSlice::new(data, gimli::LittleEndian))
58-
})?;
59-
let cx = Context::from_dwarf(dwarf)
60-
.context("failed to create addr2line dwarf mapping context")?;
61-
6261
let mut output = self.io.output_writer()?;
6362

6463
for addr in self.addresses.iter() {
65-
self.addr2line(&addr, code_start, &cx, &mut output)
64+
self.addr2line(&addr, &modules, &mut output)
6665
.with_context(|| format!("failed to find frames for `{addr}`"))?;
6766
}
6867

6968
Ok(())
7069
}
7170

72-
fn parse_custom_sections<'a>(
73-
&self,
74-
wasm: &'a [u8],
75-
) -> Result<(Option<u64>, HashMap<&'a str, &'a [u8]>)> {
76-
let mut ret = HashMap::new();
77-
let mut code_start = None;
71+
fn parse_custom_sections<'a>(&self, wasm: &'a [u8]) -> Result<Vec<Module<'a>>> {
72+
let mut ret = Vec::new();
73+
let mut cur_module = None;
7874
for payload in Parser::new(0).parse_all(wasm) {
7975
match payload? {
76+
Payload::Version {
77+
encoding: Encoding::Module,
78+
range,
79+
..
80+
} => {
81+
assert!(cur_module.is_none());
82+
cur_module = Some(Module {
83+
range: range.start as u64..0,
84+
code_start: None,
85+
custom_sections: HashMap::new(),
86+
});
87+
}
88+
8089
Payload::CustomSection(s) => {
81-
ret.insert(s.name(), s.data());
90+
if let Some(cur) = &mut cur_module {
91+
cur.custom_sections.insert(s.name(), s.data());
92+
}
8293
}
8394
Payload::CodeSectionStart { range, .. } => {
84-
code_start = Some(range.start as u64);
95+
assert!(cur_module.is_some());
96+
cur_module.as_mut().unwrap().code_start = Some(range.start as u64);
97+
}
98+
99+
Payload::End(offset) => {
100+
if let Some(mut module) = cur_module.take() {
101+
module.range.end = offset as u64;
102+
ret.push(module);
103+
}
85104
}
86105
_ => {}
87106
}
88107
}
89-
Ok((code_start, ret))
108+
Ok(ret)
90109
}
91110

92-
fn addr2line(
93-
&self,
94-
addr: &str,
95-
code_start: Option<u64>,
96-
cx: &Context<EndianSlice<gimli::LittleEndian>>,
97-
out: &mut dyn Write,
98-
) -> Result<()> {
111+
fn addr2line(&self, addr: &str, modules: &[Module<'_>], out: &mut dyn Write) -> Result<()> {
99112
// Support either `0x` or `@` prefixes for hex addresses since 0x is
100113
// standard and @ is used by wasmprinter (and web browsers I think?)
101114
let addr = if let Some(hex) = addr.strip_prefix("0x").or_else(|| addr.strip_prefix("@")) {
@@ -104,12 +117,28 @@ impl Opts {
104117
addr.parse()?
105118
};
106119

120+
let module = modules
121+
.iter()
122+
.find(|module| module.range.start <= addr && addr <= module.range.end)
123+
.ok_or_else(|| anyhow!("no module found which contains this address"))?;
124+
125+
let dwarf = gimli::Dwarf::load(|id| -> Result<_> {
126+
let data = module
127+
.custom_sections
128+
.get(id.name())
129+
.copied()
130+
.unwrap_or(&[]);
131+
Ok(EndianSlice::new(data, gimli::LittleEndian))
132+
})?;
133+
let cx = Context::from_dwarf(dwarf)
134+
.context("failed to create addr2line dwarf mapping context")?;
135+
107136
// Addresses in DWARF are relative to the start of the text section, so
108137
// factor that in here.
109138
let text_relative_addr = if self.code_section_relative {
110139
addr
111140
} else {
112-
match code_start {
141+
match module.code_start {
113142
Some(start) => addr
114143
.checked_sub(start)
115144
.context("address is before the beginning of the text section")?,

0 commit comments

Comments
 (0)