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 mut 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 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    /// 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 &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        // 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) = &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                // 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) = &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                    // 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
731        info!(
732            "Generating eBPF code for '{}' (function: {})",
733            target.function_name.as_deref().unwrap_or("unknown"),
734            ebpf_function_name
735        );
736
737        // Save AST immediately when we know the target details (before generating LLVM IR)
738        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                // Create a Program from statements to save
742                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        // Use new codegen implementation with full AST compilation
754        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        // Set compile-time context for DWARF queries
764        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        // Use full AST compilation
774        let (_main_function, trace_context) = codegen_new
775            .compile_program(
776                &crate::script::ast::Program { statements: vec![] }, // Empty program - statements passed separately
777                &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        // Generate eBPF bytecode from LLVM module
795        let ebpf_bytecode =
796            self.generate_ebpf_bytecode(module, &ebpf_function_name, target, assigned_trace_id)?;
797
798        // Use the TraceContext returned from compile_program (no need to get it again)
799
800        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    /// Generate summary of all targets for reporting
816    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    /// Generate unified eBPF function name for all contexts
829    ///
830    /// This is the SINGLE source of truth for eBPF function naming.
831    /// All other naming logic should use this method to ensure consistency.
832    /// Calculate 8-digit hex hash for module path with logging
833    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    /// Generate unified function name with format: ghostscope_{module_hash}_{address_hex}_{trace_id}
846    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    /// Get save options (helper method)
865    fn get_compile_options(&self) -> Option<&crate::CompileOptions> {
866        Some(&self.compile_options)
867    }
868
869    /// Pick a binary path, falling back to compiler hint when the resolved target is empty
870    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    /// Generate filename for output files
883    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    /// Generate eBPF bytecode from LLVM module
895    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        // Get LLVM IR string for logging and saving
906        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        // Save LLVM IR file if requested
915        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        // Get target triple
927        let triple = TargetTriple::create("bpf-pc-linux");
928        info!("Created target triple: bpf-pc-linux for {}", function_name);
929
930        // Get BPF target
931        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        // Create target machine
938        let target_machine = llvm_target
939            .create_target_machine(
940                &triple,
941                "generic", // CPU
942                "+alu32",  // Enable BPF ALU32 instructions
943                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        // Validate module before generating object code
956        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        // Generate eBPF object file
969        info!("Generating eBPF object file for {}...", function_name);
970        info!("About to call LLVM write_to_memory_buffer...");
971
972        let object_code = {
973            // Print module for debugging before compilation
974            let module_string = module.print_to_string();
975            debug!(
976                "Module IR before compilation:\n{}",
977                module_string.to_string()
978            );
979
980            // Add a flush to ensure logs are written before potential crash
981            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        // Convert to Vec<u8>
1009        let bytecode = object_code.as_slice().to_vec();
1010
1011        // Save eBPF object file and AST if requested
1012        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            // AST has already been saved earlier in generate_ebpf_for_target
1023        }
1024
1025        Ok(bytecode)
1026    }
1027
1028    /// Save AST to file
1029    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}