use std::fmt;
use std::str::FromStr;
use uuid::Uuid;
use symbolic_common::{Arch, Error, ErrorKind, ObjectKind, Result};
use object::{FatObject, Object, ObjectId};
pub enum BreakpadRecord<'input> {
Module(BreakpadModuleRecord<'input>),
File(BreakpadFileRecord<'input>),
Function(BreakpadFuncRecord<'input>),
Line(BreakpadLineRecord),
Public(BreakpadPublicRecord<'input>),
Info(&'input [u8]),
Stack,
}
pub struct BreakpadModuleRecord<'input> {
pub arch: Arch,
pub uuid: Uuid,
pub name: &'input [u8],
}
impl<'input> fmt::Debug for BreakpadModuleRecord<'input> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_struct("BreakpadModuleRecord")
.field("arch", &self.arch)
.field("uuid", &self.uuid)
.field("name", &String::from_utf8_lossy(self.name))
.finish()
}
}
pub struct BreakpadFileRecord<'input> {
pub id: u64,
pub name: &'input [u8],
}
impl<'input> fmt::Debug for BreakpadFileRecord<'input> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_struct("BreakpadFileRecord")
.field("id", &self.id)
.field("name", &String::from_utf8_lossy(self.name))
.finish()
}
}
#[derive(Debug)]
pub struct BreakpadLineRecord {
pub address: u64,
pub line: u64,
pub file_id: u64,
}
pub struct BreakpadFuncRecord<'input> {
pub address: u64,
pub size: u64,
pub name: &'input [u8],
pub lines: Vec<BreakpadLineRecord>,
}
impl<'input> fmt::Debug for BreakpadFuncRecord<'input> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_struct("BreakpadFuncRecord")
.field("address", &self.address)
.field("size", &self.size)
.field("name", &String::from_utf8_lossy(self.name))
.field("lines", &self.lines)
.finish()
}
}
pub struct BreakpadPublicRecord<'input> {
pub address: u64,
pub size: u64,
pub name: &'input [u8],
}
impl<'input> fmt::Debug for BreakpadPublicRecord<'input> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_struct("BreakpadPublicRecord")
.field("address", &self.address)
.field("size", &self.size)
.field("name", &String::from_utf8_lossy(self.name))
.finish()
}
}
#[derive(Debug)]
pub(crate) struct BreakpadSym {
id: ObjectId,
arch: Arch,
}
impl BreakpadSym {
pub fn parse(bytes: &[u8]) -> Result<BreakpadSym> {
let mut words = bytes.splitn(5, |b| *b == b' ');
match words.next() {
Some(b"MODULE") => (),
_ => return Err(ErrorKind::BadBreakpadSym("Invalid breakpad magic").into()),
};
words.next();
let arch = match words.next() {
Some(word) => String::from_utf8_lossy(word),
None => return Err(ErrorKind::BadBreakpadSym("Missing breakpad arch").into()),
};
let uuid_hex = match words.next() {
Some(word) => String::from_utf8_lossy(word),
None => return Err(ErrorKind::BadBreakpadSym("Missing breakpad uuid").into()),
};
let id = match ObjectId::parse(&uuid_hex[0..33]) {
Ok(uuid) => uuid,
Err(_) => return Err(ErrorKind::Parse("Invalid breakpad uuid").into()),
};
Ok(BreakpadSym {
id: id,
arch: Arch::from_breakpad(arch.as_ref()),
})
}
pub fn id(&self) -> ObjectId {
self.id
}
pub fn arch(&self) -> Arch {
self.arch
}
}
#[derive(Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
enum IterState {
Started,
Reading,
Function,
}
pub struct BreakpadRecords<'data> {
lines: Box<Iterator<Item = &'data [u8]> + 'data>,
state: IterState,
}
impl<'data> BreakpadRecords<'data> {
fn from_bytes(bytes: &'data [u8]) -> BreakpadRecords<'data> {
BreakpadRecords {
lines: Box::new(bytes.split(|b| *b == b'\n')),
state: IterState::Started,
}
}
fn parse(&mut self, line: &'data [u8]) -> Result<BreakpadRecord<'data>> {
let mut words = line.splitn(2, |b| *b == b' ');
let magic = words.next().unwrap_or(b"");
let record = words.next().unwrap_or(b"");
match magic {
b"MODULE" => {
if self.state != IterState::Started {
return Err(ErrorKind::BadBreakpadSym("unexpected module header").into());
}
self.state = IterState::Reading;
parse_module(record)
}
b"FILE" => {
self.state = IterState::Reading;
parse_file(record)
}
b"FUNC" => {
self.state = IterState::Function;
parse_func(record)
}
b"STACK" => {
self.state = IterState::Reading;
parse_stack(record)
}
b"PUBLIC" => {
self.state = IterState::Reading;
parse_public(record)
}
b"INFO" => {
self.state = IterState::Reading;
parse_info(record)
}
_ => {
if self.state == IterState::Function {
parse_line(line)
} else {
Err(ErrorKind::BadBreakpadSym("unexpected line record").into())
}
}
}
}
}
impl<'data> Iterator for BreakpadRecords<'data> {
type Item = Result<BreakpadRecord<'data>>;
fn next(&mut self) -> Option<Self::Item> {
while let Some(next) = self.lines.next() {
let mut len = next.len();
while len > 0 && next[len - 1] == b'\r' {
len -= 1;
}
if len > 0 {
return Some(self.parse(&next[0..len]));
}
}
None
}
}
pub trait BreakpadData {
fn has_breakpad_data(&self) -> bool;
fn breakpad_records<'input>(&'input self) -> BreakpadRecords<'input>;
}
impl<'data> BreakpadData for Object<'data> {
fn has_breakpad_data(&self) -> bool {
self.kind() == ObjectKind::Breakpad
}
fn breakpad_records<'input>(&'input self) -> BreakpadRecords<'input> {
BreakpadRecords::from_bytes(self.as_bytes())
}
}
impl<'data> BreakpadData for FatObject<'data> {
fn has_breakpad_data(&self) -> bool {
self.kind() == ObjectKind::Breakpad
}
fn breakpad_records<'input>(&'input self) -> BreakpadRecords<'input> {
BreakpadRecords::from_bytes(self.as_bytes())
}
}
fn parse_module(line: &[u8]) -> Result<BreakpadRecord> {
let mut record = line.splitn(4, |b| *b == b' ');
record.next();
let arch = match record.next() {
Some(word) => String::from_utf8_lossy(word),
None => return Err(ErrorKind::BadBreakpadSym("missing module arch").into()),
};
let uuid_hex = match record.next() {
Some(word) => String::from_utf8_lossy(word),
None => return Err(ErrorKind::BadBreakpadSym("missing module uuid").into()),
};
let uuid = match Uuid::parse_str(&uuid_hex[0..32]) {
Ok(uuid) => uuid,
Err(_) => return Err(ErrorKind::Parse("invalid breakpad uuid").into()),
};
let name = match record.next() {
Some(word) => word,
None => return Err(ErrorKind::BadBreakpadSym("missing module name").into()),
};
Ok(BreakpadRecord::Module(BreakpadModuleRecord {
name: name,
arch: Arch::from_breakpad(&arch),
uuid: uuid,
}))
}
fn parse_file<'data>(line: &'data [u8]) -> Result<BreakpadRecord<'data>> {
let mut record = line.splitn(2, |b| *b == b' ');
let id = match record.next() {
Some(text) => u64::from_str(&String::from_utf8_lossy(text))
.map_err(|e| Error::with_chain(e, "invalid file id"))?,
None => return Err(ErrorKind::Parse("missing file ID").into()),
};
let name = match record.next() {
Some(text) => text,
None => return Err(ErrorKind::Parse("missing file name").into()),
};
Ok(BreakpadRecord::File(BreakpadFileRecord {
id: id,
name: name,
}))
}
fn parse_func<'data>(line: &'data [u8]) -> Result<BreakpadRecord<'data>> {
let line = if line.starts_with(b"m ") {
&line[2..]
} else {
line
};
let mut record = line.splitn(4, |b| *b == b' ');
let address = match record.next() {
Some(text) => u64::from_str_radix(&String::from_utf8_lossy(text), 16)
.map_err(|e| Error::with_chain(e, "invalid function address"))?,
None => return Err(ErrorKind::Parse("missing function address").into()),
};
let size = match record.next() {
Some(text) => u64::from_str_radix(&String::from_utf8_lossy(text), 16)
.map_err(|e| Error::with_chain(e, "invalid function size"))?,
None => return Err(ErrorKind::Parse("missing function size").into()),
};
record.next();
let name = match record.next() {
Some(text) => text,
None => return Err(ErrorKind::Parse("missing function name").into()),
};
Ok(BreakpadRecord::Function(BreakpadFuncRecord {
address: address,
size: size,
name: name,
lines: vec![],
}))
}
fn parse_stack<'data>(_line: &'data [u8]) -> Result<BreakpadRecord<'data>> {
Ok(BreakpadRecord::Stack)
}
fn parse_public<'data>(line: &'data [u8]) -> Result<BreakpadRecord<'data>> {
let line = if line.starts_with(b"m ") {
&line[2..]
} else {
line
};
let mut record = line.splitn(4, |b| *b == b' ');
let address = match record.next() {
Some(text) => u64::from_str_radix(&String::from_utf8_lossy(text), 16)
.map_err(|e| Error::with_chain(e, "invalid symbol address"))?,
None => return Err(ErrorKind::Parse("missing symbol address").into()),
};
record.next();
let name = match record.next() {
Some(text) => text,
None => return Err(ErrorKind::Parse("missing function name").into()),
};
Ok(BreakpadRecord::Public(BreakpadPublicRecord {
address: address,
size: 0, name: name,
}))
}
fn parse_info<'data>(line: &'data [u8]) -> Result<BreakpadRecord<'data>> {
Ok(BreakpadRecord::Info(line))
}
fn parse_line<'data>(line: &'data [u8]) -> Result<BreakpadRecord<'data>> {
let mut record = line.splitn(4, |b| *b == b' ');
let address = match record.next() {
Some(text) => u64::from_str_radix(&String::from_utf8_lossy(text), 16)
.map_err(|e| Error::with_chain(e, "invalid line address"))?,
None => return Err(ErrorKind::Parse("Missing line address").into()),
};
record.next();
let line_number = match record.next() {
Some(text) => u64::from_str(&String::from_utf8_lossy(text))
.map_err(|e| Error::with_chain(e, "invalid line number"))?,
None => return Err(ErrorKind::Parse("Missing line number").into()),
};
let file_id = match record.next() {
Some(text) => u64::from_str(&String::from_utf8_lossy(text))
.map_err(|e| Error::with_chain(e, "invalid line file id"))?,
None => return Err(ErrorKind::Parse("missing line file id").into()),
};
Ok(BreakpadRecord::Line(BreakpadLineRecord {
address: address,
line: line_number,
file_id: file_id,
}))
}