use std::fmt;
use symbolic_common::{Arch, AsSelf, DebugId, Language, Name, NameMangling};
use crate::error::{SymCacheError, SymCacheErrorKind};
use crate::format;
pub struct SymCache<'a> {
header: format::Header,
data: &'a [u8],
}
impl<'a> SymCache<'a> {
pub fn parse(mut data: &'a [u8]) -> Result<Self, SymCacheError> {
let header = format::Header::parse(data)?;
if header.preamble.version == 1 {
let offset = std::mem::size_of::<format::HeaderV1>();
if data.len() > offset {
data = &data[offset..];
} else {
data = &[];
}
}
Ok(SymCache { header, data })
}
pub fn version(&self) -> u32 {
self.header.preamble.version
}
pub fn is_latest(&self) -> bool {
self.version() == format::SYMCACHE_VERSION
}
pub fn arch(&self) -> Arch {
Arch::from_u32(self.header.arch)
}
pub fn debug_id(&self) -> DebugId {
self.header.debug_id
}
pub fn has_line_info(&self) -> bool {
self.header.has_line_records != 0
}
pub fn has_file_info(&self) -> bool {
self.has_line_info()
}
pub fn functions(&self) -> Functions<'a> {
Functions {
functions: self.header.functions,
symbols: self.header.symbols,
files: self.header.files,
data: self.data,
index: 0,
}
}
pub fn lookup(&self, addr: u64) -> Result<Lookup<'a, '_>, SymCacheError> {
let funcs = self.function_records()?;
let mut current_id = match funcs.binary_search_by_key(&addr, format::FuncRecord::addr_start)
{
Ok(index) => index,
Err(0) => return Ok(Lookup::empty(self)),
Err(next) => next - 1,
};
while let Some(current_fn) = funcs.get(current_id + 1) {
if current_fn.addr_start() != funcs[current_id].addr_start() {
break;
}
current_id += 1;
}
let mut closest = None;
let mut last_id = current_id;
loop {
let current_fn = &funcs[current_id];
if current_fn.addr_in_range(addr) {
let current_addr = self
.run_to_line(current_fn, addr)?
.map_or(current_fn.addr_start(), |(line_addr, _, _)| line_addr);
if closest.map_or(true, |(_, _, a)| current_addr > a) {
closest = Some((current_id, current_fn, current_addr));
}
}
if let Some(parent_id) = current_fn.parent(current_id) {
last_id = parent_id.min(last_id);
}
if current_id == 0 || current_id == last_id {
break;
}
current_id -= 1;
}
let (closest_id, closest_fn) = match closest {
Some((closest_id, closest_fn, _)) => (closest_id, closest_fn),
None => return Ok(Lookup::empty(self)),
};
Ok(Lookup {
cache: self,
funcs,
current: Some((addr, closest_id, closest_fn)),
inner: None,
})
}
fn function_records(&self) -> Result<&'a [format::FuncRecord], SymCacheError> {
self.header.functions.read(self.data)
}
fn run_to_line(
&self,
fun: &format::FuncRecord,
addr: u64,
) -> Result<Option<(u64, u16, u32)>, SymCacheError> {
let records = fun.line_records.read(self.data)?;
if records.is_empty() {
return Ok(None);
}
let mut file_id = records[0].file_id;
let mut line = u32::from(records[0].line);
let mut running_addr = fun.addr_start();
let mut line_addr = running_addr;
for rec in records {
running_addr += u64::from(rec.addr_off);
if running_addr > addr {
break;
}
if u32::from(rec.line) != line {
line_addr = running_addr;
}
line = u32::from(rec.line);
file_id = rec.file_id;
}
Ok(Some((line_addr, file_id, line)))
}
fn build_line_info(
&self,
fun: &'a format::FuncRecord,
addr: u64,
inner_sym: Option<(u32, u64, &'a str, &'a str)>,
) -> Result<LineInfo<'a>, SymCacheError> {
let (line, line_addr, filename, base_dir) =
if let Some((line_addr, file_id, line)) = self.run_to_line(fun, addr)? {
let file_record = read_file_record(self.data, self.header.files, file_id)?
.ok_or(SymCacheErrorKind::BadCacheFile)?;
(
line,
line_addr,
file_record.filename.read_str(self.data)?,
file_record.base_dir.read_str(self.data)?,
)
} else if let Some(inner_sym) = inner_sym {
inner_sym
} else {
(0, 0, "", "")
};
Ok(LineInfo {
arch: self.arch(),
debug_id: self.debug_id(),
sym_addr: fun.addr_start(),
line_addr,
instr_addr: addr,
line,
lang: Language::from_u32(fun.lang.into()),
symbol: read_symbol(self.data, self.header.symbols, fun.symbol_id())?,
filename,
base_dir,
comp_dir: fun.comp_dir.read_str(self.data)?,
})
}
}
impl<'slf, 'd: 'slf> AsSelf<'slf> for SymCache<'d> {
type Ref = SymCache<'slf>;
fn as_self(&'slf self) -> &Self::Ref {
self
}
}
impl fmt::Debug for SymCache<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("SymCache")
.field("debug_id", &self.debug_id())
.field("arch", &self.arch())
.field("has_line_info", &self.has_line_info())
.field("has_file_info", &self.has_file_info())
.field("functions", &self.function_records().unwrap_or(&[]).len())
.finish()
}
}
#[derive(Clone)]
pub struct Lookup<'a, 'c> {
cache: &'c SymCache<'a>,
funcs: &'a [format::FuncRecord],
current: Option<(u64, usize, &'a format::FuncRecord)>,
inner: Option<(u32, u64, &'a str, &'a str)>,
}
impl<'a, 'c> Lookup<'a, 'c> {
fn empty(cache: &'c SymCache<'a>) -> Self {
Lookup {
cache,
funcs: &[],
current: None,
inner: None,
}
}
pub fn collect<B>(self) -> Result<B, SymCacheError>
where
B: std::iter::FromIterator<LineInfo<'a>>,
{
Iterator::collect(self)
}
}
impl<'a, 'c> Iterator for Lookup<'a, 'c> {
type Item = Result<LineInfo<'a>, SymCacheError>;
fn next(&mut self) -> Option<Self::Item> {
let (addr, id, fun) = self.current?;
let line_result = self.cache.build_line_info(fun, addr, None);
self.current = fun
.parent(id)
.map(|parent_id| (addr, parent_id, &self.funcs[parent_id]));
if let Ok(ref line_info) = line_result {
self.inner = Some((
line_info.line(),
line_info.line_address(),
line_info.filename(),
line_info.compilation_dir(),
));
}
Some(line_result)
}
}
impl fmt::Debug for Lookup<'_, '_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut list = f.debug_list();
for line in self.clone() {
match line {
Ok(line) => {
list.entry(&line);
}
Err(error) => {
return error.fmt(f);
}
}
}
list.finish()
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct LineInfo<'a> {
arch: Arch,
debug_id: DebugId,
sym_addr: u64,
line_addr: u64,
instr_addr: u64,
line: u32,
lang: Language,
symbol: Option<&'a str>,
filename: &'a str,
base_dir: &'a str,
comp_dir: &'a str,
}
impl<'a> LineInfo<'a> {
pub fn arch(&self) -> Arch {
self.arch
}
pub fn debug_id(&self) -> DebugId {
self.debug_id
}
pub fn function_address(&self) -> u64 {
self.sym_addr
}
pub fn line_address(&self) -> u64 {
self.line_addr
}
pub fn instruction_address(&self) -> u64 {
self.instr_addr
}
pub fn compilation_dir(&self) -> &'a str {
self.comp_dir
}
pub fn base_dir(&self) -> &str {
self.base_dir
}
pub fn filename(&self) -> &'a str {
self.filename
}
pub fn path(&self) -> String {
let joined = symbolic_common::join_path(self.base_dir, self.filename);
symbolic_common::clean_path(&joined).into_owned()
}
pub fn abs_path(&self) -> String {
let joined_path = symbolic_common::join_path(self.base_dir, self.filename);
let joined = symbolic_common::join_path(self.comp_dir, &joined_path);
symbolic_common::clean_path(&joined).into_owned()
}
pub fn line(&self) -> u32 {
self.line
}
pub fn language(&self) -> Language {
self.lang
}
pub fn symbol(&self) -> &'a str {
self.symbol.unwrap_or("?")
}
pub fn function_name(&self) -> Name<'_> {
Name::new(self.symbol(), NameMangling::Unknown, self.language())
}
}
impl fmt::Display for LineInfo<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.function_name())?;
if f.alternate() {
let path = self.path();
let line = self.line();
let lang = self.language();
if !path.is_empty() || line != 0 || lang != Language::Unknown {
write!(f, "\n ")?;
if !path.is_empty() {
write!(f, " at {}", path)?;
}
if line != 0 {
write!(f, " line {}", line)?;
}
if lang != Language::Unknown {
write!(f, " lang {}", lang)?;
}
}
}
Ok(())
}
}
#[derive(Clone, Debug)]
pub struct Functions<'a> {
functions: format::Seg<format::FuncRecord>,
symbols: format::Seg<format::Seg<u8, u16>>,
files: format::Seg<format::FileRecord, u16>,
data: &'a [u8],
index: u32,
}
impl<'a> Iterator for Functions<'a> {
type Item = Result<Function<'a>, SymCacheError>;
fn next(&mut self) -> Option<Self::Item> {
let record = match self.functions.get(self.data, self.index) {
Ok(Some(record)) => record,
Ok(None) => return None,
Err(error) => return Some(Err(error)),
};
let function = Some(Ok(Function {
record,
symbols: self.symbols,
files: self.files,
data: self.data,
index: self.index,
}));
self.index += 1;
function
}
}
#[derive(Clone)]
pub struct Function<'a> {
record: &'a format::FuncRecord,
symbols: format::Seg<format::Seg<u8, u16>>,
files: format::Seg<format::FileRecord, u16>,
data: &'a [u8],
index: u32,
}
impl<'a> Function<'a> {
pub fn id(&self) -> usize {
self.index as usize
}
pub fn parent_id(&self) -> Option<usize> {
self.record.parent(self.id())
}
pub fn address(&self) -> u64 {
self.record.addr_start()
}
pub fn symbol(&self) -> &'a str {
read_symbol(self.data, self.symbols, self.record.symbol_id())
.unwrap_or(None)
.unwrap_or("?")
}
pub fn language(&self) -> Language {
Language::from_u32(self.record.lang.into())
}
pub fn name(&self) -> Name<'_> {
Name::new(self.symbol(), NameMangling::Unknown, self.language())
}
pub fn compilation_dir(&self) -> &str {
self.record.comp_dir.read_str(self.data).unwrap_or("")
}
pub fn lines(&self) -> Lines<'a> {
Lines {
lines: self.record.line_records,
files: self.files,
data: self.data,
address: 0,
index: 0,
}
}
}
struct LinesDebug<'a>(Lines<'a>);
impl fmt::Debug for LinesDebug<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut list = f.debug_list();
for line in self.0.clone() {
match line {
Ok(line) => {
list.entry(&line);
}
Err(error) => {
return error.fmt(f);
}
}
}
list.finish()
}
}
impl fmt::Debug for Function<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Function")
.field("id", &self.id())
.field("parent_id", &self.parent_id())
.field("symbol", &self.symbol())
.field("address", &self.address())
.field("compilation_dir", &self.compilation_dir())
.field("language", &self.language())
.field("lines", &LinesDebug(self.lines()))
.finish()
}
}
#[derive(Clone)]
pub struct Lines<'a> {
lines: format::Seg<format::LineRecord, u16>,
files: format::Seg<format::FileRecord, u16>,
data: &'a [u8],
address: u64,
index: u16,
}
impl<'a> Iterator for Lines<'a> {
type Item = Result<Line<'a>, SymCacheError>;
fn next(&mut self) -> Option<Self::Item> {
let record = match self.lines.get(self.data, self.index) {
Ok(Some(record)) => record,
Ok(None) => return None,
Err(error) => return Some(Err(error)),
};
self.address += u64::from(record.addr_off);
self.index += 1;
Some(Ok(Line {
record,
file: read_file_record(self.data, self.files, record.file_id).unwrap_or(None),
address: self.address,
data: self.data,
}))
}
}
pub struct Line<'a> {
record: &'a format::LineRecord,
file: Option<&'a format::FileRecord>,
data: &'a [u8],
address: u64,
}
impl<'a> Line<'a> {
pub fn address(&self) -> u64 {
self.address
}
pub fn line(&self) -> u16 {
self.record.line
}
pub fn base_dir(&self) -> &str {
match self.file {
Some(ref record) => record.base_dir.read_str(self.data).unwrap_or(""),
None => "",
}
}
pub fn filename(&self) -> &'a str {
match self.file {
Some(ref record) => record.filename.read_str(self.data).unwrap_or(""),
None => "",
}
}
}
impl fmt::Debug for Line<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Line")
.field("address", &self.address())
.field("line", &self.line())
.field("base_dir", &self.base_dir())
.field("filename", &self.filename())
.finish()
}
}
fn read_symbol(
data: &[u8],
symbols: format::Seg<format::Seg<u8, u16>>,
index: u32,
) -> Result<Option<&str>, SymCacheError> {
if index == !0 {
Ok(None)
} else if let Some(symbol) = symbols.get(data, index)? {
symbol.read_str(data).map(Some)
} else {
Ok(None)
}
}
fn read_file_record(
data: &[u8],
files: format::Seg<format::FileRecord, u16>,
index: u16,
) -> Result<Option<&format::FileRecord>, SymCacheError> {
if index == !0 {
Ok(None)
} else {
files.get(data, index)
}
}