Browse Source

aya-ebpf: reduce repetition and excessive traits

The traits `FromBtfArgument`, `FromRawTracepointArgs`, `FromPtRegs` are
all fancy ways of saying `Argument` - so replace these traits with it.

This also removes the use of `bpf_probe_read` which was introduced in
05c1586202ce8719ef92b9b83dd30032bfa11edd because I can't reproduce the
need for it.
Tamir Duberstein 6 days ago
parent
commit
05250da20b

+ 228 - 541
ebpf/aya-ebpf/src/args.rs

@@ -1,3 +1,4 @@
+use crate::bindings::bpf_raw_tracepoint_args;
 #[cfg(any(
     bpf_target_arch = "arm",
     bpf_target_arch = "mips",
@@ -13,619 +14,305 @@ use crate::bindings::pt_regs;
 use crate::bindings::user_pt_regs as pt_regs;
 #[cfg(bpf_target_arch = "riscv64")]
 use crate::bindings::user_regs_struct as pt_regs;
-use crate::{bindings::bpf_raw_tracepoint_args, cty::c_void, helpers::bpf_probe_read};
 
-/// A trait that indicates a valid type for an argument which can be coerced from a BTF
-/// context.
-///
-/// Users should not implement this trait.
-///
-/// # Safety
-///
-/// This trait is _only_ safe to implement on primitive types that can fit into
-/// a `u64`. For example, integers and raw pointers may be coerced from a BTF context.
-pub unsafe trait FromBtfArgument: Sized {
-    /// Coerces a `T` from the `n`th argument from a BTF context where `n` starts
-    /// at 0 and increases by 1 for each successive argument.
-    ///
-    /// # Safety
-    ///
-    /// This function is deeply unsafe, as we are reading raw pointers into kernel
-    /// memory. In particular, the value of `n` must not exceed the number of function
-    /// arguments. Moreover, `ctx` must be a valid pointer to a BTF context, and `T` must
-    /// be the right type for the given argument.
-    unsafe fn from_argument(ctx: *const c_void, n: usize) -> Self;
-}
-
-unsafe impl<T> FromBtfArgument for *const T {
-    unsafe fn from_argument(ctx: *const c_void, n: usize) -> *const T {
-        // BTF arguments are exposed as an array of `usize` where `usize` can
-        // either be treated as a pointer or a primitive type
-        let ctx: *const usize = ctx.cast();
-        (unsafe { *ctx.add(n) }) as _
+mod sealed {
+    #[expect(clippy::missing_safety_doc)]
+    pub unsafe trait Argument {
+        fn from_register(value: u64) -> Self;
     }
-}
 
-/// Helper macro to implement [`FromBtfArgument`] for a primitive type.
-macro_rules! unsafe_impl_from_btf_argument {
-    ($type:ident) => {
-        unsafe impl FromBtfArgument for $type {
-            #[allow(trivial_numeric_casts)]
-            unsafe fn from_argument(ctx: *const c_void, n: usize) -> Self {
-                // BTF arguments are exposed as an array of `usize` where `usize` can
-                // either be treated as a pointer or a primitive type
-                let ctx: *const usize = ctx.cast();
-                (unsafe { *ctx.add(n) }) as _
-            }
+    macro_rules! unsafe_impl_argument {
+        ($($( { $($generics:tt)* } )? $ty:ty $( { where $($where:tt)* } )?),+ $(,)?) => {
+            $(
+                #[allow(clippy::cast_lossless, trivial_numeric_casts)]
+                unsafe impl$($($generics)*)? Argument for $ty $(where $($where)*)? {
+                    fn from_register(value: u64) -> Self {
+                        value as Self
+                    }
+                }
+            )+
         }
-    };
-}
-
-unsafe_impl_from_btf_argument!(u8);
-unsafe_impl_from_btf_argument!(u16);
-unsafe_impl_from_btf_argument!(u32);
-unsafe_impl_from_btf_argument!(u64);
-unsafe_impl_from_btf_argument!(i8);
-unsafe_impl_from_btf_argument!(i16);
-unsafe_impl_from_btf_argument!(i32);
-unsafe_impl_from_btf_argument!(i64);
-unsafe_impl_from_btf_argument!(usize);
-unsafe_impl_from_btf_argument!(isize);
-
-pub struct PtRegs {
-    regs: *mut pt_regs,
-}
-
-/// A portable wrapper around pt_regs, user_pt_regs and user_regs_struct.
-impl PtRegs {
-    pub fn new(regs: *mut pt_regs) -> Self {
-        Self { regs }
-    }
-
-    /// Returns the value of the register used to pass arg `n`.
-    pub fn arg<T: FromPtRegs>(&self, n: usize) -> Option<T> {
-        T::from_argument(unsafe { &*self.regs }, n)
-    }
-
-    /// Returns the value of the register used to pass the return value.
-    pub fn ret<T: FromPtRegs>(&self) -> Option<T> {
-        T::from_retval(unsafe { &*self.regs })
     }
 
-    /// Returns a pointer to the wrapped value.
-    pub fn as_ptr(&self) -> *mut pt_regs {
-        self.regs
-    }
+    unsafe_impl_argument!(
+        i8,
+        u8,
+        i16,
+        u16,
+        i32,
+        u32,
+        i64,
+        u64,
+        i128,
+        u128,
+        isize,
+        usize,
+        {<T>} *const T {where T: 'static},
+        {<T>} *mut T {where T: 'static},
+    );
 }
 
-/// A trait that indicates a valid type for an argument which can be coerced from
-/// a pt_regs context.
-///
-/// Any implementation of this trait is strictly architecture-specific and depends on the
-/// layout of the underlying pt_regs struct and the target processor's calling
-/// conventions. Users should not implement this trait.
-pub trait FromPtRegs: Sized {
-    /// Coerces a `T` from the `n`th argument of a pt_regs context where `n` starts
-    /// at 0 and increases by 1 for each successive argument.
-    fn from_argument(ctx: &pt_regs, n: usize) -> Option<Self>;
-
-    /// Coerces a `T` from the return value of a pt_regs context.
-    fn from_retval(ctx: &pt_regs) -> Option<Self>;
-}
+pub trait Argument: sealed::Argument {}
 
-#[cfg(bpf_target_arch = "x86_64")]
-impl<T> FromPtRegs for *const T {
-    fn from_argument(ctx: &pt_regs, n: usize) -> Option<Self> {
-        match n {
-            0 => unsafe { bpf_probe_read(&ctx.rdi).map(|v| v as *const _).ok() },
-            1 => unsafe { bpf_probe_read(&ctx.rsi).map(|v| v as *const _).ok() },
-            2 => unsafe { bpf_probe_read(&ctx.rdx).map(|v| v as *const _).ok() },
-            3 => unsafe { bpf_probe_read(&ctx.rcx).map(|v| v as *const _).ok() },
-            4 => unsafe { bpf_probe_read(&ctx.r8).map(|v| v as *const _).ok() },
-            5 => unsafe { bpf_probe_read(&ctx.r9).map(|v| v as *const _).ok() },
-            _ => None,
-        }
-    }
+impl<T: sealed::Argument> Argument for T {}
 
-    fn from_retval(ctx: &pt_regs) -> Option<Self> {
-        unsafe { bpf_probe_read(&ctx.rax).map(|v| v as *const _).ok() }
-    }
+/// Coerces a `T` from the `n`th argument from a BTF context where `n` starts
+/// at 0 and increases by 1 for each successive argument.
+pub(crate) fn btf_arg<T: Argument>(ctx: &impl crate::EbpfContext, n: usize) -> T {
+    // BTF arguments are exposed as an array of `usize` where `usize` can
+    // either be treated as a pointer or a primitive type
+    let ptr: *const usize = ctx.as_ptr().cast();
+    let ptr = unsafe { ptr.add(n) };
+    T::from_register(unsafe { *ptr as u64 })
 }
 
-#[cfg(bpf_target_arch = "arm")]
-impl<T> FromPtRegs for *const T {
-    fn from_argument(ctx: &pt_regs, n: usize) -> Option<Self> {
-        if n <= 6 {
-            unsafe { bpf_probe_read(&ctx.uregs[n]).map(|v| v as *const _).ok() }
-        } else {
-            None
-        }
-    }
+trait PtRegsLayout {
+    type Reg;
 
-    fn from_retval(ctx: &pt_regs) -> Option<Self> {
-        unsafe { bpf_probe_read(&ctx.uregs[0]).map(|v| v as *const _).ok() }
-    }
+    fn arg_reg(&self, index: usize) -> Option<&Self::Reg>;
+    fn rc_reg(&self) -> &Self::Reg;
 }
 
 #[cfg(bpf_target_arch = "aarch64")]
-impl<T> FromPtRegs for *const T {
-    fn from_argument(ctx: &pt_regs, n: usize) -> Option<Self> {
-        if n <= 7 {
-            unsafe { bpf_probe_read(&ctx.regs[n]).map(|v| v as *const _).ok() }
-        } else {
-            None
-        }
-    }
-
-    fn from_retval(ctx: &pt_regs) -> Option<Self> {
-        unsafe { bpf_probe_read(&ctx.regs[0]).map(|v| v as *const _).ok() }
-    }
-}
-
-#[cfg(bpf_target_arch = "loongarch64")]
-impl<T> FromPtRegs for *const T {
-    fn from_argument(ctx: &pt_regs, n: usize) -> Option<Self> {
-        if n <= 7 {
-            unsafe { bpf_probe_read(&ctx.regs[4 + n]).map(|v| v as *const _).ok() }
-        } else {
-            None
-        }
-    }
-
-    fn from_retval(ctx: &pt_regs) -> Option<Self> {
-        unsafe { bpf_probe_read(&ctx.regs[4]).map(|v| v as *const _).ok() }
-    }
-}
-
-#[cfg(bpf_target_arch = "riscv64")]
-impl<T> FromPtRegs for *const T {
-    fn from_argument(ctx: &pt_regs, n: usize) -> Option<Self> {
-        match n {
-            0 => unsafe { bpf_probe_read(&ctx.a0).map(|v| v as *const _).ok() },
-            1 => unsafe { bpf_probe_read(&ctx.a1).map(|v| v as *const _).ok() },
-            2 => unsafe { bpf_probe_read(&ctx.a2).map(|v| v as *const _).ok() },
-            3 => unsafe { bpf_probe_read(&ctx.a3).map(|v| v as *const _).ok() },
-            4 => unsafe { bpf_probe_read(&ctx.a4).map(|v| v as *const _).ok() },
-            5 => unsafe { bpf_probe_read(&ctx.a5).map(|v| v as *const _).ok() },
-            6 => unsafe { bpf_probe_read(&ctx.a6).map(|v| v as *const _).ok() },
-            7 => unsafe { bpf_probe_read(&ctx.a7).map(|v| v as *const _).ok() },
+impl PtRegsLayout for pt_regs {
+    type Reg = crate::bindings::__u64;
+
+    fn arg_reg(&self, index: usize) -> Option<&Self::Reg> {
+        // AArch64 arguments align with libbpf's __PT_PARM{1..8}_REG (regs[0..7]).
+        // https://github.com/torvalds/linux/blob/v6.17/arch/arm64/include/uapi/asm/ptrace.h#L88-L93
+        // https://github.com/torvalds/linux/blob/v6.17/tools/lib/bpf/bpf_tracing.h#L229-L244
+        match index {
+            0..=7 => Some(&self.regs[index]),
             _ => None,
         }
     }
 
-    fn from_retval(ctx: &pt_regs) -> Option<Self> {
-        unsafe { bpf_probe_read(&ctx.ra).map(|v| v as *const _).ok() }
+    fn rc_reg(&self) -> &Self::Reg {
+        // Return codes use libbpf's __PT_RC_REG (regs[0]/x0).
+        // https://github.com/torvalds/linux/blob/v6.17/tools/lib/bpf/bpf_tracing.h#L248-L251
+        &self.regs[0]
     }
 }
 
-#[cfg(bpf_target_arch = "powerpc64")]
-impl<T> FromPtRegs for *const T {
-    fn from_argument(ctx: &pt_regs, n: usize) -> Option<Self> {
-        if n <= 7 {
-            unsafe { bpf_probe_read(&ctx.gpr[3 + n]).map(|v| v as *const _).ok() }
-        } else {
-            None
+#[cfg(bpf_target_arch = "arm")]
+impl PtRegsLayout for pt_regs {
+    type Reg = crate::cty::c_long;
+
+    fn arg_reg(&self, index: usize) -> Option<&Self::Reg> {
+        // ARM arguments follow libbpf's __PT_PARM{1..7}_REG mapping (uregs[0..6]).
+        // https://github.com/torvalds/linux/blob/v6.17/arch/arm/include/uapi/asm/ptrace.h#L124-L152
+        // https://github.com/torvalds/linux/blob/v6.17/tools/lib/bpf/bpf_tracing.h#L198-L210
+        match index {
+            0..=6 => Some(&self.uregs[index]),
+            _ => None,
         }
     }
 
-    fn from_retval(ctx: &pt_regs) -> Option<Self> {
-        unsafe { bpf_probe_read(&ctx.gpr[3]).map(|v| v as *const _).ok() }
+    fn rc_reg(&self) -> &Self::Reg {
+        // Return codes use libbpf's __PT_RC_REG (uregs[0]).
+        // https://github.com/torvalds/linux/blob/v6.17/tools/lib/bpf/bpf_tracing.h#L211-L214
+        &self.uregs[0]
     }
 }
 
-#[cfg(bpf_target_arch = "s390x")]
-impl<T> FromPtRegs for *const T {
-    fn from_argument(ctx: &pt_regs, n: usize) -> Option<Self> {
-        if n <= 4 {
-            unsafe { bpf_probe_read(&ctx.gprs[2 + n]).map(|v| v as *const _).ok() }
-        } else {
-            None
+#[cfg(bpf_target_arch = "loongarch64")]
+impl PtRegsLayout for pt_regs {
+    type Reg = crate::cty::c_ulong;
+
+    fn arg_reg(&self, index: usize) -> Option<&Self::Reg> {
+        // LoongArch arguments correspond to libbpf's __PT_PARM{1..8}_REG (regs[4..11]).
+        // https://github.com/torvalds/linux/blob/v6.17/arch/loongarch/include/asm/ptrace.h#L20-L33
+        // https://github.com/torvalds/linux/blob/v6.17/tools/lib/bpf/bpf_tracing.h#L427-L444
+        match index {
+            0..=7 => Some(&self.regs[4 + index]),
+            _ => None,
         }
     }
 
-    fn from_retval(ctx: &pt_regs) -> Option<Self> {
-        unsafe { bpf_probe_read(&ctx.gprs[2]).map(|v| v as *const _).ok() }
+    fn rc_reg(&self) -> &Self::Reg {
+        // Return codes use libbpf's __PT_RC_REG (regs[4], a0).
+        // https://github.com/torvalds/linux/blob/v6.17/tools/lib/bpf/bpf_tracing.h#L445-L447
+        &self.regs[4]
     }
 }
 
 #[cfg(bpf_target_arch = "mips")]
-impl<T> FromPtRegs for *const T {
-    fn from_argument(ctx: &pt_regs, n: usize) -> Option<Self> {
-        // Assume N64 ABI like libbpf does.
-        if n <= 7 {
-            unsafe { bpf_probe_read(&ctx.regs[n + 4]).map(|v| v as *const _).ok() }
-        } else {
-            None
-        }
-    }
-
-    fn from_retval(ctx: &pt_regs) -> Option<Self> {
-        unsafe { bpf_probe_read(&ctx.regs[31]).map(|v| v as *const _).ok() }
-    }
-}
-
-#[cfg(bpf_target_arch = "x86_64")]
-impl<T> FromPtRegs for *mut T {
-    fn from_argument(ctx: &pt_regs, n: usize) -> Option<Self> {
-        match n {
-            0 => unsafe { bpf_probe_read(&ctx.rdi).map(|v| v as *mut _).ok() },
-            1 => unsafe { bpf_probe_read(&ctx.rsi).map(|v| v as *mut _).ok() },
-            2 => unsafe { bpf_probe_read(&ctx.rdx).map(|v| v as *mut _).ok() },
-            3 => unsafe { bpf_probe_read(&ctx.rcx).map(|v| v as *mut _).ok() },
-            4 => unsafe { bpf_probe_read(&ctx.r8).map(|v| v as *mut _).ok() },
-            5 => unsafe { bpf_probe_read(&ctx.r9).map(|v| v as *mut _).ok() },
+impl PtRegsLayout for pt_regs {
+    type Reg = crate::bindings::__u64;
+
+    fn arg_reg(&self, index: usize) -> Option<&Self::Reg> {
+        // MIPS N64 arguments correspond to libbpf's __PT_PARM{1..8}_REG (regs[4..11]).
+        // https://github.com/torvalds/linux/blob/v6.17/arch/mips/include/asm/ptrace.h#L28-L52
+        // https://github.com/torvalds/linux/blob/v6.17/tools/lib/bpf/bpf_tracing.h#L261-L275
+        match index {
+            0..=7 => Some(&self.regs[4 + index]),
             _ => None,
         }
     }
 
-    fn from_retval(ctx: &pt_regs) -> Option<Self> {
-        unsafe { bpf_probe_read(&ctx.rax).map(|v| v as *mut _).ok() }
-    }
-}
-
-#[cfg(bpf_target_arch = "arm")]
-impl<T> FromPtRegs for *mut T {
-    fn from_argument(ctx: &pt_regs, n: usize) -> Option<Self> {
-        if n <= 6 {
-            unsafe { bpf_probe_read(&ctx.uregs[n]).map(|v| v as *mut _).ok() }
-        } else {
-            None
-        }
-    }
-
-    fn from_retval(ctx: &pt_regs) -> Option<Self> {
-        unsafe { bpf_probe_read(&ctx.uregs[0]).map(|v| v as *mut _).ok() }
-    }
-}
-
-#[cfg(bpf_target_arch = "aarch64")]
-impl<T> FromPtRegs for *mut T {
-    fn from_argument(ctx: &pt_regs, n: usize) -> Option<Self> {
-        if n <= 7 {
-            unsafe { bpf_probe_read(&ctx.regs[n]).map(|v| v as *mut _).ok() }
-        } else {
-            None
-        }
-    }
-
-    fn from_retval(ctx: &pt_regs) -> Option<Self> {
-        unsafe { bpf_probe_read(&ctx.regs[0]).map(|v| v as *mut _).ok() }
+    fn rc_reg(&self) -> &Self::Reg {
+        // Return codes use libbpf's __PT_RC_REG (regs[2], which aliases MIPS $v0).
+        // https://github.com/torvalds/linux/blob/v6.17/tools/lib/bpf/bpf_tracing.h#L277-L279
+        &self.regs[2]
     }
 }
 
-#[cfg(bpf_target_arch = "loongarch64")]
-impl<T> FromPtRegs for *mut T {
-    fn from_argument(ctx: &pt_regs, n: usize) -> Option<Self> {
-        if n <= 7 {
-            unsafe { bpf_probe_read(&ctx.regs[4 + n]).map(|v| v as *mut _).ok() }
-        } else {
-            None
+#[cfg(bpf_target_arch = "powerpc64")]
+impl PtRegsLayout for pt_regs {
+    type Reg = crate::cty::c_ulong;
+
+    fn arg_reg(&self, index: usize) -> Option<&Self::Reg> {
+        // PowerPC64 arguments follow libbpf's __PT_PARM{1..8}_REG (gpr[3..10]).
+        // https://github.com/torvalds/linux/blob/v6.17/arch/powerpc/include/asm/ptrace.h#L28-L56
+        // https://github.com/torvalds/linux/blob/v6.17/tools/lib/bpf/bpf_tracing.h#L290-L308
+        match index {
+            0..=7 => Some(&self.gpr[3 + index]),
+            _ => None,
         }
     }
 
-    fn from_retval(ctx: &pt_regs) -> Option<Self> {
-        unsafe { bpf_probe_read(&ctx.regs[4]).map(|v| v as *mut _).ok() }
+    fn rc_reg(&self) -> &Self::Reg {
+        // Return codes use libbpf's __PT_RC_REG (gpr[3]).
+        // https://github.com/torvalds/linux/blob/v6.17/tools/lib/bpf/bpf_tracing.h#L311-L314
+        &self.gpr[3]
     }
 }
 
 #[cfg(bpf_target_arch = "riscv64")]
-impl<T> FromPtRegs for *mut T {
-    fn from_argument(ctx: &pt_regs, n: usize) -> Option<Self> {
-        match n {
-            0 => unsafe { bpf_probe_read(&ctx.a0).map(|v| v as *mut _).ok() },
-            1 => unsafe { bpf_probe_read(&ctx.a1).map(|v| v as *mut _).ok() },
-            2 => unsafe { bpf_probe_read(&ctx.a2).map(|v| v as *mut _).ok() },
-            3 => unsafe { bpf_probe_read(&ctx.a3).map(|v| v as *mut _).ok() },
-            4 => unsafe { bpf_probe_read(&ctx.a4).map(|v| v as *mut _).ok() },
-            5 => unsafe { bpf_probe_read(&ctx.a5).map(|v| v as *mut _).ok() },
-            6 => unsafe { bpf_probe_read(&ctx.a6).map(|v| v as *mut _).ok() },
-            7 => unsafe { bpf_probe_read(&ctx.a7).map(|v| v as *mut _).ok() },
+impl PtRegsLayout for pt_regs {
+    type Reg = crate::cty::c_ulong;
+
+    fn arg_reg(&self, index: usize) -> Option<&Self::Reg> {
+        // RISC-V arguments track libbpf's __PT_PARM{1..8}_REG (a0-a7).
+        // https://github.com/torvalds/linux/blob/v6.17/arch/riscv/include/asm/ptrace.h#L15-L55
+        // https://github.com/torvalds/linux/blob/v6.17/tools/lib/bpf/bpf_tracing.h#L360-L376
+        match index {
+            0 => Some(&self.a0),
+            1 => Some(&self.a1),
+            2 => Some(&self.a2),
+            3 => Some(&self.a3),
+            4 => Some(&self.a4),
+            5 => Some(&self.a5),
+            6 => Some(&self.a6),
+            7 => Some(&self.a7),
             _ => None,
         }
     }
 
-    fn from_retval(ctx: &pt_regs) -> Option<Self> {
-        unsafe { bpf_probe_read(&ctx.ra).map(|v| v as *mut _).ok() }
-    }
-}
-
-#[cfg(bpf_target_arch = "powerpc64")]
-impl<T> FromPtRegs for *mut T {
-    fn from_argument(ctx: &pt_regs, n: usize) -> Option<Self> {
-        if n <= 7 {
-            unsafe { bpf_probe_read(&ctx.gpr[3 + n]).map(|v| v as *mut _).ok() }
-        } else {
-            None
-        }
-    }
-
-    fn from_retval(ctx: &pt_regs) -> Option<Self> {
-        unsafe { bpf_probe_read(&ctx.gpr[3]).map(|v| v as *mut _).ok() }
+    fn rc_reg(&self) -> &Self::Reg {
+        // Return codes use libbpf's __PT_RC_REG (a0).
+        // https://github.com/torvalds/linux/blob/v6.17/tools/lib/bpf/bpf_tracing.h#L379-L382
+        &self.a0
     }
 }
 
 #[cfg(bpf_target_arch = "s390x")]
-impl<T> FromPtRegs for *mut T {
-    fn from_argument(ctx: &pt_regs, n: usize) -> Option<Self> {
-        if n <= 4 {
-            unsafe { bpf_probe_read(&ctx.gprs[2 + n]).map(|v| v as *mut _).ok() }
-        } else {
-            None
-        }
-    }
-
-    fn from_retval(ctx: &pt_regs) -> Option<Self> {
-        unsafe { bpf_probe_read(&ctx.gprs[2]).map(|v| v as *mut _).ok() }
-    }
-}
-
-#[cfg(bpf_target_arch = "mips")]
-impl<T> FromPtRegs for *mut T {
-    fn from_argument(ctx: &pt_regs, n: usize) -> Option<Self> {
-        // Assume N64 ABI like libbpf does.
-        if n <= 7 {
-            unsafe { bpf_probe_read(&ctx.regs[n + 4]).map(|v| v as *mut _).ok() }
-        } else {
-            None
+impl PtRegsLayout for pt_regs {
+    type Reg = crate::cty::c_ulong;
+
+    fn arg_reg(&self, index: usize) -> Option<&Self::Reg> {
+        // s390 arguments match libbpf's __PT_PARM{1..5}_REG (gprs[2..6]).
+        // https://github.com/torvalds/linux/blob/v6.17/arch/s390/include/asm/ptrace.h#L111-L131
+        // https://github.com/torvalds/linux/blob/v6.17/tools/lib/bpf/bpf_tracing.h#L170-L181
+        match index {
+            0..=4 => Some(&self.gprs[2 + index]),
+            _ => None,
         }
     }
 
-    fn from_retval(ctx: &pt_regs) -> Option<Self> {
-        unsafe { bpf_probe_read(&ctx.regs[31]).map(|v| v as *mut _).ok() }
+    fn rc_reg(&self) -> &Self::Reg {
+        // Return codes use libbpf's __PT_RC_REG (gprs[2]).
+        // https://github.com/torvalds/linux/blob/v6.17/tools/lib/bpf/bpf_tracing.h#L186-L188
+        &self.gprs[2]
     }
 }
 
-/// Helper macro to implement [`FromPtRegs`] for a primitive type.
-macro_rules! impl_from_pt_regs {
-    ($type:ident) => {
-        #[cfg(bpf_target_arch = "x86_64")]
-        impl FromPtRegs for $type {
-            fn from_argument(ctx: &pt_regs, n: usize) -> Option<Self> {
-                match n {
-                    0 => Some(ctx.rdi as *const $type as _),
-                    1 => Some(ctx.rsi as *const $type as _),
-                    2 => Some(ctx.rdx as *const $type as _),
-                    3 => Some(ctx.rcx as *const $type as _),
-                    4 => Some(ctx.r8 as *const $type as _),
-                    5 => Some(ctx.r9 as *const $type as _),
-                    _ => None,
-                }
-            }
-
-            fn from_retval(ctx: &pt_regs) -> Option<Self> {
-                Some(ctx.rax as *const $type as _)
-            }
-        }
-
-        #[cfg(bpf_target_arch = "arm")]
-        impl FromPtRegs for $type {
-            fn from_argument(ctx: &pt_regs, n: usize) -> Option<Self> {
-                if n <= 6 {
-                    Some(ctx.uregs[n] as *const $type as _)
-                } else {
-                    None
-                }
-            }
-
-            fn from_retval(ctx: &pt_regs) -> Option<Self> {
-                Some(ctx.uregs[0] as *const $type as _)
-            }
-        }
-
-        #[cfg(bpf_target_arch = "aarch64")]
-        impl FromPtRegs for $type {
-            fn from_argument(ctx: &pt_regs, n: usize) -> Option<Self> {
-                if n <= 7 {
-                    Some(ctx.regs[n] as *const $type as _)
-                } else {
-                    None
-                }
-            }
-
-            fn from_retval(ctx: &pt_regs) -> Option<Self> {
-                Some(ctx.regs[0] as *const $type as _)
-            }
-        }
-
-        #[cfg(bpf_target_arch = "loongarch64")]
-        impl FromPtRegs for $type {
-            fn from_argument(ctx: &pt_regs, n: usize) -> Option<Self> {
-                if n <= 7 {
-                    Some(ctx.regs[4 + n] as *const $type as _)
-                } else {
-                    None
-                }
-            }
-
-            fn from_retval(ctx: &pt_regs) -> Option<Self> {
-                Some(ctx.regs[4] as *const $type as _)
-            }
-        }
-
-        #[cfg(bpf_target_arch = "riscv64")]
-        impl FromPtRegs for $type {
-            fn from_argument(ctx: &pt_regs, n: usize) -> Option<Self> {
-                match n {
-                    0 => Some(ctx.a0 as *const $type as _),
-                    1 => Some(ctx.a1 as *const $type as _),
-                    2 => Some(ctx.a2 as *const $type as _),
-                    3 => Some(ctx.a3 as *const $type as _),
-                    4 => Some(ctx.a4 as *const $type as _),
-                    5 => Some(ctx.a5 as *const $type as _),
-                    6 => Some(ctx.a6 as *const $type as _),
-                    7 => Some(ctx.a7 as *const $type as _),
-                    _ => None,
-                }
-            }
-
-            fn from_retval(ctx: &pt_regs) -> Option<Self> {
-                Some(ctx.ra as *const $type as _)
-            }
-        }
-
-        #[cfg(bpf_target_arch = "powerpc64")]
-        impl FromPtRegs for $type {
-            fn from_argument(ctx: &pt_regs, n: usize) -> Option<Self> {
-                if n <= 7 {
-                    Some(ctx.gpr[3 + n] as *const $type as _)
-                } else {
-                    None
-                }
-            }
-
-            fn from_retval(ctx: &pt_regs) -> Option<Self> {
-                Some(ctx.gpr[3] as *const $type as _)
-            }
-        }
-
-        #[cfg(bpf_target_arch = "s390x")]
-        impl FromPtRegs for $type {
-            fn from_argument(ctx: &pt_regs, n: usize) -> Option<Self> {
-                if n <= 4 {
-                    Some(ctx.gprs[2 + n] as *const $type as _)
-                } else {
-                    None
-                }
-            }
-
-            fn from_retval(ctx: &pt_regs) -> Option<Self> {
-                Some(ctx.gprs[2] as *const $type as _)
-            }
-        }
-
-        #[cfg(bpf_target_arch = "mips")]
-        impl FromPtRegs for $type {
-            fn from_argument(ctx: &pt_regs, n: usize) -> Option<Self> {
-                if n <= 7 {
-                    Some(ctx.regs[n + 4] as *const $type as _)
-                } else {
-                    None
-                }
-            }
-
-            fn from_retval(ctx: &pt_regs) -> Option<Self> {
-                Some(ctx.regs[31] as *const $type as _)
-            }
+#[cfg(bpf_target_arch = "x86_64")]
+impl PtRegsLayout for pt_regs {
+    type Reg = crate::cty::c_ulong;
+
+    fn arg_reg(&self, index: usize) -> Option<&Self::Reg> {
+        // x86-64 arguments mirror libbpf's __PT_PARM{1..6}_REG mapping (rdi, rsi, rdx, rcx, r8, r9).
+        // https://github.com/torvalds/linux/blob/v6.17/arch/x86/include/asm/ptrace.h#L103-L155
+        // https://github.com/torvalds/linux/blob/v6.17/tools/lib/bpf/bpf_tracing.h#L134-L152
+        match index {
+            0 => Some(&self.rdi),
+            1 => Some(&self.rsi),
+            2 => Some(&self.rdx),
+            3 => Some(&self.rcx),
+            4 => Some(&self.r8),
+            5 => Some(&self.r9),
+            _ => None,
         }
-    };
-}
-
-impl_from_pt_regs!(u8);
-impl_from_pt_regs!(u16);
-impl_from_pt_regs!(u32);
-impl_from_pt_regs!(u64);
-impl_from_pt_regs!(i8);
-impl_from_pt_regs!(i16);
-impl_from_pt_regs!(i32);
-impl_from_pt_regs!(i64);
-impl_from_pt_regs!(usize);
-impl_from_pt_regs!(isize);
-
-/// A Rust wrapper on `bpf_raw_tracepoint_args`.
-pub struct RawTracepointArgs {
-    args: *mut bpf_raw_tracepoint_args,
-}
-
-impl RawTracepointArgs {
-    /// Creates a new instance of `RawTracepointArgs` from the given
-    /// `bpf_raw_tracepoint_args` raw pointer to allow easier access
-    /// to raw tracepoint argumetns.
-    pub fn new(args: *mut bpf_raw_tracepoint_args) -> Self {
-        Self { args }
     }
 
-    /// Returns the n-th argument of the raw tracepoint.
-    ///
-    /// # Safety
-    ///
-    /// This method is unsafe because it performs raw pointer conversion and makes assumptions
-    /// about the structure of the `bpf_raw_tracepoint_args` type. The tracepoint arguments are
-    /// represented as an array of `__u64` values. To be precise, the wrapped
-    /// `bpf_raw_tracepoint_args` binding defines it as `__IncompleteArrayField<__u64>` and the
-    /// original C type as `__u64 args[0]`. This method provides a way to access these arguments
-    /// conveniently in Rust using `__IncompleteArrayField<T>::as_slice` to represent that array
-    /// as a slice of length n and then retrieve the n-th element of it.
-    ///
-    /// However, the method does not check the total number of available arguments for a given
-    /// tracepoint and assumes that the slice has at least `n` elements, leading to undefined
-    /// behavior if this condition is not met. Such check is impossible to do, because the
-    /// tracepoint context doesn't contain any information about number of arguments.
-    ///
-    /// This method also cannot guarantee that the requested type matches the actual value type.
-    /// Wrong assumptions about types can lead to undefined behavior. The tracepoint context
-    /// doesn't provide any type information.
-    ///
-    /// The caller is responsible for ensuring they have accurate knowledge of the arguments
-    /// and their respective types for the accessed tracepoint context.
-    pub unsafe fn arg<T: FromRawTracepointArgs>(&self, n: usize) -> T {
-        unsafe { T::from_argument(&*self.args, n) }
+    fn rc_reg(&self) -> &Self::Reg {
+        // Return codes use libbpf's __PT_RC_REG (rax).
+        // https://github.com/torvalds/linux/blob/v6.17/tools/lib/bpf/bpf_tracing.h#L148-L152
+        &self.rax
     }
 }
 
-#[expect(clippy::missing_safety_doc)]
-pub unsafe trait FromRawTracepointArgs: Sized {
-    /// Returns the n-th argument of the raw tracepoint.
-    ///
-    /// # Safety
-    ///
-    /// This method is unsafe because it performs raw pointer conversion and makes assumptions
-    /// about the structure of the `bpf_raw_tracepoint_args` type. The tracepoint arguments are
-    /// represented as an array of `__u64` values. To be precise, the wrapped
-    /// `bpf_raw_tracepoint_args` binding defines it as `__IncompleteArrayField<__u64>` and the
-    /// original C type as `__u64 args[0]`. This method provides a way to access these arguments
-    /// conveniently in Rust using `__IncompleteArrayField<T>::as_slice` to represent that array
-    /// as a slice of length n and then retrieve the n-th element of it.
-    ///
-    /// However, the method does not check the total number of available arguments for a given
-    /// tracepoint and assumes that the slice has at least `n` elements, leading to undefined
-    /// behavior if this condition is not met. Such check is impossible to do, because the
-    /// tracepoint context doesn't contain any information about number of arguments.
-    ///
-    /// This method also cannot guarantee that the requested type matches the actual value type.
-    /// Wrong assumptions about types can lead to undefined behavior. The tracepoint context
-    /// doesn't provide any type information.
-    ///
-    /// The caller is responsible for ensuring they have accurate knowledge of the arguments
-    /// and their respective types for the accessed tracepoint context.
-    unsafe fn from_argument(ctx: &bpf_raw_tracepoint_args, n: usize) -> Self;
+/// Coerces a `T` from the `n`th argument of a pt_regs context where `n` starts
+/// at 0 and increases by 1 for each successive argument.
+pub(crate) fn arg<T: Argument>(ctx: &pt_regs, n: usize) -> Option<T> {
+    let reg = ctx.arg_reg(n)?;
+    #[allow(
+        clippy::cast_sign_loss,
+        clippy::unnecessary_cast,
+        trivial_numeric_casts
+    )]
+    Some(T::from_register((*reg) as u64))
 }
 
-unsafe impl<T> FromRawTracepointArgs for *const T {
-    unsafe fn from_argument(ctx: &bpf_raw_tracepoint_args, n: usize) -> *const T {
-        // Raw tracepoint arguments are exposed as `__u64 args[0]`.
-        // https://elixir.bootlin.com/linux/v6.5.5/source/include/uapi/linux/bpf.h#L6829
-        // They are represented as `__IncompleteArrayField<T>` in the Rust
-        // wraapper.
-        //
-        // The most convenient way of accessing such type in Rust is to use
-        // `__IncompleteArrayField<T>::as_slice` to represent that array as a
-        // slice of length n and then retrieve the n-th element of it.
-        //
-        // We don't know how many arguments are there for the given tracepoint,
-        // so we just assume that the slice has at least n elements. The whole
-        // assumntion and implementation is unsafe.
-        (unsafe { ctx.args.as_slice(n + 1) })[n] as _
-    }
+/// Coerces a `T` from the return value of a pt_regs context.
+pub(crate) fn ret<T: Argument>(ctx: &pt_regs) -> T {
+    let reg = ctx.rc_reg();
+    #[allow(
+        clippy::cast_sign_loss,
+        clippy::unnecessary_cast,
+        trivial_numeric_casts
+    )]
+    T::from_register((*reg) as u64)
 }
 
-macro_rules! unsafe_impl_from_raw_tracepoint_args {
-    ($type:ident) => {
-        unsafe impl FromRawTracepointArgs for $type {
-            #[allow(trivial_numeric_casts)]
-            unsafe fn from_argument(ctx: &bpf_raw_tracepoint_args, n: usize) -> Self {
-                (unsafe { ctx.args.as_slice(n + 1) })[n] as _
-            }
-        }
-    };
+/// Returns the n-th argument of the raw tracepoint.
+///
+/// # Safety
+///
+/// This method is unsafe because it performs raw pointer conversion and makes assumptions
+/// about the structure of the `bpf_raw_tracepoint_args` type. The tracepoint arguments are
+/// represented as an array of `__u64` values. To be precise, the wrapped
+/// `bpf_raw_tracepoint_args` binding defines it as `__IncompleteArrayField<__u64>` and the
+/// original C type as `__u64 args[0]`. This method provides a way to access these arguments
+/// conveniently in Rust using `__IncompleteArrayField<T>::as_slice` to represent that array
+/// as a slice of length n and then retrieve the n-th element of it.
+///
+/// However, the method does not check the total number of available arguments for a given
+/// tracepoint and assumes that the slice has at least `n` elements, leading to undefined
+/// behavior if this condition is not met. Such check is impossible to do, because the
+/// tracepoint context doesn't contain any information about number of arguments.
+///
+/// This method also cannot guarantee that the requested type matches the actual value type.
+/// Wrong assumptions about types can lead to undefined behavior. The tracepoint context
+/// doesn't provide any type information.
+///
+/// The caller is responsible for ensuring they have accurate knowledge of the arguments
+/// and their respective types for the accessed tracepoint context.
+pub(crate) fn raw_tracepoint_arg<T: Argument>(ctx: &bpf_raw_tracepoint_args, n: usize) -> T {
+    // Raw tracepoint arguments are exposed as `__u64 args[0]`.
+    // https://github.com/torvalds/linux/blob/v6.17/include/uapi/linux/bpf.h#L7231-L7233
+    // They are represented as `__IncompleteArrayField<T>` in the Rust
+    // wrapper.
+    //
+    // The most convenient way of accessing such type in Rust is to use
+    // `__IncompleteArrayField<T>::as_slice` to represent that array as a
+    // slice of length n and then retrieve the n-th element of it.
+    //
+    // We don't know how many arguments are there for the given tracepoint,
+    // so we just assume that the slice has at least n elements. The whole
+    // assumption and implementation is unsafe.
+    let ptr = ctx.args.as_ptr();
+    let ptr = unsafe { ptr.add(n) };
+    T::from_register(unsafe { *ptr })
 }
-
-unsafe_impl_from_raw_tracepoint_args!(u8);
-unsafe_impl_from_raw_tracepoint_args!(u16);
-unsafe_impl_from_raw_tracepoint_args!(u32);
-unsafe_impl_from_raw_tracepoint_args!(u64);
-unsafe_impl_from_raw_tracepoint_args!(i8);
-unsafe_impl_from_raw_tracepoint_args!(i16);
-unsafe_impl_from_raw_tracepoint_args!(i32);
-unsafe_impl_from_raw_tracepoint_args!(i64);
-unsafe_impl_from_raw_tracepoint_args!(usize);
-unsafe_impl_from_raw_tracepoint_args!(isize);

+ 1 - 1
ebpf/aya-ebpf/src/lib.rs

@@ -26,7 +26,7 @@
 pub use aya_ebpf_bindings::bindings;
 
 mod args;
-pub use args::{PtRegs, RawTracepointArgs};
+pub use args::Argument;
 pub mod btf_maps;
 #[expect(clippy::missing_safety_doc, unsafe_op_in_unsafe_fn)]
 pub mod helpers;

+ 3 - 4
ebpf/aya-ebpf/src/programs/fentry.rs

@@ -1,6 +1,6 @@
 use core::ffi::c_void;
 
-use crate::{EbpfContext, args::FromBtfArgument};
+use crate::{Argument, EbpfContext, args::btf_arg};
 
 pub struct FEntryContext {
     ctx: *mut c_void,
@@ -31,9 +31,8 @@ impl FEntryContext {
     ///     Ok(0)
     /// }
     /// ```
-    #[expect(clippy::missing_safety_doc)]
-    pub unsafe fn arg<T: FromBtfArgument>(&self, n: usize) -> T {
-        unsafe { T::from_argument(self.ctx.cast(), n) }
+    pub fn arg<T: Argument>(&self, n: usize) -> T {
+        btf_arg(self, n)
     }
 }
 

+ 3 - 4
ebpf/aya-ebpf/src/programs/fexit.rs

@@ -1,6 +1,6 @@
 use core::ffi::c_void;
 
-use crate::{EbpfContext, args::FromBtfArgument};
+use crate::{Argument, EbpfContext, args::btf_arg};
 
 pub struct FExitContext {
     ctx: *mut c_void,
@@ -31,9 +31,8 @@ impl FExitContext {
     ///     Ok(0)
     /// }
     /// ```
-    #[expect(clippy::missing_safety_doc)]
-    pub unsafe fn arg<T: FromBtfArgument>(&self, n: usize) -> T {
-        unsafe { T::from_argument(self.ctx.cast(), n) }
+    pub fn arg<T: Argument>(&self, n: usize) -> T {
+        btf_arg(self, n)
     }
 }
 

+ 3 - 9
ebpf/aya-ebpf/src/programs/lsm.rs

@@ -1,6 +1,6 @@
 use core::ffi::c_void;
 
-use crate::{EbpfContext, args::FromBtfArgument};
+use crate::{Argument, EbpfContext, args::btf_arg};
 
 pub struct LsmContext {
     ctx: *mut c_void,
@@ -22,12 +22,6 @@ impl LsmContext {
     /// this code path, or 0 if this is the first LSM program to be called. This phony
     /// argument is always last in the argument list.
     ///
-    /// # Safety
-    ///
-    /// This function is deeply unsafe, as we are reading raw pointers into kernel memory.
-    /// In particular, the value of `n` must not exceed the number of function arguments.
-    /// Luckily, the BPF verifier will catch this for us.
-    ///
     /// # Examples
     ///
     /// ```no_run
@@ -52,8 +46,8 @@ impl LsmContext {
     /// ```
     ///
     /// [1]: https://elixir.bootlin.com/linux/latest/source/include/linux/lsm_hook_defs.h
-    pub unsafe fn arg<T: FromBtfArgument>(&self, n: usize) -> T {
-        unsafe { T::from_argument(self.ctx.cast(), n) }
+    pub fn arg<T: Argument>(&self, n: usize) -> T {
+        btf_arg(self, n)
     }
 }
 

+ 3 - 3
ebpf/aya-ebpf/src/programs/probe.rs

@@ -15,7 +15,7 @@ use crate::bindings::pt_regs;
 use crate::bindings::user_pt_regs as pt_regs;
 #[cfg(bpf_target_arch = "riscv64")]
 use crate::bindings::user_regs_struct as pt_regs;
-use crate::{EbpfContext, args::FromPtRegs};
+use crate::{Argument, EbpfContext, args::arg};
 
 pub struct ProbeContext {
     pub regs: *mut pt_regs,
@@ -47,8 +47,8 @@ impl ProbeContext {
     ///     Ok(0)
     /// }
     /// ```
-    pub fn arg<T: FromPtRegs>(&self, n: usize) -> Option<T> {
-        T::from_argument(unsafe { &*self.regs }, n)
+    pub fn arg<T: Argument>(&self, n: usize) -> Option<T> {
+        arg(unsafe { &*self.regs }, n)
     }
 }
 

+ 3 - 4
ebpf/aya-ebpf/src/programs/raw_tracepoint.rs

@@ -1,6 +1,6 @@
 use core::ffi::c_void;
 
-use crate::{EbpfContext, args::FromRawTracepointArgs, bindings::bpf_raw_tracepoint_args};
+use crate::{Argument, EbpfContext, args::raw_tracepoint_arg, bindings::bpf_raw_tracepoint_args};
 
 pub struct RawTracePointContext {
     ctx: *mut bpf_raw_tracepoint_args,
@@ -11,9 +11,8 @@ impl RawTracePointContext {
         Self { ctx: ctx.cast() }
     }
 
-    #[expect(clippy::missing_safety_doc)]
-    pub unsafe fn arg<T: FromRawTracepointArgs>(&self, n: usize) -> T {
-        unsafe { T::from_argument(&*self.ctx, n) }
+    pub fn arg<T: Argument>(&self, n: usize) -> T {
+        raw_tracepoint_arg(unsafe { &*self.ctx }, n)
     }
 }
 

+ 4 - 4
ebpf/aya-ebpf/src/programs/retprobe.rs

@@ -15,7 +15,7 @@ use crate::bindings::pt_regs;
 use crate::bindings::user_pt_regs as pt_regs;
 #[cfg(bpf_target_arch = "riscv64")]
 use crate::bindings::user_regs_struct as pt_regs;
-use crate::{EbpfContext, args::FromPtRegs};
+use crate::{Argument, EbpfContext, args::ret};
 
 pub struct RetProbeContext {
     pub regs: *mut pt_regs,
@@ -34,15 +34,15 @@ impl RetProbeContext {
     /// # #![expect(dead_code)]
     /// # use aya_ebpf::{programs::RetProbeContext, cty::c_int};
     /// unsafe fn try_kretprobe_try_to_wake_up(ctx: RetProbeContext) -> Result<u32, u32> {
-    ///     let retval: c_int = ctx.ret().ok_or(1u32)?;
+    ///     let retval: c_int = ctx.ret();
     ///
     ///     // Do something with retval
     ///
     ///     Ok(0)
     /// }
     /// ```
-    pub fn ret<T: FromPtRegs>(&self) -> Option<T> {
-        T::from_retval(unsafe { &*self.regs })
+    pub fn ret<T: Argument>(&self) -> T {
+        ret(unsafe { &*self.regs })
     }
 }
 

+ 3 - 9
ebpf/aya-ebpf/src/programs/tp_btf.rs

@@ -1,6 +1,6 @@
 use core::ffi::c_void;
 
-use crate::{EbpfContext, args::FromBtfArgument};
+use crate::{Argument, EbpfContext, args::btf_arg};
 
 pub struct BtfTracePointContext {
     ctx: *mut c_void,
@@ -16,12 +16,6 @@ impl BtfTracePointContext {
     /// You can use the tplist tool provided by bcc to get a list of tracepoints and their
     /// arguments. TODO: document this better, possibly add a tplist alternative to aya.
     ///
-    /// # Safety
-    ///
-    /// This function is deeply unsafe, as we are reading raw pointers into kernel memory.
-    /// In particular, the value of `n` must not exceed the number of function arguments.
-    /// Luckily, the BPF verifier will catch this for us.
-    ///
     /// # Examples
     ///
     /// ```no_run
@@ -42,8 +36,8 @@ impl BtfTracePointContext {
     /// ```
     ///
     /// [1]: https://elixir.bootlin.com/linux/latest/source/include/linux/lsm_hook_defs.h
-    pub unsafe fn arg<T: FromBtfArgument>(&self, n: usize) -> T {
-        unsafe { T::from_argument(self.ctx.cast(), n) }
+    pub fn arg<T: Argument>(&self, n: usize) -> T {
+        btf_arg(self, n)
     }
 }
 

+ 2 - 2
test/integration-ebpf/src/raw_tracepoint.rs

@@ -16,8 +16,8 @@ static RESULT: Array<SysEnterEvent> = Array::with_max_entries(1, 0);
 
 #[raw_tracepoint(tracepoint = "sys_enter")]
 fn sys_enter(ctx: RawTracePointContext) -> i32 {
-    let common_type: u16 = unsafe { ctx.arg(0) };
-    let common_flags: u8 = unsafe { ctx.arg(1) };
+    let common_type: u16 = ctx.arg(0);
+    let common_flags: u8 = ctx.arg(1);
 
     if let Some(ptr) = RESULT.get_ptr_mut(0) {
         unsafe {

+ 16 - 68
xtask/public-api/aya-ebpf.txt

@@ -1603,7 +1603,7 @@ pub fn aya_ebpf::programs::device::DeviceContext::from(t: T) -> T
 pub mod aya_ebpf::programs::fentry
 pub struct aya_ebpf::programs::fentry::FEntryContext
 impl aya_ebpf::programs::fentry::FEntryContext
-pub unsafe fn aya_ebpf::programs::fentry::FEntryContext::arg<T: aya_ebpf::args::FromBtfArgument>(&self, n: usize) -> T
+pub fn aya_ebpf::programs::fentry::FEntryContext::arg<T: aya_ebpf::Argument>(&self, n: usize) -> T
 pub fn aya_ebpf::programs::fentry::FEntryContext::new(ctx: *mut core::ffi::c_void) -> Self
 impl aya_ebpf::EbpfContext for aya_ebpf::programs::fentry::FEntryContext
 pub fn aya_ebpf::programs::fentry::FEntryContext::as_ptr(&self) -> *mut core::ffi::c_void
@@ -1632,7 +1632,7 @@ pub fn aya_ebpf::programs::fentry::FEntryContext::from(t: T) -> T
 pub mod aya_ebpf::programs::fexit
 pub struct aya_ebpf::programs::fexit::FExitContext
 impl aya_ebpf::programs::fexit::FExitContext
-pub unsafe fn aya_ebpf::programs::fexit::FExitContext::arg<T: aya_ebpf::args::FromBtfArgument>(&self, n: usize) -> T
+pub fn aya_ebpf::programs::fexit::FExitContext::arg<T: aya_ebpf::Argument>(&self, n: usize) -> T
 pub fn aya_ebpf::programs::fexit::FExitContext::new(ctx: *mut core::ffi::c_void) -> Self
 impl aya_ebpf::EbpfContext for aya_ebpf::programs::fexit::FExitContext
 pub fn aya_ebpf::programs::fexit::FExitContext::as_ptr(&self) -> *mut core::ffi::c_void
@@ -1693,7 +1693,7 @@ pub fn aya_ebpf::programs::flow_dissector::FlowDissectorContext::from(t: T) -> T
 pub mod aya_ebpf::programs::lsm
 pub struct aya_ebpf::programs::lsm::LsmContext
 impl aya_ebpf::programs::lsm::LsmContext
-pub unsafe fn aya_ebpf::programs::lsm::LsmContext::arg<T: aya_ebpf::args::FromBtfArgument>(&self, n: usize) -> T
+pub fn aya_ebpf::programs::lsm::LsmContext::arg<T: aya_ebpf::Argument>(&self, n: usize) -> T
 pub fn aya_ebpf::programs::lsm::LsmContext::new(ctx: *mut core::ffi::c_void) -> Self
 impl aya_ebpf::EbpfContext for aya_ebpf::programs::lsm::LsmContext
 pub fn aya_ebpf::programs::lsm::LsmContext::as_ptr(&self) -> *mut core::ffi::c_void
@@ -1751,7 +1751,7 @@ pub mod aya_ebpf::programs::probe
 pub struct aya_ebpf::programs::probe::ProbeContext
 pub aya_ebpf::programs::probe::ProbeContext::regs: *mut aya_ebpf_bindings::x86_64::bindings::pt_regs
 impl aya_ebpf::programs::probe::ProbeContext
-pub fn aya_ebpf::programs::probe::ProbeContext::arg<T: aya_ebpf::args::FromPtRegs>(&self, n: usize) -> core::option::Option<T>
+pub fn aya_ebpf::programs::probe::ProbeContext::arg<T: aya_ebpf::Argument>(&self, n: usize) -> core::option::Option<T>
 pub fn aya_ebpf::programs::probe::ProbeContext::new(ctx: *mut core::ffi::c_void) -> Self
 impl aya_ebpf::EbpfContext for aya_ebpf::programs::probe::ProbeContext
 pub fn aya_ebpf::programs::probe::ProbeContext::as_ptr(&self) -> *mut core::ffi::c_void
@@ -1780,7 +1780,7 @@ pub fn aya_ebpf::programs::probe::ProbeContext::from(t: T) -> T
 pub mod aya_ebpf::programs::raw_tracepoint
 pub struct aya_ebpf::programs::raw_tracepoint::RawTracePointContext
 impl aya_ebpf::programs::raw_tracepoint::RawTracePointContext
-pub unsafe fn aya_ebpf::programs::raw_tracepoint::RawTracePointContext::arg<T: aya_ebpf::args::FromRawTracepointArgs>(&self, n: usize) -> T
+pub fn aya_ebpf::programs::raw_tracepoint::RawTracePointContext::arg<T: aya_ebpf::Argument>(&self, n: usize) -> T
 pub fn aya_ebpf::programs::raw_tracepoint::RawTracePointContext::new(ctx: *mut core::ffi::c_void) -> Self
 impl aya_ebpf::EbpfContext for aya_ebpf::programs::raw_tracepoint::RawTracePointContext
 pub fn aya_ebpf::programs::raw_tracepoint::RawTracePointContext::as_ptr(&self) -> *mut core::ffi::c_void
@@ -1811,7 +1811,7 @@ pub struct aya_ebpf::programs::retprobe::RetProbeContext
 pub aya_ebpf::programs::retprobe::RetProbeContext::regs: *mut aya_ebpf_bindings::x86_64::bindings::pt_regs
 impl aya_ebpf::programs::retprobe::RetProbeContext
 pub fn aya_ebpf::programs::retprobe::RetProbeContext::new(ctx: *mut core::ffi::c_void) -> Self
-pub fn aya_ebpf::programs::retprobe::RetProbeContext::ret<T: aya_ebpf::args::FromPtRegs>(&self) -> core::option::Option<T>
+pub fn aya_ebpf::programs::retprobe::RetProbeContext::ret<T: aya_ebpf::Argument>(&self) -> T
 impl aya_ebpf::EbpfContext for aya_ebpf::programs::retprobe::RetProbeContext
 pub fn aya_ebpf::programs::retprobe::RetProbeContext::as_ptr(&self) -> *mut core::ffi::c_void
 impl core::marker::Freeze for aya_ebpf::programs::retprobe::RetProbeContext
@@ -2197,7 +2197,7 @@ pub fn aya_ebpf::programs::tc::TcContext::from(t: T) -> T
 pub mod aya_ebpf::programs::tp_btf
 pub struct aya_ebpf::programs::tp_btf::BtfTracePointContext
 impl aya_ebpf::programs::tp_btf::BtfTracePointContext
-pub unsafe fn aya_ebpf::programs::tp_btf::BtfTracePointContext::arg<T: aya_ebpf::args::FromBtfArgument>(&self, n: usize) -> T
+pub fn aya_ebpf::programs::tp_btf::BtfTracePointContext::arg<T: aya_ebpf::Argument>(&self, n: usize) -> T
 pub fn aya_ebpf::programs::tp_btf::BtfTracePointContext::new(ctx: *mut core::ffi::c_void) -> Self
 impl aya_ebpf::EbpfContext for aya_ebpf::programs::tp_btf::BtfTracePointContext
 pub fn aya_ebpf::programs::tp_btf::BtfTracePointContext::as_ptr(&self) -> *mut core::ffi::c_void
@@ -2289,7 +2289,7 @@ impl<T> core::convert::From<T> for aya_ebpf::programs::xdp::XdpContext
 pub fn aya_ebpf::programs::xdp::XdpContext::from(t: T) -> T
 pub struct aya_ebpf::programs::BtfTracePointContext
 impl aya_ebpf::programs::tp_btf::BtfTracePointContext
-pub unsafe fn aya_ebpf::programs::tp_btf::BtfTracePointContext::arg<T: aya_ebpf::args::FromBtfArgument>(&self, n: usize) -> T
+pub fn aya_ebpf::programs::tp_btf::BtfTracePointContext::arg<T: aya_ebpf::Argument>(&self, n: usize) -> T
 pub fn aya_ebpf::programs::tp_btf::BtfTracePointContext::new(ctx: *mut core::ffi::c_void) -> Self
 impl aya_ebpf::EbpfContext for aya_ebpf::programs::tp_btf::BtfTracePointContext
 pub fn aya_ebpf::programs::tp_btf::BtfTracePointContext::as_ptr(&self) -> *mut core::ffi::c_void
@@ -2345,7 +2345,7 @@ impl<T> core::convert::From<T> for aya_ebpf::programs::device::DeviceContext
 pub fn aya_ebpf::programs::device::DeviceContext::from(t: T) -> T
 pub struct aya_ebpf::programs::FEntryContext
 impl aya_ebpf::programs::fentry::FEntryContext
-pub unsafe fn aya_ebpf::programs::fentry::FEntryContext::arg<T: aya_ebpf::args::FromBtfArgument>(&self, n: usize) -> T
+pub fn aya_ebpf::programs::fentry::FEntryContext::arg<T: aya_ebpf::Argument>(&self, n: usize) -> T
 pub fn aya_ebpf::programs::fentry::FEntryContext::new(ctx: *mut core::ffi::c_void) -> Self
 impl aya_ebpf::EbpfContext for aya_ebpf::programs::fentry::FEntryContext
 pub fn aya_ebpf::programs::fentry::FEntryContext::as_ptr(&self) -> *mut core::ffi::c_void
@@ -2373,7 +2373,7 @@ impl<T> core::convert::From<T> for aya_ebpf::programs::fentry::FEntryContext
 pub fn aya_ebpf::programs::fentry::FEntryContext::from(t: T) -> T
 pub struct aya_ebpf::programs::FExitContext
 impl aya_ebpf::programs::fexit::FExitContext
-pub unsafe fn aya_ebpf::programs::fexit::FExitContext::arg<T: aya_ebpf::args::FromBtfArgument>(&self, n: usize) -> T
+pub fn aya_ebpf::programs::fexit::FExitContext::arg<T: aya_ebpf::Argument>(&self, n: usize) -> T
 pub fn aya_ebpf::programs::fexit::FExitContext::new(ctx: *mut core::ffi::c_void) -> Self
 impl aya_ebpf::EbpfContext for aya_ebpf::programs::fexit::FExitContext
 pub fn aya_ebpf::programs::fexit::FExitContext::as_ptr(&self) -> *mut core::ffi::c_void
@@ -2432,7 +2432,7 @@ impl<T> core::convert::From<T> for aya_ebpf::programs::flow_dissector::FlowDisse
 pub fn aya_ebpf::programs::flow_dissector::FlowDissectorContext::from(t: T) -> T
 pub struct aya_ebpf::programs::LsmContext
 impl aya_ebpf::programs::lsm::LsmContext
-pub unsafe fn aya_ebpf::programs::lsm::LsmContext::arg<T: aya_ebpf::args::FromBtfArgument>(&self, n: usize) -> T
+pub fn aya_ebpf::programs::lsm::LsmContext::arg<T: aya_ebpf::Argument>(&self, n: usize) -> T
 pub fn aya_ebpf::programs::lsm::LsmContext::new(ctx: *mut core::ffi::c_void) -> Self
 impl aya_ebpf::EbpfContext for aya_ebpf::programs::lsm::LsmContext
 pub fn aya_ebpf::programs::lsm::LsmContext::as_ptr(&self) -> *mut core::ffi::c_void
@@ -2488,7 +2488,7 @@ pub fn aya_ebpf::programs::perf_event::PerfEventContext::from(t: T) -> T
 pub struct aya_ebpf::programs::ProbeContext
 pub aya_ebpf::programs::ProbeContext::regs: *mut aya_ebpf_bindings::x86_64::bindings::pt_regs
 impl aya_ebpf::programs::probe::ProbeContext
-pub fn aya_ebpf::programs::probe::ProbeContext::arg<T: aya_ebpf::args::FromPtRegs>(&self, n: usize) -> core::option::Option<T>
+pub fn aya_ebpf::programs::probe::ProbeContext::arg<T: aya_ebpf::Argument>(&self, n: usize) -> core::option::Option<T>
 pub fn aya_ebpf::programs::probe::ProbeContext::new(ctx: *mut core::ffi::c_void) -> Self
 impl aya_ebpf::EbpfContext for aya_ebpf::programs::probe::ProbeContext
 pub fn aya_ebpf::programs::probe::ProbeContext::as_ptr(&self) -> *mut core::ffi::c_void
@@ -2516,7 +2516,7 @@ impl<T> core::convert::From<T> for aya_ebpf::programs::probe::ProbeContext
 pub fn aya_ebpf::programs::probe::ProbeContext::from(t: T) -> T
 pub struct aya_ebpf::programs::RawTracePointContext
 impl aya_ebpf::programs::raw_tracepoint::RawTracePointContext
-pub unsafe fn aya_ebpf::programs::raw_tracepoint::RawTracePointContext::arg<T: aya_ebpf::args::FromRawTracepointArgs>(&self, n: usize) -> T
+pub fn aya_ebpf::programs::raw_tracepoint::RawTracePointContext::arg<T: aya_ebpf::Argument>(&self, n: usize) -> T
 pub fn aya_ebpf::programs::raw_tracepoint::RawTracePointContext::new(ctx: *mut core::ffi::c_void) -> Self
 impl aya_ebpf::EbpfContext for aya_ebpf::programs::raw_tracepoint::RawTracePointContext
 pub fn aya_ebpf::programs::raw_tracepoint::RawTracePointContext::as_ptr(&self) -> *mut core::ffi::c_void
@@ -2546,7 +2546,7 @@ pub struct aya_ebpf::programs::RetProbeContext
 pub aya_ebpf::programs::RetProbeContext::regs: *mut aya_ebpf_bindings::x86_64::bindings::pt_regs
 impl aya_ebpf::programs::retprobe::RetProbeContext
 pub fn aya_ebpf::programs::retprobe::RetProbeContext::new(ctx: *mut core::ffi::c_void) -> Self
-pub fn aya_ebpf::programs::retprobe::RetProbeContext::ret<T: aya_ebpf::args::FromPtRegs>(&self) -> core::option::Option<T>
+pub fn aya_ebpf::programs::retprobe::RetProbeContext::ret<T: aya_ebpf::Argument>(&self) -> T
 impl aya_ebpf::EbpfContext for aya_ebpf::programs::retprobe::RetProbeContext
 pub fn aya_ebpf::programs::retprobe::RetProbeContext::as_ptr(&self) -> *mut core::ffi::c_void
 impl core::marker::Freeze for aya_ebpf::programs::retprobe::RetProbeContext
@@ -2935,61 +2935,9 @@ impl<T> core::convert::From<T> for aya_ebpf::programs::xdp::XdpContext
 pub fn aya_ebpf::programs::xdp::XdpContext::from(t: T) -> T
 pub macro aya_ebpf::bpf_printk!
 pub macro aya_ebpf::btf_map_def!
-pub struct aya_ebpf::PtRegs
-impl aya_ebpf::PtRegs
-pub fn aya_ebpf::PtRegs::arg<T: aya_ebpf::args::FromPtRegs>(&self, n: usize) -> core::option::Option<T>
-pub fn aya_ebpf::PtRegs::as_ptr(&self) -> *mut aya_ebpf_bindings::x86_64::bindings::pt_regs
-pub fn aya_ebpf::PtRegs::new(regs: *mut aya_ebpf_bindings::x86_64::bindings::pt_regs) -> Self
-pub fn aya_ebpf::PtRegs::ret<T: aya_ebpf::args::FromPtRegs>(&self) -> core::option::Option<T>
-impl core::marker::Freeze for aya_ebpf::PtRegs
-impl !core::marker::Send for aya_ebpf::PtRegs
-impl !core::marker::Sync for aya_ebpf::PtRegs
-impl core::marker::Unpin for aya_ebpf::PtRegs
-impl core::panic::unwind_safe::RefUnwindSafe for aya_ebpf::PtRegs
-impl core::panic::unwind_safe::UnwindSafe for aya_ebpf::PtRegs
-impl<T, U> core::convert::Into<U> for aya_ebpf::PtRegs where U: core::convert::From<T>
-pub fn aya_ebpf::PtRegs::into(self) -> U
-impl<T, U> core::convert::TryFrom<U> for aya_ebpf::PtRegs where U: core::convert::Into<T>
-pub type aya_ebpf::PtRegs::Error = core::convert::Infallible
-pub fn aya_ebpf::PtRegs::try_from(value: U) -> core::result::Result<T, <T as core::convert::TryFrom<U>>::Error>
-impl<T, U> core::convert::TryInto<U> for aya_ebpf::PtRegs where U: core::convert::TryFrom<T>
-pub type aya_ebpf::PtRegs::Error = <U as core::convert::TryFrom<T>>::Error
-pub fn aya_ebpf::PtRegs::try_into(self) -> core::result::Result<U, <U as core::convert::TryFrom<T>>::Error>
-impl<T> core::any::Any for aya_ebpf::PtRegs where T: 'static + ?core::marker::Sized
-pub fn aya_ebpf::PtRegs::type_id(&self) -> core::any::TypeId
-impl<T> core::borrow::Borrow<T> for aya_ebpf::PtRegs where T: ?core::marker::Sized
-pub fn aya_ebpf::PtRegs::borrow(&self) -> &T
-impl<T> core::borrow::BorrowMut<T> for aya_ebpf::PtRegs where T: ?core::marker::Sized
-pub fn aya_ebpf::PtRegs::borrow_mut(&mut self) -> &mut T
-impl<T> core::convert::From<T> for aya_ebpf::PtRegs
-pub fn aya_ebpf::PtRegs::from(t: T) -> T
-pub struct aya_ebpf::RawTracepointArgs
-impl aya_ebpf::RawTracepointArgs
-pub unsafe fn aya_ebpf::RawTracepointArgs::arg<T: aya_ebpf::args::FromRawTracepointArgs>(&self, n: usize) -> T
-pub fn aya_ebpf::RawTracepointArgs::new(args: *mut aya_ebpf_bindings::x86_64::bindings::bpf_raw_tracepoint_args) -> Self
-impl core::marker::Freeze for aya_ebpf::RawTracepointArgs
-impl !core::marker::Send for aya_ebpf::RawTracepointArgs
-impl !core::marker::Sync for aya_ebpf::RawTracepointArgs
-impl core::marker::Unpin for aya_ebpf::RawTracepointArgs
-impl core::panic::unwind_safe::RefUnwindSafe for aya_ebpf::RawTracepointArgs
-impl core::panic::unwind_safe::UnwindSafe for aya_ebpf::RawTracepointArgs
-impl<T, U> core::convert::Into<U> for aya_ebpf::RawTracepointArgs where U: core::convert::From<T>
-pub fn aya_ebpf::RawTracepointArgs::into(self) -> U
-impl<T, U> core::convert::TryFrom<U> for aya_ebpf::RawTracepointArgs where U: core::convert::Into<T>
-pub type aya_ebpf::RawTracepointArgs::Error = core::convert::Infallible
-pub fn aya_ebpf::RawTracepointArgs::try_from(value: U) -> core::result::Result<T, <T as core::convert::TryFrom<U>>::Error>
-impl<T, U> core::convert::TryInto<U> for aya_ebpf::RawTracepointArgs where U: core::convert::TryFrom<T>
-pub type aya_ebpf::RawTracepointArgs::Error = <U as core::convert::TryFrom<T>>::Error
-pub fn aya_ebpf::RawTracepointArgs::try_into(self) -> core::result::Result<U, <U as core::convert::TryFrom<T>>::Error>
-impl<T> core::any::Any for aya_ebpf::RawTracepointArgs where T: 'static + ?core::marker::Sized
-pub fn aya_ebpf::RawTracepointArgs::type_id(&self) -> core::any::TypeId
-impl<T> core::borrow::Borrow<T> for aya_ebpf::RawTracepointArgs where T: ?core::marker::Sized
-pub fn aya_ebpf::RawTracepointArgs::borrow(&self) -> &T
-impl<T> core::borrow::BorrowMut<T> for aya_ebpf::RawTracepointArgs where T: ?core::marker::Sized
-pub fn aya_ebpf::RawTracepointArgs::borrow_mut(&mut self) -> &mut T
-impl<T> core::convert::From<T> for aya_ebpf::RawTracepointArgs
-pub fn aya_ebpf::RawTracepointArgs::from(t: T) -> T
 pub const aya_ebpf::TASK_COMM_LEN: usize
+pub trait aya_ebpf::Argument: aya_ebpf::args::sealed::Argument
+impl<T: aya_ebpf::args::sealed::Argument> aya_ebpf::Argument for T
 pub trait aya_ebpf::EbpfContext
 pub fn aya_ebpf::EbpfContext::as_ptr(&self) -> *mut aya_ebpf_cty::c_void
 pub fn aya_ebpf::EbpfContext::command(&self) -> core::result::Result<[u8; 16], aya_ebpf_cty::od::c_long>