1use crate::script::ast::{Program, Statement, TracePattern};
2use crate::CompileError;
3use ghostscope_dwarf::ModuleDefaultPolicy;
5use inkwell::context::Context;
6use std::borrow::Cow;
7use std::collections::hash_map::DefaultHasher;
8use std::fmt::Write as _;
9use std::hash::{Hash, Hasher};
10use tracing::{debug, error, info, warn};
11
12#[derive(Debug, Clone)]
14pub struct ResolvedTarget {
15 pub function_name: Option<String>,
16 pub function_address: Option<u64>,
17 pub binary_path: String,
18 pub uprobe_offset: Option<u64>,
19 pub pattern: TracePattern,
20}
21
22#[derive(Debug, Clone)]
24pub struct UProbeConfig {
25 pub trace_pattern: TracePattern,
27
28 pub binary_path: String,
30
31 pub function_name: Option<String>,
33
34 pub function_address: Option<u64>,
36
37 pub uprobe_offset: Option<u64>,
39
40 pub target_pid: Option<u32>,
42
43 pub ebpf_bytecode: Vec<u8>,
45
46 pub ebpf_function_name: String,
48
49 pub assigned_trace_id: u32,
51
52 pub trace_context: ghostscope_protocol::TraceContext,
54
55 pub backtrace_unwind_rows: Vec<ghostscope_protocol::BacktraceUnwindRow>,
57
58 pub backtrace_module_row_ranges: Vec<(u64, ghostscope_protocol::BacktraceModuleRowRange)>,
60
61 pub backtrace_tail_call_program: Option<crate::ebpf::context::BacktraceTailCallProgram>,
63
64 pub resolved_address_index: Option<usize>,
66}
67
68#[derive(Debug)]
70pub struct CompilationResult {
71 pub uprobe_configs: Vec<UProbeConfig>,
72 pub trace_count: usize,
73 pub target_info: String,
74 pub failed_targets: Vec<FailedTarget>, pub next_available_trace_id: u32, }
77
78#[derive(Debug, Clone)]
80pub struct FailedTarget {
81 pub target_name: String,
82 pub pc_address: u64,
83 pub error_message: String,
84}
85
86pub struct AstCompiler<'a> {
88 process_analyzer: Option<&'a ghostscope_dwarf::DwarfAnalyzer>,
89 uprobe_configs: Vec<UProbeConfig>,
90 failed_targets: Vec<FailedTarget>, binary_path_hint: Option<String>,
92 current_trace_id: u32, compile_options: crate::CompileOptions, }
95
96impl<'a> AstCompiler<'a> {
97 pub fn new(
98 process_analyzer: Option<&'a ghostscope_dwarf::DwarfAnalyzer>,
99 binary_path_hint: Option<String>,
100 starting_trace_id: u32,
101 compile_options: crate::CompileOptions,
102 ) -> Self {
103 Self {
104 process_analyzer,
105 uprobe_configs: Vec::new(),
106 failed_targets: Vec::new(),
107 binary_path_hint,
108 current_trace_id: starting_trace_id,
109 compile_options,
110 }
111 }
112
113 pub fn compile_program(
115 &mut self,
116 program: &Program,
117 pid: Option<u32>,
118 ) -> Result<CompilationResult, CompileError> {
119 info!(
120 "Starting unified AST compilation with {} statements",
121 program.statements.len()
122 );
123
124 if program.statements.is_empty() {
127 return Err(CompileError::Other(
128 "script must contain at least one top-level trace statement".to_string(),
129 ));
130 }
131
132 let mut successful_trace_points = 0;
135 let mut failed_trace_points = 0;
136 let mut first_error: Option<String> = None;
137
138 for (index, stmt) in program.statements.iter().enumerate() {
139 match stmt {
140 Statement::TracePoint { pattern, body } => {
141 debug!("Processing trace point {}: {:?}", index, pattern);
142 match self.process_trace_point(pattern, body, pid, index) {
143 Ok(_) => {
144 successful_trace_points += 1;
145 info!(
146 "✓ Successfully processed trace point {}: {:?}",
147 index, pattern
148 );
149 }
150 Err(e) => {
151 failed_trace_points += 1;
152 let error_msg = e.user_message().into_owned();
153 error!(
154 "❌ Failed to process trace point {}: {:?} - Error: {}",
155 index, pattern, error_msg
156 );
157
158 if first_error.is_none() {
160 first_error = Some(error_msg.clone());
161 }
162
163 let has_failed_for_this_pattern =
167 self.failed_targets.iter().any(|ft| match pattern {
168 TracePattern::FunctionName(name) => ft.target_name == *name,
169 TracePattern::SourceLine {
170 file_path,
171 line_number,
172 } => ft.target_name == format!("{file_path}:{line_number}"),
173 TracePattern::Address(addr) => {
174 ft.target_name == format!("0x{addr:x}")
175 && ft.pc_address == *addr
176 }
177 TracePattern::AddressInModule { module, address } => {
178 ft.target_name == format!("{module}:0x{address:x}")
179 && ft.pc_address == *address
180 }
181 _ => false,
182 });
183
184 if !has_failed_for_this_pattern {
185 let target_name = match pattern {
186 TracePattern::FunctionName(name) => name.clone(),
187 TracePattern::SourceLine {
188 file_path,
189 line_number,
190 } => format!("{file_path}:{line_number}"),
191 TracePattern::Address(addr) => format!("0x{addr:x}"),
192 TracePattern::AddressInModule { module, address } => {
193 format!("{module}:0x{address:x}")
194 }
195 _ => format!("trace_point_{index}"),
196 };
197 let pc_address = match pattern {
198 TracePattern::Address(addr) => *addr,
199 TracePattern::AddressInModule { address, .. } => *address,
200 _ => 0,
201 };
202
203 self.failed_targets.push(FailedTarget {
204 target_name,
205 pc_address,
206 error_message: error_msg,
207 });
208 }
209 }
210 }
211 }
212 _ => {
213 let message = Self::top_level_statement_error(stmt);
214 error!("{message}");
215 return Err(CompileError::Other(message));
216 }
217 }
218 }
219
220 if successful_trace_points > 0 && failed_trace_points == 0 {
221 info!(
222 "All {} trace points processed successfully",
223 successful_trace_points
224 );
225 } else if successful_trace_points > 0 && failed_trace_points > 0 {
226 warn!(
227 "Partial success: {} trace points successful, {} failed",
228 successful_trace_points, failed_trace_points
229 );
230 } else if failed_trace_points > 0 {
231 error!("All {} trace points failed to process", failed_trace_points);
233 return Err(CompileError::Other(
234 self.format_all_trace_points_failed_error(first_error),
235 ));
236 }
237
238 let target_info = self.generate_target_info_summary();
240
241 info!(
242 "Compilation completed: {} uprobe configs generated",
243 self.uprobe_configs.len()
244 );
245
246 let trace_count = self.uprobe_configs.len();
247 Ok(CompilationResult {
248 uprobe_configs: std::mem::take(&mut self.uprobe_configs),
249 failed_targets: std::mem::take(&mut self.failed_targets),
250 trace_count,
251 target_info,
252 next_available_trace_id: self.current_trace_id,
253 })
254 }
255
256 fn format_all_trace_points_failed_error(&self, first_error: Option<String>) -> String {
257 let mut message = first_error.unwrap_or_else(|| "All trace points failed".to_string());
258 if self.failed_targets.is_empty() {
259 return message;
260 }
261
262 message.push_str("\n\nFailed targets:\n");
263 for failed in &self.failed_targets {
264 let _ = writeln!(
265 message,
266 " - {} at 0x{:x}: {}",
267 failed.target_name, failed.pc_address, failed.error_message
268 );
269 }
270 message.push_str("\nTip: fix the reported compile-time errors above.");
271 message
272 }
273
274 fn configured_target_path(&self) -> Option<&str> {
275 self.compile_options
276 .target_binary_path
277 .as_deref()
278 .map(str::trim)
279 .filter(|path| !path.is_empty())
280 }
281
282 fn top_level_statement_error(statement: &Statement) -> String {
283 let kind = match statement {
284 Statement::Print(_) => "print",
285 Statement::Backtrace(_) => "backtrace",
286 Statement::Expr(_) => "expression",
287 Statement::VarDeclaration { .. } | Statement::AliasDeclaration { .. } => "let",
288 Statement::If { .. } => "if",
289 Statement::Block(_) => "block",
290 Statement::TracePoint { .. } => "trace",
291 };
292 format!(
293 "top-level {kind} statement is not allowed in a script file; put executable statements inside a trace block, for example: trace <target> {{ ... }}"
294 )
295 }
296
297 fn process_trace_point(
299 &mut self,
300 pattern: &TracePattern,
301 statements: &[Statement],
302 pid: Option<u32>,
303 index: usize,
304 ) -> Result<(), CompileError> {
305 match pattern {
306 TracePattern::SourceLine {
307 file_path,
308 line_number,
309 } => {
310 let analyzer = self.process_analyzer.ok_or_else(|| {
311 CompileError::Other(
312 "No process analyzer available to resolve source line".to_string(),
313 )
314 })?;
315 let target_path = self.configured_target_path();
316 let source_line = analyzer
317 .resolve_source_line_addresses_best_effort(
318 analyzer.source_line_candidates(file_path),
319 *line_number,
320 target_path,
321 )
322 .map_err(|e| CompileError::Other(e.to_string()))?;
323 let module_addresses = source_line.addresses;
324
325 if source_line.raw_address_count > 0 && module_addresses.is_empty() {
326 let target = target_path.unwrap_or("<unknown>");
327 return Err(CompileError::Other(format!(
328 "No addresses resolved for source line {file_path}:{line_number} in -t target '{target}'. When -t and -p are combined, -t takes precedence for trace target resolution."
329 )));
330 }
331 if module_addresses.is_empty() {
332 let detailed = analyzer.describe_source_line_failure(file_path, *line_number);
333 return Err(CompileError::Other(detailed));
334 }
335
336 debug!(
337 "Resolved {}:{} to {} address(es) for trace point {}",
338 file_path,
339 line_number,
340 module_addresses.len(),
341 index
342 );
343
344 if let Some(idx) = self.compile_options.selected_index {
346 if idx == 0 || idx > module_addresses.len() {
347 return Err(CompileError::Other(format!(
348 "Selected index {idx} is out of range for {file_path}:{line_number} (valid 1..={}). Use 'info' to view indices.",
349 module_addresses.len()
350 )));
351 }
352 }
353
354 let mut successful_addresses = 0;
356 let mut failed_addresses = 0;
357 let iterator: Box<dyn Iterator<Item = (usize, &ghostscope_dwarf::ModuleAddress)>> =
359 if let Some(idx) = self.compile_options.selected_index {
360 let i = idx - 1; Box::new(std::iter::once((idx, &module_addresses[i])))
362 } else {
363 Box::new(module_addresses.iter().enumerate().map(|(i, m)| (i + 1, m)))
364 };
365
366 for (global_idx, module_address) in iterator {
367 let file_off = self.process_analyzer.as_ref().and_then(|an| {
369 an.vaddr_to_file_offset(&module_address.module_path, module_address.address)
370 });
371
372 let target_info = ResolvedTarget {
373 function_name: Some(format!("{file_path}:{line_number}")),
374 function_address: Some(module_address.address),
376 binary_path: module_address.module_path.to_string_lossy().to_string(),
377 uprobe_offset: file_off,
379 pattern: pattern.clone(),
380 };
381
382 match self.generate_ebpf_for_target(
383 &target_info,
384 statements,
385 pid,
386 Some(global_idx),
387 ) {
388 Ok(uprobe_config) => {
389 self.uprobe_configs.push(uprobe_config);
390 successful_addresses += 1;
391 info!(
392 "✓ Successfully generated eBPF for {}:{} at 0x{:x}",
393 file_path, line_number, module_address.address
394 );
395 }
396 Err(e) => {
397 failed_addresses += 1;
398 error!(
399 "❌ Failed to generate eBPF for {}:{} at 0x{:x}: {}",
400 file_path, line_number, module_address.address, e
401 );
402
403 self.failed_targets.push(FailedTarget {
405 target_name: format!("{file_path}:{line_number}"),
406 pc_address: module_address.address,
407 error_message: e.user_message().into_owned(),
408 });
409
410 }
412 }
413 }
414
415 if successful_addresses > 0 && failed_addresses == 0 {
417 info!(
418 "All {} addresses for {}:{} processed successfully",
419 successful_addresses, file_path, line_number
420 );
421 } else if successful_addresses > 0 && failed_addresses > 0 {
422 warn!(
423 "Partial success for {}:{}: {} successful, {} failed addresses",
424 file_path, line_number, successful_addresses, failed_addresses
425 );
426 } else {
427 error!(
428 "All {} addresses for {}:{} failed to process",
429 failed_addresses, file_path, line_number
430 );
431 }
433 Ok(())
434 }
435 TracePattern::Address(addr) => {
436 let analyzer = self.process_analyzer.ok_or_else(|| {
437 CompileError::Other(
438 "No process analyzer available to resolve address".to_string(),
439 )
440 })?;
441 let module_path = analyzer
442 .resolve_address_module(
443 None,
444 self.configured_target_path(),
445 ModuleDefaultPolicy::MainExecutableOrSingleSharedLibrary,
446 )
447 .map_err(|e| CompileError::Other(e.to_string()))?;
448
449 let file_off = analyzer.vaddr_to_file_offset(&module_path, *addr);
451 let module_path = module_path.to_string_lossy().to_string();
452
453 if file_off.is_none() {
454 return Err(CompileError::Other(format!(
455 "Address 0x{addr:x} is not within a loadable segment of '{module_path}' (cannot compute file offset)"
456 )));
457 }
458
459 let target_info = ResolvedTarget {
460 function_name: None,
461 function_address: Some(*addr),
462 binary_path: module_path,
463 uprobe_offset: file_off,
464 pattern: pattern.clone(),
465 };
466
467 match self.generate_ebpf_for_target(&target_info, statements, pid, None) {
468 Ok(uprobe_config) => {
469 self.uprobe_configs.push(uprobe_config);
470 info!("✓ Successfully generated eBPF for address 0x{:x}", addr);
471 Ok(())
472 }
473 Err(e) => {
474 let error_msg = e.user_message().into_owned();
475 error!(
476 "❌ Failed to generate eBPF for address 0x{:x}: {}",
477 addr, error_msg
478 );
479 self.failed_targets.push(FailedTarget {
480 target_name: format!("0x{addr:x}"),
481 pc_address: *addr,
482 error_message: error_msg,
483 });
484 Err(e)
485 }
486 }
487 }
488 TracePattern::AddressInModule { module, address } => {
489 let analyzer = self.process_analyzer.ok_or_else(|| {
490 CompileError::Other(
491 "No process analyzer available to resolve module".to_string(),
492 )
493 })?;
494 let module_path = analyzer
495 .resolve_address_module(
496 Some(module),
497 self.configured_target_path(),
498 ModuleDefaultPolicy::MainExecutableOrSingleSharedLibrary,
499 )
500 .map_err(|e| CompileError::Other(e.to_string()))?;
501
502 let file_off = analyzer.vaddr_to_file_offset(&module_path, *address);
504 let module_path = module_path.to_string_lossy().to_string();
505
506 if file_off.is_none() {
507 return Err(CompileError::Other(format!(
508 "Address 0x{address:x} is not within a loadable segment of '{module_path}' (cannot compute file offset)"
509 )));
510 }
511
512 let target_info = ResolvedTarget {
513 function_name: None,
514 function_address: Some(*address),
515 binary_path: module_path,
516 uprobe_offset: file_off,
517 pattern: pattern.clone(),
518 };
519
520 match self.generate_ebpf_for_target(&target_info, statements, pid, None) {
521 Ok(uprobe_config) => {
522 self.uprobe_configs.push(uprobe_config);
523 info!(
524 "✓ Successfully generated eBPF for module-qualified address {}:0x{:x}",
525 module, address
526 );
527 Ok(())
528 }
529 Err(e) => {
530 let error_msg = e.user_message().into_owned();
531 error!(
532 "❌ Failed to generate eBPF for module-qualified address {}:0x{:x}: {}",
533 module, address, error_msg
534 );
535 self.failed_targets.push(FailedTarget {
536 target_name: format!("{module}:0x{address:x}"),
537 pc_address: *address,
538 error_message: error_msg,
539 });
540 Err(e)
541 }
542 }
543 }
544 TracePattern::FunctionName(func_name) => {
545 let module_addresses = if let Some(analyzer) = self.process_analyzer {
547 analyzer.lookup_function_addresses(func_name)
548 } else {
549 Vec::new()
550 };
551
552 if module_addresses.is_empty() {
553 return Err(CompileError::Other(format!(
555 "No addresses resolved for function '{func_name}' - function not found in debug symbols"
556 )));
557 }
558
559 let original_address_count = module_addresses.len();
560 let target_path = self.configured_target_path();
561 let module_addresses = self
562 .process_analyzer
563 .ok_or_else(|| {
564 CompileError::Other(
565 "No process analyzer available to resolve -t target".to_string(),
566 )
567 })?
568 .filter_module_addresses_to_target(module_addresses, target_path)
569 .map_err(|e| CompileError::Other(e.to_string()))?;
570 if original_address_count > 0 && module_addresses.is_empty() {
571 let target = target_path.unwrap_or("<unknown>");
572 return Err(CompileError::Other(format!(
573 "No addresses resolved for function '{func_name}' in -t target '{target}'. When -t and -p are combined, -t takes precedence for trace target resolution."
574 )));
575 }
576
577 let total_addresses: usize = module_addresses.len();
578 debug!(
579 "Resolved function '{}' to {} address(es) across {} modules",
580 func_name,
581 total_addresses,
582 module_addresses.len()
583 );
584
585 if let Some(idx) = self.compile_options.selected_index {
587 if idx == 0 || idx > module_addresses.len() {
588 return Err(CompileError::Other(format!(
589 "Selected index {idx} is out of range for function '{func_name}' (valid 1..={}). Use 'info function {func_name}' to view indices.",
590 module_addresses.len()
591 )));
592 }
593 }
594
595 let mut successful_addresses = 0;
598 let mut failed_addresses = 0;
599
600 let iterator: Box<dyn Iterator<Item = (usize, &ghostscope_dwarf::ModuleAddress)>> =
602 if let Some(idx) = self.compile_options.selected_index {
603 let i = idx - 1; Box::new(std::iter::once((idx, &module_addresses[i])))
605 } else {
606 Box::new(module_addresses.iter().enumerate().map(|(i, m)| (i + 1, m)))
607 };
608
609 for (global_idx, module_address) in iterator {
610 let file_off = self.process_analyzer.as_ref().and_then(|an| {
612 an.vaddr_to_file_offset(&module_address.module_path, module_address.address)
613 });
614
615 let target_info = ResolvedTarget {
616 function_name: Some(func_name.clone()),
617 function_address: Some(module_address.address),
618 binary_path: module_address.module_path.to_string_lossy().to_string(),
619 uprobe_offset: file_off,
620 pattern: pattern.clone(),
621 };
622
623 match self.generate_ebpf_for_target(
624 &target_info,
625 statements,
626 pid,
627 Some(global_idx),
628 ) {
629 Ok(uprobe_config) => {
630 self.uprobe_configs.push(uprobe_config);
631 successful_addresses += 1;
632 info!(
633 "✓ Successfully generated eBPF for function '{}' at 0x{:x}",
634 func_name, module_address.address
635 );
636 }
637 Err(e) => {
638 failed_addresses += 1;
639 error!(
640 "❌ Failed to generate eBPF for function '{}' at 0x{:x}: {}",
641 func_name, module_address.address, e
642 );
643
644 self.failed_targets.push(FailedTarget {
646 target_name: func_name.clone(),
647 pc_address: module_address.address,
648 error_message: e.user_message().into_owned(),
649 });
650
651 }
653 }
654 }
655
656 if successful_addresses > 0 && failed_addresses == 0 {
658 info!(
659 "All {} addresses for function '{}' processed successfully",
660 successful_addresses, func_name
661 );
662 Ok(())
663 } else if successful_addresses > 0 && failed_addresses > 0 {
664 warn!(
665 "Partial success for function '{}': {} successful, {} failed addresses",
666 func_name, successful_addresses, failed_addresses
667 );
668 Ok(())
669 } else {
670 error!(
673 "All {} addresses for function '{}' failed to process",
674 failed_addresses, func_name
675 );
676 Ok(())
677 }
678 }
679 _ => {
680 unimplemented!();
681 }
682 }
683 }
684
685 fn generate_ebpf_for_target(
687 &mut self,
688 target: &ResolvedTarget,
689 statements: &[Statement],
690 pid: Option<u32>,
691 resolved_address_index: Option<usize>,
692 ) -> Result<UProbeConfig, CompileError> {
693 let context = Context::create();
694
695 let assigned_trace_id = self.current_trace_id;
697 self.current_trace_id += 1;
698
699 let ebpf_function_name = self.generate_unified_function_name(target, assigned_trace_id);
701 let compile_options = self.compile_options.clone();
702 let binary_path_hint = self.binary_path_hint.clone();
703
704 info!(
705 "Generating eBPF code for '{}' (function: {})",
706 target.function_name.as_deref().unwrap_or("unknown"),
707 ebpf_function_name
708 );
709
710 if let Some(compile_options) = self.get_compile_options() {
712 if compile_options.save_ast {
713 let ast_filename = self.generate_filename(target, assigned_trace_id, "txt");
714 let program = Program {
716 statements: statements.to_vec(),
717 };
718 if let Err(e) = self.save_ast_to_file(&program, &ast_filename) {
719 warn!("Failed to save AST to {}: {}", ast_filename, e);
720 } else {
721 info!("Saved AST to: {}", ast_filename);
722 }
723 }
724 }
725
726 let mut codegen = crate::ebpf::context::EbpfContext::new_with_process_analyzer(
728 &context,
729 &ebpf_function_name,
730 self.process_analyzer,
731 Some(assigned_trace_id),
732 &self.compile_options,
733 )
734 .map_err(|e| CompileError::LLVM(format!("Failed to create new codegen: {e}")))?;
735
736 if let Some(function_address) = target.function_address {
738 codegen.set_compile_time_context(function_address, target.binary_path.clone());
739 }
740
741 info!(
742 "Compiling full AST program with {} statements",
743 statements.len()
744 );
745
746 let (_main_function, trace_context) = codegen
748 .compile_program(
749 &crate::script::ast::Program { statements: vec![] }, &ebpf_function_name,
751 statements,
752 pid,
753 target.function_address,
754 Some(&target.binary_path),
755 )
756 .map_err(CompileError::CodeGen)?;
757
758 info!(
759 "Generated TraceContext for '{}' with {} strings and {} variables",
760 ebpf_function_name,
761 trace_context.string_count(),
762 trace_context.variable_name_count()
763 );
764
765 let module = codegen.get_module();
766
767 let ebpf_bytecode = Self::generate_ebpf_bytecode(
769 module,
770 &ebpf_function_name,
771 target,
772 assigned_trace_id,
773 &compile_options,
774 binary_path_hint.as_deref(),
775 )?;
776
777 Ok(UProbeConfig {
780 trace_pattern: target.pattern.clone(),
781 binary_path: target.binary_path.clone(),
782 function_name: target.function_name.clone(),
783 function_address: target.function_address,
784 uprobe_offset: target.uprobe_offset,
785 target_pid: pid,
786 ebpf_bytecode,
787 ebpf_function_name,
788 assigned_trace_id,
789 trace_context,
790 backtrace_unwind_rows: codegen.backtrace_unwind_rows.clone(),
791 backtrace_module_row_ranges: codegen
792 .backtrace_module_row_ranges
793 .iter()
794 .map(|entry| (entry.cookie, entry.range))
795 .collect(),
796 backtrace_tail_call_program: codegen.backtrace_tail_call_program(),
797 resolved_address_index,
798 })
799 }
800
801 fn generate_target_info_summary(&self) -> String {
803 if self.uprobe_configs.is_empty() {
804 return "no_targets".to_string();
805 }
806
807 let first_target = &self.uprobe_configs[0];
808 match &first_target.function_name {
809 Some(name) => name.clone(),
810 None => format!("addr_0x{:x}", first_target.function_address.unwrap_or(0)),
811 }
812 }
813
814 fn calculate_module_hash(&self, module_path: &str) -> String {
820 let effective_path = self.effective_binary_path(module_path);
821 let mut hasher = DefaultHasher::new();
822 effective_path.hash(&mut hasher);
823 let hash = hasher.finish();
824 let truncated = (hash & 0xFFFF_FFFF) as u32;
825 let hash_hex = format!("{truncated:08x}");
826
827 info!("Module hash calculated: {} -> {}", effective_path, hash_hex);
828 hash_hex
829 }
830
831 fn generate_unified_function_name(&self, target: &ResolvedTarget, trace_id: u32) -> String {
833 let module_hash = self.calculate_module_hash(&target.binary_path);
834 let effective_path = self.effective_binary_path(&target.binary_path);
835 let address_hex = if let Some(addr) = target.function_address {
836 format!("{addr:x}")
837 } else {
838 "unknown".to_string()
839 };
840
841 let function_name = format!("ghostscope_{module_hash}_{address_hex}_trace{trace_id}");
842 info!(
843 "Generated eBPF function name: {} (module: {}, address: 0x{}, trace_id: {})",
844 function_name, effective_path, address_hex, trace_id
845 );
846
847 function_name
848 }
849
850 fn get_compile_options(&self) -> Option<&crate::CompileOptions> {
852 Some(&self.compile_options)
853 }
854
855 fn effective_binary_path<'b>(&'b self, target_path: &'b str) -> Cow<'b, str> {
857 if target_path.is_empty() {
858 if let Some(hint) = &self.binary_path_hint {
859 Cow::Owned(hint.clone())
860 } else {
861 Cow::Borrowed("unknown")
862 }
863 } else {
864 Cow::Borrowed(target_path)
865 }
866 }
867
868 fn generate_filename(&self, target: &ResolvedTarget, trace_id: u32, extension: &str) -> String {
870 let module_hash = self.calculate_module_hash(&target.binary_path);
871 let address_hex = if let Some(addr) = target.function_address {
872 format!("{addr:x}")
873 } else {
874 "unknown".to_string()
875 };
876
877 format!("gs_{module_hash}_{address_hex}_trace{trace_id}.{extension}")
878 }
879
880 fn generate_filename_with_hint(
881 target: &ResolvedTarget,
882 trace_id: u32,
883 extension: &str,
884 binary_path_hint: Option<&str>,
885 ) -> String {
886 let effective_path = if target.binary_path.is_empty() {
887 binary_path_hint.unwrap_or("unknown")
888 } else {
889 target.binary_path.as_str()
890 };
891 let mut hasher = DefaultHasher::new();
892 effective_path.hash(&mut hasher);
893 let module_hash = format!("{:08x}", (hasher.finish() & 0xFFFF_FFFF) as u32);
894 let address_hex = if let Some(addr) = target.function_address {
895 format!("{addr:x}")
896 } else {
897 "unknown".to_string()
898 };
899
900 format!("gs_{module_hash}_{address_hex}_trace{trace_id}.{extension}")
901 }
902
903 fn generate_ebpf_bytecode(
905 module: &inkwell::module::Module,
906 function_name: &str,
907 target: &ResolvedTarget,
908 assigned_trace_id: u32,
909 compile_options: &crate::CompileOptions,
910 binary_path_hint: Option<&str>,
911 ) -> Result<Vec<u8>, CompileError> {
912 use inkwell::targets::{FileType, Target, TargetTriple};
913 use inkwell::OptimizationLevel;
914
915 if compile_options.save_llvm_ir {
916 let filename = Self::generate_filename_with_hint(
917 target,
918 assigned_trace_id,
919 "ll",
920 binary_path_hint,
921 );
922 if let Err(e) = module.print_to_file(&filename) {
923 warn!("Failed to save LLVM IR to {}: {}", filename, e);
924 } else {
925 info!("Saved LLVM IR to: {}", filename);
926 }
927 }
928 info!("Successfully generated LLVM module for {}", function_name);
929
930 let triple = TargetTriple::create("bpf-pc-linux");
932 info!("Created target triple: bpf-pc-linux for {}", function_name);
933
934 let llvm_target = Target::from_triple(&triple).map_err(|e| {
936 error!("Failed to get target for {}: {}", function_name, e);
937 CompileError::LLVM(format!("Failed to get target for {function_name}: {e}"))
938 })?;
939 info!("Successfully got LLVM target for {}", function_name);
940
941 let target_machine = llvm_target
943 .create_target_machine(
944 &triple,
945 "generic", "+alu32", OptimizationLevel::Default,
948 inkwell::targets::RelocMode::PIC,
949 inkwell::targets::CodeModel::Small,
950 )
951 .ok_or_else(|| {
952 error!("Failed to create target machine for {}", function_name);
953 CompileError::LLVM(format!(
954 "Failed to create target machine for {function_name}"
955 ))
956 })?;
957 info!("Successfully created target machine for {}", function_name);
958
959 info!("Validating LLVM module for {}...", function_name);
961 if let Err(llvm_errors) = module.verify() {
962 error!(
963 "LLVM module validation failed for {}: {}",
964 function_name, llvm_errors
965 );
966 return Err(CompileError::LLVM(format!(
967 "Module validation failed for {function_name}: {llvm_errors}"
968 )));
969 }
970 info!("Module validation passed for {}", function_name);
971
972 info!("Generating eBPF object file for {}...", function_name);
974 info!("About to call LLVM write_to_memory_buffer...");
975
976 let object_code = {
977 use std::io::Write;
979 let _ = std::io::stderr().flush();
980 let _ = std::io::stdout().flush();
981
982 info!("Calling target_machine.write_to_memory_buffer...");
983 match target_machine.write_to_memory_buffer(module, FileType::Object) {
984 Ok(code) => {
985 info!("Successfully generated object code for {}", function_name);
986 code
987 }
988 Err(e) => {
989 error!("LLVM compilation failed for {}: {}", function_name, e);
990 error!("This might be due to unsupported eBPF instructions or invalid LLVM IR");
991
992 return Err(CompileError::LLVM(format!(
993 "eBPF compilation failed for {function_name}: {e}. This often indicates unsupported instructions or invalid IR."
994 )));
995 }
996 }
997 };
998
999 info!(
1000 "Successfully generated object code for {}! Size: {}",
1001 function_name,
1002 object_code.get_size()
1003 );
1004
1005 let bytecode = object_code.as_slice().to_vec();
1007
1008 if compile_options.save_ebpf {
1010 let filename =
1011 Self::generate_filename_with_hint(target, assigned_trace_id, "o", binary_path_hint);
1012 if let Err(e) = std::fs::write(&filename, &bytecode) {
1013 warn!("Failed to save eBPF object to {}: {}", filename, e);
1014 } else {
1015 info!("Saved eBPF object to: {}", filename);
1016 }
1017 }
1018
1019 Ok(bytecode)
1021 }
1022
1023 fn save_ast_to_file(
1025 &mut self,
1026 program: &crate::script::ast::Program,
1027 filename: &str,
1028 ) -> Result<(), CompileError> {
1029 let mut ast_content = String::new();
1030 ast_content.push_str("=== AST Tree ===\n");
1031 ast_content.push_str("Program:\n");
1032 for (i, stmt) in program.statements.iter().enumerate() {
1033 ast_content.push_str(&format!(" Statement {i}: {stmt:?}\n"));
1034 }
1035 ast_content.push_str("=== End AST Tree ===\n");
1036
1037 std::fs::write(filename, ast_content).map_err(|e| {
1038 CompileError::Other(format!("Failed to save AST file '{filename}': {e}"))
1039 })?;
1040
1041 Ok(())
1042 }
1043}