use std::borrow::Cow;
use std::collections::BTreeMap;
use std::error::Error;
use std::fmt;
use std::ops::Range;
use std::str;
use thiserror::Error;
use symbolic_common::{Arch, AsSelf, CodeId, DebugId, Language, Name, NameMangling};
use crate::base::*;
use crate::function_builder::FunctionBuilder;
use crate::Parse;
#[derive(Clone, Debug)]
struct LineOffsets<'data> {
data: &'data [u8],
finished: bool,
index: usize,
}
impl<'data> LineOffsets<'data> {
#[inline]
fn new(data: &'data [u8]) -> Self {
Self {
data,
finished: false,
index: 0,
}
}
}
impl Default for LineOffsets<'_> {
#[inline]
fn default() -> Self {
Self {
data: &[],
finished: true,
index: 0,
}
}
}
impl<'data> Iterator for LineOffsets<'data> {
type Item = (usize, &'data [u8]);
#[inline]
fn next(&mut self) -> Option<Self::Item> {
if self.finished {
return None;
}
match self.data.iter().position(|b| *b == b'\n') {
None => {
if self.finished {
None
} else {
self.finished = true;
Some((self.index, self.data))
}
}
Some(index) => {
let mut data = &self.data[..index];
if index > 0 && data[index - 1] == b'\r' {
data = &data[..index - 1];
}
let item = Some((self.index, data));
self.index += index + 1;
self.data = &self.data[index + 1..];
item
}
}
}
#[inline]
fn size_hint(&self) -> (usize, Option<usize>) {
if self.finished {
(0, Some(0))
} else {
(1, Some(self.data.len() + 1))
}
}
}
impl std::iter::FusedIterator for LineOffsets<'_> {}
#[allow(missing_docs)]
#[derive(Clone, Debug, Default)]
pub struct Lines<'data>(LineOffsets<'data>);
impl<'data> Lines<'data> {
#[inline]
#[allow(missing_docs)]
pub fn new(data: &'data [u8]) -> Self {
Self(LineOffsets::new(data))
}
}
impl<'data> Iterator for Lines<'data> {
type Item = &'data [u8];
fn next(&mut self) -> Option<Self::Item> {
self.0.next().map(|tup| tup.1)
}
fn size_hint(&self) -> (usize, Option<usize>) {
self.0.size_hint()
}
}
impl std::iter::FusedIterator for Lines<'_> {}
const BREAKPAD_HEADER_CAP: usize = 320;
const UNKNOWN_NAME: &str = "<unknown>";
#[non_exhaustive]
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum BreakpadErrorKind {
InvalidMagic,
BadEncoding,
#[deprecated(note = "This is now covered by the Parse variant")]
BadSyntax,
Parse(&'static str),
InvalidModuleId,
InvalidArchitecture,
}
impl fmt::Display for BreakpadErrorKind {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::InvalidMagic => write!(f, "missing breakpad symbol header"),
Self::BadEncoding => write!(f, "bad utf-8 sequence"),
Self::Parse(_) => write!(f, "parsing error"),
Self::InvalidModuleId => write!(f, "invalid module id"),
Self::InvalidArchitecture => write!(f, "invalid architecture"),
_ => Ok(()),
}
}
}
#[derive(Debug, Error)]
#[error("{kind}")]
pub struct BreakpadError {
kind: BreakpadErrorKind,
#[source]
source: Option<Box<dyn Error + Send + Sync + 'static>>,
}
impl BreakpadError {
fn new<E>(kind: BreakpadErrorKind, source: E) -> Self
where
E: Into<Box<dyn Error + Send + Sync>>,
{
let source = Some(source.into());
Self { kind, source }
}
pub fn kind(&self) -> BreakpadErrorKind {
self.kind
}
}
impl From<BreakpadErrorKind> for BreakpadError {
fn from(kind: BreakpadErrorKind) -> Self {
Self { kind, source: None }
}
}
impl From<str::Utf8Error> for BreakpadError {
fn from(e: str::Utf8Error) -> Self {
Self::new(BreakpadErrorKind::BadEncoding, e)
}
}
impl From<parsing::ParseBreakpadError> for BreakpadError {
fn from(e: parsing::ParseBreakpadError) -> Self {
Self::new(BreakpadErrorKind::Parse(""), e)
}
}
#[derive(Clone, Debug, Default, Eq, PartialEq)]
pub struct BreakpadModuleRecord<'d> {
pub os: &'d str,
pub arch: &'d str,
pub id: &'d str,
pub name: &'d str,
}
impl<'d> BreakpadModuleRecord<'d> {
pub fn parse(data: &'d [u8]) -> Result<Self, BreakpadError> {
let string = str::from_utf8(data)?;
Ok(parsing::module_record_final(string.trim())?)
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum BreakpadInfoRecord<'d> {
CodeId {
code_id: &'d str,
code_file: &'d str,
},
Other {
scope: &'d str,
info: &'d str,
},
}
impl<'d> BreakpadInfoRecord<'d> {
pub fn parse(data: &'d [u8]) -> Result<Self, BreakpadError> {
let string = str::from_utf8(data)?;
Ok(parsing::info_record_final(string.trim())?)
}
}
#[derive(Clone, Debug)]
pub struct BreakpadInfoRecords<'d> {
lines: Lines<'d>,
finished: bool,
}
impl<'d> Iterator for BreakpadInfoRecords<'d> {
type Item = Result<BreakpadInfoRecord<'d>, BreakpadError>;
fn next(&mut self) -> Option<Self::Item> {
if self.finished {
return None;
}
for line in &mut self.lines {
if line.starts_with(b"MODULE ") {
continue;
}
if !line.starts_with(b"INFO ") {
break;
}
return Some(BreakpadInfoRecord::parse(line));
}
self.finished = true;
None
}
}
#[derive(Clone, Debug, Default, Eq, PartialEq)]
pub struct BreakpadFileRecord<'d> {
pub id: u64,
pub name: &'d str,
}
impl<'d> BreakpadFileRecord<'d> {
pub fn parse(data: &'d [u8]) -> Result<Self, BreakpadError> {
let string = str::from_utf8(data)?;
Ok(parsing::file_record_final(string.trim())?)
}
}
#[derive(Clone, Debug)]
pub struct BreakpadFileRecords<'d> {
lines: Lines<'d>,
finished: bool,
}
impl<'d> Iterator for BreakpadFileRecords<'d> {
type Item = Result<BreakpadFileRecord<'d>, BreakpadError>;
fn next(&mut self) -> Option<Self::Item> {
if self.finished {
return None;
}
for line in &mut self.lines {
if line.starts_with(b"MODULE ") || line.starts_with(b"INFO ") {
continue;
}
if !line.starts_with(b"FILE ") {
break;
}
return Some(BreakpadFileRecord::parse(line));
}
self.finished = true;
None
}
}
pub type BreakpadFileMap<'d> = BTreeMap<u64, &'d str>;
#[derive(Clone, Debug, Default, Eq, PartialEq)]
pub struct BreakpadInlineOriginRecord<'d> {
pub id: u64,
pub name: &'d str,
}
impl<'d> BreakpadInlineOriginRecord<'d> {
pub fn parse(data: &'d [u8]) -> Result<Self, BreakpadError> {
let string = str::from_utf8(data)?;
Ok(parsing::inline_origin_record_final(string.trim())?)
}
}
pub type BreakpadInlineOriginMap<'d> = BTreeMap<u64, &'d str>;
#[derive(Clone, Debug, Default, Eq, PartialEq)]
pub struct BreakpadPublicRecord<'d> {
pub multiple: bool,
pub address: u64,
pub parameter_size: u64,
pub name: &'d str,
}
impl<'d> BreakpadPublicRecord<'d> {
pub fn parse(data: &'d [u8]) -> Result<Self, BreakpadError> {
let string = str::from_utf8(data)?;
Ok(parsing::public_record_final(string.trim())?)
}
}
#[derive(Clone, Debug)]
pub struct BreakpadPublicRecords<'d> {
lines: Lines<'d>,
finished: bool,
}
impl<'d> Iterator for BreakpadPublicRecords<'d> {
type Item = Result<BreakpadPublicRecord<'d>, BreakpadError>;
fn next(&mut self) -> Option<Self::Item> {
if self.finished {
return None;
}
for line in &mut self.lines {
if line.starts_with(b"STACK ") {
break;
}
if !line.starts_with(b"PUBLIC ") {
continue;
}
return Some(BreakpadPublicRecord::parse(line));
}
self.finished = true;
None
}
}
#[derive(Clone, Default)]
pub struct BreakpadFuncRecord<'d> {
pub multiple: bool,
pub address: u64,
pub size: u64,
pub parameter_size: u64,
pub name: &'d str,
lines: Lines<'d>,
}
impl<'d> BreakpadFuncRecord<'d> {
pub fn parse(data: &'d [u8], lines: Lines<'d>) -> Result<Self, BreakpadError> {
let string = str::from_utf8(data)?;
let mut record = parsing::func_record_final(string.trim())?;
record.lines = lines;
Ok(record)
}
pub fn lines(&self) -> BreakpadLineRecords<'d> {
BreakpadLineRecords {
lines: self.lines.clone(),
finished: false,
}
}
pub fn range(&self) -> Range<u64> {
self.address..self.address + self.size
}
}
impl PartialEq for BreakpadFuncRecord<'_> {
fn eq(&self, other: &BreakpadFuncRecord<'_>) -> bool {
self.multiple == other.multiple
&& self.address == other.address
&& self.size == other.size
&& self.parameter_size == other.parameter_size
&& self.name == other.name
}
}
impl Eq for BreakpadFuncRecord<'_> {}
impl fmt::Debug for BreakpadFuncRecord<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("BreakpadFuncRecord")
.field("multiple", &self.multiple)
.field("address", &self.address)
.field("size", &self.size)
.field("parameter_size", &self.parameter_size)
.field("name", &self.name)
.finish()
}
}
#[derive(Clone, Debug)]
pub struct BreakpadFuncRecords<'d> {
lines: Lines<'d>,
finished: bool,
}
impl<'d> Iterator for BreakpadFuncRecords<'d> {
type Item = Result<BreakpadFuncRecord<'d>, BreakpadError>;
fn next(&mut self) -> Option<Self::Item> {
if self.finished {
return None;
}
for line in &mut self.lines {
if line.starts_with(b"STACK ") {
break;
}
if !line.starts_with(b"FUNC ") {
continue;
}
return Some(BreakpadFuncRecord::parse(line, self.lines.clone()));
}
self.finished = true;
None
}
}
#[derive(Clone, Debug, Default, Eq, PartialEq)]
pub struct BreakpadLineRecord {
pub address: u64,
pub size: u64,
pub line: u64,
pub file_id: u64,
}
impl BreakpadLineRecord {
pub fn parse(data: &[u8]) -> Result<Self, BreakpadError> {
let string = str::from_utf8(data)?;
Ok(parsing::line_record_final(string.trim())?)
}
pub fn filename<'d>(&self, file_map: &BreakpadFileMap<'d>) -> Option<&'d str> {
file_map.get(&self.file_id).cloned()
}
pub fn range(&self) -> Range<u64> {
self.address..self.address + self.size
}
}
#[derive(Clone, Debug)]
pub struct BreakpadLineRecords<'d> {
lines: Lines<'d>,
finished: bool,
}
impl<'d> Iterator for BreakpadLineRecords<'d> {
type Item = Result<BreakpadLineRecord, BreakpadError>;
fn next(&mut self) -> Option<Self::Item> {
if self.finished {
return None;
}
for line in &mut self.lines {
if line.starts_with(b"FUNC ")
|| line.starts_with(b"PUBLIC ")
|| line.starts_with(b"STACK ")
{
break;
}
if line.is_empty() {
continue;
}
let record = match BreakpadLineRecord::parse(line) {
Ok(record) => record,
Err(error) => return Some(Err(error)),
};
if record.size > 0 {
return Some(Ok(record));
}
}
self.finished = true;
None
}
}
#[derive(Clone, Debug, Default, Eq, PartialEq)]
pub struct BreakpadInlineRecord {
pub inline_depth: u64,
pub call_site_line: u64,
pub call_site_file_id: u64,
pub origin_id: u64,
pub address_ranges: Vec<BreakpadInlineAddressRange>,
}
impl BreakpadInlineRecord {
pub fn parse(data: &[u8]) -> Result<Self, BreakpadError> {
let string = str::from_utf8(data)?;
Ok(parsing::inline_record_final(string.trim())?)
}
}
#[derive(Clone, Debug, Default, Eq, PartialEq)]
pub struct BreakpadInlineAddressRange {
pub address: u64,
pub size: u64,
}
impl BreakpadInlineAddressRange {
pub fn range(&self) -> Range<u64> {
self.address..self.address + self.size
}
}
#[derive(Clone, Debug, Eq, PartialEq, Default)]
pub struct BreakpadStackCfiDeltaRecord<'d> {
pub address: u64,
pub rules: &'d str,
}
impl<'d> BreakpadStackCfiDeltaRecord<'d> {
pub fn parse(data: &'d [u8]) -> Result<Self, BreakpadError> {
let string = str::from_utf8(data)?;
Ok(parsing::stack_cfi_delta_record_final(string.trim())?)
}
}
#[derive(Clone, Debug, Default)]
pub struct BreakpadStackCfiRecord<'d> {
pub start: u64,
pub size: u64,
pub init_rules: &'d str,
deltas: Lines<'d>,
}
impl<'d> BreakpadStackCfiRecord<'d> {
pub fn parse(data: &'d [u8]) -> Result<Self, BreakpadError> {
let string = str::from_utf8(data)?;
Ok(parsing::stack_cfi_record_final(string.trim())?)
}
pub fn deltas(&self) -> BreakpadStackCfiDeltaRecords<'d> {
BreakpadStackCfiDeltaRecords {
lines: self.deltas.clone(),
}
}
pub fn range(&self) -> Range<u64> {
self.start..self.start + self.size
}
}
impl<'d> PartialEq for BreakpadStackCfiRecord<'d> {
fn eq(&self, other: &Self) -> bool {
self.start == other.start && self.size == other.size && self.init_rules == other.init_rules
}
}
impl<'d> Eq for BreakpadStackCfiRecord<'d> {}
#[derive(Clone, Debug, Default)]
pub struct BreakpadStackCfiDeltaRecords<'d> {
lines: Lines<'d>,
}
impl<'d> Iterator for BreakpadStackCfiDeltaRecords<'d> {
type Item = Result<BreakpadStackCfiDeltaRecord<'d>, BreakpadError>;
fn next(&mut self) -> Option<Self::Item> {
if let Some(line) = self.lines.next() {
if line.starts_with(b"STACK CFI INIT") || !line.starts_with(b"STACK CFI") {
self.lines = Lines::default();
} else {
return Some(BreakpadStackCfiDeltaRecord::parse(line));
}
}
None
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum BreakpadStackWinRecordType {
Fpo = 0,
Trap = 1,
Tss = 2,
Standard = 3,
FrameData = 4,
Unknown = -1,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct BreakpadStackWinRecord<'d> {
pub ty: BreakpadStackWinRecordType,
pub code_start: u32,
pub code_size: u32,
pub prolog_size: u16,
pub epilog_size: u16,
pub params_size: u32,
pub saved_regs_size: u16,
pub locals_size: u32,
pub max_stack_size: u32,
pub uses_base_pointer: bool,
pub program_string: Option<&'d str>,
}
impl<'d> BreakpadStackWinRecord<'d> {
pub fn parse(data: &'d [u8]) -> Result<Self, BreakpadError> {
let string = str::from_utf8(data)?;
Ok(parsing::stack_win_record_final(string.trim())?)
}
pub fn code_range(&self) -> Range<u32> {
self.code_start..self.code_start + self.code_size
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum BreakpadStackRecord<'d> {
Cfi(BreakpadStackCfiRecord<'d>),
Win(BreakpadStackWinRecord<'d>),
}
impl<'d> BreakpadStackRecord<'d> {
pub fn parse(data: &'d [u8]) -> Result<Self, BreakpadError> {
let string = str::from_utf8(data)?;
Ok(parsing::stack_record_final(string.trim())?)
}
}
#[derive(Clone, Debug)]
pub struct BreakpadStackRecords<'d> {
lines: Lines<'d>,
finished: bool,
}
impl<'d> BreakpadStackRecords<'d> {
pub fn new(data: &'d [u8]) -> Self {
Self {
lines: Lines::new(data),
finished: false,
}
}
}
impl<'d> Iterator for BreakpadStackRecords<'d> {
type Item = Result<BreakpadStackRecord<'d>, BreakpadError>;
fn next(&mut self) -> Option<Self::Item> {
if self.finished {
return None;
}
while let Some(line) = self.lines.next() {
if line.starts_with(b"STACK WIN") {
return Some(BreakpadStackRecord::parse(line));
}
if line.starts_with(b"STACK CFI INIT") {
return Some(BreakpadStackCfiRecord::parse(line).map(|mut r| {
r.deltas = self.lines.clone();
BreakpadStackRecord::Cfi(r)
}));
}
}
self.finished = true;
None
}
}
pub struct BreakpadObject<'data> {
id: DebugId,
arch: Arch,
module: BreakpadModuleRecord<'data>,
data: &'data [u8],
}
impl<'data> BreakpadObject<'data> {
pub fn test(data: &[u8]) -> bool {
data.starts_with(b"MODULE ")
}
pub fn parse(data: &'data [u8]) -> Result<Self, BreakpadError> {
let header = if data.len() > BREAKPAD_HEADER_CAP {
match str::from_utf8(&data[..BREAKPAD_HEADER_CAP]) {
Ok(_) => &data[..BREAKPAD_HEADER_CAP],
Err(e) => match e.error_len() {
None => &data[..e.valid_up_to()],
Some(_) => return Err(e.into()),
},
}
} else {
data
};
let first_line = header.split(|b| *b == b'\n').next().unwrap_or_default();
let module = BreakpadModuleRecord::parse(first_line)?;
Ok(BreakpadObject {
id: module
.id
.parse()
.map_err(|_| BreakpadErrorKind::InvalidModuleId)?,
arch: module
.arch
.parse()
.map_err(|_| BreakpadErrorKind::InvalidArchitecture)?,
module,
data,
})
}
pub fn file_format(&self) -> FileFormat {
FileFormat::Breakpad
}
pub fn code_id(&self) -> Option<CodeId> {
for result in self.info_records().flatten() {
if let BreakpadInfoRecord::CodeId { code_id, .. } = result {
if !code_id.is_empty() {
return Some(CodeId::new(code_id.into()));
}
}
}
None
}
pub fn debug_id(&self) -> DebugId {
self.id
}
pub fn arch(&self) -> Arch {
self.arch
}
pub fn name(&self) -> &'data str {
self.module.name
}
pub fn kind(&self) -> ObjectKind {
ObjectKind::Debug
}
pub fn load_address(&self) -> u64 {
0 }
pub fn has_symbols(&self) -> bool {
self.public_records().next().is_some()
}
pub fn symbols(&self) -> BreakpadSymbolIterator<'data> {
BreakpadSymbolIterator {
records: self.public_records(),
}
}
pub fn symbol_map(&self) -> SymbolMap<'data> {
self.symbols().collect()
}
pub fn has_debug_info(&self) -> bool {
self.func_records().next().is_some()
}
pub fn debug_session(&self) -> Result<BreakpadDebugSession<'data>, BreakpadError> {
Ok(BreakpadDebugSession {
file_map: self.file_map(),
lines: Lines::new(self.data),
})
}
pub fn has_unwind_info(&self) -> bool {
self.stack_records().next().is_some()
}
pub fn has_sources(&self) -> bool {
false
}
pub fn is_malformed(&self) -> bool {
false
}
pub fn info_records(&self) -> BreakpadInfoRecords<'data> {
BreakpadInfoRecords {
lines: Lines::new(self.data),
finished: false,
}
}
pub fn file_records(&self) -> BreakpadFileRecords<'data> {
BreakpadFileRecords {
lines: Lines::new(self.data),
finished: false,
}
}
pub fn file_map(&self) -> BreakpadFileMap<'data> {
self.file_records()
.filter_map(Result::ok)
.map(|file| (file.id, file.name))
.collect()
}
pub fn public_records(&self) -> BreakpadPublicRecords<'data> {
BreakpadPublicRecords {
lines: Lines::new(self.data),
finished: false,
}
}
pub fn func_records(&self) -> BreakpadFuncRecords<'data> {
BreakpadFuncRecords {
lines: Lines::new(self.data),
finished: false,
}
}
pub fn stack_records(&self) -> BreakpadStackRecords<'data> {
BreakpadStackRecords {
lines: Lines::new(self.data),
finished: false,
}
}
pub fn data(&self) -> &'data [u8] {
self.data
}
}
impl fmt::Debug for BreakpadObject<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("BreakpadObject")
.field("code_id", &self.code_id())
.field("debug_id", &self.debug_id())
.field("arch", &self.arch())
.field("name", &self.name())
.field("has_symbols", &self.has_symbols())
.field("has_debug_info", &self.has_debug_info())
.field("has_unwind_info", &self.has_unwind_info())
.field("is_malformed", &self.is_malformed())
.finish()
}
}
impl<'slf, 'data: 'slf> AsSelf<'slf> for BreakpadObject<'data> {
type Ref = BreakpadObject<'slf>;
fn as_self(&'slf self) -> &Self::Ref {
self
}
}
impl<'data> Parse<'data> for BreakpadObject<'data> {
type Error = BreakpadError;
fn test(data: &[u8]) -> bool {
Self::test(data)
}
fn parse(data: &'data [u8]) -> Result<Self, BreakpadError> {
Self::parse(data)
}
}
impl<'data: 'object, 'object> ObjectLike<'data, 'object> for BreakpadObject<'data> {
type Error = BreakpadError;
type Session = BreakpadDebugSession<'data>;
type SymbolIterator = BreakpadSymbolIterator<'data>;
fn file_format(&self) -> FileFormat {
self.file_format()
}
fn code_id(&self) -> Option<CodeId> {
self.code_id()
}
fn debug_id(&self) -> DebugId {
self.debug_id()
}
fn arch(&self) -> Arch {
self.arch()
}
fn kind(&self) -> ObjectKind {
self.kind()
}
fn load_address(&self) -> u64 {
self.load_address()
}
fn has_symbols(&self) -> bool {
self.has_symbols()
}
fn symbols(&self) -> Self::SymbolIterator {
self.symbols()
}
fn symbol_map(&self) -> SymbolMap<'data> {
self.symbol_map()
}
fn has_debug_info(&self) -> bool {
self.has_debug_info()
}
fn debug_session(&self) -> Result<Self::Session, Self::Error> {
self.debug_session()
}
fn has_unwind_info(&self) -> bool {
self.has_unwind_info()
}
fn has_sources(&self) -> bool {
self.has_sources()
}
fn is_malformed(&self) -> bool {
self.is_malformed()
}
}
pub struct BreakpadSymbolIterator<'data> {
records: BreakpadPublicRecords<'data>,
}
impl<'data> Iterator for BreakpadSymbolIterator<'data> {
type Item = Symbol<'data>;
fn next(&mut self) -> Option<Self::Item> {
self.records.find_map(Result::ok).map(|record| Symbol {
name: Some(Cow::Borrowed(record.name)),
address: record.address,
size: 0,
})
}
}
pub struct BreakpadDebugSession<'data> {
file_map: BreakpadFileMap<'data>,
lines: Lines<'data>,
}
impl<'data> BreakpadDebugSession<'data> {
pub fn functions(&self) -> BreakpadFunctionIterator<'_> {
BreakpadFunctionIterator::new(&self.file_map, self.lines.clone())
}
pub fn files(&self) -> BreakpadFileIterator<'_> {
BreakpadFileIterator {
files: self.file_map.values(),
}
}
pub fn source_by_path(&self, _path: &str) -> Result<Option<Cow<'_, str>>, BreakpadError> {
Ok(None)
}
}
impl<'data, 'session> DebugSession<'session> for BreakpadDebugSession<'data> {
type Error = BreakpadError;
type FunctionIterator = BreakpadFunctionIterator<'session>;
type FileIterator = BreakpadFileIterator<'session>;
fn functions(&'session self) -> Self::FunctionIterator {
self.functions()
}
fn files(&'session self) -> Self::FileIterator {
self.files()
}
fn source_by_path(&self, path: &str) -> Result<Option<Cow<'_, str>>, Self::Error> {
self.source_by_path(path)
}
}
pub struct BreakpadFileIterator<'s> {
files: std::collections::btree_map::Values<'s, u64, &'s str>,
}
impl<'s> Iterator for BreakpadFileIterator<'s> {
type Item = Result<FileEntry<'s>, BreakpadError>;
fn next(&mut self) -> Option<Self::Item> {
let path = self.files.next()?;
Some(Ok(FileEntry::new(
Cow::default(),
FileInfo::from_path(path.as_bytes()),
)))
}
}
pub struct BreakpadFunctionIterator<'s> {
file_map: &'s BreakpadFileMap<'s>,
next_line: Option<&'s [u8]>,
inline_origin_map: BreakpadInlineOriginMap<'s>,
lines: Lines<'s>,
}
impl<'s> BreakpadFunctionIterator<'s> {
fn new(file_map: &'s BreakpadFileMap<'s>, mut lines: Lines<'s>) -> Self {
let next_line = lines.next();
Self {
file_map,
next_line,
inline_origin_map: Default::default(),
lines,
}
}
}
impl<'s> Iterator for BreakpadFunctionIterator<'s> {
type Item = Result<Function<'s>, BreakpadError>;
fn next(&mut self) -> Option<Self::Item> {
let line = loop {
let line = self.next_line.take()?;
if line.starts_with(b"FUNC ") {
break line;
}
if line.starts_with(b"STACK ") {
return None;
}
if line.starts_with(b"INLINE_ORIGIN ") {
let inline_origin_record = match BreakpadInlineOriginRecord::parse(line) {
Ok(record) => record,
Err(e) => return Some(Err(e)),
};
self.inline_origin_map
.insert(inline_origin_record.id, inline_origin_record.name);
}
self.next_line = self.lines.next();
};
let fun_record = match BreakpadFuncRecord::parse(line, Lines::new(&[])) {
Ok(record) => record,
Err(e) => return Some(Err(e)),
};
let mut builder = FunctionBuilder::new(
Name::new(fun_record.name, NameMangling::Unmangled, Language::Unknown),
b"",
fun_record.address,
fun_record.size,
);
for line in self.lines.by_ref() {
if line.starts_with(b"FUNC ")
|| line.starts_with(b"PUBLIC ")
|| line.starts_with(b"STACK ")
{
self.next_line = Some(line);
break;
}
if line.starts_with(b"INLINE_ORIGIN ") {
let inline_origin_record = match BreakpadInlineOriginRecord::parse(line) {
Ok(record) => record,
Err(e) => return Some(Err(e)),
};
self.inline_origin_map
.insert(inline_origin_record.id, inline_origin_record.name);
continue;
}
if line.starts_with(b"INLINE ") {
let inline_record = match BreakpadInlineRecord::parse(line) {
Ok(record) => record,
Err(e) => return Some(Err(e)),
};
let name = self
.inline_origin_map
.get(&inline_record.origin_id)
.cloned()
.unwrap_or_default();
for address_range in &inline_record.address_ranges {
builder.add_inlinee(
inline_record.inline_depth as u32,
Name::new(name, NameMangling::Unmangled, Language::Unknown),
address_range.address,
address_range.size,
FileInfo::from_path(
self.file_map
.get(&inline_record.call_site_file_id)
.cloned()
.unwrap_or_default()
.as_bytes(),
),
inline_record.call_site_line,
);
}
continue;
}
if line.is_empty() {
continue;
}
let line_record = match BreakpadLineRecord::parse(line) {
Ok(line_record) => line_record,
Err(e) => return Some(Err(e)),
};
if line_record.size == 0 {
continue;
}
let filename = line_record.filename(self.file_map).unwrap_or_default();
builder.add_leaf_line(
line_record.address,
Some(line_record.size),
FileInfo::from_path(filename.as_bytes()),
line_record.line,
);
}
Some(Ok(builder.finish()))
}
}
impl std::iter::FusedIterator for BreakpadFunctionIterator<'_> {}
mod parsing {
use nom::branch::alt;
use nom::bytes::complete::take_while;
use nom::character::complete::{char, hex_digit1, multispace1};
use nom::combinator::{cond, eof, map, rest};
use nom::multi::many1;
use nom::sequence::{pair, tuple};
use nom::{IResult, Parser};
use nom_supreme::error::ErrorTree;
use nom_supreme::final_parser::{Location, RecreateContext};
use nom_supreme::parser_ext::ParserExt;
use nom_supreme::tag::complete::tag;
use super::*;
type ParseResult<'a, T> = IResult<&'a str, T, ErrorTree<&'a str>>;
pub type ParseBreakpadError = ErrorTree<ErrorLine>;
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct ErrorLine {
pub line: String,
pub column: usize,
}
impl<'a> RecreateContext<&'a str> for ErrorLine {
fn recreate_context(original_input: &'a str, tail: &'a str) -> Self {
let Location { column, .. } = Location::recreate_context(original_input, tail);
Self {
line: original_input.to_string(),
column,
}
}
}
impl fmt::Display for ErrorLine {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
if f.alternate() {
writeln!(f)?;
}
write!(f, "\"{}\"", self.line)?;
if f.alternate() {
writeln!(f, "\n{:>width$}", "^", width = self.column + 1)?;
} else {
write!(f, ", column {}", self.column)?;
}
Ok(())
}
}
macro_rules! num_dec {
($ty:ty) => {
nom::character::complete::digit1.map_res(|s: &str| s.parse::<$ty>())
};
}
macro_rules! num_hex {
($ty:ty) => {
nom::character::complete::hex_digit1.map_res(|n| <$ty>::from_str_radix(n, 16))
};
}
fn non_whitespace(input: &str) -> ParseResult<&str> {
take_while(|c: char| !c.is_whitespace())(input)
}
fn name(input: &str) -> ParseResult<&str> {
rest.map(|name: &str| if name.is_empty() { UNKNOWN_NAME } else { name })
.parse(input)
}
fn multiple(input: &str) -> ParseResult<bool> {
let (mut input, multiple) = char('m').opt().parse(input)?;
let multiple = multiple.is_some();
if multiple {
input = multispace1(input)?.0;
}
Ok((input, multiple))
}
fn line_num(input: &str) -> ParseResult<u64> {
pair(char('-').opt(), num_dec!(u64))
.map(|(sign, num)| if sign.is_some() { 0 } else { num })
.parse(input)
}
fn stack_win_record_type(input: &str) -> ParseResult<BreakpadStackWinRecordType> {
alt((
char('0').value(BreakpadStackWinRecordType::Fpo),
char('1').value(BreakpadStackWinRecordType::Trap),
char('2').value(BreakpadStackWinRecordType::Tss),
char('3').value(BreakpadStackWinRecordType::Standard),
char('4').value(BreakpadStackWinRecordType::FrameData),
non_whitespace.value(BreakpadStackWinRecordType::Unknown),
))(input)
}
fn module_record(input: &str) -> ParseResult<BreakpadModuleRecord> {
let (input, _) = tag("MODULE")
.terminated(multispace1)
.context("module record prefix")
.parse(input)?;
let (input, (os, arch, id, name)) = tuple((
non_whitespace.terminated(multispace1).context("os"),
non_whitespace.terminated(multispace1).context("arch"),
hex_digit1
.terminated(multispace1.or(eof))
.context("module id"),
name.context("module name"),
))
.cut()
.context("module record body")
.parse(input)?;
Ok((input, BreakpadModuleRecord { os, arch, id, name }))
}
pub fn module_record_final(input: &str) -> Result<BreakpadModuleRecord, ErrorTree<ErrorLine>> {
nom_supreme::final_parser::final_parser(module_record)(input)
}
fn info_code_id_record(input: &str) -> ParseResult<BreakpadInfoRecord> {
let (input, _) = tag("CODE_ID")
.terminated(multispace1)
.context("info code_id record prefix")
.parse(input)?;
let (input, (code_id, code_file)) = pair(
hex_digit1
.terminated(multispace1.or(eof))
.context("code id"),
name.context("file name"),
)
.cut()
.context("info code_id record body")
.parse(input)?;
Ok((input, BreakpadInfoRecord::CodeId { code_id, code_file }))
}
fn info_other_record(input: &str) -> ParseResult<BreakpadInfoRecord> {
let (input, (scope, info)) = pair(
non_whitespace
.terminated(multispace1.or(eof))
.context("info scope"),
rest,
)
.cut()
.context("info other record body")
.parse(input)?;
Ok((input, BreakpadInfoRecord::Other { scope, info }))
}
fn info_record(input: &str) -> ParseResult<BreakpadInfoRecord> {
let (input, _) = tag("INFO")
.terminated(multispace1)
.context("info record prefix")
.parse(input)?;
info_code_id_record
.or(info_other_record)
.cut()
.context("info record body")
.parse(input)
}
pub fn info_record_final(input: &str) -> Result<BreakpadInfoRecord, ErrorTree<ErrorLine>> {
nom_supreme::final_parser::final_parser(info_record)(input)
}
fn file_record(input: &str) -> ParseResult<BreakpadFileRecord> {
let (input, _) = tag("FILE")
.terminated(multispace1)
.context("file record prefix")
.parse(input)?;
let (input, (id, name)) = pair(
num_dec!(u64)
.terminated(multispace1.or(eof))
.context("file id"),
rest.context("file name"),
)
.cut()
.context("file record body")
.parse(input)?;
Ok((input, BreakpadFileRecord { id, name }))
}
pub fn file_record_final(input: &str) -> Result<BreakpadFileRecord, ErrorTree<ErrorLine>> {
nom_supreme::final_parser::final_parser(file_record)(input)
}
fn inline_origin_record(input: &str) -> ParseResult<BreakpadInlineOriginRecord> {
let (input, _) = tag("INLINE_ORIGIN")
.terminated(multispace1)
.context("inline origin record prefix")
.parse(input)?;
let (input, (id, name)) = pair(
num_dec!(u64)
.terminated(multispace1)
.context("inline origin id"),
rest.context("inline origin name"),
)
.cut()
.context("inline origin record body")
.parse(input)?;
Ok((input, BreakpadInlineOriginRecord { id, name }))
}
pub fn inline_origin_record_final(
input: &str,
) -> Result<BreakpadInlineOriginRecord, ErrorTree<ErrorLine>> {
nom_supreme::final_parser::final_parser(inline_origin_record)(input)
}
fn public_record(input: &str) -> ParseResult<BreakpadPublicRecord> {
let (input, _) = tag("PUBLIC")
.terminated(multispace1)
.context("public record prefix")
.parse(input)?;
let (input, (multiple, address, parameter_size, name)) = tuple((
multiple.context("multiple flag"),
num_hex!(u64).terminated(multispace1).context("address"),
num_hex!(u64)
.terminated(multispace1.or(eof))
.context("param size"),
name.context("symbol name"),
))
.cut()
.context("public record body")
.parse(input)?;
Ok((
input,
BreakpadPublicRecord {
multiple,
address,
parameter_size,
name,
},
))
}
pub fn public_record_final(input: &str) -> Result<BreakpadPublicRecord, ErrorTree<ErrorLine>> {
nom_supreme::final_parser::final_parser(public_record)(input)
}
fn func_record(input: &str) -> ParseResult<BreakpadFuncRecord> {
let (input, _) = tag("FUNC")
.terminated(multispace1)
.context("func record prefix")
.parse(input)?;
let (input, (multiple, address, size, parameter_size, name)) = tuple((
multiple.context("multiple flag"),
num_hex!(u64).terminated(multispace1).context("address"),
num_hex!(u64).terminated(multispace1).context("size"),
num_hex!(u64)
.terminated(multispace1.or(eof))
.context("param size"),
name.context("symbol name"),
))
.cut()
.context("func record body")
.parse(input)?;
Ok((
input,
BreakpadFuncRecord {
multiple,
address,
size,
parameter_size,
name,
lines: Lines::default(),
},
))
}
pub fn func_record_final(input: &str) -> Result<BreakpadFuncRecord, ErrorTree<ErrorLine>> {
nom_supreme::final_parser::final_parser(func_record)(input)
}
fn line_record(input: &str) -> ParseResult<BreakpadLineRecord> {
let (input, (address, size, line, file_id)) = tuple((
num_hex!(u64).terminated(multispace1).context("address"),
num_hex!(u64).terminated(multispace1).context("size"),
line_num.terminated(multispace1).context("line number"),
num_dec!(u64).context("file id"),
))
.context("line record")
.parse(input)?;
Ok((
input,
BreakpadLineRecord {
address,
size,
line,
file_id,
},
))
}
pub fn line_record_final(input: &str) -> Result<BreakpadLineRecord, ErrorTree<ErrorLine>> {
nom_supreme::final_parser::final_parser(line_record)(input)
}
fn inline_record(input: &str) -> ParseResult<BreakpadInlineRecord> {
let (input, _) = tag("INLINE")
.terminated(multispace1)
.context("inline record prefix")
.parse(input)?;
let (input, (inline_depth, call_site_line, call_site_file_id, origin_id)) = tuple((
num_dec!(u64)
.terminated(multispace1)
.context("inline_nest_level"),
num_dec!(u64)
.terminated(multispace1)
.context("call_site_line"),
num_dec!(u64)
.terminated(multispace1)
.context("call_site_file_id"),
num_dec!(u64).terminated(multispace1).context("origin_id"),
))
.cut()
.context("func record body")
.parse(input)?;
let (input, address_ranges) = many1(map(
pair(
num_hex!(u64).terminated(multispace1).context("address"),
num_hex!(u64)
.terminated(multispace1.or(eof))
.context("size"),
),
|(address, size)| BreakpadInlineAddressRange { address, size },
))
.cut()
.context("inline record body")
.parse(input)?;
Ok((
input,
BreakpadInlineRecord {
inline_depth,
call_site_line,
call_site_file_id,
origin_id,
address_ranges,
},
))
}
pub fn inline_record_final(input: &str) -> Result<BreakpadInlineRecord, ErrorTree<ErrorLine>> {
nom_supreme::final_parser::final_parser(inline_record)(input)
}
fn stack_cfi_delta_record(input: &str) -> ParseResult<BreakpadStackCfiDeltaRecord> {
let (input, _) = tag("STACK CFI")
.terminated(multispace1)
.context("stack cfi prefix")
.parse(input)?;
let (input, (address, rules)) = pair(
num_hex!(u64).terminated(multispace1).context("address"),
rest.context("rules"),
)
.cut()
.context("stack cfi delta record body")
.parse(input)?;
Ok((input, BreakpadStackCfiDeltaRecord { address, rules }))
}
pub fn stack_cfi_delta_record_final(
input: &str,
) -> Result<BreakpadStackCfiDeltaRecord, ErrorTree<ErrorLine>> {
nom_supreme::final_parser::final_parser(stack_cfi_delta_record)(input)
}
fn stack_cfi_record(input: &str) -> ParseResult<BreakpadStackCfiRecord> {
let (input, _) = tag("STACK CFI INIT")
.terminated(multispace1)
.context("stack cfi init prefix")
.parse(input)?;
let (input, (start, size, init_rules)) = tuple((
num_hex!(u64).terminated(multispace1).context("start"),
num_hex!(u64).terminated(multispace1).context("size"),
rest.context("rules"),
))
.cut()
.context("stack cfi record body")
.parse(input)?;
Ok((
input,
BreakpadStackCfiRecord {
start,
size,
init_rules,
deltas: Lines::default(),
},
))
}
pub fn stack_cfi_record_final(
input: &str,
) -> Result<BreakpadStackCfiRecord, ErrorTree<ErrorLine>> {
nom_supreme::final_parser::final_parser(stack_cfi_record)(input)
}
fn stack_win_record(input: &str) -> ParseResult<BreakpadStackWinRecord> {
let (input, _) = tag("STACK WIN")
.terminated(multispace1)
.context("stack win prefix")
.parse(input)?;
let (
input,
(
ty,
code_start,
code_size,
prolog_size,
epilog_size,
params_size,
saved_regs_size,
locals_size,
max_stack_size,
has_program_string,
),
) = tuple((
stack_win_record_type
.terminated(multispace1)
.context("record type"),
num_hex!(u32).terminated(multispace1).context("code start"),
num_hex!(u32).terminated(multispace1).context("code size"),
num_hex!(u16).terminated(multispace1).context("prolog size"),
num_hex!(u16).terminated(multispace1).context("epilog size"),
num_hex!(u32).terminated(multispace1).context("params size"),
num_hex!(u16)
.terminated(multispace1)
.context("saved regs size"),
num_hex!(u32).terminated(multispace1).context("locals size"),
num_hex!(u32)
.terminated(multispace1)
.context("max stack size"),
non_whitespace
.map(|s| s != "0")
.terminated(multispace1)
.context("has_program_string"),
))
.cut()
.context("stack win record body")
.parse(input)?;
let (input, program_string) =
cond(has_program_string, rest.context("program string"))(input)?;
let (input, uses_base_pointer) =
cond(!has_program_string, non_whitespace.map(|s| s != "0"))
.map(|o| o.unwrap_or(false))
.parse(input)?;
Ok((
input,
BreakpadStackWinRecord {
ty,
code_start,
code_size,
prolog_size,
epilog_size,
params_size,
saved_regs_size,
locals_size,
max_stack_size,
uses_base_pointer,
program_string,
},
))
}
pub fn stack_win_record_final(
input: &str,
) -> Result<BreakpadStackWinRecord, ErrorTree<ErrorLine>> {
nom_supreme::final_parser::final_parser(stack_win_record)(input)
}
pub fn stack_record_final(input: &str) -> Result<BreakpadStackRecord, ParseBreakpadError> {
nom_supreme::final_parser::final_parser(alt((
stack_cfi_record.map(BreakpadStackRecord::Cfi),
stack_win_record.map(BreakpadStackRecord::Win),
)))(input)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_parse_module_record() -> Result<(), BreakpadError> {
let string = b"MODULE Linux x86_64 492E2DD23CC306CA9C494EEF1533A3810 crash";
let record = BreakpadModuleRecord::parse(string)?;
insta::assert_debug_snapshot!(record, @r###"
â‹®BreakpadModuleRecord {
â‹® os: "Linux",
â‹® arch: "x86_64",
â‹® id: "492E2DD23CC306CA9C494EEF1533A3810",
â‹® name: "crash",
â‹®}
"###);
Ok(())
}
#[test]
fn test_parse_module_record_short_id() -> Result<(), BreakpadError> {
let string = b"MODULE Linux x86_64 6216C672A8D33EC9CF4A1BAB8B29D00E libdispatch.so";
let record = BreakpadModuleRecord::parse(string)?;
insta::assert_debug_snapshot!(record, @r###"
â‹®BreakpadModuleRecord {
â‹® os: "Linux",
â‹® arch: "x86_64",
â‹® id: "6216C672A8D33EC9CF4A1BAB8B29D00E",
â‹® name: "libdispatch.so",
â‹®}
"###);
Ok(())
}
#[test]
fn test_parse_file_record() -> Result<(), BreakpadError> {
let string = b"FILE 37 /usr/include/libkern/i386/_OSByteOrder.h";
let record = BreakpadFileRecord::parse(string)?;
insta::assert_debug_snapshot!(record, @r###"
â‹®BreakpadFileRecord {
â‹® id: 37,
â‹® name: "/usr/include/libkern/i386/_OSByteOrder.h",
â‹®}
"###);
Ok(())
}
#[test]
fn test_parse_file_record_space() -> Result<(), BreakpadError> {
let string = b"FILE 38 /usr/local/src/filename with spaces.c";
let record = BreakpadFileRecord::parse(string)?;
insta::assert_debug_snapshot!(record, @r###"
â‹®BreakpadFileRecord {
â‹® id: 38,
â‹® name: "/usr/local/src/filename with spaces.c",
â‹®}
"###);
Ok(())
}
#[test]
fn test_parse_inline_origin_record() -> Result<(), BreakpadError> {
let string = b"INLINE_ORIGIN 3529 LZ4F_initStream";
let record = BreakpadInlineOriginRecord::parse(string)?;
insta::assert_debug_snapshot!(record, @r###"
BreakpadInlineOriginRecord {
id: 3529,
name: "LZ4F_initStream",
}
"###);
Ok(())
}
#[test]
fn test_parse_inline_origin_record_space() -> Result<(), BreakpadError> {
let string =
b"INLINE_ORIGIN 3576 unsigned int mozilla::AddToHash<char, 0>(unsigned int, char)";
let record = BreakpadInlineOriginRecord::parse(string)?;
insta::assert_debug_snapshot!(record, @r###"
BreakpadInlineOriginRecord {
id: 3576,
name: "unsigned int mozilla::AddToHash<char, 0>(unsigned int, char)",
}
"###);
Ok(())
}
#[test]
fn test_parse_func_record() -> Result<(), BreakpadError> {
let string = b"FUNC 1730 1a 0 <name omitted>";
let record = BreakpadFuncRecord::parse(string, Lines::default())?;
insta::assert_debug_snapshot!(record, @r###"
â‹®BreakpadFuncRecord {
â‹® multiple: false,
â‹® address: 5936,
â‹® size: 26,
â‹® parameter_size: 0,
â‹® name: "<name omitted>",
â‹®}
"###);
Ok(())
}
#[test]
fn test_parse_func_record_multiple() -> Result<(), BreakpadError> {
let string = b"FUNC m 1730 1a 0 <name omitted>";
let record = BreakpadFuncRecord::parse(string, Lines::default())?;
insta::assert_debug_snapshot!(record, @r###"
â‹®BreakpadFuncRecord {
â‹® multiple: true,
â‹® address: 5936,
â‹® size: 26,
â‹® parameter_size: 0,
â‹® name: "<name omitted>",
â‹®}
"###);
Ok(())
}
#[test]
fn test_parse_func_record_no_name() -> Result<(), BreakpadError> {
let string = b"FUNC 0 f 0";
let record = BreakpadFuncRecord::parse(string, Lines::default())?;
insta::assert_debug_snapshot!(record, @r###"
â‹®BreakpadFuncRecord {
â‹® multiple: false,
â‹® address: 0,
â‹® size: 15,
â‹® parameter_size: 0,
â‹® name: "<unknown>",
â‹®}
"###);
Ok(())
}
#[test]
fn test_parse_line_record() -> Result<(), BreakpadError> {
let string = b"1730 6 93 20";
let record = BreakpadLineRecord::parse(string)?;
insta::assert_debug_snapshot!(record, @r###"
â‹®BreakpadLineRecord {
â‹® address: 5936,
â‹® size: 6,
â‹® line: 93,
â‹® file_id: 20,
â‹®}
"###);
Ok(())
}
#[test]
fn test_parse_line_record_negative_line() -> Result<(), BreakpadError> {
let string = b"e0fd10 5 -376 2225";
let record = BreakpadLineRecord::parse(string)?;
insta::assert_debug_snapshot!(record, @r###"
BreakpadLineRecord {
address: 14744848,
size: 5,
line: 0,
file_id: 2225,
}
"###);
Ok(())
}
#[test]
fn test_parse_line_record_whitespace() -> Result<(), BreakpadError> {
let string = b" 1000 1c 2972 2
";
let record = BreakpadLineRecord::parse(string)?;
insta::assert_debug_snapshot!(
record, @r###"
BreakpadLineRecord {
address: 4096,
size: 28,
line: 2972,
file_id: 2,
}
"###);
Ok(())
}
#[test]
fn test_parse_public_record() -> Result<(), BreakpadError> {
let string = b"PUBLIC 5180 0 __clang_call_terminate";
let record = BreakpadPublicRecord::parse(string)?;
insta::assert_debug_snapshot!(record, @r###"
â‹®BreakpadPublicRecord {
â‹® multiple: false,
â‹® address: 20864,
â‹® parameter_size: 0,
â‹® name: "__clang_call_terminate",
â‹®}
"###);
Ok(())
}
#[test]
fn test_parse_public_record_multiple() -> Result<(), BreakpadError> {
let string = b"PUBLIC m 5180 0 __clang_call_terminate";
let record = BreakpadPublicRecord::parse(string)?;
insta::assert_debug_snapshot!(record, @r###"
â‹®BreakpadPublicRecord {
â‹® multiple: true,
â‹® address: 20864,
â‹® parameter_size: 0,
â‹® name: "__clang_call_terminate",
â‹®}
"###);
Ok(())
}
#[test]
fn test_parse_public_record_no_name() -> Result<(), BreakpadError> {
let string = b"PUBLIC 5180 0";
let record = BreakpadPublicRecord::parse(string)?;
insta::assert_debug_snapshot!(record, @r###"
â‹®BreakpadPublicRecord {
â‹® multiple: false,
â‹® address: 20864,
â‹® parameter_size: 0,
â‹® name: "<unknown>",
â‹®}
"###);
Ok(())
}
#[test]
fn test_parse_inline_record() -> Result<(), BreakpadError> {
let string = b"INLINE 0 3082 52 1410 49200 10";
let record = BreakpadInlineRecord::parse(string)?;
insta::assert_debug_snapshot!(record, @r###"
BreakpadInlineRecord {
inline_depth: 0,
call_site_line: 3082,
call_site_file_id: 52,
origin_id: 1410,
address_ranges: [
BreakpadInlineAddressRange {
address: 299520,
size: 16,
},
],
}
"###);
Ok(())
}
#[test]
fn test_parse_inline_record_multiple() -> Result<(), BreakpadError> {
let string = b"INLINE 6 642 8 207 8b110 18 8b154 18";
let record = BreakpadInlineRecord::parse(string)?;
insta::assert_debug_snapshot!(record, @r###"
BreakpadInlineRecord {
inline_depth: 6,
call_site_line: 642,
call_site_file_id: 8,
origin_id: 207,
address_ranges: [
BreakpadInlineAddressRange {
address: 569616,
size: 24,
},
BreakpadInlineAddressRange {
address: 569684,
size: 24,
},
],
}
"###);
Ok(())
}
#[test]
fn test_parse_inline_record_err_missing_address_range() {
let string = b"INLINE 6 642 8 207";
let record = BreakpadInlineRecord::parse(string);
assert!(record.is_err());
}
#[test]
fn test_parse_stack_cfi_init_record() -> Result<(), BreakpadError> {
let string = b"STACK CFI INIT 1880 2d .cfa: $rsp 8 + .ra: .cfa -8 + ^";
let record = BreakpadStackRecord::parse(string)?;
insta::assert_debug_snapshot!(record, @r###"
Cfi(
BreakpadStackCfiRecord {
start: 6272,
size: 45,
init_rules: ".cfa: $rsp 8 + .ra: .cfa -8 + ^",
deltas: Lines(
LineOffsets {
data: [],
finished: true,
index: 0,
},
),
},
)
"###);
Ok(())
}
#[test]
fn test_parse_stack_win_record() -> Result<(), BreakpadError> {
let string =
b"STACK WIN 4 371a c 0 0 0 0 0 0 1 $T0 .raSearch = $eip $T0 ^ = $esp $T0 4 + =";
let record = BreakpadStackRecord::parse(string)?;
insta::assert_debug_snapshot!(record, @r###"
Win(
BreakpadStackWinRecord {
ty: FrameData,
code_start: 14106,
code_size: 12,
prolog_size: 0,
epilog_size: 0,
params_size: 0,
saved_regs_size: 0,
locals_size: 0,
max_stack_size: 0,
uses_base_pointer: false,
program_string: Some(
"$T0 .raSearch = $eip $T0 ^ = $esp $T0 4 + =",
),
},
)
"###);
Ok(())
}
#[test]
fn test_parse_stack_win_record_type_3() -> Result<(), BreakpadError> {
let string = b"STACK WIN 3 8a10b ec b 0 c c 4 0 0 1";
let record = BreakpadStackWinRecord::parse(string)?;
insta::assert_debug_snapshot!(record, @r###"
BreakpadStackWinRecord {
ty: Standard,
code_start: 565515,
code_size: 236,
prolog_size: 11,
epilog_size: 0,
params_size: 12,
saved_regs_size: 12,
locals_size: 4,
max_stack_size: 0,
uses_base_pointer: true,
program_string: None,
}
"###);
Ok(())
}
#[test]
fn test_parse_stack_win_whitespace() -> Result<(), BreakpadError> {
let string =
b" STACK WIN 4 371a c 0 0 0 0 0 0 1 $T0 .raSearch = $eip $T0 ^ = $esp $T0 4 + =
";
let record = BreakpadStackRecord::parse(string)?;
insta::assert_debug_snapshot!(record, @r###"
Win(
BreakpadStackWinRecord {
ty: FrameData,
code_start: 14106,
code_size: 12,
prolog_size: 0,
epilog_size: 0,
params_size: 0,
saved_regs_size: 0,
locals_size: 0,
max_stack_size: 0,
uses_base_pointer: false,
program_string: Some(
"$T0 .raSearch = $eip $T0 ^ = $esp $T0 4 + =",
),
},
)
"###);
Ok(())
}
use similar_asserts::assert_eq;
#[test]
fn test_lineoffsets_fused() {
let data = b"";
let mut offsets = LineOffsets::new(data);
offsets.next();
assert_eq!(None, offsets.next());
assert_eq!(None, offsets.next());
assert_eq!(None, offsets.next());
}
macro_rules! test_lineoffsets {
($name:ident, $data:literal, $( ($index:literal, $line:literal) ),*) => {
#[test]
fn $name() {
let mut offsets = LineOffsets::new($data);
$(
assert_eq!(Some(($index, &$line[..])), offsets.next());
)*
assert_eq!(None, offsets.next());
}
};
}
test_lineoffsets!(test_lineoffsets_empty, b"", (0, b""));
test_lineoffsets!(test_lineoffsets_oneline, b"hello", (0, b"hello"));
test_lineoffsets!(
test_lineoffsets_trailing_n,
b"hello\n",
(0, b"hello"),
(6, b"")
);
test_lineoffsets!(
test_lineoffsets_trailing_rn,
b"hello\r\n",
(0, b"hello"),
(7, b"")
);
test_lineoffsets!(
test_lineoffsets_n,
b"hello\nworld\nyo",
(0, b"hello"),
(6, b"world"),
(12, b"yo")
);
test_lineoffsets!(
test_lineoffsets_rn,
b"hello\r\nworld\r\nyo",
(0, b"hello"),
(7, b"world"),
(14, b"yo")
);
test_lineoffsets!(
test_lineoffsets_mixed,
b"hello\r\nworld\nyo",
(0, b"hello"),
(7, b"world"),
(13, b"yo")
);
}