Skip to main content

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