Skip to main content

ghostscope_compiler/ebpf/
helper_functions.rs

1//! eBPF helper function management
2//!
3//! This module handles eBPF helper function calls, register mapping, and pt_regs
4//! access for different target architectures.
5
6use super::context::{CodeGenError, EbpfContext, Result, RuntimeAddress};
7use aya_ebpf_bindings::bindings::bpf_func_id::{
8    BPF_FUNC_get_current_pid_tgid, BPF_FUNC_get_current_task, BPF_FUNC_ktime_get_ns,
9    BPF_FUNC_map_lookup_elem, BPF_FUNC_perf_event_output, BPF_FUNC_probe_read_kernel,
10    BPF_FUNC_probe_read_user, BPF_FUNC_probe_read_user_str, BPF_FUNC_ringbuf_output,
11};
12use ghostscope_dwarf::MemoryAccessSize;
13use ghostscope_platform::register_mapping;
14use ghostscope_protocol::trace_event::VariableStatus;
15use inkwell::types::{BasicType, BasicTypeEnum};
16use inkwell::values::{BasicMetadataValueEnum, BasicValueEnum, IntValue, PointerValue};
17use inkwell::AddressSpace;
18
19struct ProbeReadResult<'ctx> {
20    loaded_i64: IntValue<'ctx>,
21    combined_fail: IntValue<'ctx>,
22    not_found: IntValue<'ctx>,
23}
24
25pub(crate) struct ProcModuleOffsetsLookup<'ctx> {
26    pub(crate) found: IntValue<'ctx>,
27    pub(crate) text: IntValue<'ctx>,
28    pub(crate) rodata: IntValue<'ctx>,
29    pub(crate) data: IntValue<'ctx>,
30    pub(crate) bss: IntValue<'ctx>,
31}
32
33impl<'ctx, 'dw> EbpfContext<'ctx, 'dw> {
34    fn get_or_create_tls_scratch_buffer(&mut self) -> Result<PointerValue<'ctx>> {
35        if let Some(alloca) = self.tls_scratch_alloca {
36            return Ok(alloca);
37        }
38
39        let current_block = self.builder.get_insert_block().ok_or_else(|| {
40            CodeGenError::LLVMError("no current block for TLS scratch allocation".to_string())
41        })?;
42        let current_fn = current_block.get_parent().ok_or_else(|| {
43            CodeGenError::LLVMError("no current function for TLS scratch allocation".to_string())
44        })?;
45        let entry_block = current_fn.get_first_basic_block().ok_or_else(|| {
46            CodeGenError::LLVMError("no entry block for TLS scratch allocation".to_string())
47        })?;
48
49        if let Some(first_instruction) = entry_block.get_first_instruction() {
50            self.builder.position_before(&first_instruction);
51        } else {
52            self.builder.position_at_end(entry_block);
53        }
54        let scratch = self
55            .builder
56            .build_alloca(self.context.i64_type(), "tls_scratch")
57            .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
58        self.builder.position_at_end(current_block);
59
60        self.tls_scratch_alloca = Some(scratch);
61        Ok(scratch)
62    }
63
64    fn get_probe_read_scratch_buffer(
65        &mut self,
66        result_size: usize,
67        name_prefix: &str,
68    ) -> Result<PointerValue<'ctx>> {
69        if result_size <= 4 {
70            if let Some(key_alloca) = self.pm_key_alloca {
71                // Safe aliasing: the map key stack slot is only reused after the
72                // preceding map lookup has consumed it, and before any later
73                // lookup rewrites it on the current straight-line code path.
74                //
75                // Keep this reuse limited to <=4-byte reads. `pm_key_alloca` is a
76                // `[4 x i32]` stack slot, so it only guarantees i32 alignment; the
77                // U64/pointer read paths later issue an `i64` load and therefore
78                // need an 8-byte-aligned scratch buffer.
79                let i32_type = self.context.i32_type();
80                let key_arr_ty = i32_type.array_type(4);
81                let zero = i32_type.const_zero();
82                // SAFETY: pm_key_alloca is a [4 x i32] entry-block alloca and
83                // [0, 0] addresses its first byte-compatible element.
84                return unsafe {
85                    self.builder
86                        .build_gep(
87                            key_arr_ty,
88                            key_alloca,
89                            &[zero, zero],
90                            &format!("{name_prefix}_scratch_i8"),
91                        )
92                        .map_err(|e| CodeGenError::LLVMError(e.to_string()))
93                };
94            }
95        }
96
97        let buffer_name = format!("_temp_read_buffer_{result_size}");
98        let global_buffer = match self.module.get_global(&buffer_name) {
99            Some(existing) => existing.as_pointer_value(),
100            None => {
101                let array_type = self.context.i8_type().array_type(result_size as u32);
102                let global =
103                    self.module
104                        .add_global(array_type, Some(AddressSpace::default()), &buffer_name);
105                global.set_initializer(&array_type.const_zero());
106                global.as_pointer_value()
107            }
108        };
109        Ok(global_buffer)
110    }
111
112    pub fn lookup_proc_pid_alias(
113        &mut self,
114        runtime_pid: IntValue<'ctx>,
115        name_prefix: &str,
116    ) -> Result<IntValue<'ctx>> {
117        let Some(map_global) = self.module.get_global("pid_aliases") else {
118            return Ok(runtime_pid);
119        };
120
121        let i32_type = self.context.i32_type();
122        let ptr_type = self.context.ptr_type(AddressSpace::default());
123        let map_ptr = map_global.as_pointer_value();
124        let map_ptr_cast = self
125            .builder
126            .build_bit_cast(map_ptr, ptr_type, &format!("{name_prefix}_map_ptr"))
127            .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
128
129        let key_alloca = self.pm_key_alloca.ok_or_else(|| {
130            CodeGenError::LLVMError("pm_key not allocated in entry block".to_string())
131        })?;
132        let key_arr_ty = i32_type.array_type(4);
133        let zero = i32_type.const_zero();
134        // SAFETY: key_alloca is the [4 x i32] pm_key stack slot and [0, 0]
135        // addresses the pid key element.
136        let key_ptr = unsafe {
137            self.builder
138                .build_gep(
139                    key_arr_ty,
140                    key_alloca,
141                    &[zero, zero],
142                    &format!("{name_prefix}_alias_key_ptr"),
143                )
144                .map_err(|e| CodeGenError::LLVMError(e.to_string()))?
145        };
146        self.builder
147            .build_store(key_ptr, runtime_pid)
148            .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
149        let key_arg = self
150            .builder
151            .build_bit_cast(key_ptr, ptr_type, &format!("{name_prefix}_alias_key_arg"))
152            .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
153
154        let lookup_id = self
155            .context
156            .i64_type()
157            .const_int(BPF_FUNC_map_lookup_elem as u64, false);
158        let lookup_fn_type = ptr_type.fn_type(&[ptr_type.into(), ptr_type.into()], false);
159        let lookup_fn_ptr = self
160            .builder
161            .build_int_to_ptr(lookup_id, ptr_type, &format!("{name_prefix}_lookup_fn"))
162            .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
163        let lookup_args: Vec<BasicMetadataValueEnum> = vec![map_ptr_cast.into(), key_arg.into()];
164        let value_ptr_any = self
165            .builder
166            .build_indirect_call(
167                lookup_fn_type,
168                lookup_fn_ptr,
169                &lookup_args,
170                &format!("{name_prefix}_alias_lookup"),
171            )
172            .map_err(|e| CodeGenError::LLVMError(e.to_string()))?
173            .try_as_basic_value()
174            .left()
175            .ok_or_else(|| {
176                CodeGenError::LLVMError("pid_aliases lookup returned void".to_string())
177            })?;
178
179        let value_ptr = match value_ptr_any {
180            BasicValueEnum::PointerValue(p) => p,
181            _ => {
182                return Err(CodeGenError::LLVMError(
183                    "pid_aliases lookup did not return pointer".to_string(),
184                ));
185            }
186        };
187
188        let helper_fn = self.current_function("lookup proc pid alias")?;
189        let alias_hit_block = self
190            .context
191            .append_basic_block(helper_fn, &format!("{name_prefix}_alias_hit"));
192        let alias_miss_block = self
193            .context
194            .append_basic_block(helper_fn, &format!("{name_prefix}_alias_miss"));
195        let alias_cont_block = self
196            .context
197            .append_basic_block(helper_fn, &format!("{name_prefix}_alias_cont"));
198
199        let i64_type = self.context.i64_type();
200        let value_ptr_int = self
201            .builder
202            .build_ptr_to_int(
203                value_ptr,
204                i64_type,
205                &format!("{name_prefix}_alias_value_ptr_int"),
206            )
207            .map_err(|e| CodeGenError::Builder(e.to_string()))?;
208        let is_hit = self
209            .builder
210            .build_int_compare(
211                inkwell::IntPredicate::NE,
212                value_ptr_int,
213                i64_type.const_zero(),
214                &format!("{name_prefix}_alias_found"),
215            )
216            .map_err(|e| CodeGenError::Builder(e.to_string()))?;
217        self.builder
218            .build_conditional_branch(is_hit, alias_hit_block, alias_miss_block)
219            .map_err(|e| CodeGenError::Builder(e.to_string()))?;
220
221        self.builder.position_at_end(alias_hit_block);
222        let alias_value = self
223            .builder
224            .build_load(i32_type, value_ptr, &format!("{name_prefix}_alias_value"))
225            .map_err(|e| CodeGenError::LLVMError(e.to_string()))?
226            .into_int_value();
227        self.builder
228            .build_unconditional_branch(alias_cont_block)
229            .map_err(|e| CodeGenError::Builder(e.to_string()))?;
230        let alias_hit_end = self.current_insert_block("finish pid alias hit block")?;
231
232        self.builder.position_at_end(alias_miss_block);
233        self.builder
234            .build_unconditional_branch(alias_cont_block)
235            .map_err(|e| CodeGenError::Builder(e.to_string()))?;
236        let alias_miss_end = self.current_insert_block("finish pid alias miss block")?;
237
238        self.builder.position_at_end(alias_cont_block);
239        let alias_phi = self
240            .builder
241            .build_phi(i32_type, &format!("{name_prefix}_alias_pid"))
242            .map_err(|e| CodeGenError::Builder(e.to_string()))?;
243        alias_phi.add_incoming(&[
244            (&alias_value, alias_hit_end),
245            (&runtime_pid, alias_miss_end),
246        ]);
247
248        Ok(alias_phi.as_basic_value().into_int_value())
249    }
250
251    pub(crate) fn proc_module_pid_key(&mut self, name_prefix: &str) -> Result<IntValue<'ctx>> {
252        const BPF_FUNC_GET_NS_CURRENT_PID_TGID: u64 = 120;
253        const BPF_PIDNS_INFO_SIZE: u64 = 8; // struct { u32 pid; u32 tgid; }
254
255        let i32_type = self.context.i32_type();
256        let i64_type = self.context.i64_type();
257        let ptr_type = self.context.ptr_type(AddressSpace::default());
258        let key_arr_ty = i32_type.array_type(4);
259        let key_alloca = self.pm_key_alloca.ok_or_else(|| {
260            CodeGenError::LLVMError("pm_key not allocated in entry block".to_string())
261        })?;
262
263        let helper_id = i64_type.const_int(BPF_FUNC_get_current_pid_tgid as u64, false);
264        let helper_fn_type = i64_type.fn_type(&[], false);
265        let helper_fn_ptr = self
266            .builder
267            .build_int_to_ptr(helper_id, ptr_type, &format!("{name_prefix}_get_pid_fn"))
268            .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
269        let pid_tgid = self
270            .builder
271            .build_indirect_call(
272                helper_fn_type,
273                helper_fn_ptr,
274                &[],
275                &format!("{name_prefix}_pid_tgid"),
276            )
277            .map_err(|e| CodeGenError::LLVMError(e.to_string()))?
278            .try_as_basic_value()
279            .left()
280            .ok_or_else(|| {
281                CodeGenError::LLVMError("get_current_pid_tgid returned void".to_string())
282            })?;
283        let host_tgid = if let BasicValueEnum::IntValue(v) = pid_tgid {
284            let shifted = self
285                .builder
286                .build_right_shift(
287                    v,
288                    i64_type.const_int(32, false),
289                    false,
290                    &format!("{name_prefix}_pid_shift"),
291                )
292                .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
293            self.builder
294                .build_int_truncate(shifted, i32_type, &format!("{name_prefix}_pid32"))
295                .map_err(|e| CodeGenError::LLVMError(e.to_string()))?
296        } else {
297            return Err(CodeGenError::LLVMError(
298                "pid_tgid is not IntValue".to_string(),
299            ));
300        };
301
302        let ns_spec = self
303            .compile_options
304            .proc_offsets_pid_ns
305            .and_then(|pid_ns| pid_ns.helper_dev_inode());
306
307        let runtime_pid = if let Some((pid_ns_dev, pid_ns_inode)) = ns_spec {
308            self.builder
309                .build_store(key_alloca, key_arr_ty.const_zero())
310                .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
311            let pidns_info_ptr = self
312                .builder
313                .build_bit_cast(
314                    key_alloca,
315                    ptr_type,
316                    &format!("{name_prefix}_pidns_info_ptr"),
317                )
318                .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
319            let helper_args = [
320                i64_type.const_int(pid_ns_dev, false).into(),
321                i64_type.const_int(pid_ns_inode, false).into(),
322                pidns_info_ptr,
323                i64_type.const_int(BPF_PIDNS_INFO_SIZE, false).into(),
324            ];
325            let helper_ret = self.create_bpf_helper_call(
326                BPF_FUNC_GET_NS_CURRENT_PID_TGID,
327                &helper_args,
328                i64_type.into(),
329                &format!("{name_prefix}_ns_pid_tgid_ret"),
330            )?;
331            let helper_ret = match helper_ret {
332                BasicValueEnum::IntValue(v) => v,
333                _ => {
334                    return Err(CodeGenError::LLVMError(
335                        "bpf_get_ns_current_pid_tgid did not return integer".to_string(),
336                    ));
337                }
338            };
339            let helper_ok = self
340                .builder
341                .build_int_compare(
342                    inkwell::IntPredicate::EQ,
343                    helper_ret,
344                    i64_type.const_zero(),
345                    &format!("{name_prefix}_ns_helper_ok"),
346                )
347                .map_err(|e| CodeGenError::Builder(e.to_string()))?;
348            // SAFETY: key_alloca temporarily holds the two-field pid namespace
349            // helper result, so [0, 1] addresses the tgid field.
350            let ns_tgid_ptr = unsafe {
351                self.builder.build_gep(
352                    key_arr_ty,
353                    key_alloca,
354                    &[i32_type.const_zero(), i32_type.const_int(1, false)],
355                    &format!("{name_prefix}_ns_tgid_ptr"),
356                )
357            }
358            .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
359            let ns_tgid = self
360                .builder
361                .build_load(i32_type, ns_tgid_ptr, &format!("{name_prefix}_ns_tgid"))
362                .map_err(|e| CodeGenError::LLVMError(e.to_string()))?
363                .into_int_value();
364            // The proc module maps are populated from `/proc/<proc_pid>/maps`,
365            // so their keys must use the same PID namespace view GhostScope used
366            // for those `/proc` reads.
367            self.builder
368                .build_select(
369                    helper_ok,
370                    ns_tgid,
371                    host_tgid,
372                    &format!("{name_prefix}_pid_key"),
373                )
374                .map_err(|e| CodeGenError::Builder(e.to_string()))?
375                .into_int_value()
376        } else {
377            host_tgid
378        };
379
380        self.lookup_proc_pid_alias(runtime_pid, name_prefix)
381    }
382
383    /// Get or create a static i8 buffer global of a given size, returning its ArrayType and pointer
384    pub fn get_or_create_i8_buffer(
385        &mut self,
386        size: u32,
387        name_prefix: &str,
388    ) -> (
389        inkwell::types::ArrayType<'ctx>,
390        inkwell::values::PointerValue<'ctx>,
391    ) {
392        let array_ty = self.context.i8_type().array_type(size);
393        let name = format!("{name_prefix}_{size}");
394        let global_ptr = match self.module.get_global(&name) {
395            Some(g) => g.as_pointer_value(),
396            None => {
397                let g = self
398                    .module
399                    .add_global(array_ty, Some(AddressSpace::default()), &name);
400                g.set_initializer(&array_ty.const_zero());
401                g.as_pointer_value()
402            }
403        };
404        (array_ty, global_ptr)
405    }
406
407    /// Read a user C-string into a static buffer using bpf_probe_read_user_str.
408    /// Returns (buffer_ptr, len_including_nul).
409    pub(crate) fn read_user_cstr_into_buffer(
410        &mut self,
411        src_addr: RuntimeAddress<'ctx>,
412        size: u32,
413        name_prefix: &str,
414    ) -> Result<(
415        inkwell::values::PointerValue<'ctx>,
416        inkwell::values::IntValue<'ctx>,
417        inkwell::types::ArrayType<'ctx>,
418    )> {
419        let (arr_ty, buf_global) = self.get_or_create_i8_buffer(size, name_prefix);
420
421        let ptr_ty = self.context.ptr_type(AddressSpace::default());
422        // Cast addresses to void pointers
423        let dst_ptr = self
424            .builder
425            .build_bit_cast(buf_global, ptr_ty, "dst_ptr")
426            .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
427        let src_ptr = self
428            .builder
429            .build_int_to_ptr(src_addr.value, ptr_ty, "src_ptr")
430            .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
431        let src_ptr = self
432            .builder
433            .build_select::<BasicValueEnum<'ctx>, _>(
434                src_addr.offsets_found,
435                src_ptr.into(),
436                ptr_ty.const_null().into(),
437                "cstr_src_or_null",
438            )
439            .map_err(|e| CodeGenError::LLVMError(e.to_string()))?
440            .into_pointer_value();
441
442        // Helper signature: long fn(void *dst, u32 size, const void *src)
443        let i64_ty = self.context.i64_type();
444        let i32_ty = self.context.i32_type();
445        let effective_size = self
446            .builder
447            .build_select::<BasicValueEnum<'ctx>, _>(
448                src_addr.offsets_found,
449                i32_ty.const_int(size as u64, false).into(),
450                i32_ty.const_zero().into(),
451                "cstr_size_or_zero",
452            )
453            .map_err(|e| CodeGenError::LLVMError(e.to_string()))?
454            .into_int_value();
455        let args: [inkwell::values::BasicValueEnum; 3] = [
456            dst_ptr,
457            effective_size.into(),
458            inkwell::values::BasicValueEnum::PointerValue(src_ptr),
459        ];
460        let ret = self.create_bpf_helper_call(
461            BPF_FUNC_probe_read_user_str as u64,
462            &args,
463            i64_ty.into(),
464            "probe_read_user_str",
465        )?;
466        let len = if let inkwell::values::BasicValueEnum::IntValue(iv) = ret {
467            iv
468        } else {
469            return Err(CodeGenError::LLVMError(
470                "probe_read_user_str did not return integer".to_string(),
471            ));
472        };
473        let len = self
474            .builder
475            .build_select::<BasicValueEnum<'ctx>, _>(
476                src_addr.offsets_found,
477                len.into(),
478                i64_ty.const_zero().into(),
479                "cstr_len_or_zero",
480            )
481            .map_err(|e| CodeGenError::LLVMError(e.to_string()))?
482            .into_int_value();
483        Ok((buf_global, len, arr_ty))
484    }
485
486    /// Read raw user bytes into a static buffer using bpf_probe_read_user.
487    /// Returns (buffer_ptr, status==0?).
488    pub(crate) fn read_user_bytes_into_buffer(
489        &mut self,
490        src_addr: RuntimeAddress<'ctx>,
491        size: u32,
492        name_prefix: &str,
493    ) -> Result<(
494        inkwell::values::PointerValue<'ctx>,
495        inkwell::values::IntValue<'ctx>,
496        inkwell::types::ArrayType<'ctx>,
497    )> {
498        let (arr_ty, buf_global) = self.get_or_create_i8_buffer(size, name_prefix);
499        let ptr_ty = self.context.ptr_type(AddressSpace::default());
500        let i32_ty = self.context.i32_type();
501        let i64_ty = self.context.i64_type();
502        // Cast addresses to void pointers
503        let dst_ptr = self
504            .builder
505            .build_bit_cast(buf_global, ptr_ty, "dst_ptr")
506            .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
507        let src_ptr = self
508            .builder
509            .build_int_to_ptr(src_addr.value, ptr_ty, "src_ptr")
510            .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
511        let src_ptr = self
512            .builder
513            .build_select::<BasicValueEnum<'ctx>, _>(
514                src_addr.offsets_found,
515                src_ptr.into(),
516                ptr_ty.const_null().into(),
517                "bytes_src_or_null",
518            )
519            .map_err(|e| CodeGenError::LLVMError(e.to_string()))?
520            .into_pointer_value();
521        let effective_size = self
522            .builder
523            .build_select::<BasicValueEnum<'ctx>, _>(
524                src_addr.offsets_found,
525                i32_ty.const_int(size as u64, false).into(),
526                i32_ty.const_zero().into(),
527                "bytes_size_or_zero",
528            )
529            .map_err(|e| CodeGenError::LLVMError(e.to_string()))?
530            .into_int_value();
531
532        let args: [inkwell::values::BasicValueEnum; 3] = [
533            dst_ptr,
534            effective_size.into(),
535            inkwell::values::BasicValueEnum::PointerValue(src_ptr),
536        ];
537        // Helper returns long (0 on success, -errno on failure)
538        let ret = self.create_bpf_helper_call(
539            BPF_FUNC_probe_read_user as u64,
540            &args,
541            i64_ty.into(),
542            "probe_read_user",
543        )?;
544        let status = if let inkwell::values::BasicValueEnum::IntValue(iv) = ret {
545            iv
546        } else {
547            return Err(CodeGenError::LLVMError(
548                "probe_read_user did not return integer".to_string(),
549            ));
550        };
551        let status = self
552            .builder
553            .build_select::<BasicValueEnum<'ctx>, _>(
554                src_addr.offsets_found,
555                status.into(),
556                i64_ty.const_int(u64::MAX, true).into(),
557                "bytes_status_or_missing_offsets",
558            )
559            .map_err(|e| CodeGenError::LLVMError(e.to_string()))?
560            .into_int_value();
561        Ok((buf_global, status, arr_ty))
562    }
563    pub(crate) fn lookup_proc_module_offsets_value(
564        &mut self,
565        module_cookie: u64,
566        name_prefix: &str,
567    ) -> Result<ProcModuleOffsetsLookup<'ctx>> {
568        let i64_type = self.context.i64_type();
569        let ptr_type = self.context.ptr_type(AddressSpace::default());
570
571        // Resolve map global pointer
572        let map_global = self
573            .module
574            .get_global("proc_module_offsets")
575            .ok_or_else(|| {
576                CodeGenError::LLVMError("proc_module_offsets map not found".to_string())
577            })?;
578        let map_ptr = map_global.as_pointer_value();
579        let map_ptr_cast = self
580            .builder
581            .build_bit_cast(map_ptr, ptr_type, &format!("{name_prefix}_map_ptr"))
582            .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
583
584        // Use per-invocation key buffer [4 x u32] pre-allocated in entry block
585        // struct { u32 pid; u32 pad; u32 cookie_lo; u32 cookie_hi; }
586        let i32_type = self.context.i32_type();
587        let key_alloca = self.pm_key_alloca.ok_or_else(|| {
588            CodeGenError::LLVMError("pm_key not allocated in entry block".to_string())
589        })?;
590        let pid = self.proc_module_pid_key(name_prefix)?;
591
592        let store_key_u32 = |offset: usize,
593                             value: IntValue<'ctx>,
594                             name: &str,
595                             ctx: &mut EbpfContext<'ctx, 'dw>|
596         -> Result<()> {
597            let offset_i32 = ctx.context.i32_type().const_int(offset as u64, false);
598            // SAFETY: key_alloca is the ProcModuleKey stack slot. `offset`
599            // comes from ghostscope_protocol::bpf_abi field offsets.
600            let ptr = unsafe {
601                ctx.builder
602                    .build_gep(ctx.context.i8_type(), key_alloca, &[offset_i32], name)
603                    .map_err(|e| CodeGenError::LLVMError(e.to_string()))?
604            };
605            ctx.builder
606                .build_store(ptr, value)
607                .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
608            Ok(())
609        };
610
611        store_key_u32(
612            ghostscope_protocol::PROC_MODULE_KEY_PID_OFFSET,
613            pid,
614            &format!("{name_prefix}_pm_key_pid_ptr"),
615            self,
616        )?;
617        store_key_u32(
618            ghostscope_protocol::PROC_MODULE_KEY_PAD_OFFSET,
619            self.context.i32_type().const_zero(),
620            &format!("{name_prefix}_pm_key_pad_ptr"),
621            self,
622        )?;
623
624        let cookie_lo = i32_type.const_int(module_cookie & 0xffff_ffff, false);
625        let cookie_hi = i32_type.const_int(module_cookie >> 32, false);
626        store_key_u32(
627            ghostscope_protocol::PROC_MODULE_KEY_COOKIE_LO_OFFSET,
628            cookie_lo,
629            &format!("{name_prefix}_pm_key_cookie_lo_ptr"),
630            self,
631        )?;
632        store_key_u32(
633            ghostscope_protocol::PROC_MODULE_KEY_COOKIE_HI_OFFSET,
634            cookie_hi,
635            &format!("{name_prefix}_pm_key_cookie_hi_ptr"),
636            self,
637        )?;
638
639        // Call bpf_map_lookup_elem(map, &key)
640        let lookup_id = i64_type.const_int(BPF_FUNC_map_lookup_elem as u64, false);
641        let lookup_fn_type = ptr_type.fn_type(&[ptr_type.into(), ptr_type.into()], false);
642        let lookup_fn_ptr = self
643            .builder
644            .build_int_to_ptr(lookup_id, ptr_type, &format!("{name_prefix}_lookup_fn"))
645            .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
646        // Pass pointer to the beginning of the key buffer (void*)
647        let key_arg = self
648            .builder
649            .build_bit_cast(key_alloca, ptr_type, &format!("{name_prefix}_key_arg"))
650            .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
651        let args: Vec<BasicMetadataValueEnum> = vec![map_ptr_cast.into(), key_arg.into()];
652        let val_ptr_any = self
653            .builder
654            .build_indirect_call(
655                lookup_fn_type,
656                lookup_fn_ptr,
657                &args,
658                &format!("{name_prefix}_val_ptr_any"),
659            )
660            .map_err(|e| CodeGenError::LLVMError(e.to_string()))?
661            .try_as_basic_value()
662            .left()
663            .ok_or_else(|| CodeGenError::LLVMError("map_lookup_elem returned void".to_string()))?;
664
665        let null_ptr = ptr_type.const_null();
666        let current_fn = self.current_function("generate module offset lookup")?;
667        let found_block = self
668            .context
669            .append_basic_block(current_fn, &format!("{name_prefix}_found_offsets"));
670        let miss_block = self
671            .context
672            .append_basic_block(current_fn, &format!("{name_prefix}_miss_offsets"));
673        let cont_block = self
674            .context
675            .append_basic_block(current_fn, &format!("{name_prefix}_cont_offsets"));
676
677        // Compare against NULL
678        let val_ptr = if let BasicValueEnum::PointerValue(p) = val_ptr_any {
679            p
680        } else {
681            null_ptr
682        };
683        let is_null = self
684            .builder
685            .build_int_compare(
686                inkwell::IntPredicate::EQ,
687                self.builder
688                    .build_ptr_to_int(val_ptr, i64_type, &format!("{name_prefix}_val_ptr_i64"))
689                    .map_err(|e| CodeGenError::LLVMError(e.to_string()))?,
690                i64_type.const_zero(),
691                &format!("{name_prefix}_is_null_offsets"),
692            )
693            .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
694        self.builder
695            .build_conditional_branch(is_null, miss_block, found_block)
696            .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
697
698        // Found: load fields from ProcModuleOffsetsValue. The
699        // byte offsets are shared through ghostscope_protocol::bpf_abi because
700        // this map is an ABI between generated eBPF and userspace.
701        self.builder.position_at_end(found_block);
702        let load_field = |offset: usize,
703                          field_name: &str,
704                          ctx: &mut EbpfContext<'ctx, 'dw>,
705                          base: PointerValue<'ctx>|
706         -> Result<IntValue<'ctx>> {
707            let offset_i32 = ctx.context.i32_type().const_int(offset as u64, false);
708            // SAFETY: `base` points at ProcModuleOffsetsValue returned by
709            // bpf_map_lookup_elem. `offset` is one of its u64 field offsets.
710            let field_ptr = unsafe {
711                ctx.builder
712                    .build_gep(ctx.context.i8_type(), base, &[offset_i32], "field_ptr")
713                    .map_err(|e| CodeGenError::LLVMError(e.to_string()))?
714            };
715            let loaded = ctx
716                .builder
717                .build_load(ctx.context.i64_type(), field_ptr, field_name)
718                .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
719            if let BasicValueEnum::IntValue(iv) = loaded {
720                Ok(iv)
721            } else {
722                Err(CodeGenError::LLVMError("offset load failed".to_string()))
723            }
724        };
725        let off_text = load_field(
726            ghostscope_protocol::PROC_MODULE_OFFSETS_VALUE_TEXT_OFFSET,
727            &format!("{name_prefix}_text"),
728            self,
729            val_ptr,
730        )?;
731        let off_rodata = load_field(
732            ghostscope_protocol::PROC_MODULE_OFFSETS_VALUE_RODATA_OFFSET,
733            &format!("{name_prefix}_rodata"),
734            self,
735            val_ptr,
736        )?;
737        let off_data = load_field(
738            ghostscope_protocol::PROC_MODULE_OFFSETS_VALUE_DATA_OFFSET,
739            &format!("{name_prefix}_data"),
740            self,
741            val_ptr,
742        )?;
743        let off_bss = load_field(
744            ghostscope_protocol::PROC_MODULE_OFFSETS_VALUE_BSS_OFFSET,
745            &format!("{name_prefix}_bss"),
746            self,
747            val_ptr,
748        )?;
749        self.builder
750            .build_unconditional_branch(cont_block)
751            .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
752        let found_end = self.current_insert_block("finish proc offsets found block")?;
753
754        self.builder.position_at_end(miss_block);
755        self.builder
756            .build_unconditional_branch(cont_block)
757            .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
758        let miss_end = self.current_insert_block("finish proc offsets miss block")?;
759
760        self.builder.position_at_end(cont_block);
761        let zero_i64 = i64_type.const_zero();
762        let phi_i64 = |ctx: &mut EbpfContext<'ctx, 'dw>,
763                       name: &str,
764                       hit_value: IntValue<'ctx>|
765         -> Result<IntValue<'ctx>> {
766            let phi = ctx
767                .builder
768                .build_phi(i64_type, name)
769                .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
770            phi.add_incoming(&[(&hit_value, found_end), (&zero_i64, miss_end)]);
771            Ok(phi.as_basic_value().into_int_value())
772        };
773        let found_type = self.context.bool_type();
774        let found_phi = self
775            .builder
776            .build_phi(found_type, &format!("{name_prefix}_found_phi"))
777            .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
778        found_phi.add_incoming(&[
779            (&found_type.const_int(1, false), found_end),
780            (&found_type.const_zero(), miss_end),
781        ]);
782
783        Ok(ProcModuleOffsetsLookup {
784            found: found_phi.as_basic_value().into_int_value(),
785            text: phi_i64(self, &format!("{name_prefix}_text_phi"), off_text)?,
786            rodata: phi_i64(self, &format!("{name_prefix}_rodata_phi"), off_rodata)?,
787            data: phi_i64(self, &format!("{name_prefix}_data_phi"), off_data)?,
788            bss: phi_i64(self, &format!("{name_prefix}_bss_phi"), off_bss)?,
789        })
790    }
791
792    /// Compute runtime address from link-time address using proc_module_offsets map
793    /// section_type: 0=text, 1=rodata, 2=data, 3=bss; other values fallback to data
794    pub fn generate_runtime_address_from_offsets(
795        &mut self,
796        link_addr: IntValue<'ctx>,
797        section_type: u8,
798        module_cookie: u64,
799    ) -> Result<(IntValue<'ctx>, IntValue<'ctx>)> {
800        let i32_type = self.context.i32_type();
801        let offsets = self.lookup_proc_module_offsets_value(module_cookie, "offset")?;
802
803        // Build a bottom-up cascade to preserve earlier choices:
804        // tmp  = (section==data)   ? off_data   : off_bss
805        // tmp2 = (section==rodata) ? off_rodata : tmp
806        // off  = (section==text)   ? off_text  : tmp2
807        let st_val = i32_type.const_int(section_type as u64, false);
808        let eq_text = self
809            .builder
810            .build_int_compare(
811                inkwell::IntPredicate::EQ,
812                st_val,
813                i32_type.const_int(0, false),
814                "is_text",
815            )
816            .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
817        let eq_ro = self
818            .builder
819            .build_int_compare(
820                inkwell::IntPredicate::EQ,
821                st_val,
822                i32_type.const_int(1, false),
823                "is_ro",
824            )
825            .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
826        let eq_da = self
827            .builder
828            .build_int_compare(
829                inkwell::IntPredicate::EQ,
830                st_val,
831                i32_type.const_int(2, false),
832                "is_da",
833            )
834            .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
835
836        let tmp_any = self
837            .builder
838            .build_select(eq_da, offsets.data, offsets.bss, "sel_data_bss")
839            .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
840        let tmp = tmp_any.into_int_value();
841
842        let tmp2_any = self
843            .builder
844            .build_select(eq_ro, offsets.rodata, tmp, "sel_rodata_else")
845            .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
846        let tmp2 = tmp2_any.into_int_value();
847
848        let off_final_any = self
849            .builder
850            .build_select(eq_text, offsets.text, tmp2, "sel_text_else")
851            .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
852        let off_final = off_final_any.into_int_value();
853        let rt_addr = self
854            .builder
855            .build_int_add(link_addr, off_final, "runtime_addr")
856            .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
857        let final_addr = self
858            .builder
859            .build_select::<BasicValueEnum<'ctx>, _>(
860                offsets.found,
861                rt_addr.into(),
862                link_addr.into(),
863                "addr_or_link",
864            )
865            .map_err(|e| CodeGenError::LLVMError(e.to_string()))?
866            .into_int_value();
867
868        Ok((final_addr, offsets.found))
869    }
870    /// Load a register value from pt_regs
871    pub fn load_register_value(
872        &mut self,
873        reg_num: u16,
874        pt_regs_ptr: PointerValue<'ctx>,
875    ) -> Result<BasicValueEnum<'ctx>> {
876        // Map DWARF register number to pt_regs offset
877        let pt_regs_offset = self.dwarf_reg_to_pt_regs_offset(reg_num)?;
878
879        // Calculate pointer to register in pt_regs structure
880        let i64_type = self.context.i64_type();
881        let offset_value = i64_type.const_int(pt_regs_offset as u64, false);
882
883        // SAFETY: pt_regs_offset was converted to a u64 slot index by the platform
884        // register mapping, so the generated access targets a pt_regs register slot.
885        let reg_ptr = unsafe {
886            self.builder
887                .build_gep(
888                    i64_type,
889                    pt_regs_ptr,
890                    &[offset_value],
891                    &format!("reg_{reg_num}_ptr"),
892                )
893                .map_err(|e| CodeGenError::LLVMError(e.to_string()))?
894        };
895
896        // Load the register value
897        let reg_value = self
898            .builder
899            .build_load(i64_type, reg_ptr, &format!("reg_{reg_num}"))
900            .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
901
902        if let BasicValueEnum::IntValue(_) = reg_value {
903            Ok(reg_value)
904        } else {
905            Err(CodeGenError::RegisterMappingError(format!(
906                "Failed to load register {reg_num} as integer"
907            )))
908        }
909    }
910
911    fn probe_read_user_core(
912        &mut self,
913        address: RuntimeAddress<'ctx>,
914        size: MemoryAccessSize,
915        name_suffix: &str,
916    ) -> Result<ProbeReadResult<'ctx>> {
917        let i64_type = self.context.i64_type();
918        let ptr_type = self.context.ptr_type(AddressSpace::default());
919        let offsets_found = address.offsets_found;
920        let not_found = self
921            .builder
922            .build_not(offsets_found, "offsets_miss")
923            .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
924
925        let result_size = size.bytes();
926        let scratch_buffer = self.get_probe_read_scratch_buffer(result_size, name_suffix)?;
927        let dst_ptr = self
928            .builder
929            .build_bit_cast(scratch_buffer, ptr_type, "dst_ptr")
930            .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
931        let base_src_ptr = self
932            .builder
933            .build_int_to_ptr(address.value, ptr_type, "src_ptr")
934            .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
935        let null_ptr = ptr_type.const_null();
936        let src_ptr = self
937            .builder
938            .build_select::<BasicValueEnum<'ctx>, _>(
939                offsets_found,
940                base_src_ptr.into(),
941                null_ptr.into(),
942                "src_or_null",
943            )
944            .map_err(|e| CodeGenError::LLVMError(e.to_string()))?
945            .into_pointer_value();
946
947        let i32_type = self.context.i32_type();
948        let helper_id = i64_type.const_int(BPF_FUNC_probe_read_user as u64, false);
949        let helper_fn_type =
950            i32_type.fn_type(&[ptr_type.into(), i32_type.into(), ptr_type.into()], false);
951        let helper_fn_ptr = self
952            .builder
953            .build_int_to_ptr(helper_id, ptr_type, "probe_read_user_fn")
954            .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
955        let size_val = i32_type.const_int(result_size as u64, false);
956        let zero_i32 = i32_type.const_zero();
957        let effective_size = self
958            .builder
959            .build_select::<BasicValueEnum<'ctx>, _>(
960                offsets_found,
961                size_val.into(),
962                zero_i32.into(),
963                "size_or_zero",
964            )
965            .map_err(|e| CodeGenError::LLVMError(e.to_string()))?
966            .into_int_value();
967        let call_args: Vec<BasicMetadataValueEnum> =
968            vec![dst_ptr.into(), effective_size.into(), src_ptr.into()];
969
970        let call_site = self
971            .builder
972            .build_indirect_call(
973                helper_fn_type,
974                helper_fn_ptr,
975                &call_args,
976                "probe_read_result",
977            )
978            .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
979        let ret_iv = call_site.try_as_basic_value().left().ok_or_else(|| {
980            CodeGenError::LLVMError("Expected integer return from helper".to_string())
981        })?;
982        let ret_i32 = ret_iv.into_int_value();
983        let read_fail = self
984            .builder
985            .build_int_compare(
986                inkwell::IntPredicate::NE,
987                ret_i32,
988                i32_type.const_zero(),
989                "read_fail",
990            )
991            .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
992        let combined_fail = self
993            .builder
994            .build_or(read_fail, not_found, "combined_fail")
995            .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
996
997        let result_type: BasicTypeEnum = match size {
998            MemoryAccessSize::U8 => self.context.i8_type().into(),
999            MemoryAccessSize::U16 => self.context.i16_type().into(),
1000            MemoryAccessSize::U32 => self.context.i32_type().into(),
1001            MemoryAccessSize::U64 => self.context.i64_type().into(),
1002        };
1003        let typed_ptr = self
1004            .builder
1005            .build_bit_cast(
1006                scratch_buffer,
1007                self.context.ptr_type(AddressSpace::default()),
1008                "typed_ptr",
1009            )
1010            .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
1011        let loaded_value = self
1012            .builder
1013            .build_load(result_type, typed_ptr.into_pointer_value(), "loaded_value")
1014            .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
1015        let loaded_i64 = if let BasicValueEnum::IntValue(int_val) = loaded_value {
1016            if int_val.get_type().get_bit_width() < 64 {
1017                self.builder
1018                    .build_int_z_extend(int_val, i64_type, "extended")
1019                    .map_err(|e| CodeGenError::LLVMError(e.to_string()))?
1020            } else {
1021                int_val
1022            }
1023        } else {
1024            return Err(CodeGenError::MemoryAccessError(
1025                "Expected integer value from memory read".to_string(),
1026            ));
1027        };
1028
1029        Ok(ProbeReadResult {
1030            loaded_i64,
1031            combined_fail,
1032            not_found,
1033        })
1034    }
1035
1036    fn update_any_fail_flag(
1037        &mut self,
1038        combined_fail: IntValue<'ctx>,
1039        name_suffix: &str,
1040    ) -> Result<()> {
1041        let i8_type = self.context.i8_type();
1042        let fail_i8 = self
1043            .builder
1044            .build_int_z_extend(combined_fail, i8_type, &format!("fail_i8_{name_suffix}"))
1045            .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
1046        let fail_ptr = self.get_or_create_flag_global("_gs_any_fail");
1047        let cur_fail = self
1048            .builder
1049            .build_load(i8_type, fail_ptr, &format!("cur_fail_{name_suffix}"))
1050            .map_err(|e| CodeGenError::LLVMError(e.to_string()))?
1051            .into_int_value();
1052        let new_fail = self
1053            .builder
1054            .build_or(cur_fail, fail_i8, &format!("fail_or_miss_{name_suffix}"))
1055            .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
1056        self.builder
1057            .build_store(fail_ptr, new_fail)
1058            .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
1059        Ok(())
1060    }
1061
1062    pub(crate) fn store_variable_read_status(
1063        &mut self,
1064        status_ptr: PointerValue<'ctx>,
1065        combined_fail: IntValue<'ctx>,
1066        not_found: IntValue<'ctx>,
1067        name_suffix: &str,
1068    ) -> Result<()> {
1069        let cur_status = self
1070            .builder
1071            .build_load(
1072                self.context.i8_type(),
1073                status_ptr,
1074                &format!("cur_status_{name_suffix}"),
1075            )
1076            .map_err(|e| CodeGenError::LLVMError(e.to_string()))?
1077            .into_int_value();
1078        let is_ok = self
1079            .builder
1080            .build_int_compare(
1081                inkwell::IntPredicate::EQ,
1082                cur_status,
1083                self.context.i8_type().const_zero(),
1084                &format!("status_is_ok_{name_suffix}"),
1085            )
1086            .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
1087        let desired_status = self
1088            .builder
1089            .build_select::<BasicValueEnum<'ctx>, _>(
1090                not_found,
1091                self.context
1092                    .i8_type()
1093                    .const_int(VariableStatus::OffsetsUnavailable as u64, false)
1094                    .into(),
1095                self.context
1096                    .i8_type()
1097                    .const_int(VariableStatus::ReadError as u64, false)
1098                    .into(),
1099                &format!("desired_read_status_{name_suffix}"),
1100            )
1101            .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
1102        let should_store = self
1103            .builder
1104            .build_and(
1105                is_ok,
1106                combined_fail,
1107                &format!("should_store_read_status_{name_suffix}"),
1108            )
1109            .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
1110        let new_status = self
1111            .builder
1112            .build_select::<BasicValueEnum<'ctx>, _>(
1113                should_store,
1114                desired_status,
1115                cur_status.into(),
1116                &format!("new_status_{name_suffix}"),
1117            )
1118            .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
1119        self.builder
1120            .build_store(status_ptr, new_status)
1121            .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
1122        Ok(())
1123    }
1124
1125    /// Generate memory read using bpf_probe_read_user
1126    pub(crate) fn generate_memory_read(
1127        &mut self,
1128        address: RuntimeAddress<'ctx>,
1129        size: MemoryAccessSize,
1130        status_ptr: Option<PointerValue<'ctx>>,
1131    ) -> Result<BasicValueEnum<'ctx>> {
1132        let zero_const = self.context.i64_type().const_zero();
1133        let ProbeReadResult {
1134            loaded_i64,
1135            combined_fail,
1136            not_found,
1137        } = self.probe_read_user_core(address, size, "probe_read_user")?;
1138
1139        if let Some(status_ptr) = status_ptr {
1140            self.store_variable_read_status(
1141                status_ptr,
1142                combined_fail,
1143                not_found,
1144                "probe_read_user",
1145            )?;
1146        }
1147        self.update_any_fail_flag(combined_fail, "probe_read_user")?;
1148
1149        let zero_bv: BasicValueEnum = zero_const.into();
1150        let val_bv: BasicValueEnum = loaded_i64.into();
1151        self.builder
1152            .build_select::<BasicValueEnum<'ctx>, _>(
1153                combined_fail,
1154                zero_bv,
1155                val_bv,
1156                "value_or_zero",
1157            )
1158            .map_err(|e| CodeGenError::LLVMError(e.to_string()))
1159    }
1160
1161    /// Generate a user memory read and return both the zero-on-failure value and failure flag.
1162    pub(crate) fn generate_memory_read_with_fail_flag(
1163        &mut self,
1164        address: RuntimeAddress<'ctx>,
1165        size: MemoryAccessSize,
1166        name_suffix: &str,
1167    ) -> Result<(BasicValueEnum<'ctx>, IntValue<'ctx>)> {
1168        let zero_const = self.context.i64_type().const_zero();
1169        let ProbeReadResult {
1170            loaded_i64,
1171            combined_fail,
1172            ..
1173        } = self.probe_read_user_core(address, size, name_suffix)?;
1174
1175        self.update_any_fail_flag(combined_fail, name_suffix)?;
1176
1177        let zero_bv: BasicValueEnum = zero_const.into();
1178        let val_bv: BasicValueEnum = loaded_i64.into();
1179        let value = self
1180            .builder
1181            .build_select::<BasicValueEnum<'ctx>, _>(
1182                combined_fail,
1183                zero_bv,
1184                val_bv,
1185                &format!("{name_suffix}_value_or_zero"),
1186            )
1187            .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
1188        Ok((value, combined_fail))
1189    }
1190
1191    /// Generate memory read with runtime status capture (for control-flow conditions).
1192    /// On helper failure, sets condition error code (if active) and returns zero value.
1193    pub(crate) fn generate_memory_read_with_status(
1194        &mut self,
1195        address: RuntimeAddress<'ctx>,
1196        size: MemoryAccessSize,
1197    ) -> Result<BasicValueEnum<'ctx>> {
1198        let zero_const = self.context.i64_type().const_zero();
1199        let ProbeReadResult {
1200            loaded_i64,
1201            combined_fail,
1202            ..
1203        } = self.probe_read_user_core(address, size, "probe_read_user_cf")?;
1204
1205        let func = self.current_function("generate memory read with status")?;
1206        let set_block = self.context.append_basic_block(func, "set_cond_err");
1207        let cont_block = self.context.append_basic_block(func, "read_cont");
1208        self.builder
1209            .build_conditional_branch(combined_fail, set_block, cont_block)
1210            .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
1211        self.builder.position_at_end(set_block);
1212        let _ = self.set_condition_error_if_unset(2u8);
1213        let _ = self.set_condition_error_addr_if_unset(address.value);
1214        self.builder
1215            .build_unconditional_branch(cont_block)
1216            .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
1217        self.builder.position_at_end(cont_block);
1218
1219        let zero_bv: BasicValueEnum = zero_const.into();
1220        let val_bv: BasicValueEnum = loaded_i64.into();
1221        let sel_bv = self
1222            .builder
1223            .build_select::<BasicValueEnum<'ctx>, _>(combined_fail, zero_bv, val_bv, "val_or_zero")
1224            .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
1225
1226        self.update_any_fail_flag(combined_fail, "probe_read_user_cf")?;
1227        Ok(sel_bv)
1228    }
1229    /// Map DWARF register number to pt_regs offset (simplified)
1230    pub fn dwarf_reg_to_pt_regs_offset(&self, dwarf_reg: u16) -> Result<usize> {
1231        // Use platform-specific register mapping to get byte offset
1232        let byte_offset = register_mapping::dwarf_reg_to_pt_regs_byte_offset(dwarf_reg)
1233            .ok_or_else(|| match register_mapping::dwarf_reg_to_name(dwarf_reg) {
1234                Some(reg_name) if reg_name.starts_with("XMM") => {
1235                    CodeGenError::RegisterMappingError(format!(
1236                        "Unsupported DWARF register: {dwarf_reg} ({reg_name}) is a SIMD/FP register; uprobe pt_regs does not expose XMM register values, so optimized float by-value parameters are unavailable unless the compiler spills them to memory"
1237                    ))
1238                }
1239                Some(reg_name) => CodeGenError::RegisterMappingError(format!(
1240                    "Unsupported DWARF register: {dwarf_reg} ({reg_name})"
1241                )),
1242                None => CodeGenError::RegisterMappingError(format!(
1243                    "Unsupported DWARF register: {dwarf_reg}"
1244                )),
1245            })?;
1246
1247        // Convert byte offset to u64 array index for pt_regs access
1248        let u64_index = byte_offset / core::mem::size_of::<u64>();
1249        Ok(u64_index)
1250    }
1251
1252    /// Create eBPF helper call using the correct calling convention
1253    /// This creates an indirect call through the eBPF helper mechanism
1254    pub fn create_bpf_helper_call(
1255        &mut self,
1256        helper_id: u64,
1257        args: &[BasicValueEnum<'ctx>],
1258        return_type: BasicTypeEnum<'ctx>,
1259        call_name: &str,
1260    ) -> Result<BasicValueEnum<'ctx>> {
1261        use inkwell::types::BasicMetadataTypeEnum;
1262
1263        // Create function type for the helper
1264        let arg_types: Vec<BasicMetadataTypeEnum> =
1265            args.iter().map(|arg| arg.get_type().into()).collect();
1266        let fn_type = return_type.fn_type(&arg_types, false);
1267
1268        // Convert helper ID to function pointer for indirect call
1269        let i64_type = self.context.i64_type();
1270        let ptr_type = self.context.ptr_type(AddressSpace::default());
1271
1272        let helper_id_val = i64_type.const_int(helper_id, false);
1273        let helper_fn_ptr = self
1274            .builder
1275            .build_int_to_ptr(helper_id_val, ptr_type, "helper_fn")
1276            .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
1277
1278        // Convert args to metadata values
1279        let metadata_args: Vec<BasicMetadataValueEnum> =
1280            args.iter().map(|arg| (*arg).into()).collect();
1281
1282        // Make the indirect call
1283        let call_result = self
1284            .builder
1285            .build_indirect_call(fn_type, helper_fn_ptr, &metadata_args, call_name)
1286            .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
1287
1288        // Convert CallSiteValue to BasicValueEnum
1289        Ok(call_result.try_as_basic_value().left().unwrap_or_else(|| {
1290            // If it's void, return a null value of the expected type
1291            return_type.const_zero()
1292        }))
1293    }
1294
1295    /// Get current timestamp using bpf_ktime_get_ns
1296    pub fn get_current_timestamp(&mut self) -> Result<IntValue<'ctx>> {
1297        let i64_type = self.context.i64_type();
1298
1299        // Call bpf_ktime_get_ns() - takes no arguments
1300        let timestamp = self.create_bpf_helper_call(
1301            BPF_FUNC_ktime_get_ns as u64,
1302            &[],
1303            i64_type.into(),
1304            "timestamp",
1305        )?;
1306
1307        if let BasicValueEnum::IntValue(int_val) = timestamp {
1308            Ok(int_val)
1309        } else {
1310            Err(CodeGenError::LLVMError(
1311                "bpf_ktime_get_ns did not return integer".to_string(),
1312            ))
1313        }
1314    }
1315
1316    /// Get current PID/TID using bpf_get_current_pid_tgid
1317    pub fn get_current_pid_tgid(&mut self) -> Result<IntValue<'ctx>> {
1318        let i64_type = self.context.i64_type();
1319
1320        // Call bpf_get_current_pid_tgid() - returns combined PID/TID
1321        let pid_tgid = self.create_bpf_helper_call(
1322            BPF_FUNC_get_current_pid_tgid as u64,
1323            &[],
1324            i64_type.into(),
1325            "pid_tgid",
1326        )?;
1327
1328        if let BasicValueEnum::IntValue(int_val) = pid_tgid {
1329            Ok(int_val)
1330        } else {
1331            Err(CodeGenError::LLVMError(
1332                "bpf_get_current_pid_tgid did not return integer".to_string(),
1333            ))
1334        }
1335    }
1336
1337    pub(crate) fn generate_static_tls_address(
1338        &mut self,
1339        tls_offset: RuntimeAddress<'ctx>,
1340        module_hint: Option<&str>,
1341    ) -> Result<RuntimeAddress<'ctx>> {
1342        let module_path = match module_hint {
1343            Some(module_path) => module_path.to_string(),
1344            None => self.get_compile_time_context()?.module_path.clone(),
1345        };
1346        if ghostscope_process::is_shared_object(std::path::Path::new(&module_path)) {
1347            return Err(CodeGenError::NotImplemented(format!(
1348                "dynamic/shared-library TLS is not supported yet for DW_OP_form_tls_address in {module_path}; only x86_64 executable static TLS is currently supported"
1349            )));
1350        }
1351        let tls_bias =
1352            ghostscope_platform::static_tls_bias_for_elf(std::path::Path::new(&module_path))
1353                .map_err(|err| {
1354                    CodeGenError::LLVMError(format!(
1355                        "failed to read static TLS layout for {module_path}: {err}"
1356                    ))
1357                })?
1358                .ok_or_else(|| {
1359                    CodeGenError::LLVMError(format!(
1360                        "module {module_path} does not contain a PT_TLS segment"
1361                    ))
1362                })?;
1363        let fsbase_offset = ghostscope_platform::current_task_fsbase_offset().map_err(|err| {
1364            CodeGenError::LLVMError(format!(
1365                "failed to resolve task_struct.thread.fsbase offset from kernel BTF: {err}"
1366            ))
1367        })?;
1368
1369        let i64_type = self.context.i64_type();
1370        let i32_type = self.context.i32_type();
1371        let ptr_type = self.context.ptr_type(AddressSpace::default());
1372        let task_ptr_value = self.create_bpf_helper_call(
1373            BPF_FUNC_get_current_task as u64,
1374            &[],
1375            i64_type.into(),
1376            "current_task",
1377        )?;
1378        let BasicValueEnum::IntValue(task_ptr_int) = task_ptr_value else {
1379            return Err(CodeGenError::LLVMError(
1380                "bpf_get_current_task did not return integer".to_string(),
1381            ));
1382        };
1383        let fsbase_field_addr = self
1384            .builder
1385            .build_int_add(
1386                task_ptr_int,
1387                i64_type.const_int(fsbase_offset, false),
1388                "task_fsbase_addr",
1389            )
1390            .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
1391        let src_ptr = self
1392            .builder
1393            .build_int_to_ptr(fsbase_field_addr, ptr_type, "task_fsbase_ptr")
1394            .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
1395        let scratch_buffer = self.get_or_create_tls_scratch_buffer()?;
1396        let dst_ptr = self
1397            .builder
1398            .build_bit_cast(scratch_buffer, ptr_type, "tls_fsbase_dst")
1399            .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
1400
1401        let helper_id = i64_type.const_int(BPF_FUNC_probe_read_kernel as u64, false);
1402        let helper_fn_type =
1403            i32_type.fn_type(&[ptr_type.into(), i32_type.into(), ptr_type.into()], false);
1404        let helper_fn_ptr = self
1405            .builder
1406            .build_int_to_ptr(helper_id, ptr_type, "probe_read_kernel_fn")
1407            .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
1408        let call_args: Vec<BasicMetadataValueEnum> = vec![
1409            dst_ptr.into(),
1410            i32_type.const_int(8, false).into(),
1411            src_ptr.into(),
1412        ];
1413        let call_site = self
1414            .builder
1415            .build_indirect_call(
1416                helper_fn_type,
1417                helper_fn_ptr,
1418                &call_args,
1419                "probe_read_kernel_result",
1420            )
1421            .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
1422        let ret_i32 = call_site
1423            .try_as_basic_value()
1424            .left()
1425            .ok_or_else(|| {
1426                CodeGenError::LLVMError("Expected integer return from helper".to_string())
1427            })?
1428            .into_int_value();
1429        let read_ok = self
1430            .builder
1431            .build_int_compare(
1432                inkwell::IntPredicate::EQ,
1433                ret_i32,
1434                i32_type.const_zero(),
1435                "tls_fsbase_read_ok",
1436            )
1437            .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
1438        let read_fail = self
1439            .builder
1440            .build_not(read_ok, "tls_fsbase_read_fail")
1441            .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
1442        self.update_any_fail_flag(read_fail, "tls_fsbase")?;
1443
1444        let typed_ptr = self
1445            .builder
1446            .build_bit_cast(scratch_buffer, ptr_type, "tls_fsbase_typed_ptr")
1447            .map_err(|e| CodeGenError::LLVMError(e.to_string()))?
1448            .into_pointer_value();
1449        let fsbase = self
1450            .builder
1451            .build_load(i64_type, typed_ptr, "tls_fsbase")
1452            .map_err(|e| CodeGenError::LLVMError(e.to_string()))?
1453            .into_int_value();
1454        let tls_base = self
1455            .builder
1456            .build_int_add(
1457                fsbase,
1458                i64_type.const_int(tls_bias as u64, true),
1459                "tls_static_base",
1460            )
1461            .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
1462        let tls_addr = self
1463            .builder
1464            .build_int_add(tls_base, tls_offset.value, "tls_addr")
1465            .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
1466        let offsets_found = self
1467            .builder
1468            .build_and(tls_offset.offsets_found, read_ok, "tls_addr_available")
1469            .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
1470        let value = self
1471            .builder
1472            .build_select::<BasicValueEnum<'ctx>, _>(
1473                offsets_found,
1474                tls_addr.into(),
1475                i64_type.const_zero().into(),
1476                "tls_addr_or_zero",
1477            )
1478            .map_err(|e| CodeGenError::LLVMError(e.to_string()))?
1479            .into_int_value();
1480
1481        Ok(RuntimeAddress::with_offsets_found(value, offsets_found))
1482    }
1483
1484    /// Create event output using either RingBuf or PerfEventArray based on compile options
1485    /// This is the unified interface that should be used for all event output
1486    pub fn create_event_output(&mut self, data: PointerValue<'ctx>, size: u64) -> Result<()> {
1487        match self.compile_options.event_map_type {
1488            crate::EventMapType::RingBuf => self.create_ringbuf_output_internal(data, size),
1489            crate::EventMapType::PerfEventArray => {
1490                self.create_perf_event_output_internal(data, size)
1491            }
1492        }
1493    }
1494
1495    fn increment_event_loss_counter(&mut self) -> Result<()> {
1496        let i64_type = self.context.i64_type();
1497
1498        let counter_ptr = self.lookup_percpu_value_ptr("event_loss_counters", 0)?;
1499        let current_fn = self
1500            .builder
1501            .get_insert_block()
1502            .and_then(|block| block.get_parent())
1503            .ok_or_else(|| {
1504                CodeGenError::LLVMError(
1505                    "Cannot increment event loss counter outside a function".to_string(),
1506                )
1507            })?;
1508        let counter_hit_block = self
1509            .context
1510            .append_basic_block(current_fn, "event_loss_counter_hit");
1511        let counter_done_block = self
1512            .context
1513            .append_basic_block(current_fn, "event_loss_counter_done");
1514        let is_null = self
1515            .builder
1516            .build_is_null(counter_ptr, "event_loss_counter_is_null")
1517            .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
1518        self.builder
1519            .build_conditional_branch(is_null, counter_done_block, counter_hit_block)
1520            .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
1521
1522        self.builder.position_at_end(counter_hit_block);
1523        let current = self
1524            .builder
1525            .build_load(i64_type, counter_ptr, "event_loss_counter")
1526            .map_err(|e| CodeGenError::LLVMError(e.to_string()))?
1527            .into_int_value();
1528        let next = self
1529            .builder
1530            .build_int_add(
1531                current,
1532                i64_type.const_int(1, false),
1533                "event_loss_counter_next",
1534            )
1535            .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
1536        self.builder
1537            .build_store(counter_ptr, next)
1538            .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
1539        self.builder
1540            .build_unconditional_branch(counter_done_block)
1541            .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
1542
1543        self.builder.position_at_end(counter_done_block);
1544        Ok(())
1545    }
1546
1547    fn record_event_output_loss_on_error(&mut self, output_result: IntValue<'ctx>) -> Result<()> {
1548        let i64_type = self.context.i64_type();
1549        let current_fn = self
1550            .builder
1551            .get_insert_block()
1552            .and_then(|block| block.get_parent())
1553            .ok_or_else(|| {
1554                CodeGenError::LLVMError(
1555                    "Cannot record event output loss outside a function".to_string(),
1556                )
1557            })?;
1558        let output_failed = self
1559            .builder
1560            .build_int_compare(
1561                inkwell::IntPredicate::SLT,
1562                output_result,
1563                i64_type.const_zero(),
1564                "event_output_failed",
1565            )
1566            .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
1567        let loss_block = self
1568            .context
1569            .append_basic_block(current_fn, "event_output_loss");
1570        let cont_block = self
1571            .context
1572            .append_basic_block(current_fn, "event_output_after_loss_check");
1573        self.builder
1574            .build_conditional_branch(output_failed, loss_block, cont_block)
1575            .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
1576
1577        self.builder.position_at_end(loss_block);
1578        self.increment_event_loss_counter()?;
1579        self.builder
1580            .build_unconditional_branch(cont_block)
1581            .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
1582
1583        self.builder.position_at_end(cont_block);
1584        Ok(())
1585    }
1586
1587    /// Create ringbuf output using bpf_ringbuf_output (internal implementation)
1588    fn create_ringbuf_output_internal(
1589        &mut self,
1590        data: PointerValue<'ctx>,
1591        size: u64,
1592    ) -> Result<()> {
1593        let i64_type = self.context.i64_type();
1594
1595        // Get ringbuf map
1596        let ringbuf_global = self
1597            .map_manager
1598            .get_ringbuf_map(&self.module, "ringbuf")
1599            .map_err(|e| {
1600                CodeGenError::MemoryAccessError(format!("Failed to get ringbuf map: {e}"))
1601            })?;
1602
1603        // Arguments: map, data, size, flags
1604        let args = [
1605            ringbuf_global.into(),
1606            data.into(),
1607            i64_type.const_int(size, false).into(),
1608            i64_type.const_zero().into(), // flags = 0
1609        ];
1610
1611        let result = self.create_bpf_helper_call(
1612            BPF_FUNC_ringbuf_output as u64,
1613            &args,
1614            i64_type.into(),
1615            "ringbuf_output",
1616        )?;
1617        let BasicValueEnum::IntValue(result) = result else {
1618            return Err(CodeGenError::LLVMError(
1619                "bpf_ringbuf_output did not return integer".to_string(),
1620            ));
1621        };
1622        self.record_event_output_loss_on_error(result)?;
1623
1624        Ok(())
1625    }
1626
1627    /// Create ringbuf output with dynamic size (IntValue)
1628    pub fn create_ringbuf_output_dynamic(
1629        &mut self,
1630        data: PointerValue<'ctx>,
1631        size: IntValue<'ctx>,
1632    ) -> Result<()> {
1633        let i64_type = self.context.i64_type();
1634
1635        // Get ringbuf map
1636        let ringbuf_global = self
1637            .map_manager
1638            .get_ringbuf_map(&self.module, "ringbuf")
1639            .map_err(|e| {
1640                CodeGenError::MemoryAccessError(format!("Failed to get ringbuf map: {e}"))
1641            })?;
1642
1643        // Arguments: map, data, size (dynamic), flags
1644        let args = [
1645            ringbuf_global.into(),
1646            data.into(),
1647            size.into(),
1648            i64_type.const_zero().into(), // flags = 0
1649        ];
1650
1651        let result = self.create_bpf_helper_call(
1652            BPF_FUNC_ringbuf_output as u64,
1653            &args,
1654            i64_type.into(),
1655            "ringbuf_output",
1656        )?;
1657        let BasicValueEnum::IntValue(result) = result else {
1658            return Err(CodeGenError::LLVMError(
1659                "bpf_ringbuf_output did not return integer".to_string(),
1660            ));
1661        };
1662        self.record_event_output_loss_on_error(result)?;
1663
1664        Ok(())
1665    }
1666
1667    /// Lookup per-CPU map value pointer for a given map name and u32 key constant
1668    pub fn lookup_percpu_value_ptr(
1669        &mut self,
1670        map_name: &str,
1671        key_const: u32,
1672    ) -> Result<PointerValue<'ctx>> {
1673        let ptr_ty = self.context.ptr_type(AddressSpace::default());
1674        let i32_ty = self.context.i32_type();
1675        let map_global = self
1676            .map_manager
1677            .get_map(&self.module, map_name)
1678            .map_err(|e| CodeGenError::LLVMError(format!("Map not found {map_name}: {e}")))?;
1679        let map_ptr = self
1680            .builder
1681            .build_bit_cast(map_global, ptr_ty, "map_ptr")
1682            .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
1683
1684        // Prepare stack key in the entry-block alloca (reuse pm_key's first i32 slot)
1685        let key_arr_ty = i32_ty.array_type(4);
1686        let key_alloca = self.pm_key_alloca.ok_or_else(|| {
1687            CodeGenError::LLVMError("pm_key not allocated in entry block".to_string())
1688        })?;
1689        let zero = i32_ty.const_zero();
1690        // SAFETY: key_alloca is the [4 x i32] pm_key stack slot and [0, 0]
1691        // addresses the first key element.
1692        let base_i32_ptr = unsafe {
1693            self.builder
1694                .build_gep(key_arr_ty, key_alloca, &[zero, zero], "percpu_key_i32_ptr")
1695                .map_err(|e| CodeGenError::LLVMError(e.to_string()))?
1696        };
1697        self.builder
1698            .build_store(base_i32_ptr, i32_ty.const_int(key_const as u64, false))
1699            .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
1700        let key_ptr = self
1701            .builder
1702            .build_bit_cast(base_i32_ptr, ptr_ty, "key_ptr")
1703            .map_err(|e| CodeGenError::LLVMError(e.to_string()))?;
1704
1705        // long bpf_map_lookup_elem(void *map, const void *key) -> void *
1706        let ret = self.create_bpf_helper_call(
1707            BPF_FUNC_map_lookup_elem as u64,
1708            &[map_ptr, key_ptr],
1709            ptr_ty.into(),
1710            "map_lookup_elem",
1711        )?;
1712        let val_ptr = if let BasicValueEnum::PointerValue(p) = ret {
1713            p
1714        } else {
1715            return Err(CodeGenError::LLVMError(
1716                "map_lookup_elem did not return pointer".to_string(),
1717            ));
1718        };
1719        Ok(val_ptr)
1720    }
1721
1722    /// Create perf event output using bpf_perf_event_output (internal implementation)
1723    fn create_perf_event_output_internal(
1724        &mut self,
1725        data: PointerValue<'ctx>,
1726        size: u64,
1727    ) -> Result<()> {
1728        let size_val = self.context.i64_type().const_int(size, false);
1729        self.create_perf_event_output_dynamic(data, size_val)
1730    }
1731
1732    /// Create perf event output with dynamic size (IntValue)
1733    pub fn create_perf_event_output_dynamic(
1734        &mut self,
1735        data: PointerValue<'ctx>,
1736        size: IntValue<'ctx>,
1737    ) -> Result<()> {
1738        let i64_type = self.context.i64_type();
1739
1740        // Get the current pt_regs pointer (first argument to eBPF program)
1741        let ctx_param = self
1742            .builder
1743            .get_insert_block()
1744            .and_then(|bb| bb.get_parent())
1745            .and_then(|func| func.get_first_param())
1746            .ok_or_else(|| {
1747                CodeGenError::LLVMError("Failed to get context parameter".to_string())
1748            })?;
1749
1750        // Get perf event array map
1751        let events_global = self
1752            .map_manager
1753            .get_perf_map(&self.module, "events")
1754            .map_err(|e| {
1755                CodeGenError::MemoryAccessError(format!("Failed to get perf event map: {e}"))
1756            })?;
1757
1758        // Arguments: ctx, map, flags, data, size
1759        // flags = BPF_F_CURRENT_CPU (0xFFFFFFFF) means use current CPU
1760        let args = [
1761            ctx_param,
1762            events_global.into(),
1763            i64_type.const_int(0xFFFFFFFF_u64, false).into(), // BPF_F_CURRENT_CPU
1764            data.into(),
1765            size.into(),
1766        ];
1767
1768        let result = self.create_bpf_helper_call(
1769            BPF_FUNC_perf_event_output as u64,
1770            &args,
1771            i64_type.into(),
1772            "perf_event_output",
1773        )?;
1774        let BasicValueEnum::IntValue(result) = result else {
1775            return Err(CodeGenError::LLVMError(
1776                "bpf_perf_event_output did not return integer".to_string(),
1777            ));
1778        };
1779        self.record_event_output_loss_on_error(result)?;
1780
1781        Ok(())
1782    }
1783}