1use crate::script::ast::{Program, Statement, TracePattern};
2use crate::CompileError;
3use inkwell::context::Context;
5use std::borrow::Cow;
6use std::collections::hash_map::DefaultHasher;
7use std::hash::{Hash, Hasher};
8use std::path::Path;
9use tracing::{debug, error, info, warn};
10
11#[derive(Debug, Clone)]
13pub struct ResolvedTarget {
14 pub function_name: Option<String>,
15 pub function_address: Option<u64>,
16 pub binary_path: String,
17 pub uprobe_offset: Option<u64>,
18 pub pattern: TracePattern,
19}
20
21#[derive(Debug, Clone)]
23pub struct UProbeConfig {
24 pub trace_pattern: TracePattern,
26
27 pub binary_path: String,
29
30 pub function_name: Option<String>,
32
33 pub function_address: Option<u64>,
35
36 pub uprobe_offset: Option<u64>,
38
39 pub target_pid: Option<u32>,
41
42 pub ebpf_bytecode: Vec<u8>,
44
45 pub ebpf_function_name: String,
47
48 pub assigned_trace_id: u32,
50
51 pub trace_context: ghostscope_protocol::TraceContext,
53
54 pub resolved_address_index: Option<usize>,
56}
57
58#[derive(Debug)]
60pub struct CompilationResult {
61 pub uprobe_configs: Vec<UProbeConfig>,
62 pub trace_count: usize,
63 pub target_info: String,
64 pub failed_targets: Vec<FailedTarget>, pub next_available_trace_id: u32, }
67
68#[derive(Debug, Clone)]
70pub struct FailedTarget {
71 pub target_name: String,
72 pub pc_address: u64,
73 pub error_message: String,
74}
75
76pub struct AstCompiler<'a> {
78 process_analyzer: Option<&'a mut ghostscope_dwarf::DwarfAnalyzer>,
79 uprobe_configs: Vec<UProbeConfig>,
80 failed_targets: Vec<FailedTarget>, binary_path_hint: Option<String>,
82 current_trace_id: u32, compile_options: crate::CompileOptions, }
85
86impl<'a> AstCompiler<'a> {
87 pub fn new(
88 process_analyzer: Option<&'a mut ghostscope_dwarf::DwarfAnalyzer>,
89 binary_path_hint: Option<String>,
90 starting_trace_id: u32,
91 compile_options: crate::CompileOptions,
92 ) -> Self {
93 Self {
94 process_analyzer,
95 uprobe_configs: Vec::new(),
96 failed_targets: Vec::new(),
97 binary_path_hint,
98 current_trace_id: starting_trace_id,
99 compile_options,
100 }
101 }
102
103 pub fn compile_program(
105 &mut self,
106 program: &Program,
107 pid: Option<u32>,
108 ) -> Result<CompilationResult, CompileError> {
109 info!(
110 "Starting unified AST compilation with {} statements",
111 program.statements.len()
112 );
113
114 let mut successful_trace_points = 0;
119 let mut failed_trace_points = 0;
120 let mut first_error: Option<String> = None;
121
122 for (index, stmt) in program.statements.iter().enumerate() {
123 match stmt {
124 Statement::TracePoint { pattern, body } => {
125 debug!("Processing trace point {}: {:?}", index, pattern);
126 match self.process_trace_point(pattern, body, pid, index) {
127 Ok(_) => {
128 successful_trace_points += 1;
129 info!(
130 "✓ Successfully processed trace point {}: {:?}",
131 index, pattern
132 );
133 }
134 Err(e) => {
135 failed_trace_points += 1;
136 let error_msg = e.to_string();
137 error!(
138 "❌ Failed to process trace point {}: {:?} - Error: {}",
139 index, pattern, error_msg
140 );
141
142 if first_error.is_none() {
144 first_error = Some(error_msg.clone());
145 }
146
147 let has_failed_for_this_pattern =
151 self.failed_targets.iter().any(|ft| match pattern {
152 TracePattern::FunctionName(name) => ft.target_name == *name,
153 TracePattern::SourceLine {
154 file_path,
155 line_number,
156 } => ft.target_name == format!("{file_path}:{line_number}"),
157 _ => false,
158 });
159
160 if !has_failed_for_this_pattern {
161 let target_name = match pattern {
162 TracePattern::FunctionName(name) => name.clone(),
163 TracePattern::SourceLine {
164 file_path,
165 line_number,
166 } => format!("{file_path}:{line_number}"),
167 _ => format!("trace_point_{index}"),
168 };
169
170 self.failed_targets.push(FailedTarget {
171 target_name,
172 pc_address: 0,
173 error_message: error_msg,
174 });
175 }
176 }
177 }
178 }
179 _ => {
180 warn!("Skipping non-trace statement: {:?}", stmt);
181 }
183 }
184 }
185
186 if successful_trace_points > 0 && failed_trace_points == 0 {
187 info!(
188 "All {} trace points processed successfully",
189 successful_trace_points
190 );
191 } else if successful_trace_points > 0 && failed_trace_points > 0 {
192 warn!(
193 "Partial success: {} trace points successful, {} failed",
194 successful_trace_points, failed_trace_points
195 );
196 } else if failed_trace_points > 0 {
197 error!("All {} trace points failed to process", failed_trace_points);
199 return Err(CompileError::Other(
200 first_error.unwrap_or_else(|| "All trace points failed".to_string()),
201 ));
202 }
203
204 let target_info = self.generate_target_info_summary();
206
207 info!(
208 "Compilation completed: {} uprobe configs generated",
209 self.uprobe_configs.len()
210 );
211
212 Ok(CompilationResult {
213 uprobe_configs: std::mem::take(&mut self.uprobe_configs),
214 failed_targets: std::mem::take(&mut self.failed_targets),
215 trace_count: self.uprobe_configs.len(),
216 target_info,
217 next_available_trace_id: self.current_trace_id,
218 })
219 }
220
221 fn describe_source_line_failure(&mut self, file_path: &str, line_number: u32) -> String {
223 let default_msg =
224 format!("No addresses resolved for source line {file_path}:{line_number}");
225
226 let analyzer = match &mut self.process_analyzer {
227 Some(a) => a,
228 None => return default_msg,
229 };
230
231 let grouped = match analyzer.get_grouped_file_info_by_module() {
232 Ok(g) => g,
233 Err(_) => return default_msg,
234 };
235
236 let mut all_full_paths: Vec<String> = Vec::new();
238 let mut same_basename_paths: Vec<String> = Vec::new();
239 let has_sep = file_path.contains('/') || file_path.contains('\\');
240 let basename = Path::new(file_path)
241 .file_name()
242 .and_then(|s| s.to_str())
243 .unwrap_or(file_path);
244
245 for (_module, files) in &grouped {
246 for f in files {
247 all_full_paths.push(f.full_path.clone());
248 if f.basename == basename {
249 same_basename_paths.push(f.full_path.clone());
250 }
251 }
252 }
253
254 if all_full_paths.is_empty() {
255 return default_msg;
256 }
257
258 let exact_match = all_full_paths.iter().any(|p| p == file_path);
259 let suffix_matches: Vec<String> = all_full_paths
260 .iter()
261 .filter(|p| p.ends_with(file_path) || file_path.ends_with(p.as_str()))
262 .cloned()
263 .collect();
264
265 if same_basename_paths.is_empty() {
266 return format!(
267 "Source file not found in DWARF: {file_path}.\n- Tips: use 'srcpath map <dwf_comp_dir> <local_dir>' or pass full DWARF path.\n- List files with: dwarf-tool source-files or 'info source-files'"
268 );
269 }
270
271 if !exact_match && suffix_matches.is_empty() && has_sep && same_basename_paths.len() > 1 {
272 let mut samples = same_basename_paths.clone();
273 samples.sort();
274 samples.dedup();
275 if samples.len() > 3 {
276 samples.truncate(3);
277 }
278 let sample_list = samples.join("\n - ");
279 return format!(
280 "Multiple files named '{basename}' found; the given path '{file_path}' did not uniquely match by suffix.\nTry a more specific path or add a path mapping (srcpath map).\nExamples:\n - {sample_list}"
281 );
282 }
283
284 let mut hit_candidates: Vec<String> = Vec::new();
286 let probe_list: Vec<String> = if !suffix_matches.is_empty() {
287 suffix_matches
288 } else {
289 let mut v = same_basename_paths.clone();
290 v.sort();
291 v.dedup();
292 v.truncate(20);
293 v
294 };
295
296 for cand in &probe_list {
297 let addrs = analyzer.lookup_addresses_by_source_line(cand, line_number);
298 if !addrs.is_empty() {
299 hit_candidates.push(cand.clone());
300 if hit_candidates.len() >= 3 {
301 break;
302 }
303 }
304 }
305
306 if !hit_candidates.is_empty() {
307 let list = hit_candidates.join("\n - ");
308 return format!(
309 "Ambiguous path: '{file_path}' did not resolve, but found addresses for the same line in:\n - {list}\nPlease use a more specific path (full DWARF path) or add a mapping (srcpath map)."
310 );
311 }
312
313 format!(
314 "No executable addresses for {file_path}:{line_number} (file exists in DWARF but this line has no statement).\nTry a nearby line, or rebuild with debug info and lower optimization (e.g., -g -O0)."
315 )
316 }
317
318 fn process_trace_point(
320 &mut self,
321 pattern: &TracePattern,
322 statements: &[Statement],
323 pid: Option<u32>,
324 index: usize,
325 ) -> Result<(), CompileError> {
326 match pattern {
327 TracePattern::SourceLine {
328 file_path,
329 line_number,
330 } => {
331 let module_addresses = if let Some(analyzer) = &mut self.process_analyzer {
333 analyzer.lookup_addresses_by_source_line(file_path, *line_number)
334 } else {
335 Vec::new()
336 };
337
338 if module_addresses.is_empty() {
339 let detailed = self.describe_source_line_failure(file_path, *line_number);
340 return Err(CompileError::Other(detailed));
341 }
342
343 debug!(
344 "Resolved {}:{} to {} address(es) for trace point {}",
345 file_path,
346 line_number,
347 module_addresses.len(),
348 index
349 );
350
351 if let Some(idx) = self.compile_options.selected_index {
353 if idx == 0 || idx > module_addresses.len() {
354 return Err(CompileError::Other(format!(
355 "Selected index {idx} is out of range for {file_path}:{line_number} (valid 1..={}). Use 'info' to view indices.",
356 module_addresses.len()
357 )));
358 }
359 }
360
361 let mut successful_addresses = 0;
363 let mut failed_addresses = 0;
364 let iterator: Box<dyn Iterator<Item = (usize, &ghostscope_dwarf::ModuleAddress)>> =
366 if let Some(idx) = self.compile_options.selected_index {
367 let i = idx - 1; Box::new(std::iter::once((idx, &module_addresses[i])))
369 } else {
370 Box::new(module_addresses.iter().enumerate().map(|(i, m)| (i + 1, m)))
371 };
372
373 for (global_idx, module_address) in iterator {
374 let file_off = self.process_analyzer.as_ref().and_then(|an| {
376 an.vaddr_to_file_offset(&module_address.module_path, module_address.address)
377 });
378
379 let target_info = ResolvedTarget {
380 function_name: Some(format!("{file_path}:{line_number}")),
381 function_address: Some(module_address.address),
383 binary_path: module_address.module_path.to_string_lossy().to_string(),
384 uprobe_offset: file_off,
386 pattern: pattern.clone(),
387 };
388
389 match self.generate_ebpf_for_target(
390 &target_info,
391 statements,
392 pid,
393 Some(global_idx),
394 ) {
395 Ok(uprobe_config) => {
396 self.uprobe_configs.push(uprobe_config);
397 successful_addresses += 1;
398 info!(
399 "✓ Successfully generated eBPF for {}:{} at 0x{:x}",
400 file_path, line_number, module_address.address
401 );
402 }
403 Err(e) => {
404 failed_addresses += 1;
405 error!(
406 "❌ Failed to generate eBPF for {}:{} at 0x{:x}: {}",
407 file_path, line_number, module_address.address, e
408 );
409
410 self.failed_targets.push(FailedTarget {
412 target_name: format!("{file_path}:{line_number}"),
413 pc_address: module_address.address,
414 error_message: e.to_string(),
415 });
416
417 }
419 }
420 }
421
422 if successful_addresses > 0 && failed_addresses == 0 {
424 info!(
425 "All {} addresses for {}:{} processed successfully",
426 successful_addresses, file_path, line_number
427 );
428 } else if successful_addresses > 0 && failed_addresses > 0 {
429 warn!(
430 "Partial success for {}:{}: {} successful, {} failed addresses",
431 file_path, line_number, successful_addresses, failed_addresses
432 );
433 } else {
434 error!(
435 "All {} addresses for {}:{} failed to process",
436 failed_addresses, file_path, line_number
437 );
438 }
440 Ok(())
441 }
442 TracePattern::Address(addr) => {
443 let module_path: Option<String> = if let Some(analyzer) = &self.process_analyzer {
447 if let Some(main) = analyzer.get_main_executable() {
448 Some(main.path)
449 } else {
450 let libs = analyzer.get_shared_library_info();
451 if libs.len() == 1 {
452 Some(libs[0].library_path.clone())
453 } else {
454 None
455 }
456 }
457 } else {
458 None
459 };
460
461 let module_path = match module_path {
462 Some(p) => p,
463 None => {
464 return Err(CompileError::Other(
465 "No module available to resolve address. In PID mode, default module is the main executable. In target mode (-t <binary>), the specified binary is used (including .so).".to_string(),
466 ));
467 }
468 };
469
470 let file_off = self
472 .process_analyzer
473 .as_ref()
474 .and_then(|an| an.vaddr_to_file_offset(&module_path, *addr));
475
476 if file_off.is_none() {
477 return Err(CompileError::Other(format!(
478 "Address 0x{addr:x} is not within a loadable segment of '{module_path}' (cannot compute file offset)"
479 )));
480 }
481
482 let target_info = ResolvedTarget {
483 function_name: None,
484 function_address: Some(*addr),
485 binary_path: module_path,
486 uprobe_offset: file_off,
487 pattern: pattern.clone(),
488 };
489
490 match self.generate_ebpf_for_target(&target_info, statements, pid, None) {
491 Ok(uprobe_config) => {
492 self.uprobe_configs.push(uprobe_config);
493 info!("✓ Successfully generated eBPF for address 0x{:x}", addr);
494 Ok(())
495 }
496 Err(e) => {
497 let error_msg = e.to_string();
498 self.failed_targets.push(FailedTarget {
499 target_name: format!("0x{addr:x}"),
500 pc_address: *addr,
501 error_message: error_msg.clone(),
502 });
503 Err(CompileError::Other(error_msg))
504 }
505 }
506 }
507 TracePattern::AddressInModule { module, address } => {
508 let module_path = if let Some(analyzer) = &self.process_analyzer {
510 let mut modules: Vec<String> = Vec::new();
511 if let Some(main) = analyzer.get_main_executable() {
512 modules.push(main.path);
513 }
514 for lib in analyzer.get_shared_library_info() {
515 modules.push(lib.library_path);
516 }
517
518 if let Some(found) = modules.iter().find(|p| p.as_str() == module) {
520 found.clone()
521 } else {
522 let candidates: Vec<String> = modules
524 .into_iter()
525 .filter(|p| p.ends_with(module))
526 .collect();
527 match candidates.len() {
528 0 => {
529 return Err(CompileError::Other(format!(
530 "Module '{module}' not found among loaded modules. Use full path or a unique suffix."
531 )));
532 }
533 1 => candidates[0].clone(),
534 _ => {
535 let mut sample = candidates.clone();
536 sample.truncate(5);
537 return Err(CompileError::Other(format!(
538 "Ambiguous module suffix '{}'. Candidates:\n - {}\nPlease use a more specific suffix or full path.",
539 module,
540 sample.join("\n - ")
541 )));
542 }
543 }
544 }
545 } else {
546 return Err(CompileError::Other(
547 "No process analyzer available to resolve module".to_string(),
548 ));
549 };
550
551 let file_off = self
553 .process_analyzer
554 .as_ref()
555 .and_then(|an| an.vaddr_to_file_offset(&module_path, *address));
556
557 if file_off.is_none() {
558 return Err(CompileError::Other(format!(
559 "Address 0x{address:x} is not within a loadable segment of '{module_path}' (cannot compute file offset)"
560 )));
561 }
562
563 let target_info = ResolvedTarget {
564 function_name: None,
565 function_address: Some(*address),
566 binary_path: module_path,
567 uprobe_offset: file_off,
568 pattern: pattern.clone(),
569 };
570
571 match self.generate_ebpf_for_target(&target_info, statements, pid, None) {
572 Ok(uprobe_config) => {
573 self.uprobe_configs.push(uprobe_config);
574 info!(
575 "✓ Successfully generated eBPF for module-qualified address {}:0x{:x}",
576 module, address
577 );
578 Ok(())
579 }
580 Err(e) => {
581 let error_msg = e.to_string();
582 self.failed_targets.push(FailedTarget {
583 target_name: format!("{module}:0x{address:x}"),
584 pc_address: *address,
585 error_message: error_msg.clone(),
586 });
587 Err(CompileError::Other(error_msg))
588 }
589 }
590 }
591 TracePattern::FunctionName(func_name) => {
592 let module_addresses = if let Some(analyzer) = &mut self.process_analyzer {
594 analyzer.lookup_function_addresses(func_name)
595 } else {
596 Vec::new()
597 };
598
599 if module_addresses.is_empty() {
600 return Err(CompileError::Other(format!(
602 "No addresses resolved for function '{func_name}' - function not found in debug symbols"
603 )));
604 }
605
606 let total_addresses: usize = module_addresses.len();
607 debug!(
608 "Resolved function '{}' to {} address(es) across {} modules",
609 func_name,
610 total_addresses,
611 module_addresses.len()
612 );
613
614 if let Some(idx) = self.compile_options.selected_index {
616 if idx == 0 || idx > module_addresses.len() {
617 return Err(CompileError::Other(format!(
618 "Selected index {idx} is out of range for function '{func_name}' (valid 1..={}). Use 'info function {func_name}' to view indices.",
619 module_addresses.len()
620 )));
621 }
622 }
623
624 let mut successful_addresses = 0;
627 let mut failed_addresses = 0;
628
629 let iterator: Box<dyn Iterator<Item = (usize, &ghostscope_dwarf::ModuleAddress)>> =
631 if let Some(idx) = self.compile_options.selected_index {
632 let i = idx - 1; Box::new(std::iter::once((idx, &module_addresses[i])))
634 } else {
635 Box::new(module_addresses.iter().enumerate().map(|(i, m)| (i + 1, m)))
636 };
637
638 for (global_idx, module_address) in iterator {
639 let file_off = self.process_analyzer.as_ref().and_then(|an| {
641 an.vaddr_to_file_offset(&module_address.module_path, module_address.address)
642 });
643
644 let target_info = ResolvedTarget {
645 function_name: Some(func_name.clone()),
646 function_address: Some(module_address.address),
647 binary_path: module_address.module_path.to_string_lossy().to_string(),
648 uprobe_offset: file_off,
649 pattern: pattern.clone(),
650 };
651
652 match self.generate_ebpf_for_target(
653 &target_info,
654 statements,
655 pid,
656 Some(global_idx),
657 ) {
658 Ok(uprobe_config) => {
659 self.uprobe_configs.push(uprobe_config);
660 successful_addresses += 1;
661 info!(
662 "✓ Successfully generated eBPF for function '{}' at 0x{:x}",
663 func_name, module_address.address
664 );
665 }
666 Err(e) => {
667 failed_addresses += 1;
668 error!(
669 "❌ Failed to generate eBPF for function '{}' at 0x{:x}: {}",
670 func_name, module_address.address, e
671 );
672
673 self.failed_targets.push(FailedTarget {
675 target_name: func_name.clone(),
676 pc_address: module_address.address,
677 error_message: e.to_string(),
678 });
679
680 }
682 }
683 }
684
685 if successful_addresses > 0 && failed_addresses == 0 {
687 info!(
688 "All {} addresses for function '{}' processed successfully",
689 successful_addresses, func_name
690 );
691 Ok(())
692 } else if successful_addresses > 0 && failed_addresses > 0 {
693 warn!(
694 "Partial success for function '{}': {} successful, {} failed addresses",
695 func_name, successful_addresses, failed_addresses
696 );
697 Ok(())
698 } else {
699 error!(
702 "All {} addresses for function '{}' failed to process",
703 failed_addresses, func_name
704 );
705 Ok(())
706 }
707 }
708 _ => {
709 unimplemented!();
710 }
711 }
712 }
713
714 fn generate_ebpf_for_target(
716 &mut self,
717 target: &ResolvedTarget,
718 statements: &[Statement],
719 pid: Option<u32>,
720 resolved_address_index: Option<usize>,
721 ) -> Result<UProbeConfig, CompileError> {
722 let context = Context::create();
723
724 let assigned_trace_id = self.current_trace_id;
726 self.current_trace_id += 1;
727
728 let ebpf_function_name = self.generate_unified_function_name(target, assigned_trace_id);
730
731 info!(
732 "Generating eBPF code for '{}' (function: {})",
733 target.function_name.as_deref().unwrap_or("unknown"),
734 ebpf_function_name
735 );
736
737 if let Some(compile_options) = self.get_compile_options() {
739 if compile_options.save_ast {
740 let ast_filename = self.generate_filename(target, assigned_trace_id, "txt");
741 let program = Program {
743 statements: statements.to_vec(),
744 };
745 if let Err(e) = self.save_ast_to_file(&program, &ast_filename) {
746 warn!("Failed to save AST to {}: {}", ast_filename, e);
747 } else {
748 info!("Saved AST to: {}", ast_filename);
749 }
750 }
751 }
752
753 let mut codegen_new = crate::ebpf::context::NewCodeGen::new_with_process_analyzer(
755 &context,
756 &ebpf_function_name,
757 self.process_analyzer.as_deref_mut(),
758 Some(assigned_trace_id),
759 &self.compile_options,
760 )
761 .map_err(|e| CompileError::LLVM(format!("Failed to create new codegen: {e}")))?;
762
763 if let Some(function_address) = target.function_address {
765 codegen_new.set_compile_time_context(function_address, target.binary_path.clone());
766 }
767
768 info!(
769 "Compiling full AST program with {} statements",
770 statements.len()
771 );
772
773 let (_main_function, trace_context) = codegen_new
775 .compile_program(
776 &crate::script::ast::Program { statements: vec![] }, &ebpf_function_name,
778 statements,
779 pid,
780 target.function_address,
781 Some(&target.binary_path),
782 )
783 .map_err(|e| CompileError::LLVM(format!("Failed to compile AST program: {e}")))?;
784
785 info!(
786 "Generated TraceContext for '{}' with {} strings and {} variables",
787 ebpf_function_name,
788 trace_context.string_count(),
789 trace_context.variable_name_count()
790 );
791
792 let module = codegen_new.get_module();
793
794 let ebpf_bytecode =
796 self.generate_ebpf_bytecode(module, &ebpf_function_name, target, assigned_trace_id)?;
797
798 Ok(UProbeConfig {
801 trace_pattern: target.pattern.clone(),
802 binary_path: target.binary_path.clone(),
803 function_name: target.function_name.clone(),
804 function_address: target.function_address,
805 uprobe_offset: target.uprobe_offset,
806 target_pid: pid,
807 ebpf_bytecode,
808 ebpf_function_name,
809 assigned_trace_id,
810 trace_context,
811 resolved_address_index,
812 })
813 }
814
815 fn generate_target_info_summary(&self) -> String {
817 if self.uprobe_configs.is_empty() {
818 return "no_targets".to_string();
819 }
820
821 let first_target = &self.uprobe_configs[0];
822 match &first_target.function_name {
823 Some(name) => name.clone(),
824 None => format!("addr_0x{:x}", first_target.function_address.unwrap_or(0)),
825 }
826 }
827
828 fn calculate_module_hash(&self, module_path: &str) -> String {
834 let effective_path = self.effective_binary_path(module_path);
835 let mut hasher = DefaultHasher::new();
836 effective_path.hash(&mut hasher);
837 let hash = hasher.finish();
838 let truncated = (hash & 0xFFFF_FFFF) as u32;
839 let hash_hex = format!("{truncated:08x}");
840
841 info!("Module hash calculated: {} -> {}", effective_path, hash_hex);
842 hash_hex
843 }
844
845 fn generate_unified_function_name(&self, target: &ResolvedTarget, trace_id: u32) -> String {
847 let module_hash = self.calculate_module_hash(&target.binary_path);
848 let effective_path = self.effective_binary_path(&target.binary_path);
849 let address_hex = if let Some(addr) = target.function_address {
850 format!("{addr:x}")
851 } else {
852 "unknown".to_string()
853 };
854
855 let function_name = format!("ghostscope_{module_hash}_{address_hex}_trace{trace_id}");
856 info!(
857 "Generated eBPF function name: {} (module: {}, address: 0x{}, trace_id: {})",
858 function_name, effective_path, address_hex, trace_id
859 );
860
861 function_name
862 }
863
864 fn get_compile_options(&self) -> Option<&crate::CompileOptions> {
866 Some(&self.compile_options)
867 }
868
869 fn effective_binary_path<'b>(&'b self, target_path: &'b str) -> Cow<'b, str> {
871 if target_path.is_empty() {
872 if let Some(hint) = &self.binary_path_hint {
873 Cow::Owned(hint.clone())
874 } else {
875 Cow::Borrowed("unknown")
876 }
877 } else {
878 Cow::Borrowed(target_path)
879 }
880 }
881
882 fn generate_filename(&self, target: &ResolvedTarget, trace_id: u32, extension: &str) -> String {
884 let module_hash = self.calculate_module_hash(&target.binary_path);
885 let address_hex = if let Some(addr) = target.function_address {
886 format!("{addr:x}")
887 } else {
888 "unknown".to_string()
889 };
890
891 format!("gs_{module_hash}_{address_hex}_trace{trace_id}.{extension}")
892 }
893
894 fn generate_ebpf_bytecode(
896 &mut self,
897 module: &inkwell::module::Module,
898 function_name: &str,
899 target: &ResolvedTarget,
900 assigned_trace_id: u32,
901 ) -> Result<Vec<u8>, CompileError> {
902 use inkwell::targets::{FileType, Target, TargetTriple};
903 use inkwell::OptimizationLevel;
904
905 let llvm_ir = module.print_to_string().to_string();
907 let llvm_ir = llvm_ir.trim_end().to_string();
908 info!(
909 "Successfully generated LLVM IR for {}, length: {}",
910 function_name,
911 llvm_ir.len()
912 );
913
914 if let Some(compile_options) = self.get_compile_options() {
916 if compile_options.save_llvm_ir {
917 let filename = self.generate_filename(target, assigned_trace_id, "ll");
918 if let Err(e) = std::fs::write(&filename, &llvm_ir) {
919 warn!("Failed to save LLVM IR to {}: {}", filename, e);
920 } else {
921 info!("Saved LLVM IR to: {}", filename);
922 }
923 }
924 }
925
926 let triple = TargetTriple::create("bpf-pc-linux");
928 info!("Created target triple: bpf-pc-linux for {}", function_name);
929
930 let llvm_target = Target::from_triple(&triple).map_err(|e| {
932 error!("Failed to get target for {}: {}", function_name, e);
933 CompileError::LLVM(format!("Failed to get target for {function_name}: {e}"))
934 })?;
935 info!("Successfully got LLVM target for {}", function_name);
936
937 let target_machine = llvm_target
939 .create_target_machine(
940 &triple,
941 "generic", "+alu32", OptimizationLevel::Default,
944 inkwell::targets::RelocMode::PIC,
945 inkwell::targets::CodeModel::Small,
946 )
947 .ok_or_else(|| {
948 error!("Failed to create target machine for {}", function_name);
949 CompileError::LLVM(format!(
950 "Failed to create target machine for {function_name}"
951 ))
952 })?;
953 info!("Successfully created target machine for {}", function_name);
954
955 info!("Validating LLVM module for {}...", function_name);
957 if let Err(llvm_errors) = module.verify() {
958 error!(
959 "LLVM module validation failed for {}: {}",
960 function_name, llvm_errors
961 );
962 return Err(CompileError::LLVM(format!(
963 "Module validation failed for {function_name}: {llvm_errors}"
964 )));
965 }
966 info!("Module validation passed for {}", function_name);
967
968 info!("Generating eBPF object file for {}...", function_name);
970 info!("About to call LLVM write_to_memory_buffer...");
971
972 let object_code = {
973 let module_string = module.print_to_string();
975 debug!(
976 "Module IR before compilation:\n{}",
977 module_string.to_string()
978 );
979
980 use std::io::Write;
982 let _ = std::io::stderr().flush();
983 let _ = std::io::stdout().flush();
984
985 info!("Calling target_machine.write_to_memory_buffer...");
986 match target_machine.write_to_memory_buffer(module, FileType::Object) {
987 Ok(code) => {
988 info!("Successfully generated object code for {}", function_name);
989 code
990 }
991 Err(e) => {
992 error!("LLVM compilation failed for {}: {}", function_name, e);
993 error!("This might be due to unsupported eBPF instructions or invalid LLVM IR");
994
995 return Err(CompileError::LLVM(format!(
996 "eBPF compilation failed for {function_name}: {e}. This often indicates unsupported instructions or invalid IR."
997 )));
998 }
999 }
1000 };
1001
1002 info!(
1003 "Successfully generated object code for {}! Size: {}",
1004 function_name,
1005 object_code.get_size()
1006 );
1007
1008 let bytecode = object_code.as_slice().to_vec();
1010
1011 if let Some(compile_options) = self.get_compile_options() {
1013 if compile_options.save_ebpf {
1014 let filename = self.generate_filename(target, assigned_trace_id, "o");
1015 if let Err(e) = std::fs::write(&filename, &bytecode) {
1016 warn!("Failed to save eBPF object to {}: {}", filename, e);
1017 } else {
1018 info!("Saved eBPF object to: {}", filename);
1019 }
1020 }
1021
1022 }
1024
1025 Ok(bytecode)
1026 }
1027
1028 fn save_ast_to_file(
1030 &mut self,
1031 program: &crate::script::ast::Program,
1032 filename: &str,
1033 ) -> Result<(), CompileError> {
1034 let mut ast_content = String::new();
1035 ast_content.push_str("=== AST Tree ===\n");
1036 ast_content.push_str("Program:\n");
1037 for (i, stmt) in program.statements.iter().enumerate() {
1038 ast_content.push_str(&format!(" Statement {i}: {stmt:?}\n"));
1039 }
1040 ast_content.push_str("=== End AST Tree ===\n");
1041
1042 std::fs::write(filename, ast_content).map_err(|e| {
1043 CompileError::Other(format!("Failed to save AST file '{filename}': {e}"))
1044 })?;
1045
1046 Ok(())
1047 }
1048}