use std::{
fs,
io::{Cursor, Read as _},
};
use anyhow::{Context as _, Result};
use cargo_metadata::camino::Utf8Path;
use object::{
Object as _, ObjectSymbol as _, SymbolKind as ObjectSymbolKind,
SymbolScope as ObjectSymbolScope,
};
pub use capability::{Capability, CapabilitySet};
use crate::symbol::{Symbol, SymbolKind, SymbolScope};
mod build_graph_analysis;
mod cap_rule;
mod capability;
mod checker;
mod commands;
mod config;
mod crate_name;
mod demangle;
mod print;
mod reservoir_sample;
mod rust_path;
mod rust_type;
mod symbol;
mod tree;
pub use commands::Commands;
use crate_name::CrateName;
fn extract_symbols(binary_path: &Utf8Path) -> Result<Vec<Symbol>> {
let file_bytes =
fs::read(binary_path).with_context(|| format!("Failed to read {binary_path}"))?;
let is_ar_archive = file_bytes.starts_with(b"!<arch>\n");
let mut symbols = Vec::new();
if is_ar_archive {
let mut archive = ar::Archive::new(Cursor::new(file_bytes));
while let Some(entry_result) = archive.next_entry() {
let mut entry = entry_result.context("Failed to read archive entry")?;
let header = entry.header();
let filename = String::from_utf8_lossy(header.identifier());
if !filename.ends_with(".o") {
continue;
}
let mut obj_data = Vec::new();
entry
.read_to_end(&mut obj_data)
.context("Failed to read object file from archive")?;
if let Ok(file) = object::File::parse(&*obj_data) {
collect_file_symbols(&mut symbols, &file);
} else {
}
}
} else {
let file =
object::File::parse(&*file_bytes).with_context(|| "Failed to parse binary file")?;
collect_file_symbols(&mut symbols, &file);
}
Ok(symbols)
}
pub fn filter_symbols(
symbols: Vec<Symbol>,
include_local: bool,
include_all_kinds: bool,
) -> Vec<Symbol> {
symbols
.into_iter()
.filter(|symbol| {
let scope_allowed = include_local || !matches!(symbol.scope, SymbolScope::Compilation);
let kind_allowed = include_all_kinds
|| matches!(
symbol.kind,
SymbolKind::Text | SymbolKind::Label | SymbolKind::Unknown
);
scope_allowed && kind_allowed
})
.collect()
}
fn collect_file_symbols(all_symbols: &mut Vec<Symbol>, file: &object::File<'_>) {
for symbol in file.symbols() {
if let Ok(name) = symbol.name()
&& !name.is_empty()
{
let scope = match symbol.scope() {
ObjectSymbolScope::Unknown => SymbolScope::Unknown,
ObjectSymbolScope::Compilation => SymbolScope::Compilation,
ObjectSymbolScope::Linkage => SymbolScope::Linkage,
ObjectSymbolScope::Dynamic => SymbolScope::Dynamic,
};
#[expect(clippy::match_same_arms)]
let kind = match symbol.kind() {
ObjectSymbolKind::Unknown => SymbolKind::Unknown,
ObjectSymbolKind::Text => SymbolKind::Text,
ObjectSymbolKind::Data => SymbolKind::Data,
ObjectSymbolKind::Section => SymbolKind::Section,
ObjectSymbolKind::File => SymbolKind::File,
ObjectSymbolKind::Label => SymbolKind::Label,
ObjectSymbolKind::Tls => SymbolKind::Tls,
_ => SymbolKind::Unknown,
};
all_symbols.push(Symbol::with_metadata(name.to_owned(), scope, kind));
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_filter_symbols() {
let symbols = vec![
Symbol::with_metadata("func1".to_owned(), SymbolScope::Linkage, SymbolKind::Text),
Symbol::with_metadata(
"local_func".to_owned(),
SymbolScope::Compilation,
SymbolKind::Text,
),
Symbol::with_metadata(
"data_var".to_owned(),
SymbolScope::Linkage,
SymbolKind::Data,
),
Symbol::with_metadata("label1".to_owned(), SymbolScope::Dynamic, SymbolKind::Label),
Symbol::with_metadata(
"unknown_sym".to_owned(),
SymbolScope::Linkage,
SymbolKind::Unknown,
),
];
let filtered = filter_symbols(symbols.clone(), false, false);
assert_eq!(filtered.len(), 3);
let filtered = filter_symbols(symbols.clone(), true, false);
assert_eq!(filtered.len(), 4);
let filtered = filter_symbols(symbols.clone(), false, true);
assert_eq!(filtered.len(), 4);
let filtered = filter_symbols(symbols, true, true);
assert_eq!(filtered.len(), 5); }
}