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 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 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 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) = 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) = 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 let compile_options = self.compile_options.clone();
731 let binary_path_hint = self.binary_path_hint.clone();
732
733 info!(
734 "Generating eBPF code for '{}' (function: {})",
735 target.function_name.as_deref().unwrap_or("unknown"),
736 ebpf_function_name
737 );
738
739 if let Some(compile_options) = self.get_compile_options() {
741 if compile_options.save_ast {
742 let ast_filename = self.generate_filename(target, assigned_trace_id, "txt");
743 let program = Program {
745 statements: statements.to_vec(),
746 };
747 if let Err(e) = self.save_ast_to_file(&program, &ast_filename) {
748 warn!("Failed to save AST to {}: {}", ast_filename, e);
749 } else {
750 info!("Saved AST to: {}", ast_filename);
751 }
752 }
753 }
754
755 let mut codegen_new = crate::ebpf::context::NewCodeGen::new_with_process_analyzer(
757 &context,
758 &ebpf_function_name,
759 self.process_analyzer,
760 Some(assigned_trace_id),
761 &self.compile_options,
762 )
763 .map_err(|e| CompileError::LLVM(format!("Failed to create new codegen: {e}")))?;
764
765 if let Some(function_address) = target.function_address {
767 codegen_new.set_compile_time_context(function_address, target.binary_path.clone());
768 }
769
770 info!(
771 "Compiling full AST program with {} statements",
772 statements.len()
773 );
774
775 let (_main_function, trace_context) = codegen_new
777 .compile_program(
778 &crate::script::ast::Program { statements: vec![] }, &ebpf_function_name,
780 statements,
781 pid,
782 target.function_address,
783 Some(&target.binary_path),
784 )
785 .map_err(|e| CompileError::LLVM(format!("Failed to compile AST program: {e}")))?;
786
787 info!(
788 "Generated TraceContext for '{}' with {} strings and {} variables",
789 ebpf_function_name,
790 trace_context.string_count(),
791 trace_context.variable_name_count()
792 );
793
794 let module = codegen_new.get_module();
795
796 let ebpf_bytecode = Self::generate_ebpf_bytecode(
798 module,
799 &ebpf_function_name,
800 target,
801 assigned_trace_id,
802 &compile_options,
803 binary_path_hint.as_deref(),
804 )?;
805
806 Ok(UProbeConfig {
809 trace_pattern: target.pattern.clone(),
810 binary_path: target.binary_path.clone(),
811 function_name: target.function_name.clone(),
812 function_address: target.function_address,
813 uprobe_offset: target.uprobe_offset,
814 target_pid: pid,
815 ebpf_bytecode,
816 ebpf_function_name,
817 assigned_trace_id,
818 trace_context,
819 resolved_address_index,
820 })
821 }
822
823 fn generate_target_info_summary(&self) -> String {
825 if self.uprobe_configs.is_empty() {
826 return "no_targets".to_string();
827 }
828
829 let first_target = &self.uprobe_configs[0];
830 match &first_target.function_name {
831 Some(name) => name.clone(),
832 None => format!("addr_0x{:x}", first_target.function_address.unwrap_or(0)),
833 }
834 }
835
836 fn calculate_module_hash(&self, module_path: &str) -> String {
842 let effective_path = self.effective_binary_path(module_path);
843 let mut hasher = DefaultHasher::new();
844 effective_path.hash(&mut hasher);
845 let hash = hasher.finish();
846 let truncated = (hash & 0xFFFF_FFFF) as u32;
847 let hash_hex = format!("{truncated:08x}");
848
849 info!("Module hash calculated: {} -> {}", effective_path, hash_hex);
850 hash_hex
851 }
852
853 fn generate_unified_function_name(&self, target: &ResolvedTarget, trace_id: u32) -> String {
855 let module_hash = self.calculate_module_hash(&target.binary_path);
856 let effective_path = self.effective_binary_path(&target.binary_path);
857 let address_hex = if let Some(addr) = target.function_address {
858 format!("{addr:x}")
859 } else {
860 "unknown".to_string()
861 };
862
863 let function_name = format!("ghostscope_{module_hash}_{address_hex}_trace{trace_id}");
864 info!(
865 "Generated eBPF function name: {} (module: {}, address: 0x{}, trace_id: {})",
866 function_name, effective_path, address_hex, trace_id
867 );
868
869 function_name
870 }
871
872 fn get_compile_options(&self) -> Option<&crate::CompileOptions> {
874 Some(&self.compile_options)
875 }
876
877 fn effective_binary_path<'b>(&'b self, target_path: &'b str) -> Cow<'b, str> {
879 if target_path.is_empty() {
880 if let Some(hint) = &self.binary_path_hint {
881 Cow::Owned(hint.clone())
882 } else {
883 Cow::Borrowed("unknown")
884 }
885 } else {
886 Cow::Borrowed(target_path)
887 }
888 }
889
890 fn generate_filename(&self, target: &ResolvedTarget, trace_id: u32, extension: &str) -> String {
892 let module_hash = self.calculate_module_hash(&target.binary_path);
893 let address_hex = if let Some(addr) = target.function_address {
894 format!("{addr:x}")
895 } else {
896 "unknown".to_string()
897 };
898
899 format!("gs_{module_hash}_{address_hex}_trace{trace_id}.{extension}")
900 }
901
902 fn generate_filename_with_hint(
903 target: &ResolvedTarget,
904 trace_id: u32,
905 extension: &str,
906 binary_path_hint: Option<&str>,
907 ) -> String {
908 let effective_path = if target.binary_path.is_empty() {
909 binary_path_hint.unwrap_or("unknown")
910 } else {
911 target.binary_path.as_str()
912 };
913 let mut hasher = DefaultHasher::new();
914 effective_path.hash(&mut hasher);
915 let module_hash = format!("{:08x}", (hasher.finish() & 0xFFFF_FFFF) as u32);
916 let address_hex = if let Some(addr) = target.function_address {
917 format!("{addr:x}")
918 } else {
919 "unknown".to_string()
920 };
921
922 format!("gs_{module_hash}_{address_hex}_trace{trace_id}.{extension}")
923 }
924
925 fn generate_ebpf_bytecode(
927 module: &inkwell::module::Module,
928 function_name: &str,
929 target: &ResolvedTarget,
930 assigned_trace_id: u32,
931 compile_options: &crate::CompileOptions,
932 binary_path_hint: Option<&str>,
933 ) -> Result<Vec<u8>, CompileError> {
934 use inkwell::targets::{FileType, Target, TargetTriple};
935 use inkwell::OptimizationLevel;
936
937 let llvm_ir = module.print_to_string().to_string();
939 let llvm_ir = llvm_ir.trim_end().to_string();
940 info!(
941 "Successfully generated LLVM IR for {}, length: {}",
942 function_name,
943 llvm_ir.len()
944 );
945
946 if compile_options.save_llvm_ir {
948 let filename = Self::generate_filename_with_hint(
949 target,
950 assigned_trace_id,
951 "ll",
952 binary_path_hint,
953 );
954 if let Err(e) = std::fs::write(&filename, &llvm_ir) {
955 warn!("Failed to save LLVM IR to {}: {}", filename, e);
956 } else {
957 info!("Saved LLVM IR to: {}", filename);
958 }
959 }
960
961 let triple = TargetTriple::create("bpf-pc-linux");
963 info!("Created target triple: bpf-pc-linux for {}", function_name);
964
965 let llvm_target = Target::from_triple(&triple).map_err(|e| {
967 error!("Failed to get target for {}: {}", function_name, e);
968 CompileError::LLVM(format!("Failed to get target for {function_name}: {e}"))
969 })?;
970 info!("Successfully got LLVM target for {}", function_name);
971
972 let target_machine = llvm_target
974 .create_target_machine(
975 &triple,
976 "generic", "+alu32", OptimizationLevel::Default,
979 inkwell::targets::RelocMode::PIC,
980 inkwell::targets::CodeModel::Small,
981 )
982 .ok_or_else(|| {
983 error!("Failed to create target machine for {}", function_name);
984 CompileError::LLVM(format!(
985 "Failed to create target machine for {function_name}"
986 ))
987 })?;
988 info!("Successfully created target machine for {}", function_name);
989
990 info!("Validating LLVM module for {}...", function_name);
992 if let Err(llvm_errors) = module.verify() {
993 error!(
994 "LLVM module validation failed for {}: {}",
995 function_name, llvm_errors
996 );
997 return Err(CompileError::LLVM(format!(
998 "Module validation failed for {function_name}: {llvm_errors}"
999 )));
1000 }
1001 info!("Module validation passed for {}", function_name);
1002
1003 info!("Generating eBPF object file for {}...", function_name);
1005 info!("About to call LLVM write_to_memory_buffer...");
1006
1007 let object_code = {
1008 let module_string = module.print_to_string();
1010 debug!(
1011 "Module IR before compilation:\n{}",
1012 module_string.to_string()
1013 );
1014
1015 use std::io::Write;
1017 let _ = std::io::stderr().flush();
1018 let _ = std::io::stdout().flush();
1019
1020 info!("Calling target_machine.write_to_memory_buffer...");
1021 match target_machine.write_to_memory_buffer(module, FileType::Object) {
1022 Ok(code) => {
1023 info!("Successfully generated object code for {}", function_name);
1024 code
1025 }
1026 Err(e) => {
1027 error!("LLVM compilation failed for {}: {}", function_name, e);
1028 error!("This might be due to unsupported eBPF instructions or invalid LLVM IR");
1029
1030 return Err(CompileError::LLVM(format!(
1031 "eBPF compilation failed for {function_name}: {e}. This often indicates unsupported instructions or invalid IR."
1032 )));
1033 }
1034 }
1035 };
1036
1037 info!(
1038 "Successfully generated object code for {}! Size: {}",
1039 function_name,
1040 object_code.get_size()
1041 );
1042
1043 let bytecode = object_code.as_slice().to_vec();
1045
1046 if compile_options.save_ebpf {
1048 let filename =
1049 Self::generate_filename_with_hint(target, assigned_trace_id, "o", binary_path_hint);
1050 if let Err(e) = std::fs::write(&filename, &bytecode) {
1051 warn!("Failed to save eBPF object to {}: {}", filename, e);
1052 } else {
1053 info!("Saved eBPF object to: {}", filename);
1054 }
1055 }
1056
1057 Ok(bytecode)
1059 }
1060
1061 fn save_ast_to_file(
1063 &mut self,
1064 program: &crate::script::ast::Program,
1065 filename: &str,
1066 ) -> Result<(), CompileError> {
1067 let mut ast_content = String::new();
1068 ast_content.push_str("=== AST Tree ===\n");
1069 ast_content.push_str("Program:\n");
1070 for (i, stmt) in program.statements.iter().enumerate() {
1071 ast_content.push_str(&format!(" Statement {i}: {stmt:?}\n"));
1072 }
1073 ast_content.push_str("=== End AST Tree ===\n");
1074
1075 std::fs::write(filename, ast_content).map_err(|e| {
1076 CompileError::Other(format!("Failed to save AST file '{filename}': {e}"))
1077 })?;
1078
1079 Ok(())
1080 }
1081}