pub use crate::call_graph::{CallGraph, CallerInfo};
pub use crate::crate_line_table::{CrateLineEntry, CrateLineTable};
pub use crate::debug_info::load_debug_info;
pub use crate::debug_info::{DSymInfo, DebugInfo, DebugMapInfo, ObjectFileInfo};
pub use crate::full_line_table::{FullLineEntry, FullLineTable};
pub(crate) use crate::function_index::resolve_line_file_path;
pub use crate::function_index::{FunctionIndex, FunctionInfo, get_functions_from_dwarf};
pub use crate::library_call_graph::LibraryCallGraph;
pub use crate::project_context::ProjectContext;
pub use crate::string_tables::StringTables;
use crate::binary_format::BinaryRef;
use goblin::Object;
use goblin::elf::Elf;
use goblin::mach::MachO;
use regex::Regex;
use rustc_demangle::demangle;
use std::io;
#[allow(clippy::large_enum_variant)]
pub enum SymbolTable<'a> {
MachO(goblin::mach::Mach<'a>),
Elf(goblin::elf::Elf<'a>),
Archive(goblin::archive::Archive<'a>),
}
impl<'a> SymbolTable<'a> {
pub fn from(buffer: &'a [u8]) -> io::Result<Self> {
match Object::parse(buffer).map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))? {
Object::Mach(mach) => Ok(SymbolTable::MachO(mach)),
Object::Elf(elf) => Ok(SymbolTable::Elf(elf)),
Object::Archive(archive) => Ok(SymbolTable::Archive(archive)),
Object::PE(_) => Err(io::Error::new(
io::ErrorKind::InvalidData,
"PE format not supported",
)),
Object::COFF(_) => Err(io::Error::new(
io::ErrorKind::InvalidData,
"COFF format not supported",
)),
_ => Err(io::Error::new(
io::ErrorKind::InvalidData,
"Unknown binary format",
)),
}
}
pub fn macho(&self) -> Option<&MachO<'_>> {
match self {
SymbolTable::MachO(goblin::mach::Mach::Binary(macho)) => Some(macho),
_ => None,
}
}
pub fn find_symbol_containing(
&self,
pattern: &str,
) -> Result<Option<(String, String)>, regex::Error> {
let regex = Regex::new(pattern)?;
match self {
SymbolTable::MachO(goblin::mach::Mach::Binary(macho)) => {
let symbols = match macho.symbols.as_ref() {
Some(s) => s,
None => return Ok(None),
};
for (sym_name, _) in symbols.iter().flatten() {
let stripped = sym_name.strip_prefix("_").unwrap_or(sym_name);
let demangled = format!("{:#}", demangle(stripped));
if regex.is_match(&demangled) {
return Ok(Some((sym_name.to_string(), demangled)));
}
}
Ok(None)
}
SymbolTable::Elf(elf) => {
for sym in elf.syms.iter() {
if sym.st_value == 0 || sym.st_shndx == 0 {
continue;
}
if let Some(name) = elf.strtab.get_at(sym.st_name) {
if name.is_empty() {
continue;
}
let demangled = format!("{:#}", demangle(name));
if regex.is_match(&demangled) {
return Ok(Some((name.to_string(), demangled)));
}
}
}
Ok(None)
}
_ => Ok(None),
}
}
pub fn find_all_symbols_matching(
&self,
patterns: &[&str],
) -> Result<Vec<(String, String)>, regex::Error> {
let regexes: Vec<Regex> = patterns
.iter()
.map(|p| Regex::new(p))
.collect::<Result<Vec<_>, _>>()?;
let mut results = Vec::new();
match self {
SymbolTable::MachO(goblin::mach::Mach::Binary(macho)) => {
let symbols = match macho.symbols.as_ref() {
Some(s) => s,
None => return Ok(results),
};
for (sym_name, _) in symbols.iter().flatten() {
let stripped = sym_name.strip_prefix("_").unwrap_or(sym_name);
let demangled = format!("{:#}", demangle(stripped));
for regex in ®exes {
if regex.is_match(&demangled) {
results.push((sym_name.to_string(), demangled.clone()));
break;
}
}
}
}
SymbolTable::Elf(elf) => {
for sym in elf.syms.iter() {
if sym.st_value == 0 || sym.st_shndx == 0 {
continue;
}
if let Some(name) = elf.strtab.get_at(sym.st_name) {
if name.is_empty() {
continue;
}
let demangled = format!("{:#}", demangle(name));
for regex in ®exes {
if regex.is_match(&demangled) {
results.push((name.to_string(), demangled.clone()));
break;
}
}
}
}
}
_ => {}
}
Ok(results)
}
pub fn find_all_symbols_with_addresses(
&self,
patterns: &[&str],
) -> Result<Vec<(String, String, u64)>, regex::Error> {
let regexes: Vec<Regex> = patterns
.iter()
.map(|p| Regex::new(p))
.collect::<Result<Vec<_>, _>>()?;
let mut results = Vec::new();
match self {
SymbolTable::MachO(goblin::mach::Mach::Binary(macho)) => {
if let Some(symbols) = macho.symbols.as_ref() {
for (sym_name, nlist) in symbols.iter().flatten() {
if nlist.is_undefined() || nlist.n_value == 0 {
continue;
}
let stripped = sym_name.strip_prefix("_").unwrap_or(sym_name);
let demangled = format!("{:#}", demangle(stripped));
for regex in ®exes {
if regex.is_match(&demangled) {
results.push((
sym_name.to_string(),
demangled.clone(),
nlist.n_value,
));
break;
}
}
}
}
}
SymbolTable::Elf(elf) => {
for sym in elf.syms.iter() {
if sym.st_value == 0 || sym.st_shndx == 0 {
continue;
}
if let Some(name) = elf.strtab.get_at(sym.st_name) {
if name.is_empty() {
continue;
}
let demangled = format!("{:#}", demangle(name));
for regex in ®exes {
if regex.is_match(&demangled) {
results.push((name.to_string(), demangled.clone(), sym.st_value));
break;
}
}
}
}
}
_ => {}
}
Ok(results)
}
pub fn find_symbol_address(&self, name: &str) -> Option<u64> {
match self {
SymbolTable::MachO(goblin::mach::Mach::Binary(macho)) => {
let symbols = macho.symbols.as_ref()?;
for symbol in symbols.iter() {
if let Ok((sym_name, nlist)) = symbol
&& sym_name == name
&& !nlist.is_undefined()
&& nlist.n_value != 0
{
return Some(nlist.n_value);
}
}
None
}
SymbolTable::Elf(elf) => {
for sym in elf.syms.iter() {
if sym.st_value == 0 || sym.st_shndx == 0 {
continue;
}
if let Some(sym_name) = elf.strtab.get_at(sym.st_name) {
if sym_name == name {
return Some(sym.st_value);
}
}
}
None
}
_ => None,
}
}
}
#[derive(Debug)]
struct SymbolEntry {
address: u64,
mangled: String,
demangled: std::sync::OnceLock<String>,
}
impl SymbolEntry {
fn new(address: u64, mangled: String) -> Self {
Self {
address,
mangled,
demangled: std::sync::OnceLock::new(),
}
}
fn demangled(&self) -> &str {
self.demangled
.get_or_init(|| format!("{:#}", demangle(&self.mangled)))
}
}
#[derive(Debug)]
pub struct SymbolIndex {
entries: Vec<SymbolEntry>,
}
impl SymbolIndex {
pub fn new(macho: &MachO) -> Option<Self> {
use rayon::prelude::*;
let symbols = macho.symbols.as_ref()?;
let raw_symbols: Vec<(u64, &str)> = symbols
.iter()
.filter_map(|s| s.ok())
.filter(|(name, nlist)| !nlist.is_undefined() && !name.is_empty())
.map(|(name, nlist)| (nlist.n_value, name))
.collect();
let mut entries: Vec<SymbolEntry> = raw_symbols
.par_iter()
.map(|(addr, name)| {
let stripped = name.strip_prefix("_").unwrap_or(name);
SymbolEntry::new(*addr, stripped.to_string())
})
.collect();
entries.par_sort_by_key(|e| e.address);
Some(Self { entries })
}
pub fn from_elf(elf: &Elf) -> Option<Self> {
use rayon::prelude::*;
let raw_symbols: Vec<(u64, String)> = elf
.syms
.iter()
.filter_map(|sym| {
if sym.st_value == 0 || sym.st_shndx == 0 {
return None;
}
elf.strtab.get_at(sym.st_name).map(|name| {
(sym.st_value, name.to_string())
})
})
.filter(|(_, name)| !name.is_empty())
.collect();
if raw_symbols.is_empty() {
return None;
}
let mut entries: Vec<SymbolEntry> = raw_symbols
.par_iter()
.map(|(addr, name)| SymbolEntry::new(*addr, name.clone()))
.collect();
entries.par_sort_by_key(|e| e.address);
Some(Self { entries })
}
pub fn from_binary(binary: &BinaryRef) -> Option<Self> {
match binary {
BinaryRef::MachO(macho) => Self::new(macho),
BinaryRef::Elf(elf) => Self::from_elf(elf),
}
}
pub fn find_containing(&self, addr: u64) -> Option<(u64, &str)> {
match self.entries.binary_search_by_key(&addr, |e| e.address) {
Ok(i) => Some((self.entries[i].address, self.entries[i].demangled())),
Err(0) => None, Err(i) => Some((self.entries[i - 1].address, self.entries[i - 1].demangled())),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_symbol_entry_demangling() {
let entry = SymbolEntry::new(0x1000, "std::io::Read::read".to_string());
assert_eq!(entry.demangled(), "std::io::Read::read");
assert_eq!(entry.demangled(), "std::io::Read::read");
}
#[test]
fn test_symbol_entry_mangled_rust_symbol() {
let entry = SymbolEntry::new(
0x2000,
"ZN3std2io4Read4read17h1234567890abcdefE".to_string(),
);
let name = entry.demangled();
assert!(!name.contains("h1234567890abcdef"));
}
#[test]
fn test_symbol_entry_debug_format() {
let entry = SymbolEntry::new(0x1000, "main".to_string());
let debug = format!("{:?}", entry);
assert!(debug.contains("address: 4096")); assert!(debug.contains("main"));
}
#[test]
fn test_symbol_table_from_invalid_data() {
let result = SymbolTable::from(b"not a valid binary");
assert!(result.is_err());
}
#[test]
fn test_symbol_table_from_empty_data() {
let result = SymbolTable::from(b"");
assert!(result.is_err());
}
#[test]
fn test_symbol_table_from_real_binary() {
let binary_path = format!("{}/target/debug/jonesy", env!("CARGO_MANIFEST_DIR"));
if let Ok(buffer) = std::fs::read(&binary_path) {
let symbols = SymbolTable::from(&buffer);
assert!(symbols.is_ok());
let symbols = symbols.unwrap();
assert!(symbols.macho().is_some());
}
}
#[test]
fn test_find_symbol_containing_on_real_binary() {
let binary_path = format!("{}/target/debug/jonesy", env!("CARGO_MANIFEST_DIR"));
if let Ok(buffer) = std::fs::read(&binary_path) {
if let Ok(symbols) = SymbolTable::from(&buffer) {
let result = symbols.find_symbol_containing("rust_panic$");
assert!(result.is_ok());
if let Ok(Some((_mangled, demangled))) = result {
assert!(demangled.contains("rust_panic"));
}
}
}
}
#[test]
fn test_find_symbol_containing_no_match() {
let binary_path = format!("{}/target/debug/jonesy", env!("CARGO_MANIFEST_DIR"));
if let Ok(buffer) = std::fs::read(&binary_path) {
if let Ok(symbols) = SymbolTable::from(&buffer) {
let result = symbols.find_symbol_containing("zzz_nonexistent_symbol_zzz$");
assert!(result.is_ok());
assert!(result.unwrap().is_none());
}
}
}
#[test]
fn test_find_symbol_containing_invalid_regex() {
let binary_path = format!("{}/target/debug/jonesy", env!("CARGO_MANIFEST_DIR"));
if let Ok(buffer) = std::fs::read(&binary_path) {
if let Ok(symbols) = SymbolTable::from(&buffer) {
let result = symbols.find_symbol_containing("[invalid regex");
assert!(result.is_err());
}
}
}
#[test]
fn test_find_all_symbols_matching() {
let binary_path = format!("{}/target/debug/jonesy", env!("CARGO_MANIFEST_DIR"));
if let Ok(buffer) = std::fs::read(&binary_path) {
if let Ok(symbols) = SymbolTable::from(&buffer) {
let result =
symbols.find_all_symbols_matching(&["rust_panic$", "zzz_no_match_zzz"]);
assert!(result.is_ok());
let matches = result.unwrap();
assert!(
matches.iter().any(|(_, d)| d.contains("rust_panic")),
"Should find rust_panic"
);
}
}
}
#[test]
fn test_find_symbol_address() {
let binary_path = format!("{}/target/debug/jonesy", env!("CARGO_MANIFEST_DIR"));
if let Ok(buffer) = std::fs::read(&binary_path) {
if let Ok(symbols) = SymbolTable::from(&buffer) {
if let Ok(Some((mangled, _))) = symbols.find_symbol_containing("rust_panic$") {
let addr = symbols.find_symbol_address(&mangled);
assert!(addr.is_some(), "Should find address for rust_panic");
assert!(addr.unwrap() > 0);
}
}
}
}
#[test]
fn test_find_symbol_address_nonexistent() {
let binary_path = format!("{}/target/debug/jonesy", env!("CARGO_MANIFEST_DIR"));
if let Ok(buffer) = std::fs::read(&binary_path) {
if let Ok(symbols) = SymbolTable::from(&buffer) {
assert!(symbols.find_symbol_address("_zzz_nonexistent").is_none());
}
}
}
#[test]
fn test_symbol_index_from_real_binary() {
let binary_path = format!("{}/target/debug/jonesy", env!("CARGO_MANIFEST_DIR"));
if let Ok(buffer) = std::fs::read(&binary_path) {
if let Ok(symbols) = SymbolTable::from(&buffer) {
if let Some(macho) = symbols.macho() {
let index = SymbolIndex::new(macho);
assert!(index.is_some(), "Should build symbol index from binary");
}
}
}
}
#[test]
fn test_symbol_index_find_containing() {
let binary_path = format!("{}/target/debug/jonesy", env!("CARGO_MANIFEST_DIR"));
if let Ok(buffer) = std::fs::read(&binary_path) {
if let Ok(symbols) = SymbolTable::from(&buffer) {
if let Some(macho) = symbols.macho() {
if let Some(index) = SymbolIndex::new(macho) {
assert!(index.find_containing(0).is_none());
if let Some((addr, name)) = index.find_containing(0x100000000) {
assert!(addr > 0);
assert!(!name.is_empty());
}
}
}
}
}
}
}