use crate::{error::TraceError, type_value_tree::TypeValueTree, Frame, FrameType, Location};
use addr2line::object::{Object, ObjectSection, SectionKind};
use funty::Fundamental;
use gimli::{DebugInfoOffset, EndianRcSlice, RunTimeEndian};
use stackdump_core::{device_memory::DeviceMemory, memory_region::VecMemoryRegion};
use std::collections::HashMap;
pub mod cortex_m;
pub enum UnwindResult<ADDR: funty::Integral> {
Finished,
Corrupted {
error_frame: Option<Frame<ADDR>>,
},
Proceeded,
}
pub trait Platform<'data> {
type Word: funty::Integral;
fn create_context(
elf: &addr2line::object::File<'data, &'data [u8]>,
) -> Result<Self, TraceError>
where
Self: Sized;
fn unwind(
&mut self,
device_memory: &mut DeviceMemory<Self::Word>,
previous_frame: Option<&mut Frame<Self::Word>>,
) -> Result<UnwindResult<Self::Word>, TraceError>;
}
pub fn trace<'data, P: Platform<'data>>(
mut device_memory: DeviceMemory<P::Word>,
elf_data: &'data [u8],
) -> Result<Vec<Frame<P::Word>>, TraceError>
where
<P::Word as funty::Numeric>::Bytes: bitvec::view::BitView<Store = u8>,
{
let elf = addr2line::object::File::parse(elf_data)?;
for section in elf.sections().filter(|section| {
matches!(
section.kind(),
SectionKind::Text | SectionKind::ReadOnlyData | SectionKind::ReadOnlyString
)
}) {
device_memory.add_memory_region(VecMemoryRegion::new(
section.address(),
section.uncompressed_data()?.to_vec(),
));
}
let endian = if elf.is_little_endian() {
gimli::RunTimeEndian::Little
} else {
gimli::RunTimeEndian::Big
};
fn load_section<'data: 'file, 'file, O, Endian>(
id: gimli::SectionId,
file: &'file O,
endian: Endian,
) -> Result<gimli::EndianRcSlice<Endian>, TraceError>
where
O: addr2line::object::Object<'data, 'file>,
Endian: gimli::Endianity,
{
let data = file
.section_by_name(id.name())
.and_then(|section| section.uncompressed_data().ok())
.unwrap_or(std::borrow::Cow::Borrowed(&[]));
Ok(gimli::EndianRcSlice::new(std::rc::Rc::from(&*data), endian))
}
let dwarf = gimli::Dwarf::load(|id| load_section(id, &elf, endian))?;
let mut frames = Vec::new();
let addr2line_context =
addr2line::Context::from_dwarf(gimli::Dwarf::load(|id| load_section(id, &elf, endian))?)?;
let mut platform_context = P::create_context(&elf)?;
let mut type_cache = Default::default();
loop {
match add_current_frames::<P>(
&device_memory,
&addr2line_context,
&mut frames,
&mut type_cache,
) {
Ok(_) => {}
Err(e @ TraceError::DwarfUnitNotFound { pc: _ }) => {
frames.push(Frame {
function: "Unknown".into(),
location: Location::default(),
frame_type: FrameType::Corrupted(e.to_string()),
variables: Vec::default(),
});
break;
}
Err(e) => return Err(e),
}
match platform_context.unwind(&mut device_memory, frames.last_mut())? {
UnwindResult::Finished => {
frames.push(Frame {
function: "RESET".into(),
location: crate::Location {
file: None,
line: None,
column: None,
},
frame_type: FrameType::Function,
variables: Vec::new(),
});
break;
}
UnwindResult::Corrupted {
error_frame: Some(error_frame),
} => {
frames.push(error_frame);
break;
}
UnwindResult::Corrupted { error_frame: None } => {
break;
}
UnwindResult::Proceeded => {
continue;
}
}
}
let static_variables =
crate::variables::find_static_variables(&dwarf, &device_memory, &mut type_cache)?;
let static_frame = Frame {
function: "Static".into(),
location: Location {
file: None,
line: None,
column: None,
},
frame_type: FrameType::Static,
variables: static_variables,
};
frames.push(static_frame);
Ok(frames)
}
fn add_current_frames<'a, P: Platform<'a>>(
device_memory: &DeviceMemory<P::Word>,
addr2line_context: &addr2line::Context<EndianRcSlice<RunTimeEndian>>,
frames: &mut Vec<Frame<P::Word>>,
type_cache: &mut HashMap<DebugInfoOffset, Result<TypeValueTree<P::Word>, TraceError>>,
) -> Result<(), TraceError>
where
<P::Word as funty::Numeric>::Bytes: bitvec::view::BitView<Store = u8>,
{
let mut context_frames = addr2line_context
.find_frames(device_memory.register(gimli::Arm::PC)?.as_u64())
.skip_all_loads()?;
let (dwarf, unit) = addr2line_context
.find_dwarf_and_unit(device_memory.register(gimli::Arm::PC)?.as_u64())
.skip_all_loads()
.ok_or(TraceError::DwarfUnitNotFound {
pc: device_memory.register(gimli::Arm::PC)?.as_u64(),
})?;
let abbreviations = dwarf.abbreviations(&unit.header)?;
let mut added_frames = 0;
while let Some(context_frame) = context_frames.next()? {
let (file, line, column) = context_frame
.location
.map(|l| {
(
l.file.map(|f| f.to_string()),
l.line.map(|line| line as _),
l.column.map(|column| column as _),
)
})
.unwrap_or_default();
let mut variables = Vec::new();
if let Some(die_offset) = context_frame.dw_die_offset {
let mut entries = match unit.header.entries_tree(&abbreviations, Some(die_offset)) {
Ok(entries) => entries,
Err(_) => {
continue;
}
};
if let Ok(entry_root) = entries.root() {
variables = crate::variables::find_variables_in_function(
dwarf,
unit,
&abbreviations,
device_memory,
entry_root,
type_cache,
)?;
}
}
frames.push(Frame {
function: context_frame
.function
.and_then(|f| f.demangle().ok().map(|f| f.into_owned()))
.unwrap_or_else(|| "UNKNOWN".into()),
location: crate::Location { file, line, column },
frame_type: FrameType::InlineFunction,
variables,
});
added_frames += 1;
}
if added_frames > 0 {
frames.last_mut().unwrap().frame_type = FrameType::Function;
}
Ok(())
}