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