202 lines
6.4 KiB
Rust
202 lines
6.4 KiB
Rust
use std::{borrow::Cow, path::Path, sync::Arc};
|
|
|
|
use anyhow::{Result, bail};
|
|
use fxprof_processed_profile::{LibraryInfo, Symbol, SymbolTable, debugid::DebugId};
|
|
use object::{Object, ObjectSection, ObjectSymbol};
|
|
use wholesym::samply_symbols::{DebugIdExt, demangle_any};
|
|
|
|
use crate::emulator::inline_stack_map::{InlineStackMap, InlineStackMapBuilder};
|
|
|
|
#[derive(Debug)]
|
|
pub struct GameInfo {
|
|
library_info: LibraryInfo,
|
|
inline_stack_map: InlineStackMap,
|
|
}
|
|
|
|
impl GameInfo {
|
|
pub fn new(file_path: &Path, input: &[u8]) -> Result<Self> {
|
|
let file = object::File::parse(input)?;
|
|
|
|
let (name, path) = name_and_path(file_path);
|
|
let debug_id = file
|
|
.build_id()?
|
|
.map(|id| DebugId::from_identifier(id, true))
|
|
.unwrap_or_default();
|
|
let code_id = file.build_id()?.map(hex::encode);
|
|
let mut symbols = vec![];
|
|
for sym in file.symbols() {
|
|
symbols.push(Symbol {
|
|
address: sym.address() as u32,
|
|
size: Some(sym.size() as u32),
|
|
name: demangle_any(sym.name()?),
|
|
});
|
|
}
|
|
|
|
let inline_stack_map =
|
|
build_inline_stack_map(file).unwrap_or_else(|_| InlineStackMap::empty());
|
|
|
|
let library_info = LibraryInfo {
|
|
name: name.clone(),
|
|
debug_name: name,
|
|
path: path.clone(),
|
|
debug_path: path,
|
|
debug_id,
|
|
code_id,
|
|
arch: None,
|
|
symbol_table: Some(Arc::new(SymbolTable::new(symbols))),
|
|
};
|
|
|
|
Ok(Self {
|
|
library_info,
|
|
inline_stack_map,
|
|
})
|
|
}
|
|
|
|
pub fn empty(file_path: &Path) -> Self {
|
|
let (name, path) = name_and_path(file_path);
|
|
let library_info = LibraryInfo {
|
|
name: name.clone(),
|
|
debug_name: name,
|
|
path: path.clone(),
|
|
debug_path: path,
|
|
debug_id: DebugId::default(),
|
|
code_id: None,
|
|
arch: None,
|
|
symbol_table: None,
|
|
};
|
|
let inline_stack_map = InlineStackMap::empty();
|
|
Self {
|
|
library_info,
|
|
inline_stack_map,
|
|
}
|
|
}
|
|
|
|
pub fn name(&self) -> &str {
|
|
&self.library_info.name
|
|
}
|
|
|
|
pub fn library_info(&self) -> &LibraryInfo {
|
|
&self.library_info
|
|
}
|
|
|
|
pub fn inline_stack_map(&self) -> &InlineStackMap {
|
|
&self.inline_stack_map
|
|
}
|
|
}
|
|
|
|
fn build_inline_stack_map(file: object::File) -> Result<InlineStackMap> {
|
|
let endian = if file.is_little_endian() {
|
|
gimli::RunTimeEndian::Little
|
|
} else {
|
|
gimli::RunTimeEndian::Big
|
|
};
|
|
fn load_section<'a>(file: &'a object::File, id: gimli::SectionId) -> Result<Cow<'a, [u8]>> {
|
|
let input = match file.section_by_name(id.name()) {
|
|
Some(section) => section.uncompressed_data()?,
|
|
None => Cow::Owned(vec![]),
|
|
};
|
|
Ok(input)
|
|
}
|
|
let dorf = gimli::DwarfSections::load(|id| load_section(&file, id))?;
|
|
let dorf = dorf.borrow(|sec| gimli::EndianSlice::new(sec, endian));
|
|
let mut units = dorf.units();
|
|
let mut frames = InlineStackMap::builder();
|
|
while let Some(header) = units.next()? {
|
|
let unit = dorf.unit(header)?;
|
|
let mut entree = unit.entries_tree(None)?;
|
|
let root = entree.root()?;
|
|
let mut ctx = ParseContext {
|
|
dorf: &dorf,
|
|
unit: &unit,
|
|
frames: &mut frames,
|
|
};
|
|
parse_inline(&mut ctx, root)?;
|
|
}
|
|
Ok(frames.build())
|
|
}
|
|
|
|
type Reader<'a> = gimli::EndianSlice<'a, gimli::RunTimeEndian>;
|
|
|
|
struct ParseContext<'a> {
|
|
dorf: &'a gimli::Dwarf<Reader<'a>>,
|
|
unit: &'a gimli::Unit<Reader<'a>>,
|
|
frames: &'a mut InlineStackMapBuilder,
|
|
}
|
|
impl ParseContext<'_> {
|
|
fn name_attr(&self, attr: gimli::AttributeValue<Reader>) -> Result<Option<String>> {
|
|
match attr {
|
|
gimli::AttributeValue::DebugInfoRef(offset) => {
|
|
let mut units = self.dorf.units();
|
|
while let Some(header) = units.next()? {
|
|
if let Some(offset) = offset.to_unit_offset(&header) {
|
|
let unit = self.dorf.unit(header)?;
|
|
return self.name_entry(&unit, offset);
|
|
}
|
|
}
|
|
Ok(None)
|
|
}
|
|
gimli::AttributeValue::UnitRef(offset) => self.name_entry(self.unit, offset),
|
|
other => {
|
|
bail!("unrecognized attr {other:?}");
|
|
}
|
|
}
|
|
}
|
|
|
|
fn name_entry(
|
|
&self,
|
|
unit: &gimli::Unit<Reader>,
|
|
offset: gimli::UnitOffset,
|
|
) -> Result<Option<String>> {
|
|
let abbreviations = self.dorf.abbreviations(&unit.header)?;
|
|
let mut entries = unit.header.entries_raw(&abbreviations, Some(offset))?;
|
|
let Some(abbrev) = entries.read_abbreviation()? else {
|
|
return Ok(None);
|
|
};
|
|
let mut name = None;
|
|
for spec in abbrev.attributes() {
|
|
let attr = entries.read_attribute(*spec)?;
|
|
if attr.name() == gimli::DW_AT_linkage_name
|
|
|| (attr.name() == gimli::DW_AT_name && name.is_none())
|
|
{
|
|
name = Some(self.dorf.attr_string(unit, attr.value())?)
|
|
}
|
|
}
|
|
Ok(name.map(|n| demangle_any(&String::from_utf8_lossy(&n))))
|
|
}
|
|
}
|
|
|
|
fn parse_inline(ctx: &mut ParseContext, node: gimli::EntriesTreeNode<Reader>) -> Result<()> {
|
|
if node.entry().tag() == gimli::DW_TAG_inlined_subroutine
|
|
&& let Some(attr) = node.entry().attr_value(gimli::DW_AT_abstract_origin)?
|
|
&& let Some(name) = ctx.name_attr(attr)?
|
|
{
|
|
let name = Arc::new(name);
|
|
let mut ranges = ctx.dorf.die_ranges(ctx.unit, node.entry())?;
|
|
while let Some(range) = ranges.next()? {
|
|
let start = range.begin as u32;
|
|
let end = range.end as u32;
|
|
ctx.frames.add(start, end, name.clone());
|
|
}
|
|
}
|
|
let mut children = node.children();
|
|
while let Some(child) = children.next()? {
|
|
parse_inline(ctx, child)?;
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
fn name_and_path(file_path: &Path) -> (String, String) {
|
|
let normalized = normpath::PathExt::normalize(file_path);
|
|
let path = normalized
|
|
.as_ref()
|
|
.map(|n| n.as_path())
|
|
.unwrap_or(file_path);
|
|
|
|
let name = match path.file_stem() {
|
|
Some(s) => s.to_string_lossy().into_owned(),
|
|
None => "game".to_string(),
|
|
};
|
|
let path = path.to_string_lossy().into_owned();
|
|
(name, path)
|
|
}
|