From 7f25956aea94a2267dd721d94ad51625e109fe7f Mon Sep 17 00:00:00 2001
From: Tamir Duberstein <tamird@gmail.com>
Date: Sun, 9 Jul 2023 19:46:04 -0400
Subject: [PATCH 1/7] Add missing test annotation

---
 aya-log-common/src/lib.rs | 1 +
 1 file changed, 1 insertion(+)

diff --git a/aya-log-common/src/lib.rs b/aya-log-common/src/lib.rs
index ac11efdd4..ec2cd2b32 100644
--- a/aya-log-common/src/lib.rs
+++ b/aya-log-common/src/lib.rs
@@ -295,6 +295,7 @@ pub fn write_record_header(
 mod test {
     use super::*;
 
+    #[test]
     fn log_value_length_sufficient() {
         assert!(
             LOG_BUF_CAPACITY >= LogValueLength::MAX.into(),

From b611038d5b41a45ca70553550dbdef9aa1fd117c Mon Sep 17 00:00:00 2001
From: Tamir Duberstein <tamird@gmail.com>
Date: Sun, 9 Jul 2023 16:45:47 -0400
Subject: [PATCH 2/7] Use procfs crate for kernel version parsing

This allows the logic to be shared between aya and the integration tests
without exposing additional public API surface.
---
 aya-obj/src/obj.rs                    |  70 +++++--------
 aya/Cargo.toml                        |  26 +++--
 aya/src/maps/mod.rs                   |  30 +++---
 aya/src/programs/cgroup_device.rs     |   7 +-
 aya/src/programs/cgroup_skb.rs        |   7 +-
 aya/src/programs/cgroup_sock.rs       |   7 +-
 aya/src/programs/cgroup_sock_addr.rs  |   7 +-
 aya/src/programs/cgroup_sockopt.rs    |   7 +-
 aya/src/programs/cgroup_sysctl.rs     |   7 +-
 aya/src/programs/mod.rs               |  18 ++--
 aya/src/programs/probe.rs             |   6 +-
 aya/src/programs/xdp.rs               |  13 +--
 aya/src/sys/bpf.rs                    |  13 ++-
 aya/src/sys/mod.rs                    | 143 ++++----------------------
 test/integration-test/Cargo.toml      |   2 +-
 test/integration-test/tests/common.rs |  23 -----
 test/integration-test/tests/load.rs   |   8 +-
 test/integration-test/tests/smoke.rs  |  14 +--
 18 files changed, 137 insertions(+), 271 deletions(-)
 delete mode 100644 test/integration-test/tests/common.rs

diff --git a/aya-obj/src/obj.rs b/aya-obj/src/obj.rs
index e9093e628..e5e2f3ab9 100644
--- a/aya-obj/src/obj.rs
+++ b/aya-obj/src/obj.rs
@@ -109,7 +109,7 @@ pub struct Object {
     /// Program license
     pub license: CString,
     /// Kernel version
-    pub kernel_version: KernelVersion,
+    pub kernel_version: Option<u32>,
     /// Program BTF
     pub btf: Option<Btf>,
     /// Program BTF.ext
@@ -135,7 +135,7 @@ pub struct Program {
     /// The license
     pub license: CString,
     /// The kernel version
-    pub kernel_version: KernelVersion,
+    pub kernel_version: Option<u32>,
     /// The section containing the program
     pub section: ProgramSection,
     /// The section index of the program
@@ -579,7 +579,7 @@ impl Object {
         let kernel_version = if let Some(section) = obj.section_by_name("version") {
             parse_version(Section::try_from(&section)?.data, endianness)?
         } else {
-            KernelVersion::Any
+            None
         };
 
         let mut bpf_obj = Object::new(endianness, license, kernel_version);
@@ -631,7 +631,7 @@ impl Object {
         Ok(bpf_obj)
     }
 
-    fn new(endianness: Endianness, license: CString, kernel_version: KernelVersion) -> Object {
+    fn new(endianness: Endianness, license: CString, kernel_version: Option<u32>) -> Object {
         Object {
             endianness,
             license,
@@ -1256,7 +1256,7 @@ fn parse_license(data: &[u8]) -> Result<CString, ParseError> {
         .to_owned())
 }
 
-fn parse_version(data: &[u8], endianness: object::Endianness) -> Result<KernelVersion, ParseError> {
+fn parse_version(data: &[u8], endianness: object::Endianness) -> Result<Option<u32>, ParseError> {
     let data = match data.len() {
         4 => data.try_into().unwrap(),
         _ => {
@@ -1271,9 +1271,10 @@ fn parse_version(data: &[u8], endianness: object::Endianness) -> Result<KernelVe
         object::Endianness::Little => u32::from_le_bytes(data),
     };
 
-    Ok(match v {
-        KERNEL_VERSION_ANY => KernelVersion::Any,
-        v => KernelVersion::Version(v),
+    Ok(if v == KERNEL_VERSION_ANY {
+        None
+    } else {
+        Some(v)
     })
 }
 
@@ -1301,24 +1302,6 @@ fn get_map_field(btf: &Btf, type_id: u32) -> Result<u32, BtfError> {
     Ok(arr.len)
 }
 
-/// The parsed kernel version
-#[derive(Copy, Clone, Debug, PartialEq, Eq)]
-pub enum KernelVersion {
-    /// Specified version
-    Version(u32),
-    /// Any version
-    Any,
-}
-
-impl From<KernelVersion> for u32 {
-    fn from(version: KernelVersion) -> u32 {
-        match version {
-            KernelVersion::Any => KERNEL_VERSION_ANY,
-            KernelVersion::Version(v) => v,
-        }
-    }
-}
-
 // Parsed '.bss' '.data' and '.rodata' sections. These sections are arrays of
 // bytes and are relocated based on their section index.
 fn parse_data_map_section(section: &Section) -> Result<Map, ParseError> {
@@ -1592,23 +1575,20 @@ mod tests {
             Err(ParseError::InvalidKernelVersion { .. })
         ));
 
-        assert_eq!(
-            parse_version(&0xFFFF_FFFEu32.to_le_bytes(), Endianness::Little)
-                .expect("failed to parse magic version"),
-            KernelVersion::Any
-        );
+        assert!(matches!(
+            parse_version(&0xFFFF_FFFEu32.to_le_bytes(), Endianness::Little),
+            Ok(None)
+        ));
 
-        assert_eq!(
-            parse_version(&0xFFFF_FFFEu32.to_be_bytes(), Endianness::Big)
-                .expect("failed to parse magic version"),
-            KernelVersion::Any
-        );
+        assert!(matches!(
+            parse_version(&0xFFFF_FFFEu32.to_be_bytes(), Endianness::Big),
+            Ok(None)
+        ));
 
-        assert_eq!(
-            parse_version(&1234u32.to_le_bytes(), Endianness::Little)
-                .expect("failed to parse magic version"),
-            KernelVersion::Version(1234)
-        );
+        assert!(matches!(
+            parse_version(&1234u32.to_le_bytes(), Endianness::Little),
+            Ok(Some(1234))
+        ));
     }
 
     #[test]
@@ -1699,11 +1679,7 @@ mod tests {
     }
 
     fn fake_obj() -> Object {
-        Object::new(
-            Endianness::Little,
-            CString::new("GPL").unwrap(),
-            KernelVersion::Any,
-        )
+        Object::new(Endianness::Little, CString::new("GPL").unwrap(), None)
     }
 
     #[test]
@@ -1753,7 +1729,7 @@ mod tests {
             obj.parse_program(&fake_section(BpfSectionKind::Program,"kprobe/foo", bytes_of(&fake_ins()))),
             Ok((Program {
                 license,
-                kernel_version: KernelVersion::Any,
+                kernel_version: None,
                 section: ProgramSection::KProbe { .. },
                 .. }, Function {
                     name,
diff --git a/aya/Cargo.toml b/aya/Cargo.toml
index f12ebf278..2abcfc475 100644
--- a/aya/Cargo.toml
+++ b/aya/Cargo.toml
@@ -11,21 +11,31 @@ documentation = "https://docs.rs/aya"
 edition = "2021"
 
 [dependencies]
-libc = { version = "0.2.105" }
+async-io = { version = "1.3", optional = true }
 aya-obj = { path = "../aya-obj", version = "0.1.0", features = ["std"] }
-thiserror = "1"
-object = { version = "0.31", default-features = false, features = ["std", "read_core", "elf"] }
 bitflags = "2.2.1"
 bytes = "1"
 lazy_static = "1"
-parking_lot = { version = "0.12.0", features = ["send_guard"] }
-tokio = { version = "1.24.0", features = ["macros", "rt", "rt-multi-thread", "net"], optional = true }
-async-io = { version = "1.3", optional = true }
+libc = { version = "0.2.105" }
 log = "0.4"
+object = { version = "0.31", default-features = false, features = [
+    "std",
+    "read_core",
+    "elf",
+] }
+parking_lot = { version = "0.12.0", features = ["send_guard"] }
+thiserror = "1"
+tokio = { version = "1.24.0", features = [
+    "macros",
+    "rt",
+    "rt-multi-thread",
+    "net",
+], optional = true }
+procfs = { version = "0.15.1", default-features = false }
 
 [dev-dependencies]
-matches = "0.1.8"
 futures = { version = "0.3.12", default-features = false, features = ["std"] }
+matches = "0.1.8"
 
 [features]
 default = []
@@ -35,4 +45,4 @@ async_std = ["async-io", "async"]
 
 [package.metadata.docs.rs]
 all-features = true
-rustdoc-args = ["--cfg", "docsrs","-D", "warnings"]
+rustdoc-args = ["--cfg", "docsrs", "-D", "warnings"]
diff --git a/aya/src/maps/mod.rs b/aya/src/maps/mod.rs
index f2b7a6a58..3961c212b 100644
--- a/aya/src/maps/mod.rs
+++ b/aya/src/maps/mod.rs
@@ -49,6 +49,7 @@ use std::{
 
 use libc::{getrlimit, rlimit, RLIMIT_MEMLOCK, RLIM_INFINITY};
 use log::warn;
+use procfs::KernelVersion;
 use thiserror::Error;
 
 use crate::{
@@ -56,7 +57,7 @@ use crate::{
     pin::PinError,
     sys::{
         bpf_create_map, bpf_get_object, bpf_map_get_info_by_fd, bpf_map_get_next_key,
-        bpf_pin_object, kernel_version,
+        bpf_pin_object,
     },
     util::nr_cpus,
     PinningType, Pod,
@@ -489,18 +490,23 @@ impl MapData {
 
         let c_name = CString::new(name).map_err(|_| MapError::InvalidName { name: name.into() })?;
 
-        let fd = bpf_create_map(&c_name, &self.obj, self.btf_fd).map_err(|(code, io_error)| {
-            let k_ver = kernel_version().unwrap();
-            if k_ver < (5, 11, 0) {
-                maybe_warn_rlimit();
-            }
+        #[cfg(not(test))]
+        let kernel_version = KernelVersion::current().unwrap();
+        #[cfg(test)]
+        let kernel_version = KernelVersion::new(0xff, 0xff, 0xff);
+        let fd = bpf_create_map(&c_name, &self.obj, self.btf_fd, kernel_version).map_err(
+            |(code, io_error)| {
+                if kernel_version < KernelVersion::new(5, 11, 0) {
+                    maybe_warn_rlimit();
+                }
 
-            MapError::CreateError {
-                name: name.into(),
-                code,
-                io_error,
-            }
-        })? as RawFd;
+                MapError::CreateError {
+                    name: name.into(),
+                    code,
+                    io_error,
+                }
+            },
+        )? as RawFd;
 
         self.fd = Some(fd);
 
diff --git a/aya/src/programs/cgroup_device.rs b/aya/src/programs/cgroup_device.rs
index ad211b83b..b1a8e39db 100644
--- a/aya/src/programs/cgroup_device.rs
+++ b/aya/src/programs/cgroup_device.rs
@@ -1,4 +1,6 @@
 //! Cgroup device programs.
+
+use procfs::KernelVersion;
 use std::os::fd::{AsRawFd, RawFd};
 
 use crate::{
@@ -6,7 +8,7 @@ use crate::{
     programs::{
         define_link_wrapper, load_program, FdLink, Link, ProgAttachLink, ProgramData, ProgramError,
     },
-    sys::{bpf_link_create, bpf_prog_attach, kernel_version},
+    sys::{bpf_link_create, bpf_prog_attach},
 };
 
 /// A program used to watch or prevent device interaction from a cgroup.
@@ -62,8 +64,7 @@ impl CgroupDevice {
         let prog_fd = self.data.fd_or_err()?;
         let cgroup_fd = cgroup.as_raw_fd();
 
-        let k_ver = kernel_version().unwrap();
-        if k_ver >= (5, 7, 0) {
+        if KernelVersion::current().unwrap() >= KernelVersion::new(5, 7, 0) {
             let link_fd = bpf_link_create(prog_fd, cgroup_fd, BPF_CGROUP_DEVICE, None, 0).map_err(
                 |(_, io_error)| ProgramError::SyscallError {
                     call: "bpf_link_create",
diff --git a/aya/src/programs/cgroup_skb.rs b/aya/src/programs/cgroup_skb.rs
index ed92fd2c9..f275441f3 100644
--- a/aya/src/programs/cgroup_skb.rs
+++ b/aya/src/programs/cgroup_skb.rs
@@ -1,4 +1,6 @@
 //! Cgroup skb programs.
+
+use procfs::KernelVersion;
 use std::{
     hash::Hash,
     os::fd::{AsRawFd, RawFd},
@@ -13,7 +15,7 @@ use crate::{
     programs::{
         define_link_wrapper, load_program, FdLink, Link, ProgAttachLink, ProgramData, ProgramError,
     },
-    sys::{bpf_link_create, bpf_prog_attach, kernel_version},
+    sys::{bpf_link_create, bpf_prog_attach},
 };
 
 /// A program used to inspect or filter network activity for a given cgroup.
@@ -96,8 +98,7 @@ impl CgroupSkb {
             CgroupSkbAttachType::Ingress => BPF_CGROUP_INET_INGRESS,
             CgroupSkbAttachType::Egress => BPF_CGROUP_INET_EGRESS,
         };
-        let k_ver = kernel_version().unwrap();
-        if k_ver >= (5, 7, 0) {
+        if KernelVersion::current().unwrap() >= KernelVersion::new(5, 7, 0) {
             let link_fd = bpf_link_create(prog_fd, cgroup_fd, attach_type, None, 0).map_err(
                 |(_, io_error)| ProgramError::SyscallError {
                     call: "bpf_link_create",
diff --git a/aya/src/programs/cgroup_sock.rs b/aya/src/programs/cgroup_sock.rs
index a1c321793..bddea214c 100644
--- a/aya/src/programs/cgroup_sock.rs
+++ b/aya/src/programs/cgroup_sock.rs
@@ -1,6 +1,8 @@
 //! Cgroup socket programs.
+
 pub use aya_obj::programs::CgroupSockAttachType;
 
+use procfs::KernelVersion;
 use std::{
     hash::Hash,
     os::fd::{AsRawFd, RawFd},
@@ -12,7 +14,7 @@ use crate::{
     programs::{
         define_link_wrapper, load_program, FdLink, Link, ProgAttachLink, ProgramData, ProgramError,
     },
-    sys::{bpf_link_create, bpf_prog_attach, kernel_version},
+    sys::{bpf_link_create, bpf_prog_attach},
 };
 
 /// A program that is called on socket creation, bind and release.
@@ -71,8 +73,7 @@ impl CgroupSock {
         let prog_fd = self.data.fd_or_err()?;
         let cgroup_fd = cgroup.as_raw_fd();
         let attach_type = self.data.expected_attach_type.unwrap();
-        let k_ver = kernel_version().unwrap();
-        if k_ver >= (5, 7, 0) {
+        if KernelVersion::current().unwrap() >= KernelVersion::new(5, 7, 0) {
             let link_fd = bpf_link_create(prog_fd, cgroup_fd, attach_type, None, 0).map_err(
                 |(_, io_error)| ProgramError::SyscallError {
                     call: "bpf_link_create",
diff --git a/aya/src/programs/cgroup_sock_addr.rs b/aya/src/programs/cgroup_sock_addr.rs
index de9a48a20..555a7c954 100644
--- a/aya/src/programs/cgroup_sock_addr.rs
+++ b/aya/src/programs/cgroup_sock_addr.rs
@@ -1,6 +1,8 @@
 //! Cgroup socket address programs.
+
 pub use aya_obj::programs::CgroupSockAddrAttachType;
 
+use procfs::KernelVersion;
 use std::{
     hash::Hash,
     os::fd::{AsRawFd, RawFd},
@@ -12,7 +14,7 @@ use crate::{
     programs::{
         define_link_wrapper, load_program, FdLink, Link, ProgAttachLink, ProgramData, ProgramError,
     },
-    sys::{bpf_link_create, bpf_prog_attach, kernel_version},
+    sys::{bpf_link_create, bpf_prog_attach},
 };
 
 /// A program that can be used to inspect or modify socket addresses (`struct sockaddr`).
@@ -72,8 +74,7 @@ impl CgroupSockAddr {
         let prog_fd = self.data.fd_or_err()?;
         let cgroup_fd = cgroup.as_raw_fd();
         let attach_type = self.data.expected_attach_type.unwrap();
-        let k_ver = kernel_version().unwrap();
-        if k_ver >= (5, 7, 0) {
+        if KernelVersion::current().unwrap() >= KernelVersion::new(5, 7, 0) {
             let link_fd = bpf_link_create(prog_fd, cgroup_fd, attach_type, None, 0).map_err(
                 |(_, io_error)| ProgramError::SyscallError {
                     call: "bpf_link_create",
diff --git a/aya/src/programs/cgroup_sockopt.rs b/aya/src/programs/cgroup_sockopt.rs
index 021ba38af..9f855cb01 100644
--- a/aya/src/programs/cgroup_sockopt.rs
+++ b/aya/src/programs/cgroup_sockopt.rs
@@ -1,6 +1,8 @@
 //! Cgroup socket option programs.
+
 pub use aya_obj::programs::CgroupSockoptAttachType;
 
+use procfs::KernelVersion;
 use std::{
     hash::Hash,
     os::fd::{AsRawFd, RawFd},
@@ -12,7 +14,7 @@ use crate::{
     programs::{
         define_link_wrapper, load_program, FdLink, Link, ProgAttachLink, ProgramData, ProgramError,
     },
-    sys::{bpf_link_create, bpf_prog_attach, kernel_version},
+    sys::{bpf_link_create, bpf_prog_attach},
 };
 
 /// A program that can be used to get or set options on sockets.
@@ -69,8 +71,7 @@ impl CgroupSockopt {
         let prog_fd = self.data.fd_or_err()?;
         let cgroup_fd = cgroup.as_raw_fd();
         let attach_type = self.data.expected_attach_type.unwrap();
-        let k_ver = kernel_version().unwrap();
-        if k_ver >= (5, 7, 0) {
+        if KernelVersion::current().unwrap() >= KernelVersion::new(5, 7, 0) {
             let link_fd = bpf_link_create(prog_fd, cgroup_fd, attach_type, None, 0).map_err(
                 |(_, io_error)| ProgramError::SyscallError {
                     call: "bpf_link_create",
diff --git a/aya/src/programs/cgroup_sysctl.rs b/aya/src/programs/cgroup_sysctl.rs
index f2bfd56bd..c3ce10114 100644
--- a/aya/src/programs/cgroup_sysctl.rs
+++ b/aya/src/programs/cgroup_sysctl.rs
@@ -1,4 +1,6 @@
 //! Cgroup sysctl programs.
+
+use procfs::KernelVersion;
 use std::{
     hash::Hash,
     os::fd::{AsRawFd, RawFd},
@@ -9,7 +11,7 @@ use crate::{
     programs::{
         define_link_wrapper, load_program, FdLink, Link, ProgAttachLink, ProgramData, ProgramError,
     },
-    sys::{bpf_link_create, bpf_prog_attach, kernel_version},
+    sys::{bpf_link_create, bpf_prog_attach},
 };
 
 /// A program used to watch for sysctl changes.
@@ -64,8 +66,7 @@ impl CgroupSysctl {
         let prog_fd = self.data.fd_or_err()?;
         let cgroup_fd = cgroup.as_raw_fd();
 
-        let k_ver = kernel_version().unwrap();
-        if k_ver >= (5, 7, 0) {
+        if KernelVersion::current().unwrap() >= KernelVersion::new(5, 7, 0) {
             let link_fd = bpf_link_create(prog_fd, cgroup_fd, BPF_CGROUP_SYSCTL, None, 0).map_err(
                 |(_, io_error)| ProgramError::SyscallError {
                     call: "bpf_link_create",
diff --git a/aya/src/programs/mod.rs b/aya/src/programs/mod.rs
index b47687b2f..5aa6b1566 100644
--- a/aya/src/programs/mod.rs
+++ b/aya/src/programs/mod.rs
@@ -65,6 +65,7 @@ mod utils;
 pub mod xdp;
 
 use libc::ENOSPC;
+use procfs::KernelVersion;
 use std::{
     ffi::CString,
     io,
@@ -105,7 +106,7 @@ pub use xdp::{Xdp, XdpError, XdpFlags};
 use crate::{
     generated::{bpf_attach_type, bpf_prog_info, bpf_prog_type},
     maps::MapError,
-    obj::{self, btf::BtfError, Function, KernelVersion},
+    obj::{self, btf::BtfError, Function},
     pin::PinError,
     sys::{
         bpf_btf_get_fd_by_id, bpf_get_object, bpf_load_program, bpf_pin_object,
@@ -573,13 +574,14 @@ fn load_program<T: Link>(
         },
     ) = obj;
 
-    let target_kernel_version = match *kernel_version {
-        KernelVersion::Any => {
-            let (major, minor, patch) = crate::sys::kernel_version().unwrap();
-            (major << 16) + (minor << 8) + patch
-        }
-        _ => (*kernel_version).into(),
-    };
+    let target_kernel_version = kernel_version.unwrap_or_else(|| {
+        let KernelVersion {
+            major,
+            minor,
+            patch,
+        } = KernelVersion::current().unwrap();
+        (u32::from(major) << 16) + (u32::from(minor) << 8) + u32::from(patch)
+    });
 
     let mut logger = VerifierLog::new();
 
diff --git a/aya/src/programs/probe.rs b/aya/src/programs/probe.rs
index f9d17bc09..c6320e1aa 100644
--- a/aya/src/programs/probe.rs
+++ b/aya/src/programs/probe.rs
@@ -1,4 +1,5 @@
 use libc::pid_t;
+use procfs::KernelVersion;
 use std::{
     fs::{self, OpenOptions},
     io::{self, Write},
@@ -13,7 +14,7 @@ use crate::{
         trace_point::read_sys_fs_trace_point_id, uprobe::UProbeError, utils::find_tracefs_path,
         Link, ProgramData, ProgramError,
     },
-    sys::{kernel_version, perf_event_open_probe, perf_event_open_trace_point},
+    sys::{perf_event_open_probe, perf_event_open_trace_point},
 };
 
 static PROBE_NAME_INDEX: AtomicUsize = AtomicUsize::new(0);
@@ -49,8 +50,7 @@ pub(crate) fn attach<T: Link + From<PerfLinkInner>>(
 ) -> Result<T::Id, ProgramError> {
     // https://github.com/torvalds/linux/commit/e12f03d7031a977356e3d7b75a68c2185ff8d155
     // Use debugfs to create probe
-    let k_ver = kernel_version().unwrap();
-    if k_ver < (4, 17, 0) {
+    if KernelVersion::current().unwrap() >= KernelVersion::new(4, 17, 0) {
         let (fd, event_alias) = create_as_trace_point(kind, fn_name, offset, pid)?;
         let link = T::from(perf_attach_debugfs(
             program_data.fd_or_err()?,
diff --git a/aya/src/programs/xdp.rs b/aya/src/programs/xdp.rs
index 4940fc7e9..01843928c 100644
--- a/aya/src/programs/xdp.rs
+++ b/aya/src/programs/xdp.rs
@@ -1,6 +1,8 @@
 //! eXpress Data Path (XDP) programs.
+
 use bitflags;
 use libc::if_nametoindex;
+use procfs::KernelVersion;
 use std::{convert::TryFrom, ffi::CString, hash::Hash, io, mem, os::unix::io::RawFd};
 use thiserror::Error;
 
@@ -15,10 +17,7 @@ use crate::{
     programs::{
         define_link_wrapper, load_program, FdLink, Link, LinkError, ProgramData, ProgramError,
     },
-    sys::{
-        bpf_link_create, bpf_link_get_info_by_fd, bpf_link_update, kernel_version,
-        netlink_set_xdp_fd,
-    },
+    sys::{bpf_link_create, bpf_link_get_info_by_fd, bpf_link_update, netlink_set_xdp_fd},
 };
 
 /// The type returned when attaching an [`Xdp`] program fails on kernels `< 5.9`.
@@ -126,8 +125,7 @@ impl Xdp {
         let prog_fd = self.data.fd_or_err()?;
         let if_index = if_index as RawFd;
 
-        let k_ver = kernel_version().unwrap();
-        if k_ver >= (5, 9, 0) {
+        if KernelVersion::current().unwrap() >= KernelVersion::new(5, 9, 0) {
             let link_fd = bpf_link_create(prog_fd, if_index, BPF_XDP, None, flags.bits()).map_err(
                 |(_, io_error)| ProgramError::SyscallError {
                     call: "bpf_link_create",
@@ -224,8 +222,7 @@ impl Link for NlLink {
     }
 
     fn detach(self) -> Result<(), ProgramError> {
-        let k_ver = kernel_version().unwrap();
-        let flags = if k_ver >= (5, 7, 0) {
+        let flags = if KernelVersion::current().unwrap() >= KernelVersion::new(5, 7, 0) {
             self.flags.bits() | XDP_FLAGS_REPLACE
         } else {
             self.flags.bits()
diff --git a/aya/src/sys/bpf.rs b/aya/src/sys/bpf.rs
index e0aa1669f..542896cd2 100644
--- a/aya/src/sys/bpf.rs
+++ b/aya/src/sys/bpf.rs
@@ -12,6 +12,7 @@ use obj::{
     maps::{bpf_map_def, LegacyMap},
     BpfSectionKind,
 };
+use procfs::KernelVersion;
 
 use crate::{
     generated::{
@@ -27,12 +28,17 @@ use crate::{
         },
         copy_instructions,
     },
-    sys::{kernel_version, syscall, SysResult, Syscall},
+    sys::{syscall, SysResult, Syscall},
     util::VerifierLog,
     Btf, Pod, BPF_OBJ_NAME_LEN,
 };
 
-pub(crate) fn bpf_create_map(name: &CStr, def: &obj::Map, btf_fd: Option<RawFd>) -> SysResult {
+pub(crate) fn bpf_create_map(
+    name: &CStr,
+    def: &obj::Map,
+    btf_fd: Option<RawFd>,
+    kernel_version: KernelVersion,
+) -> SysResult {
     let mut attr = unsafe { mem::zeroed::<bpf_attr>() };
 
     let u = unsafe { &mut attr.__bindgen_anon_1 };
@@ -78,8 +84,7 @@ pub(crate) fn bpf_create_map(name: &CStr, def: &obj::Map, btf_fd: Option<RawFd>)
     // https://github.com/torvalds/linux/commit/ad5b177bd73f5107d97c36f56395c4281fb6f089
     // The map name was added as a parameter in kernel 4.15+ so we skip adding it on
     // older kernels for compatibility
-    let k_ver = kernel_version().unwrap();
-    if k_ver >= (4, 15, 0) {
+    if kernel_version >= KernelVersion::new(4, 15, 0) {
         // u.map_name is 16 bytes max and must be NULL terminated
         let name_len = cmp::min(name.to_bytes().len(), BPF_OBJ_NAME_LEN - 1);
         u.map_name[..name_len]
diff --git a/aya/src/sys/mod.rs b/aya/src/sys/mod.rs
index 294ff3860..646d6cfe5 100644
--- a/aya/src/sys/mod.rs
+++ b/aya/src/sys/mod.rs
@@ -5,15 +5,9 @@ mod perf_event;
 #[cfg(test)]
 mod fake;
 
-use std::io;
-#[cfg(not(test))]
-use std::{ffi::CString, mem};
-#[cfg(not(test))]
-use std::{fs::File, io::Read};
+use std::{io, mem};
 
-#[cfg(not(test))]
-use libc::utsname;
-use libc::{c_int, c_long, pid_t};
+use libc::{c_int, c_long, pid_t, SYS_bpf, SYS_perf_event_open};
 
 pub(crate) use bpf::*;
 #[cfg(test)]
@@ -25,7 +19,6 @@ use crate::generated::{bpf_attr, bpf_cmd, perf_event_attr};
 
 pub(crate) type SysResult = Result<c_long, (c_long, io::Error)>;
 
-#[cfg_attr(test, allow(dead_code))]
 pub(crate) enum Syscall<'a> {
     Bpf {
         cmd: bpf_cmd,
@@ -46,126 +39,28 @@ pub(crate) enum Syscall<'a> {
 }
 
 fn syscall(call: Syscall) -> SysResult {
-    #[cfg(not(test))]
-    return unsafe { syscall_impl(call) };
-
     #[cfg(test)]
     return TEST_SYSCALL.with(|test_impl| unsafe { test_impl.borrow()(call) });
-}
-
-#[cfg(not(test))]
-unsafe fn syscall_impl(call: Syscall) -> SysResult {
-    use libc::{SYS_bpf, SYS_perf_event_open};
-
-    use Syscall::*;
-    let ret = match call {
-        Bpf { cmd, attr } => libc::syscall(SYS_bpf, cmd, attr, mem::size_of::<bpf_attr>()),
-        PerfEventOpen {
-            attr,
-            pid,
-            cpu,
-            group,
-            flags,
-        } => libc::syscall(SYS_perf_event_open, &attr, pid, cpu, group, flags),
-        PerfEventIoctl { fd, request, arg } => {
-            libc::ioctl(fd, request.try_into().unwrap(), arg) as libc::c_long
-        }
-    };
-
-    if ret < 0 {
-        return Err((ret, io::Error::last_os_error()));
-    }
-
-    Ok(ret)
-}
-
-#[cfg(test)]
-pub(crate) fn kernel_version() -> Result<(u32, u32, u32), ()> {
-    Ok((0xff, 0xff, 0xff))
-}
 
-#[cfg(not(test))]
-fn ubuntu_kernel_version() -> Result<(u32, u32, u32), ()> {
-    if let Ok(mut file) = File::open("/proc/version_signature") {
-        let mut buf = String::new();
-        let mut major = 0u32;
-        let mut minor = 0u32;
-        let mut patch = 0u32;
-        let format = CString::new("%*s %*s %u.%u.%u\n").unwrap();
-
-        file.read_to_string(&mut buf).map_err(|_| ())?;
-
-        unsafe {
-            if libc::sscanf(
-                buf.as_ptr() as *const _,
-                format.as_ptr(),
-                &mut major as *mut u32,
-                &mut minor as *mut _,
-                &mut patch as *mut _,
-            ) == 3
-            {
-                return Ok((major, minor, patch));
+    #[cfg_attr(test, allow(unreachable_code))]
+    match unsafe {
+        match call {
+            Syscall::Bpf { cmd, attr } => {
+                libc::syscall(SYS_bpf, cmd, attr, mem::size_of::<bpf_attr>())
             }
-        }
-    }
-
-    Err(())
-}
-
-#[cfg(not(test))]
-pub(crate) fn kernel_version() -> Result<(u32, u32, u32), ()> {
-    if let Ok(version) = ubuntu_kernel_version() {
-        return Ok(version);
-    }
-
-    unsafe {
-        let mut v = mem::zeroed::<utsname>();
-        if libc::uname(&mut v as *mut _) != 0 {
-            return Err(());
-        }
-
-        let mut major = 0u32;
-        let mut minor = 0u32;
-        let mut patch = 0u32;
-
-        let debian_marker = CString::new("Debian").unwrap();
-
-        let p = libc::strstr(v.version.as_ptr(), debian_marker.as_ptr());
-
-        if !p.is_null() {
-            let debian_format = CString::new("Debian %u.%u.%u").map_err(|_| ())?;
-
-            if libc::sscanf(
-                p,
-                debian_format.as_ptr(),
-                &mut major as *mut u32,
-                &mut minor as *mut _,
-                &mut patch as *mut _,
-            ) == 3
-            {
-                // On Debian 10, kernels after 4.19.229 expect 4.19.255 due to broken Makefile patches.
-                let patch_level_limit = if major == 4 && minor == 19 { 230 } else { 255 };
-
-                if patch >= patch_level_limit {
-                    patch = 255;
-                }
-
-                return Ok((major, minor, patch));
+            Syscall::PerfEventOpen {
+                attr,
+                pid,
+                cpu,
+                group,
+                flags,
+            } => libc::syscall(SYS_perf_event_open, &attr, pid, cpu, group, flags),
+            Syscall::PerfEventIoctl { fd, request, arg } => {
+                libc::ioctl(fd, request.try_into().unwrap(), arg) as libc::c_long
             }
         }
-
-        let format = CString::new("%u.%u.%u").unwrap();
-        if libc::sscanf(
-            v.release.as_ptr(),
-            format.as_ptr(),
-            &mut major as *mut u32,
-            &mut minor as *mut _,
-            &mut patch as *mut _,
-        ) != 3
-        {
-            return Err(());
-        }
-
-        Ok((major, minor, patch))
+    } {
+        ret @ 0.. => Ok(ret),
+        ret => Err((ret, io::Error::last_os_error())),
     }
 }
diff --git a/test/integration-test/Cargo.toml b/test/integration-test/Cargo.toml
index 07eaa9270..36f8270b7 100644
--- a/test/integration-test/Cargo.toml
+++ b/test/integration-test/Cargo.toml
@@ -12,7 +12,7 @@ aya-obj = { path = "../../aya-obj" }
 libc = { version = "0.2.105" }
 log = "0.4"
 object = { version = "0.31", default-features = false, features = ["std", "read_core", "elf"] }
+procfs = "0.15.1"
 rbpf = "0.2.0"
-regex = "1"
 tempfile = "3.3.0"
 tokio = { version = "1.24", features = ["rt", "rt-multi-thread", "sync", "time"] }
diff --git a/test/integration-test/tests/common.rs b/test/integration-test/tests/common.rs
deleted file mode 100644
index 3af8a5976..000000000
--- a/test/integration-test/tests/common.rs
+++ /dev/null
@@ -1,23 +0,0 @@
-use anyhow::bail;
-use libc::{uname, utsname};
-use regex::Regex;
-use std::{cell::OnceCell, ffi::CStr, mem};
-
-pub fn kernel_version() -> anyhow::Result<(u8, u8, u8)> {
-    static mut RE: OnceCell<Regex> = OnceCell::new();
-    let re =
-        unsafe { &mut RE }.get_or_init(|| Regex::new(r"^([0-9]+)\.([0-9]+)\.([0-9]+)").unwrap());
-    let mut data: utsname = unsafe { mem::zeroed() };
-    let ret = unsafe { uname(&mut data) };
-    assert!(ret >= 0, "libc::uname failed.");
-    let release_cstr = unsafe { CStr::from_ptr(data.release.as_ptr()) };
-    let release = release_cstr.to_string_lossy();
-    if let Some(caps) = re.captures(&release) {
-        let major = caps.get(1).unwrap().as_str().parse().unwrap();
-        let minor = caps.get(2).unwrap().as_str().parse().unwrap();
-        let patch = caps.get(3).unwrap().as_str().parse().unwrap();
-        Ok((major, minor, patch))
-    } else {
-        bail!("no kernel version found");
-    }
-}
diff --git a/test/integration-test/tests/load.rs b/test/integration-test/tests/load.rs
index 110025b2d..d242338d2 100644
--- a/test/integration-test/tests/load.rs
+++ b/test/integration-test/tests/load.rs
@@ -1,3 +1,4 @@
+use procfs::KernelVersion;
 use std::{convert::TryInto as _, thread, time};
 
 use aya::{
@@ -10,9 +11,6 @@ use aya::{
     Bpf,
 };
 
-mod common;
-use common::kernel_version;
-
 const MAX_RETRIES: u32 = 100;
 const RETRY_DURATION_MS: u64 = 10;
 
@@ -133,7 +131,7 @@ fn unload_kprobe() {
 
 #[test]
 fn pin_link() {
-    if kernel_version().unwrap() < (5, 9, 0) {
+    if KernelVersion::current().unwrap() < KernelVersion::new(5, 9, 0) {
         eprintln!("skipping test, XDP uses netlink");
         return;
     }
@@ -168,7 +166,7 @@ fn pin_link() {
 
 #[test]
 fn pin_lifecycle() {
-    if kernel_version().unwrap() < (5, 9, 0) {
+    if KernelVersion::current().unwrap() < KernelVersion::new(5, 9, 0) {
         eprintln!("skipping test, XDP uses netlink");
         return;
     }
diff --git a/test/integration-test/tests/smoke.rs b/test/integration-test/tests/smoke.rs
index daeed03a4..b30060721 100644
--- a/test/integration-test/tests/smoke.rs
+++ b/test/integration-test/tests/smoke.rs
@@ -1,12 +1,11 @@
+use procfs::KernelVersion;
+
 use aya::{
     include_bytes_aligned,
     programs::{Extension, Xdp, XdpFlags},
     Bpf, BpfLoader,
 };
 
-mod common;
-use common::kernel_version;
-
 #[test]
 fn xdp() {
     let bytes = include_bytes_aligned!("../../../target/bpfel-unknown-none/release/pass");
@@ -18,15 +17,10 @@ fn xdp() {
 
 #[test]
 fn extension() {
-    let (major, minor, _) = kernel_version().unwrap();
-    if major < 5 || (minor == 5 && minor < 9) {
-        eprintln!(
-            "skipping as {}.{} does not meet version requirement of 5.9",
-            major, minor
-        );
+    if KernelVersion::current().unwrap() < KernelVersion::new(5, 9, 0) {
+        eprintln!("skipping test, XDP uses netlink");
         return;
     }
-    // TODO: Check kernel version == 5.9 or later
     let main_bytes =
         include_bytes_aligned!("../../../target/bpfel-unknown-none/release/main.bpf.o");
     let mut bpf = Bpf::load(main_bytes).unwrap();

From b0a4ab5f20b9838d035913e48fc350b97e8e4a06 Mon Sep 17 00:00:00 2001
From: Tamir Duberstein <tamird@gmail.com>
Date: Sun, 9 Jul 2023 16:05:06 -0400
Subject: [PATCH 3/7] xtask: Standardize command logging

Don't run `cargo build --verbose`; it's too noisy.
---
 .../integration-test/tests/btf_relocations.rs |  40 +++---
 xtask/Cargo.toml                              |   1 -
 xtask/src/build_ebpf.rs                       | 117 +++++++-----------
 xtask/src/build_test.rs                       |  30 ++---
 xtask/src/docs/mod.rs                         |  70 ++++++-----
 xtask/src/run.rs                              |  20 +--
 xtask/src/utils.rs                            |  25 ++--
 7 files changed, 146 insertions(+), 157 deletions(-)

diff --git a/test/integration-test/tests/btf_relocations.rs b/test/integration-test/tests/btf_relocations.rs
index 2811f74a6..34bc3dbe7 100644
--- a/test/integration-test/tests/btf_relocations.rs
+++ b/test/integration-test/tests/btf_relocations.rs
@@ -1,4 +1,4 @@
-use anyhow::{Context, Result};
+use anyhow::{bail, Context as _, Result};
 use std::{path::PathBuf, process::Command, thread::sleep, time::Duration};
 use tempfile::TempDir;
 
@@ -266,15 +266,20 @@ impl RelocationTest {
             "#
         ))
         .context("Failed to compile BTF")?;
-        Command::new("llvm-objcopy")
-            .current_dir(tmp_dir.path())
+        let mut cmd = Command::new("llvm-objcopy");
+        cmd.current_dir(tmp_dir.path())
             .args(["--dump-section", ".BTF=target.btf"])
-            .arg(compiled_file)
+            .arg(compiled_file);
+        let status = cmd
             .status()
-            .context("Failed to run llvm-objcopy")?
-            .success()
-            .then_some(())
-            .context("Failed to extract BTF")?;
+            .with_context(|| format!("Failed to run {cmd:?}"))?;
+        match status.code() {
+            Some(code) => match code {
+                0 => {}
+                code => bail!("{cmd:?} exited with code {code}"),
+            },
+            None => bail!("{cmd:?} terminated by signal"),
+        }
         let btf = Btf::parse_file(tmp_dir.path().join("target.btf"), Endianness::default())
             .context("Error parsing generated BTF")?;
         Ok(btf)
@@ -287,15 +292,20 @@ fn compile(source_code: &str) -> Result<(TempDir, PathBuf)> {
     let tmp_dir = tempfile::tempdir().context("Error making temp dir")?;
     let source = tmp_dir.path().join("source.c");
     std::fs::write(&source, source_code).context("Writing bpf program failed")?;
-    Command::new("clang")
-        .current_dir(&tmp_dir)
+    let mut cmd = Command::new("clang");
+    cmd.current_dir(&tmp_dir)
         .args(["-c", "-g", "-O2", "-target", "bpf"])
-        .arg(&source)
+        .arg(&source);
+    let status = cmd
         .status()
-        .context("Failed to run clang")?
-        .success()
-        .then_some(())
-        .context("Failed to compile eBPF source")?;
+        .with_context(|| format!("Failed to run {cmd:?}"))?;
+    match status.code() {
+        Some(code) => match code {
+            0 => {}
+            code => bail!("{cmd:?} exited with code {code}"),
+        },
+        None => bail!("{cmd:?} terminated by signal"),
+    }
     Ok((tmp_dir, source.with_extension("o")))
 }
 
diff --git a/xtask/Cargo.toml b/xtask/Cargo.toml
index 2f8a74d51..24d91ef4f 100644
--- a/xtask/Cargo.toml
+++ b/xtask/Cargo.toml
@@ -12,5 +12,4 @@ clap = { version = "4", features = ["derive"] }
 indoc = "2.0"
 proc-macro2 = "1"
 quote = "1"
-serde_json = "1"
 syn = "2"
diff --git a/xtask/src/build_ebpf.rs b/xtask/src/build_ebpf.rs
index 5a9889c34..bdbf44edf 100644
--- a/xtask/src/build_ebpf.rs
+++ b/xtask/src/build_ebpf.rs
@@ -4,13 +4,13 @@ use std::{
     ffi::{OsStr, OsString},
     fs,
     path::{Path, PathBuf},
-    process::{Command, Output},
+    process::Command,
 };
 
-use anyhow::{bail, Context};
+use anyhow::Result;
 use clap::Parser;
 
-use crate::utils::workspace_root;
+use crate::utils::{exec, workspace_root};
 
 #[derive(Debug, Copy, Clone)]
 pub enum Architecture {
@@ -49,61 +49,56 @@ pub struct BuildEbpfOptions {
     pub libbpf_dir: PathBuf,
 }
 
-pub fn build_ebpf(opts: BuildEbpfOptions) -> anyhow::Result<()> {
+pub fn build_ebpf(opts: BuildEbpfOptions) -> Result<()> {
     build_rust_ebpf(&opts)?;
     build_c_ebpf(&opts)
 }
 
-fn build_rust_ebpf(opts: &BuildEbpfOptions) -> anyhow::Result<()> {
+fn build_rust_ebpf(opts: &BuildEbpfOptions) -> Result<()> {
+    let BuildEbpfOptions {
+        target,
+        libbpf_dir: _,
+    } = opts;
+
     let mut dir = PathBuf::from(workspace_root());
     dir.push("test/integration-ebpf");
 
-    let target = format!("--target={}", opts.target);
-    let args = vec![
-        "+nightly",
-        "build",
-        "--release",
-        "--verbose",
-        target.as_str(),
-        "-Z",
-        "build-std=core",
-    ];
-    let status = Command::new("cargo")
-        .current_dir(&dir)
-        .args(&args)
-        .status()
-        .expect("failed to build bpf program");
-    assert!(status.success());
-    Ok(())
+    exec(
+        Command::new("cargo")
+            .current_dir(&dir)
+            .args(["+nightly", "build", "--release", "--target"])
+            .arg(target.to_string())
+            .args(["-Z", "build-std=core"])
+            .current_dir(&dir),
+    )
 }
 
-fn get_libbpf_headers<P: AsRef<Path>>(libbpf_dir: P, include_path: P) -> anyhow::Result<()> {
-    let dir = include_path.as_ref();
-    fs::create_dir_all(dir)?;
+fn get_libbpf_headers(libbpf_dir: &Path, include_path: &Path) -> Result<()> {
+    fs::create_dir_all(include_path)?;
     let mut includedir = OsString::new();
     includedir.push("INCLUDEDIR=");
-    includedir.push(dir.as_os_str());
-    let status = Command::new("make")
-        .current_dir(libbpf_dir.as_ref().join("src"))
-        .arg(includedir)
-        .arg("install_headers")
-        .status()
-        .expect("failed to build get libbpf headers");
-    assert!(status.success());
-    Ok(())
+    includedir.push(include_path);
+    exec(
+        Command::new("make")
+            .current_dir(libbpf_dir.join("src"))
+            .arg(includedir)
+            .arg("install_headers"),
+    )
 }
 
-fn build_c_ebpf(opts: &BuildEbpfOptions) -> anyhow::Result<()> {
+fn build_c_ebpf(opts: &BuildEbpfOptions) -> Result<()> {
+    let BuildEbpfOptions { target, libbpf_dir } = opts;
+
     let mut src = PathBuf::from(workspace_root());
     src.push("test/integration-ebpf/src/bpf");
 
     let mut out_path = PathBuf::from(workspace_root());
     out_path.push("target");
-    out_path.push(opts.target.to_string());
+    out_path.push(target.to_string());
     out_path.push("release");
 
     let include_path = out_path.join("include");
-    get_libbpf_headers(&opts.libbpf_dir, &include_path)?;
+    get_libbpf_headers(libbpf_dir, &include_path)?;
     let files = fs::read_dir(&src).unwrap();
     for file in files {
         let p = file.unwrap().path();
@@ -120,11 +115,7 @@ fn build_c_ebpf(opts: &BuildEbpfOptions) -> anyhow::Result<()> {
 }
 
 /// Build eBPF programs with clang and libbpf headers.
-fn compile_with_clang<P: Clone + AsRef<Path>>(
-    src: P,
-    out: P,
-    include_path: P,
-) -> anyhow::Result<()> {
+fn compile_with_clang(src: &Path, out: &Path, include_path: &Path) -> Result<()> {
     let clang: Cow<'_, _> = match env::var_os("CLANG") {
         Some(val) => val.into(),
         None => OsStr::new("/usr/bin/clang").into(),
@@ -134,36 +125,14 @@ fn compile_with_clang<P: Clone + AsRef<Path>>(
         "aarch64" => "arm64",
         arch => arch,
     };
-    let mut cmd = Command::new(clang);
-    cmd.arg("-v")
-        .arg("-I")
-        .arg(include_path.as_ref())
-        .arg("-g")
-        .arg("-O2")
-        .arg("-target")
-        .arg("bpf")
-        .arg("-c")
-        .arg(format!("-D__TARGET_ARCH_{arch}"))
-        .arg(src.as_ref().as_os_str())
-        .arg("-o")
-        .arg(out.as_ref().as_os_str());
-
-    let Output {
-        status,
-        stdout,
-        stderr,
-    } = cmd.output().context("Failed to execute clang")?;
-    if !status.success() {
-        bail!(
-            "Failed to compile eBPF programs\n \
-            stdout=\n \
-            {}\n \
-            stderr=\n \
-            {}\n",
-            String::from_utf8(stdout).unwrap(),
-            String::from_utf8(stderr).unwrap()
-        );
-    }
-
-    Ok(())
+    exec(
+        Command::new(clang)
+            .arg("-I")
+            .arg(include_path)
+            .args(["-g", "-O2", "-target", "bpf", "-c"])
+            .arg(format!("-D__TARGET_ARCH_{arch}"))
+            .arg(src)
+            .arg("-o")
+            .arg(out),
+    )
 }
diff --git a/xtask/src/build_test.rs b/xtask/src/build_test.rs
index b45943a10..0fe4c277f 100644
--- a/xtask/src/build_test.rs
+++ b/xtask/src/build_test.rs
@@ -1,7 +1,8 @@
+use anyhow::Result;
 use clap::Parser;
 use std::process::Command;
 
-use crate::build_ebpf;
+use crate::{build_ebpf, utils::exec};
 
 #[derive(Parser)]
 pub struct Options {
@@ -13,20 +14,19 @@ pub struct Options {
     pub ebpf_options: build_ebpf::BuildEbpfOptions,
 }
 
-pub fn build_test(opts: Options) -> anyhow::Result<()> {
-    build_ebpf::build_ebpf(opts.ebpf_options)?;
+pub fn build_test(opts: Options) -> Result<()> {
+    let Options {
+        musl_target,
+        ebpf_options,
+    } = opts;
 
-    let mut args = ["build", "-p", "integration-test", "--verbose"]
-        .iter()
-        .map(|s| s.to_string())
-        .collect::<Vec<_>>();
-    if let Some(target) = opts.musl_target {
-        args.push(format!("--target={target}"));
+    build_ebpf::build_ebpf(ebpf_options)?;
+
+    let mut cmd = Command::new("cargo");
+    cmd.args(["build", "-p", "integration-test"]);
+
+    if let Some(target) = musl_target {
+        cmd.args(["--target", &target]);
     }
-    let status = Command::new("cargo")
-        .args(&args)
-        .status()
-        .expect("failed to build bpf program");
-    assert!(status.success());
-    Ok(())
+    exec(&mut cmd)
 }
diff --git a/xtask/src/docs/mod.rs b/xtask/src/docs/mod.rs
index bfeca3a99..d392798c2 100644
--- a/xtask/src/docs/mod.rs
+++ b/xtask/src/docs/mod.rs
@@ -1,3 +1,5 @@
+use crate::utils::exec;
+use anyhow::{Context as _, Result};
 use std::{
     path::{Path, PathBuf},
     process::Command,
@@ -7,7 +9,7 @@ use std::{fs, io, io::Write};
 
 use indoc::indoc;
 
-pub fn docs() -> Result<(), anyhow::Error> {
+pub fn docs() -> Result<()> {
     let current_dir = PathBuf::from(".");
     let header_path = current_dir.join("header.html");
     let mut header = fs::File::create(&header_path).expect("can't create header.html");
@@ -19,8 +21,11 @@ pub fn docs() -> Result<(), anyhow::Error> {
 
     build_docs(&current_dir.join("aya"), &abs_header_path)?;
     build_docs(&current_dir.join("bpf/aya-bpf"), &abs_header_path)?;
-    copy_dir_all("./target/doc", "./site/user")?;
-    copy_dir_all("./target/bpfel-unknown-none/doc", "./site/bpf")?;
+    copy_dir_all("./target/doc".as_ref(), "./site/user".as_ref())?;
+    copy_dir_all(
+        "./target/bpfel-unknown-none/doc".as_ref(),
+        "./site/bpf".as_ref(),
+    )?;
 
     let mut robots = fs::File::create("site/robots.txt").expect("can't create robots.txt");
     robots
@@ -53,49 +58,46 @@ pub fn docs() -> Result<(), anyhow::Error> {
     Ok(())
 }
 
-fn build_docs(working_dir: &PathBuf, abs_header_path: &Path) -> Result<(), anyhow::Error> {
-    let replace = Command::new("sed")
-        .current_dir(working_dir)
-        .args(vec!["-i.bak", "s/crabby.svg/crabby_dev.svg/", "src/lib.rs"])
-        .status()
-        .expect("failed to replace logo");
-    assert!(replace.success());
+fn build_docs(working_dir: &Path, abs_header_path: &Path) -> Result<()> {
+    exec(Command::new("sed").current_dir(working_dir).args([
+        "-i.bak",
+        "s/crabby.svg/crabby_dev.svg/",
+        "src/lib.rs",
+    ]))?;
 
-    let args = vec!["+nightly", "doc", "--no-deps", "--all-features"];
+    exec(
+        Command::new("cargo")
+            .current_dir(working_dir)
+            .env(
+                "RUSTDOCFLAGS",
+                format!(
+                    "--cfg docsrs --html-in-header {} -D warnings",
+                    abs_header_path.to_str().unwrap()
+                ),
+            )
+            .args(["+nightly", "doc", "--no-deps", "--all-features"]),
+    )?;
 
-    let status = Command::new("cargo")
-        .current_dir(working_dir)
-        .env(
-            "RUSTDOCFLAGS",
-            format!(
-                "--cfg docsrs --html-in-header {} -D warnings",
-                abs_header_path.to_str().unwrap()
-            ),
-        )
-        .args(args)
-        .status()
-        .expect("failed to build aya docs");
-    assert!(status.success());
     fs::rename(
         working_dir.join("src/lib.rs.bak"),
         working_dir.join("src/lib.rs"),
     )
-    .unwrap();
-    Ok(())
+    .context("Failed to rename lib.rs.bak to lib.rs")
 }
 
-fn copy_dir_all<P1: AsRef<Path>, P2: AsRef<Path>>(src: P1, dst: P2) -> io::Result<()> {
-    fs::create_dir_all(&dst)?;
+fn copy_dir_all(src: &Path, dst: &Path) -> io::Result<()> {
+    fs::create_dir_all(dst)?;
     for entry in fs::read_dir(src)? {
         let entry = entry?;
         let ty = entry.file_type()?;
+        let src = entry.path();
+        let src = src.as_path();
+        let dst = dst.join(entry.file_name());
+        let dst = dst.as_path();
         if ty.is_dir() {
-            copy_dir_all(entry.path(), dst.as_ref().join(entry.file_name()))?;
-        } else {
-            let new_path = dst.as_ref().join(entry.file_name());
-            if !new_path.exists() {
-                fs::copy(entry.path(), dst.as_ref().join(entry.file_name()))?;
-            }
+            copy_dir_all(src, dst)?;
+        } else if !dst.exists() {
+            fs::copy(src, dst)?;
         }
     }
     Ok(())
diff --git a/xtask/src/run.rs b/xtask/src/run.rs
index 42ec51b93..0c7a241dd 100644
--- a/xtask/src/run.rs
+++ b/xtask/src/run.rs
@@ -5,7 +5,7 @@ use std::{
     process::{Command, Stdio},
 };
 
-use anyhow::Context as _;
+use anyhow::{Context as _, Result};
 use cargo_metadata::{Artifact, CompilerMessage, Message, Target};
 use clap::Parser;
 
@@ -31,12 +31,14 @@ pub struct Options {
 }
 
 /// Build the project
-fn build(release: bool) -> Result<Vec<(PathBuf, PathBuf)>, anyhow::Error> {
+fn build(release: bool) -> Result<Vec<(PathBuf, PathBuf)>> {
     let mut cmd = Command::new("cargo");
-    cmd.arg("build")
-        .arg("--tests")
-        .arg("--message-format=json")
-        .arg("--package=integration-test");
+    cmd.args([
+        "build",
+        "--tests",
+        "--message-format=json",
+        "--package=integration-test",
+    ]);
     if release {
         cmd.arg("--release");
     }
@@ -83,7 +85,7 @@ fn build(release: bool) -> Result<Vec<(PathBuf, PathBuf)>, anyhow::Error> {
 }
 
 /// Build and run the project
-pub fn run(opts: Options) -> Result<(), anyhow::Error> {
+pub fn run(opts: Options) -> Result<()> {
     let Options {
         bpf_target,
         release,
@@ -116,10 +118,8 @@ pub fn run(opts: Options) -> Result<(), anyhow::Error> {
         println!("{} running {cmd:?}", src_path.display());
 
         let status = cmd
-            .stdout(Stdio::inherit())
-            .stderr(Stdio::inherit())
             .status()
-            .context("failed to run {cmd:?}")?;
+            .with_context(|| format!("failed to run {cmd:?}"))?;
         match status.code() {
             Some(code) => match code {
                 0 => {}
diff --git a/xtask/src/utils.rs b/xtask/src/utils.rs
index e1e3eb2aa..f2cd24713 100644
--- a/xtask/src/utils.rs
+++ b/xtask/src/utils.rs
@@ -1,15 +1,24 @@
-use serde_json::Value;
 use std::{cell::OnceCell, process::Command};
 
+use anyhow::{bail, Context as _, Result};
+
 pub fn workspace_root() -> &'static str {
     static mut WORKSPACE_ROOT: OnceCell<String> = OnceCell::new();
     unsafe { &mut WORKSPACE_ROOT }.get_or_init(|| {
-        let output = Command::new("cargo").arg("metadata").output().unwrap();
-        if !output.status.success() {
-            panic!("unable to run cargo metadata")
-        }
-        let stdout = String::from_utf8(output.stdout).unwrap();
-        let v: Value = serde_json::from_str(&stdout).unwrap();
-        v["workspace_root"].as_str().unwrap().to_string()
+        let cmd = cargo_metadata::MetadataCommand::new();
+        cmd.exec().unwrap().workspace_root.to_string()
     })
 }
+
+pub fn exec(cmd: &mut Command) -> Result<()> {
+    let status = cmd
+        .status()
+        .with_context(|| format!("failed to run {cmd:?}"))?;
+    match status.code() {
+        Some(code) => match code {
+            0 => Ok(()),
+            code => bail!("{cmd:?} exited with code {code}"),
+        },
+        None => bail!("{cmd:?} terminated by signal"),
+    }
+}

From 6b94b2080dc4c122954beea814b2a1a4569e9aa3 Mon Sep 17 00:00:00 2001
From: Tamir Duberstein <tamird@gmail.com>
Date: Sun, 9 Jul 2023 14:47:49 -0400
Subject: [PATCH 4/7] Hide details of VerifierLog

This type is really only used by one function.
---
 aya-obj/src/btf/btf.rs  |  2 +-
 aya/src/bpf.rs          | 22 +++++----------
 aya/src/programs/mod.rs | 22 +++++----------
 aya/src/sys/bpf.rs      | 62 ++++++++++++++++++++++-------------------
 aya/src/util.rs         | 53 +----------------------------------
 5 files changed, 49 insertions(+), 112 deletions(-)

diff --git a/aya-obj/src/btf/btf.rs b/aya-obj/src/btf/btf.rs
index a9049b1ea..295fb55e9 100644
--- a/aya-obj/src/btf/btf.rs
+++ b/aya-obj/src/btf/btf.rs
@@ -135,7 +135,7 @@ pub enum BtfError {
         #[source]
         io_error: std::io::Error,
         /// The error log produced by the kernel verifier.
-        verifier_log: String,
+        verifier_log: Cow<'static, str>,
     },
 
     /// offset not found for symbol
diff --git a/aya/src/bpf.rs b/aya/src/bpf.rs
index 74eb53177..ce4273d43 100644
--- a/aya/src/bpf.rs
+++ b/aya/src/bpf.rs
@@ -39,7 +39,7 @@ use crate::{
         is_btf_supported, is_btf_type_tag_supported, is_perf_link_supported,
         is_probe_read_kernel_supported, is_prog_name_supported, retry_with_verifier_logs,
     },
-    util::{bytes_of, bytes_of_slice, possible_cpus, VerifierLog, POSSIBLE_CPUS},
+    util::{bytes_of, bytes_of_slice, possible_cpus, POSSIBLE_CPUS},
 };
 
 pub(crate) const BPF_OBJ_NAME_LEN: usize = 16;
@@ -907,22 +907,14 @@ pub enum BpfError {
 }
 
 fn load_btf(raw_btf: Vec<u8>) -> Result<RawFd, BtfError> {
-    let mut logger = VerifierLog::new();
-    let ret = retry_with_verifier_logs(10, &mut logger, |logger| {
-        bpf_load_btf(raw_btf.as_slice(), logger)
-    });
+    let (ret, verifier_log) =
+        retry_with_verifier_logs(10, |logger| bpf_load_btf(raw_btf.as_slice(), logger));
     match ret {
         Ok(fd) => Ok(fd as RawFd),
-        Err((_, io_error)) => {
-            logger.truncate();
-            Err(BtfError::LoadError {
-                io_error,
-                verifier_log: logger
-                    .as_c_str()
-                    .map(|s| s.to_string_lossy().to_string())
-                    .unwrap_or_else(|| "[none]".to_owned()),
-            })
-        }
+        Err((_, io_error)) => Err(BtfError::LoadError {
+            io_error,
+            verifier_log,
+        }),
     }
 }
 
diff --git a/aya/src/programs/mod.rs b/aya/src/programs/mod.rs
index 5aa6b1566..793c94e62 100644
--- a/aya/src/programs/mod.rs
+++ b/aya/src/programs/mod.rs
@@ -67,6 +67,7 @@ pub mod xdp;
 use libc::ENOSPC;
 use procfs::KernelVersion;
 use std::{
+    borrow::Cow,
     ffi::CString,
     io,
     os::unix::io::{AsRawFd, RawFd},
@@ -113,7 +114,6 @@ use crate::{
         bpf_prog_get_fd_by_id, bpf_prog_get_info_by_fd, bpf_prog_get_next_id, bpf_prog_query,
         retry_with_verifier_logs, BpfLoadProgramAttrs,
     },
-    util::VerifierLog,
 };
 
 /// Error type returned when working with programs.
@@ -142,7 +142,7 @@ pub enum ProgramError {
         #[source]
         io_error: io::Error,
         /// The error log produced by the kernel verifier.
-        verifier_log: String,
+        verifier_log: Cow<'static, str>,
     },
 
     /// A syscall failed.
@@ -583,8 +583,6 @@ fn load_program<T: Link>(
         (u32::from(major) << 16) + (u32::from(minor) << 8) + u32::from(patch)
     });
 
-    let mut logger = VerifierLog::new();
-
     let prog_name = if let Some(name) = &data.name {
         let mut name = name.clone();
         if name.len() > 15 {
@@ -616,7 +614,7 @@ fn load_program<T: Link>(
     };
 
     let verifier_log_level = data.verifier_log_level;
-    let ret = retry_with_verifier_logs(10, &mut logger, |logger| {
+    let (ret, verifier_log) = retry_with_verifier_logs(10, |logger| {
         bpf_load_program(&attr, logger, verifier_log_level)
     });
 
@@ -625,16 +623,10 @@ fn load_program<T: Link>(
             *fd = Some(prog_fd as RawFd);
             Ok(())
         }
-        Err((_, io_error)) => {
-            logger.truncate();
-            return Err(ProgramError::LoadError {
-                io_error,
-                verifier_log: logger
-                    .as_c_str()
-                    .map(|s| s.to_string_lossy().to_string())
-                    .unwrap_or_else(|| "[none]".to_owned()),
-            });
-        }
+        Err((_, io_error)) => Err(ProgramError::LoadError {
+            io_error,
+            verifier_log,
+        }),
     }
 }
 
diff --git a/aya/src/sys/bpf.rs b/aya/src/sys/bpf.rs
index 542896cd2..b5fe2d64f 100644
--- a/aya/src/sys/bpf.rs
+++ b/aya/src/sys/bpf.rs
@@ -1,4 +1,5 @@
 use std::{
+    borrow::Cow,
     cmp::{self, min},
     ffi::{CStr, CString},
     io,
@@ -29,7 +30,6 @@ use crate::{
         copy_instructions,
     },
     sys::{syscall, SysResult, Syscall},
-    util::VerifierLog,
     Btf, Pod, BPF_OBJ_NAME_LEN,
 };
 
@@ -129,7 +129,7 @@ pub(crate) struct BpfLoadProgramAttrs<'a> {
 
 pub(crate) fn bpf_load_program(
     aya_attr: &BpfLoadProgramAttrs,
-    logger: &mut VerifierLog,
+    log_buf: &mut [u8],
     verifier_log_level: u32,
 ) -> SysResult {
     let mut attr = unsafe { mem::zeroed::<bpf_attr>() };
@@ -174,11 +174,10 @@ pub(crate) fn bpf_load_program(
             u.func_info_rec_size = aya_attr.func_info_rec_size as u32;
         }
     }
-    let log_buf = logger.buf();
-    if log_buf.capacity() > 0 {
+    if !log_buf.is_empty() {
         u.log_level = verifier_log_level;
         u.log_buf = log_buf.as_mut_ptr() as u64;
-        u.log_size = log_buf.capacity() as u32;
+        u.log_size = log_buf.len() as u32;
     }
     if let Some(v) = aya_attr.attach_btf_obj_fd {
         u.__bindgen_anon_1.attach_btf_obj_fd = v;
@@ -549,16 +548,15 @@ pub(crate) fn bpf_raw_tracepoint_open(name: Option<&CStr>, prog_fd: RawFd) -> Sy
     sys_bpf(bpf_cmd::BPF_RAW_TRACEPOINT_OPEN, &attr)
 }
 
-pub(crate) fn bpf_load_btf(raw_btf: &[u8], log: &mut VerifierLog) -> SysResult {
+pub(crate) fn bpf_load_btf(raw_btf: &[u8], log_buf: &mut [u8]) -> SysResult {
     let mut attr = unsafe { mem::zeroed::<bpf_attr>() };
     let u = unsafe { &mut attr.__bindgen_anon_7 };
     u.btf = raw_btf.as_ptr() as *const _ as u64;
     u.btf_size = mem::size_of_val(raw_btf) as u32;
-    let log_buf = log.buf();
-    if log_buf.capacity() > 0 {
+    if !log_buf.is_empty() {
         u.btf_log_level = 1;
         u.btf_log_buf = log_buf.as_mut_ptr() as u64;
-        u.btf_log_size = log_buf.capacity() as u32;
+        u.btf_log_size = log_buf.len() as u32;
     }
     sys_bpf(bpf_cmd::BPF_BTF_LOAD, &attr)
 }
@@ -993,35 +991,41 @@ pub(crate) fn bpf_prog_get_next_id(id: u32) -> Result<Option<u32>, (c_long, io::
 
 pub(crate) fn retry_with_verifier_logs<F>(
     max_retries: usize,
-    log: &mut VerifierLog,
     f: F,
-) -> SysResult
+) -> (SysResult, Cow<'static, str>)
 where
-    F: Fn(&mut VerifierLog) -> SysResult,
+    F: Fn(&mut [u8]) -> SysResult,
 {
-    // 1. Try the syscall
-    let ret = f(log);
-    if ret.is_ok() {
-        return ret;
-    }
+    const MIN_LOG_BUF_SIZE: usize = 1024 * 10;
+    const MAX_LOG_BUF_SIZE: usize = (std::u32::MAX >> 8) as usize;
 
-    // 2. Grow the log buffer so we can capture verifier output
-    //    Retry this up to max_retries times
-    log.grow();
+    let mut log_buf = Vec::new();
     let mut retries = 0;
-
     loop {
-        let ret = f(log);
-        match ret {
-            Err((v, io_error)) if retries == 0 || io_error.raw_os_error() == Some(ENOSPC) => {
-                if retries == max_retries {
-                    return Err((v, io_error));
+        let ret = f(log_buf.as_mut_slice());
+        if retries != max_retries {
+            if let Err((_, io_error)) = &ret {
+                if retries == 0 || io_error.raw_os_error() == Some(ENOSPC) {
+                    let len = (log_buf.capacity() * 10).clamp(MIN_LOG_BUF_SIZE, MAX_LOG_BUF_SIZE);
+                    log_buf.resize(len, 0);
+                    if let Some(first) = log_buf.first_mut() {
+                        *first = 0;
+                    }
+                    retries += 1;
+                    continue;
                 }
-                retries += 1;
-                log.grow();
             }
-            r => return r,
         }
+        if let Some(pos) = log_buf.iter().position(|b| *b == 0) {
+            log_buf.truncate(pos);
+        }
+        let log_buf = if log_buf.is_empty() {
+            "none".into()
+        } else {
+            String::from_utf8(log_buf).unwrap().into()
+        };
+
+        break (ret, log_buf);
     }
 }
 
diff --git a/aya/src/util.rs b/aya/src/util.rs
index e31060464..b0bb37812 100644
--- a/aya/src/util.rs
+++ b/aya/src/util.rs
@@ -1,7 +1,7 @@
 //! Utility functions.
 use std::{
     collections::BTreeMap,
-    ffi::{CStr, CString},
+    ffi::CString,
     fs::{self, File},
     io::{self, BufReader},
     mem, slice,
@@ -200,57 +200,6 @@ pub(crate) fn bytes_of_slice<T: Pod>(val: &[T]) -> &[u8] {
     unsafe { slice::from_raw_parts(val.as_ptr().cast(), size) }
 }
 
-const MIN_LOG_BUF_SIZE: usize = 1024 * 10;
-const MAX_LOG_BUF_SIZE: usize = (std::u32::MAX >> 8) as usize;
-
-pub(crate) struct VerifierLog {
-    buf: Vec<u8>,
-}
-
-impl VerifierLog {
-    pub(crate) fn new() -> VerifierLog {
-        VerifierLog { buf: Vec::new() }
-    }
-
-    pub(crate) fn buf(&mut self) -> &mut Vec<u8> {
-        &mut self.buf
-    }
-
-    pub(crate) fn grow(&mut self) {
-        let len = (self.buf.capacity() * 10).clamp(MIN_LOG_BUF_SIZE, MAX_LOG_BUF_SIZE);
-        self.buf.resize(len, 0);
-        self.reset();
-    }
-
-    pub(crate) fn reset(&mut self) {
-        if !self.buf.is_empty() {
-            self.buf[0] = 0;
-        }
-    }
-
-    pub(crate) fn truncate(&mut self) {
-        if self.buf.is_empty() {
-            return;
-        }
-
-        let pos = self
-            .buf
-            .iter()
-            .position(|b| *b == 0)
-            .unwrap_or(self.buf.len() - 1);
-        self.buf[pos] = 0;
-        self.buf.truncate(pos + 1);
-    }
-
-    pub(crate) fn as_c_str(&self) -> Option<&CStr> {
-        if self.buf.is_empty() {
-            None
-        } else {
-            Some(CStr::from_bytes_with_nul(&self.buf).unwrap())
-        }
-    }
-}
-
 #[cfg(test)]
 mod tests {
     use super::*;

From b45a5bb71b485dbf05e21ddc4392bfda78f2e6f5 Mon Sep 17 00:00:00 2001
From: Tamir Duberstein <tamird@gmail.com>
Date: Sun, 9 Jul 2023 18:29:20 -0400
Subject: [PATCH 5/7] Get verifier logs when loading programs

---
 aya/src/bpf.rs                       | 91 +++++++++++++++-------------
 aya/src/programs/cgroup_skb.rs       |  3 +-
 aya/src/programs/cgroup_sock.rs      |  3 +-
 aya/src/programs/cgroup_sock_addr.rs |  3 +-
 aya/src/programs/cgroup_sockopt.rs   |  3 +-
 aya/src/programs/kprobe.rs           |  3 +-
 aya/src/programs/mod.rs              | 48 +++++++++------
 aya/src/programs/sk_skb.rs           |  3 +-
 aya/src/programs/tc.rs               |  3 +-
 aya/src/programs/uprobe.rs           |  3 +-
 aya/src/sys/bpf.rs                   | 14 +++--
 11 files changed, 106 insertions(+), 71 deletions(-)

diff --git a/aya/src/bpf.rs b/aya/src/bpf.rs
index ce4273d43..0c9d1d512 100644
--- a/aya/src/bpf.rs
+++ b/aya/src/bpf.rs
@@ -133,7 +133,7 @@ pub struct BpfLoader<'a> {
 
 bitflags! {
     /// Used to set the verifier log level flags in [BpfLoader](BpfLoader::verifier_log_level()).
-    #[derive(Debug)]
+    #[derive(Clone, Copy, Debug)]
     pub struct VerifierLogLevel: u32 {
         /// Sets no verifier logging.
         const DISABLE = 0;
@@ -349,14 +349,22 @@ impl<'a> BpfLoader<'a> {
     /// # Ok::<(), aya::BpfError>(())
     /// ```
     pub fn load(&mut self, data: &[u8]) -> Result<Bpf, BpfError> {
-        let verifier_log_level = self.verifier_log_level.bits();
+        let Self {
+            btf,
+            map_pin_path,
+            globals,
+            max_entries,
+            extensions,
+            verifier_log_level,
+        } = self;
         let mut obj = Object::parse(data)?;
-        obj.patch_map_data(self.globals.clone())?;
+        obj.patch_map_data(globals.clone())?;
 
         let btf_fd = if let Some(features) = &FEATURES.btf() {
             if let Some(btf) = obj.fixup_and_sanitize_btf(features)? {
                 // load btf to the kernel
-                Some(load_btf(btf.to_bytes())?)
+                let btf = load_btf(btf.to_bytes(), *verifier_log_level)?;
+                Some(btf)
             } else {
                 None
             }
@@ -364,7 +372,7 @@ impl<'a> BpfLoader<'a> {
             None
         };
 
-        if let Some(btf) = &self.btf {
+        if let Some(btf) = &btf {
             obj.relocate_btf(btf)?;
         }
         let mut maps = HashMap::new();
@@ -375,7 +383,7 @@ impl<'a> BpfLoader<'a> {
                 continue;
             }
 
-            match self.max_entries.get(name.as_str()) {
+            match max_entries.get(name.as_str()) {
                 Some(size) => obj.set_max_entries(*size),
                 None => {
                     if obj.map_type() == BPF_MAP_TYPE_PERF_EVENT_ARRAY as u32
@@ -400,7 +408,7 @@ impl<'a> BpfLoader<'a> {
             };
             let fd = match map.obj.pinning() {
                 PinningType::ByName => {
-                    let path = match &self.map_pin_path {
+                    let path = match &map_pin_path {
                         Some(p) => p,
                         None => return Err(BpfError::NoPinPath),
                     };
@@ -466,72 +474,72 @@ impl<'a> BpfLoader<'a> {
                 let section = prog_obj.section.clone();
                 let obj = (prog_obj, function_obj);
 
-                let program = if self.extensions.contains(name.as_str()) {
+                let program = if extensions.contains(name.as_str()) {
                     Program::Extension(Extension {
-                        data: ProgramData::new(prog_name, obj, btf_fd, verifier_log_level),
+                        data: ProgramData::new(prog_name, obj, btf_fd, *verifier_log_level),
                     })
                 } else {
                     match &section {
                         ProgramSection::KProbe { .. } => Program::KProbe(KProbe {
-                            data: ProgramData::new(prog_name, obj, btf_fd, verifier_log_level),
+                            data: ProgramData::new(prog_name, obj, btf_fd, *verifier_log_level),
                             kind: ProbeKind::KProbe,
                         }),
                         ProgramSection::KRetProbe { .. } => Program::KProbe(KProbe {
-                            data: ProgramData::new(prog_name, obj, btf_fd, verifier_log_level),
+                            data: ProgramData::new(prog_name, obj, btf_fd, *verifier_log_level),
                             kind: ProbeKind::KRetProbe,
                         }),
                         ProgramSection::UProbe { .. } => Program::UProbe(UProbe {
-                            data: ProgramData::new(prog_name, obj, btf_fd, verifier_log_level),
+                            data: ProgramData::new(prog_name, obj, btf_fd, *verifier_log_level),
                             kind: ProbeKind::UProbe,
                         }),
                         ProgramSection::URetProbe { .. } => Program::UProbe(UProbe {
-                            data: ProgramData::new(prog_name, obj, btf_fd, verifier_log_level),
+                            data: ProgramData::new(prog_name, obj, btf_fd, *verifier_log_level),
                             kind: ProbeKind::URetProbe,
                         }),
                         ProgramSection::TracePoint { .. } => Program::TracePoint(TracePoint {
-                            data: ProgramData::new(prog_name, obj, btf_fd, verifier_log_level),
+                            data: ProgramData::new(prog_name, obj, btf_fd, *verifier_log_level),
                         }),
                         ProgramSection::SocketFilter { .. } => {
                             Program::SocketFilter(SocketFilter {
-                                data: ProgramData::new(prog_name, obj, btf_fd, verifier_log_level),
+                                data: ProgramData::new(prog_name, obj, btf_fd, *verifier_log_level),
                             })
                         }
                         ProgramSection::Xdp { frags, .. } => {
                             let mut data =
-                                ProgramData::new(prog_name, obj, btf_fd, verifier_log_level);
+                                ProgramData::new(prog_name, obj, btf_fd, *verifier_log_level);
                             if *frags {
                                 data.flags = BPF_F_XDP_HAS_FRAGS;
                             }
                             Program::Xdp(Xdp { data })
                         }
                         ProgramSection::SkMsg { .. } => Program::SkMsg(SkMsg {
-                            data: ProgramData::new(prog_name, obj, btf_fd, verifier_log_level),
+                            data: ProgramData::new(prog_name, obj, btf_fd, *verifier_log_level),
                         }),
                         ProgramSection::CgroupSysctl { .. } => {
                             Program::CgroupSysctl(CgroupSysctl {
-                                data: ProgramData::new(prog_name, obj, btf_fd, verifier_log_level),
+                                data: ProgramData::new(prog_name, obj, btf_fd, *verifier_log_level),
                             })
                         }
                         ProgramSection::CgroupSockopt { attach_type, .. } => {
                             Program::CgroupSockopt(CgroupSockopt {
-                                data: ProgramData::new(prog_name, obj, btf_fd, verifier_log_level),
+                                data: ProgramData::new(prog_name, obj, btf_fd, *verifier_log_level),
                                 attach_type: *attach_type,
                             })
                         }
                         ProgramSection::SkSkbStreamParser { .. } => Program::SkSkb(SkSkb {
-                            data: ProgramData::new(prog_name, obj, btf_fd, verifier_log_level),
+                            data: ProgramData::new(prog_name, obj, btf_fd, *verifier_log_level),
                             kind: SkSkbKind::StreamParser,
                         }),
                         ProgramSection::SkSkbStreamVerdict { .. } => Program::SkSkb(SkSkb {
-                            data: ProgramData::new(prog_name, obj, btf_fd, verifier_log_level),
+                            data: ProgramData::new(prog_name, obj, btf_fd, *verifier_log_level),
                             kind: SkSkbKind::StreamVerdict,
                         }),
                         ProgramSection::SockOps { .. } => Program::SockOps(SockOps {
-                            data: ProgramData::new(prog_name, obj, btf_fd, verifier_log_level),
+                            data: ProgramData::new(prog_name, obj, btf_fd, *verifier_log_level),
                         }),
                         ProgramSection::SchedClassifier { .. } => {
                             Program::SchedClassifier(SchedClassifier {
-                                data: ProgramData::new(prog_name, obj, btf_fd, verifier_log_level),
+                                data: ProgramData::new(prog_name, obj, btf_fd, *verifier_log_level),
                                 name: unsafe {
                                     CString::from_vec_unchecked(Vec::from(name.clone()))
                                         .into_boxed_c_str()
@@ -539,37 +547,37 @@ impl<'a> BpfLoader<'a> {
                             })
                         }
                         ProgramSection::CgroupSkb { .. } => Program::CgroupSkb(CgroupSkb {
-                            data: ProgramData::new(prog_name, obj, btf_fd, verifier_log_level),
+                            data: ProgramData::new(prog_name, obj, btf_fd, *verifier_log_level),
                             expected_attach_type: None,
                         }),
                         ProgramSection::CgroupSkbIngress { .. } => Program::CgroupSkb(CgroupSkb {
-                            data: ProgramData::new(prog_name, obj, btf_fd, verifier_log_level),
+                            data: ProgramData::new(prog_name, obj, btf_fd, *verifier_log_level),
                             expected_attach_type: Some(CgroupSkbAttachType::Ingress),
                         }),
                         ProgramSection::CgroupSkbEgress { .. } => Program::CgroupSkb(CgroupSkb {
-                            data: ProgramData::new(prog_name, obj, btf_fd, verifier_log_level),
+                            data: ProgramData::new(prog_name, obj, btf_fd, *verifier_log_level),
                             expected_attach_type: Some(CgroupSkbAttachType::Egress),
                         }),
                         ProgramSection::CgroupSockAddr { attach_type, .. } => {
                             Program::CgroupSockAddr(CgroupSockAddr {
-                                data: ProgramData::new(prog_name, obj, btf_fd, verifier_log_level),
+                                data: ProgramData::new(prog_name, obj, btf_fd, *verifier_log_level),
                                 attach_type: *attach_type,
                             })
                         }
                         ProgramSection::LircMode2 { .. } => Program::LircMode2(LircMode2 {
-                            data: ProgramData::new(prog_name, obj, btf_fd, verifier_log_level),
+                            data: ProgramData::new(prog_name, obj, btf_fd, *verifier_log_level),
                         }),
                         ProgramSection::PerfEvent { .. } => Program::PerfEvent(PerfEvent {
-                            data: ProgramData::new(prog_name, obj, btf_fd, verifier_log_level),
+                            data: ProgramData::new(prog_name, obj, btf_fd, *verifier_log_level),
                         }),
                         ProgramSection::RawTracePoint { .. } => {
                             Program::RawTracePoint(RawTracePoint {
-                                data: ProgramData::new(prog_name, obj, btf_fd, verifier_log_level),
+                                data: ProgramData::new(prog_name, obj, btf_fd, *verifier_log_level),
                             })
                         }
                         ProgramSection::Lsm { sleepable, .. } => {
                             let mut data =
-                                ProgramData::new(prog_name, obj, btf_fd, verifier_log_level);
+                                ProgramData::new(prog_name, obj, btf_fd, *verifier_log_level);
                             if *sleepable {
                                 data.flags = BPF_F_SLEEPABLE;
                             }
@@ -577,30 +585,30 @@ impl<'a> BpfLoader<'a> {
                         }
                         ProgramSection::BtfTracePoint { .. } => {
                             Program::BtfTracePoint(BtfTracePoint {
-                                data: ProgramData::new(prog_name, obj, btf_fd, verifier_log_level),
+                                data: ProgramData::new(prog_name, obj, btf_fd, *verifier_log_level),
                             })
                         }
                         ProgramSection::FEntry { .. } => Program::FEntry(FEntry {
-                            data: ProgramData::new(prog_name, obj, btf_fd, verifier_log_level),
+                            data: ProgramData::new(prog_name, obj, btf_fd, *verifier_log_level),
                         }),
                         ProgramSection::FExit { .. } => Program::FExit(FExit {
-                            data: ProgramData::new(prog_name, obj, btf_fd, verifier_log_level),
+                            data: ProgramData::new(prog_name, obj, btf_fd, *verifier_log_level),
                         }),
                         ProgramSection::Extension { .. } => Program::Extension(Extension {
-                            data: ProgramData::new(prog_name, obj, btf_fd, verifier_log_level),
+                            data: ProgramData::new(prog_name, obj, btf_fd, *verifier_log_level),
                         }),
                         ProgramSection::SkLookup { .. } => Program::SkLookup(SkLookup {
-                            data: ProgramData::new(prog_name, obj, btf_fd, verifier_log_level),
+                            data: ProgramData::new(prog_name, obj, btf_fd, *verifier_log_level),
                         }),
                         ProgramSection::CgroupSock { attach_type, .. } => {
                             Program::CgroupSock(CgroupSock {
-                                data: ProgramData::new(prog_name, obj, btf_fd, verifier_log_level),
+                                data: ProgramData::new(prog_name, obj, btf_fd, *verifier_log_level),
                                 attach_type: *attach_type,
                             })
                         }
                         ProgramSection::CgroupDevice { .. } => {
                             Program::CgroupDevice(CgroupDevice {
-                                data: ProgramData::new(prog_name, obj, btf_fd, verifier_log_level),
+                                data: ProgramData::new(prog_name, obj, btf_fd, *verifier_log_level),
                             })
                         }
                     }
@@ -906,9 +914,10 @@ pub enum BpfError {
     ProgramError(#[from] ProgramError),
 }
 
-fn load_btf(raw_btf: Vec<u8>) -> Result<RawFd, BtfError> {
-    let (ret, verifier_log) =
-        retry_with_verifier_logs(10, |logger| bpf_load_btf(raw_btf.as_slice(), logger));
+fn load_btf(raw_btf: Vec<u8>, verifier_log_level: VerifierLogLevel) -> Result<RawFd, BtfError> {
+    let (ret, verifier_log) = retry_with_verifier_logs(10, |logger| {
+        bpf_load_btf(raw_btf.as_slice(), logger, verifier_log_level)
+    });
     match ret {
         Ok(fd) => Ok(fd as RawFd),
         Err((_, io_error)) => Err(BtfError::LoadError {
diff --git a/aya/src/programs/cgroup_skb.rs b/aya/src/programs/cgroup_skb.rs
index f275441f3..a76bb14ab 100644
--- a/aya/src/programs/cgroup_skb.rs
+++ b/aya/src/programs/cgroup_skb.rs
@@ -16,6 +16,7 @@ use crate::{
         define_link_wrapper, load_program, FdLink, Link, ProgAttachLink, ProgramData, ProgramError,
     },
     sys::{bpf_link_create, bpf_prog_attach},
+    VerifierLogLevel,
 };
 
 /// A program used to inspect or filter network activity for a given cgroup.
@@ -151,7 +152,7 @@ impl CgroupSkb {
         path: P,
         expected_attach_type: CgroupSkbAttachType,
     ) -> Result<Self, ProgramError> {
-        let data = ProgramData::from_pinned_path(path)?;
+        let data = ProgramData::from_pinned_path(path, VerifierLogLevel::default())?;
         Ok(Self {
             data,
             expected_attach_type: Some(expected_attach_type),
diff --git a/aya/src/programs/cgroup_sock.rs b/aya/src/programs/cgroup_sock.rs
index bddea214c..19ac6cf8e 100644
--- a/aya/src/programs/cgroup_sock.rs
+++ b/aya/src/programs/cgroup_sock.rs
@@ -15,6 +15,7 @@ use crate::{
         define_link_wrapper, load_program, FdLink, Link, ProgAttachLink, ProgramData, ProgramError,
     },
     sys::{bpf_link_create, bpf_prog_attach},
+    VerifierLogLevel,
 };
 
 /// A program that is called on socket creation, bind and release.
@@ -126,7 +127,7 @@ impl CgroupSock {
         path: P,
         attach_type: CgroupSockAttachType,
     ) -> Result<Self, ProgramError> {
-        let data = ProgramData::from_pinned_path(path)?;
+        let data = ProgramData::from_pinned_path(path, VerifierLogLevel::default())?;
         Ok(Self { data, attach_type })
     }
 }
diff --git a/aya/src/programs/cgroup_sock_addr.rs b/aya/src/programs/cgroup_sock_addr.rs
index 555a7c954..72eca7ecc 100644
--- a/aya/src/programs/cgroup_sock_addr.rs
+++ b/aya/src/programs/cgroup_sock_addr.rs
@@ -15,6 +15,7 @@ use crate::{
         define_link_wrapper, load_program, FdLink, Link, ProgAttachLink, ProgramData, ProgramError,
     },
     sys::{bpf_link_create, bpf_prog_attach},
+    VerifierLogLevel,
 };
 
 /// A program that can be used to inspect or modify socket addresses (`struct sockaddr`).
@@ -132,7 +133,7 @@ impl CgroupSockAddr {
         path: P,
         attach_type: CgroupSockAddrAttachType,
     ) -> Result<Self, ProgramError> {
-        let data = ProgramData::from_pinned_path(path)?;
+        let data = ProgramData::from_pinned_path(path, VerifierLogLevel::default())?;
         Ok(Self { data, attach_type })
     }
 }
diff --git a/aya/src/programs/cgroup_sockopt.rs b/aya/src/programs/cgroup_sockopt.rs
index 9f855cb01..9d8b314a8 100644
--- a/aya/src/programs/cgroup_sockopt.rs
+++ b/aya/src/programs/cgroup_sockopt.rs
@@ -15,6 +15,7 @@ use crate::{
         define_link_wrapper, load_program, FdLink, Link, ProgAttachLink, ProgramData, ProgramError,
     },
     sys::{bpf_link_create, bpf_prog_attach},
+    VerifierLogLevel,
 };
 
 /// A program that can be used to get or set options on sockets.
@@ -127,7 +128,7 @@ impl CgroupSockopt {
         path: P,
         attach_type: CgroupSockoptAttachType,
     ) -> Result<Self, ProgramError> {
-        let data = ProgramData::from_pinned_path(path)?;
+        let data = ProgramData::from_pinned_path(path, VerifierLogLevel::default())?;
         Ok(Self { data, attach_type })
     }
 }
diff --git a/aya/src/programs/kprobe.rs b/aya/src/programs/kprobe.rs
index 65a505d27..53dac2169 100644
--- a/aya/src/programs/kprobe.rs
+++ b/aya/src/programs/kprobe.rs
@@ -10,6 +10,7 @@ use crate::{
         probe::{attach, ProbeKind},
         ProgramData, ProgramError,
     },
+    VerifierLogLevel,
 };
 
 /// A kernel probe.
@@ -91,7 +92,7 @@ impl KProbe {
     /// On drop, any managed links are detached and the program is unloaded. This will not result in
     /// the program being unloaded from the kernel if it is still pinned.
     pub fn from_pin<P: AsRef<Path>>(path: P, kind: ProbeKind) -> Result<Self, ProgramError> {
-        let data = ProgramData::from_pinned_path(path)?;
+        let data = ProgramData::from_pinned_path(path, VerifierLogLevel::default())?;
         Ok(Self { data, kind })
     }
 }
diff --git a/aya/src/programs/mod.rs b/aya/src/programs/mod.rs
index 793c94e62..3ca6854c3 100644
--- a/aya/src/programs/mod.rs
+++ b/aya/src/programs/mod.rs
@@ -114,6 +114,7 @@ use crate::{
         bpf_prog_get_fd_by_id, bpf_prog_get_info_by_fd, bpf_prog_get_next_id, bpf_prog_query,
         retry_with_verifier_logs, BpfLoadProgramAttrs,
     },
+    VerifierLogLevel,
 };
 
 /// Error type returned when working with programs.
@@ -414,7 +415,7 @@ pub(crate) struct ProgramData<T: Link> {
     pub(crate) attach_btf_id: Option<u32>,
     pub(crate) attach_prog_fd: Option<RawFd>,
     pub(crate) btf_fd: Option<RawFd>,
-    pub(crate) verifier_log_level: u32,
+    pub(crate) verifier_log_level: VerifierLogLevel,
     pub(crate) path: Option<PathBuf>,
     pub(crate) flags: u32,
 }
@@ -424,7 +425,7 @@ impl<T: Link> ProgramData<T> {
         name: Option<String>,
         obj: (obj::Program, obj::Function),
         btf_fd: Option<RawFd>,
-        verifier_log_level: u32,
+        verifier_log_level: VerifierLogLevel,
     ) -> ProgramData<T> {
         ProgramData {
             name,
@@ -447,6 +448,7 @@ impl<T: Link> ProgramData<T> {
         fd: RawFd,
         path: &Path,
         info: bpf_prog_info,
+        verifier_log_level: VerifierLogLevel,
     ) -> Result<ProgramData<T>, ProgramError> {
         let attach_btf_id = if info.attach_btf_id > 0 {
             Some(info.attach_btf_id)
@@ -475,7 +477,7 @@ impl<T: Link> ProgramData<T> {
             attach_btf_id,
             attach_prog_fd: None,
             btf_fd: None,
-            verifier_log_level: 0,
+            verifier_log_level,
             path: Some(path.to_path_buf()),
             flags: 0,
         })
@@ -483,6 +485,7 @@ impl<T: Link> ProgramData<T> {
 
     pub(crate) fn from_pinned_path<P: AsRef<Path>>(
         path: P,
+        verifier_log_level: VerifierLogLevel,
     ) -> Result<ProgramData<T>, ProgramError> {
         let path_string =
             CString::new(path.as_ref().as_os_str().to_string_lossy().as_bytes()).unwrap();
@@ -497,9 +500,8 @@ impl<T: Link> ProgramData<T> {
             io_error,
         })?;
 
-        let info = ProgramInfo(info);
-        let name = info.name_as_str().map(|s| s.to_string());
-        ProgramData::from_bpf_prog_info(name, fd, path.as_ref(), info.0)
+        let name = ProgramInfo(info).name_as_str().map(|s| s.to_string());
+        ProgramData::from_bpf_prog_info(name, fd, path.as_ref(), info, verifier_log_level)
     }
 }
 
@@ -549,7 +551,20 @@ fn load_program<T: Link>(
     prog_type: bpf_prog_type,
     data: &mut ProgramData<T>,
 ) -> Result<(), ProgramError> {
-    let ProgramData { obj, fd, .. } = data;
+    let ProgramData {
+        name,
+        obj,
+        fd,
+        links: _,
+        expected_attach_type,
+        attach_btf_obj_fd,
+        attach_btf_id,
+        attach_prog_fd,
+        btf_fd,
+        verifier_log_level,
+        path: _,
+        flags,
+    } = data;
     if fd.is_some() {
         return Err(ProgramError::AlreadyLoaded);
     }
@@ -583,7 +598,7 @@ fn load_program<T: Link>(
         (u32::from(major) << 16) + (u32::from(minor) << 8) + u32::from(patch)
     });
 
-    let prog_name = if let Some(name) = &data.name {
+    let prog_name = if let Some(name) = name {
         let mut name = name.clone();
         if name.len() > 15 {
             name.truncate(15);
@@ -601,21 +616,20 @@ fn load_program<T: Link>(
         insns: instructions,
         license,
         kernel_version: target_kernel_version,
-        expected_attach_type: data.expected_attach_type,
-        prog_btf_fd: data.btf_fd,
-        attach_btf_obj_fd: data.attach_btf_obj_fd,
-        attach_btf_id: data.attach_btf_id,
-        attach_prog_fd: data.attach_prog_fd,
+        expected_attach_type: *expected_attach_type,
+        prog_btf_fd: *btf_fd,
+        attach_btf_obj_fd: *attach_btf_obj_fd,
+        attach_btf_id: *attach_btf_id,
+        attach_prog_fd: *attach_prog_fd,
         func_info_rec_size: *func_info_rec_size,
         func_info: func_info.clone(),
         line_info_rec_size: *line_info_rec_size,
         line_info: line_info.clone(),
-        flags: data.flags,
+        flags: *flags,
     };
 
-    let verifier_log_level = data.verifier_log_level;
     let (ret, verifier_log) = retry_with_verifier_logs(10, |logger| {
-        bpf_load_program(&attr, logger, verifier_log_level)
+        bpf_load_program(&attr, logger, *verifier_log_level)
     });
 
     match ret {
@@ -823,7 +837,7 @@ macro_rules! impl_from_pin {
                 /// On drop, any managed links are detached and the program is unloaded. This will not result in
                 /// the program being unloaded from the kernel if it is still pinned.
                 pub fn from_pin<P: AsRef<Path>>(path: P) -> Result<Self, ProgramError> {
-                    let data = ProgramData::from_pinned_path(path)?;
+                    let data = ProgramData::from_pinned_path(path, VerifierLogLevel::default())?;
                     Ok(Self { data })
                 }
             }
diff --git a/aya/src/programs/sk_skb.rs b/aya/src/programs/sk_skb.rs
index df7729e88..ec90a1749 100644
--- a/aya/src/programs/sk_skb.rs
+++ b/aya/src/programs/sk_skb.rs
@@ -13,6 +13,7 @@ use crate::{
         ProgramError,
     },
     sys::bpf_prog_attach,
+    VerifierLogLevel,
 };
 
 /// The kind of [`SkSkb`] program.
@@ -113,7 +114,7 @@ impl SkSkb {
     /// On drop, any managed links are detached and the program is unloaded. This will not result in
     /// the program being unloaded from the kernel if it is still pinned.
     pub fn from_pin<P: AsRef<Path>>(path: P, kind: SkSkbKind) -> Result<Self, ProgramError> {
-        let data = ProgramData::from_pinned_path(path)?;
+        let data = ProgramData::from_pinned_path(path, VerifierLogLevel::default())?;
         Ok(Self { data, kind })
     }
 }
diff --git a/aya/src/programs/tc.rs b/aya/src/programs/tc.rs
index da20c6358..fad633a33 100644
--- a/aya/src/programs/tc.rs
+++ b/aya/src/programs/tc.rs
@@ -17,6 +17,7 @@ use crate::{
         netlink_qdisc_detach,
     },
     util::{ifindex_from_ifname, tc_handler_make},
+    VerifierLogLevel,
 };
 
 /// Traffic control attach type.
@@ -199,7 +200,7 @@ impl SchedClassifier {
     /// On drop, any managed links are detached and the program is unloaded. This will not result in
     /// the program being unloaded from the kernel if it is still pinned.
     pub fn from_pin<P: AsRef<Path>>(path: P) -> Result<Self, ProgramError> {
-        let data = ProgramData::from_pinned_path(path)?;
+        let data = ProgramData::from_pinned_path(path, VerifierLogLevel::default())?;
         let cname = CString::new(data.name.clone().unwrap_or_default())
             .unwrap()
             .into_boxed_c_str();
diff --git a/aya/src/programs/uprobe.rs b/aya/src/programs/uprobe.rs
index 5c38e5de2..b632407e5 100644
--- a/aya/src/programs/uprobe.rs
+++ b/aya/src/programs/uprobe.rs
@@ -21,6 +21,7 @@ use crate::{
         probe::{attach, ProbeKind},
         ProgramData, ProgramError,
     },
+    VerifierLogLevel,
 };
 
 const LD_SO_CACHE_FILE: &str = "/etc/ld.so.cache";
@@ -145,7 +146,7 @@ impl UProbe {
     /// On drop, any managed links are detached and the program is unloaded. This will not result in
     /// the program being unloaded from the kernel if it is still pinned.
     pub fn from_pin<P: AsRef<Path>>(path: P, kind: ProbeKind) -> Result<Self, ProgramError> {
-        let data = ProgramData::from_pinned_path(path)?;
+        let data = ProgramData::from_pinned_path(path, VerifierLogLevel::default())?;
         Ok(Self { data, kind })
     }
 }
diff --git a/aya/src/sys/bpf.rs b/aya/src/sys/bpf.rs
index b5fe2d64f..5a7dc3c56 100644
--- a/aya/src/sys/bpf.rs
+++ b/aya/src/sys/bpf.rs
@@ -30,7 +30,7 @@ use crate::{
         copy_instructions,
     },
     sys::{syscall, SysResult, Syscall},
-    Btf, Pod, BPF_OBJ_NAME_LEN,
+    Btf, Pod, VerifierLogLevel, BPF_OBJ_NAME_LEN,
 };
 
 pub(crate) fn bpf_create_map(
@@ -130,7 +130,7 @@ pub(crate) struct BpfLoadProgramAttrs<'a> {
 pub(crate) fn bpf_load_program(
     aya_attr: &BpfLoadProgramAttrs,
     log_buf: &mut [u8],
-    verifier_log_level: u32,
+    verifier_log_level: VerifierLogLevel,
 ) -> SysResult {
     let mut attr = unsafe { mem::zeroed::<bpf_attr>() };
 
@@ -175,7 +175,7 @@ pub(crate) fn bpf_load_program(
         }
     }
     if !log_buf.is_empty() {
-        u.log_level = verifier_log_level;
+        u.log_level = verifier_log_level.bits();
         u.log_buf = log_buf.as_mut_ptr() as u64;
         u.log_size = log_buf.len() as u32;
     }
@@ -548,13 +548,17 @@ pub(crate) fn bpf_raw_tracepoint_open(name: Option<&CStr>, prog_fd: RawFd) -> Sy
     sys_bpf(bpf_cmd::BPF_RAW_TRACEPOINT_OPEN, &attr)
 }
 
-pub(crate) fn bpf_load_btf(raw_btf: &[u8], log_buf: &mut [u8]) -> SysResult {
+pub(crate) fn bpf_load_btf(
+    raw_btf: &[u8],
+    log_buf: &mut [u8],
+    verifier_log_level: VerifierLogLevel,
+) -> SysResult {
     let mut attr = unsafe { mem::zeroed::<bpf_attr>() };
     let u = unsafe { &mut attr.__bindgen_anon_7 };
     u.btf = raw_btf.as_ptr() as *const _ as u64;
     u.btf_size = mem::size_of_val(raw_btf) as u32;
     if !log_buf.is_empty() {
-        u.btf_log_level = 1;
+        u.btf_log_level = verifier_log_level.bits();
         u.btf_log_buf = log_buf.as_mut_ptr() as u64;
         u.btf_log_size = log_buf.len() as u32;
     }

From 91a415f095bc182deac618b4b63e0e075fa3ef63 Mon Sep 17 00:00:00 2001
From: Tamir Duberstein <tamird@gmail.com>
Date: Sun, 9 Jul 2023 18:14:27 -0400
Subject: [PATCH 6/7] Skip relocation tests on unsupported kernels

---
 test/integration-test/tests/btf_relocations.rs | 16 ++++++++++++++++
 test/integration-test/tests/load.rs            | 10 ++++++----
 test/integration-test/tests/smoke.rs           |  5 +++--
 3 files changed, 25 insertions(+), 6 deletions(-)

diff --git a/test/integration-test/tests/btf_relocations.rs b/test/integration-test/tests/btf_relocations.rs
index 34bc3dbe7..069bc76c4 100644
--- a/test/integration-test/tests/btf_relocations.rs
+++ b/test/integration-test/tests/btf_relocations.rs
@@ -1,4 +1,5 @@
 use anyhow::{bail, Context as _, Result};
+use procfs::KernelVersion;
 use std::{path::PathBuf, process::Command, thread::sleep, time::Duration};
 use tempfile::TempDir;
 
@@ -60,6 +61,11 @@ fn relocate_enum() {
 
 #[test]
 fn relocate_enum_signed() {
+    let kernel_version = KernelVersion::current().unwrap();
+    if kernel_version < KernelVersion::new(6, 0, 0) {
+        eprintln!("skipping test on kernel {kernel_version:?}, support for signed enum was added in 6.0.0; see https://github.com/torvalds/linux/commit/6089fb3");
+        return;
+    }
     let test = RelocationTest {
         local_definition: r#"
             enum foo { D = -0x7AAAAAAA };
@@ -80,6 +86,11 @@ fn relocate_enum_signed() {
 
 #[test]
 fn relocate_enum64() {
+    let kernel_version = KernelVersion::current().unwrap();
+    if kernel_version < KernelVersion::new(6, 0, 0) {
+        eprintln!("skipping test on kernel {kernel_version:?}, support for enum64 was added in 6.0.0; see https://github.com/torvalds/linux/commit/6089fb3");
+        return;
+    }
     let test = RelocationTest {
         local_definition: r#"
             enum foo { D = 0xAAAAAAAABBBBBBBB };
@@ -100,6 +111,11 @@ fn relocate_enum64() {
 
 #[test]
 fn relocate_enum64_signed() {
+    let kernel_version = KernelVersion::current().unwrap();
+    if kernel_version < KernelVersion::new(6, 0, 0) {
+        eprintln!("skipping test on kernel {kernel_version:?}, support for enum64 was added in 6.0.0; see https://github.com/torvalds/linux/commit/6089fb3");
+        return;
+    }
     let test = RelocationTest {
         local_definition: r#"
             enum foo { D = -0xAAAAAAABBBBBBBB };
diff --git a/test/integration-test/tests/load.rs b/test/integration-test/tests/load.rs
index d242338d2..e27e46f01 100644
--- a/test/integration-test/tests/load.rs
+++ b/test/integration-test/tests/load.rs
@@ -131,8 +131,9 @@ fn unload_kprobe() {
 
 #[test]
 fn pin_link() {
-    if KernelVersion::current().unwrap() < KernelVersion::new(5, 9, 0) {
-        eprintln!("skipping test, XDP uses netlink");
+    let kernel_version = KernelVersion::current().unwrap();
+    if kernel_version < KernelVersion::new(5, 9, 0) {
+        eprintln!("skipping test on kernel {kernel_version:?}, XDP uses netlink");
         return;
     }
 
@@ -166,8 +167,9 @@ fn pin_link() {
 
 #[test]
 fn pin_lifecycle() {
-    if KernelVersion::current().unwrap() < KernelVersion::new(5, 9, 0) {
-        eprintln!("skipping test, XDP uses netlink");
+    let kernel_version = KernelVersion::current().unwrap();
+    if kernel_version < KernelVersion::new(5, 9, 0) {
+        eprintln!("skipping test on kernel {kernel_version:?}, XDP uses netlink");
         return;
     }
 
diff --git a/test/integration-test/tests/smoke.rs b/test/integration-test/tests/smoke.rs
index b30060721..51c545551 100644
--- a/test/integration-test/tests/smoke.rs
+++ b/test/integration-test/tests/smoke.rs
@@ -17,8 +17,9 @@ fn xdp() {
 
 #[test]
 fn extension() {
-    if KernelVersion::current().unwrap() < KernelVersion::new(5, 9, 0) {
-        eprintln!("skipping test, XDP uses netlink");
+    let kernel_version = KernelVersion::current().unwrap();
+    if kernel_version < KernelVersion::new(5, 9, 0) {
+        eprintln!("skipping test on kernel {kernel_version:?}, XDP uses netlink");
         return;
     }
     let main_bytes =

From b8252f46d9c268e8973f1c87ce7a7986d77d861f Mon Sep 17 00:00:00 2001
From: Tamir Duberstein <tamird@gmail.com>
Date: Sun, 9 Jul 2023 19:39:41 -0400
Subject: [PATCH 7/7] Skip BPF_F_XDP_HAS_FRAGS tests on unsupported kernels

---
 test/integration-ebpf/src/pass.rs    | 2 ++
 test/integration-test/tests/load.rs  | 4 ++--
 test/integration-test/tests/smoke.rs | 6 ++++++
 3 files changed, 10 insertions(+), 2 deletions(-)

diff --git a/test/integration-ebpf/src/pass.rs b/test/integration-ebpf/src/pass.rs
index b1bdde993..8aaf085f2 100644
--- a/test/integration-ebpf/src/pass.rs
+++ b/test/integration-ebpf/src/pass.rs
@@ -3,6 +3,8 @@
 
 use aya_bpf::{bindings::xdp_action, macros::xdp, programs::XdpContext};
 
+// Note: the `frags` attribute causes this probe to be incompatible with kernel versions < 5.18.0.
+// See https://github.com/torvalds/linux/commit/c2f2cdb.
 #[xdp(name = "pass", frags = "true")]
 pub fn pass(ctx: XdpContext) -> u32 {
     match unsafe { try_pass(ctx) } {
diff --git a/test/integration-test/tests/load.rs b/test/integration-test/tests/load.rs
index e27e46f01..47a851cf2 100644
--- a/test/integration-test/tests/load.rs
+++ b/test/integration-test/tests/load.rs
@@ -168,8 +168,8 @@ fn pin_link() {
 #[test]
 fn pin_lifecycle() {
     let kernel_version = KernelVersion::current().unwrap();
-    if kernel_version < KernelVersion::new(5, 9, 0) {
-        eprintln!("skipping test on kernel {kernel_version:?}, XDP uses netlink");
+    if kernel_version < KernelVersion::new(5, 18, 0) {
+        eprintln!("skipping test on kernel {kernel_version:?}, support for BPF_F_XDP_HAS_FRAGS was added in 5.18.0; see https://github.com/torvalds/linux/commit/c2f2cdb");
         return;
     }
 
diff --git a/test/integration-test/tests/smoke.rs b/test/integration-test/tests/smoke.rs
index 51c545551..2bc10ff42 100644
--- a/test/integration-test/tests/smoke.rs
+++ b/test/integration-test/tests/smoke.rs
@@ -8,6 +8,12 @@ use aya::{
 
 #[test]
 fn xdp() {
+    let kernel_version = KernelVersion::current().unwrap();
+    if kernel_version < KernelVersion::new(5, 18, 0) {
+        eprintln!("skipping test on kernel {kernel_version:?}, support for BPF_F_XDP_HAS_FRAGS was added in 5.18.0; see https://github.com/torvalds/linux/commit/c2f2cdb");
+        return;
+    }
+
     let bytes = include_bytes_aligned!("../../../target/bpfel-unknown-none/release/pass");
     let mut bpf = Bpf::load(bytes).unwrap();
     let dispatcher: &mut Xdp = bpf.program_mut("pass").unwrap().try_into().unwrap();