pub mod ebpf;
pub mod script;
use crate::script::compiler::AstCompiler;
use ebpf::context::CodeGenError;
pub use ghostscope_process::{PidFilterSpec, PidNamespaceId};
use script::parser::ParseError;
use tracing::info;
pub fn hello() -> &'static str {
"Hello from ghostscope-compiler!"
}
#[derive(Debug, thiserror::Error)]
pub enum CompileError {
#[error("Parse error: {0}")]
Parse(#[from] Box<ParseError>),
#[error("Code generation error: {0}")]
CodeGen(#[from] CodeGenError),
#[error("LLVM error: {0}")]
LLVM(String),
#[error("{0}")]
Other(String),
}
pub type Result<T> = std::result::Result<T, CompileError>;
impl From<ParseError> for CompileError {
fn from(err: ParseError) -> Self {
CompileError::Parse(Box::new(err))
}
}
pub use script::compiler::{CompilationResult, UProbeConfig};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum EventMapType {
RingBuf,
PerfEventArray,
}
#[derive(Debug, Clone)]
pub struct CompileOptions {
pub save_llvm_ir: bool,
pub save_ebpf: bool,
pub save_ast: bool,
pub binary_path_hint: Option<String>,
pub ringbuf_size: u64,
pub proc_module_offsets_max_entries: u64,
pub perf_page_count: u32,
pub event_map_type: EventMapType,
pub mem_dump_cap: u32,
pub compare_cap: u32,
pub max_trace_event_size: u32,
pub selected_index: Option<usize>,
pub pid_filter_spec: Option<PidFilterSpec>,
pub special_pid_ns: Option<PidNamespaceId>,
pub proc_offsets_pid_ns: Option<PidNamespaceId>,
pub input_pid: Option<u32>,
}
impl Default for CompileOptions {
fn default() -> Self {
Self {
save_llvm_ir: false,
save_ebpf: false,
save_ast: false,
binary_path_hint: None,
ringbuf_size: 262144, proc_module_offsets_max_entries: 4096, perf_page_count: 64, event_map_type: EventMapType::RingBuf, mem_dump_cap: 256, compare_cap: 64, max_trace_event_size: 32768, selected_index: None,
pid_filter_spec: None,
special_pid_ns: None,
proc_offsets_pid_ns: None,
input_pid: None,
}
}
}
pub fn compile_script(
script_source: &str,
process_analyzer: &ghostscope_dwarf::DwarfAnalyzer,
pid: Option<u32>,
trace_id: Option<u32>,
compile_options: &CompileOptions,
) -> Result<CompilationResult> {
info!("Starting unified script compilation with DwarfAnalyzer (multi-module support)");
let program = script::parser::parse(script_source)?;
info!("Parsed script with {} statements", program.statements.len());
let mut compiler = AstCompiler::new(
Some(process_analyzer),
compile_options.binary_path_hint.clone(),
trace_id.unwrap_or(0), compile_options.clone(),
);
let result = compiler.compile_program(&program, pid)?;
if result.uprobe_configs.is_empty() {
if !result.failed_targets.is_empty() {
tracing::warn!(
"Compilation produced 0 uprobe configs; {} target(s) failed to compile",
result.failed_targets.len()
);
} else {
tracing::warn!(
"Compilation completed with 0 uprobe configs (no attachable targets resolved)"
);
}
} else {
info!(
"Successfully compiled script: {} trace points, {} uprobe configs",
result.trace_count,
result.uprobe_configs.len()
);
}
info!(
"Compilation summary: trace_points={}, uprobe_configs={}, failed_targets={}",
result.trace_count,
result.uprobe_configs.len(),
result.failed_targets.len()
);
Ok(result)
}
pub fn print_ast(program: &crate::script::Program) {
info!("\n=== AST Tree ===");
info!("Program:");
for (i, stmt) in program.statements.iter().enumerate() {
info!(" Statement {}: {:?}", i, stmt);
}
info!("=== End AST Tree ===\n");
}
pub fn save_ast_to_file(program: &crate::script::Program, filename: &str) -> Result<()> {
let mut ast_content = String::new();
ast_content.push_str("=== AST Tree ===\n");
ast_content.push_str("Program:\n");
for (i, stmt) in program.statements.iter().enumerate() {
ast_content.push_str(&format!(" Statement {i}: {stmt:?}\n"));
}
ast_content.push_str("=== End AST Tree ===\n");
let file_path = format!("{filename}.txt");
std::fs::write(&file_path, ast_content)
.map_err(|e| CompileError::Other(format!("Failed to save AST file '{file_path}': {e}")))?;
Ok(())
}
pub fn format_ebpf_bytecode(bytecode: &[u8]) -> String {
bytecode
.iter()
.map(|byte| format!("{byte:02x}"))
.collect::<Vec<String>>()
.join(" ")
}
pub fn generate_file_name_for_ast(pid: Option<u32>, binary_path: Option<&str>) -> String {
let pid_part = pid
.map(|p| p.to_string())
.unwrap_or_else(|| "unknown".to_string());
let exec_part = binary_path
.and_then(|path| std::path::Path::new(path).file_name())
.and_then(|name| name.to_str())
.unwrap_or("unknown");
format!("gs_{pid_part}_{exec_part}_ast")
}