1pub mod ebpf;
4pub mod script;
5
6use crate::script::compiler::AstCompiler;
7use ebpf::context::CodeGenError;
8pub use ghostscope_dwarf::RuntimeCapabilities;
9use ghostscope_dwarf::{CfaRulePlan, CompactUnwindRow, RegisterRecoveryPlan};
10use ghostscope_process::module_probe;
11pub use ghostscope_process::{PidFilterSpec, PidNamespaceId};
12use script::parser::ParseError;
13use std::borrow::Cow;
14use tracing::info;
15
16const X86_64_DWARF_RIP: u16 = 16;
17const X86_64_DWARF_RBP: u16 = 6;
18const X86_64_DWARF_RSP: u16 = 7;
19
20pub fn hello() -> &'static str {
21 "Hello from ghostscope-compiler!"
22}
23
24pub use ghostscope_protocol::bpf_abi::{
25 BacktraceTailCallState, BacktraceUnwindRow, BACKTRACE_RA_AT_CFA_OFFSET, BACKTRACE_RA_REGISTER,
26 BACKTRACE_RA_SAME_VALUE, BACKTRACE_RA_UNDEFINED, BACKTRACE_RA_VAL_CFA_OFFSET,
27 BACKTRACE_RECOVERY_AT_CFA_OFFSET, BACKTRACE_RECOVERY_REGISTER, BACKTRACE_RECOVERY_SAME_VALUE,
28 BACKTRACE_RECOVERY_UNDEFINED, BACKTRACE_RECOVERY_VAL_CFA_OFFSET, BACKTRACE_TAIL_NO_NEXT_SLOT,
29 BACKTRACE_TAIL_STATE_ACTIVE_SLOT_OFFSET, BACKTRACE_TAIL_STATE_CURRENT_IP_OFFSET,
30 BACKTRACE_TAIL_STATE_CURRENT_RBP_OFFSET, BACKTRACE_TAIL_STATE_CURRENT_RSP_OFFSET,
31 BACKTRACE_TAIL_STATE_ERROR_CODE_OFFSET, BACKTRACE_TAIL_STATE_EVENT_SIZE_OFFSET,
32 BACKTRACE_TAIL_STATE_FLAGS_OFFSET, BACKTRACE_TAIL_STATE_FRAME_COUNT_OFFSET,
33 BACKTRACE_TAIL_STATE_INST_OFFSET_OFFSET, BACKTRACE_TAIL_STATE_MODULE_BIAS_OFFSET,
34 BACKTRACE_TAIL_STATE_MODULE_COOKIE_OFFSET, BACKTRACE_TAIL_STATE_NEXT_SLOT_OFFSET,
35 BACKTRACE_TAIL_STATE_OFFSETS_FOUND_OFFSET, BACKTRACE_TAIL_STATE_REQUESTED_DEPTH_OFFSET,
36 BACKTRACE_TAIL_STATE_SIZE, BACKTRACE_TAIL_STATE_TAIL_CALLS_OFFSET,
37 BACKTRACE_UNWIND_ROW_CFA_OFFSET_OFFSET, BACKTRACE_UNWIND_ROW_CFA_REGISTER_OFFSET,
38 BACKTRACE_UNWIND_ROW_PC_END_OFFSET, BACKTRACE_UNWIND_ROW_PC_START_OFFSET,
39 BACKTRACE_UNWIND_ROW_RA_KIND_OFFSET, BACKTRACE_UNWIND_ROW_RA_OFFSET_OFFSET,
40 BACKTRACE_UNWIND_ROW_RA_REGISTER_OFFSET, BACKTRACE_UNWIND_ROW_RBP_KIND_OFFSET,
41 BACKTRACE_UNWIND_ROW_RBP_OFFSET_OFFSET, BACKTRACE_UNWIND_ROW_RBP_REGISTER_OFFSET,
42 BACKTRACE_UNWIND_ROW_SIZE, BACKTRACE_UNWIND_WORDS_PER_ROW, BACKTRACE_UNWIND_WORD_CFA_OFFSET,
43 BACKTRACE_UNWIND_WORD_PC_END, BACKTRACE_UNWIND_WORD_PC_START, BACKTRACE_UNWIND_WORD_RA_OFFSET,
44 BACKTRACE_UNWIND_WORD_RBP_OFFSET, BACKTRACE_UNWIND_WORD_REGISTERS,
45};
46
47#[derive(Debug, thiserror::Error)]
48pub enum CompileError {
49 #[error("Parse error: {0}")]
50 Parse(#[from] Box<ParseError>),
51
52 #[error("Code generation error: {0}")]
53 CodeGen(#[from] CodeGenError),
54
55 #[error("LLVM error: {0}")]
56 LLVM(String),
57
58 #[error("{0}")]
59 Other(String),
60}
61
62pub type Result<T> = std::result::Result<T, CompileError>;
63
64impl From<ParseError> for CompileError {
65 fn from(err: ParseError) -> Self {
66 CompileError::Parse(Box::new(err))
67 }
68}
69
70impl CompileError {
71 pub fn user_message(&self) -> Cow<'_, str> {
72 match self {
73 CompileError::Parse(err) => Cow::Owned(format!("Parse error: {err}")),
74 CompileError::CodeGen(err) => err.user_message(),
75 CompileError::LLVM(message) | CompileError::Other(message) => Cow::Borrowed(message),
76 }
77 }
78}
79
80impl CodeGenError {
81 pub fn user_message(&self) -> Cow<'_, str> {
82 match self {
83 CodeGenError::VariableNotInScope(name) => {
84 Cow::Owned(format!("Use of variable '{name}' outside of its scope"))
85 }
86 CodeGenError::VariableUnavailable(message) => Cow::Borrowed(message),
87 CodeGenError::TypeSizeNotAvailable(name) => Cow::Owned(format!(
88 "Variable '{name}' has no concrete DWARF size at this probe PC"
89 )),
90 _ => Cow::Owned(self.to_string()),
91 }
92 }
93}
94
95pub use script::compiler::{CompilationResult, UProbeConfig};
97
98#[derive(Debug, Clone, Copy, PartialEq, Eq)]
100pub enum EventMapType {
101 RingBuf,
103 PerfEventArray,
105}
106
107#[derive(Debug, Clone)]
109pub struct CompileOptions {
110 pub save_llvm_ir: bool,
111 pub save_ebpf: bool,
112 pub save_ast: bool,
113 pub binary_path_hint: Option<String>,
114 pub target_binary_path: Option<String>,
117 pub ringbuf_size: u64,
118 pub proc_module_offsets_max_entries: u64,
119 pub backtrace_unwind_rows_max_entries: u32,
121 pub perf_page_count: u32,
122 pub event_map_type: EventMapType,
123 pub mem_dump_cap: u32,
125 pub compare_cap: u32,
127 pub max_trace_event_size: u32,
129 pub backtrace_depth: u8,
131 pub selected_index: Option<usize>,
134 pub pid_filter_spec: Option<PidFilterSpec>,
137 pub special_pid_ns: Option<PidNamespaceId>,
140 pub proc_offsets_pid_ns: Option<PidNamespaceId>,
151 pub input_pid: Option<u32>,
154 pub runtime_capabilities: RuntimeCapabilities,
156}
157
158impl Default for CompileOptions {
159 fn default() -> Self {
160 Self {
161 save_llvm_ir: false,
162 save_ebpf: false,
163 save_ast: false,
164 binary_path_hint: None,
165 target_binary_path: None,
166 ringbuf_size: 262144, proc_module_offsets_max_entries: 4096, backtrace_unwind_rows_max_entries: DEFAULT_BACKTRACE_UNWIND_ROWS_MAX_ENTRIES,
169 perf_page_count: 64, event_map_type: EventMapType::RingBuf, mem_dump_cap: 256, compare_cap: 64, max_trace_event_size: 32768, backtrace_depth: DEFAULT_BACKTRACE_DEPTH,
175 selected_index: None,
176 pid_filter_spec: None,
177 special_pid_ns: None,
178 proc_offsets_pid_ns: None,
179 input_pid: None,
180 runtime_capabilities: RuntimeCapabilities::default(),
181 }
182 }
183}
184
185pub const DEFAULT_BACKTRACE_DEPTH: u8 = 128;
186pub const MAX_BACKTRACE_DEPTH: u8 = 128;
187pub const DEFAULT_BACKTRACE_UNWIND_ROWS_MAX_ENTRIES: u32 = 65_536;
188pub const MIN_BACKTRACE_UNWIND_ROWS_MAX_ENTRIES: u32 = 1_024;
189pub const MAX_BACKTRACE_UNWIND_ROWS_MAX_ENTRIES: u32 = 1_048_576;
190
191pub fn module_cookie_for_path(module_path: &str) -> u64 {
192 module_probe::cookie_for_path(module_path)
193}
194
195pub fn backtrace_unwind_row_from_compact(
196 row: &CompactUnwindRow,
197) -> Option<ghostscope_protocol::BacktraceUnwindRow> {
198 if !row.bpf_supported {
199 return None;
200 }
201 let CfaRulePlan::RegPlusOffset {
202 register,
203 offset: cfa_offset,
204 } = &row.cfa
205 else {
206 return None;
207 };
208 if !backtrace_supported_state_register(*register) {
209 return None;
210 }
211
212 let mut wire = ghostscope_protocol::BacktraceUnwindRow {
213 pc_start: row.pc_start,
214 pc_end: row.pc_end,
215 cfa_offset: *cfa_offset,
216 cfa_register: *register,
217 ..Default::default()
218 };
219
220 match &row.return_address {
221 RegisterRecoveryPlan::AtCfaOffset { offset } => {
222 wire.ra_kind = BACKTRACE_RECOVERY_AT_CFA_OFFSET;
223 wire.ra_offset = *offset;
224 wire.ra_register = row.return_address_register;
225 }
226 _ => return None,
227 }
228
229 match row.rbp.as_ref() {
230 Some(RegisterRecoveryPlan::AtCfaOffset { offset }) => {
231 wire.rbp_kind = BACKTRACE_RECOVERY_AT_CFA_OFFSET;
232 wire.rbp_offset = *offset;
233 wire.rbp_register = X86_64_DWARF_RBP;
234 }
235 Some(RegisterRecoveryPlan::ValCfaOffset { offset }) => {
236 wire.rbp_kind = BACKTRACE_RECOVERY_VAL_CFA_OFFSET;
237 wire.rbp_offset = *offset;
238 wire.rbp_register = X86_64_DWARF_RBP;
239 }
240 Some(RegisterRecoveryPlan::Register { register }) => {
241 if !backtrace_supported_state_register(*register) {
242 return None;
243 }
244 wire.rbp_kind = BACKTRACE_RECOVERY_REGISTER;
245 wire.rbp_register = *register;
246 }
247 Some(RegisterRecoveryPlan::SameValue { register }) => {
248 if !backtrace_supported_state_register(*register) {
249 return None;
250 }
251 wire.rbp_kind = BACKTRACE_RECOVERY_SAME_VALUE;
252 wire.rbp_register = *register;
253 }
254 Some(RegisterRecoveryPlan::Undefined) | None => {
255 wire.rbp_kind = BACKTRACE_RECOVERY_SAME_VALUE;
256 wire.rbp_register = X86_64_DWARF_RBP;
257 }
258 _ => return None,
259 }
260
261 Some(wire)
262}
263
264fn backtrace_supported_state_register(register: u16) -> bool {
265 matches!(
266 register,
267 X86_64_DWARF_RIP | X86_64_DWARF_RBP | X86_64_DWARF_RSP
268 )
269}
270
271pub fn compile_script(
276 script_source: &str,
277 process_analyzer: &ghostscope_dwarf::DwarfAnalyzer,
278 pid: Option<u32>,
279 trace_id: Option<u32>,
280 compile_options: &CompileOptions,
281) -> Result<CompilationResult> {
282 info!("Starting unified script compilation with DwarfAnalyzer (multi-module support)");
283
284 let program = script::parser::parse(script_source)?;
286 info!("Parsed script with {} statements", program.statements.len());
287
288 let mut compiler = AstCompiler::new(
290 Some(process_analyzer),
291 compile_options.binary_path_hint.clone(),
292 trace_id.unwrap_or(0), compile_options.clone(),
294 );
295
296 let result = compiler.compile_program(&program, pid)?;
298
299 if result.uprobe_configs.is_empty() {
300 if !result.failed_targets.is_empty() {
301 tracing::warn!(
302 "Compilation produced 0 uprobe configs; {} target(s) failed to compile",
303 result.failed_targets.len()
304 );
305 } else {
306 tracing::warn!(
307 "Compilation completed with 0 uprobe configs (no attachable targets resolved)"
308 );
309 }
310 } else {
311 info!(
312 "Successfully compiled script: {} trace points, {} uprobe configs",
313 result.trace_count,
314 result.uprobe_configs.len()
315 );
316 }
317
318 info!(
320 "Compilation summary: trace_points={}, uprobe_configs={}, failed_targets={}",
321 result.trace_count,
322 result.uprobe_configs.len(),
323 result.failed_targets.len()
324 );
325
326 Ok(result)
327}
328
329pub fn print_ast(program: &crate::script::Program) {
331 info!("\n=== AST Tree ===");
332 info!("Program:");
333 for (i, stmt) in program.statements.iter().enumerate() {
334 info!(" Statement {}: {:?}", i, stmt);
335 }
336 info!("=== End AST Tree ===\n");
337}
338
339pub fn save_ast_to_file(program: &crate::script::Program, filename: &str) -> Result<()> {
341 let mut ast_content = String::new();
342 ast_content.push_str("=== AST Tree ===\n");
343 ast_content.push_str("Program:\n");
344 for (i, stmt) in program.statements.iter().enumerate() {
345 ast_content.push_str(&format!(" Statement {i}: {stmt:?}\n"));
346 }
347 ast_content.push_str("=== End AST Tree ===\n");
348
349 let file_path = format!("{filename}.txt");
350 std::fs::write(&file_path, ast_content)
351 .map_err(|e| CompileError::Other(format!("Failed to save AST file '{file_path}': {e}")))?;
352
353 Ok(())
354}
355
356pub fn format_ebpf_bytecode(bytecode: &[u8]) -> String {
358 bytecode
359 .iter()
360 .map(|byte| format!("{byte:02x}"))
361 .collect::<Vec<String>>()
362 .join(" ")
363}
364
365pub fn generate_file_name_for_ast(pid: Option<u32>, binary_path: Option<&str>) -> String {
367 let pid_part = pid
368 .map(|p| p.to_string())
369 .unwrap_or_else(|| "unknown".to_string());
370 let exec_part = binary_path
371 .and_then(|path| std::path::Path::new(path).file_name())
372 .and_then(|name| name.to_str())
373 .unwrap_or("unknown");
374
375 format!("gs_{pid_part}_{exec_part}_ast")
376}
377
378#[cfg(test)]
379mod tests {
380 use super::*;
381
382 #[test]
383 fn user_message_strips_codegen_prefix_for_unavailable_variable() {
384 let err = CompileError::CodeGen(CodeGenError::VariableUnavailable(
385 "'x' is optimized out at the selected probe PC".to_string(),
386 ));
387
388 assert_eq!(
389 err.user_message().as_ref(),
390 "'x' is optimized out at the selected probe PC"
391 );
392 }
393
394 #[test]
395 fn user_message_formats_scope_errors_for_users() {
396 let err = CompileError::CodeGen(CodeGenError::VariableNotInScope("x".to_string()));
397
398 assert_eq!(
399 err.user_message().as_ref(),
400 "Use of variable 'x' outside of its scope"
401 );
402 }
403
404 #[test]
405 fn backtrace_unwind_row_layout_matches_bpf_map_value() {
406 assert_eq!(BACKTRACE_UNWIND_ROW_SIZE, 56);
407 assert_eq!(BACKTRACE_UNWIND_ROW_PC_START_OFFSET, 0);
408 assert_eq!(BACKTRACE_UNWIND_ROW_PC_END_OFFSET, 8);
409 assert_eq!(BACKTRACE_UNWIND_ROW_CFA_OFFSET_OFFSET, 16);
410 assert_eq!(BACKTRACE_UNWIND_ROW_RA_OFFSET_OFFSET, 24);
411 assert_eq!(BACKTRACE_UNWIND_ROW_RBP_OFFSET_OFFSET, 32);
412 assert_eq!(BACKTRACE_UNWIND_ROW_CFA_REGISTER_OFFSET, 40);
413 assert_eq!(BACKTRACE_UNWIND_ROW_RA_REGISTER_OFFSET, 42);
414 assert_eq!(BACKTRACE_UNWIND_ROW_RBP_REGISTER_OFFSET, 44);
415 assert_eq!(BACKTRACE_UNWIND_ROW_RA_KIND_OFFSET, 46);
416 assert_eq!(BACKTRACE_UNWIND_ROW_RBP_KIND_OFFSET, 47);
417 }
418
419 #[test]
420 fn backtrace_tail_call_state_layout_matches_bpf_accessors() {
421 assert_eq!(BACKTRACE_TAIL_STATE_SIZE, 64);
422 assert_eq!(BACKTRACE_TAIL_STATE_CURRENT_IP_OFFSET, 0);
423 assert_eq!(BACKTRACE_TAIL_STATE_CURRENT_RSP_OFFSET, 8);
424 assert_eq!(BACKTRACE_TAIL_STATE_CURRENT_RBP_OFFSET, 16);
425 assert_eq!(BACKTRACE_TAIL_STATE_MODULE_BIAS_OFFSET, 24);
426 assert_eq!(BACKTRACE_TAIL_STATE_MODULE_COOKIE_OFFSET, 32);
427 assert_eq!(BACKTRACE_TAIL_STATE_INST_OFFSET_OFFSET, 40);
428 assert_eq!(BACKTRACE_TAIL_STATE_EVENT_SIZE_OFFSET, 44);
429 assert_eq!(BACKTRACE_TAIL_STATE_FRAME_COUNT_OFFSET, 48);
430 assert_eq!(BACKTRACE_TAIL_STATE_REQUESTED_DEPTH_OFFSET, 49);
431 assert_eq!(BACKTRACE_TAIL_STATE_OFFSETS_FOUND_OFFSET, 50);
432 assert_eq!(BACKTRACE_TAIL_STATE_TAIL_CALLS_OFFSET, 51);
433 assert_eq!(BACKTRACE_TAIL_STATE_FLAGS_OFFSET, 52);
434 assert_eq!(BACKTRACE_TAIL_STATE_ACTIVE_SLOT_OFFSET, 53);
435 assert_eq!(BACKTRACE_TAIL_STATE_ERROR_CODE_OFFSET, 54);
436 assert_eq!(BACKTRACE_TAIL_STATE_NEXT_SLOT_OFFSET, 56);
437 }
438}