Skip to main content

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_pid_aliases_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        self.create_map_definition(
288            module,
289            di_builder,
290            compile_unit,
291            name,
292            BpfMapType::Hash,
293            max_entries,
294            SizedType::integer(32),
295            SizedType::integer(32),
296        )
297    }
298
299    pub fn create_event_loss_counter_map(
300        &mut self,
301        module: &Module<'ctx>,
302        di_builder: &DebugInfoBuilder<'ctx>,
303        compile_unit: &inkwell::debug_info::DICompileUnit<'ctx>,
304        name: &str,
305        max_entries: u64,
306    ) -> Result<()> {
307        info!(
308            "Creating event loss counter map: {} with {} max entries",
309            name, max_entries
310        );
311        // For event loss counter, key and value are both integers
312        // Key size is sizeof(event_loss_cnt_key_) * 8
313        // Value size is sizeof(event_loss_cnt_val_) * 8
314        self.create_map_definition(
315            module,
316            di_builder,
317            compile_unit,
318            name,
319            BpfMapType::Array,
320            max_entries,
321            SizedType::integer(64), // Assuming event_loss_cnt_key_ is u64
322            SizedType::integer(64), // Assuming event_loss_cnt_val_ is u64
323        )
324    }
325
326    /// Create BTF type information for a BPF map matching clang's output format
327    /// This allows aya to understand the map's key and value types
328    #[allow(clippy::too_many_arguments)]
329    fn create_map_btf_info(
330        &self,
331        di_builder: &DebugInfoBuilder<'ctx>,
332        compile_unit: &inkwell::debug_info::DICompileUnit<'ctx>,
333        map_name: &str,
334        map_type: BpfMapType,
335        max_entries: u64,
336        key_type: SizedType,
337        value_type: SizedType,
338    ) -> Result<inkwell::debug_info::DIType<'ctx>> {
339        info!(
340            "Creating BTF info for map: {} (type: {:?})",
341            map_name, map_type
342        );
343
344        // Create basic types needed for the map structure
345        let i32_type = di_builder.create_basic_type("int", 32, 0x05, 0)?; // DW_ATE_signed = 0x05
346
347        let file = compile_unit.get_file();
348        let scope = compile_unit.as_debug_info_scope();
349
350        // Create the map structure based on map type, matching clang/aya BTF format:
351        // fields are pointers to arrays whose nr_elems encode values.
352        let map_type_id = map_type.to_aya_map_type();
353
354        // Helper: pointer to array with given element count (encoded in range)
355        let mk_ptr_to_array = |name: &str, nr_elems: i64| {
356            let range = 0..nr_elems;
357            let arr = di_builder.create_array_type(
358                i32_type.as_type(),
359                64,
360                32,
361                std::slice::from_ref(&range),
362            );
363            di_builder.create_pointer_type(name, arr.as_type(), 64, 64, AddressSpace::default())
364        };
365
366        let type_ptr = mk_ptr_to_array("type", map_type_id as i64);
367
368        let members = match map_type {
369            BpfMapType::Ringbuf => {
370                info!("Creating ringbuf BTF with 2 fields (type, max_entries) as pointer-to-array");
371                let max_entries_ptr = mk_ptr_to_array("max_entries", max_entries as i64);
372                vec![
373                    di_builder.create_member_type(
374                        scope,
375                        "type",
376                        file,
377                        0,
378                        64,
379                        64,
380                        0,
381                        0,
382                        type_ptr.as_type(),
383                    ),
384                    di_builder.create_member_type(
385                        scope,
386                        "max_entries",
387                        file,
388                        0,
389                        64,
390                        64,
391                        64,
392                        0,
393                        max_entries_ptr.as_type(),
394                    ),
395                ]
396            }
397            _ => {
398                info!("Creating array/hash BTF with pointer-to-array fields for aya compatibility");
399                let key_size_val = if key_type.is_none {
400                    0
401                } else {
402                    (key_type.size / 8) as i64
403                };
404                let value_size_val = if value_type.is_none {
405                    0
406                } else {
407                    (value_type.size / 8) as i64
408                };
409                let key_size_ptr = mk_ptr_to_array("key_size", key_size_val);
410                let value_size_ptr = mk_ptr_to_array("value_size", value_size_val);
411                let max_entries_ptr = mk_ptr_to_array("max_entries", max_entries as i64);
412                let mut v = vec![
413                    di_builder.create_member_type(
414                        scope,
415                        "type",
416                        file,
417                        0,
418                        64,
419                        64,
420                        0,
421                        0,
422                        type_ptr.as_type(),
423                    ),
424                    di_builder.create_member_type(
425                        scope,
426                        "key_size",
427                        file,
428                        0,
429                        64,
430                        64,
431                        64,
432                        0,
433                        key_size_ptr.as_type(),
434                    ),
435                    di_builder.create_member_type(
436                        scope,
437                        "value_size",
438                        file,
439                        0,
440                        64,
441                        64,
442                        128,
443                        0,
444                        value_size_ptr.as_type(),
445                    ),
446                    di_builder.create_member_type(
447                        scope,
448                        "max_entries",
449                        file,
450                        0,
451                        64,
452                        64,
453                        192,
454                        0,
455                        max_entries_ptr.as_type(),
456                    ),
457                ];
458                // For pinned maps, include optional 'pinning' to signal Aya ByName pinning.
459                if matches!(map_name, "proc_module_offsets" | "pid_aliases") {
460                    // ByName is typically encoded as 1 in aya_obj::maps::PinningType
461                    let pinning_ptr = mk_ptr_to_array("pinning", 1);
462                    v.push(di_builder.create_member_type(
463                        scope,
464                        "pinning",
465                        file,
466                        0,
467                        64,
468                        64,
469                        256,
470                        0,
471                        pinning_ptr.as_type(),
472                    ));
473                }
474                v
475            }
476        };
477
478        // Convert members to DIType vector
479        let member_types: Vec<_> = members.iter().map(|m| m.as_type()).collect();
480
481        // Total structure size: pointers (64-bit) per field
482        let (total_size_bits, field_count) = match map_type {
483            BpfMapType::Ringbuf => (128, 2), // 2 * 64 bits
484            _ => {
485                if matches!(map_name, "proc_module_offsets" | "pid_aliases") {
486                    (320, 5) // include 'pinning'
487                } else {
488                    (256, 4)
489                }
490            }
491        };
492
493        // Create the map structure type (anonymous like reference)
494        let map_struct_type = di_builder.create_struct_type(
495            scope,           // scope
496            "",              // name - empty for anonymous struct
497            file,            // file
498            0,               // line_number
499            total_size_bits, // size_in_bits
500            32,              // align_in_bits
501            0,               // flags
502            None,            // derived_from
503            &member_types,   // elements
504            0,               // runtime_lang
505            None,            // vtable_holder
506            "",              // unique_id
507        );
508
509        info!(
510            "Created BTF struct type for map: {} with {} fields, {} total bits",
511            map_name, field_count, total_size_bits
512        );
513        Ok(map_struct_type.as_type())
514    }
515
516    /// Get ringbuf map by name
517    pub fn get_ringbuf_map(&self, module: &Module<'ctx>, name: &str) -> Result<PointerValue<'ctx>> {
518        self.get_map(module, name)
519    }
520
521    /// Get a perf event array map by name
522    pub fn get_perf_map(&self, module: &Module<'ctx>, name: &str) -> Result<PointerValue<'ctx>> {
523        self.get_map(module, name)
524    }
525
526    /// Create a Per-CPU Array map (key=u32, value arbitrary size)
527    pub fn create_percpu_array_map(
528        &mut self,
529        module: &Module<'ctx>,
530        di_builder: &DebugInfoBuilder<'ctx>,
531        compile_unit: &inkwell::debug_info::DICompileUnit<'ctx>,
532        name: &str,
533        max_entries: u64,
534        value_size_bytes: u64,
535    ) -> Result<()> {
536        self.create_map_definition(
537            module,
538            di_builder,
539            compile_unit,
540            name,
541            BpfMapType::PerCpuArray,
542            max_entries,
543            SizedType::integer(32),
544            SizedType::integer(value_size_bytes * 8),
545        )
546    }
547}