use crate::config::MergedConfig;
use crate::core::GhostSession;
use anyhow::Result;
use tracing::{debug, error, info, warn};
pub async fn run_command_line_runtime_with_config(config: MergedConfig) -> Result<()> {
info!("Starting GhostScope in command line mode");
let script_content = get_script_content_from_config(&config)?;
info!("Initializing debug session and DWARF information processing...");
let session = GhostSession::new_with_binary_and_config(&config)
.await
.map_err(|e| anyhow::anyhow!("Failed to create debug session: {}", e))?;
run_cli_with_session(session, script_content, &config).await
}
async fn run_cli_with_session(
mut session: GhostSession,
script_content: String,
config: &MergedConfig,
) -> Result<()> {
info!("Debug session created");
info!("Save LLVM IR files: {}", config.should_save_llvm_ir);
info!("Save eBPF bytecode files: {}", config.should_save_ebpf);
info!("Save AST files: {}", config.should_save_ast);
if let Some(ref binary) = session.target_binary {
info!("Target binary: {}", binary);
if !session.target_args.is_empty() {
info!("Binary arguments: {:?}", session.target_args);
}
}
if let Some(pid) = session.target_pid {
info!("Target PID: {}", pid);
}
if config.pid.is_some() || config.binary_path.is_some() {
match session.get_module_stats() {
Some(stats) => {
info!("✓ Process analysis successful");
info!(" Total modules: {}", stats.total_modules);
info!(" Executable modules: {}", stats.executable_modules);
info!(" Library modules: {}", stats.library_modules);
info!(" Total symbols: {}", stats.total_symbols);
info!(
" Modules with debug info: {}",
stats.modules_with_debug_info
);
if stats.modules_with_debug_info == 0 {
warn!("Warning: No debug information available in any module. Source line tracing (trace file.c:line) will not work.");
warn!("To enable source line tracing, compile your target with debug symbols (-g flag).");
} else {
info!("✓ Debug information available for source line tracing");
}
}
None => {
return Err(anyhow::anyhow!(
"Process analysis failed! Cannot proceed without process information. \
Possible solutions: 1. Check that PID {} exists: ps -p {}, \
2. Check process permissions, 3. Run with sudo if needed for /proc access",
config.pid.unwrap_or(0),
config.pid.unwrap_or(0)
));
}
}
} else {
info!("No target binary or PID specified - running in standalone mode");
}
if let Some(_stats) = session.get_module_stats() {
let functions = session.list_functions();
if !functions.is_empty() {
info!("Available functions (showing first 10):");
for func in functions.iter().take(10) {
info!(" {}", func);
}
if functions.len() > 10 {
info!(" ... and {} more", functions.len() - 10);
}
}
}
let binary_path_hint = crate::util::derive_binary_path_hint(&session);
let compile_options = config.get_compile_options(
config.should_save_llvm_ir,
config.should_save_ebpf,
config.should_save_ast,
binary_path_hint,
);
if let Err(e) = crate::script::compile_and_load_script_for_cli(
&script_content,
&mut session,
&compile_options,
)
.await
{
error!("Failed to compile and load script: {:#}", e);
info!("GhostScope encountered an error during script compilation. Exiting gracefully.");
return Err(e);
}
info!(
"Starting event monitoring for {} active traces",
session.trace_manager.active_trace_count()
);
let mut event_count = 0;
loop {
tokio::select! {
result = session.trace_manager.wait_for_all_events_async() => {
match result {
Ok(events) => {
for event in events {
event_count += 1;
let formatted_output = event.to_formatted_output();
if !formatted_output.is_empty() {
println!("[Event #{event_count}] Output:");
for line in formatted_output {
println!(" {line}");
}
}
debug!("[Event #{}] Raw: {:?}", event_count, event);
}
}
Err(e) => {
error!("Fatal error receiving trace events: {}", e);
return Err(e);
}
}
}
_ = tokio::signal::ctrl_c() => {
info!("Received Ctrl+C, shutting down...");
break;
}
}
}
Ok(())
}
fn get_script_content_from_config(config: &MergedConfig) -> Result<String> {
match (&config.script, &config.script_file) {
(Some(script), _) => {
info!("Using inline script from command line");
Ok(script.clone())
}
(None, Some(script_file)) => {
info!("Loading script from file: {}", script_file.display());
std::fs::read_to_string(script_file).map_err(|e| {
anyhow::anyhow!(
"Failed to read script file '{}': {}",
script_file.display(),
e
)
})
}
(None, None) => {
warn!("No script provided, using default trace example");
Ok(r#"
trace main {
print "Entering main function";
print $arg0;
print $arg1;
}
"#
.to_string())
}
}
}