use crate::{
core::{
mapping::ModuleMapping, CallerFrameRecovery, GlobalVariableInfo, ModuleAddress, Result,
SourceLocation,
},
objfile::LoadedObjfile,
};
use object::{Object, ObjectSection};
use std::collections::HashMap;
use std::path::{Path, PathBuf};
#[derive(Debug, Clone)]
pub enum ModuleLoadingEvent {
Discovered {
module_path: String,
current: usize,
total: usize,
},
LoadingStarted {
module_path: String,
current: usize,
total: usize,
},
LoadingCompleted {
module_path: String,
stats: ModuleLoadingStats,
current: usize,
total: usize,
},
LoadingFailed {
module_path: String,
error: String,
current: usize,
total: usize,
},
}
#[derive(Debug, Clone)]
pub struct ModuleLoadingStats {
pub functions: usize,
pub variables: usize,
pub types: usize,
pub load_time_ms: u64,
pub parse_time_ms: u64,
pub index_time_ms: u64,
pub module_total_time_ms: u64,
}
#[derive(Debug, Clone)]
pub struct AddressQueryResult {
pub module_path: PathBuf,
pub address: u64,
pub source_file: Option<String>,
pub source_line: Option<u32>,
pub source_column: Option<u32>,
pub function_name: Option<String>,
pub is_inline: Option<bool>,
pub variables: Vec<crate::VariableWithEvaluation>,
pub parameters: Vec<crate::VariableWithEvaluation>,
}
#[derive(Debug, Clone)]
pub struct FunctionQueryResult {
pub function_name: String,
pub addresses: Vec<AddressQueryResult>,
}
#[derive(Debug)]
pub struct DwarfAnalyzer {
pid: u32,
modules: HashMap<PathBuf, LoadedObjfile>,
}
impl DwarfAnalyzer {
fn resolve_type_shallow_by_name_in_module_with_tags<P: AsRef<Path>>(
&self,
module_path: P,
name: &str,
tags: &[gimli::DwTag],
) -> Option<crate::TypeInfo> {
let path_buf = module_path.as_ref().to_path_buf();
self.modules
.get(&path_buf)
.and_then(|module_data| module_data.resolve_type_shallow_by_name_with_tags(name, tags))
}
fn resolve_type_shallow_by_name_with_tags(
&self,
name: &str,
tags: &[gimli::DwTag],
) -> Option<crate::TypeInfo> {
self.modules
.values()
.find_map(|module_data| module_data.resolve_type_shallow_by_name_with_tags(name, tags))
}
fn build_address_query_result(
&self,
module_address: &ModuleAddress,
) -> Result<AddressQueryResult> {
let mut variables = Vec::new();
let mut parameters = Vec::new();
for variable in self.get_all_variables_at_address(module_address)? {
if variable.is_parameter {
parameters.push(variable);
} else {
variables.push(variable);
}
}
let source_location = self.lookup_source_location(module_address);
let function_name = self.find_function_name_by_module_address(module_address);
let is_inline = self.is_inline_at(module_address);
Ok(AddressQueryResult {
module_path: module_address.module_path.clone(),
address: module_address.address,
source_file: source_location.as_ref().map(|sl| sl.file_path.clone()),
source_line: source_location.as_ref().map(|sl| sl.line_number),
source_column: source_location.as_ref().and_then(|sl| sl.column),
function_name,
is_inline,
variables,
parameters,
})
}
fn query_module_addresses(
&self,
module_addresses: Vec<ModuleAddress>,
) -> Result<Vec<AddressQueryResult>> {
module_addresses
.iter()
.map(|module_address| self.build_address_query_result(module_address))
.collect()
}
fn query_module_addresses_best_effort(
&self,
module_addresses: Vec<ModuleAddress>,
query_label: &str,
) -> Result<Vec<AddressQueryResult>> {
let mut results = Vec::new();
let mut first_error: Option<(ModuleAddress, String)> = None;
for module_address in &module_addresses {
match self.build_address_query_result(module_address) {
Ok(result) => results.push(result),
Err(error) => {
let error_string = error.to_string();
tracing::warn!(
"Skipping failed address query for {} at {}:0x{:x}: {}",
query_label,
module_address.module_display(),
module_address.address,
error_string
);
if first_error.is_none() {
first_error = Some((module_address.clone(), error_string));
}
}
}
}
if results.is_empty() {
if let Some((module_address, error)) = first_error {
return Err(anyhow::anyhow!(
"Failed to analyze any address for {} (first failure at {}:0x{:x}: {})",
query_label,
module_address.module_display(),
module_address.address,
error
));
}
}
Ok(results)
}
fn find_function_name_by_module_address(
&self,
module_address: &ModuleAddress,
) -> Option<String> {
self.modules
.get(&module_address.module_path)
.and_then(|module_data| {
module_data.find_function_name_by_address(module_address.address)
})
}
pub async fn from_pid(pid: u32) -> Result<Self> {
Self::from_pid_parallel(pid).await
}
pub fn is_inline_at(&self, module_address: &ModuleAddress) -> Option<bool> {
if let Some(module_data) = self.modules.get(&module_address.module_path) {
module_data.is_inline_at(module_address.address)
} else {
None
}
}
pub fn resolve_struct_type_shallow_by_name_in_module<P: AsRef<Path>>(
&self,
module_path: P,
name: &str,
) -> Option<crate::TypeInfo> {
self.resolve_type_shallow_by_name_in_module_with_tags(
module_path,
name,
&[
gimli::constants::DW_TAG_structure_type,
gimli::constants::DW_TAG_class_type,
],
)
}
pub fn resolve_struct_type_shallow_by_name(&self, name: &str) -> Option<crate::TypeInfo> {
self.resolve_type_shallow_by_name_with_tags(
name,
&[
gimli::constants::DW_TAG_structure_type,
gimli::constants::DW_TAG_class_type,
],
)
}
pub fn resolve_union_type_shallow_by_name_in_module<P: AsRef<Path>>(
&self,
module_path: P,
name: &str,
) -> Option<crate::TypeInfo> {
self.resolve_type_shallow_by_name_in_module_with_tags(
module_path,
name,
&[gimli::constants::DW_TAG_union_type],
)
}
pub fn resolve_union_type_shallow_by_name(&self, name: &str) -> Option<crate::TypeInfo> {
self.resolve_type_shallow_by_name_with_tags(name, &[gimli::constants::DW_TAG_union_type])
}
pub fn resolve_enum_type_shallow_by_name_in_module<P: AsRef<Path>>(
&self,
module_path: P,
name: &str,
) -> Option<crate::TypeInfo> {
self.resolve_type_shallow_by_name_in_module_with_tags(
module_path,
name,
&[gimli::constants::DW_TAG_enumeration_type],
)
}
pub fn resolve_enum_type_shallow_by_name(&self, name: &str) -> Option<crate::TypeInfo> {
self.resolve_type_shallow_by_name_with_tags(
name,
&[gimli::constants::DW_TAG_enumeration_type],
)
}
pub async fn from_pid_parallel(pid: u32) -> Result<Self> {
Self::from_pid_parallel_with_config(pid, &[], false, |_event| {}).await
}
pub async fn from_pid_parallel_with_progress<F>(pid: u32, progress_callback: F) -> Result<Self>
where
F: Fn(ModuleLoadingEvent) + Send + Sync + 'static,
{
Self::from_pid_parallel_with_config(pid, &[], false, progress_callback).await
}
pub async fn from_pid_parallel_with_config<F>(
pid: u32,
debug_search_paths: &[String],
allow_loose_debug_match: bool,
progress_callback: F,
) -> Result<Self>
where
F: Fn(ModuleLoadingEvent) + Send + Sync + 'static,
{
tracing::info!("Creating DWARF analyzer for PID {} (parallel)", pid);
let mut coord = ghostscope_process::ProcessManager::new();
coord.ensure_prefill_pid(pid)?;
let mut module_mappings: Vec<crate::core::mapping::ModuleMapping> = Vec::new();
if let Some(entries) = coord.cached_offsets_with_paths_for_pid(pid) {
use std::collections::HashSet;
let mut seen = HashSet::new();
for e in entries {
if seen.insert(e.module_path.clone()) {
let mut mm = crate::core::mapping::ModuleMapping::from_path(
std::path::PathBuf::from(&e.module_path),
);
mm.loaded_address = Some(e.base);
mm.size = e.size;
module_mappings.push(mm);
}
}
}
tracing::info!(
"Discovered {} modules for PID {}",
module_mappings.len(),
pid
);
for (index, mapping) in module_mappings.iter().enumerate() {
progress_callback(ModuleLoadingEvent::Discovered {
module_path: mapping.path.to_string_lossy().to_string(),
current: index + 1,
total: module_mappings.len(),
});
}
let mut loader = crate::loader::ModuleLoader::new(module_mappings).parallel();
if !debug_search_paths.is_empty() {
loader = loader.with_debug_search_paths(debug_search_paths.to_vec());
}
loader = loader.with_loose_debug_match(allow_loose_debug_match);
let modules = loader
.with_progress_callback(progress_callback)
.load()
.await?;
tracing::info!(
"Created DWARF analyzer for PID {} with {} modules (parallel)",
pid,
modules.len()
);
Ok(Self::from_modules(pid, modules))
}
pub async fn from_exec_path<P: AsRef<std::path::Path>>(exec_path: P) -> Result<Self> {
Self::from_exec_path_with_config(exec_path, &[], false).await
}
pub async fn from_exec_path_with_config<P: AsRef<std::path::Path>>(
exec_path: P,
debug_search_paths: &[String],
allow_loose_debug_match: bool,
) -> Result<Self> {
Self::from_exec_path_with_config_and_progress(
exec_path,
debug_search_paths,
allow_loose_debug_match,
|_event| {},
)
.await
}
pub async fn from_exec_path_with_config_and_progress<P, F>(
exec_path: P,
debug_search_paths: &[String],
allow_loose_debug_match: bool,
progress_callback: F,
) -> Result<Self>
where
P: AsRef<std::path::Path>,
F: Fn(ModuleLoadingEvent) + Send + Sync + 'static,
{
let exec_path = exec_path.as_ref().to_path_buf();
tracing::info!(
"Creating DWARF analyzer for executable: {}",
exec_path.display()
);
let mut analyzer = Self {
pid: 0, modules: HashMap::new(),
};
let module_mapping = ModuleMapping {
path: exec_path.clone(),
loaded_address: None, size: 0, };
let module_path = exec_path.to_string_lossy().to_string();
progress_callback(ModuleLoadingEvent::Discovered {
module_path: module_path.clone(),
current: 1,
total: 1,
});
progress_callback(ModuleLoadingEvent::LoadingStarted {
module_path: module_path.clone(),
current: 1,
total: 1,
});
let start_time = std::time::Instant::now();
match LoadedObjfile::load_parallel(
module_mapping,
debug_search_paths,
allow_loose_debug_match,
)
.await
{
Ok(module_data) => {
let (functions, variables, types) = module_data.get_lightweight_index().get_stats();
let (parse_time_ms, index_time_ms, module_total_time_ms) =
module_data.get_load_timing_ms();
progress_callback(ModuleLoadingEvent::LoadingCompleted {
module_path,
stats: ModuleLoadingStats {
functions,
variables,
types,
load_time_ms: start_time.elapsed().as_millis() as u64,
parse_time_ms,
index_time_ms,
module_total_time_ms,
},
current: 1,
total: 1,
});
analyzer.modules.insert(exec_path.clone(), module_data);
tracing::info!(
"Created DWARF analyzer for executable {} with 1 module",
exec_path.display()
);
}
Err(e) => {
progress_callback(ModuleLoadingEvent::LoadingFailed {
module_path,
error: e.to_string(),
current: 1,
total: 1,
});
return Err(crate::DwarfError::ModuleLoadError(format!(
"Failed to load executable {}: {}",
exec_path.display(),
e
))
.into());
}
}
Ok(analyzer)
}
pub(crate) fn from_modules(pid: u32, modules: Vec<LoadedObjfile>) -> Self {
let mut analyzer = Self {
pid,
modules: HashMap::new(),
};
for module in modules {
let module_path = module.module_path().clone();
analyzer.modules.insert(module_path, module);
}
tracing::info!(
"Created DWARF analyzer for PID {} with {} pre-loaded modules",
pid,
analyzer.modules.len()
);
analyzer
}
pub fn lookup_function_addresses(&self, name: &str) -> Vec<ModuleAddress> {
let mut results = Vec::new();
for (module_path, module_data) in &self.modules {
let addresses = module_data.lookup_function_addresses_any(name);
for address in addresses {
tracing::debug!(
"Function '{}' found in module {} at address: 0x{:x}",
name,
module_path.display(),
address
);
results.push(ModuleAddress::new(module_path.clone(), address));
}
}
results.sort_by(|a, b| {
let pa = a.module_path.to_string_lossy();
let pb = b.module_path.to_string_lossy();
match pa.cmp(&pb) {
std::cmp::Ordering::Equal => a.address.cmp(&b.address),
other => other,
}
});
results
}
pub fn query_function(&self, name: &str) -> Result<FunctionQueryResult> {
let module_addresses = self.lookup_function_addresses(name);
let addresses = self.query_module_addresses(module_addresses)?;
Ok(FunctionQueryResult {
function_name: name.to_string(),
addresses,
})
}
pub fn query_function_best_effort(&self, name: &str) -> Result<FunctionQueryResult> {
let module_addresses = self.lookup_function_addresses(name);
let addresses = self
.query_module_addresses_best_effort(module_addresses, &format!("function '{name}'"))?;
Ok(FunctionQueryResult {
function_name: name.to_string(),
addresses,
})
}
pub fn vaddr_to_file_offset<P: AsRef<std::path::Path>>(
&self,
module_path: P,
vaddr: u64,
) -> Option<u64> {
let path_buf = module_path.as_ref().to_path_buf();
if let Some(module_data) = self.modules.get(&path_buf) {
module_data.vaddr_to_file_offset(vaddr)
} else {
None
}
}
pub fn get_all_variables_at_address(
&self,
module_address: &ModuleAddress,
) -> Result<Vec<crate::VariableWithEvaluation>> {
tracing::info!(
"Looking up variables at address 0x{:x} in module {}",
module_address.address,
module_address.module_display()
);
if let Some(module_data) = self.modules.get(&module_address.module_path) {
module_data.get_all_variables_at_address(module_address.address)
} else {
tracing::warn!(
"Module {} not found in loaded modules",
module_address.module_display()
);
Err(anyhow::anyhow!(
"Module {} not loaded",
module_address.module_display()
))
}
}
pub fn plan_chain_access(
&self,
module_address: &ModuleAddress,
base_var: &str,
chain: &[String],
) -> Result<Option<crate::VariableWithEvaluation>> {
if let Some(module_data) = self.modules.get(&module_address.module_path) {
module_data.plan_chain_access(module_address.address, base_var, chain)
} else {
Ok(None)
}
}
pub fn recover_caller_frame(
&self,
module_address: &ModuleAddress,
registers: &[u16],
) -> Result<Option<CallerFrameRecovery>> {
if let Some(module_data) = self.modules.get(&module_address.module_path) {
module_data.recover_caller_frame(module_address.address, registers)
} else {
Ok(None)
}
}
pub fn get_loaded_modules(&self) -> Vec<&PathBuf> {
self.modules.keys().collect()
}
pub fn find_global_variables_by_name(&self, name: &str) -> Vec<(PathBuf, GlobalVariableInfo)> {
let mut results = Vec::new();
for (module_path, module_data) in &self.modules {
let vars = module_data.find_global_variables_by_name_any(name);
for v in vars {
results.push((module_path.clone(), v));
}
}
if !results.is_empty() {
return results;
}
for (module_path, module_data) in &self.modules {
let all = module_data.list_all_global_variables();
for v in all {
let leaf = v.name.rsplit("::").next().unwrap_or(&v.name).to_string();
if v.name == name || leaf == name {
results.push((module_path.clone(), v));
}
}
}
results
}
pub fn plan_global_chain_access(
&self,
prefer_module: &PathBuf,
base: &str,
fields: &[String],
) -> Result<Option<(PathBuf, crate::VariableWithEvaluation)>> {
let matches = self.find_global_variables_by_name(base);
if matches.is_empty() {
return Ok(None);
}
let mut ordered: Vec<(PathBuf, GlobalVariableInfo)> = Vec::new();
for (mpath, info) in matches.iter() {
if *mpath == *prefer_module {
ordered.push((mpath.clone(), info.clone()));
}
}
for (mpath, info) in matches.into_iter() {
if mpath != *prefer_module {
ordered.push((mpath, info));
}
}
for (mpath, info) in ordered.into_iter() {
if let Some(link) = info.link_address {
if let Ok(Some((off, final_ty))) = self.compute_global_member_static_offset(
&mpath,
link,
info.unit_offset,
info.die_offset,
fields,
) {
let name = if fields.is_empty() {
base.to_string()
} else {
format!("{base}.{}", fields.join("."))
};
let var = crate::VariableWithEvaluation {
name,
type_name: final_ty.type_name(),
dwarf_type: Some(final_ty),
evaluation_result: crate::core::EvaluationResult::MemoryLocation(
crate::core::LocationResult::Address(link + off),
),
scope_depth: 0,
is_parameter: false,
is_artificial: false,
};
tracing::info!(
"plan_global_chain_access: resolved '{}' in module '{}' via static-offset",
base,
mpath.display()
);
return Ok(Some((mpath, var)));
}
}
let ma = ModuleAddress::new(mpath.clone(), 0);
match self.plan_chain_access(&ma, base, fields) {
Ok(Some(v)) => {
tracing::info!(
"plan_global_chain_access: resolved '{}' in module '{}' via planner",
base,
ma.module_display()
);
return Ok(Some((mpath, v)));
}
Ok(None) => {}
Err(e) => {
tracing::debug!(
"plan_global_chain_access: planner miss in module '{}': {}",
ma.module_display(),
e
);
}
}
}
Ok(None)
}
pub fn resolve_variable_by_offsets_in_module<P: AsRef<Path>>(
&self,
module_path: P,
cu_off: gimli::DebugInfoOffset,
die_off: gimli::UnitOffset,
) -> Result<crate::VariableWithEvaluation> {
let path_buf = module_path.as_ref().to_path_buf();
if let Some(module_data) = self.modules.get(&path_buf) {
let items = vec![(cu_off, die_off)];
let vars = module_data.resolve_variables_by_offsets_at_address(0, &items)?;
let mut var = vars.into_iter().next().ok_or_else(|| {
anyhow::anyhow!(
"Failed to resolve variable at offsets {:?}/{:?} in module {}",
cu_off,
die_off,
path_buf.display()
)
})?;
if var.dwarf_type.is_none() {
if let Some(ti) = module_data.shallow_type_for_variable_offsets(cu_off, die_off) {
var.type_name = ti.type_name();
var.dwarf_type = Some(ti);
}
}
Ok(var)
} else {
Err(anyhow::anyhow!(
"Module {} not loaded",
module_path.as_ref().display()
))
}
}
pub fn list_all_global_variables(&self) -> Vec<(PathBuf, GlobalVariableInfo)> {
let mut results = Vec::new();
for (module_path, module_data) in &self.modules {
for v in module_data.list_all_global_variables() {
results.push((module_path.clone(), v));
}
}
results
}
pub fn classify_section_for_address<P: AsRef<Path>>(
&self,
module_path: P,
vaddr: u64,
) -> Option<crate::core::SectionType> {
let path = module_path.as_ref();
if let Some(module_data) = self.modules.get(path) {
module_data.classify_section_for_vaddr(vaddr)
} else {
None
}
}
pub fn compute_global_member_static_offset<P: AsRef<Path>>(
&self,
module_path: P,
link_address: u64,
cu_off: gimli::DebugInfoOffset,
var_die: gimli::UnitOffset,
fields: &[String],
) -> Result<Option<(u64, crate::TypeInfo)>> {
let path_buf = module_path.as_ref().to_path_buf();
if let Some(module_data) = self.modules.get(&path_buf) {
module_data.compute_global_member_static_offset(cu_off, var_die, link_address, fields)
} else {
Err(anyhow::anyhow!(
"Module {} not loaded",
module_path.as_ref().display()
))
}
}
pub fn lookup_function_address_by_name(&self, function_name: &str) -> Option<ModuleAddress> {
let module_addresses = self.lookup_function_addresses(function_name);
if let Some(first_module_address) = module_addresses.first() {
tracing::info!(
"Found function '{}' in module '{}' at address 0x{:x}",
function_name,
first_module_address.module_display(),
first_module_address.address
);
Some(first_module_address.clone())
} else {
tracing::warn!("Function '{}' not found in any module", function_name);
None
}
}
pub fn lookup_source_location(&self, module_address: &ModuleAddress) -> Option<SourceLocation> {
if let Some(module_data) = self.modules.get(&module_address.module_path) {
module_data.lookup_source_location(module_address.address)
} else {
tracing::warn!("Module {} not found", module_address.module_display());
None
}
}
pub fn lookup_addresses_by_source_line(
&self,
file_path: &str,
line_number: u32,
) -> Vec<ModuleAddress> {
let mut results = Vec::new();
for (module_path, module_data) in &self.modules {
let addresses = module_data.lookup_addresses_by_source_line(file_path, line_number);
for address in addresses {
results.push(ModuleAddress::new(module_path.clone(), address));
}
}
if !results.is_empty() {
tracing::info!(
"Found {} addresses for {}:{} across {} modules",
results.len(),
file_path,
line_number,
self.modules.len()
);
}
results.sort_by(|a, b| {
let pa = a.module_path.to_string_lossy();
let pb = b.module_path.to_string_lossy();
match pa.cmp(&pb) {
std::cmp::Ordering::Equal => a.address.cmp(&b.address),
other => other,
}
});
results
}
pub fn query_source_line(
&self,
file_path: &str,
line_number: u32,
) -> Result<Vec<AddressQueryResult>> {
let module_addresses = self.lookup_addresses_by_source_line(file_path, line_number);
self.query_module_addresses(module_addresses)
}
pub fn query_source_line_best_effort(
&self,
file_path: &str,
line_number: u32,
) -> Result<Vec<AddressQueryResult>> {
let module_addresses = self.lookup_addresses_by_source_line(file_path, line_number);
self.query_module_addresses_best_effort(
module_addresses,
&format!("source line '{file_path}:{line_number}'"),
)
}
pub fn query_address<P: AsRef<Path>>(
&self,
module_path: P,
address: u64,
) -> Result<AddressQueryResult> {
let module_address = ModuleAddress::new(module_path.as_ref().to_path_buf(), address);
self.build_address_query_result(&module_address)
}
pub fn get_all_function_names(&self) -> Vec<String> {
let mut all_names = std::collections::HashSet::new();
for module_data in self.modules.values() {
for name in module_data.get_function_names() {
all_names.insert(name.clone());
}
}
all_names.into_iter().collect()
}
pub fn get_stats(&self) -> AnalyzerStats {
let mut total_functions = 0;
let mut total_variables = 0;
let mut total_line_headers = 0;
for module_data in self.modules.values() {
total_functions += module_data.get_function_names().len();
total_variables += module_data.get_variable_names().len();
total_line_headers += module_data.get_line_header_count();
}
AnalyzerStats {
pid: self.pid,
module_count: self.modules.len(),
total_functions,
total_variables,
total_line_headers,
}
}
pub fn get_module_stats(&self) -> ModuleStats {
let mut total_symbols = 0;
let mut executable_modules = 0;
let mut library_modules = 0;
for (module_path, module_data) in &self.modules {
let function_names = module_data.get_function_names();
total_symbols += function_names.len();
if self.is_main_executable_module(module_path) {
executable_modules += 1;
} else {
library_modules += 1;
}
}
ModuleStats {
total_modules: self.modules.len(),
executable_modules,
library_modules,
total_symbols,
modules_with_debug_info: self.modules.len(), }
}
pub fn get_main_executable(&self) -> Option<MainExecutableInfo> {
for module_path in self.modules.keys() {
if self.is_main_executable_module(module_path) {
return Some(MainExecutableInfo {
path: module_path.to_string_lossy().to_string(),
});
}
}
None
}
fn is_main_executable_module(&self, module_path: &Path) -> bool {
let filename = module_path
.file_name()
.and_then(|name| name.to_str())
.unwrap_or("");
!filename.contains(".so") &&
!module_path.to_string_lossy().starts_with("/lib") &&
!module_path.to_string_lossy().starts_with("/usr/lib")
}
pub fn list_functions(&self) -> Vec<String> {
let mut all_functions = Vec::new();
for module_data in self.modules.values() {
let function_names = module_data.get_function_names();
for name in function_names {
all_functions.push(name.clone());
}
}
all_functions.sort();
all_functions.dedup();
tracing::debug!(
"Listed {} unique functions across {} modules",
all_functions.len(),
self.modules.len()
);
all_functions
}
pub fn lookup_functions_by_pattern(&self, pattern: &str) -> Vec<String> {
let all_functions = self.list_functions();
all_functions
.into_iter()
.filter(|name| name.contains(pattern))
.collect()
}
pub fn lookup_all_function_names(&self) -> Vec<String> {
self.list_functions()
}
pub fn get_pid(&self) -> u32 {
self.pid
}
pub fn get_shared_library_info(&self) -> Vec<SharedLibraryInfo> {
self.modules
.iter()
.filter(|(path, _)| self.is_shared_library(path))
.map(|(path, module_data)| {
let mapping = module_data.module_mapping();
let debug_file_path = module_data
.get_debug_file_path()
.map(|p| p.to_string_lossy().to_string());
SharedLibraryInfo {
from_address: mapping.loaded_address.unwrap_or(0),
to_address: mapping.loaded_address.map_or(0, |addr| addr + mapping.size),
symbols_read: !module_data.get_function_names().is_empty(),
debug_info_available: module_data.has_dwarf_info(),
library_path: path.to_string_lossy().to_string(),
size: mapping.size,
debug_file_path,
}
})
.collect()
}
pub fn get_executable_file_info(&self) -> Option<ExecutableFileInfo> {
let executable = self
.modules
.iter()
.find(|(path, _)| !self.is_shared_library(path))?;
let (exe_path, module_data) = executable;
let file_path = exe_path.to_string_lossy().to_string();
let file_bytes = std::fs::read(exe_path).ok()?;
let obj = object::File::parse(&file_bytes[..]).ok()?;
let file_type = match obj.format() {
object::BinaryFormat::Elf => {
if obj.is_64() {
"ELF 64-bit executable"
} else {
"ELF 32-bit executable"
}
}
_ => "Unknown format",
}
.to_string();
let has_symbols = !module_data.get_function_names().is_empty()
|| obj.symbols().count() > 0
|| obj.dynamic_symbols().count() > 0;
let has_debug_info = module_data.has_dwarf_info();
let debug_file_path = module_data.get_debug_file_path();
let load_bias = if self.pid != 0 {
module_data.module_mapping().loaded_address.unwrap_or(0)
} else {
0
};
let entry_point = Some(obj.entry() + load_bias);
let text_section = obj.section_by_name(".text").map(|section| {
let addr = section.address() + load_bias;
let size = section.size();
SectionInfo {
start_address: addr,
end_address: addr + size,
size,
}
});
let data_section = obj.section_by_name(".data").map(|section| {
let addr = section.address() + load_bias;
let size = section.size();
SectionInfo {
start_address: addr,
end_address: addr + size,
size,
}
});
let mode_description = if self.pid != 0 {
format!("Attached to process {} (PID mode)", self.pid)
} else {
"Static analysis mode (target file specified with -t)".to_string()
};
Some(ExecutableFileInfo {
file_path,
file_type,
entry_point,
has_symbols,
has_debug_info,
debug_file_path: debug_file_path.map(|p| p.to_string_lossy().to_string()),
text_section,
data_section,
mode_description,
})
}
fn is_shared_library(&self, module_path: &Path) -> bool {
let filename = module_path
.file_name()
.and_then(|name| name.to_str())
.unwrap_or("");
filename.contains(".so")
|| module_path.to_string_lossy().starts_with("/lib")
|| module_path.to_string_lossy().starts_with("/usr/lib")
}
pub fn get_grouped_file_info_by_module(&self) -> Result<Vec<(String, Vec<SimpleFileInfo>)>> {
let mut grouped = Vec::new();
for (module_path, module_data) in &self.modules {
let files = module_data.get_all_files();
if !files.is_empty() {
let simple_files: Vec<SimpleFileInfo> = files
.into_iter()
.map(|source_file| SimpleFileInfo {
full_path: source_file.full_path,
basename: source_file.filename,
directory: source_file.directory_path,
})
.collect();
grouped.push((module_path.to_string_lossy().to_string(), simple_files));
}
}
Ok(grouped)
}
}
#[derive(Debug, Clone)]
pub struct ModuleStats {
pub total_modules: usize,
pub executable_modules: usize,
pub library_modules: usize,
pub total_symbols: usize,
pub modules_with_debug_info: usize,
}
#[derive(Debug, Clone)]
pub struct MainExecutableInfo {
pub path: String,
}
#[derive(Debug, Clone)]
pub struct AnalyzerStats {
pub pid: u32,
pub module_count: usize,
pub total_functions: usize,
pub total_variables: usize,
pub total_line_headers: usize,
}
#[derive(Debug, Clone)]
pub struct SharedLibraryInfo {
pub from_address: u64, pub to_address: u64, pub symbols_read: bool, pub debug_info_available: bool, pub library_path: String, pub size: u64, pub debug_file_path: Option<String>, }
#[derive(Debug, Clone)]
pub struct ExecutableFileInfo {
pub file_path: String,
pub file_type: String,
pub entry_point: Option<u64>,
pub has_symbols: bool,
pub has_debug_info: bool,
pub debug_file_path: Option<String>,
pub text_section: Option<SectionInfo>,
pub data_section: Option<SectionInfo>,
pub mode_description: String,
}
#[derive(Debug, Clone)]
pub struct SectionInfo {
pub start_address: u64,
pub end_address: u64,
pub size: u64,
}
#[derive(Debug, Clone)]
pub struct SimpleFileInfo {
pub full_path: String,
pub basename: String,
pub directory: String,
}