use std::collections::BTreeMap;
use std::fmt::{Display, Formatter};
use std::ops::Bound::{Excluded, Included};
use symbolic::debuginfo::Object;
use crate::line::Lines;
const PDB_MAGIC: u64 = 7381245236781214029;
#[derive(Clone, Debug, Default)]
pub struct Symbol {
pub name: String,
pub is_public: bool,
pub is_multiple: bool,
pub is_synthetic: bool,
pub rva: u32,
pub len: u32,
pub parameter_size: u32,
pub source: Lines,
}
pub type Symbols = BTreeMap<u32, Symbol>;
pub trait ContainsSymbol {
fn is_inside_symbol(&self, rva: u32) -> bool;
}
impl ContainsSymbol for Symbols {
fn is_inside_symbol(&self, rva: u32) -> bool {
let last = self.range((Included(0), Excluded(rva))).next_back();
last.is_some_and(|last| rva < (last.1.rva + last.1.len))
}
}
impl Display for Symbol {
fn fmt(&self, f: &mut Formatter) -> std::fmt::Result {
if self.is_public {
writeln!(
f,
"PUBLIC {}{:x} {:x} {}",
if self.is_multiple { "m " } else { "" },
self.rva,
self.parameter_size,
self.name,
)?;
} else {
writeln!(
f,
"FUNC {}{:x} {:x} {:x} {}",
if self.is_multiple { "m " } else { "" },
self.rva,
self.len,
self.parameter_size,
self.name,
)?;
write!(f, "{}", self.source)?;
}
Ok(())
}
}
impl Symbol {
pub(super) fn remap_lines(&mut self, file_remapping: Option<&[u32]>) {
if let Some(file_remapping) = file_remapping {
for line in self.source.lines.iter_mut() {
line.file_id = file_remapping[line.file_id as usize];
}
}
}
pub(super) fn remap_inlines(
&mut self,
file_remapping: Option<&[u32]>,
inline_origin_remapping: &[u32],
) {
let inlines = std::mem::take(&mut self.source.inlines);
self.source.inlines = inlines
.into_iter()
.map(|(mut inline_site, address_ranges)| {
if let Some(file_remapping) = file_remapping {
inline_site.call_file_id = file_remapping[inline_site.call_file_id as usize];
}
inline_site.inline_origin_id =
inline_origin_remapping[inline_site.inline_origin_id as usize];
(inline_site, address_ranges)
})
.collect();
}
}
pub(super) fn add_executable_section_symbols(
mut syms: Symbols,
name: &str,
object: &Object,
) -> Symbols {
let object = goblin::Object::parse(object.data());
match object {
Ok(goblin::Object::Elf(elf)) => {
for header in elf.section_headers {
if header.is_executable() {
let name = if name.is_empty() { "unknown" } else { name };
let section_name = elf.shdr_strtab.get_at(header.sh_name).unwrap_or("unknown");
let symbol_name = format!("<{section_name} ELF section in {name}>");
let rva = header.sh_addr as u32;
syms.entry(rva).or_insert(Symbol {
name: symbol_name,
is_public: true,
is_multiple: false,
is_synthetic: true,
rva,
len: 0,
parameter_size: 0,
source: Lines::new(),
});
}
}
}
Ok(goblin::Object::PE(_)) | Ok(goblin::Object::Unknown(PDB_MAGIC)) => {
syms = append_dummy_symbol_pe_pdb(syms, name);
}
_ => (),
}
syms
}
fn append_dummy_symbol_pe_pdb(mut syms: Symbols, name: &str) -> Symbols {
let (rva, len) = if let Some((_, last_sym)) = syms.iter().next_back() {
(last_sym.rva, last_sym.len)
} else {
return syms;
};
let rva = if len == 0 { rva + 1 } else { rva + len };
let name = if name.is_empty() {
String::from("<unknown>")
} else {
format!("<unknown in {name}>")
};
syms.entry(rva).or_insert(Symbol {
name,
is_public: true,
is_multiple: false,
is_synthetic: true,
rva,
len: 0,
parameter_size: 0,
source: Lines::new(),
});
syms
}
pub(super) fn get_compressed_minidebuginfo(object: &Object) -> Option<Vec<u8>> {
let data = object.data();
let object = goblin::Object::parse(data);
if let Ok(goblin::Object::Elf(elf)) = object {
for header in elf.section_headers {
if let Some(section_name) = elf.shdr_strtab.get_at(header.sh_name) {
if section_name == ".gnu_debugdata" {
let (start, length) = (header.sh_offset as usize, header.sh_size as usize);
let mut buf: &[u8] = &data[start..(start + length)];
let mut out: Vec<u8> = Vec::new();
lzma_rs::xz_decompress(&mut buf, &mut out).ok()?;
return Some(out);
}
}
}
}
None
}
#[derive(Clone, Debug)]
pub struct ParsedWinFuncName {
pub name: String,
pub param_size: Option<u32>,
}
impl ParsedWinFuncName {
pub fn name_only(name: String) -> Self {
Self {
name,
param_size: None,
}
}
pub fn parse_unknown(name: &str) -> Self {
if name.contains([':', '(']) {
Self::name_only(name.to_string())
} else {
Self::parse_c_decorated(name)
}
}
pub fn parse_c_decorated(c_decorated_name: &str) -> Self {
if let Some((name, param_size)) = c_decorated_name.rsplit_once("@@") {
if let Ok(param_size) = param_size.parse::<u32>() {
return Self {
name: name.to_string(),
param_size: Some(param_size),
};
}
}
if !c_decorated_name.is_empty() {
if let ("@" | "_", rest) = c_decorated_name.split_at(1) {
if let Some((name, param_size)) = rest.rsplit_once('@') {
if let Ok(param_size) = param_size.parse::<u32>() {
return Self {
name: name.to_string(),
param_size: Some(param_size),
};
}
}
if &c_decorated_name[0..1] == "_" {
return Self::name_only(rest.to_string());
}
}
}
Self::name_only(c_decorated_name.to_string())
}
}
fn is_constant_string(name: &str) -> bool {
name.starts_with("??_C")
}
fn is_constant_number(name: &str) -> bool {
if let Some(name) = name.strip_prefix("__") {
name.starts_with("real@") || name.starts_with("xmm@") || name.starts_with("ymm@")
} else {
false
}
}
pub fn should_skip_symbol(name: &str) -> bool {
is_constant_string(name) || is_constant_number(name)
}