diff --git a/symbolic-debuginfo/src/base.rs b/symbolic-debuginfo/src/base.rs index c3c3164e9..41f00a19d 100644 --- a/symbolic-debuginfo/src/base.rs +++ b/symbolic-debuginfo/src/base.rs @@ -8,15 +8,20 @@ use symbolic_common::{clean_path, join_path, Arch, CodeId, DebugId, Name}; use crate::sourcebundle::SourceFileDescriptor; use crate::ParseObjectOptions; -pub(crate) trait Parse<'data>: Sized { +/// A generic parser trait, used by the various debuginfos. +pub trait Parse<'data>: Sized { + /// The type of error to emit during parsing. type Error; - fn parse_with_opts(data: &'data [u8], _opts: ParseObjectOptions) -> Result; + /// Parse the supplied bytes, with the specified parsing options. + fn parse_with_opts(data: &'data [u8], opts: ParseObjectOptions) -> Result; + /// Parse the supplied bytes with default parsing options. fn parse(data: &'data [u8]) -> Result { Self::parse_with_opts(data, Default::default()) } + /// Returns true if the supplied bytes can be successfully parsed. fn test(data: &'data [u8]) -> bool { Self::parse(data).is_ok() } diff --git a/symbolic-debuginfo/src/breakpad.rs b/symbolic-debuginfo/src/breakpad.rs index ffe9e4fa2..21b746fe1 100644 --- a/symbolic-debuginfo/src/breakpad.rs +++ b/symbolic-debuginfo/src/breakpad.rs @@ -945,7 +945,7 @@ pub struct BreakpadObject<'data> { arch: Arch, module: BreakpadModuleRecord<'data>, data: &'data [u8], - max_inline_depth: Option, + max_inline_depth: u32, } impl<'data> BreakpadObject<'data> { @@ -1270,7 +1270,7 @@ impl<'data> Iterator for BreakpadSymbolIterator<'data> { pub struct BreakpadDebugSession<'data> { file_map: BreakpadFileMap<'data>, lines: Lines<'data>, - max_inline_depth: Option, + max_inline_depth: u32, } impl BreakpadDebugSession<'_> { @@ -1336,15 +1336,11 @@ pub struct BreakpadFunctionIterator<'s> { next_line: Option<&'s [u8]>, inline_origin_map: BreakpadInlineOriginMap<'s>, lines: Lines<'s>, - max_inline_depth: Option, + max_inline_depth: u32, } impl<'s> BreakpadFunctionIterator<'s> { - fn new( - file_map: &'s BreakpadFileMap<'s>, - mut lines: Lines<'s>, - max_inline_depth: Option, - ) -> Self { + fn new(file_map: &'s BreakpadFileMap<'s>, mut lines: Lines<'s>, max_inline_depth: u32) -> Self { let next_line = lines.next(); Self { file_map, @@ -1395,8 +1391,8 @@ impl<'s> Iterator for BreakpadFunctionIterator<'s> { b"", fun_record.address, fun_record.size, - ) - .max_inline_depth(self.max_inline_depth); + self.max_inline_depth, + ); for line in self.lines.by_ref() { // Stop parsing LINE records once other expected records are encountered. diff --git a/symbolic-debuginfo/src/dwarf.rs b/symbolic-debuginfo/src/dwarf.rs index 67dacd623..e6915f0f7 100644 --- a/symbolic-debuginfo/src/dwarf.rs +++ b/symbolic-debuginfo/src/dwarf.rs @@ -494,7 +494,7 @@ impl<'d> UnitRef<'d, '_> { entry: &Die<'d>, language: Language, bcsymbolmap: Option<&'d BcSymbolMap<'d>>, - prior_offset: Option, + depth: u8, ) -> Result>, DwarfError> { let mut fallback_name = None; let mut reference_target = None; @@ -527,21 +527,16 @@ impl<'d> UnitRef<'d, '_> { if let Some(attr) = reference_target { return self.resolve_reference(*attr, |ref_unit, ref_entry| { - // Self-references may have a layer of indirection. Avoid infinite recursion - // in this scenario. - if let Some(prior) = prior_offset { - if self.offset() == ref_unit.offset() && prior == ref_entry.offset() { - return Ok(None); - } + // Avoid infinite recursion. + if depth == 0 { + return Err(DwarfError::new( + DwarfErrorKind::CorruptedData, + "Exceeded maximum resolve_function_name depth", + )); } if self.offset() != ref_unit.offset() || entry.offset() != ref_entry.offset() { - ref_unit.resolve_function_name( - ref_entry, - language, - bcsymbolmap, - Some(entry.offset()), - ) + ref_unit.resolve_function_name(ref_entry, language, bcsymbolmap, depth - 1) } else { Ok(None) } @@ -563,6 +558,9 @@ struct DwarfUnit<'d, 'a> { } impl<'d, 'a> DwarfUnit<'d, 'a> { + /// The maximum depth to recurse to in order to resolve a function name. + const MAX_RESOLVE_FUNCTION_DEPTH: u8 = 32; + /// Creates a DWARF unit from the gimli `Unit` type. fn from_unit( unit: &'a Unit<'d>, @@ -815,6 +813,7 @@ impl<'d, 'a> DwarfUnit<'d, 'a> { fn parse_functions( &self, depth: isize, + remaining_inline_depth: u32, entries: &mut EntriesRaw<'d, '_>, output: &mut FunctionsOutput<'_, 'd>, ) -> Result<(), DwarfError> { @@ -824,9 +823,17 @@ impl<'d, 'a> DwarfUnit<'d, 'a> { if next_depth <= depth { return Ok(()); } + if let Some(abbrev) = entries.read_abbreviation()? { if abbrev.tag() == constants::DW_TAG_subprogram { - self.parse_function(dw_die_offset, next_depth, entries, abbrev, output)?; + self.parse_function( + dw_die_offset, + next_depth, + remaining_inline_depth, + entries, + abbrev, + output, + )?; } else { entries.skip_attributes(abbrev.attributes())?; } @@ -848,6 +855,7 @@ impl<'d, 'a> DwarfUnit<'d, 'a> { &self, dw_die_offset: gimli::UnitOffset, depth: isize, + remaining_inline_depth: u32, entries: &mut EntriesRaw<'d, '_>, abbrev: &gimli::Abbreviation, output: &mut FunctionsOutput<'_, 'd>, @@ -883,7 +891,7 @@ impl<'d, 'a> DwarfUnit<'d, 'a> { // However, non-inlined functions may be present in this subtree, so we must still descend // into it. if ranges.is_empty() { - return self.parse_functions(depth, entries, output); + return self.parse_functions(depth, remaining_inline_depth, entries, output); } // Resolve functions in the symbol table first. Only if there is no entry, fall back @@ -910,7 +918,12 @@ impl<'d, 'a> DwarfUnit<'d, 'a> { let name = symbol_name .or_else(|| { self.inner - .resolve_function_name(&entry, language, self.bcsymbolmap, None) + .resolve_function_name( + &entry, + language, + self.bcsymbolmap, + DwarfUnit::MAX_RESOLVE_FUNCTION_DEPTH, + ) .ok() .flatten() }) @@ -925,12 +938,26 @@ impl<'d, 'a> DwarfUnit<'d, 'a> { let size = range.end - range.begin; ( *range, - FunctionBuilder::new(name.clone(), self.compilation_dir(), address, size), + FunctionBuilder::new( + name.clone(), + self.compilation_dir(), + address, + size, + remaining_inline_depth, + ), ) }) .collect(); - self.parse_function_children(depth, 0, entries, &mut builders, output, language)?; + self.parse_function_children( + depth, + 0, + remaining_inline_depth, + entries, + &mut builders, + output, + language, + )?; if let Some(line_program) = &self.line_program { for (range, builder) in &mut builders { @@ -952,10 +979,12 @@ impl<'d, 'a> DwarfUnit<'d, 'a> { } /// Traverses a subtree during function parsing. + #[allow(clippy::too_many_arguments)] fn parse_function_children( &self, depth: isize, inline_depth: u32, + remaining_inline_depth: u32, entries: &mut EntriesRaw<'d, '_>, builders: &mut [(Range, FunctionBuilder<'d>)], output: &mut FunctionsOutput<'_, 'd>, @@ -974,13 +1003,21 @@ impl<'d, 'a> DwarfUnit<'d, 'a> { match abbrev.tag() { constants::DW_TAG_subprogram => { // Nested subprograms resolve their own language independently. - self.parse_function(dw_die_offset, next_depth, entries, abbrev, output)?; + self.parse_function( + dw_die_offset, + next_depth, + remaining_inline_depth, + entries, + abbrev, + output, + )?; } constants::DW_TAG_inlined_subroutine => { self.parse_inlinee( dw_die_offset, next_depth, inline_depth, + remaining_inline_depth, entries, abbrev, builders, @@ -1010,12 +1047,20 @@ impl<'d, 'a> DwarfUnit<'d, 'a> { dw_die_offset: gimli::UnitOffset, depth: isize, inline_depth: u32, + remaining_inline_depth: u32, entries: &mut EntriesRaw<'d, '_>, abbrev: &gimli::Abbreviation, builders: &mut [(Range, FunctionBuilder<'d>)], output: &mut FunctionsOutput<'_, 'd>, language: Language, ) -> Result<(), DwarfError> { + if remaining_inline_depth == 0 { + return Err(DwarfError::new( + DwarfErrorKind::CorruptedData, + "Exceeded max parse inlinee depth", + )); + } + let (ranges, call_location) = self.parse_ranges(entries, abbrev, &mut output.range_buf)?; ranges.retain(|range| range.end > range.begin); @@ -1028,7 +1073,7 @@ impl<'d, 'a> DwarfUnit<'d, 'a> { // However, non-inlined functions may be present in this subtree, so we must still descend // into it. if ranges.is_empty() { - return self.parse_functions(depth, entries, output); + return self.parse_functions(depth, remaining_inline_depth, entries, output); } let entry = self.inner.unit.entry(dw_die_offset)?; @@ -1039,7 +1084,12 @@ impl<'d, 'a> DwarfUnit<'d, 'a> { // which carries the wrong language (e.g. a C++ LTO partial unit for C code). let name = self .inner - .resolve_function_name(&entry, language, self.bcsymbolmap, None) + .resolve_function_name( + &entry, + language, + self.bcsymbolmap, + DwarfUnit::MAX_RESOLVE_FUNCTION_DEPTH, + ) .ok() .flatten() .unwrap_or_else(|| Name::new("", NameMangling::Unmangled, language)); @@ -1073,17 +1123,26 @@ impl<'d, 'a> DwarfUnit<'d, 'a> { ); } - self.parse_function_children(depth, inline_depth + 1, entries, builders, output, language) + self.parse_function_children( + depth, + inline_depth + 1, + remaining_inline_depth - 1, + entries, + builders, + output, + language, + ) } /// Collects all functions within this compilation unit. fn functions( &self, seen_ranges: &mut BTreeSet<(u64, u64)>, + max_inline_depth: u32, ) -> Result>, DwarfError> { let mut entries = self.inner.unit.entries_raw(None)?; let mut output = FunctionsOutput::with_seen_ranges(seen_ranges); - self.parse_functions(-1, &mut entries, &mut output)?; + self.parse_functions(-1, max_inline_depth, &mut entries, &mut output)?; Ok(output.functions) } } @@ -1407,6 +1466,7 @@ impl std::iter::FusedIterator for DwarfUnitIterator<'_> {} pub struct DwarfDebugSession<'data> { cell: SelfCell>, DwarfInfo<'data>>, bcsymbolmap: Option>>, + max_inline_depth: u32, } impl<'data> DwarfDebugSession<'data> { @@ -1416,6 +1476,7 @@ impl<'data> DwarfDebugSession<'data> { symbol_map: SymbolMap<'data>, address_offset: i64, kind: ObjectKind, + max_inline_depth: u32, ) -> Result where D: Dwarf<'data>, @@ -1428,6 +1489,7 @@ impl<'data> DwarfDebugSession<'data> { Ok(DwarfDebugSession { cell, bcsymbolmap: None, + max_inline_depth, }) } @@ -1456,6 +1518,7 @@ impl<'data> DwarfDebugSession<'data> { functions: Vec::new().into_iter(), seen_ranges: BTreeSet::new(), finished: false, + max_inline_depth: self.max_inline_depth, } } @@ -1566,6 +1629,7 @@ pub struct DwarfFunctionIterator<'s> { functions: std::vec::IntoIter>, seen_ranges: BTreeSet<(u64, u64)>, finished: bool, + max_inline_depth: u32, } impl<'s> Iterator for DwarfFunctionIterator<'s> { @@ -1587,7 +1651,7 @@ impl<'s> Iterator for DwarfFunctionIterator<'s> { None => break, }; - self.functions = match unit.functions(&mut self.seen_ranges) { + self.functions = match unit.functions(&mut self.seen_ranges, self.max_inline_depth) { Ok(functions) => functions.into_iter(), Err(error) => return Some(Err(error)), }; diff --git a/symbolic-debuginfo/src/elf.rs b/symbolic-debuginfo/src/elf.rs index 9a95a25d8..3eb18dcfc 100644 --- a/symbolic-debuginfo/src/elf.rs +++ b/symbolic-debuginfo/src/elf.rs @@ -72,6 +72,7 @@ pub struct ElfObject<'data> { data: &'data [u8], is_malformed: bool, max_decompressed_section_size: Option, + max_inline_depth: u32, } impl<'data> ElfObject<'data> { @@ -171,6 +172,7 @@ impl<'data> ElfObject<'data> { data, is_malformed: true, max_decompressed_section_size: opts.max_decompressed_section_size, + max_inline_depth: opts.max_inline_depth, }); } }; @@ -355,6 +357,7 @@ impl<'data> ElfObject<'data> { data, is_malformed: false, max_decompressed_section_size: opts.max_decompressed_section_size, + max_inline_depth: opts.max_inline_depth, }) } @@ -562,7 +565,13 @@ impl<'data> ElfObject<'data> { /// [`has_debug_info`](struct.ElfObject.html#method.has_debug_info). pub fn debug_session(&self) -> Result, DwarfError> { let symbols = self.symbol_map(); - DwarfDebugSession::parse(self, symbols, self.load_address() as i64, self.kind()) + DwarfDebugSession::parse( + self, + symbols, + self.load_address() as i64, + self.kind(), + self.max_inline_depth, + ) } /// Determines whether this object contains stack unwinding information. diff --git a/symbolic-debuginfo/src/function_builder.rs b/symbolic-debuginfo/src/function_builder.rs index 59b25e363..8de6c56a5 100644 --- a/symbolic-debuginfo/src/function_builder.rs +++ b/symbolic-debuginfo/src/function_builder.rs @@ -29,12 +29,18 @@ pub struct FunctionBuilder<'s> { /// The lines, in any order. They will be sorted in `finish()`. These record specify locations /// at the innermost level of the inline stack at the line record's address. lines: Vec>, - max_inline_depth: Option, + max_inline_depth: u32, } impl<'s> FunctionBuilder<'s> { /// Create a new builder for a given outer function. - pub fn new(name: Name<'s>, compilation_dir: &'s [u8], address: u64, size: u64) -> Self { + pub fn new( + name: Name<'s>, + compilation_dir: &'s [u8], + address: u64, + size: u64, + max_inline_depth: u32, + ) -> Self { Self { name, compilation_dir, @@ -42,18 +48,10 @@ impl<'s> FunctionBuilder<'s> { size, inlinees: BinaryHeap::new(), lines: Vec::new(), - max_inline_depth: None, + max_inline_depth, } } - /// Sets the maximum inline nesting depth to process. - /// - /// Inline records nested deeper than this are dropped. - pub fn max_inline_depth(mut self, max_inline_depth: Option) -> Self { - self.max_inline_depth = max_inline_depth; - self - } - /// Add an inlinee record. This method can be called in any order. /// /// Inlinees which are called directly from the outer function have depth 0. @@ -68,7 +66,7 @@ impl<'s> FunctionBuilder<'s> { ) { // An inlinee that starts before the function is obviously bogus same for an inlinee that // has a depth deeper than the limit. - if address < self.address || depth > self.max_inline_depth.unwrap_or(u32::MAX) { + if address < self.address || depth > self.max_inline_depth { return; } @@ -459,7 +457,7 @@ mod tests { #[test] fn test_simple() { // 0x10 - 0x40: foo in foo.c on line 1 - let mut builder = FunctionBuilder::new(Name::from("foo"), &[], 0x10, 0x30); + let mut builder = FunctionBuilder::new(Name::from("foo"), &[], 0x10, 0x30, 255); builder.add_leaf_line(0x10, Some(0x30), FileInfo::from_filename(b"foo.c"), 1); let func = builder.finish(); @@ -472,7 +470,7 @@ mod tests { // 0x10 - 0x20: foo in foo.c on line 1 // 0x20 - 0x40: bar in bar.c on line 1 // - inlined into: foo in foo.c on line 2 - let mut builder = FunctionBuilder::new(Name::from("foo"), &[], 0x10, 0x30); + let mut builder = FunctionBuilder::new(Name::from("foo"), &[], 0x10, 0x30, 255); builder.add_inlinee( 0, Name::from("bar"), @@ -531,7 +529,7 @@ mod tests { // |----| |----| (parent.c line 1) // |---------| (child2.c line 1) - let mut builder = FunctionBuilder::new(Name::from("parent"), &[], 0x10, 0x40); + let mut builder = FunctionBuilder::new(Name::from("parent"), &[], 0x10, 0x40, 255); builder.add_inlinee( 0, Name::from("child1"), diff --git a/symbolic-debuginfo/src/macho/mod.rs b/symbolic-debuginfo/src/macho/mod.rs index 5c4d131a6..c95c13c2c 100644 --- a/symbolic-debuginfo/src/macho/mod.rs +++ b/symbolic-debuginfo/src/macho/mod.rs @@ -62,6 +62,7 @@ pub struct MachObject<'d> { macho: mach::MachO<'d>, data: &'d [u8], bcsymbolmap: Option>>, + max_inline_depth: u32, } impl<'d> MachObject<'d> { @@ -70,17 +71,6 @@ impl<'d> MachObject<'d> { matches!(MachArchive::is_fat(data), Some(false)) } - /// Tries to parse a MachO from the given slice. - pub fn parse(data: &'d [u8]) -> Result { - mach::MachO::parse(data, 0) - .map(|macho| MachObject { - macho, - data, - bcsymbolmap: None, - }) - .map_err(MachError::new) - } - /// Parses and loads the [`BcSymbolMap`] into the object. /// /// The bitcode symbol map must match the object, there is nothing in the symbol map @@ -93,6 +83,7 @@ impl<'d> MachObject<'d> { /// /// ``` /// use symbolic_debuginfo::macho::{BcSymbolMap, MachObject}; + /// use symbolic_debuginfo::Parse; /// /// // let object_data = std::fs::read("dSYMs/.../Resources/DWARF/object").unwrap(); /// # let object_data = @@ -318,8 +309,13 @@ impl<'d> MachObject<'d> { /// [`has_debug_info`](struct.MachObject.html#method.has_debug_info). pub fn debug_session(&self) -> Result, DwarfError> { let symbols = self.symbol_map(); - let mut session = - DwarfDebugSession::parse(self, symbols, self.load_address() as i64, self.kind())?; + let mut session = DwarfDebugSession::parse( + self, + symbols, + self.load_address() as i64, + self.kind(), + self.max_inline_depth, + )?; session.load_symbolmap(self.bcsymbolmap.clone()); Ok(session) } @@ -386,8 +382,16 @@ impl<'d> Parse<'d> for MachObject<'d> { Self::test(data) } - fn parse_with_opts(data: &'d [u8], _opts: ParseObjectOptions) -> Result { - Self::parse(data) + /// Tries to parse a MachO from the given slice. + fn parse_with_opts(data: &'d [u8], opts: ParseObjectOptions) -> Result { + mach::MachO::parse(data, 0) + .map(|macho| MachObject { + macho, + data, + bcsymbolmap: None, + max_inline_depth: opts.max_inline_depth, + }) + .map_err(MachError::new) } } diff --git a/symbolic-debuginfo/src/object.rs b/symbolic-debuginfo/src/object.rs index c3ced0e4c..9694c3401 100644 --- a/symbolic-debuginfo/src/object.rs +++ b/symbolic-debuginfo/src/object.rs @@ -124,9 +124,13 @@ impl Error for ObjectError { } } +// For reference, macOS Chromium (around July 2026) has a max inlinee depth of around 60, so +// let's double it; 128 ought to be enough for anybody. +const MAX_INLINE_DEPTH_DEFAULT: u32 = 128; + /// Options for parsing object files. #[non_exhaustive] -#[derive(Debug, Clone, Copy, Default)] +#[derive(Debug, Clone, Copy)] pub struct ParseObjectOptions { /// The maximum uncompressed size for compressed debug file sections. /// @@ -139,9 +143,17 @@ pub struct ParseObjectOptions { pub max_decompressed_embedded_source_size: Option, /// The maximum inline nesting depth to process. - /// - /// This is only relevant for Breakpad objects (for the time being). - pub max_inline_depth: Option, + pub max_inline_depth: u32, +} + +impl Default for ParseObjectOptions { + fn default() -> Self { + Self { + max_decompressed_section_size: Default::default(), + max_decompressed_embedded_source_size: Default::default(), + max_inline_depth: MAX_INLINE_DEPTH_DEFAULT, + } + } } /// Tries to infer the object type from the start of the given buffer. diff --git a/symbolic-debuginfo/src/pe.rs b/symbolic-debuginfo/src/pe.rs index 570639b06..962410abb 100644 --- a/symbolic-debuginfo/src/pe.rs +++ b/symbolic-debuginfo/src/pe.rs @@ -70,6 +70,7 @@ pub struct PeObject<'data> { pe: pe::PE<'data>, data: &'data [u8], is_stub: bool, + max_inline_depth: u32, } impl<'data> PeObject<'data> { @@ -82,16 +83,6 @@ impl<'data> PeObject<'data> { ) } - /// Tries to parse a PE object from the given slice. - pub fn parse(data: &'data [u8]) -> Result { - let opts = pe::options::ParseOptions::default() - .with_parse_mode(goblin::pe::options::ParseMode::Permissive) - .with_parse_imports(false); - let pe = pe::PE::parse_with_opts(data, &opts).map_err(PeError::new)?; - let is_stub = is_pe_stub(&pe); - Ok(PeObject { pe, data, is_stub }) - } - /// The container file format, which is always `FileFormat::Pe`. pub fn file_format(&self) -> FileFormat { FileFormat::Pe @@ -251,7 +242,13 @@ impl<'data> PeObject<'data> { /// [`has_debug_info`](struct.PeObject.html#method.has_debug_info). pub fn debug_session(&self) -> Result, DwarfError> { let symbols = self.symbol_map(); - DwarfDebugSession::parse(self, symbols, self.load_address() as i64, self.kind()) + DwarfDebugSession::parse( + self, + symbols, + self.load_address() as i64, + self.kind(), + self.max_inline_depth, + ) } /// Determines whether this object contains stack unwinding information. @@ -397,8 +394,18 @@ impl<'data> Parse<'data> for PeObject<'data> { Self::test(data) } - fn parse_with_opts(data: &'data [u8], _opts: ParseObjectOptions) -> Result { - Self::parse(data) + fn parse_with_opts(data: &'data [u8], popts: ParseObjectOptions) -> Result { + let opts = pe::options::ParseOptions::default() + .with_parse_mode(goblin::pe::options::ParseMode::Permissive) + .with_parse_imports(false); + let pe = pe::PE::parse_with_opts(data, &opts).map_err(PeError::new)?; + let is_stub = is_pe_stub(&pe); + Ok(PeObject { + pe, + data, + is_stub, + max_inline_depth: popts.max_inline_depth, + }) } } diff --git a/symbolic-debuginfo/src/wasm.rs b/symbolic-debuginfo/src/wasm.rs index 5f8d71b98..c9775b5a5 100644 --- a/symbolic-debuginfo/src/wasm.rs +++ b/symbolic-debuginfo/src/wasm.rs @@ -6,8 +6,8 @@ use thiserror::Error; use symbolic_common::{Arch, AsSelf, CodeId, DebugId, Uuid}; +use crate::base::*; use crate::dwarf::{Dwarf, DwarfDebugSession, DwarfError, DwarfSection, Endian}; -use crate::{base::*, ParseObjectOptions}; mod parser; @@ -34,6 +34,7 @@ pub struct WasmObject<'data> { data: &'data [u8], code_offset: u64, kind: ObjectKind, + max_inline_depth: u32, } impl<'data> WasmObject<'data> { @@ -118,7 +119,13 @@ impl<'data> WasmObject<'data> { pub fn debug_session(&self) -> Result, DwarfError> { let symbols = self.symbol_map(); // WASM is offset by the negative offset to the code section instead of the load address - DwarfDebugSession::parse(self, symbols, -(self.code_offset() as i64), self.kind()) + DwarfDebugSession::parse( + self, + symbols, + -(self.code_offset() as i64), + self.kind(), + self.max_inline_depth, + ) } /// Determines whether this object contains stack unwinding information. @@ -174,18 +181,6 @@ impl<'slf, 'd: 'slf> AsSelf<'slf> for WasmObject<'d> { } } -impl<'d> Parse<'d> for WasmObject<'d> { - type Error = WasmError; - - fn test(data: &[u8]) -> bool { - Self::test(data) - } - - fn parse_with_opts(data: &'d [u8], _opts: ParseObjectOptions) -> Result { - Self::parse(data) - } -} - impl<'data: 'object, 'object> ObjectLike<'data, 'object> for WasmObject<'data> { type Error = DwarfError; type Session = DwarfDebugSession<'data>; diff --git a/symbolic-debuginfo/src/wasm/parser.rs b/symbolic-debuginfo/src/wasm/parser.rs index d28b4350b..e672d6587 100644 --- a/symbolic-debuginfo/src/wasm/parser.rs +++ b/symbolic-debuginfo/src/wasm/parser.rs @@ -1,7 +1,11 @@ //! Contains utilities for parsing a WASM module to retrieve the information needed by [`super::WasmObject`] use super::WasmError; -use crate::base::{ObjectKind, Symbol}; +use crate::{ + base::{ObjectKind, Symbol}, + wasm::WasmObject, + Parse, ParseObjectOptions, +}; use wasmparser::{ BinaryReader, CompositeInnerType, FuncValidatorAllocations, NameSectionReader, Payload, TypeRef, Validator, WasmFeatures, @@ -48,9 +52,14 @@ impl BitVec { } } -impl<'data> super::WasmObject<'data> { - /// Tries to parse a WASM from the given slice. - pub fn parse(data: &'data [u8]) -> Result { +impl<'d> Parse<'d> for WasmObject<'d> { + type Error = WasmError; + + fn test(data: &[u8]) -> bool { + Self::test(data) + } + + fn parse_with_opts(data: &'d [u8], popts: ParseObjectOptions) -> Result { let mut code_offset = 0; let mut build_id = None; let mut dwarf_sections = Vec::new(); @@ -224,6 +233,7 @@ impl<'data> super::WasmObject<'data> { data, code_offset, kind, + max_inline_depth: popts.max_inline_depth, }) } } diff --git a/symbolic-debuginfo/tests/fixtures/deep_inline.elf b/symbolic-debuginfo/tests/fixtures/deep_inline.elf new file mode 100644 index 000000000..ed0c27d3e Binary files /dev/null and b/symbolic-debuginfo/tests/fixtures/deep_inline.elf differ diff --git a/symbolic-debuginfo/tests/fixtures/resolve_function_cycle.elf b/symbolic-debuginfo/tests/fixtures/resolve_function_cycle.elf new file mode 100644 index 000000000..85cb78dc4 Binary files /dev/null and b/symbolic-debuginfo/tests/fixtures/resolve_function_cycle.elf differ diff --git a/symbolic-debuginfo/tests/test_objects.rs b/symbolic-debuginfo/tests/test_objects.rs index 8e43e01f6..e145939b8 100644 --- a/symbolic-debuginfo/tests/test_objects.rs +++ b/symbolic-debuginfo/tests/test_objects.rs @@ -5,6 +5,7 @@ use std::io::BufWriter; use symbolic_common::{ByteView, Language}; use symbolic_debuginfo::dwarf::DwarfErrorKind; use symbolic_debuginfo::elf::ElfObject; +use symbolic_debuginfo::Parse; use symbolic_debuginfo::{ pe::PeObject, FileEntry, Function, LineInfo, Object, ParseObjectOptions, SymbolMap, }; diff --git a/symbolic-debuginfo/tests/test_recursion_limits.rs b/symbolic-debuginfo/tests/test_recursion_limits.rs new file mode 100644 index 000000000..76fdd4af8 --- /dev/null +++ b/symbolic-debuginfo/tests/test_recursion_limits.rs @@ -0,0 +1,29 @@ +use std::assert_matches; +use symbolic_debuginfo::Object; + +#[test] +fn test_resolve_function() { + let data = std::fs::read("tests/fixtures/resolve_function_cycle.elf").unwrap(); + + let object = Object::parse(&data).unwrap(); + + let session = object.debug_session().unwrap(); + + let func = session.functions().next().unwrap().unwrap(); + + // The recursion error is swallowed, and the missing function name replaced with + // an empty string. + assert_eq!(func.name, ""); +} + +#[test] +fn test_function_inlining() { + let data = std::fs::read("tests/fixtures/deep_inline.elf").unwrap(); + let object = Object::parse(&data).unwrap(); + + let session = object.debug_session().unwrap(); + + let func = session.functions().next().unwrap(); + + assert_matches!(func, Err(_)); +} diff --git a/symbolic-ppdb/tests/test_ppdb.rs b/symbolic-ppdb/tests/test_ppdb.rs index d46d8afb7..9a5660368 100644 --- a/symbolic-ppdb/tests/test_ppdb.rs +++ b/symbolic-ppdb/tests/test_ppdb.rs @@ -1,6 +1,6 @@ use std::path::Path; -use symbolic_debuginfo::pe::PeObject; +use symbolic_debuginfo::{pe::PeObject, Parse}; use symbolic_ppdb::{EmbeddedSource, PortablePdb}; use symbolic_testutils::fixture; diff --git a/symbolic-symcache/tests/breakpad.rs b/symbolic-symcache/tests/breakpad.rs index 86dab7208..a1e9a6a65 100644 --- a/symbolic-symcache/tests/breakpad.rs +++ b/symbolic-symcache/tests/breakpad.rs @@ -161,7 +161,7 @@ FUNC 1000 2000 0 outer let limit = 512; let mut opts = ParseObjectOptions::default(); - opts.max_inline_depth = Some(limit); + opts.max_inline_depth = limit; let breakpad = Object::parse_with_opts(sym.as_bytes(), opts).unwrap(); let mut buffer = Vec::new();