use std::borrow::Cow;
use std::collections::BTreeMap;
use std::fmt;
use std::str;
use failure::Fail;
use pest::Parser;
use symbolic_common::{derive_failure, Arch, AsSelf, CodeId, DebugId, Name};
use crate::base::*;
use crate::private::{Lines, Parse};
mod parser {
use pest_derive::Parser;
#[derive(Debug, Parser)]
#[grammar = "breakpad.pest"]
pub struct BreakpadParser;
}
use self::parser::{BreakpadParser, Rule};
const BREAKPAD_HEADER_CAP: usize = 320;
const UNKNOWN_NAME: &str = "<unknown>";
#[derive(Debug, Fail)]
pub enum BreakpadErrorKind {
#[fail(display = "missing breakpad symbol header")]
InvalidMagic,
#[fail(display = "bad utf-8 sequence")]
BadEncoding(#[fail(cause)] str::Utf8Error),
#[fail(display = "{}", _0)]
BadSyntax(pest::error::Error<Rule>),
#[fail(display = "{}", _0)]
Parse(&'static str),
}
derive_failure!(
BreakpadError,
BreakpadErrorKind,
doc = "An error when dealing with [`BreakpadObject`](struct.BreakpadObject.html).",
);
impl From<str::Utf8Error> for BreakpadError {
fn from(error: str::Utf8Error) -> Self {
BreakpadErrorKind::BadEncoding(error).into()
}
}
impl From<pest::error::Error<Rule>> for BreakpadError {
fn from(error: pest::error::Error<Rule>) -> Self {
BreakpadErrorKind::BadSyntax(error).into()
}
}
#[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)?;
let parsed = BreakpadParser::parse(Rule::module, string)?.next().unwrap();
let mut record = BreakpadModuleRecord::default();
for pair in parsed.into_inner() {
match pair.as_rule() {
Rule::os => record.os = pair.as_str(),
Rule::arch => record.arch = pair.as_str(),
Rule::debug_id => record.id = pair.as_str(),
Rule::name => record.name = pair.as_str(),
_ => unreachable!(),
}
}
Ok(record)
}
}
#[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)?;
let parsed = BreakpadParser::parse(Rule::info, string)?.next().unwrap();
for pair in parsed.into_inner() {
match pair.as_rule() {
Rule::info_code_id => return Self::code_info_from_pair(pair),
Rule::info_other => return Self::other_from_pair(pair),
_ => unreachable!(),
}
}
Err(BreakpadErrorKind::Parse("unknown INFO record").into())
}
fn code_info_from_pair(pair: pest::iterators::Pair<'d, Rule>) -> Result<Self, BreakpadError> {
let mut code_id = "";
let mut code_file = "";
for pair in pair.into_inner() {
match pair.as_rule() {
Rule::code_id => code_id = pair.as_str(),
Rule::name => code_file = pair.as_str(),
_ => unreachable!(),
}
}
Ok(BreakpadInfoRecord::CodeId { code_id, code_file })
}
fn other_from_pair(pair: pest::iterators::Pair<'d, Rule>) -> Result<Self, BreakpadError> {
let mut scope = "";
let mut info = "";
for pair in pair.into_inner() {
match pair.as_rule() {
Rule::ident => scope = pair.as_str(),
Rule::text => info = pair.as_str(),
_ => unreachable!(),
}
}
Ok(BreakpadInfoRecord::Other { scope, info })
}
}
#[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;
}
while let Some(line) = self.lines.next() {
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)?;
let parsed = BreakpadParser::parse(Rule::file, string)?.next().unwrap();
let mut record = BreakpadFileRecord::default();
for pair in parsed.into_inner() {
match pair.as_rule() {
Rule::file_id => {
record.id = u64::from_str_radix(pair.as_str(), 10)
.map_err(|_| BreakpadErrorKind::Parse("file identifier"))?;
}
Rule::name => record.name = pair.as_str(),
_ => unreachable!(),
}
}
Ok(record)
}
}
#[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;
}
while let Some(line) = self.lines.next() {
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 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)?;
let parsed = BreakpadParser::parse(Rule::public, string)?.next().unwrap();
let mut record = BreakpadPublicRecord::default();
for pair in parsed.into_inner() {
match pair.as_rule() {
Rule::multiple => record.multiple = true,
Rule::addr => {
record.address = u64::from_str_radix(pair.as_str(), 16)
.map_err(|_| BreakpadErrorKind::Parse("symbol address"))?;
}
Rule::param_size => {
record.parameter_size = u64::from_str_radix(pair.as_str(), 16)
.map_err(|_| BreakpadErrorKind::Parse("symbol parameter size"))?;
}
Rule::name => record.name = pair.as_str(),
_ => unreachable!(),
}
}
if record.name.is_empty() {
record.name = UNKNOWN_NAME;
}
Ok(record)
}
}
#[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;
}
while let Some(line) = self.lines.next() {
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 parsed = BreakpadParser::parse(Rule::func, string)?.next().unwrap();
let mut record = BreakpadFuncRecord::default();
for pair in parsed.into_inner() {
match pair.as_rule() {
Rule::multiple => record.multiple = true,
Rule::addr => {
record.address = u64::from_str_radix(pair.as_str(), 16)
.map_err(|_| BreakpadErrorKind::Parse("function address"))?;
}
Rule::size => {
record.size = u64::from_str_radix(pair.as_str(), 16)
.map_err(|_| BreakpadErrorKind::Parse("function size"))?;
}
Rule::param_size => {
record.parameter_size = u64::from_str_radix(pair.as_str(), 16)
.map_err(|_| BreakpadErrorKind::Parse("function parameter size"))?;
}
Rule::name => record.name = pair.as_str(),
_ => unreachable!(),
}
}
if record.name.is_empty() {
record.name = UNKNOWN_NAME;
}
record.lines = lines;
Ok(record)
}
pub fn lines(&self) -> BreakpadLineRecords<'d> {
BreakpadLineRecords {
lines: self.lines.clone(),
finished: false,
}
}
}
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;
}
while let Some(line) = self.lines.next() {
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)?;
let parsed = BreakpadParser::parse(Rule::line, string)?.next().unwrap();
let mut record = BreakpadLineRecord::default();
for pair in parsed.into_inner() {
match pair.as_rule() {
Rule::addr => {
record.address = u64::from_str_radix(pair.as_str(), 16)
.map_err(|_| BreakpadErrorKind::Parse("line address"))?;
}
Rule::size => {
record.size = u64::from_str_radix(pair.as_str(), 16)
.map_err(|_| BreakpadErrorKind::Parse("line size"))?;
}
Rule::line_num => {
record.line = i32::from_str_radix(pair.as_str(), 10)
.map(|line| u64::from(line as u32))
.map_err(|_| BreakpadErrorKind::Parse("line number"))?;
}
Rule::file_id => {
record.file_id = u64::from_str_radix(pair.as_str(), 10)
.map_err(|_| BreakpadErrorKind::Parse("file number"))?;
}
_ => unreachable!(),
}
}
Ok(record)
}
pub fn filename<'d>(&self, file_map: &BreakpadFileMap<'d>) -> Option<&'d str> {
file_map.get(&self.file_id).cloned()
}
}
#[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;
}
while let Some(line) = self.lines.next() {
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 BreakpadStackCfiRecord<'d> {
pub text: &'d str,
}
impl<'d> BreakpadStackCfiRecord<'d> {
pub fn parse(data: &'d [u8]) -> Result<Self, BreakpadError> {
let string = str::from_utf8(data)?;
let parsed = BreakpadParser::parse(Rule::stack_cfi, string)?
.next()
.unwrap();
Self::from_pair(parsed)
}
fn from_pair(pair: pest::iterators::Pair<'d, Rule>) -> Result<Self, BreakpadError> {
let mut record = BreakpadStackCfiRecord::default();
for pair in pair.into_inner() {
match pair.as_rule() {
Rule::text => record.text = pair.as_str(),
_ => unreachable!(),
}
}
Ok(record)
}
}
#[derive(Clone, Debug, Default, Eq, PartialEq)]
pub struct BreakpadStackWinRecord<'d> {
pub text: &'d str,
}
impl<'d> BreakpadStackWinRecord<'d> {
pub fn parse(data: &'d [u8]) -> Result<Self, BreakpadError> {
let string = str::from_utf8(data)?;
let parsed = BreakpadParser::parse(Rule::stack_win, string)?
.next()
.unwrap();
Self::from_pair(parsed)
}
fn from_pair(pair: pest::iterators::Pair<'d, Rule>) -> Result<Self, BreakpadError> {
let mut record = BreakpadStackWinRecord::default();
for pair in pair.into_inner() {
match pair.as_rule() {
Rule::text => record.text = pair.as_str(),
_ => unreachable!(),
}
}
Ok(record)
}
}
#[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)?;
let parsed = BreakpadParser::parse(Rule::stack, string)?.next().unwrap();
let pair = parsed.into_inner().next().unwrap();
Ok(match pair.as_rule() {
Rule::stack_cfi => BreakpadStackRecord::Cfi(BreakpadStackCfiRecord::from_pair(pair)?),
Rule::stack_win => BreakpadStackRecord::Win(BreakpadStackWinRecord::from_pair(pair)?),
_ => unreachable!(),
})
}
}
#[derive(Clone, Debug)]
pub struct BreakpadStackRecords<'d> {
lines: Lines<'d>,
finished: bool,
}
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 ") {
return Some(BreakpadStackRecord::parse(line));
}
}
self.finished = true;
None
}
}
pub struct BreakpadObject<'d> {
id: DebugId,
arch: Arch,
module: BreakpadModuleRecord<'d>,
data: &'d [u8],
}
impl<'d> BreakpadObject<'d> {
pub fn test(data: &[u8]) -> bool {
data.starts_with(b"MODULE ")
}
pub fn parse(data: &'d [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 module = BreakpadModuleRecord::parse(header)?;
Ok(BreakpadObject {
id: module
.id
.parse()
.map_err(|_| BreakpadErrorKind::Parse("module id"))?,
arch: module
.arch
.parse()
.map_err(|_| BreakpadErrorKind::Parse("module architecture"))?,
module,
data,
})
}
pub fn file_format(&self) -> FileFormat {
FileFormat::Breakpad
}
pub fn code_id(&self) -> Option<CodeId> {
for result in self.info_records() {
if let Ok(record) = result {
if let BreakpadInfoRecord::CodeId { code_id, .. } = record {
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) -> &'d 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<'d> {
BreakpadSymbolIterator {
records: self.public_records(),
}
}
pub fn symbol_map(&self) -> SymbolMap<'d> {
self.symbols().collect()
}
pub fn has_debug_info(&self) -> bool {
self.func_records().next().is_some()
}
pub fn debug_session(&self) -> Result<BreakpadDebugSession<'d>, BreakpadError> {
Ok(BreakpadDebugSession {
file_map: self.file_map(),
func_records: self.func_records(),
})
}
pub fn has_unwind_info(&self) -> bool {
self.stack_records().next().is_some()
}
pub fn has_sources(&self) -> bool {
false
}
pub fn info_records(&self) -> BreakpadInfoRecords<'d> {
BreakpadInfoRecords {
lines: Lines::new(self.data),
finished: false,
}
}
pub fn file_records(&self) -> BreakpadFileRecords<'d> {
BreakpadFileRecords {
lines: Lines::new(self.data),
finished: false,
}
}
pub fn file_map(&self) -> BreakpadFileMap<'d> {
self.file_records()
.filter_map(Result::ok)
.map(|file| (file.id, file.name))
.collect()
}
pub fn public_records(&self) -> BreakpadPublicRecords<'d> {
BreakpadPublicRecords {
lines: Lines::new(self.data),
finished: false,
}
}
pub fn func_records(&self) -> BreakpadFuncRecords<'d> {
BreakpadFuncRecords {
lines: Lines::new(self.data),
finished: false,
}
}
pub fn stack_records(&self) -> BreakpadStackRecords<'d> {
BreakpadStackRecords {
lines: Lines::new(self.data),
finished: false,
}
}
pub fn data(&self) -> &'d [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())
.finish()
}
}
impl<'slf, 'd: 'slf> AsSelf<'slf> for BreakpadObject<'d> {
type Ref = BreakpadObject<'slf>;
fn as_self(&'slf self) -> &Self::Ref {
self
}
}
impl<'d> Parse<'d> for BreakpadObject<'d> {
type Error = BreakpadError;
fn test(data: &[u8]) -> bool {
Self::test(data)
}
fn parse(data: &'d [u8]) -> Result<Self, BreakpadError> {
Self::parse(data)
}
}
impl<'d> ObjectLike for BreakpadObject<'d> {
type Error = BreakpadError;
type Session = BreakpadDebugSession<'d>;
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) -> DynIterator<'_, Symbol<'_>> {
Box::new(self.symbols())
}
fn symbol_map(&self) -> SymbolMap<'_> {
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()
}
}
pub struct BreakpadSymbolIterator<'d> {
records: BreakpadPublicRecords<'d>,
}
impl<'d> Iterator for BreakpadSymbolIterator<'d> {
type Item = Symbol<'d>;
fn next(&mut self) -> Option<Self::Item> {
while let Some(result) = self.records.next() {
if let Ok(record) = result {
return Some(Symbol {
name: Some(Cow::Borrowed(record.name)),
address: record.address,
size: 0,
});
}
}
None
}
}
pub struct BreakpadDebugSession<'d> {
file_map: BreakpadFileMap<'d>,
func_records: BreakpadFuncRecords<'d>,
}
impl<'d> BreakpadDebugSession<'d> {
pub fn functions(&self) -> BreakpadFunctionIterator<'_> {
BreakpadFunctionIterator {
file_map: &self.file_map,
func_records: self.func_records.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<'d> DebugSession for BreakpadDebugSession<'d> {
type Error = BreakpadError;
fn functions(&self) -> DynIterator<'_, Result<Function<'_>, Self::Error>> {
Box::new(self.functions())
}
fn files(&self) -> DynIterator<'_, Result<FileEntry<'_>, Self::Error>> {
Box::new(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 {
compilation_dir: &[],
info: FileInfo::from_path(path.as_bytes()),
}))
}
}
pub struct BreakpadFunctionIterator<'s> {
file_map: &'s BreakpadFileMap<'s>,
func_records: BreakpadFuncRecords<'s>,
}
impl<'s> BreakpadFunctionIterator<'s> {
fn convert(&self, record: BreakpadFuncRecord<'s>) -> Result<Function<'s>, BreakpadError> {
let mut lines = Vec::new();
for line in record.lines() {
let line = line?;
let filename = line.filename(&self.file_map).unwrap_or_default();
lines.push(LineInfo {
address: line.address,
size: Some(line.size),
file: FileInfo::from_path(filename.as_bytes()),
line: line.line,
});
}
Ok(Function {
address: record.address,
size: record.size,
name: Name::from(record.name),
compilation_dir: &[],
lines,
inlinees: Vec::new(),
inline: false,
})
}
}
impl<'s> Iterator for BreakpadFunctionIterator<'s> {
type Item = Result<Function<'s>, BreakpadError>;
fn next(&mut self) -> Option<Self::Item> {
match self.func_records.next() {
Some(Ok(record)) => Some(self.convert(record)),
Some(Err(error)) => Some(Err(error)),
None => None,
}
}
}
impl std::iter::FusedIterator for BreakpadFunctionIterator<'_> {}
#[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_matches!(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_matches!(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_matches!(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_matches!(record, @r###"
⋮BreakpadFileRecord {
⋮ id: 38,
⋮ name: "/usr/local/src/filename with spaces.c",
⋮}
"###);
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_matches!(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_matches!(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_matches!(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_matches!(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_matches!(record, @r###"
⋮BreakpadLineRecord {
⋮ address: 14744848,
⋮ size: 5,
⋮ line: 4294966920,
⋮ file_id: 2225,
⋮}
"###);
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_matches!(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_matches!(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_matches!(record, @r###"
⋮BreakpadPublicRecord {
⋮ multiple: false,
⋮ address: 20864,
⋮ parameter_size: 0,
⋮ name: "<unknown>",
⋮}
"###);
Ok(())
}
#[test]
fn test_parse_stack_cfi_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_matches!(record, @r###"
⋮Cfi(
⋮ BreakpadStackCfiRecord {
⋮ text: "INIT 1880 2d .cfa: $rsp 8 + .ra: .cfa -8 + ^",
⋮ },
⋮)
"###);
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_matches!(record, @r###"
⋮Win(
⋮ BreakpadStackWinRecord {
⋮ text: "4 371a c 0 0 0 0 0 0 1 $T0 .raSearch = $eip $T0 ^ = $esp $T0 4 + =",
⋮ },
⋮)
"###);
Ok(())
}
}