Skip to content

Commit

Permalink
Add component support to addr2line
Browse files Browse the repository at this point in the history
Previously this didn't take into account components so components with
multiple modules internally wouldn't work with `addr2line`.
  • Loading branch information
alexcrichton committed Nov 29, 2023
1 parent 39a6029 commit 3c90a91
Showing 1 changed file with 58 additions and 29 deletions.
87 changes: 58 additions & 29 deletions src/bin/wasm-tools/addr2line.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
use addr2line::{Context, LookupResult};
use anyhow::{bail, Context as _, Result};
use anyhow::{anyhow, bail, Context as _, Result};
use gimli::EndianSlice;
use std::collections::HashMap;
use std::io::Write;
use std::ops::Range;
use std::u64;
use wasmparser::{Parser, Payload};
use wasmparser::{Encoding, Parser, Payload};

/// Translate a WebAssembly address to a filename and line number using DWARF
/// debugging information.
Expand Down Expand Up @@ -40,6 +41,12 @@ pub struct Opts {
code_section_relative: bool,
}

struct Module<'a> {
range: Range<u64>,
code_start: Option<u64>,
custom_sections: HashMap<&'a str, &'a [u8]>,
}

impl Opts {
pub fn general_opts(&self) -> &wasm_tools::GeneralOpts {
self.io.general_opts()
Expand All @@ -48,54 +55,60 @@ impl Opts {
pub fn run(&self) -> Result<()> {
let wasm = self.io.parse_input_wasm()?;

let (code_start, custom_sections) = self
let modules = self
.parse_custom_sections(&wasm)
.context("failed to parse input and read custom sections")?;

let dwarf = gimli::Dwarf::load(|id| -> Result<_> {
let data = custom_sections.get(id.name()).copied().unwrap_or(&[]);
Ok(EndianSlice::new(data, gimli::LittleEndian))
})?;
let cx = Context::from_dwarf(dwarf)
.context("failed to create addr2line dwarf mapping context")?;

let mut output = self.io.output_writer()?;

for addr in self.addresses.iter() {
self.addr2line(&addr, code_start, &cx, &mut output)
self.addr2line(&addr, &modules, &mut output)
.with_context(|| format!("failed to find frames for `{addr}`"))?;
}

Ok(())
}

fn parse_custom_sections<'a>(
&self,
wasm: &'a [u8],
) -> Result<(Option<u64>, HashMap<&'a str, &'a [u8]>)> {
let mut ret = HashMap::new();
let mut code_start = None;
fn parse_custom_sections<'a>(&self, wasm: &'a [u8]) -> Result<Vec<Module<'a>>> {
let mut ret = Vec::new();
let mut cur_module = None;
for payload in Parser::new(0).parse_all(wasm) {
match payload? {
Payload::Version {
encoding: Encoding::Module,
range,
..
} => {
assert!(cur_module.is_none());
cur_module = Some(Module {
range: range.start as u64..0,
code_start: None,
custom_sections: HashMap::new(),
});
}

Payload::CustomSection(s) => {
ret.insert(s.name(), s.data());
if let Some(cur) = &mut cur_module {
cur.custom_sections.insert(s.name(), s.data());
}
}
Payload::CodeSectionStart { range, .. } => {
code_start = Some(range.start as u64);
assert!(cur_module.is_some());
cur_module.as_mut().unwrap().code_start = Some(range.start as u64);
}

Payload::End(offset) => {
if let Some(mut module) = cur_module.take() {
module.range.end = offset as u64;
ret.push(module);
}
}
_ => {}
}
}
Ok((code_start, ret))
Ok(ret)
}

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

let module = modules
.iter()
.find(|module| module.range.start <= addr && addr <= module.range.end)
.ok_or_else(|| anyhow!("no module found which contains this address"))?;

let dwarf = gimli::Dwarf::load(|id| -> Result<_> {
let data = module
.custom_sections
.get(id.name())
.copied()
.unwrap_or(&[]);
Ok(EndianSlice::new(data, gimli::LittleEndian))
})?;
let cx = Context::from_dwarf(dwarf)
.context("failed to create addr2line dwarf mapping context")?;

// Addresses in DWARF are relative to the start of the text section, so
// factor that in here.
let text_relative_addr = if self.code_section_relative {
addr
} else {
match code_start {
match module.code_start {
Some(start) => addr
.checked_sub(start)
.context("address is before the beginning of the text section")?,
Expand Down

0 comments on commit 3c90a91

Please sign in to comment.