use crate::core::GhostSession;
use ghostscope_ui::{events::*, RuntimeChannels, RuntimeStatus};
use std::collections::HashSet;
use tracing::info;
pub async fn handle_main_source_request(
session: &mut Option<GhostSession>,
runtime_channels: &mut RuntimeChannels,
) {
let result = try_get_main_source_info(session);
match result {
Ok(source_info) => {
let _ = runtime_channels
.status_sender
.send(RuntimeStatus::SourceCodeLoaded(source_info));
}
Err(error_msg) => {
info!("Source code loading failed: {}", error_msg);
let _ = runtime_channels
.status_sender
.send(RuntimeStatus::SourceCodeLoadFailed(error_msg));
}
}
}
fn try_get_main_source_info(session: &mut Option<GhostSession>) -> Result<SourceCodeInfo, String> {
let session = session.as_mut().ok_or("No active session available")?;
let process_analyzer = session
.process_analyzer
.as_mut()
.ok_or("Process analyzer not available. Try reloading the process.")?;
let module_address_opt = process_analyzer.lookup_function_address_by_name("main");
if let Some(addr) = module_address_opt {
info!(
"Found main function at address 0x{:x} in module: {}",
addr.address,
addr.module_display()
);
if let Some(source_location) = process_analyzer.lookup_source_location(&addr) {
info!(
"Main function source location (DWARF): {}:{}",
source_location.file_path, source_location.line_number
);
let resolved_path = session
.source_path_resolver
.resolve(&source_location.file_path)
.unwrap_or_else(|| std::path::PathBuf::from(&source_location.file_path));
info!(
"Resolved source path: {} -> {}",
source_location.file_path,
resolved_path.display()
);
return Ok(SourceCodeInfo {
file_path: resolved_path.to_string_lossy().to_string(),
current_line: Some(source_location.line_number as usize),
});
}
}
info!("Main function not found or has no source info, trying fallback");
if let Some(module_address) = try_find_any_function_with_source(process_analyzer) {
if let Some(source_location) = process_analyzer.lookup_source_location(&module_address) {
info!(
"Fallback function source location (DWARF): {}:{}",
source_location.file_path, source_location.line_number
);
let resolved_path = session
.source_path_resolver
.resolve(&source_location.file_path)
.unwrap_or_else(|| std::path::PathBuf::from(&source_location.file_path));
return Ok(SourceCodeInfo {
file_path: resolved_path.to_string_lossy().to_string(),
current_line: Some(source_location.line_number as usize),
});
}
}
if let Ok(grouped) = process_analyzer.get_grouped_file_info_by_module() {
for (_module_path, files) in grouped {
if let Some(file) = files.first() {
let full_path = format!("{}/{}", file.directory, file.basename);
info!(
"Last resort fallback: using first available file: {}",
full_path
);
let resolved_path = session
.source_path_resolver
.resolve(&full_path)
.unwrap_or_else(|| std::path::PathBuf::from(&full_path));
return Ok(SourceCodeInfo {
file_path: resolved_path.to_string_lossy().to_string(),
current_line: Some(1),
});
}
}
}
Err(
"No source code information available. This may be due to:\n\
1. Binary was compiled without debug symbols (-g flag)\n\
2. Using stripped binary without separate debug file\n\
3. Analyzing a library without entry point\n\
\n\
💡 Try: Recompile with debug symbols or load a binary with DWARF information"
.to_string(),
)
}
fn try_find_any_function_with_source(
process_analyzer: &mut ghostscope_dwarf::DwarfAnalyzer,
) -> Option<ghostscope_dwarf::ModuleAddress> {
let common_entry_points = [
"_start", "__libc_start_main",
"start", "_main", "WinMain", "wWinMain", ];
for name in &common_entry_points {
if let Some(addr) = process_analyzer.lookup_function_address_by_name(name) {
if process_analyzer.lookup_source_location(&addr).is_some() {
info!("Fallback: found {} with source info", name);
return Some(addr);
}
}
}
if let Ok(grouped) = process_analyzer.get_grouped_file_info_by_module() {
for (_module_path, files) in grouped {
if let Some(file) = files.first() {
let full_path = format!("{}/{}", file.directory, file.basename);
for line in 1..100 {
let addrs = process_analyzer.lookup_addresses_by_source_line(&full_path, line);
if let Some(addr) = addrs.first() {
info!(
"Fallback: using first available source location at {}:{}",
full_path, line
);
return Some(addr.clone());
}
}
}
}
}
info!("Fallback: no address mapping with source information found");
None
}
pub async fn handle_request_source_code(
session: &Option<GhostSession>,
runtime_channels: &mut RuntimeChannels,
) {
if let Some(ref session) = session {
info!("Source code request received");
match get_grouped_source_files_info(session) {
Ok(groups) => {
let _ = runtime_channels
.status_sender
.send(RuntimeStatus::FileInfo { groups });
}
Err(error) => {
let _ = runtime_channels
.status_sender
.send(RuntimeStatus::FileInfoFailed {
error: error.to_string(),
});
}
}
} else {
let _ = runtime_channels
.status_sender
.send(RuntimeStatus::FileInfoFailed {
error: "No debug session available for source code request".to_string(),
});
}
}
fn get_grouped_source_files_info(session: &GhostSession) -> anyhow::Result<Vec<SourceFileGroup>> {
use crate::runtime::source_path_resolver::apply_substitutions_to_directory;
let mut groups = Vec::new();
if let Some(ref process_analyzer) = session.process_analyzer {
let grouped = process_analyzer.get_grouped_file_info_by_module()?;
for (module_path, files) in grouped {
let mut seen = HashSet::new();
let mut ui_files = Vec::new();
for file in files {
let dwarf_full_path = format!("{}/{}", file.directory, file.basename);
let (resolved_dir, resolved_basename) = if let Some(resolved_path) =
session.source_path_resolver.resolve(&dwarf_full_path)
{
let resolved_str = resolved_path.to_string_lossy().to_string();
if let Some(last_slash) = resolved_str.rfind('/') {
let dir = resolved_str[..last_slash].to_string();
let basename = resolved_str[last_slash + 1..].to_string();
(dir, basename)
} else {
(".".to_string(), resolved_str)
}
} else {
let resolved_dir = apply_substitutions_to_directory(
&session.source_path_resolver,
&file.directory,
);
(resolved_dir, file.basename.clone())
};
let key = format!("{resolved_dir}:{resolved_basename}");
if seen.insert(key) {
ui_files.push(SourceFileInfo {
path: resolved_basename,
directory: resolved_dir,
});
}
}
ui_files.sort_by(|a, b| a.path.cmp(&b.path));
groups.push(SourceFileGroup {
module_path,
files: ui_files,
});
}
}
groups.sort_by(|a, b| a.module_path.cmp(&b.module_path));
Ok(groups)
}