ghostscope_compiler/
lib.rs

1// Keep library clippy-clean without allow attributes
2
3// New modular organization
4pub mod ebpf;
5pub mod script; // New instruction generator
6                // Legacy codegen - kept for reference, not compiled
7                // pub mod codegen_legacy;
8                // pub mod codegen_new;
9
10use crate::script::compiler::AstCompiler;
11use ebpf::context::CodeGenError;
12use script::parser::ParseError;
13use tracing::info;
14
15pub fn hello() -> &'static str {
16    "Hello from ghostscope-compiler!"
17}
18
19#[derive(Debug, thiserror::Error)]
20pub enum CompileError {
21    #[error("Parse error: {0}")]
22    Parse(#[from] Box<ParseError>),
23
24    #[error("Code generation error: {0}")]
25    CodeGen(#[from] CodeGenError),
26
27    #[error("LLVM error: {0}")]
28    LLVM(String),
29
30    #[error("{0}")]
31    Other(String),
32}
33
34pub type Result<T> = std::result::Result<T, CompileError>;
35
36impl From<ParseError> for CompileError {
37    fn from(err: ParseError) -> Self {
38        CompileError::Parse(Box::new(err))
39    }
40}
41
42// Public re-exports from script::compiler module
43pub use script::compiler::{CompilationResult, UProbeConfig};
44
45/// Event output map type for eBPF tracing
46#[derive(Debug, Clone, Copy, PartialEq, Eq)]
47pub enum EventMapType {
48    /// BPF_MAP_TYPE_RINGBUF (requires kernel >= 5.8)
49    RingBuf,
50    /// BPF_MAP_TYPE_PERF_EVENT_ARRAY (kernel >= 4.3, fallback)
51    PerfEventArray,
52}
53
54/// Compilation options including save options and eBPF map configuration
55#[derive(Debug, Clone)]
56pub struct CompileOptions {
57    pub save_llvm_ir: bool,
58    pub save_ebpf: bool,
59    pub save_ast: bool,
60    pub binary_path_hint: Option<String>,
61    pub ringbuf_size: u64,
62    pub proc_module_offsets_max_entries: u64,
63    pub perf_page_count: u32,
64    pub event_map_type: EventMapType,
65    /// Max bytes to read per memory-dump argument (format {:x}/{:s}).
66    pub mem_dump_cap: u32,
67    /// Max bytes to compare for string/memory comparisons (strncmp/starts_with/memcmp)
68    pub compare_cap: u32,
69    /// Max total bytes in a single trace event (used for PerfEventArray accumulation buffer size).
70    pub max_trace_event_size: u32,
71    /// Optional single-address filter: if set, only the Nth (1-based) address
72    /// resolved for a target will be compiled. When None, compile all.
73    pub selected_index: Option<usize>,
74}
75
76impl Default for CompileOptions {
77    fn default() -> Self {
78        Self {
79            save_llvm_ir: false,
80            save_ebpf: false,
81            save_ast: false,
82            binary_path_hint: None,
83            ringbuf_size: 262144,                  // 256KB
84            proc_module_offsets_max_entries: 4096, // Default
85            perf_page_count: 64,                   // 64 pages = 256KB per CPU
86            event_map_type: EventMapType::RingBuf, // Default to RingBuf
87            mem_dump_cap: 4096,                    // Default per-arg dump cap (bytes)
88            compare_cap: 64,                       // Default compare cap for strncmp/memcmp (bytes)
89            max_trace_event_size: 32768,           // Default event size cap (32KB)
90            selected_index: None,
91        }
92    }
93}
94
95/// Main compilation interface with DwarfAnalyzer (multi-module support)
96///
97/// This is the new multi-module interface that uses DwarfAnalyzer
98/// to perform compilation across main executable and dynamic libraries
99pub fn compile_script(
100    script_source: &str,
101    process_analyzer: &mut ghostscope_dwarf::DwarfAnalyzer,
102    pid: Option<u32>,
103    trace_id: Option<u32>,
104    compile_options: &CompileOptions,
105) -> Result<CompilationResult> {
106    info!("Starting unified script compilation with DwarfAnalyzer (multi-module support)");
107
108    // Step 1: Parse script to AST
109    let program = script::parser::parse(script_source)?;
110    info!("Parsed script with {} statements", program.statements.len());
111
112    // Step 2: Use AstCompiler with full DwarfAnalyzer integration
113    let mut compiler = AstCompiler::new(
114        Some(process_analyzer),
115        compile_options.binary_path_hint.clone(),
116        trace_id.unwrap_or(0), // Default starting trace_id is 0 if not provided
117        compile_options.clone(),
118    );
119
120    // Step 3: Compile using unified interface
121    let result = compiler.compile_program(&program, pid)?;
122
123    if result.uprobe_configs.is_empty() {
124        if !result.failed_targets.is_empty() {
125            tracing::warn!(
126                "Compilation produced 0 uprobe configs; {} target(s) failed to compile",
127                result.failed_targets.len()
128            );
129        } else {
130            tracing::warn!(
131                "Compilation completed with 0 uprobe configs (no attachable targets resolved)"
132            );
133        }
134    } else {
135        info!(
136            "Successfully compiled script: {} trace points, {} uprobe configs",
137            result.trace_count,
138            result.uprobe_configs.len()
139        );
140    }
141
142    // Concise summary for downstream logs
143    info!(
144        "Compilation summary: trace_points={}, uprobe_configs={}, failed_targets={}",
145        result.trace_count,
146        result.uprobe_configs.len(),
147        result.failed_targets.len()
148    );
149
150    Ok(result)
151}
152
153/// Print AST for debugging
154pub fn print_ast(program: &crate::script::Program) {
155    info!("\n=== AST Tree ===");
156    info!("Program:");
157    for (i, stmt) in program.statements.iter().enumerate() {
158        info!("  Statement {}: {:?}", i, stmt);
159    }
160    info!("=== End AST Tree ===\n");
161}
162
163/// Save AST to file
164pub fn save_ast_to_file(program: &crate::script::Program, filename: &str) -> Result<()> {
165    let mut ast_content = String::new();
166    ast_content.push_str("=== AST Tree ===\n");
167    ast_content.push_str("Program:\n");
168    for (i, stmt) in program.statements.iter().enumerate() {
169        ast_content.push_str(&format!("  Statement {i}: {stmt:?}\n"));
170    }
171    ast_content.push_str("=== End AST Tree ===\n");
172
173    let file_path = format!("{filename}.txt");
174    std::fs::write(&file_path, ast_content)
175        .map_err(|e| CompileError::Other(format!("Failed to save AST file '{file_path}': {e}")))?;
176
177    Ok(())
178}
179
180/// Format eBPF bytecode as hexadecimal string for inspection
181pub fn format_ebpf_bytecode(bytecode: &[u8]) -> String {
182    bytecode
183        .iter()
184        .map(|byte| format!("{byte:02x}"))
185        .collect::<Vec<String>>()
186        .join(" ")
187}
188
189/// Generate filename for AST files
190pub fn generate_file_name_for_ast(pid: Option<u32>, binary_path: Option<&str>) -> String {
191    let pid_part = pid
192        .map(|p| p.to_string())
193        .unwrap_or_else(|| "unknown".to_string());
194    let exec_part = binary_path
195        .and_then(|path| std::path::Path::new(path).file_name())
196        .and_then(|name| name.to_str())
197        .unwrap_or("unknown");
198
199    format!("gs_{pid_part}_{exec_part}_ast")
200}