Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
- Adds variable information to symcaches. ([#1036](https://github.com/getsentry/symbolic/pull/1036), [#1037](https://github.com/getsentry/symbolic/pull/1037))
- Implements variables at a frame offset. ([#1026](https://github.com/getsentry/symbolic/pull/1026))
- Implement resolution for primitive and pointer type references contained in variables. ([#1032](https://github.com/getsentry/symbolic/pull/1032))
- Build symcaches for WASM modules from an Emscripten source map, behind the `wasm-sourcemap` feature.

## 14.0.0-alpha.3

Expand Down
10 changes: 10 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

14 changes: 14 additions & 0 deletions examples/wasm_sourcemap_debug/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
[package]
name = "wasm_sourcemap_debug"
version = "14.0.0-alpha.3"
authors = ["Jan Michael Auer <mail@jauer.org>"]
edition = "2021"
publish = false

[dependencies]
anyhow = { workspace = true }
clap = { workspace = true }
symbolic = { version = "14.0.0-alpha.3", path = "../../symbolic", features = [
"symcache",
"wasm-sourcemap",
] }
166 changes: 166 additions & 0 deletions examples/wasm_sourcemap_debug/src/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
use std::fs::File;
use std::io::{Cursor, Write};
use std::path::PathBuf;
use anyhow::{Context, Result};
use clap::builder::ValueParser;
use clap::{Arg, ArgAction, ArgMatches, Command};

use symbolic::common::ByteView;
use symbolic::debuginfo::js::discover_sourcemap_embedded_debug_id;
use symbolic::debuginfo::wasm::WasmObject;
use symbolic::symcache::wasm_sourcemap::process_wasm_sourcemap;
use symbolic::symcache::{SymCache, SymCacheConverter};

fn execute(matches: &ArgMatches) -> Result<()> {
let wasm_path = matches
.get_one::<PathBuf>("wasm_path")
.context("missing wasm path")?;
let map_path = matches
.get_one::<PathBuf>("map_path")
.context("missing source map path")?;

let wasm = ByteView::open(wasm_path)?;
let object = WasmObject::parse(&wasm)?;
let map = ByteView::open(map_path)?;

let map_text = std::str::from_utf8(map.as_ref()).context("source map is not valid utf-8")?;
let wasm_debug_id = object.debug_id();

println!("wasm debug_id: {}", wasm_debug_id);
match discover_sourcemap_embedded_debug_id(map_text) {
Some(map_debug_id) => {
println!("map debug_id: {}", map_debug_id);
if map_debug_id != wasm_debug_id {
eprintln!("warning: debug ids do not match");
}
}
None => println!("map debug_id: (not set)"),
}
println!("has_debug_info: {}", object.has_debug_info());
println!("wasm size: {} bytes", wasm.len());
println!();

println!("function bodies:");
for body in object.function_bodies() {
println!(
" [{}] {:#x}..{:#x} ({}) {:?}",
body.index,
body.address,
body.address + body.size,
body.size,
body.name,
);
}
println!();

let mut converter = SymCacheConverter::new();
let stats = process_wasm_sourcemap(&mut converter, &object, &map)?;
println!("stats: {stats:?}");
println!();

let mut buffer = Vec::new();
converter.serialize(&mut Cursor::new(&mut buffer))?;
let symcache = SymCache::parse(&buffer)?;

if *matches.get_one("write_cache_file").unwrap() {
let filename = matches
.get_one::<PathBuf>("symcache_path")
.cloned()
.unwrap_or_else(|| {
let mut path = wasm_path.clone().into_os_string();
path.push(".symcache");
PathBuf::from(path)
});
File::create(&filename)?.write_all(&buffer)?;
println!("symcache written to {}", filename.display());
println!();
}

if let Some(addrs) = matches.get_many::<u64>("lookups") {
for addr in addrs {
lookup_and_print(&symcache, *addr);
}
} else {
println!(
"symcache built ({} bytes). pass --lookup ADDR to resolve an address.",
buffer.len()
);
}

Ok(())
}

fn lookup_and_print(symcache: &SymCache<'_>, addr: u64) {
match symcache.lookup(addr).next() {
None => println!("lookup {addr:#x}: no match"),
Some(location) => {
let file = location
.file()
.map(|file| file.full_path())
.filter(|path| !path.is_empty());
let line = location.line();
print!("lookup {addr:#x}: {}", location.function().name());
match (file, line) {
(Some(path), line) if line != 0 => println!(" at {path} line {line}"),
(Some(path), _) => println!(" at {path}"),
(None, line) if line != 0 => println!(" line {line}"),
_ => println!(" (function only)"),
}
}
}
}

fn parse_addr(addr: &str) -> Result<u64> {
match addr.strip_prefix("0x") {
Some(addr) => u64::from_str_radix(addr, 16),
None => addr.parse(),
}
.context("unable to parse address")
}

fn main() {
let matches = Command::new("wasm-sourcemap-debug")
.about("Build and inspect a SymCache from a wasm module and Emscripten source map")
.arg(
Arg::new("wasm_path")
.value_name("WASM")
.value_parser(clap::value_parser!(PathBuf))
.help("Path to the .wasm module")
.required(true)
.index(1),
)
.arg(
Arg::new("map_path")
.value_name("MAP")
.value_parser(clap::value_parser!(PathBuf))
.help("Path to the .wasm.map file")
.required(true)
.index(2),
)
.arg(
Arg::new("lookups")
.short('l')
.long("lookup")
.value_name("ADDR")
.value_parser(ValueParser::new(parse_addr))
.action(ArgAction::Append)
.help("Look up one or more module-relative byte offsets"),
)
.arg(
Arg::new("write_cache_file")
.short('w')
.long("write-cache-file")
.action(ArgAction::SetTrue)
.help("Write the symcache next to the wasm file, or to --symcache-file"),
)
.arg(
Arg::new("symcache_path")
.long("symcache-file")
.value_name("PATH")
.value_parser(clap::value_parser!(PathBuf))
.help("Destination path when writing a symcache"),
)
.get_matches();

execute(&matches).unwrap();
}
35 changes: 35 additions & 0 deletions symbolic-debuginfo/src/wasm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,25 @@ pub struct WasmObject<'data> {
data: &'data [u8],
code_offset: u64,
kind: ObjectKind,
/// True body size of every function, parallel to `funcs`.
///
/// The sizes in `funcs` are stretched to be contiguous with the next function, which hides the
/// padding between two bodies. Consumers that need exact bounds use these instead.
body_sizes: Vec<u64>,
num_imported_funcs: u32,
}

/// A function body in the code section of a [`WasmObject`].
#[derive(Clone, Copy, Debug)]
pub struct WasmFunctionBody<'data> {
/// The wasm function index, counting imported functions.
pub index: u32,
/// Offset of the body in the module, relative to the start of the file.
pub address: u64,
/// Exact size of the body, excluding any padding to the next function.
pub size: u64,
/// Name from the `name` section, if the module has one.
pub name: Option<&'data str>,
}

impl<'data> WasmObject<'data> {
Expand Down Expand Up @@ -101,6 +120,22 @@ impl<'data> WasmObject<'data> {
}
}

/// Returns an iterator over the function bodies in the code section, in code section order.
///
/// Unlike [`symbols`](Self::symbols) this reports exact body bounds and wasm function indices.
pub fn function_bodies(&self) -> impl Iterator<Item = WasmFunctionBody<'_>> + '_ {
self.funcs
.iter()
.zip(self.body_sizes.iter())
.enumerate()
.map(move |(i, (func, size))| WasmFunctionBody {
index: self.num_imported_funcs + i as u32,
address: func.address,
size: *size,
name: func.name.as_deref(),
})
}

/// Returns an ordered map of symbols in the symbol table.
pub fn symbol_map(&self) -> SymbolMap<'data> {
self.symbols().collect()
Expand Down
7 changes: 6 additions & 1 deletion symbolic-debuginfo/src/wasm/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ impl<'d> Parse<'d> for WasmObject<'d> {
let features = WasmFeatures::all();
let mut validator = Validator::new_with_features(features);
let mut funcs = Vec::<Symbol>::new();
let mut body_sizes = Vec::<u64>::new();
let mut num_imported_funcs = 0u32;
let mut func_allocs = FuncValidatorAllocations::default();

Expand Down Expand Up @@ -162,7 +163,9 @@ impl<'d> Parse<'d> for WasmObject<'d> {

// Though we have an accurate? size of the function body, the old method of symbol
// iterating with walrus extends the size of each body to be contiguous with the
// next function, so we do the same, other than the final function
// next function, so we do the same, other than the final function. The exact
// size is kept alongside for consumers that need the real bounds.
body_sizes.push(size);
if let Some(prev) = funcs.last_mut() {
prev.size = address - prev.address;
}
Expand Down Expand Up @@ -240,6 +243,8 @@ impl<'d> Parse<'d> for WasmObject<'d> {
data,
code_offset,
kind,
body_sizes,
num_imported_funcs,
})
}
}
Expand Down
3 changes: 3 additions & 0 deletions symbolic-symcache/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ all-features = true

[dependencies]
indexmap = { workspace = true }
sourcemap = { workspace = true, optional = true }
symbolic-common = { version = "14.0.0-alpha.3", path = "../symbolic-common" }
symbolic-debuginfo = { version = "14.0.0-alpha.3", path = "../symbolic-debuginfo" }
symbolic-il2cpp = { version = "14.0.0-alpha.3", path = "../symbolic-il2cpp", optional = true }
Expand All @@ -38,6 +39,8 @@ symbolic-testutils = { path = "../symbolic-testutils" }
[features]
bench = []
il2cpp = ["symbolic-il2cpp"]
# Synthesize SymCaches for WASM modules from Emscripten source maps
wasm-sourcemap = ["dep:sourcemap", "symbolic-debuginfo/wasm"]

[[bench]]
name = "bench_writer"
Expand Down
2 changes: 2 additions & 0 deletions symbolic-symcache/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,8 @@ mod raw;
pub mod transform;
mod utils;
mod v9;
#[cfg(feature = "wasm-sourcemap")]
pub mod wasm_sourcemap;
mod writer;

use symbolic_common::{Arch, AsSelf, DebugId};
Expand Down
Loading
Loading