use crate::script::ast::{Program, Statement, TracePattern};
use crate::CompileError;
use ghostscope_dwarf::ModuleDefaultPolicy;
use inkwell::context::Context;
use std::borrow::Cow;
use std::collections::hash_map::DefaultHasher;
use std::fmt::Write as _;
use std::hash::{Hash, Hasher};
use tracing::{debug, error, info, warn};
#[derive(Debug, Clone)]
pub struct ResolvedTarget {
pub function_name: Option<String>,
pub function_address: Option<u64>,
pub binary_path: String,
pub uprobe_offset: Option<u64>,
pub pattern: TracePattern,
}
#[derive(Debug, Clone)]
pub struct UProbeConfig {
pub trace_pattern: TracePattern,
pub binary_path: String,
pub function_name: Option<String>,
pub function_address: Option<u64>,
pub uprobe_offset: Option<u64>,
pub target_pid: Option<u32>,
pub ebpf_bytecode: Vec<u8>,
pub ebpf_function_name: String,
pub assigned_trace_id: u32,
pub trace_context: ghostscope_protocol::TraceContext,
pub backtrace_unwind_rows: Vec<ghostscope_protocol::BacktraceUnwindRow>,
pub backtrace_module_row_ranges: Vec<(u64, ghostscope_protocol::BacktraceModuleRowRange)>,
pub backtrace_tail_call_program: Option<crate::ebpf::context::BacktraceTailCallProgram>,
pub resolved_address_index: Option<usize>,
}
#[derive(Debug)]
pub struct CompilationResult {
pub uprobe_configs: Vec<UProbeConfig>,
pub trace_count: usize,
pub target_info: String,
pub failed_targets: Vec<FailedTarget>, pub next_available_trace_id: u32, }
#[derive(Debug, Clone)]
pub struct FailedTarget {
pub target_name: String,
pub pc_address: u64,
pub error_message: String,
}
pub struct AstCompiler<'a> {
process_analyzer: Option<&'a ghostscope_dwarf::DwarfAnalyzer>,
uprobe_configs: Vec<UProbeConfig>,
failed_targets: Vec<FailedTarget>, binary_path_hint: Option<String>,
current_trace_id: u32, compile_options: crate::CompileOptions, }
impl<'a> AstCompiler<'a> {
pub fn new(
process_analyzer: Option<&'a ghostscope_dwarf::DwarfAnalyzer>,
binary_path_hint: Option<String>,
starting_trace_id: u32,
compile_options: crate::CompileOptions,
) -> Self {
Self {
process_analyzer,
uprobe_configs: Vec::new(),
failed_targets: Vec::new(),
binary_path_hint,
current_trace_id: starting_trace_id,
compile_options,
}
}
pub fn compile_program(
&mut self,
program: &Program,
pid: Option<u32>,
) -> Result<CompilationResult, CompileError> {
info!(
"Starting unified AST compilation with {} statements",
program.statements.len()
);
if program.statements.is_empty() {
return Err(CompileError::Other(
"script must contain at least one top-level trace statement".to_string(),
));
}
let mut successful_trace_points = 0;
let mut failed_trace_points = 0;
let mut first_error: Option<String> = None;
for (index, stmt) in program.statements.iter().enumerate() {
match stmt {
Statement::TracePoint { pattern, body } => {
debug!("Processing trace point {}: {:?}", index, pattern);
match self.process_trace_point(pattern, body, pid, index) {
Ok(_) => {
successful_trace_points += 1;
info!(
"✓ Successfully processed trace point {}: {:?}",
index, pattern
);
}
Err(e) => {
failed_trace_points += 1;
let error_msg = e.user_message().into_owned();
error!(
"❌ Failed to process trace point {}: {:?} - Error: {}",
index, pattern, error_msg
);
if first_error.is_none() {
first_error = Some(error_msg.clone());
}
let has_failed_for_this_pattern =
self.failed_targets.iter().any(|ft| match pattern {
TracePattern::FunctionName(name) => ft.target_name == *name,
TracePattern::SourceLine {
file_path,
line_number,
} => ft.target_name == format!("{file_path}:{line_number}"),
TracePattern::Address(addr) => {
ft.target_name == format!("0x{addr:x}")
&& ft.pc_address == *addr
}
TracePattern::AddressInModule { module, address } => {
ft.target_name == format!("{module}:0x{address:x}")
&& ft.pc_address == *address
}
_ => false,
});
if !has_failed_for_this_pattern {
let target_name = match pattern {
TracePattern::FunctionName(name) => name.clone(),
TracePattern::SourceLine {
file_path,
line_number,
} => format!("{file_path}:{line_number}"),
TracePattern::Address(addr) => format!("0x{addr:x}"),
TracePattern::AddressInModule { module, address } => {
format!("{module}:0x{address:x}")
}
_ => format!("trace_point_{index}"),
};
let pc_address = match pattern {
TracePattern::Address(addr) => *addr,
TracePattern::AddressInModule { address, .. } => *address,
_ => 0,
};
self.failed_targets.push(FailedTarget {
target_name,
pc_address,
error_message: error_msg,
});
}
}
}
}
_ => {
let message = Self::top_level_statement_error(stmt);
error!("{message}");
return Err(CompileError::Other(message));
}
}
}
if successful_trace_points > 0 && failed_trace_points == 0 {
info!(
"All {} trace points processed successfully",
successful_trace_points
);
} else if successful_trace_points > 0 && failed_trace_points > 0 {
warn!(
"Partial success: {} trace points successful, {} failed",
successful_trace_points, failed_trace_points
);
} else if failed_trace_points > 0 {
error!("All {} trace points failed to process", failed_trace_points);
return Err(CompileError::Other(
self.format_all_trace_points_failed_error(first_error),
));
}
let target_info = self.generate_target_info_summary();
info!(
"Compilation completed: {} uprobe configs generated",
self.uprobe_configs.len()
);
let trace_count = self.uprobe_configs.len();
Ok(CompilationResult {
uprobe_configs: std::mem::take(&mut self.uprobe_configs),
failed_targets: std::mem::take(&mut self.failed_targets),
trace_count,
target_info,
next_available_trace_id: self.current_trace_id,
})
}
fn format_all_trace_points_failed_error(&self, first_error: Option<String>) -> String {
let mut message = first_error.unwrap_or_else(|| "All trace points failed".to_string());
if self.failed_targets.is_empty() {
return message;
}
message.push_str("\n\nFailed targets:\n");
for failed in &self.failed_targets {
let _ = writeln!(
message,
" - {} at 0x{:x}: {}",
failed.target_name, failed.pc_address, failed.error_message
);
}
message.push_str("\nTip: fix the reported compile-time errors above.");
message
}
fn configured_target_path(&self) -> Option<&str> {
self.compile_options
.target_binary_path
.as_deref()
.map(str::trim)
.filter(|path| !path.is_empty())
}
fn top_level_statement_error(statement: &Statement) -> String {
let kind = match statement {
Statement::Print(_) => "print",
Statement::Backtrace(_) => "backtrace",
Statement::Expr(_) => "expression",
Statement::VarDeclaration { .. } | Statement::AliasDeclaration { .. } => "let",
Statement::If { .. } => "if",
Statement::Block(_) => "block",
Statement::TracePoint { .. } => "trace",
};
format!(
"top-level {kind} statement is not allowed in a script file; put executable statements inside a trace block, for example: trace <target> {{ ... }}"
)
}
fn process_trace_point(
&mut self,
pattern: &TracePattern,
statements: &[Statement],
pid: Option<u32>,
index: usize,
) -> Result<(), CompileError> {
match pattern {
TracePattern::SourceLine {
file_path,
line_number,
} => {
let analyzer = self.process_analyzer.ok_or_else(|| {
CompileError::Other(
"No process analyzer available to resolve source line".to_string(),
)
})?;
let target_path = self.configured_target_path();
let source_line = analyzer
.resolve_source_line_addresses_best_effort(
analyzer.source_line_candidates(file_path),
*line_number,
target_path,
)
.map_err(|e| CompileError::Other(e.to_string()))?;
let module_addresses = source_line.addresses;
if source_line.raw_address_count > 0 && module_addresses.is_empty() {
let target = target_path.unwrap_or("<unknown>");
return Err(CompileError::Other(format!(
"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."
)));
}
if module_addresses.is_empty() {
let detailed = analyzer.describe_source_line_failure(file_path, *line_number);
return Err(CompileError::Other(detailed));
}
debug!(
"Resolved {}:{} to {} address(es) for trace point {}",
file_path,
line_number,
module_addresses.len(),
index
);
if let Some(idx) = self.compile_options.selected_index {
if idx == 0 || idx > module_addresses.len() {
return Err(CompileError::Other(format!(
"Selected index {idx} is out of range for {file_path}:{line_number} (valid 1..={}). Use 'info' to view indices.",
module_addresses.len()
)));
}
}
let mut successful_addresses = 0;
let mut failed_addresses = 0;
let iterator: Box<dyn Iterator<Item = (usize, &ghostscope_dwarf::ModuleAddress)>> =
if let Some(idx) = self.compile_options.selected_index {
let i = idx - 1; Box::new(std::iter::once((idx, &module_addresses[i])))
} else {
Box::new(module_addresses.iter().enumerate().map(|(i, m)| (i + 1, m)))
};
for (global_idx, module_address) in iterator {
let file_off = self.process_analyzer.as_ref().and_then(|an| {
an.vaddr_to_file_offset(&module_address.module_path, module_address.address)
});
let target_info = ResolvedTarget {
function_name: Some(format!("{file_path}:{line_number}")),
function_address: Some(module_address.address),
binary_path: module_address.module_path.to_string_lossy().to_string(),
uprobe_offset: file_off,
pattern: pattern.clone(),
};
match self.generate_ebpf_for_target(
&target_info,
statements,
pid,
Some(global_idx),
) {
Ok(uprobe_config) => {
self.uprobe_configs.push(uprobe_config);
successful_addresses += 1;
info!(
"✓ Successfully generated eBPF for {}:{} at 0x{:x}",
file_path, line_number, module_address.address
);
}
Err(e) => {
failed_addresses += 1;
error!(
"❌ Failed to generate eBPF for {}:{} at 0x{:x}: {}",
file_path, line_number, module_address.address, e
);
self.failed_targets.push(FailedTarget {
target_name: format!("{file_path}:{line_number}"),
pc_address: module_address.address,
error_message: e.user_message().into_owned(),
});
}
}
}
if successful_addresses > 0 && failed_addresses == 0 {
info!(
"All {} addresses for {}:{} processed successfully",
successful_addresses, file_path, line_number
);
} else if successful_addresses > 0 && failed_addresses > 0 {
warn!(
"Partial success for {}:{}: {} successful, {} failed addresses",
file_path, line_number, successful_addresses, failed_addresses
);
} else {
error!(
"All {} addresses for {}:{} failed to process",
failed_addresses, file_path, line_number
);
}
Ok(())
}
TracePattern::Address(addr) => {
let analyzer = self.process_analyzer.ok_or_else(|| {
CompileError::Other(
"No process analyzer available to resolve address".to_string(),
)
})?;
let module_path = analyzer
.resolve_address_module(
None,
self.configured_target_path(),
ModuleDefaultPolicy::MainExecutableOrSingleSharedLibrary,
)
.map_err(|e| CompileError::Other(e.to_string()))?;
let file_off = analyzer.vaddr_to_file_offset(&module_path, *addr);
let module_path = module_path.to_string_lossy().to_string();
if file_off.is_none() {
return Err(CompileError::Other(format!(
"Address 0x{addr:x} is not within a loadable segment of '{module_path}' (cannot compute file offset)"
)));
}
let target_info = ResolvedTarget {
function_name: None,
function_address: Some(*addr),
binary_path: module_path,
uprobe_offset: file_off,
pattern: pattern.clone(),
};
match self.generate_ebpf_for_target(&target_info, statements, pid, None) {
Ok(uprobe_config) => {
self.uprobe_configs.push(uprobe_config);
info!("✓ Successfully generated eBPF for address 0x{:x}", addr);
Ok(())
}
Err(e) => {
let error_msg = e.user_message().into_owned();
error!(
"❌ Failed to generate eBPF for address 0x{:x}: {}",
addr, error_msg
);
self.failed_targets.push(FailedTarget {
target_name: format!("0x{addr:x}"),
pc_address: *addr,
error_message: error_msg,
});
Err(e)
}
}
}
TracePattern::AddressInModule { module, address } => {
let analyzer = self.process_analyzer.ok_or_else(|| {
CompileError::Other(
"No process analyzer available to resolve module".to_string(),
)
})?;
let module_path = analyzer
.resolve_address_module(
Some(module),
self.configured_target_path(),
ModuleDefaultPolicy::MainExecutableOrSingleSharedLibrary,
)
.map_err(|e| CompileError::Other(e.to_string()))?;
let file_off = analyzer.vaddr_to_file_offset(&module_path, *address);
let module_path = module_path.to_string_lossy().to_string();
if file_off.is_none() {
return Err(CompileError::Other(format!(
"Address 0x{address:x} is not within a loadable segment of '{module_path}' (cannot compute file offset)"
)));
}
let target_info = ResolvedTarget {
function_name: None,
function_address: Some(*address),
binary_path: module_path,
uprobe_offset: file_off,
pattern: pattern.clone(),
};
match self.generate_ebpf_for_target(&target_info, statements, pid, None) {
Ok(uprobe_config) => {
self.uprobe_configs.push(uprobe_config);
info!(
"✓ Successfully generated eBPF for module-qualified address {}:0x{:x}",
module, address
);
Ok(())
}
Err(e) => {
let error_msg = e.user_message().into_owned();
error!(
"❌ Failed to generate eBPF for module-qualified address {}:0x{:x}: {}",
module, address, error_msg
);
self.failed_targets.push(FailedTarget {
target_name: format!("{module}:0x{address:x}"),
pc_address: *address,
error_message: error_msg,
});
Err(e)
}
}
}
TracePattern::FunctionName(func_name) => {
let module_addresses = if let Some(analyzer) = self.process_analyzer {
analyzer.lookup_function_addresses(func_name)
} else {
Vec::new()
};
if module_addresses.is_empty() {
return Err(CompileError::Other(format!(
"No addresses resolved for function '{func_name}' - function not found in debug symbols"
)));
}
let original_address_count = module_addresses.len();
let target_path = self.configured_target_path();
let module_addresses = self
.process_analyzer
.ok_or_else(|| {
CompileError::Other(
"No process analyzer available to resolve -t target".to_string(),
)
})?
.filter_module_addresses_to_target(module_addresses, target_path)
.map_err(|e| CompileError::Other(e.to_string()))?;
if original_address_count > 0 && module_addresses.is_empty() {
let target = target_path.unwrap_or("<unknown>");
return Err(CompileError::Other(format!(
"No addresses resolved for function '{func_name}' in -t target '{target}'. When -t and -p are combined, -t takes precedence for trace target resolution."
)));
}
let total_addresses: usize = module_addresses.len();
debug!(
"Resolved function '{}' to {} address(es) across {} modules",
func_name,
total_addresses,
module_addresses.len()
);
if let Some(idx) = self.compile_options.selected_index {
if idx == 0 || idx > module_addresses.len() {
return Err(CompileError::Other(format!(
"Selected index {idx} is out of range for function '{func_name}' (valid 1..={}). Use 'info function {func_name}' to view indices.",
module_addresses.len()
)));
}
}
let mut successful_addresses = 0;
let mut failed_addresses = 0;
let iterator: Box<dyn Iterator<Item = (usize, &ghostscope_dwarf::ModuleAddress)>> =
if let Some(idx) = self.compile_options.selected_index {
let i = idx - 1; Box::new(std::iter::once((idx, &module_addresses[i])))
} else {
Box::new(module_addresses.iter().enumerate().map(|(i, m)| (i + 1, m)))
};
for (global_idx, module_address) in iterator {
let file_off = self.process_analyzer.as_ref().and_then(|an| {
an.vaddr_to_file_offset(&module_address.module_path, module_address.address)
});
let target_info = ResolvedTarget {
function_name: Some(func_name.clone()),
function_address: Some(module_address.address),
binary_path: module_address.module_path.to_string_lossy().to_string(),
uprobe_offset: file_off,
pattern: pattern.clone(),
};
match self.generate_ebpf_for_target(
&target_info,
statements,
pid,
Some(global_idx),
) {
Ok(uprobe_config) => {
self.uprobe_configs.push(uprobe_config);
successful_addresses += 1;
info!(
"✓ Successfully generated eBPF for function '{}' at 0x{:x}",
func_name, module_address.address
);
}
Err(e) => {
failed_addresses += 1;
error!(
"❌ Failed to generate eBPF for function '{}' at 0x{:x}: {}",
func_name, module_address.address, e
);
self.failed_targets.push(FailedTarget {
target_name: func_name.clone(),
pc_address: module_address.address,
error_message: e.user_message().into_owned(),
});
}
}
}
if successful_addresses > 0 && failed_addresses == 0 {
info!(
"All {} addresses for function '{}' processed successfully",
successful_addresses, func_name
);
Ok(())
} else if successful_addresses > 0 && failed_addresses > 0 {
warn!(
"Partial success for function '{}': {} successful, {} failed addresses",
func_name, successful_addresses, failed_addresses
);
Ok(())
} else {
error!(
"All {} addresses for function '{}' failed to process",
failed_addresses, func_name
);
Ok(())
}
}
_ => {
unimplemented!();
}
}
}
fn generate_ebpf_for_target(
&mut self,
target: &ResolvedTarget,
statements: &[Statement],
pid: Option<u32>,
resolved_address_index: Option<usize>,
) -> Result<UProbeConfig, CompileError> {
let context = Context::create();
let assigned_trace_id = self.current_trace_id;
self.current_trace_id += 1;
let ebpf_function_name = self.generate_unified_function_name(target, assigned_trace_id);
let compile_options = self.compile_options.clone();
let binary_path_hint = self.binary_path_hint.clone();
info!(
"Generating eBPF code for '{}' (function: {})",
target.function_name.as_deref().unwrap_or("unknown"),
ebpf_function_name
);
if let Some(compile_options) = self.get_compile_options() {
if compile_options.save_ast {
let ast_filename = self.generate_filename(target, assigned_trace_id, "txt");
let program = Program {
statements: statements.to_vec(),
};
if let Err(e) = self.save_ast_to_file(&program, &ast_filename) {
warn!("Failed to save AST to {}: {}", ast_filename, e);
} else {
info!("Saved AST to: {}", ast_filename);
}
}
}
let mut codegen = crate::ebpf::context::EbpfContext::new_with_process_analyzer(
&context,
&ebpf_function_name,
self.process_analyzer,
Some(assigned_trace_id),
&self.compile_options,
)
.map_err(|e| CompileError::LLVM(format!("Failed to create new codegen: {e}")))?;
if let Some(function_address) = target.function_address {
codegen.set_compile_time_context(function_address, target.binary_path.clone());
}
info!(
"Compiling full AST program with {} statements",
statements.len()
);
let (_main_function, trace_context) = codegen
.compile_program(
&crate::script::ast::Program { statements: vec![] }, &ebpf_function_name,
statements,
pid,
target.function_address,
Some(&target.binary_path),
)
.map_err(CompileError::CodeGen)?;
info!(
"Generated TraceContext for '{}' with {} strings and {} variables",
ebpf_function_name,
trace_context.string_count(),
trace_context.variable_name_count()
);
let module = codegen.get_module();
let ebpf_bytecode = Self::generate_ebpf_bytecode(
module,
&ebpf_function_name,
target,
assigned_trace_id,
&compile_options,
binary_path_hint.as_deref(),
)?;
Ok(UProbeConfig {
trace_pattern: target.pattern.clone(),
binary_path: target.binary_path.clone(),
function_name: target.function_name.clone(),
function_address: target.function_address,
uprobe_offset: target.uprobe_offset,
target_pid: pid,
ebpf_bytecode,
ebpf_function_name,
assigned_trace_id,
trace_context,
backtrace_unwind_rows: codegen.backtrace_unwind_rows.clone(),
backtrace_module_row_ranges: codegen
.backtrace_module_row_ranges
.iter()
.map(|entry| (entry.cookie, entry.range))
.collect(),
backtrace_tail_call_program: codegen.backtrace_tail_call_program(),
resolved_address_index,
})
}
fn generate_target_info_summary(&self) -> String {
if self.uprobe_configs.is_empty() {
return "no_targets".to_string();
}
let first_target = &self.uprobe_configs[0];
match &first_target.function_name {
Some(name) => name.clone(),
None => format!("addr_0x{:x}", first_target.function_address.unwrap_or(0)),
}
}
fn calculate_module_hash(&self, module_path: &str) -> String {
let effective_path = self.effective_binary_path(module_path);
let mut hasher = DefaultHasher::new();
effective_path.hash(&mut hasher);
let hash = hasher.finish();
let truncated = (hash & 0xFFFF_FFFF) as u32;
let hash_hex = format!("{truncated:08x}");
info!("Module hash calculated: {} -> {}", effective_path, hash_hex);
hash_hex
}
fn generate_unified_function_name(&self, target: &ResolvedTarget, trace_id: u32) -> String {
let module_hash = self.calculate_module_hash(&target.binary_path);
let effective_path = self.effective_binary_path(&target.binary_path);
let address_hex = if let Some(addr) = target.function_address {
format!("{addr:x}")
} else {
"unknown".to_string()
};
let function_name = format!("ghostscope_{module_hash}_{address_hex}_trace{trace_id}");
info!(
"Generated eBPF function name: {} (module: {}, address: 0x{}, trace_id: {})",
function_name, effective_path, address_hex, trace_id
);
function_name
}
fn get_compile_options(&self) -> Option<&crate::CompileOptions> {
Some(&self.compile_options)
}
fn effective_binary_path<'b>(&'b self, target_path: &'b str) -> Cow<'b, str> {
if target_path.is_empty() {
if let Some(hint) = &self.binary_path_hint {
Cow::Owned(hint.clone())
} else {
Cow::Borrowed("unknown")
}
} else {
Cow::Borrowed(target_path)
}
}
fn generate_filename(&self, target: &ResolvedTarget, trace_id: u32, extension: &str) -> String {
let module_hash = self.calculate_module_hash(&target.binary_path);
let address_hex = if let Some(addr) = target.function_address {
format!("{addr:x}")
} else {
"unknown".to_string()
};
format!("gs_{module_hash}_{address_hex}_trace{trace_id}.{extension}")
}
fn generate_filename_with_hint(
target: &ResolvedTarget,
trace_id: u32,
extension: &str,
binary_path_hint: Option<&str>,
) -> String {
let effective_path = if target.binary_path.is_empty() {
binary_path_hint.unwrap_or("unknown")
} else {
target.binary_path.as_str()
};
let mut hasher = DefaultHasher::new();
effective_path.hash(&mut hasher);
let module_hash = format!("{:08x}", (hasher.finish() & 0xFFFF_FFFF) as u32);
let address_hex = if let Some(addr) = target.function_address {
format!("{addr:x}")
} else {
"unknown".to_string()
};
format!("gs_{module_hash}_{address_hex}_trace{trace_id}.{extension}")
}
fn generate_ebpf_bytecode(
module: &inkwell::module::Module,
function_name: &str,
target: &ResolvedTarget,
assigned_trace_id: u32,
compile_options: &crate::CompileOptions,
binary_path_hint: Option<&str>,
) -> Result<Vec<u8>, CompileError> {
use inkwell::targets::{FileType, Target, TargetTriple};
use inkwell::OptimizationLevel;
if compile_options.save_llvm_ir {
let filename = Self::generate_filename_with_hint(
target,
assigned_trace_id,
"ll",
binary_path_hint,
);
if let Err(e) = module.print_to_file(&filename) {
warn!("Failed to save LLVM IR to {}: {}", filename, e);
} else {
info!("Saved LLVM IR to: {}", filename);
}
}
info!("Successfully generated LLVM module for {}", function_name);
let triple = TargetTriple::create("bpf-pc-linux");
info!("Created target triple: bpf-pc-linux for {}", function_name);
let llvm_target = Target::from_triple(&triple).map_err(|e| {
error!("Failed to get target for {}: {}", function_name, e);
CompileError::LLVM(format!("Failed to get target for {function_name}: {e}"))
})?;
info!("Successfully got LLVM target for {}", function_name);
let target_machine = llvm_target
.create_target_machine(
&triple,
"generic", "+alu32", OptimizationLevel::Default,
inkwell::targets::RelocMode::PIC,
inkwell::targets::CodeModel::Small,
)
.ok_or_else(|| {
error!("Failed to create target machine for {}", function_name);
CompileError::LLVM(format!(
"Failed to create target machine for {function_name}"
))
})?;
info!("Successfully created target machine for {}", function_name);
info!("Validating LLVM module for {}...", function_name);
if let Err(llvm_errors) = module.verify() {
error!(
"LLVM module validation failed for {}: {}",
function_name, llvm_errors
);
return Err(CompileError::LLVM(format!(
"Module validation failed for {function_name}: {llvm_errors}"
)));
}
info!("Module validation passed for {}", function_name);
info!("Generating eBPF object file for {}...", function_name);
info!("About to call LLVM write_to_memory_buffer...");
let object_code = {
use std::io::Write;
let _ = std::io::stderr().flush();
let _ = std::io::stdout().flush();
info!("Calling target_machine.write_to_memory_buffer...");
match target_machine.write_to_memory_buffer(module, FileType::Object) {
Ok(code) => {
info!("Successfully generated object code for {}", function_name);
code
}
Err(e) => {
error!("LLVM compilation failed for {}: {}", function_name, e);
error!("This might be due to unsupported eBPF instructions or invalid LLVM IR");
return Err(CompileError::LLVM(format!(
"eBPF compilation failed for {function_name}: {e}. This often indicates unsupported instructions or invalid IR."
)));
}
}
};
info!(
"Successfully generated object code for {}! Size: {}",
function_name,
object_code.get_size()
);
let bytecode = object_code.as_slice().to_vec();
if compile_options.save_ebpf {
let filename =
Self::generate_filename_with_hint(target, assigned_trace_id, "o", binary_path_hint);
if let Err(e) = std::fs::write(&filename, &bytecode) {
warn!("Failed to save eBPF object to {}: {}", filename, e);
} else {
info!("Saved eBPF object to: {}", filename);
}
}
Ok(bytecode)
}
fn save_ast_to_file(
&mut self,
program: &crate::script::ast::Program,
filename: &str,
) -> Result<(), CompileError> {
let mut ast_content = String::new();
ast_content.push_str("=== AST Tree ===\n");
ast_content.push_str("Program:\n");
for (i, stmt) in program.statements.iter().enumerate() {
ast_content.push_str(&format!(" Statement {i}: {stmt:?}\n"));
}
ast_content.push_str("=== End AST Tree ===\n");
std::fs::write(filename, ast_content).map_err(|e| {
CompileError::Other(format!("Failed to save AST file '{filename}': {e}"))
})?;
Ok(())
}
}