use std::collections::HashMap;
use std::io::{self, Write};
use std::ops::Range;
use failure::{Fail, ResultExt};
use symbolic_common::{derive_failure, Arch, ByteView, UnknownArchError};
use symbolic_debuginfo::breakpad::{BreakpadObject, BreakpadStackRecord};
use symbolic_debuginfo::dwarf::gimli::{
BaseAddresses, CfaRule, CieOrFde, DebugFrame, EhFrame, Error, FrameDescriptionEntry, Reader,
Register, RegisterRule, UninitializedUnwindContext, UnwindSection,
};
use symbolic_debuginfo::dwarf::Dwarf;
use symbolic_debuginfo::pdb::pdb::{self, FallibleIterator, FrameData, Rva, StringTable};
use symbolic_debuginfo::pdb::PdbObject;
use symbolic_debuginfo::pe::{PeObject, RuntimeFunction, UnwindOperation};
use symbolic_debuginfo::{Object, ObjectLike};
pub const CFICACHE_LATEST_VERSION: u32 = 1;
const EMPTY_FUNCTION: RuntimeFunction = RuntimeFunction {
begin_address: 0,
end_address: 0,
unwind_info_address: 0,
};
#[derive(Debug, Fail, Copy, Clone)]
pub enum CfiErrorKind {
#[fail(display = "missing cfi debug sections")]
MissingDebugInfo,
#[fail(display = "unsupported debug format")]
UnsupportedDebugFormat,
#[fail(display = "bad debug information")]
BadDebugInfo,
#[fail(display = "unsupported architecture")]
UnsupportedArch,
#[fail(display = "invalid cfi address")]
InvalidAddress,
#[fail(display = "failed to write cfi")]
WriteError,
#[fail(display = "bad cfi cache magic")]
BadFileMagic,
}
derive_failure!(
CfiError,
CfiErrorKind,
doc = "An error returned by [`AsciiCfiWriter`](struct.AsciiCfiWriter.html)."
);
impl From<UnknownArchError> for CfiError {
fn from(_: UnknownArchError) -> CfiError {
CfiErrorKind::UnsupportedArch.into()
}
}
trait UnwindSectionExt<R>: UnwindSection<R>
where
R: Reader,
{
fn set_address_size(&mut self, address_size: u8);
}
impl<R: Reader> UnwindSectionExt<R> for EhFrame<R> {
fn set_address_size(&mut self, address_size: u8) {
self.set_address_size(address_size)
}
}
impl<R: Reader> UnwindSectionExt<R> for DebugFrame<R> {
fn set_address_size(&mut self, address_size: u8) {
self.set_address_size(address_size)
}
}
struct UnwindInfo<U> {
arch: Arch,
load_address: u64,
section: U,
bases: BaseAddresses,
}
impl<U> UnwindInfo<U> {
pub fn new<O, R>(object: &O, addr: u64, mut section: U) -> Self
where
O: ObjectLike,
R: Reader,
U: UnwindSectionExt<R>,
{
let arch = object.arch();
let load_address = object.load_address();
let bases = BaseAddresses::default().set_eh_frame(addr);
if let Some(pointer_size) = arch.pointer_size() {
section.set_address_size(pointer_size as u8);
}
UnwindInfo {
arch,
load_address,
section,
bases,
}
}
}
pub struct AsciiCfiWriter<W: Write> {
inner: W,
}
impl<W: Write> AsciiCfiWriter<W> {
pub fn new(inner: W) -> Self {
AsciiCfiWriter { inner }
}
pub fn process(&mut self, object: &Object<'_>) -> Result<(), CfiError> {
match object {
Object::Breakpad(o) => self.process_breakpad(o),
Object::MachO(o) => self.process_dwarf(o),
Object::Elf(o) => self.process_dwarf(o),
Object::Pdb(o) => self.process_pdb(o),
Object::Pe(o) => self.process_pe(o),
Object::SourceBundle(_) => Ok(()),
}
}
pub fn into_inner(self) -> W {
self.inner
}
fn process_breakpad(&mut self, object: &BreakpadObject<'_>) -> Result<(), CfiError> {
for record in object.stack_records() {
match record.context(CfiErrorKind::BadDebugInfo)? {
BreakpadStackRecord::Cfi(r) => writeln!(self.inner, "STACK CFI {}", r.text),
BreakpadStackRecord::Win(r) => writeln!(self.inner, "STACK WIN {}", r.text),
}
.context(CfiErrorKind::WriteError)?
}
Ok(())
}
fn process_dwarf<'o, O>(&mut self, object: &O) -> Result<(), CfiError>
where
O: ObjectLike + Dwarf<'o>,
{
let endian = object.endianity();
let debug_frame_result = if let Some(section) = object.section("debug_frame") {
let frame = DebugFrame::new(§ion.data, endian);
let info = UnwindInfo::new(object, section.address, frame);
self.read_cfi(&info)
} else {
Ok(())
};
if let Some(section) = object.section("eh_frame") {
let frame = EhFrame::new(§ion.data, endian);
let info = UnwindInfo::new(object, section.address, frame);
self.read_cfi(&info)?;
}
debug_frame_result
}
fn read_cfi<U, R>(&mut self, info: &UnwindInfo<U>) -> Result<(), CfiError>
where
R: Reader + Eq,
U: UnwindSection<R>,
{
let mut ctx = UninitializedUnwindContext::new();
let mut entries = info.section.entries(&info.bases);
while let Some(entry) = entries.next().context(CfiErrorKind::BadDebugInfo)? {
if let CieOrFde::Fde(partial_fde) = entry {
if let Ok(fde) = partial_fde.parse(U::cie_from_offset) {
self.process_fde(info, &mut ctx, &fde)?
}
}
}
Ok(())
}
fn process_fde<R, U>(
&mut self,
info: &UnwindInfo<U>,
ctx: &mut UninitializedUnwindContext<R>,
fde: &FrameDescriptionEntry<R>,
) -> Result<(), CfiError>
where
R: Reader + Eq,
U: UnwindSection<R>,
{
let ra = fde.cie().return_address_register();
let mut table = fde
.rows(&info.section, &info.bases, ctx)
.context(CfiErrorKind::BadDebugInfo)?;
let mut rows = Vec::new();
loop {
match table.next_row() {
Ok(None) => break,
Ok(Some(row)) => rows.push(row.clone()),
Err(Error::UnknownCallFrameInstruction(_)) => continue,
Err(Error::TooManyRegisterRules) => continue,
Err(e) => return Err(e.context(CfiErrorKind::BadDebugInfo).into()),
}
}
if let Some(first_row) = rows.first() {
let start = first_row.start_address();
let length = rows.last().unwrap().end_address() - start;
if start < info.load_address {
return Ok(());
}
let mut rule_cache = HashMap::new();
let mut cfa_cache = None;
for row in &rows {
let mut written = false;
let mut line = Vec::new();
if row.start_address() == start {
let start_addr = start - info.load_address;
write!(line, "STACK CFI INIT {:x} {:x}", start_addr, length)
.context(CfiErrorKind::WriteError)?;
} else {
let start_addr = row.start_address() - info.load_address;
write!(line, "STACK CFI {:x}", start_addr).context(CfiErrorKind::WriteError)?;
}
if cfa_cache != Some(row.cfa()) {
cfa_cache = Some(row.cfa());
written |= Self::write_cfa_rule(&mut line, info.arch, row.cfa())?;
}
for &(register, ref rule) in row.registers() {
if !rule_cache.get(®ister).map_or(false, |c| c == &rule) {
rule_cache.insert(register, rule);
written |=
Self::write_register_rule(&mut line, info.arch, register, rule, ra)?;
}
}
if written {
self.inner
.write_all(&line)
.and_then(|_| writeln!(self.inner))
.context(CfiErrorKind::WriteError)?;
}
}
}
Ok(())
}
fn write_cfa_rule<R: Reader, T: Write>(
mut target: T,
arch: Arch,
rule: &CfaRule<R>,
) -> Result<bool, CfiError> {
let formatted = match rule {
CfaRule::RegisterAndOffset { register, offset } => {
match arch.register_name(register.0) {
Some(register) => format!("{} {} +", register, *offset),
None => return Ok(false),
}
}
CfaRule::Expression(_) => return Ok(false),
};
write!(target, " .cfa: {}", formatted).context(CfiErrorKind::WriteError)?;
Ok(true)
}
fn write_register_rule<R: Reader, T: Write>(
mut target: T,
arch: Arch,
register: Register,
rule: &RegisterRule<R>,
ra: Register,
) -> Result<bool, CfiError> {
let formatted = match rule {
RegisterRule::Undefined => return Ok(false),
RegisterRule::SameValue => match arch.register_name(register.0) {
Some(reg) => reg.into(),
None => return Ok(false),
},
RegisterRule::Offset(offset) => format!(".cfa {} + ^", offset),
RegisterRule::ValOffset(offset) => format!(".cfa {} +", offset),
RegisterRule::Register(register) => match arch.register_name(register.0) {
Some(reg) => reg.into(),
None => return Ok(false),
},
RegisterRule::Expression(_) => return Ok(false),
RegisterRule::ValExpression(_) => return Ok(false),
RegisterRule::Architectural => return Ok(false),
};
let register_name = if register == ra {
".ra"
} else {
match arch.register_name(register.0) {
Some(reg) => reg,
None => return Ok(false),
}
};
write!(target, " {}: {}", register_name, formatted).context(CfiErrorKind::WriteError)?;
Ok(true)
}
fn process_pdb(&mut self, pdb: &PdbObject<'_>) -> Result<(), CfiError> {
let mut pdb = pdb.inner().write();
let frame_table = pdb.frame_table().context(CfiErrorKind::BadDebugInfo)?;
let address_map = pdb.address_map().context(CfiErrorKind::BadDebugInfo)?;
let string_table = match pdb.string_table() {
Ok(string_table) => Some(string_table),
Err(pdb::Error::StreamNameNotFound) => None,
Err(e) => Err(e).context(CfiErrorKind::BadDebugInfo)?,
};
let mut frames = frame_table.iter();
let mut last_frame: Option<FrameData> = None;
while let Some(frame) = frames.next().context(CfiErrorKind::BadDebugInfo)? {
if frame.code_size > i32::max_value() as u32 {
continue;
}
if let Some(ref last) = last_frame {
if frame.ty == last.ty
&& frame.code_start == last.code_start
&& frame.code_size == last.code_size
&& frame.prolog_size == last.prolog_size
{
continue;
}
}
let prolog_size = u32::from(frame.prolog_size);
let prolog_end = frame.code_start + prolog_size;
let code_end = frame.code_start + frame.code_size;
let mut prolog_ranges = address_map
.rva_ranges(frame.code_start..prolog_end)
.collect::<Vec<_>>();
let mut code_ranges = address_map
.rva_ranges(prolog_end..code_end)
.collect::<Vec<_>>();
let is_contiguous = prolog_ranges.len() == 1
&& code_ranges.len() == 1
&& prolog_ranges[0].end == code_ranges[0].start;
if is_contiguous {
self.write_pdb_stackinfo(
string_table.as_ref(),
&frame,
prolog_ranges[0].start,
code_ranges[0].end,
prolog_ranges[0].end - prolog_ranges[0].start,
)?;
} else {
prolog_ranges.sort_unstable_by_key(|range| range.start);
code_ranges.sort_unstable_by_key(|range| range.start);
for Range { start, end } in prolog_ranges {
self.write_pdb_stackinfo(
string_table.as_ref(),
&frame,
start,
end,
end - start,
)?;
}
for Range { start, end } in code_ranges {
self.write_pdb_stackinfo(string_table.as_ref(), &frame, start, end, 0)?;
}
}
last_frame = Some(frame);
}
Ok(())
}
fn write_pdb_stackinfo(
&mut self,
string_table: Option<&StringTable<'_>>,
frame: &FrameData,
start: Rva,
end: Rva,
prolog_size: u32,
) -> Result<(), CfiError> {
let code_size = end - start;
let program_or_bp =
frame.program.is_some() && string_table.is_some() || frame.uses_base_pointer;
write!(
self.inner,
"STACK WIN {:x} {:x} {:x} {:x} {:x} {:x} {:x} {:x} {:x} {} ",
frame.ty as u8,
start.0,
code_size,
prolog_size,
0,
frame.params_size,
frame.saved_regs_size,
frame.locals_size,
frame.max_stack_size.unwrap_or(0),
if program_or_bp { 1 } else { 0 },
)
.context(CfiErrorKind::WriteError)?;
match frame.program {
Some(ref prog_ref) => {
let string_table = match string_table {
Some(string_table) => string_table,
None => return Ok(writeln!(self.inner).context(CfiErrorKind::WriteError)?),
};
let program_string = prog_ref
.to_string_lossy(&string_table)
.context(CfiErrorKind::BadDebugInfo)?;
writeln!(self.inner, "{}", program_string.trim())
.context(CfiErrorKind::WriteError)?;
}
None => {
writeln!(self.inner, "{}", if program_or_bp { 1 } else { 0 })
.context(CfiErrorKind::WriteError)?;
}
}
Ok(())
}
fn process_pe(&mut self, pe: &PeObject<'_>) -> Result<(), CfiError> {
let sections = pe.sections();
let exception_data = match pe.exception_data() {
Some(data) => data,
None => return Ok(()),
};
for function_result in exception_data {
let function = function_result.context(CfiErrorKind::BadDebugInfo)?;
if function == EMPTY_FUNCTION {
continue;
}
let mut stack_size = 8;
let mut machine_frame_offset = 0;
if function.end_address < function.begin_address {
continue;
}
let mut next_function = Some(function);
while let Some(next) = next_function {
let unwind_info = exception_data
.get_unwind_info(next, sections)
.context(CfiErrorKind::BadDebugInfo)?;
for code_result in &unwind_info {
let code = match code_result {
Ok(code) => code,
Err(_) => return Ok(()),
};
match code.operation {
UnwindOperation::PushNonVolatile(_) => {
stack_size += 8;
}
UnwindOperation::Alloc(size) => {
stack_size += size;
}
UnwindOperation::PushMachineFrame(is_error) => {
stack_size += if is_error { 48 } else { 40 };
machine_frame_offset = stack_size;
}
_ => {
}
}
}
next_function = unwind_info.chained_info;
}
writeln!(
self.inner,
"STACK CFI INIT {:x} {:x} .cfa: $rsp 8 + .ra: .cfa 8 - ^",
function.begin_address,
function.end_address - function.begin_address,
)
.context(CfiErrorKind::WriteError)?;
if machine_frame_offset > 0 {
writeln!(
self.inner,
"STACK CFI {:x} .cfa: $rsp {} + $rsp: .cfa {} - ^ .ra: .cfa {} - ^",
function.begin_address,
stack_size,
stack_size - machine_frame_offset + 24,
stack_size - machine_frame_offset + 48,
)
.context(CfiErrorKind::WriteError)?
} else {
writeln!(
self.inner,
"STACK CFI {:x} .cfa: $rsp {} +",
function.begin_address, stack_size,
)
.context(CfiErrorKind::WriteError)?
}
}
Ok(())
}
}
impl<W: Write + Default> AsciiCfiWriter<W> {
pub fn transform(object: &Object<'_>) -> Result<W, CfiError> {
let mut writer = Default::default();
AsciiCfiWriter::new(&mut writer).process(object)?;
Ok(writer)
}
}
struct CfiCacheV1<'a> {
byteview: ByteView<'a>,
}
impl<'a> CfiCacheV1<'a> {
pub fn raw(&self) -> &[u8] {
&self.byteview
}
}
enum CfiCacheInner<'a> {
V1(CfiCacheV1<'a>),
}
pub struct CfiCache<'a> {
inner: CfiCacheInner<'a>,
}
impl CfiCache<'static> {
pub fn from_object(object: &Object<'_>) -> Result<Self, CfiError> {
let buffer = AsciiCfiWriter::transform(object)?;
let byteview = ByteView::from_vec(buffer);
let inner = CfiCacheInner::V1(CfiCacheV1 { byteview });
Ok(CfiCache { inner })
}
}
impl<'a> CfiCache<'a> {
pub fn from_bytes(byteview: ByteView<'a>) -> Result<Self, CfiError> {
if byteview.len() == 0 || byteview.starts_with(b"STACK") {
let inner = CfiCacheInner::V1(CfiCacheV1 { byteview });
return Ok(CfiCache { inner });
}
Err(CfiErrorKind::BadFileMagic.into())
}
pub fn version(&self) -> u32 {
match self.inner {
CfiCacheInner::V1(_) => 1,
}
}
pub fn is_latest(&self) -> bool {
self.version() == CFICACHE_LATEST_VERSION
}
pub fn as_slice(&self) -> &[u8] {
match self.inner {
CfiCacheInner::V1(ref v1) => v1.raw(),
}
}
pub fn write_to<W: Write>(&self, mut writer: W) -> Result<(), io::Error> {
io::copy(&mut self.as_slice(), &mut writer)?;
Ok(())
}
}