ghostscope_compiler/ebpf/
maps.rs

1use aya_ebpf_bindings::bindings::bpf_map_type;
2use inkwell::context::Context;
3use inkwell::debug_info::{AsDIScope, DebugInfoBuilder};
4use inkwell::module::Linkage;
5use inkwell::module::Module;
6use inkwell::values::PointerValue;
7use inkwell::AddressSpace;
8// AddressSpace was used for pointer-typed BTF encodings; no longer needed after int-field BTF
9use std::collections::HashMap;
10use tracing::{error, info};
11
12#[derive(Debug, Clone, Copy)]
13pub enum BpfMapType {
14    Ringbuf,
15    Array,
16    PerCpuArray,
17    Hash,
18    PerfEventArray,
19}
20
21impl BpfMapType {
22    fn to_aya_map_type(self) -> u32 {
23        match self {
24            BpfMapType::Ringbuf => bpf_map_type::BPF_MAP_TYPE_RINGBUF,
25            BpfMapType::PerCpuArray => bpf_map_type::BPF_MAP_TYPE_PERCPU_ARRAY,
26            BpfMapType::Array => bpf_map_type::BPF_MAP_TYPE_ARRAY,
27            BpfMapType::Hash => bpf_map_type::BPF_MAP_TYPE_HASH,
28            BpfMapType::PerfEventArray => bpf_map_type::BPF_MAP_TYPE_PERF_EVENT_ARRAY,
29        }
30    }
31}
32
33#[derive(Debug, Clone)]
34pub struct SizedType {
35    pub size: u64, // size in bits
36    pub is_none: bool,
37}
38
39impl SizedType {
40    pub fn none() -> Self {
41        SizedType {
42            size: 0,
43            is_none: true,
44        }
45    }
46
47    pub fn integer(size: u64) -> Self {
48        SizedType {
49            size,
50            is_none: false,
51        }
52    }
53}
54
55pub struct MapManager<'ctx> {
56    context: &'ctx Context,
57    map_types: HashMap<String, BpfMapType>,
58}
59
60#[derive(Debug, thiserror::Error)]
61pub enum MapError {
62    #[error("Map not found: {0}")]
63    MapNotFound(String),
64
65    #[error("Builder error: {0}")]
66    Builder(String),
67
68    #[error("Debug info error: {0}")]
69    DebugInfo(String),
70}
71
72impl From<&str> for MapError {
73    fn from(err: &str) -> Self {
74        MapError::DebugInfo(err.to_string())
75    }
76}
77
78pub type Result<T> = std::result::Result<T, MapError>;
79
80impl<'ctx> MapManager<'ctx> {
81    pub fn new(context: &'ctx Context) -> Self {
82        MapManager {
83            context,
84            map_types: HashMap::new(),
85        }
86    }
87
88    #[allow(clippy::too_many_arguments)]
89    pub fn create_map_definition(
90        &mut self,
91        module: &Module<'ctx>,
92        di_builder: &DebugInfoBuilder<'ctx>,
93        compile_unit: &inkwell::debug_info::DICompileUnit<'ctx>,
94        name: &str,
95        map_type: BpfMapType,
96        max_entries: u64,
97        key_type: SizedType,
98        value_type: SizedType,
99    ) -> Result<()> {
100        info!(
101            "Creating map definition: {} (type: {:?}, max_entries: {}, key_type: {:?}, value_type: {:?})",
102            name, map_type, max_entries, key_type, value_type
103        );
104
105        // Store map type information
106        self.map_types.insert(name.to_string(), map_type);
107
108        // Use the original map name directly (like "ringbuf")
109        let var_name = name.to_string();
110        info!("Map variable name: {}", var_name);
111
112        // Create BPF map definition structure that aya expects
113        // Match clang-style: fields are pointers (64-bit); actual values are
114        // encoded via BTF pointer-to-array lengths, not via initializers.
115        let ptr_ty = self.context.ptr_type(inkwell::AddressSpace::default());
116
117        // Values are conveyed by BTF; initializers can be null pointers.
118
119        // Create struct with appropriate fields based on map type
120        // Ringbuf only needs type and max_entries, others need all 4 fields
121        let (elements, initializer_values): (Vec<_>, Vec<_>) = match map_type {
122            BpfMapType::Ringbuf => (
123                vec![ptr_ty.into(), ptr_ty.into()],
124                vec![ptr_ty.const_null().into(), ptr_ty.const_null().into()],
125            ),
126            _ => (
127                vec![ptr_ty.into(), ptr_ty.into(), ptr_ty.into(), ptr_ty.into()],
128                vec![
129                    ptr_ty.const_null().into(),
130                    ptr_ty.const_null().into(),
131                    ptr_ty.const_null().into(),
132                    ptr_ty.const_null().into(),
133                ],
134            ),
135        };
136        let struct_type = self.context.struct_type(&elements, false);
137        let initializer = struct_type.const_named_struct(&initializer_values);
138
139        // Create BTF type information for the map
140        // This is critical for aya to understand the map structure
141        let map_di_type = self.create_map_btf_info(
142            di_builder,
143            compile_unit,
144            &var_name,
145            map_type,
146            max_entries,
147            key_type,
148            value_type,
149        )?;
150
151        // Create the global variable
152        let map_var = module.add_global(struct_type, None, &var_name);
153
154        // Set the proper initializer
155        map_var.set_initializer(&initializer);
156
157        // Set section to .maps
158        map_var.set_section(Some(".maps"));
159
160        // Set linkage to External so aya can find and relocate the map symbols
161        // Private linkage hides symbols from relocations, which breaks aya
162        map_var.set_linkage(Linkage::External);
163
164        // Associate the global variable with its debug type
165        // This ensures the BTF type information is properly linked
166        let file = compile_unit.get_file();
167        let di_global_variable = di_builder.create_global_variable_expression(
168            compile_unit.as_debug_info_scope(), // scope
169            &var_name,                          // name
170            &var_name,                          // linkage_name
171            file,                               // file
172            1,                                  // line_no
173            map_di_type,                        // ty
174            false,                              // is_local_to_unit
175            None,                               // expr
176            None,                               // decl
177            map_var.get_alignment(),            // align_in_bits
178        );
179
180        // Attach the debug info to the global variable using proper metadata API
181        // The kind_id for "dbg" in LLVM is typically 0
182        map_var.set_metadata(di_global_variable.as_metadata_value(self.context), 0);
183
184        let field_count = match map_type {
185            BpfMapType::Ringbuf => 2,
186            _ => 4,
187        };
188        info!(
189            "Successfully created map: {} with {} fields",
190            var_name, field_count
191        );
192        Ok(())
193    }
194
195    pub fn get_map(&self, module: &Module<'ctx>, name: &str) -> Result<PointerValue<'ctx>> {
196        let var_name = name.to_string(); // Use direct name like "ringbuf"
197        info!("Looking up map: {}", var_name);
198
199        if let Some(map_var) = module.get_global(&var_name) {
200            info!("Found map: {}", var_name);
201            Ok(map_var.as_pointer_value())
202        } else {
203            error!("Map not found: {}", var_name);
204            Err(MapError::MapNotFound(var_name))
205        }
206    }
207
208    pub fn create_ringbuf_map(
209        &mut self,
210        module: &Module<'ctx>,
211        di_builder: &DebugInfoBuilder<'ctx>,
212        compile_unit: &inkwell::debug_info::DICompileUnit<'ctx>,
213        name: &str,
214        ringbuf_size: u64,
215    ) -> Result<()> {
216        // For ringbuf, max_entries is the buffer size in bytes (must be power of 2)
217        // The parameter name is kept as perf_rb_pages for backward compatibility,
218        // but we now interpret it directly as the ringbuf size in bytes
219        let max_entries = ringbuf_size;
220        info!("Creating ringbuf map: {} with {} bytes", name, max_entries);
221        self.create_map_definition(
222            module,
223            di_builder,
224            compile_unit,
225            name,
226            BpfMapType::Ringbuf,
227            max_entries,
228            // Ringbuf map: key_size = 0, value_size = 0 for ringbuf
229            SizedType::none(),
230            SizedType::none(),
231        )
232    }
233
234    /// Create PerfEventArray map for event output (fallback when RingBuf not supported)
235    pub fn create_perf_event_array_map(
236        &mut self,
237        module: &Module<'ctx>,
238        di_builder: &DebugInfoBuilder<'ctx>,
239        compile_unit: &inkwell::debug_info::DICompileUnit<'ctx>,
240        name: &str,
241    ) -> Result<()> {
242        info!("Creating PerfEventArray map: {}", name);
243        self.create_map_definition(
244            module,
245            di_builder,
246            compile_unit,
247            name,
248            BpfMapType::PerfEventArray,
249            0, // max_entries = 0 means auto-detect number of CPUs
250            // PerfEventArray: key = u32 (CPU index), value = u32 (FD)
251            SizedType::integer(32),
252            SizedType::integer(32),
253        )
254    }
255
256    /// Create the per-(pid,module) section offsets map used for ASLR address calculation
257    pub fn create_proc_module_offsets_map(
258        &mut self,
259        module: &Module<'ctx>,
260        di_builder: &DebugInfoBuilder<'ctx>,
261        compile_unit: &inkwell::debug_info::DICompileUnit<'ctx>,
262        name: &str,
263        max_entries: u64,
264    ) -> Result<()> {
265        // Key: {pid:u32, pad:u32, cookie:u64} => 16 bytes => 128 bits
266        // Value: {text, rodata, data, bss: u64} => 32 bytes => 256 bits
267        self.create_map_definition(
268            module,
269            di_builder,
270            compile_unit,
271            name,
272            BpfMapType::Hash,
273            max_entries,
274            SizedType::integer(128),
275            SizedType::integer(256),
276        )
277    }
278
279    pub fn create_event_loss_counter_map(
280        &mut self,
281        module: &Module<'ctx>,
282        di_builder: &DebugInfoBuilder<'ctx>,
283        compile_unit: &inkwell::debug_info::DICompileUnit<'ctx>,
284        name: &str,
285        max_entries: u64,
286    ) -> Result<()> {
287        info!(
288            "Creating event loss counter map: {} with {} max entries",
289            name, max_entries
290        );
291        // For event loss counter, key and value are both integers
292        // Key size is sizeof(event_loss_cnt_key_) * 8
293        // Value size is sizeof(event_loss_cnt_val_) * 8
294        self.create_map_definition(
295            module,
296            di_builder,
297            compile_unit,
298            name,
299            BpfMapType::Array,
300            max_entries,
301            SizedType::integer(64), // Assuming event_loss_cnt_key_ is u64
302            SizedType::integer(64), // Assuming event_loss_cnt_val_ is u64
303        )
304    }
305
306    /// Create BTF type information for a BPF map matching clang's output format
307    /// This allows aya to understand the map's key and value types
308    #[allow(clippy::too_many_arguments)]
309    fn create_map_btf_info(
310        &self,
311        di_builder: &DebugInfoBuilder<'ctx>,
312        compile_unit: &inkwell::debug_info::DICompileUnit<'ctx>,
313        map_name: &str,
314        map_type: BpfMapType,
315        max_entries: u64,
316        key_type: SizedType,
317        value_type: SizedType,
318    ) -> Result<inkwell::debug_info::DIType<'ctx>> {
319        info!(
320            "Creating BTF info for map: {} (type: {:?})",
321            map_name, map_type
322        );
323
324        // Create basic types needed for the map structure
325        let i32_type = di_builder.create_basic_type("int", 32, 0x05, 0)?; // DW_ATE_signed = 0x05
326
327        let file = compile_unit.get_file();
328        let scope = compile_unit.as_debug_info_scope();
329
330        // Create the map structure based on map type, matching clang/aya BTF format:
331        // fields are pointers to arrays whose nr_elems encode values.
332        let map_type_id = map_type.to_aya_map_type();
333
334        // Helper: pointer to array with given element count (encoded in range)
335        let mk_ptr_to_array = |name: &str, nr_elems: i64| {
336            let range = 0..nr_elems;
337            let arr = di_builder.create_array_type(
338                i32_type.as_type(),
339                64,
340                32,
341                std::slice::from_ref(&range),
342            );
343            di_builder.create_pointer_type(name, arr.as_type(), 64, 64, AddressSpace::default())
344        };
345
346        let type_ptr = mk_ptr_to_array("type", map_type_id as i64);
347
348        let members = match map_type {
349            BpfMapType::Ringbuf => {
350                info!("Creating ringbuf BTF with 2 fields (type, max_entries) as pointer-to-array");
351                let max_entries_ptr = mk_ptr_to_array("max_entries", max_entries as i64);
352                vec![
353                    di_builder.create_member_type(
354                        scope,
355                        "type",
356                        file,
357                        0,
358                        64,
359                        64,
360                        0,
361                        0,
362                        type_ptr.as_type(),
363                    ),
364                    di_builder.create_member_type(
365                        scope,
366                        "max_entries",
367                        file,
368                        0,
369                        64,
370                        64,
371                        64,
372                        0,
373                        max_entries_ptr.as_type(),
374                    ),
375                ]
376            }
377            _ => {
378                info!("Creating array/hash BTF with pointer-to-array fields for aya compatibility");
379                let key_size_val = if key_type.is_none {
380                    0
381                } else {
382                    (key_type.size / 8) as i64
383                };
384                let value_size_val = if value_type.is_none {
385                    0
386                } else {
387                    (value_type.size / 8) as i64
388                };
389                let key_size_ptr = mk_ptr_to_array("key_size", key_size_val);
390                let value_size_ptr = mk_ptr_to_array("value_size", value_size_val);
391                let max_entries_ptr = mk_ptr_to_array("max_entries", max_entries as i64);
392                let mut v = vec![
393                    di_builder.create_member_type(
394                        scope,
395                        "type",
396                        file,
397                        0,
398                        64,
399                        64,
400                        0,
401                        0,
402                        type_ptr.as_type(),
403                    ),
404                    di_builder.create_member_type(
405                        scope,
406                        "key_size",
407                        file,
408                        0,
409                        64,
410                        64,
411                        64,
412                        0,
413                        key_size_ptr.as_type(),
414                    ),
415                    di_builder.create_member_type(
416                        scope,
417                        "value_size",
418                        file,
419                        0,
420                        64,
421                        64,
422                        128,
423                        0,
424                        value_size_ptr.as_type(),
425                    ),
426                    di_builder.create_member_type(
427                        scope,
428                        "max_entries",
429                        file,
430                        0,
431                        64,
432                        64,
433                        192,
434                        0,
435                        max_entries_ptr.as_type(),
436                    ),
437                ];
438                // For proc_module_offsets, include optional 'pinning' to signal Aya ByName pinning
439                if map_name == "proc_module_offsets" {
440                    // ByName is typically encoded as 1 in aya_obj::maps::PinningType
441                    let pinning_ptr = mk_ptr_to_array("pinning", 1);
442                    v.push(di_builder.create_member_type(
443                        scope,
444                        "pinning",
445                        file,
446                        0,
447                        64,
448                        64,
449                        256,
450                        0,
451                        pinning_ptr.as_type(),
452                    ));
453                }
454                v
455            }
456        };
457
458        // Convert members to DIType vector
459        let member_types: Vec<_> = members.iter().map(|m| m.as_type()).collect();
460
461        // Total structure size: pointers (64-bit) per field
462        let (total_size_bits, field_count) = match map_type {
463            BpfMapType::Ringbuf => (128, 2), // 2 * 64 bits
464            _ => {
465                if map_name == "proc_module_offsets" {
466                    (320, 5) // include 'pinning'
467                } else {
468                    (256, 4)
469                }
470            }
471        };
472
473        // Create the map structure type (anonymous like reference)
474        let map_struct_type = di_builder.create_struct_type(
475            scope,           // scope
476            "",              // name - empty for anonymous struct
477            file,            // file
478            0,               // line_number
479            total_size_bits, // size_in_bits
480            32,              // align_in_bits
481            0,               // flags
482            None,            // derived_from
483            &member_types,   // elements
484            0,               // runtime_lang
485            None,            // vtable_holder
486            "",              // unique_id
487        );
488
489        info!(
490            "Created BTF struct type for map: {} with {} fields, {} total bits",
491            map_name, field_count, total_size_bits
492        );
493        Ok(map_struct_type.as_type())
494    }
495
496    /// Get ringbuf map by name
497    pub fn get_ringbuf_map(&self, module: &Module<'ctx>, name: &str) -> Result<PointerValue<'ctx>> {
498        self.get_map(module, name)
499    }
500
501    /// Get a perf event array map by name
502    pub fn get_perf_map(&self, module: &Module<'ctx>, name: &str) -> Result<PointerValue<'ctx>> {
503        self.get_map(module, name)
504    }
505
506    /// Create a Per-CPU Array map (key=u32, value arbitrary size)
507    pub fn create_percpu_array_map(
508        &mut self,
509        module: &Module<'ctx>,
510        di_builder: &DebugInfoBuilder<'ctx>,
511        compile_unit: &inkwell::debug_info::DICompileUnit<'ctx>,
512        name: &str,
513        max_entries: u64,
514        value_size_bytes: u64,
515    ) -> Result<()> {
516        self.create_map_definition(
517            module,
518            di_builder,
519            compile_unit,
520            name,
521            BpfMapType::PerCpuArray,
522            max_entries,
523            SizedType::integer(32),
524            SizedType::integer(value_size_bytes * 8),
525        )
526    }
527}