Skip to main content

ghostscope_compiler/ebpf/codegen/
mod.rs

1//! Code generation for instructions
2//!
3//! This module handles the conversion from statements to compiled instructions
4//! and generates LLVM IR for individual instructions.
5
6use super::context::{CodeGenError, EbpfContext, Result, RuntimeAddress};
7use crate::script::{PrintStatement, Program, Statement};
8use aya_ebpf_bindings::bindings::bpf_func_id::BPF_FUNC_probe_read_user;
9use ghostscope_protocol::trace_event::{
10    BacktraceFrameData, BacktraceStatus, EndInstructionData, InstructionHeader,
11    PrintComplexFormatData, PrintComplexVariableData, PrintStringIndexData, PrintVariableIndexData,
12    VariableStatus, BACKTRACE_DATA_ERROR_CODE_OFFSET, BACKTRACE_DATA_FLAGS_OFFSET,
13    BACKTRACE_DATA_FRAME_COUNT_OFFSET, BACKTRACE_DATA_REQUESTED_DEPTH_OFFSET, BACKTRACE_DATA_SIZE,
14    BACKTRACE_DATA_STATUS_OFFSET, BACKTRACE_ERROR_FRAME_POINTER_READ,
15    BACKTRACE_ERROR_NEXT_CFA_NOT_ADVANCING, BACKTRACE_ERROR_NEXT_CFA_ZERO,
16    BACKTRACE_ERROR_NEXT_IP_BELOW_USER, BACKTRACE_ERROR_NEXT_IP_KERNEL_LIKE, BACKTRACE_ERROR_NONE,
17    BACKTRACE_ERROR_RETURN_ADDRESS_READ, BACKTRACE_FLAG_FULL, BACKTRACE_FLAG_INLINE,
18    BACKTRACE_FLAG_RAW, BACKTRACE_FRAME_DATA_SIZE, BACKTRACE_FRAME_FLAGS_OFFSET,
19    BACKTRACE_FRAME_MODULE_COOKIE_OFFSET, BACKTRACE_FRAME_PC_OFFSET, BACKTRACE_FRAME_RAW_IP_OFFSET,
20    EXPR_ERROR_DATA_ERROR_CODE_OFFSET, EXPR_ERROR_DATA_FAILING_ADDR_OFFSET,
21    EXPR_ERROR_DATA_FLAGS_OFFSET, EXPR_ERROR_DATA_SIZE, EXPR_ERROR_DATA_STRING_INDEX_OFFSET,
22    INSTRUCTION_HEADER_DATA_LENGTH_OFFSET, INSTRUCTION_HEADER_SIZE,
23    PRINT_COMPLEX_FORMAT_ARG_ACCESS_PATH_LEN_OFFSET, PRINT_COMPLEX_FORMAT_ARG_ACCESS_PATH_OFFSET,
24    PRINT_COMPLEX_FORMAT_ARG_FIXED_HEADER_LEN, PRINT_COMPLEX_FORMAT_ARG_STATUS_OFFSET,
25    PRINT_COMPLEX_FORMAT_ARG_TYPE_INDEX_OFFSET, PRINT_COMPLEX_FORMAT_DATA_ARG_COUNT_OFFSET,
26    VARIABLE_READ_ERROR_PAYLOAD_ADDR_OFFSET, VARIABLE_READ_ERROR_PAYLOAD_ERRNO_OFFSET,
27    VARIABLE_READ_ERROR_PAYLOAD_LEN,
28};
29use ghostscope_protocol::{InstructionType, TraceContext, TypeKind};
30use inkwell::values::{BasicValueEnum, IntValue, PointerValue};
31use inkwell::AddressSpace;
32use std::collections::HashMap;
33use tracing::{debug, info, warn};
34
35/// Parameters for generating a PrintComplexVariable with runtime read
36#[derive(Debug, Clone)]
37struct PrintVarRuntimeMeta {
38    var_name_index: u16,
39    type_index: u16,
40    access_path: String,
41    data_len_limit: usize,
42}
43
44/// Source for complex formatted argument data
45#[derive(Debug, Clone)]
46enum ComplexArgSource<'ctx> {
47    RuntimeRead {
48        address: ghostscope_dwarf::PlannedAddress,
49        dwarf_type: ghostscope_dwarf::TypeInfo,
50        module_for_offsets: Option<String>,
51    },
52    /// Memory dump from a pointer/byte address with a static length
53    MemDump {
54        address: RuntimeAddress<'ctx>,
55        len: usize,
56    },
57    /// Memory dump with dynamic runtime length; bytes read up to min(len_value, max_len)
58    MemDumpDynamic {
59        address: RuntimeAddress<'ctx>,
60        len_value: inkwell::values::IntValue<'ctx>,
61        max_len: usize,
62    },
63    ImmediateBytes {
64        bytes: Vec<u8>,
65    },
66    AddressValue {
67        address: ghostscope_dwarf::PlannedAddress,
68        module_for_offsets: Option<String>,
69    },
70    ComputedAddress {
71        address: RuntimeAddress<'ctx>,
72    },
73    // Newly added: a value computed in LLVM at runtime (e.g., expression result)
74    ComputedInt {
75        value: inkwell::values::IntValue<'ctx>,
76        byte_len: usize, // typically 8
77    },
78}
79
80/// Argument descriptor for PrintComplexFormat
81#[derive(Debug, Clone)]
82struct ComplexArg<'ctx> {
83    var_name_index: u16,
84    type_index: u16,
85    access_path: Vec<u8>,
86    data_len: usize,
87    source: ComplexArgSource<'ctx>,
88}
89
90fn print_complex_format_instruction_budget(
91    max_trace_event_size: usize,
92    bytes_reserved_so_far: usize,
93) -> usize {
94    let end_instruction_size =
95        std::mem::size_of::<InstructionHeader>() + std::mem::size_of::<EndInstructionData>();
96    let event_budget = max_trace_event_size
97        .saturating_sub(bytes_reserved_so_far)
98        .saturating_sub(end_instruction_size);
99    let instruction_budget_cap = std::mem::size_of::<InstructionHeader>() + u16::MAX as usize;
100    event_budget.min(instruction_budget_cap)
101}
102
103fn distribute_budget_fairly(caps: &[usize], budget: usize) -> Vec<usize> {
104    let mut allocations = vec![0; caps.len()];
105    let mut active: Vec<usize> = caps
106        .iter()
107        .enumerate()
108        .filter_map(|(idx, cap)| (*cap > 0).then_some(idx))
109        .collect();
110    let mut remaining = budget;
111
112    while remaining > 0 && !active.is_empty() {
113        let share = remaining / active.len();
114        if share == 0 {
115            for &idx in active.iter().take(remaining) {
116                allocations[idx] += 1;
117            }
118            break;
119        }
120
121        let mut consumed = 0usize;
122        let mut next_active = Vec::with_capacity(active.len());
123        for idx in active {
124            let cap_left = caps[idx].saturating_sub(allocations[idx]);
125            let take = share.min(cap_left);
126            allocations[idx] += take;
127            consumed += take;
128            if allocations[idx] < caps[idx] {
129                next_active.push(idx);
130            }
131        }
132
133        if consumed == 0 {
134            break;
135        }
136
137        remaining = remaining.saturating_sub(consumed);
138        active = next_active;
139    }
140
141    allocations
142}
143
144fn allocate_dynamic_payload_reservations(max_lens: &[usize], available: usize) -> Vec<usize> {
145    if max_lens.is_empty() || available == 0 {
146        return vec![0; max_lens.len()];
147    }
148
149    let base_caps = vec![VARIABLE_READ_ERROR_PAYLOAD_LEN; max_lens.len()];
150    let base_budget = available.min(VARIABLE_READ_ERROR_PAYLOAD_LEN.saturating_mul(max_lens.len()));
151    let mut reservations = distribute_budget_fairly(&base_caps, base_budget);
152    let remaining_budget = available.saturating_sub(reservations.iter().sum::<usize>());
153    if remaining_budget == 0 {
154        return reservations;
155    }
156
157    let extra_caps: Vec<usize> = max_lens
158        .iter()
159        .zip(reservations.iter())
160        .map(|(max_len, reserved)| {
161            max_len
162                .max(&VARIABLE_READ_ERROR_PAYLOAD_LEN)
163                .saturating_sub(*reserved)
164        })
165        .collect();
166    let extras = distribute_budget_fairly(&extra_caps, remaining_budget);
167    for (reservation, extra) in reservations.iter_mut().zip(extras) {
168        *reservation += extra;
169    }
170
171    reservations
172}
173
174mod args;
175mod backtrace;
176mod backtrace_plan;
177mod expr_error;
178mod format;
179mod instruction_common;
180mod print_complex_variable;
181mod print_string_index;
182mod print_variable_index;
183mod statements;
184mod types;
185
186#[cfg(test)]
187mod tests;