diff --git a/CHANGELOG.md b/CHANGELOG.md index 6efcff8cc..69cdbbe0d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ - Add `processing_pool_size` configuration option that allows to set the size of the processing pool. ([#273](https://github.com/getsentry/symbolicator/pull/273)) - Use a dedicated `tmp` sub-directory in the cache directory to write temporary files into. ([#265](https://github.com/getsentry/symbolicator/pull/265)) - Use STATSD_SERVER environment variable to set metrics.statsd configuration option ([#182](https://github.com/getsentry/symbolicator/pull/182)) +- Added WASM support. ([#301](https://github.com/getsentry/symbolicator/pull/301)) ### Bug Fixes diff --git a/Cargo.lock b/Cargo.lock index 8d7f68a52..f38cd1c8b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -575,10 +575,10 @@ checksum = "b3a71ab494c0b5b860bdc8407ae08978052417070c2ced38573a9157ad75b8ac" [[package]] name = "cpp_demangle" -version = "0.3.0" -source = "git+https://github.com/Swatinem/cpp_demangle?branch=sentry-patches#2d274c4865169e81cc84fc33c6227f61bceab1ae" +version = "0.3.1" +source = "git+https://github.com/Swatinem/cpp_demangle?branch=sentry-patches#479281f0c8c2630c5a5a1aeec2ea74ce233d8cc6" dependencies = [ - "cfg-if 0.1.10", + "cfg-if 1.0.0", "glob", ] @@ -1406,6 +1406,12 @@ dependencies = [ "tokio-tls 0.3.0", ] +[[package]] +name = "id-arena" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25a2bc672d1148e28034f176e01fffebb08b35768468cc954630da77a1449005" + [[package]] name = "idna" version = "0.1.5" @@ -1595,6 +1601,12 @@ version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b294d6fa9ee409a054354afc4352b0b9ef7ca222c69b8812cbea9e7d2bf3783f" +[[package]] +name = "leb128" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3576a87f2ba00f6f106fdfcd16db1d698d648a26ad8e0573cad8537c3c362d2a" + [[package]] name = "libc" version = "0.2.80" @@ -1820,7 +1832,7 @@ dependencies = [ [[package]] name = "msvc-demangler" version = "0.8.0" -source = "git+https://github.com/Swatinem/msvc-demangler-rust?branch=sentry-patches#47c1c61d28f5a226d7f59aeff94fc6e41f550e40" +source = "git+https://github.com/Swatinem/msvc-demangler-rust?branch=sentry-patches#075432259fe0eaac2f3fc225550a4db479bf3c81" dependencies = [ "bitflags", ] @@ -3121,7 +3133,7 @@ dependencies = [ [[package]] name = "symbolic" version = "7.5.0" -source = "git+https://github.com/getsentry/symbolic?branch=fix/demangle-fixes#4619030e1ca0470c38190b89fb580d9aea5c5f07" +source = "git+https://github.com/getsentry/symbolic?branch=fix/demangle-fixes#749a39e7f34b7c22339c0f9d44a0b57502a05655" dependencies = [ "symbolic-common", "symbolic-debuginfo", @@ -3133,7 +3145,7 @@ dependencies = [ [[package]] name = "symbolic-common" version = "7.5.0" -source = "git+https://github.com/getsentry/symbolic?branch=fix/demangle-fixes#4619030e1ca0470c38190b89fb580d9aea5c5f07" +source = "git+https://github.com/getsentry/symbolic?branch=fix/demangle-fixes#749a39e7f34b7c22339c0f9d44a0b57502a05655" dependencies = [ "debugid", "memmap", @@ -3145,7 +3157,7 @@ dependencies = [ [[package]] name = "symbolic-debuginfo" version = "7.5.0" -source = "git+https://github.com/getsentry/symbolic?branch=fix/demangle-fixes#4619030e1ca0470c38190b89fb580d9aea5c5f07" +source = "git+https://github.com/getsentry/symbolic?branch=fix/demangle-fixes#749a39e7f34b7c22339c0f9d44a0b57502a05655" dependencies = [ "dmsort", "fallible-iterator", @@ -3164,13 +3176,15 @@ dependencies = [ "smallvec 1.5.0", "symbolic-common", "thiserror", + "walrus", + "wasmparser 0.68.0", "zip", ] [[package]] name = "symbolic-demangle" version = "7.5.0" -source = "git+https://github.com/getsentry/symbolic?branch=fix/demangle-fixes#4619030e1ca0470c38190b89fb580d9aea5c5f07" +source = "git+https://github.com/getsentry/symbolic?branch=fix/demangle-fixes#749a39e7f34b7c22339c0f9d44a0b57502a05655" dependencies = [ "cc", "cpp_demangle", @@ -3182,7 +3196,7 @@ dependencies = [ [[package]] name = "symbolic-minidump" version = "7.5.0" -source = "git+https://github.com/getsentry/symbolic?branch=fix/demangle-fixes#4619030e1ca0470c38190b89fb580d9aea5c5f07" +source = "git+https://github.com/getsentry/symbolic?branch=fix/demangle-fixes#749a39e7f34b7c22339c0f9d44a0b57502a05655" dependencies = [ "cc", "lazy_static", @@ -3196,7 +3210,7 @@ dependencies = [ [[package]] name = "symbolic-symcache" version = "7.5.0" -source = "git+https://github.com/getsentry/symbolic?branch=fix/demangle-fixes#4619030e1ca0470c38190b89fb580d9aea5c5f07" +source = "git+https://github.com/getsentry/symbolic?branch=fix/demangle-fixes#749a39e7f34b7c22339c0f9d44a0b57502a05655" dependencies = [ "dmsort", "fnv", @@ -3963,6 +3977,32 @@ version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "078775d0255232fb988e6fccf26ddc9d1ac274299aaedcedce21c6f72cc533ce" +[[package]] +name = "walrus" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d470d0583e65f4cab21a1ff3c1ba3dd23ae49e68f516f0afceaeb001b32af39" +dependencies = [ + "anyhow", + "id-arena", + "leb128", + "log", + "walrus-macro", + "wasmparser 0.59.0", +] + +[[package]] +name = "walrus-macro" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7c2bb690b44cb1b0fdcc54d4998d21f8bdaf706b93775425e440b174f39ad16" +dependencies = [ + "heck", + "proc-macro2 1.0.10", + "quote 1.0.3", + "syn 1.0.17", +] + [[package]] name = "want" version = "0.2.0" @@ -4064,6 +4104,18 @@ version = "0.2.60" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "daf76fe7d25ac79748a37538b7daeed1c7a6867c92d3245c12c6222e4a20d639" +[[package]] +name = "wasmparser" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a950e6a618f62147fd514ff445b2a0b53120d382751960797f85f058c7eda9b9" + +[[package]] +name = "wasmparser" +version = "0.68.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29a00e14eed9c2ecbbdbdd4fb284f49d21b6808965de24769a6379a13ec47d4c" + [[package]] name = "web-sys" version = "0.3.37" diff --git a/docs/advanced/symbol-server-compatibility.md b/docs/advanced/symbol-server-compatibility.md index 3415e4efb..b19a7d641 100644 --- a/docs/advanced/symbol-server-compatibility.md +++ b/docs/advanced/symbol-server-compatibility.md @@ -75,6 +75,11 @@ Specifically, the code and debug identifiers are defined as follows: bidirectionally from the UUID. - **Debug ID:** The same UUID, amended by a `0` for age. +**WASM**: + +- **Code ID:** The bytes as specified in the `build_id` custom section. +- **Debug ID:** The same as code ID but truncated to 16 bytes + `0` for age. + **PE** / **PDB**: - **Code ID:** The hex value of the `time_date_stamp` in the COFF header @@ -266,6 +271,7 @@ The build-id hex representation is always provided in **lowercase**. - **ELF** (binary, potentially stripped) - **ELF** (debug info) +- **WASM** (debug info) Symbol bundles are supported by adding a `.src.zip` prefix to the ELF: @@ -297,7 +303,7 @@ The following layout types support this lookup: If you have no requirements to be compatible with another system you can also use the "unified" directory layout structure. This has the advantage that it's unified across all platforms and thus easier to manage. It can store breakpad -files, PDBs, PEs and everything else. The `symsorter` tool in the symbolicator +files, PDBs, PEs and everything else. The `symsorter` tool in the symbolicator repository can automatically sort debug symbols into this format and also automatically create source bundles. @@ -309,6 +315,7 @@ The debug id is in all cases lowercase in hex format and computed as follows: - **PDB**: `` (age in hex, not padded) - **ELF**: `` - **MachO**: `` +- **WASM**: `` The path format is then as follows: diff --git a/docs/api/response.md b/docs/api/response.md index 857de9db6..3d45842ab 100644 --- a/docs/api/response.md +++ b/docs/api/response.md @@ -34,8 +34,8 @@ symbolication succeeds within a configured timeframe (around 20 seconds): // Frame information "instruction_addr": "0xfeedbeef", // actual address of the frame + "addr_mode": "abs", // address mode "sym_addr": "0xfeed0000", // start address of the function - "line_addr": "0xfeedbe00", // start address of the line "package": "/path/to/module.so", // path to the module's code file "symbol": "__1cGmemset6FpviI_0_", // original mangled function name "function": "memset", // demangled short version of symbol @@ -73,6 +73,15 @@ occurred during symbolication, such as missing symbol files or unresolvable addresses within symbols are reported as values for `status` in both modules and frames. +## Note on Addresses + +Addresses (`instruction_addr` and `sym_addr`) can come in two versions. They +can be absolute or relative. Symbolicator will always try to make addresses +absolute but in some cases this cannot be done. For instance WASM modules do +not have absolute addresses in which case the addresses stay relative. This is +identified by the `addr_mode` property. When it's set to `"abs"` it means +the addresses are absolute, when `"rel:X"` it's relative to module index `X`. + ## Backoff Response If symbolication takes longer than the threshold `timeout`, the server instead diff --git a/docs/api/symbolication.md b/docs/api/symbolication.md index d0140fb6f..42c4b2524 100644 --- a/docs/api/symbolication.md +++ b/docs/api/symbolication.md @@ -22,7 +22,8 @@ Content-Type: application/json { "frames": [ { - "instruction_addr": "0xfeedbeef" + "instruction_addr": "0xfeedbeef", + "addr_mode": "rel:0" }, ... ], @@ -61,17 +62,18 @@ as well as external sources to pull symbols from: - `sources`: A list of descriptors for internal or external symbol sources. See [Sources](index.md). - `modules`: A list of code modules (aka debug images) that were loaded into the - process. All attributes other than `type` are required. The Symbolicator may - optimize lookups based on the `type` if present. Valid types are `macho`, - `pe`, `elf`. Invalid types are silently ignored. The Symbolicator still works - if the type is invalid, but less efficiently. However, a schematically valid - but _wrong_ type is fatal for finding symbols. + process. All attributes other than `type`, `image_addr` and `image_size` are + required. The Symbolicator may optimize lookups based on the `type` if present. + Valid types are `macho`, `pe`, `elf`. Invalid types are silently ignored. The + Symbolicator still works if the type is invalid, but less efficiently. However, + a schematically valid but _wrong_ type is fatal for finding symbols. - `threads`: A list of process threads to symbolicate. - - `registers`: Optional register values aiding symbolication heuristics. For - example, register values may be used to perform correction heuristics on the - instruction address of the top frame. - - `frames`: A list of frames with addresses. Arbitrary additional properties - may be passed with frames, but are discarded. + - `registers`: Optional register values aiding symbolication heuristics. For + example, register values may be used to perform correction heuristics on the + instruction address of the top frame. + - `frames`: A list of frames with addresses. Arbitrary additional properties + may be passed with frames, but are discarded. The `addr_mode` property + defines the beahvior of `instruction_addr`. ## Response diff --git a/src/actors/symbolication.rs b/src/actors/symbolication.rs index 5b348de01..936112af0 100644 --- a/src/actors/symbolication.rs +++ b/src/actors/symbolication.rs @@ -42,6 +42,7 @@ use crate::types::{ ObjectFileStatus, ObjectId, ObjectType, RawFrame, RawObjectInfo, RawStacktrace, Registers, RequestId, Scope, Signal, SymbolicatedFrame, SymbolicationResponse, SystemInfo, }; +use crate::utils::addr::AddrMode; use crate::utils::futures::{CallOnDrop, ThreadPool}; use crate::utils::hex::HexValue; use crate::utils::sentry::SentryFutureExt as _; @@ -97,6 +98,39 @@ type ComputationChannel = Shared>>; +#[derive(Debug, Clone)] +pub struct SymCacheLookupResult<'a> { + module_index: usize, + object_info: &'a CompleteObjectInfo, + symcache: Option<&'a SymCacheFile>, + relative_addr: Option, +} + +impl<'a> SymCacheLookupResult<'a> { + /// The preferred addr mode for this lookup. + /// + /// for the symbolicated frame we generally switch to absolute reporting of + /// addresses. This is not done for images mounted at `0x0`. This is done + /// because for instance WASM does not have a unified address space and so + /// it's not possible for us to absolutize addresses. + pub fn preferred_addr_mode(&self) -> AddrMode { + if self.object_info.supports_absolute_addresses() { + AddrMode::Abs + } else { + AddrMode::Rel(self.module_index) + } + } + + /// Exposes an address consistent with `preferred_addr_mode`. + pub fn expose_preferred_addr(&self, addr: u64) -> u64 { + if self.object_info.supports_absolute_addresses() { + self.object_info.rel_to_abs_addr(addr).unwrap_or(0) + } else { + addr + } + } +} + /// A builder for the modules list of the symbolication response. /// /// On several platforms, the list of code modules can include shared memory regions or memory @@ -342,8 +376,14 @@ fn object_info_from_minidump_module(ty: ObjectType, module: &CodeModule) -> RawO pub struct SourceObject(SelfCell, Object<'static>>); +struct SourceObjectEntry { + module_index: usize, + object_info: CompleteObjectInfo, + source_object: Option>, +} + struct SourceLookup { - inner: Vec<(CompleteObjectInfo, Option>)>, + inner: Vec, } impl SourceLookup { @@ -359,7 +399,9 @@ impl SourceLookup { for stacktrace in stacktraces { for frame in &stacktrace.frames { - if let Some(i) = self.get_object_index_by_addr(frame.raw.instruction_addr.0) { + if let Some(i) = + self.get_object_index_by_addr(frame.raw.instruction_addr.0, frame.raw.addr_mode) + { referenced_objects.insert(i); } } @@ -367,16 +409,17 @@ impl SourceLookup { let mut futures = Vec::new(); - for (i, (mut object_info, _)) in self.inner.into_iter().enumerate() { - let is_used = referenced_objects.contains(&i); + for mut entry in self.inner.into_iter() { + let is_used = referenced_objects.contains(&entry.module_index); let objects = objects.clone(); let scope = scope.clone(); let sources = sources.clone(); futures.push(async move { if !is_used { - object_info.debug_status = ObjectFileStatus::Unused; - return (object_info, None); + entry.object_info.debug_status = ObjectFileStatus::Unused; + entry.source_object = None; + return entry; } let opt_object_file_meta = objects @@ -384,14 +427,14 @@ impl SourceLookup { filetypes: FileType::sources(), purpose: ObjectPurpose::Source, scope: scope.clone(), - identifier: object_id_from_object_info(&object_info.raw), + identifier: object_id_from_object_info(&entry.object_info.raw), sources, }) .compat() .await .unwrap_or(None); - let object_file_opt = match opt_object_file_meta { + entry.source_object = match opt_object_file_meta { None => None, Some(object_file_meta) => objects .fetch(object_file_meta) @@ -405,11 +448,11 @@ impl SourceLookup { }), }; - if object_file_opt.is_some() { - object_info.features.has_sources = true; + if entry.source_object.is_some() { + entry.object_info.features.has_sources = true; } - (object_info, object_file_opt) + entry }); } @@ -421,7 +464,12 @@ impl SourceLookup { pub fn prepare_debug_sessions(&self) -> Vec>> { self.inner .iter() - .map(|&(_, ref o)| o.as_ref().and_then(|o| o.0.get().debug_session().ok())) + .map(|entry| { + entry + .source_object + .as_ref() + .and_then(|o| o.0.get().debug_session().ok()) + }) .collect() } @@ -429,11 +477,12 @@ impl SourceLookup { &self, debug_sessions: &[Option>], addr: u64, + addr_mode: AddrMode, abs_path: &str, lineno: u32, n: usize, ) -> Option<(Vec, String, Vec)> { - let index = self.get_object_index_by_addr(addr)?; + let index = self.get_object_index_by_addr(addr, addr_mode)?; let session = debug_sessions[index].as_ref()?; let source = session.source_by_path(abs_path).ok()??; @@ -452,40 +501,49 @@ impl SourceLookup { Some((pre_context, context, post_context)) } - fn get_object_index_by_addr(&self, addr: u64) -> Option { - for (i, (info, _)) in self.inner.iter().enumerate() { - let start_addr = info.raw.image_addr.0; + fn get_object_index_by_addr(&self, addr: u64, addr_mode: AddrMode) -> Option { + match addr_mode { + AddrMode::Abs => { + for entry in self.inner.iter() { + let start_addr = entry.object_info.raw.image_addr.0; - if start_addr > addr { - // The debug image starts at a too high address - continue; - } + if start_addr > addr { + // The debug image starts at a too high address + continue; + } - let size = info.raw.image_size.unwrap_or(0); - if let Some(end_addr) = start_addr.checked_add(size) { - if end_addr < addr && size != 0 { - // The debug image ends at a too low address and we're also confident that - // end_addr is accurate (size != 0) - continue; + let size = entry.object_info.raw.image_size.unwrap_or(0); + if let Some(end_addr) = start_addr.checked_add(size) { + if end_addr < addr && size != 0 { + // The debug image ends at a too low address and we're also confident that + // end_addr is accurate (size != 0) + continue; + } + } + + return Some(entry.module_index); } + None } - - return Some(i); + AddrMode::Rel(this_module_index) => self + .inner + .iter() + .find(|x| x.module_index == this_module_index) + .map(|x| x.module_index), } - - None } fn sort(&mut self) { - self.inner.sort_by_key(|(info, _)| info.raw.image_addr.0); + self.inner + .sort_by_key(|entry| entry.object_info.raw.image_addr.0); // Ignore the name `dedup_by`, I just want to iterate over consecutive items and update // some. - self.inner.dedup_by(|(ref info2, _), (ref mut info1, _)| { + self.inner.dedup_by(|entry2, entry1| { // If this underflows we didn't sort properly. - let size = info2.raw.image_addr.0 - info1.raw.image_addr.0; - if info1.raw.image_size.unwrap_or(0) == 0 { - info1.raw.image_size = Some(size); + let size = entry2.object_info.raw.image_addr.0 - entry1.object_info.raw.image_addr.0; + if entry1.object_info.raw.image_size.unwrap_or(0) == 0 { + entry1.object_info.raw.image_size = Some(size); } false @@ -499,15 +557,29 @@ impl FromIterator for SourceLookup { T: IntoIterator, { let mut rv = SourceLookup { - inner: iter.into_iter().map(|x| (x, None)).collect(), + inner: iter + .into_iter() + .enumerate() + .map(|(module_index, object_info)| SourceObjectEntry { + module_index, + object_info, + source_object: None, + }) + .collect(), }; rv.sort(); rv } } +struct SymCacheEntry { + module_index: usize, + object_info: CompleteObjectInfo, + symcache: Option>, +} + struct SymCacheLookup { - inner: Vec<(CompleteObjectInfo, Option>)>, + inner: Vec, } impl FromIterator for SymCacheLookup { @@ -516,7 +588,15 @@ impl FromIterator for SymCacheLookup { T: IntoIterator, { let mut rv = SymCacheLookup { - inner: iter.into_iter().map(|x| (x, None)).collect(), + inner: iter + .into_iter() + .enumerate() + .map(|(module_index, object_info)| SymCacheEntry { + module_index, + object_info, + symcache: None, + }) + .collect(), }; rv.sort(); rv @@ -525,15 +605,16 @@ impl FromIterator for SymCacheLookup { impl SymCacheLookup { fn sort(&mut self) { - self.inner.sort_by_key(|(info, _)| info.raw.image_addr.0); + self.inner + .sort_by_key(|entry| entry.object_info.raw.image_addr.0); // Ignore the name `dedup_by`, I just want to iterate over consecutive items and update // some. - self.inner.dedup_by(|(ref info2, _), (ref mut info1, _)| { + self.inner.dedup_by(|entry2, entry1| { // If this underflows we didn't sort properly. - let size = info2.raw.image_addr.0 - info1.raw.image_addr.0; - if info1.raw.image_size.unwrap_or(0) == 0 { - info1.raw.image_size = Some(size); + let size = entry2.object_info.raw.image_addr.0 - entry1.object_info.raw.image_addr.0; + if entry1.object_info.raw.image_size.unwrap_or(0) == 0 { + entry1.object_info.raw.image_size = Some(size); } false @@ -550,29 +631,31 @@ impl SymCacheLookup { for stacktrace in stacktraces { for frame in stacktrace.frames { - if let Some((i, ..)) = self.lookup_symcache(frame.instruction_addr.0) { - referenced_objects.insert(i); + if let Some(SymCacheLookupResult { module_index, .. }) = + self.lookup_symcache(frame.instruction_addr.0, frame.addr_mode) + { + referenced_objects.insert(module_index); } } } let mut futures = Vec::new(); - for (i, (mut object_info, _)) in self.inner.into_iter().enumerate() { - let is_used = referenced_objects.contains(&i); + for mut entry in self.inner.into_iter() { + let is_used = referenced_objects.contains(&entry.module_index); let sources = request.sources.clone(); let scope = request.scope.clone(); let symcache_actor = symcache_actor.clone(); futures.push(async move { if !is_used { - object_info.debug_status = ObjectFileStatus::Unused; - return (object_info, None); + entry.object_info.debug_status = ObjectFileStatus::Unused; + return entry; } let symcache_result = symcache_actor .fetch(FetchSymCache { - object_type: object_info.raw.ty, - identifier: object_id_from_object_info(&object_info.raw), + object_type: entry.object_info.raw.ty, + identifier: object_id_from_object_info(&entry.object_info.raw), sources, scope, }) @@ -588,15 +671,16 @@ impl SymCacheLookup { Err(e) => (None, (&*e).into()), }; - object_info.arch = Default::default(); + entry.object_info.arch = Default::default(); if let Some(ref symcache) = symcache { - object_info.arch = symcache.arch(); - object_info.features.merge(symcache.features()); + entry.object_info.arch = symcache.arch(); + entry.object_info.features.merge(symcache.features()); } - object_info.debug_status = status; - (object_info, symcache) + entry.symcache = symcache; + entry.object_info.debug_status = status; + entry }); } @@ -605,31 +689,46 @@ impl SymCacheLookup { }) } - fn lookup_symcache( - &self, - addr: u64, - ) -> Option<(usize, &CompleteObjectInfo, Option<&SymCacheFile>)> { - for (i, (info, cache)) in self.inner.iter().enumerate() { - let start_addr = info.raw.image_addr.0; + fn lookup_symcache(&self, addr: u64, addr_mode: AddrMode) -> Option> { + match addr_mode { + AddrMode::Abs => { + for entry in self.inner.iter() { + let start_addr = entry.object_info.raw.image_addr.0; - if start_addr > addr { - // The debug image starts at a too high address - continue; - } + if start_addr > addr { + // The debug image starts at a too high address + continue; + } - let size = info.raw.image_size.unwrap_or(0); - if let Some(end_addr) = start_addr.checked_add(size) { - if end_addr < addr && size != 0 { - // The debug image ends at a too low address and we're also confident that - // end_addr is accurate (size != 0) - continue; + let size = entry.object_info.raw.image_size.unwrap_or(0); + if let Some(end_addr) = start_addr.checked_add(size) { + if end_addr < addr && size != 0 { + // The debug image ends at a too low address and we're also confident that + // end_addr is accurate (size != 0) + continue; + } + } + + return Some(SymCacheLookupResult { + module_index: entry.module_index, + object_info: &entry.object_info, + symcache: entry.symcache.as_deref(), + relative_addr: entry.object_info.abs_to_rel_addr(addr), + }); } + None } - - return Some((i, info, cache.as_ref().map(|x| &**x))); + AddrMode::Rel(this_module_index) => self + .inner + .iter() + .find(|x| x.module_index == this_module_index) + .map(|entry| SymCacheLookupResult { + module_index: entry.module_index, + object_info: &entry.object_info, + symcache: entry.symcache.as_deref(), + relative_addr: Some(addr), + }), } - - None } } @@ -640,54 +739,70 @@ fn symbolicate_frame( frame: &mut RawFrame, index: usize, ) -> Result, FrameStatus> { - let (object_info, symcache) = match caches.lookup_symcache(frame.instruction_addr.0) { - Some((_, info, Some(symcache))) => { - frame.package = info.raw.code_file.clone(); - (info, symcache) - } - Some((_, info, None)) => { - frame.package = info.raw.code_file.clone(); - if info.debug_status == ObjectFileStatus::Malformed { - return Err(FrameStatus::Malformed); - } else { - return Err(FrameStatus::Missing); - } + let lookup_result = caches + .lookup_symcache(frame.instruction_addr.0, frame.addr_mode) + .ok_or(FrameStatus::UnknownImage)?; + + frame.package = lookup_result.object_info.raw.code_file.clone(); + if lookup_result.symcache.is_none() { + if lookup_result.object_info.debug_status == ObjectFileStatus::Malformed { + return Err(FrameStatus::Malformed); + } else { + return Err(FrameStatus::Missing); } - None => return Err(FrameStatus::UnknownImage), - }; + } log::trace!("Loading symcache"); - let symcache = match symcache.parse() { + let symcache = match lookup_result + .symcache + .as_ref() + .expect("symcache should always be available at this point") + .parse() + { Ok(Some(x)) => x, Ok(None) => return Err(FrameStatus::Missing), Err(_) => return Err(FrameStatus::Malformed), }; - let is_crashing_frame = index == 0; - let ip_register_value = if is_crashing_frame { - symcache - .arch() - .cpu_family() - .ip_register_name() - .and_then(|ip_reg_name| registers.get(ip_reg_name)) - .map(|x| x.0) - } else { - None - }; - - let caller_address = InstructionInfo::new(symcache.arch(), frame.instruction_addr.0) - .is_crashing_frame(is_crashing_frame) - .signal(signal.map(|signal| signal.0)) - .ip_register_value(ip_register_value) - .caller_address(); - - let relative_addr = match caller_address.checked_sub(object_info.raw.image_addr.0) { - Some(x) => x, - None => { - log::warn!("Underflow when trying to subtract image start addr from caller address"); - metric!(counter("relative_addr.underflow") += 1); - return Err(FrameStatus::MissingSymbol); + // get the relative caller address + let relative_addr = if let Some(addr) = lookup_result.relative_addr { + // heuristics currently are only supported when we can work with absolute addresses. + // In cases where this is not possible we skip this part entirely and use the relative + // address calculated by the lookup result as lookup address in the module. + if let Some(absolute_addr) = lookup_result.object_info.rel_to_abs_addr(addr) { + let is_crashing_frame = index == 0; + let ip_register_value = if is_crashing_frame { + symcache + .arch() + .cpu_family() + .ip_register_name() + .and_then(|ip_reg_name| registers.get(ip_reg_name)) + .map(|x| x.0) + } else { + None + }; + let absolute_caller_addr = InstructionInfo::new(symcache.arch(), absolute_addr) + .is_crashing_frame(is_crashing_frame) + .signal(signal.map(|signal| signal.0)) + .ip_register_value(ip_register_value) + .caller_address(); + lookup_result + .object_info + .abs_to_rel_addr(absolute_caller_addr) + .ok_or_else(|| { + log::warn!( + "Underflow when trying to subtract image start addr from caller address after heuristics" + ); + metric!(counter("relative_addr.underflow") += 1); + FrameStatus::MissingSymbol + })? + } else { + addr } + } else { + log::warn!("Underflow when trying to subtract image start addr from caller address before heuristics"); + metric!(counter("relative_addr.underflow") += 1); + return Err(FrameStatus::MissingSymbol); }; log::trace!("Symbolicating {:#x}", relative_addr); @@ -750,9 +865,10 @@ fn symbolicate_frame( status: FrameStatus::Symbolicated, original_index: Some(index), raw: RawFrame { - package: object_info.raw.code_file.clone(), + package: lookup_result.object_info.raw.code_file.clone(), + addr_mode: lookup_result.preferred_addr_mode(), instruction_addr: HexValue( - object_info.raw.image_addr.0 + line_info.instruction_address(), + lookup_result.expose_preferred_addr(line_info.instruction_address()), ), symbol: Some(line_info.symbol().to_string()), abs_path: if !abs_path.is_empty() { @@ -774,7 +890,7 @@ fn symbolicate_frame( context_line: None, post_context: vec![], sym_addr: Some(HexValue( - object_info.raw.image_addr.0 + line_info.function_address(), + lookup_result.expose_preferred_addr(line_info.function_address()), )), lang: match line_info.language() { Language::Unknown => None, @@ -942,19 +1058,23 @@ impl SymbolicationActor { .map(|trace| symbolicate_stacktrace(trace, &symcache_lookup, signal)) .collect(); - let modules: Vec<_> = symcache_lookup + let mut modules: Vec<_> = symcache_lookup .inner .into_iter() - .map(|(object_info, _)| { + .map(|entry| { metric!( counter("symbolication.debug_status") += 1, - "status" => object_info.debug_status.name() + "status" => entry.object_info.debug_status.name() ); - object_info + (entry.module_index, entry.object_info) }) .collect(); + // bring modules back into the original order + modules.sort_by_key(|&(index, _)| index); + let modules: Vec<_> = modules.into_iter().map(|(_, module)| module).collect(); + metric!(time_raw("symbolication.num_modules") = modules.len() as u64); metric!(time_raw("symbolication.num_stacktraces") = stacktraces.len() as u64); metric!( @@ -993,6 +1113,7 @@ impl SymbolicationActor { let result = source_lookup.get_context_lines( &debug_sessions, frame.raw.instruction_addr.0, + frame.raw.addr_mode, abs_path, lineno, 5, @@ -1427,7 +1548,7 @@ impl SymbolicationActor { // Start building the module list to be returned in the // symbolication response. For all modules in the minidump we - // create the CompletedObjectInfo and start populating it. + // create the CompleteObjectInfo and start populating it. let mut module_builder = process_state .modules() .into_iter() @@ -2043,10 +2164,10 @@ mod tests { let lookup = SymCacheLookup::from_iter(vec![info.clone()]); - let (a, b, c) = lookup.lookup_symcache(43).unwrap(); - assert_eq!(a, 0); - assert_eq!(b, &info); - assert!(c.is_none()); + let lookup_result = lookup.lookup_symcache(43, AddrMode::Abs).unwrap(); + assert_eq!(lookup_result.module_index, 0); + assert_eq!(lookup_result.object_info, &info); + assert!(lookup_result.symcache.is_none()); } fn create_object_info(has_id: bool, addr: u64, size: Option) -> CompleteObjectInfo { diff --git a/src/sources.rs b/src/sources.rs index e2cdeacc2..f222ec1e1 100644 --- a/src/sources.rs +++ b/src/sources.rs @@ -481,6 +481,10 @@ pub enum FileType { ElfDebug, /// Linux/ELF code files ElfCode, + /// A WASM debug file + WasmDebug, + /// A WASM code file + WasmCode, /// Breakpad files (this is the reason we have a flat enum for what at first sight could've /// been two enums) Breakpad, @@ -501,6 +505,8 @@ impl FileType { Pe, MachCode, ElfCode, + WasmCode, + WasmDebug, Breakpad, SourceBundle, ] @@ -519,6 +525,7 @@ impl FileType { ObjectType::Macho => &[FileType::MachDebug, FileType::MachCode, FileType::Breakpad], ObjectType::Pe => &[FileType::Pdb, FileType::Pe, FileType::Breakpad], ObjectType::Elf => &[FileType::ElfDebug, FileType::ElfCode, FileType::Breakpad], + ObjectType::Wasm => &[FileType::WasmCode, FileType::WasmDebug], _ => Self::all(), } } @@ -533,6 +540,8 @@ impl AsRef for FileType { FileType::MachCode => "mach_code", FileType::ElfDebug => "elf_debug", FileType::ElfCode => "elf_code", + FileType::WasmDebug => "wasm_debug", + FileType::WasmCode => "wasm_code", FileType::Breakpad => "breakpad", FileType::SourceBundle => "sourcebundle", } diff --git a/src/types.rs b/src/types.rs index 93bf4a23f..b89f7f91a 100644 --- a/src/types.rs +++ b/src/types.rs @@ -20,6 +20,7 @@ use symbolic::debuginfo::Object; use symbolic::minidump::processor::FrameTrust; use uuid::Uuid; +use crate::utils::addr::AddrMode; use crate::utils::hex::HexValue; use crate::utils::sentry::WriteSentryScope; @@ -116,15 +117,23 @@ impl fmt::Display for Scope { /// A map of register values. pub type Registers = BTreeMap; -#[allow(clippy::trivially_copy_pass_by_ref)] -fn is_default_frame_trust(trust: &FrameTrust) -> bool { - *trust == FrameTrust::None +fn is_default_value(value: &T) -> bool { + *value == T::default() } /// An unsymbolicated frame from a symbolication request. #[derive(Debug, Default, Clone, Deserialize, Serialize)] pub struct RawFrame { - /// The absolute instruction address of this frame. + /// Controls the addressing mode for `instruction_addr` and + /// `sym_addr`. If not defined it defaults to "abs". Can be + /// set to `"rel:INDEX"` to make the address relative to the + /// module at the given index. + #[serde(default, skip_serializing_if = "is_default_value")] + pub addr_mode: AddrMode, + + /// The instruction address of this frame. + /// + /// See `addr_mode` for the exact behavior of addresses. pub instruction_addr: HexValue, /// The path to the image this frame is located in. @@ -140,6 +149,8 @@ pub struct RawFrame { pub symbol: Option, /// Start address of the function this frame is located in (lower or equal to instruction_addr). + /// + /// See `addr_mode` for the exact behavior of addresses. #[serde(skip_serializing_if = "Option::is_none")] pub sym_addr: Option, @@ -172,7 +183,7 @@ pub struct RawFrame { pub post_context: Vec, /// Information about how the raw frame was created. - #[serde(default, skip_serializing_if = "is_default_frame_trust")] + #[serde(default, skip_serializing_if = "is_default_value")] pub trust: FrameTrust, } @@ -214,6 +225,12 @@ pub struct RawObjectInfo { pub debug_file: Option, /// Absolute address at which the image was mounted into virtual memory. + /// + /// We do allow the image addr to be skipped if it's zero. This is + /// because for instance systems like WASM do not actually require an + /// image to be mounted at a specific address. Per definition an image + /// mounted at 0 does not support absolute addressing. + #[serde(default)] pub image_addr: HexValue, /// Size of the image in virtual memory. @@ -228,6 +245,7 @@ pub enum ObjectType { Elf, Macho, Pe, + Wasm, Unknown, } @@ -239,6 +257,7 @@ impl FromStr for ObjectType { "elf" => ObjectType::Elf, "macho" => ObjectType::Macho, "pe" => ObjectType::Pe, + "wasm" => ObjectType::Wasm, _ => ObjectType::Unknown, }) } @@ -260,6 +279,7 @@ impl fmt::Display for ObjectType { ObjectType::Elf => write!(f, "elf"), ObjectType::Macho => write!(f, "macho"), ObjectType::Pe => write!(f, "pe"), + ObjectType::Wasm => write!(f, "wasm"), ObjectType::Unknown => write!(f, "unknown"), } } @@ -427,6 +447,38 @@ pub struct CompleteObjectInfo { pub raw: RawObjectInfo, } +impl CompleteObjectInfo { + /// Given an absolute address converts it into a relative one. + /// + /// If it does not fit into the object `None` is returned. + pub fn abs_to_rel_addr(&self, addr: u64) -> Option { + if self.supports_absolute_addresses() { + addr.checked_sub(self.raw.image_addr.0) + } else { + None + } + } + + /// Given a relative address returns the absolute address. + /// + /// Certain environments do not support absolute addresses in which + /// case this returns `None`. + pub fn rel_to_abs_addr(&self, addr: u64) -> Option { + if self.supports_absolute_addresses() { + self.raw.image_addr.0.checked_add(addr) + } else { + None + } + } + + /// Checks if this image supports absolute addressing. + /// + /// Per definition images at 0 do not support absolute addresses. + pub fn supports_absolute_addresses(&self) -> bool { + self.raw.image_addr.0 != 0 + } +} + impl From for CompleteObjectInfo { fn from(mut raw: RawObjectInfo) -> Self { raw.debug_id = raw diff --git a/src/utils/addr.rs b/src/utils/addr.rs new file mode 100644 index 000000000..2d5bce376 --- /dev/null +++ b/src/utils/addr.rs @@ -0,0 +1,74 @@ +use std::borrow::Cow; +use std::fmt; +use std::str::FromStr; + +use serde::de::{self, Deserialize, Deserializer}; +use serde::ser::{Serialize, Serializer}; +use thiserror::Error; + +/// Defines the addressing mode. +#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)] +pub enum AddrMode { + /// Declares addresses to be absolute with a shared memory space. + Abs, + /// Declares an address to be relative to an indexed module. + Rel(usize), +} + +impl Default for AddrMode { + fn default() -> AddrMode { + AddrMode::Abs + } +} + +impl fmt::Display for AddrMode { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match *self { + AddrMode::Abs => write!(f, "abs"), + AddrMode::Rel(idx) => write!(f, "rel:{}", idx), + } + } +} + +impl Serialize for AddrMode { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + serializer.serialize_str(&self.to_string()) + } +} + +#[derive(Debug, Error)] +#[error("invalid address mode")] +pub struct ParseAddrModeError; + +impl FromStr for AddrMode { + type Err = ParseAddrModeError; + + fn from_str(s: &str) -> Result { + if s == "abs" { + return Ok(AddrMode::Abs); + } + let mut iter = s.splitn(2, ':'); + let kind = iter.next().ok_or(ParseAddrModeError)?; + let index = iter + .next() + .and_then(|x| x.parse().ok()) + .ok_or(ParseAddrModeError)?; + match kind { + "rel" => Ok(AddrMode::Rel(index)), + _ => Err(ParseAddrModeError), + } + } +} + +impl<'de> Deserialize<'de> for AddrMode { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let s = Cow::::deserialize(deserializer).map_err(de::Error::custom)?; + Ok(AddrMode::from_str(&s).map_err(de::Error::custom)?) + } +} diff --git a/src/utils/mod.rs b/src/utils/mod.rs index 909b80e40..7c473ed7f 100644 --- a/src/utils/mod.rs +++ b/src/utils/mod.rs @@ -1,3 +1,4 @@ +pub mod addr; pub mod futures; pub mod hex; pub mod http; diff --git a/src/utils/paths.rs b/src/utils/paths.rs index f6e8088e0..48e406d2e 100644 --- a/src/utils/paths.rs +++ b/src/utils/paths.rs @@ -99,6 +99,11 @@ fn get_pe_symstore_path(identifier: &ObjectId, ssqp_casing: bool) -> Option Option { + // wasm files never get a breakpad path + if identifier.object_type == ObjectType::Wasm { + return None; + } + let debug_file = identifier.debug_file_basename()?; let debug_id = identifier.debug_id.as_ref()?; @@ -123,8 +128,9 @@ fn get_native_paths(filetype: FileType, identifier: &ObjectId) -> Vec { match filetype { // ELF follows GDB "Build ID Method" conventions. // See: https://sourceware.org/gdb/onlinedocs/gdb/Separate-Debug-Files.html - FileType::ElfCode => get_gdb_path(identifier).into_iter().collect(), - FileType::ElfDebug => { + // We apply the same rule for WASM. + FileType::ElfCode | FileType::WasmCode => get_gdb_path(identifier).into_iter().collect(), + FileType::ElfDebug | FileType::WasmDebug => { if let Some(mut path) = get_gdb_path(identifier) { path.push_str(".debug"); vec![path] @@ -174,7 +180,7 @@ fn get_native_paths(filetype: FileType, identifier: &ObjectId) -> Vec { Some(path) => path, None => return vec![], }, - ObjectType::Elf => match get_gdb_path(identifier) { + ObjectType::Elf | ObjectType::Wasm => match get_gdb_path(identifier) { Some(path) => path, None => return vec![], }, @@ -248,6 +254,9 @@ fn get_symstore_path( // Microsoft SymbolServer does not specify Breakpad. FileType::Breakpad => None, + // Microsoft SymbolServer does not specify WASM. + FileType::WasmDebug | FileType::WasmCode => None, + // source bundles are available through an extension for PE/PDB only. FileType::SourceBundle => { let original_file_type = match identifier.object_type { @@ -293,6 +302,9 @@ fn get_debuginfod_path(filetype: FileType, identifier: &ObjectId) -> Option None, + // WASM is not supported + FileType::WasmDebug | FileType::WasmCode => None, + // Breakpad is not supported FileType::Breakpad => None, @@ -311,6 +323,7 @@ fn get_search_target_object_type(filetype: FileType, identifier: &ObjectId) -> O FileType::Pe | FileType::Pdb => ObjectType::Pe, FileType::MachCode | FileType::MachDebug => ObjectType::Macho, FileType::ElfCode | FileType::ElfDebug => ObjectType::Elf, + FileType::WasmDebug | FileType::WasmCode => ObjectType::Wasm, FileType::SourceBundle | FileType::Breakpad => identifier.object_type, } } @@ -318,8 +331,10 @@ fn get_search_target_object_type(filetype: FileType, identifier: &ObjectId) -> O fn get_unified_path(filetype: FileType, identifier: &ObjectId) -> Option { // determine the suffix and object type let suffix = match filetype { - FileType::ElfCode | FileType::MachCode | FileType::Pe => "executable", - FileType::ElfDebug | FileType::MachDebug | FileType::Pdb => "debuginfo", + FileType::ElfCode | FileType::MachCode | FileType::Pe | FileType::WasmCode => "executable", + FileType::ElfDebug | FileType::MachDebug | FileType::Pdb | FileType::WasmDebug => { + "debuginfo" + } FileType::Breakpad => "breakpad", FileType::SourceBundle => "sourcebundle", }; @@ -332,8 +347,9 @@ fn get_unified_path(filetype: FileType, identifier: &ObjectId) -> Option // we do not want to encode. ObjectType::Pe => Cow::Owned(identifier.debug_id?.breakpad().to_string().to_lowercase()), // On mach we can always determine the code ID from the debug ID if the - // code ID is unavailable. - ObjectType::Macho => { + // code ID is unavailable. We apply the same rule to WASM files as we + // suggest Uuids to be used as build ids. + ObjectType::Macho | ObjectType::Wasm => { if identifier.code_id.is_none() { Cow::Owned(identifier.debug_id?.uuid().to_simple_ref().to_string()) } else { @@ -533,6 +549,13 @@ mod tests { debug_file: Some("/lib/x86_64-linux-gnu/libm-2.23.so".into()), object_type: ObjectType::Elf, }; + static ref WASM_OBJECT_ID: ObjectId = ObjectId { + code_id: Some("67e9247c814e392ba027dbde6748fcbf".parse().unwrap()), + code_file: None, + debug_id: Some("67e9247c-814e-392b-a027-dbde6748fcbf".parse().unwrap()), + debug_file: Some("file://foo.invalid/demo.wasm".into()), + object_type: ObjectType::Wasm, + }; } fn pattern(x: &str) -> Glob { @@ -558,6 +581,9 @@ mod tests { crash/67E9247C814E392BA027DBDE6748FCBF0/crash.src.zip 67E9/247C/814E/392B/A027/DBDE6748FCBF.src.zip "###); + path_test!(FileType::WasmDebug, WASM_OBJECT_ID, @"67/e9247c814e392ba027dbde6748fcbf.debug"); + path_test!(FileType::WasmCode, WASM_OBJECT_ID, @"67/e9247c814e392ba027dbde6748fcbf"); + path_test!(FileType::SourceBundle, WASM_OBJECT_ID, @"67/e9247c814e392ba027dbde6748fcbf.src.zip"); path_test!(FileType::ElfCode, ELF_OBJECT_ID, @"df/b85de42daffd09640c8fe377d572de3e168920"); path_test!(FileType::ElfDebug, ELF_OBJECT_ID, @"df/b85de42daffd09640c8fe377d572de3e168920.debug"); path_test!(FileType::Breakpad, ELF_OBJECT_ID, @"libm-2.23.so/E45DB8DFAF2D09FD640C8FE377D572DE0/libm-2.23.so.sym"); @@ -581,12 +607,15 @@ mod tests { path_test!(FileType::SourceBundle, PE_OBJECT_ID, @"32/49d99d0c4049318610f4e4fb0b69361/sourcebundle"); path_test!(FileType::MachCode, MACHO_OBJECT_ID, @"67/e9247c814e392ba027dbde6748fcbf/executable"); path_test!(FileType::MachDebug, MACHO_OBJECT_ID, @"67/e9247c814e392ba027dbde6748fcbf/debuginfo"); + path_test!(FileType::WasmDebug, WASM_OBJECT_ID, @"67/e9247c814e392ba027dbde6748fcbf/debuginfo"); + path_test!(FileType::WasmCode, WASM_OBJECT_ID, @"67/e9247c814e392ba027dbde6748fcbf/executable"); path_test!(FileType::Breakpad, MACHO_OBJECT_ID, @"67/e9247c814e392ba027dbde6748fcbf/breakpad"); path_test!(FileType::SourceBundle, MACHO_OBJECT_ID, @"67/e9247c814e392ba027dbde6748fcbf/sourcebundle"); path_test!(FileType::ElfCode, ELF_OBJECT_ID, @"df/b85de42daffd09640c8fe377d572de3e168920/executable"); path_test!(FileType::ElfDebug, ELF_OBJECT_ID, @"df/b85de42daffd09640c8fe377d572de3e168920/debuginfo"); path_test!(FileType::Breakpad, ELF_OBJECT_ID, @"df/b85de42daffd09640c8fe377d572de3e168920/breakpad"); path_test!(FileType::SourceBundle, ELF_OBJECT_ID, @"df/b85de42daffd09640c8fe377d572de3e168920/sourcebundle"); + path_test!(FileType::SourceBundle, WASM_OBJECT_ID, @"67/e9247c814e392ba027dbde6748fcbf/sourcebundle"); } #[test] diff --git a/tests/fixtures/symbols/bd/a18fd85d4a4eb893022d6bfad846b1.debug b/tests/fixtures/symbols/bd/a18fd85d4a4eb893022d6bfad846b1.debug new file mode 100755 index 000000000..55739be56 Binary files /dev/null and b/tests/fixtures/symbols/bd/a18fd85d4a4eb893022d6bfad846b1.debug differ diff --git a/tests/integration/conftest.py b/tests/integration/conftest.py index 95330f365..72d310d5d 100644 --- a/tests/integration/conftest.py +++ b/tests/integration/conftest.py @@ -8,7 +8,6 @@ import traceback import uuid -import boto3 import pytest import requests from pytest_localserver.http import WSGIServer @@ -201,6 +200,23 @@ def _handle_path(path, start_response): print(f"status code: {r.status_code}") start_response(f"{r.status_code} BOGUS", list(r.headers.items())) return [r.content] + elif path.startswith("/symbols/"): + print(f"got requested: {path}") + path = path[len("/symbols/") :] + try: + filename = os.path.join( + os.path.dirname(__file__), "..", "fixtures", "symbols", path + ) + with open( + filename, + "rb", + ) as f: + d = f.read() + start_response("200 OK", [("Content-Length", str(len(d)))]) + return [d] + except IOError: + start_response("404 NOT FOUND", []) + return [b""] elif path.startswith("/respond_statuscode/"): statuscode = int(path.split("/")[2]) start_response(f"{statuscode} BOGUS", []) @@ -268,6 +284,8 @@ def s3(pytestconfig): """ if not AWS_ACCESS_KEY_ID or not AWS_SECRET_ACCESS_KEY: fail_missing_secrets("No AWS credentials") + import boto3 + return boto3.resource( "s3", aws_access_key_id=AWS_ACCESS_KEY_ID, diff --git a/tests/integration/test_wasm.py b/tests/integration/test_wasm.py new file mode 100644 index 000000000..d5cc84065 --- /dev/null +++ b/tests/integration/test_wasm.py @@ -0,0 +1,87 @@ +WASM_DATA = { + "stacktraces": [ + { + "registers": {}, + "frames": [ + { + "instruction_addr": "0x8c", + "addr_mode": "rel:0", + } + ], + }, + ], + "modules": [ + { + "type": "wasm", + "debug_id": "bda18fd8-5d4a-4eb8-9302-2d6bfad846b1", + "code_id": "bda18fd85d4a4eb893022d6bfad846b1", + "debug_file": "file://foo.invalid/demo.wasm", + } + ], +} + +SUCCESS_WASM = { + "modules": [ + { + "arch": "wasm", + "code_id": "bda18fd85d4a4eb893022d6bfad846b1", + "debug_file": "file://foo.invalid/demo.wasm", + "debug_id": "bda18fd8-5d4a-4eb8-9302-2d6bfad846b1", + "debug_status": "found", + "features": { + "has_debug_info": True, + "has_sources": False, + "has_symbols": True, + "has_unwind_info": False, + }, + "image_addr": "0x0", + "type": "wasm", + } + ], + "stacktraces": [ + { + "frames": [ + { + "abs_path": "/Users/mitsuhiko/Development/wasm-example/simple/src/lib.rs", + "filename": "src/lib.rs", + "function": "internal_func", + "instruction_addr": "0x8c", + "addr_mode": "rel:0", + "lang": "rust", + "lineno": 19, + "original_index": 0, + "status": "symbolicated", + "sym_addr": "0x8b", + "symbol": "internal_func", + } + ] + } + ], + "status": "completed", +} + + +def test_basic_wasm(symbolicator, hitcounter): + scope = "myscope" + + input = dict( + **WASM_DATA, + sources=[ + { + "type": "http", + "id": "stuff", + "layout": {"type": "native"}, + "filters": {"filetypes": ["wasm_debug"]}, + "url": f"{hitcounter.url}/symbols/", + "is_public": False, + } + ], + ) + + service = symbolicator() + service.wait_healthcheck() + + response = service.post(f"/symbolicate?scope={scope}", json=input) + response.raise_for_status() + + assert response.json() == SUCCESS_WASM