Skip to main content

ghostscope_compiler/script/
compiler.rs

1use crate::script::ast::{Program, Statement, TracePattern};
2use crate::CompileError;
3// BinaryAnalyzer is now internal to ghostscope-binary, use DwarfAnalyzer instead
4use 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/// Resolved target information from DWARF queries
12#[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/// Complete uprobe configuration ready for attachment
22#[derive(Debug, Clone)]
23pub struct UProbeConfig {
24    /// The trace pattern this uprobe corresponds to
25    pub trace_pattern: TracePattern,
26
27    /// Target binary path
28    pub binary_path: String,
29
30    /// Function name (for FunctionName patterns)
31    pub function_name: Option<String>,
32
33    /// Resolved function address in the binary
34    pub function_address: Option<u64>,
35
36    /// Calculated uprobe offset (for aya uprobe attachment)
37    pub uprobe_offset: Option<u64>,
38
39    /// Process ID to attach to (None means attach to all instances)
40    pub target_pid: Option<u32>,
41
42    /// eBPF bytecode for this uprobe
43    pub ebpf_bytecode: Vec<u8>,
44
45    /// eBPF function name for this uprobe (e.g., "ghostscope_main_0", "ghostscope_printf_1")
46    pub ebpf_function_name: String,
47
48    /// Trace ID assigned by compiler (starts from starting_trace_id and increments)
49    pub assigned_trace_id: u32,
50
51    /// Trace context containing all strings, types, and variable names used in this uprobe
52    pub trace_context: ghostscope_protocol::TraceContext,
53
54    /// Global 1-based index of this address within the resolved target list (if applicable)
55    pub resolved_address_index: Option<usize>,
56}
57
58/// Compilation result containing all uprobe configurations
59#[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>, // New field for failed compilation info
65    pub next_available_trace_id: u32,      // Next trace_id that can be used by trace_manager
66}
67
68/// Information about a target that failed to compile
69#[derive(Debug, Clone)]
70pub struct FailedTarget {
71    pub target_name: String,
72    pub pc_address: u64,
73    pub error_message: String,
74}
75
76/// Unified AST compiler that performs DWARF queries and code generation in single pass
77pub struct AstCompiler<'a> {
78    process_analyzer: Option<&'a ghostscope_dwarf::DwarfAnalyzer>,
79    uprobe_configs: Vec<UProbeConfig>,
80    failed_targets: Vec<FailedTarget>, // Track failed compilation attempts
81    binary_path_hint: Option<String>,
82    current_trace_id: u32, // Current trace_id counter (increments for each uprobe)
83    compile_options: crate::CompileOptions, // Compilation options (save + eBPF map config)
84}
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    /// Main entry point: compile AST with integrated DWARF queries and code generation
104    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        // AST will be saved immediately when we know the target details in generate_ebpf_for_target
115
116        // Single-pass traversal: process each statement immediately
117        // Continue processing even if some trace points fail
118        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                            // Save first error for detailed error message
143                            if first_error.is_none() {
144                                first_error = Some(error_msg.clone());
145                            }
146
147                            // Check if failed_targets was already populated by process_trace_point
148                            // (e.g., when all addresses failed for a function)
149                            // If not, add a general failed target entry
150                            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                    // TODO: Non-trace statements are ignored in current implementation
182                }
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            // All trace points failed - return error with first failure reason
198            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        // Generate target info summary
205        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    /// Build a detailed error message for SourceLine resolution failures
222    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        // Collect all full paths and those sharing the basename
237        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        // Probe a few candidate paths to see if any has addresses on this line
285        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    /// Process a trace point: resolve target + generate eBPF in one step
319    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                // Obtain addresses first in a separate scope to avoid holding a mutable borrow of self
332                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                // Validate optional single-index selection (1-based)
352                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                // Optional single-index filter (1-based); otherwise process all
362                let mut successful_addresses = 0;
363                let mut failed_addresses = 0;
364                // Iterate with indices (1-based) so we can propagate the global address index
365                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; // safe due to validation above
368                        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                    // Convert DWARF PC (vaddr) to ELF file offset for uprobe
375                    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                        // Keep function_address as DWARF PC for compile-time DWARF queries
382                        function_address: Some(module_address.address),
383                        binary_path: module_address.module_path.to_string_lossy().to_string(),
384                        // Attach with absolute file offset if conversion succeeded
385                        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                            // Record this failed target
411                            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                            // Continue processing other addresses
418                        }
419                    }
420                }
421
422                // Log summary for this trace point
423                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                    // Don't return error here - let the caller decide based on overall results
439                }
440                Ok(())
441            }
442            TracePattern::Address(addr) => {
443                // Resolve default module path
444                // Prefer main executable; if unavailable (e.g., -t <lib>.so),
445                // fall back to the single loaded shared library (target module).
446                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                // Convert DWARF PC (vaddr) to ELF file offset for uprobe
471                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                // Resolve module path by exact or suffix match across loaded modules
509                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                    // exact match
519                    if let Some(found) = modules.iter().find(|p| p.as_str() == module) {
520                        found.clone()
521                    } else {
522                        // suffix match
523                        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                // Convert DWARF PC (vaddr) to ELF file offset for uprobe
552                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                // Resolve all addresses for the function name and generate per-PC programs
593                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                    // Strict behavior: fail this trace point immediately instead of skipping silently
601                    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                // Validate optional single-index selection (1-based)
615                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                // We may need analyzer again to compute precise uprobe offsets
625                // Optional single-index filter (1-based); otherwise process all addresses
626                let mut successful_addresses = 0;
627                let mut failed_addresses = 0;
628
629                // Iterate with indices (1-based) so we can propagate the global address index
630                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; // safe due to validation above
633                        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                    // Convert DWARF function address (vaddr) to ELF file offset for uprobe attach
640                    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                            // Record this failed target
674                            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                            // Continue processing other addresses
681                        }
682                    }
683                }
684
685                // Log summary for this trace point
686                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                    // All addresses failed to process — record failures already captured above
700                    // Defer final error shaping to the caller based on aggregated results
701                    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    /// Generate eBPF bytecode for resolved target
715    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        // Allocate trace_id for this uprobe
725        let assigned_trace_id = self.current_trace_id;
726        self.current_trace_id += 1;
727
728        // Generate unified eBPF function name using the assigned trace_id
729        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        // Save AST immediately when we know the target details (before generating LLVM IR)
740        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                // Create a Program from statements to save
744                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        // Use new codegen implementation with full AST compilation
756        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        // Set compile-time context for DWARF queries
766        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        // Use full AST compilation
776        let (_main_function, trace_context) = codegen_new
777            .compile_program(
778                &crate::script::ast::Program { statements: vec![] }, // Empty program - statements passed separately
779                &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        // Generate eBPF bytecode from LLVM module
797        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        // Use the TraceContext returned from compile_program (no need to get it again)
807
808        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    /// Generate summary of all targets for reporting
824    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    /// Generate unified eBPF function name for all contexts
837    ///
838    /// This is the SINGLE source of truth for eBPF function naming.
839    /// All other naming logic should use this method to ensure consistency.
840    /// Calculate 8-digit hex hash for module path with logging
841    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    /// Generate unified function name with format: ghostscope_{module_hash}_{address_hex}_{trace_id}
854    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    /// Get save options (helper method)
873    fn get_compile_options(&self) -> Option<&crate::CompileOptions> {
874        Some(&self.compile_options)
875    }
876
877    /// Pick a binary path, falling back to compiler hint when the resolved target is empty
878    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    /// Generate filename for output files
891    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    /// Generate eBPF bytecode from LLVM module
926    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        // Get LLVM IR string for logging and saving
938        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        // Save LLVM IR file if requested
947        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        // Get target triple
962        let triple = TargetTriple::create("bpf-pc-linux");
963        info!("Created target triple: bpf-pc-linux for {}", function_name);
964
965        // Get BPF target
966        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        // Create target machine
973        let target_machine = llvm_target
974            .create_target_machine(
975                &triple,
976                "generic", // CPU
977                "+alu32",  // Enable BPF ALU32 instructions
978                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        // Validate module before generating object code
991        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        // Generate eBPF object file
1004        info!("Generating eBPF object file for {}...", function_name);
1005        info!("About to call LLVM write_to_memory_buffer...");
1006
1007        let object_code = {
1008            // Print module for debugging before compilation
1009            let module_string = module.print_to_string();
1010            debug!(
1011                "Module IR before compilation:\n{}",
1012                module_string.to_string()
1013            );
1014
1015            // Add a flush to ensure logs are written before potential crash
1016            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        // Convert to Vec<u8>
1044        let bytecode = object_code.as_slice().to_vec();
1045
1046        // Save eBPF object file and AST if requested
1047        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        // AST has already been saved earlier in generate_ebpf_for_target
1058        Ok(bytecode)
1059    }
1060
1061    /// Save AST to file
1062    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}