diff --git a/aya/src/bpf.rs b/aya/src/bpf.rs
index 818a063b..a9bd5486 100644
--- a/aya/src/bpf.rs
+++ b/aya/src/bpf.rs
@@ -3,7 +3,10 @@ use std::{
     collections::{HashMap, HashSet},
     ffi::CString,
     fs, io,
-    os::{fd::RawFd, raw::c_int},
+    os::{
+        fd::{OwnedFd, RawFd},
+        raw::c_int,
+    },
     path::{Path, PathBuf},
 };
 
@@ -473,7 +476,6 @@ impl<'a> BpfLoader<'a> {
                 obj,
                 fd: None,
                 pinned: false,
-                btf_fd,
             };
             let fd = match map.obj.pinning() {
                 PinningType::ByName => {
@@ -488,7 +490,7 @@ impl<'a> BpfLoader<'a> {
                             fd as RawFd
                         }
                         Err(_) => {
-                            let fd = map.create(&name)?;
+                            let fd = map.create(&name, btf_fd.as_ref())?;
                             map.pin(&name, path).map_err(|error| MapError::PinError {
                                 name: Some(name.to_string()),
                                 error,
@@ -497,7 +499,7 @@ impl<'a> BpfLoader<'a> {
                         }
                     }
                 }
-                PinningType::None => map.create(&name)?,
+                PinningType::None => map.create(&name, btf_fd.as_ref())?,
             };
             if !map.obj.data().is_empty() && map.obj.section_kind() != BpfSectionKind::Bss {
                 bpf_map_update_elem_ptr(fd, &0 as *const _, map.obj.data_mut().as_mut_ptr(), 0)
@@ -545,70 +547,69 @@ impl<'a> BpfLoader<'a> {
 
                 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, *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, *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, *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, *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, *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, *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, *verifier_log_level),
                             })
                         }
                         ProgramSection::Xdp { frags, .. } => {
-                            let mut data =
-                                ProgramData::new(prog_name, obj, btf_fd, *verifier_log_level);
+                            let mut data = ProgramData::new(prog_name, obj, *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, *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, *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, *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, *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, *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, *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, *verifier_log_level),
                                 name: unsafe {
                                     CString::from_vec_unchecked(Vec::from(name.clone()))
                                         .into_boxed_c_str()
@@ -616,37 +617,36 @@ 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, *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, *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, *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, *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, *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, *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, *verifier_log_level),
                             })
                         }
                         ProgramSection::Lsm { sleepable, .. } => {
-                            let mut data =
-                                ProgramData::new(prog_name, obj, btf_fd, *verifier_log_level);
+                            let mut data = ProgramData::new(prog_name, obj, *verifier_log_level);
                             if *sleepable {
                                 data.flags = BPF_F_SLEEPABLE;
                             }
@@ -654,30 +654,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, *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, *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, *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, *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, *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, *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, *verifier_log_level),
                             })
                         }
                     }
@@ -699,7 +699,11 @@ impl<'a> BpfLoader<'a> {
             })?;
         };
 
-        Ok(Bpf { maps, programs })
+        Ok(Bpf {
+            maps,
+            programs,
+            btf_fd,
+        })
     }
 }
 
@@ -743,9 +747,26 @@ impl<'a> Default for BpfLoader<'a> {
 
 /// The main entry point into the library, used to work with eBPF programs and maps.
 #[derive(Debug)]
+#[non_exhaustive]
 pub struct Bpf {
-    maps: HashMap<String, Map>,
-    programs: HashMap<String, Program>,
+    /// Maps that were loaded from the BPF object
+    ///
+    /// After the BPF object is loaded, a `Bpf` instance keeps these
+    /// maps to manage their lifetime during drop but otherwise does
+    /// not touch them.
+    pub maps: HashMap<String, Map>,
+    /// Programs that were found in the BPF object
+    ///
+    /// Unlike maps, programs do not start loaded nor attached, that
+    /// is a separate action. After the BPF object is loaded, a `Bpf`
+    /// instance keeps these programs to manage their lifetime during
+    /// drop but otherwise does not touch them.
+    pub programs: HashMap<String, Program>,
+    /// The file descriptor of the loaded BTF section from the BPF
+    /// object.
+    ///
+    /// This may be used when loading programs
+    pub btf_fd: Option<OwnedFd>,
 }
 
 impl Bpf {
@@ -882,8 +903,8 @@ impl Bpf {
     /// # let mut bpf = aya::Bpf::load(&[])?;
     /// use aya::programs::UProbe;
     ///
-    /// let program: &mut UProbe = bpf.program_mut("SSL_read").unwrap().try_into()?;
-    /// program.load()?;
+    /// let program: &mut UProbe = bpf.programs.get_mut("SSL_read").unwrap().try_into()?;
+    /// program.load(bpf.btf_fd.as_ref())?;
     /// program.attach(Some("SSL_read"), 0, "libssl", None)?;
     /// # Ok::<(), aya::BpfError>(())
     /// ```
@@ -993,12 +1014,12 @@ pub enum BpfError {
     ProgramError(#[from] ProgramError),
 }
 
-fn load_btf(raw_btf: Vec<u8>, verifier_log_level: VerifierLogLevel) -> Result<RawFd, BtfError> {
+fn load_btf(raw_btf: Vec<u8>, verifier_log_level: VerifierLogLevel) -> Result<OwnedFd, 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),
+        Ok(fd) => Ok(fd),
         Err((_, io_error)) => Err(BtfError::LoadError {
             io_error,
             verifier_log,
diff --git a/aya/src/maps/bloom_filter.rs b/aya/src/maps/bloom_filter.rs
index f646c2bb..93df55fe 100644
--- a/aya/src/maps/bloom_filter.rs
+++ b/aya/src/maps/bloom_filter.rs
@@ -118,7 +118,6 @@ mod tests {
             obj: new_obj_map(),
             fd: None,
             pinned: false,
-            btf_fd: None,
         };
         assert_matches!(
             BloomFilter::<_, u16>::new(&map),
@@ -147,7 +146,6 @@ mod tests {
             }),
             fd: None,
             pinned: false,
-            btf_fd: None,
         };
 
         let map = Map::PerfEventArray(map_data);
@@ -164,7 +162,6 @@ mod tests {
             obj: new_obj_map(),
             fd: None,
             pinned: false,
-            btf_fd: None,
         };
 
         assert_matches!(
@@ -179,7 +176,6 @@ mod tests {
             obj: new_obj_map(),
             fd: Some(42),
             pinned: false,
-            btf_fd: None,
         };
 
         assert!(BloomFilter::<_, u32>::new(&mut map).is_ok());
@@ -191,7 +187,6 @@ mod tests {
             obj: new_obj_map(),
             fd: Some(42),
             pinned: false,
-            btf_fd: None,
         };
 
         let map = Map::BloomFilter(map_data);
@@ -206,7 +201,6 @@ mod tests {
             obj: new_obj_map(),
             fd: Some(42),
             pinned: false,
-            btf_fd: None,
         };
         let bloom_filter = BloomFilter::<_, u32>::new(&mut map).unwrap();
 
@@ -230,7 +224,6 @@ mod tests {
             obj: new_obj_map(),
             fd: Some(42),
             pinned: false,
-            btf_fd: None,
         };
 
         let bloom_filter = BloomFilter::<_, u32>::new(&mut map).unwrap();
@@ -244,7 +237,6 @@ mod tests {
             obj: new_obj_map(),
             fd: Some(42),
             pinned: false,
-            btf_fd: None,
         };
         let bloom_filter = BloomFilter::<_, u32>::new(&map).unwrap();
 
@@ -267,7 +259,6 @@ mod tests {
             obj: new_obj_map(),
             fd: Some(42),
             pinned: false,
-            btf_fd: None,
         };
         let bloom_filter = BloomFilter::<_, u32>::new(&map).unwrap();
 
diff --git a/aya/src/maps/hash_map/hash_map.rs b/aya/src/maps/hash_map/hash_map.rs
index 2fcb1334..93b6228d 100644
--- a/aya/src/maps/hash_map/hash_map.rs
+++ b/aya/src/maps/hash_map/hash_map.rs
@@ -149,7 +149,6 @@ mod tests {
             obj: new_obj_map(),
             fd: None,
             pinned: false,
-            btf_fd: None,
         };
         assert_matches!(
             HashMap::<_, u8, u32>::new(&map),
@@ -166,7 +165,6 @@ mod tests {
             obj: new_obj_map(),
             fd: None,
             pinned: false,
-            btf_fd: None,
         };
         assert_matches!(
             HashMap::<_, u32, u16>::new(&map),
@@ -183,7 +181,6 @@ mod tests {
             obj: new_obj_map(),
             fd: None,
             pinned: false,
-            btf_fd: None,
         };
 
         let map = Map::Array(map_data);
@@ -199,7 +196,6 @@ mod tests {
             obj: new_obj_map(),
             fd: None,
             pinned: false,
-            btf_fd: None,
         };
 
         let map = Map::HashMap(map_data);
@@ -218,7 +214,6 @@ mod tests {
             obj: new_obj_map(),
             fd: None,
             pinned: false,
-            btf_fd: None,
         };
 
         assert_matches!(
@@ -233,7 +228,6 @@ mod tests {
             obj: new_obj_map(),
             fd: Some(42),
             pinned: false,
-            btf_fd: None,
         };
 
         assert!(HashMap::<_, u32, u32>::new(&mut map).is_ok());
@@ -245,7 +239,6 @@ mod tests {
             obj: new_obj_map(),
             fd: Some(42),
             pinned: false,
-            btf_fd: None,
         };
 
         let map = Map::HashMap(map_data);
@@ -270,7 +263,6 @@ mod tests {
             }),
             fd: Some(42),
             pinned: false,
-            btf_fd: None,
         };
 
         let map = Map::HashMap(map_data);
@@ -286,7 +278,6 @@ mod tests {
             obj: new_obj_map(),
             fd: Some(42),
             pinned: false,
-            btf_fd: None,
         };
         let mut hm = HashMap::<_, u32, u32>::new(&mut map).unwrap();
 
@@ -310,7 +301,6 @@ mod tests {
             obj: new_obj_map(),
             fd: Some(42),
             pinned: false,
-            btf_fd: None,
         };
         let mut hm = HashMap::<_, u32, u32>::new(&mut map).unwrap();
 
@@ -331,7 +321,6 @@ mod tests {
             obj: new_obj_map(),
             fd: Some(42),
             pinned: false,
-            btf_fd: None,
         };
         let mut hm = HashMap::<_, u32, u32>::new(&mut map).unwrap();
 
@@ -346,7 +335,6 @@ mod tests {
             obj: new_obj_map(),
             fd: Some(42),
             pinned: false,
-            btf_fd: None,
         };
         let mut hm = HashMap::<_, u32, u32>::new(&mut map).unwrap();
 
@@ -370,7 +358,6 @@ mod tests {
             obj: new_obj_map(),
             fd: Some(42),
             pinned: false,
-            btf_fd: None,
         };
         let mut hm = HashMap::<_, u32, u32>::new(&mut map).unwrap();
 
@@ -384,7 +371,6 @@ mod tests {
             obj: new_obj_map(),
             fd: Some(42),
             pinned: false,
-            btf_fd: None,
         };
         let hm = HashMap::<_, u32, u32>::new(&map).unwrap();
 
@@ -407,7 +393,6 @@ mod tests {
             obj: new_obj_map(),
             fd: Some(42),
             pinned: false,
-            btf_fd: None,
         };
         let hm = HashMap::<_, u32, u32>::new(&map).unwrap();
 
@@ -444,7 +429,6 @@ mod tests {
             obj: new_obj_map(),
             fd: Some(42),
             pinned: false,
-            btf_fd: None,
         };
         let hm = HashMap::<_, u32, u32>::new(&map).unwrap();
         let keys = hm.keys().collect::<Result<Vec<_>, _>>();
@@ -493,7 +477,6 @@ mod tests {
             obj: new_obj_map(),
             fd: Some(42),
             pinned: false,
-            btf_fd: None,
         };
         let hm = HashMap::<_, u32, u32>::new(&map).unwrap();
 
@@ -526,7 +509,6 @@ mod tests {
             obj: new_obj_map(),
             fd: Some(42),
             pinned: false,
-            btf_fd: None,
         };
         let hm = HashMap::<_, u32, u32>::new(&map).unwrap();
 
@@ -561,7 +543,6 @@ mod tests {
             obj: new_obj_map(),
             fd: Some(42),
             pinned: false,
-            btf_fd: None,
         };
         let hm = HashMap::<_, u32, u32>::new(&map).unwrap();
         let items = hm.iter().collect::<Result<Vec<_>, _>>().unwrap();
@@ -599,7 +580,6 @@ mod tests {
             obj: new_obj_map(),
             fd: Some(42),
             pinned: false,
-            btf_fd: None,
         };
         let hm = HashMap::<_, u32, u32>::new(&map).unwrap();
 
@@ -638,7 +618,6 @@ mod tests {
             obj: new_obj_map(),
             fd: Some(42),
             pinned: false,
-            btf_fd: None,
         };
         let hm = HashMap::<_, u32, u32>::new(&map).unwrap();
 
@@ -683,7 +662,6 @@ mod tests {
             obj: new_obj_map(),
             fd: Some(42),
             pinned: false,
-            btf_fd: None,
         };
         let hm = HashMap::<_, u32, u32>::new(&map).unwrap();
 
diff --git a/aya/src/maps/lpm_trie.rs b/aya/src/maps/lpm_trie.rs
index 4adb3f47..55c7f3b9 100644
--- a/aya/src/maps/lpm_trie.rs
+++ b/aya/src/maps/lpm_trie.rs
@@ -237,7 +237,6 @@ mod tests {
             obj: new_obj_map(),
             fd: None,
             pinned: false,
-            btf_fd: None,
         };
         assert_matches!(
             LpmTrie::<_, u16, u32>::new(&map),
@@ -254,7 +253,6 @@ mod tests {
             obj: new_obj_map(),
             fd: None,
             pinned: false,
-            btf_fd: None,
         };
         assert_matches!(
             LpmTrie::<_, u32, u16>::new(&map),
@@ -282,7 +280,6 @@ mod tests {
                 data: Vec::new(),
             }),
             fd: None,
-            btf_fd: None,
             pinned: false,
         };
 
@@ -300,7 +297,6 @@ mod tests {
             obj: new_obj_map(),
             fd: None,
             pinned: false,
-            btf_fd: None,
         };
 
         assert_matches!(
@@ -315,7 +311,6 @@ mod tests {
             obj: new_obj_map(),
             fd: Some(42),
             pinned: false,
-            btf_fd: None,
         };
 
         assert!(LpmTrie::<_, u32, u32>::new(&mut map).is_ok());
@@ -327,7 +322,6 @@ mod tests {
             obj: new_obj_map(),
             fd: Some(42),
             pinned: false,
-            btf_fd: None,
         };
 
         let map = Map::LpmTrie(map_data);
@@ -342,7 +336,6 @@ mod tests {
             obj: new_obj_map(),
             fd: Some(42),
             pinned: false,
-            btf_fd: None,
         };
         let mut trie = LpmTrie::<_, u32, u32>::new(&mut map).unwrap();
         let ipaddr = Ipv4Addr::new(8, 8, 8, 8);
@@ -367,7 +360,6 @@ mod tests {
             obj: new_obj_map(),
             fd: Some(42),
             pinned: false,
-            btf_fd: None,
         };
 
         let mut trie = LpmTrie::<_, u32, u32>::new(&mut map).unwrap();
@@ -384,7 +376,6 @@ mod tests {
             obj: new_obj_map(),
             fd: Some(42),
             pinned: false,
-            btf_fd: None,
         };
         let mut trie = LpmTrie::<_, u32, u32>::new(&mut map).unwrap();
         let ipaddr = Ipv4Addr::new(8, 8, 8, 8);
@@ -409,7 +400,6 @@ mod tests {
             obj: new_obj_map(),
             fd: Some(42),
             pinned: false,
-            btf_fd: None,
         };
         let mut trie = LpmTrie::<_, u32, u32>::new(&mut map).unwrap();
         let ipaddr = Ipv4Addr::new(8, 8, 8, 8);
@@ -424,7 +414,6 @@ mod tests {
             obj: new_obj_map(),
             fd: Some(42),
             pinned: false,
-            btf_fd: None,
         };
         let trie = LpmTrie::<_, u32, u32>::new(&map).unwrap();
         let ipaddr = Ipv4Addr::new(8, 8, 8, 8);
@@ -449,7 +438,6 @@ mod tests {
             obj: new_obj_map(),
             fd: Some(42),
             pinned: false,
-            btf_fd: None,
         };
         let trie = LpmTrie::<_, u32, u32>::new(&map).unwrap();
         let ipaddr = Ipv4Addr::new(8, 8, 8, 8);
diff --git a/aya/src/maps/mod.rs b/aya/src/maps/mod.rs
index fca42110..406ff496 100644
--- a/aya/src/maps/mod.rs
+++ b/aya/src/maps/mod.rs
@@ -24,8 +24,8 @@
 //!
 //! let intercept_egress = SockMap::try_from(bpf.map_mut("INTERCEPT_EGRESS").unwrap())?;
 //! let map_fd = intercept_egress.fd()?;
-//! let prog: &mut SkMsg = bpf.program_mut("intercept_egress_packet").unwrap().try_into()?;
-//! prog.load()?;
+//! let prog: &mut SkMsg = bpf.programs.get_mut("intercept_egress_packet").unwrap().try_into()?;
+//! prog.load(bpf.btf_fd.as_ref())?;
 //! prog.attach(map_fd)?;
 //!
 //! # Ok::<(), aya::BpfError>(())
@@ -42,7 +42,7 @@ use std::{
     marker::PhantomData,
     mem,
     ops::Deref,
-    os::fd::{AsRawFd, RawFd},
+    os::fd::{AsFd, AsRawFd, RawFd},
     path::Path,
     ptr,
 };
@@ -486,14 +486,13 @@ pub(crate) fn check_v_size<V>(map: &MapData) -> Result<(), MapError> {
 pub struct MapData {
     pub(crate) obj: obj::Map,
     pub(crate) fd: Option<RawFd>,
-    pub(crate) btf_fd: Option<RawFd>,
     /// Indicates if this map has been pinned to bpffs
     pub pinned: bool,
 }
 
 impl MapData {
     /// Creates a new map with the provided `name`
-    pub fn create(&mut self, name: &str) -> Result<RawFd, MapError> {
+    pub fn create(&mut self, name: &str, btf_fd: Option<impl AsFd>) -> Result<RawFd, MapError> {
         if self.fd.is_some() {
             return Err(MapError::AlreadyCreated { name: name.into() });
         }
@@ -504,7 +503,7 @@ impl MapData {
         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(
+        let fd = bpf_create_map(&c_name, &self.obj, btf_fd, kernel_version).map_err(
             |(code, io_error)| {
                 if kernel_version < KernelVersion::new(5, 11, 0) {
                     maybe_warn_rlimit();
@@ -568,7 +567,6 @@ impl MapData {
         Ok(MapData {
             obj: parse_map_info(info, PinningType::ByName),
             fd: Some(fd),
-            btf_fd: None,
             pinned: true,
         })
     }
@@ -587,7 +585,6 @@ impl MapData {
         Ok(MapData {
             obj: parse_map_info(info, PinningType::None),
             fd: Some(fd),
-            btf_fd: None,
             pinned: false,
         })
     }
@@ -639,7 +636,6 @@ impl Clone for MapData {
         MapData {
             obj: self.obj.clone(),
             fd: self.fd.map(|fd| unsafe { libc::dup(fd) }),
-            btf_fd: self.btf_fd,
             pinned: self.pinned,
         }
     }
@@ -842,6 +838,8 @@ impl<T: Pod> Deref for PerCpuValues<T> {
 
 #[cfg(test)]
 mod tests {
+    use std::os::fd::BorrowedFd;
+
     use assert_matches::assert_matches;
     use libc::EFAULT;
 
@@ -876,7 +874,6 @@ mod tests {
             obj: new_obj_map(),
             fd: None,
             pinned: false,
-            btf_fd: None,
         }
     }
 
@@ -891,9 +888,12 @@ mod tests {
         });
 
         let mut map = new_map();
-        assert_matches!(map.create("foo"), Ok(42));
+        assert_matches!(map.create("foo", Option::<BorrowedFd>::None), Ok(42));
         assert_eq!(map.fd, Some(42));
-        assert_matches!(map.create("foo"), Err(MapError::AlreadyCreated { .. }));
+        assert_matches!(
+            map.create("foo", Option::<BorrowedFd>::None),
+            Err(MapError::AlreadyCreated { .. })
+        );
     }
 
     #[test]
@@ -901,7 +901,7 @@ mod tests {
         override_syscall(|_| Err((-42, io::Error::from_raw_os_error(EFAULT))));
 
         let mut map = new_map();
-        let ret = map.create("foo");
+        let ret = map.create("foo", Option::<BorrowedFd>::None);
         assert_matches!(ret, Err(MapError::CreateError { .. }));
         if let Err(MapError::CreateError {
             name,
diff --git a/aya/src/maps/sock/sock_hash.rs b/aya/src/maps/sock/sock_hash.rs
index be78e386..5e147527 100644
--- a/aya/src/maps/sock/sock_hash.rs
+++ b/aya/src/maps/sock/sock_hash.rs
@@ -49,8 +49,8 @@ use crate::{
 /// let mut intercept_egress = SockHash::<_, u32>::try_from(bpf.map("INTERCEPT_EGRESS").unwrap())?;
 /// let map_fd = intercept_egress.fd()?;
 ///
-/// let prog: &mut SkMsg = bpf.program_mut("intercept_egress_packet").unwrap().try_into()?;
-/// prog.load()?;
+/// let prog: &mut SkMsg = bpf.programs.get_mut("intercept_egress_packet").unwrap().try_into()?;
+/// prog.load(bpf.btf_fd.as_ref())?;
 /// prog.attach(map_fd)?;
 ///
 /// let mut client = TcpStream::connect("127.0.0.1:1234")?;
diff --git a/aya/src/maps/sock/sock_map.rs b/aya/src/maps/sock/sock_map.rs
index d34f658b..39290dd5 100644
--- a/aya/src/maps/sock/sock_map.rs
+++ b/aya/src/maps/sock/sock_map.rs
@@ -17,7 +17,7 @@ use crate::{
 /// sockets.
 ///
 /// A `SockMap` can also be used to redirect packets to sockets contained by the
-/// map using `bpf_redirect_map()`, `bpf_sk_redirect_map()` etc.    
+/// map using `bpf_redirect_map()`, `bpf_sk_redirect_map()` etc.
 ///
 /// # Minimum kernel version
 ///
@@ -33,8 +33,8 @@ use crate::{
 /// let intercept_ingress = SockMap::try_from(bpf.map("INTERCEPT_INGRESS").unwrap())?;
 /// let map_fd = intercept_ingress.fd()?;
 ///
-/// let prog: &mut SkSkb = bpf.program_mut("intercept_ingress_packet").unwrap().try_into()?;
-/// prog.load()?;
+/// let prog: &mut SkSkb = bpf.programs.get_mut("intercept_ingress_packet").unwrap().try_into()?;
+/// prog.load(bpf.btf_fd.as_ref())?;
 /// prog.attach(map_fd)?;
 ///
 /// # Ok::<(), aya::BpfError>(())
diff --git a/aya/src/programs/cgroup_device.rs b/aya/src/programs/cgroup_device.rs
index e3580b28..76d5a50e 100644
--- a/aya/src/programs/cgroup_device.rs
+++ b/aya/src/programs/cgroup_device.rs
@@ -1,7 +1,7 @@
 //! Cgroup device programs.
 
 use crate::util::KernelVersion;
-use std::os::fd::{AsRawFd, RawFd};
+use std::os::fd::{AsFd, AsRawFd, RawFd};
 
 use crate::{
     generated::{bpf_attach_type::BPF_CGROUP_DEVICE, bpf_prog_type::BPF_PROG_TYPE_CGROUP_DEVICE},
@@ -40,8 +40,8 @@ use crate::{
 /// use aya::programs::CgroupDevice;
 ///
 /// let cgroup = std::fs::File::open("/sys/fs/cgroup/unified")?;
-/// let program: &mut CgroupDevice = bpf.program_mut("cgroup_dev").unwrap().try_into()?;
-/// program.load()?;
+/// let program: &mut CgroupDevice = bpf.programs.get_mut("cgroup_dev").unwrap().try_into()?;
+/// program.load(bpf.btf_fd.as_ref())?;
 /// program.attach(cgroup)?;
 /// # Ok::<(), Error>(())
 /// ```
@@ -53,8 +53,8 @@ pub struct CgroupDevice {
 
 impl CgroupDevice {
     /// Loads the program inside the kernel
-    pub fn load(&mut self) -> Result<(), ProgramError> {
-        load_program(BPF_PROG_TYPE_CGROUP_DEVICE, &mut self.data)
+    pub fn load(&mut self, btf_fd: Option<impl AsFd>) -> Result<(), ProgramError> {
+        load_program(BPF_PROG_TYPE_CGROUP_DEVICE, &mut self.data, btf_fd)
     }
 
     /// Attaches the program to the given cgroup.
diff --git a/aya/src/programs/cgroup_skb.rs b/aya/src/programs/cgroup_skb.rs
index f67b56fe..9af6a0aa 100644
--- a/aya/src/programs/cgroup_skb.rs
+++ b/aya/src/programs/cgroup_skb.rs
@@ -3,7 +3,7 @@
 use crate::util::KernelVersion;
 use std::{
     hash::Hash,
-    os::fd::{AsRawFd, RawFd},
+    os::fd::{AsFd, AsRawFd, RawFd},
     path::Path,
 };
 
@@ -50,8 +50,8 @@ use crate::{
 /// use aya::programs::{CgroupSkb, CgroupSkbAttachType};
 ///
 /// let file = File::open("/sys/fs/cgroup/unified")?;
-/// let egress: &mut CgroupSkb = bpf.program_mut("egress_filter").unwrap().try_into()?;
-/// egress.load()?;
+/// let egress: &mut CgroupSkb = bpf.programs.get_mut("egress_filter").unwrap().try_into()?;
+/// egress.load(bpf.btf_fd.as_ref())?;
 /// egress.attach(file, CgroupSkbAttachType::Egress)?;
 /// # Ok::<(), Error>(())
 /// ```
@@ -64,14 +64,14 @@ pub struct CgroupSkb {
 
 impl CgroupSkb {
     /// Loads the program inside the kernel.
-    pub fn load(&mut self) -> Result<(), ProgramError> {
+    pub fn load(&mut self, btf_fd: Option<impl AsFd>) -> Result<(), ProgramError> {
         self.data.expected_attach_type =
             self.expected_attach_type
                 .map(|attach_type| match attach_type {
                     CgroupSkbAttachType::Ingress => BPF_CGROUP_INET_INGRESS,
                     CgroupSkbAttachType::Egress => BPF_CGROUP_INET_EGRESS,
                 });
-        load_program(BPF_PROG_TYPE_CGROUP_SKB, &mut self.data)
+        load_program(BPF_PROG_TYPE_CGROUP_SKB, &mut self.data, btf_fd)
     }
 
     /// Returns the expected attach type of the program.
diff --git a/aya/src/programs/cgroup_sock.rs b/aya/src/programs/cgroup_sock.rs
index 9d66f9aa..a69c51d1 100644
--- a/aya/src/programs/cgroup_sock.rs
+++ b/aya/src/programs/cgroup_sock.rs
@@ -5,7 +5,7 @@ pub use aya_obj::programs::CgroupSockAttachType;
 use crate::util::KernelVersion;
 use std::{
     hash::Hash,
-    os::fd::{AsRawFd, RawFd},
+    os::fd::{AsFd, AsRawFd, RawFd},
     path::Path,
 };
 
@@ -48,8 +48,8 @@ use crate::{
 /// use aya::programs::{CgroupSock, CgroupSockAttachType};
 ///
 /// let file = File::open("/sys/fs/cgroup/unified")?;
-/// let bind: &mut CgroupSock = bpf.program_mut("bind").unwrap().try_into()?;
-/// bind.load()?;
+/// let bind: &mut CgroupSock = bpf.programs.get_mut("bind").unwrap().try_into()?;
+/// bind.load(bpf.btf_fd.as_ref())?;
 /// bind.attach(file)?;
 /// # Ok::<(), Error>(())
 /// ```
@@ -62,9 +62,9 @@ pub struct CgroupSock {
 
 impl CgroupSock {
     /// Loads the program inside the kernel.
-    pub fn load(&mut self) -> Result<(), ProgramError> {
+    pub fn load(&mut self, btf_fd: Option<impl AsFd>) -> Result<(), ProgramError> {
         self.data.expected_attach_type = Some(self.attach_type.into());
-        load_program(BPF_PROG_TYPE_CGROUP_SOCK, &mut self.data)
+        load_program(BPF_PROG_TYPE_CGROUP_SOCK, &mut self.data, btf_fd)
     }
 
     /// Attaches the program to the given cgroup.
diff --git a/aya/src/programs/cgroup_sock_addr.rs b/aya/src/programs/cgroup_sock_addr.rs
index 045a40d2..faaa1738 100644
--- a/aya/src/programs/cgroup_sock_addr.rs
+++ b/aya/src/programs/cgroup_sock_addr.rs
@@ -5,7 +5,7 @@ pub use aya_obj::programs::CgroupSockAddrAttachType;
 use crate::util::KernelVersion;
 use std::{
     hash::Hash,
-    os::fd::{AsRawFd, RawFd},
+    os::fd::{AsFd, AsRawFd, RawFd},
     path::Path,
 };
 
@@ -49,8 +49,8 @@ use crate::{
 /// use aya::programs::{CgroupSockAddr, CgroupSockAddrAttachType};
 ///
 /// let file = File::open("/sys/fs/cgroup/unified")?;
-/// let egress: &mut CgroupSockAddr = bpf.program_mut("connect4").unwrap().try_into()?;
-/// egress.load()?;
+/// let egress: &mut CgroupSockAddr = bpf.programs.get_mut("connect4").unwrap().try_into()?;
+/// egress.load(bpf.btf_fd.as_ref())?;
 /// egress.attach(file)?;
 /// # Ok::<(), Error>(())
 /// ```
@@ -63,9 +63,9 @@ pub struct CgroupSockAddr {
 
 impl CgroupSockAddr {
     /// Loads the program inside the kernel.
-    pub fn load(&mut self) -> Result<(), ProgramError> {
+    pub fn load(&mut self, btf_fd: Option<impl AsFd>) -> Result<(), ProgramError> {
         self.data.expected_attach_type = Some(self.attach_type.into());
-        load_program(BPF_PROG_TYPE_CGROUP_SOCK_ADDR, &mut self.data)
+        load_program(BPF_PROG_TYPE_CGROUP_SOCK_ADDR, &mut self.data, btf_fd)
     }
 
     /// Attaches the program to the given cgroup.
diff --git a/aya/src/programs/cgroup_sockopt.rs b/aya/src/programs/cgroup_sockopt.rs
index 95b89dd7..b6f03f72 100644
--- a/aya/src/programs/cgroup_sockopt.rs
+++ b/aya/src/programs/cgroup_sockopt.rs
@@ -5,7 +5,7 @@ pub use aya_obj::programs::CgroupSockoptAttachType;
 use crate::util::KernelVersion;
 use std::{
     hash::Hash,
-    os::fd::{AsRawFd, RawFd},
+    os::fd::{AsFd, AsRawFd, RawFd},
     path::Path,
 };
 
@@ -46,8 +46,8 @@ use crate::{
 /// use aya::programs::CgroupSockopt;
 ///
 /// let file = File::open("/sys/fs/cgroup/unified")?;
-/// let program: &mut CgroupSockopt = bpf.program_mut("cgroup_sockopt").unwrap().try_into()?;
-/// program.load()?;
+/// let program: &mut CgroupSockopt = bpf.programs.get_mut("cgroup_sockopt").unwrap().try_into()?;
+/// program.load(bpf.btf_fd.as_ref())?;
 /// program.attach(file)?;
 /// # Ok::<(), Error>(())
 /// ```
@@ -60,9 +60,9 @@ pub struct CgroupSockopt {
 
 impl CgroupSockopt {
     /// Loads the program inside the kernel.
-    pub fn load(&mut self) -> Result<(), ProgramError> {
+    pub fn load(&mut self, btf_fd: Option<impl AsFd>) -> Result<(), ProgramError> {
         self.data.expected_attach_type = Some(self.attach_type.into());
-        load_program(BPF_PROG_TYPE_CGROUP_SOCKOPT, &mut self.data)
+        load_program(BPF_PROG_TYPE_CGROUP_SOCKOPT, &mut self.data, btf_fd)
     }
 
     /// Attaches the program to the given cgroup.
diff --git a/aya/src/programs/cgroup_sysctl.rs b/aya/src/programs/cgroup_sysctl.rs
index 3cd20195..e8c2f012 100644
--- a/aya/src/programs/cgroup_sysctl.rs
+++ b/aya/src/programs/cgroup_sysctl.rs
@@ -3,7 +3,7 @@
 use crate::util::KernelVersion;
 use std::{
     hash::Hash,
-    os::fd::{AsRawFd, RawFd},
+    os::fd::{AsFd, AsRawFd, RawFd},
 };
 
 use crate::{
@@ -42,8 +42,8 @@ use crate::{
 /// use aya::programs::CgroupSysctl;
 ///
 /// let file = File::open("/sys/fs/cgroup/unified")?;
-/// let program: &mut CgroupSysctl = bpf.program_mut("cgroup_sysctl").unwrap().try_into()?;
-/// program.load()?;
+/// let program: &mut CgroupSysctl = bpf.programs.get_mut("cgroup_sysctl").unwrap().try_into()?;
+/// program.load(bpf.btf_fd.as_ref())?;
 /// program.attach(file)?;
 /// # Ok::<(), Error>(())
 /// ```
@@ -55,8 +55,8 @@ pub struct CgroupSysctl {
 
 impl CgroupSysctl {
     /// Loads the program inside the kernel.
-    pub fn load(&mut self) -> Result<(), ProgramError> {
-        load_program(BPF_PROG_TYPE_CGROUP_SYSCTL, &mut self.data)
+    pub fn load(&mut self, btf_fd: Option<impl AsFd>) -> Result<(), ProgramError> {
+        load_program(BPF_PROG_TYPE_CGROUP_SYSCTL, &mut self.data, btf_fd)
     }
 
     /// Attaches the program to the given cgroup.
diff --git a/aya/src/programs/extension.rs b/aya/src/programs/extension.rs
index 682d14ad..7f88d471 100644
--- a/aya/src/programs/extension.rs
+++ b/aya/src/programs/extension.rs
@@ -1,5 +1,5 @@
 //! Extension programs.
-use std::os::fd::{AsRawFd, RawFd};
+use std::os::fd::{AsFd, AsRawFd, RawFd};
 use thiserror::Error;
 
 use object::Endianness;
@@ -37,13 +37,13 @@ pub enum ExtensionError {
 /// use aya::{BpfLoader, programs::{Xdp, XdpFlags, Extension}};
 ///
 /// let mut bpf = BpfLoader::new().extension("extension").load_file("app.o")?;
-/// let prog: &mut Xdp = bpf.program_mut("main").unwrap().try_into()?;
-/// prog.load()?;
+/// let prog: &mut Xdp = bpf.programs.get_mut("main").unwrap().try_into()?;
+/// prog.load(bpf.btf_fd.as_ref())?;
 /// prog.attach("eth0", XdpFlags::default())?;
 ///
 /// let prog_fd = prog.fd().unwrap();
-/// let ext: &mut Extension = bpf.program_mut("extension").unwrap().try_into()?;
-/// ext.load(prog_fd, "function_to_replace")?;
+/// let ext: &mut Extension = bpf.programs.get_mut("extension").unwrap().try_into()?;
+/// ext.load(prog_fd, "function_to_replace", bpf.btf_fd.as_ref())?;
 /// ext.attach()?;
 /// Ok::<(), aya::BpfError>(())
 /// ```
@@ -68,14 +68,19 @@ impl Extension {
     /// The extension code will be loaded but inactive until it's attached.
     /// There are no restrictions on what functions may be replaced, so you could replace
     /// the main entry point of your program with an extension.
-    pub fn load(&mut self, program: ProgramFd, func_name: &str) -> Result<(), ProgramError> {
+    pub fn load(
+        &mut self,
+        program: ProgramFd,
+        func_name: &str,
+        btf_fd: Option<impl AsFd>,
+    ) -> Result<(), ProgramError> {
         let target_prog_fd = program.as_raw_fd();
-        let (btf_fd, btf_id) = get_btf_info(target_prog_fd, func_name)?;
+        let (prog_btf_fd, prog_btf_id) = get_btf_info(target_prog_fd, func_name)?;
 
-        self.data.attach_btf_obj_fd = Some(btf_fd as u32);
+        self.data.attach_btf_obj_fd = Some(prog_btf_fd as u32);
         self.data.attach_prog_fd = Some(target_prog_fd);
-        self.data.attach_btf_id = Some(btf_id);
-        load_program(BPF_PROG_TYPE_EXT, &mut self.data)
+        self.data.attach_btf_id = Some(prog_btf_id);
+        load_program(BPF_PROG_TYPE_EXT, &mut self.data, btf_fd)
     }
 
     /// Attaches the extension.
diff --git a/aya/src/programs/fentry.rs b/aya/src/programs/fentry.rs
index 8f59061a..30c3b0ee 100644
--- a/aya/src/programs/fentry.rs
+++ b/aya/src/programs/fentry.rs
@@ -1,5 +1,7 @@
 //! Fentry programs.
 
+use std::os::fd::AsFd;
+
 use crate::{
     generated::{bpf_attach_type::BPF_TRACE_FENTRY, bpf_prog_type::BPF_PROG_TYPE_TRACING},
     obj::btf::{Btf, BtfKind},
@@ -37,8 +39,8 @@ use crate::{
 /// use aya::{Bpf, programs::FEntry, BtfError, Btf};
 ///
 /// let btf = Btf::from_sys_fs()?;
-/// let program: &mut FEntry = bpf.program_mut("filename_lookup").unwrap().try_into()?;
-/// program.load("filename_lookup", &btf)?;
+/// let program: &mut FEntry = bpf.programs.get_mut("filename_lookup").unwrap().try_into()?;
+/// program.load("filename_lookup", &btf, bpf.btf_fd.as_ref())?;
 /// program.attach()?;
 /// # Ok::<(), Error>(())
 /// ```
@@ -55,10 +57,15 @@ impl FEntry {
     /// Loads the program so it's executed when the kernel function `fn_name`
     /// is entered. The `btf` argument must contain the BTF info for the
     /// running kernel.
-    pub fn load(&mut self, fn_name: &str, btf: &Btf) -> Result<(), ProgramError> {
+    pub fn load(
+        &mut self,
+        fn_name: &str,
+        btf: &Btf,
+        btf_fd: Option<impl AsFd>,
+    ) -> Result<(), ProgramError> {
         self.data.expected_attach_type = Some(BPF_TRACE_FENTRY);
         self.data.attach_btf_id = Some(btf.id_by_type_name_kind(fn_name, BtfKind::Func)?);
-        load_program(BPF_PROG_TYPE_TRACING, &mut self.data)
+        load_program(BPF_PROG_TYPE_TRACING, &mut self.data, btf_fd)
     }
 
     /// Attaches the program.
diff --git a/aya/src/programs/fexit.rs b/aya/src/programs/fexit.rs
index ba2e1f1b..7091bbbb 100644
--- a/aya/src/programs/fexit.rs
+++ b/aya/src/programs/fexit.rs
@@ -1,5 +1,7 @@
 //! Fexit programs.
 
+use std::os::fd::AsFd;
+
 use crate::{
     generated::{bpf_attach_type::BPF_TRACE_FEXIT, bpf_prog_type::BPF_PROG_TYPE_TRACING},
     obj::btf::{Btf, BtfKind},
@@ -37,8 +39,8 @@ use crate::{
 /// use aya::{Bpf, programs::FExit, BtfError, Btf};
 ///
 /// let btf = Btf::from_sys_fs()?;
-/// let program: &mut FExit = bpf.program_mut("filename_lookup").unwrap().try_into()?;
-/// program.load("filename_lookup", &btf)?;
+/// let program: &mut FExit = bpf.programs.get_mut("filename_lookup").unwrap().try_into()?;
+/// program.load("filename_lookup", &btf, bpf.btf_fd.as_ref())?;
 /// program.attach()?;
 /// # Ok::<(), Error>(())
 /// ```
@@ -55,10 +57,15 @@ impl FExit {
     /// Loads the program so it's executed when the kernel function `fn_name`
     /// is exited. The `btf` argument must contain the BTF info for the running
     /// kernel.
-    pub fn load(&mut self, fn_name: &str, btf: &Btf) -> Result<(), ProgramError> {
+    pub fn load(
+        &mut self,
+        fn_name: &str,
+        btf: &Btf,
+        btf_fd: Option<impl AsFd>,
+    ) -> Result<(), ProgramError> {
         self.data.expected_attach_type = Some(BPF_TRACE_FEXIT);
         self.data.attach_btf_id = Some(btf.id_by_type_name_kind(fn_name, BtfKind::Func)?);
-        load_program(BPF_PROG_TYPE_TRACING, &mut self.data)
+        load_program(BPF_PROG_TYPE_TRACING, &mut self.data, btf_fd)
     }
 
     /// Attaches the program.
diff --git a/aya/src/programs/kprobe.rs b/aya/src/programs/kprobe.rs
index a61f084f..9bb8e7fd 100644
--- a/aya/src/programs/kprobe.rs
+++ b/aya/src/programs/kprobe.rs
@@ -1,5 +1,5 @@
 //! Kernel space probes.
-use std::{io, path::Path};
+use std::{io, os::fd::AsFd, path::Path};
 use thiserror::Error;
 
 use crate::{
@@ -32,8 +32,8 @@ use crate::{
 /// # let mut bpf = Bpf::load_file("ebpf_programs.o")?;
 /// use aya::{Bpf, programs::KProbe};
 ///
-/// let program: &mut KProbe = bpf.program_mut("intercept_wakeups").unwrap().try_into()?;
-/// program.load()?;
+/// let program: &mut KProbe = bpf.programs.get_mut("intercept_wakeups").unwrap().try_into()?;
+/// program.load(bpf.btf_fd.as_ref())?;
 /// program.attach("try_to_wake_up", 0)?;
 /// # Ok::<(), aya::BpfError>(())
 /// ```
@@ -46,8 +46,8 @@ pub struct KProbe {
 
 impl KProbe {
     /// Loads the program inside the kernel.
-    pub fn load(&mut self) -> Result<(), ProgramError> {
-        load_program(BPF_PROG_TYPE_KPROBE, &mut self.data)
+    pub fn load(&mut self, btf_fd: Option<&impl AsFd>) -> Result<(), ProgramError> {
+        load_program(BPF_PROG_TYPE_KPROBE, &mut self.data, btf_fd)
     }
 
     /// Returns `KProbe` if the program is a `kprobe`, or `KRetProbe` if the
diff --git a/aya/src/programs/links.rs b/aya/src/programs/links.rs
index debe7b49..06639256 100644
--- a/aya/src/programs/links.rs
+++ b/aya/src/programs/links.rs
@@ -97,8 +97,8 @@ pub struct FdLinkId(pub(crate) RawFd);
 /// # let mut bpf = Bpf::load_file("ebpf_programs.o")?;
 /// use aya::{Bpf, programs::{links::FdLink, KProbe}};
 ///
-/// let program: &mut KProbe = bpf.program_mut("intercept_wakeups").unwrap().try_into()?;
-/// program.load()?;
+/// let program: &mut KProbe = bpf.programs.get_mut("intercept_wakeups").unwrap().try_into()?;
+/// program.load(bpf.btf_fd.as_ref())?;
 /// let link_id = program.attach("try_to_wake_up", 0)?;
 /// let link = program.take_link(link_id).unwrap();
 /// let fd_link: FdLink = link.try_into().unwrap();
diff --git a/aya/src/programs/lirc_mode2.rs b/aya/src/programs/lirc_mode2.rs
index 1d445eb7..8156f821 100644
--- a/aya/src/programs/lirc_mode2.rs
+++ b/aya/src/programs/lirc_mode2.rs
@@ -1,5 +1,5 @@
 //! Lirc programs.
-use std::os::fd::{AsRawFd, RawFd};
+use std::os::fd::{AsFd, AsRawFd, RawFd};
 
 use crate::{
     generated::{bpf_attach_type::BPF_LIRC_MODE2, bpf_prog_type::BPF_PROG_TYPE_LIRC_MODE2},
@@ -40,8 +40,8 @@ use libc::{close, dup};
 ///
 /// let file = File::open("/dev/lirc0")?;
 /// let mut bpf = aya::Bpf::load_file("imon_rsc.o")?;
-/// let decoder: &mut LircMode2 = bpf.program_mut("imon_rsc").unwrap().try_into().unwrap();
-/// decoder.load()?;
+/// let decoder: &mut LircMode2 = bpf.programs.get_mut("imon_rsc").unwrap().try_into().unwrap();
+/// decoder.load(bpf.btf_fd.as_ref())?;
 /// decoder.attach(file)?;
 /// # Ok::<(), Error>(())
 /// ```
@@ -53,8 +53,8 @@ pub struct LircMode2 {
 
 impl LircMode2 {
     /// Loads the program inside the kernel.
-    pub fn load(&mut self) -> Result<(), ProgramError> {
-        load_program(BPF_PROG_TYPE_LIRC_MODE2, &mut self.data)
+    pub fn load(&mut self, btf_fd: Option<impl AsFd>) -> Result<(), ProgramError> {
+        load_program(BPF_PROG_TYPE_LIRC_MODE2, &mut self.data, btf_fd)
     }
 
     /// Attaches the program to the given lirc device.
diff --git a/aya/src/programs/lsm.rs b/aya/src/programs/lsm.rs
index 3a455707..4d35087e 100644
--- a/aya/src/programs/lsm.rs
+++ b/aya/src/programs/lsm.rs
@@ -1,5 +1,7 @@
 //! LSM probes.
 
+use std::os::fd::AsFd;
+
 use crate::{
     generated::{bpf_attach_type::BPF_LSM_MAC, bpf_prog_type::BPF_PROG_TYPE_LSM},
     obj::btf::{Btf, BtfKind},
@@ -39,8 +41,8 @@ use crate::{
 /// use aya::{Bpf, programs::Lsm, BtfError, Btf};
 ///
 /// let btf = Btf::from_sys_fs()?;
-/// let program: &mut Lsm = bpf.program_mut("lsm_prog").unwrap().try_into()?;
-/// program.load("security_bprm_exec", &btf)?;
+/// let program: &mut Lsm = bpf.programs.get_mut("lsm_prog").unwrap().try_into()?;
+/// program.load("security_bprm_exec", &btf, bpf.btf_fd.as_ref())?;
 /// program.attach()?;
 /// # Ok::<(), LsmError>(())
 /// ```
@@ -59,12 +61,17 @@ impl Lsm {
     ///
     /// * `lsm_hook_name` - full name of the LSM hook that the program should
     ///   be attached to
-    pub fn load(&mut self, lsm_hook_name: &str, btf: &Btf) -> Result<(), ProgramError> {
+    pub fn load(
+        &mut self,
+        lsm_hook_name: &str,
+        btf: &Btf,
+        btf_fd: Option<impl AsFd>,
+    ) -> Result<(), ProgramError> {
         self.data.expected_attach_type = Some(BPF_LSM_MAC);
         let type_name = format!("bpf_lsm_{lsm_hook_name}");
         self.data.attach_btf_id =
             Some(btf.id_by_type_name_kind(type_name.as_str(), BtfKind::Func)?);
-        load_program(BPF_PROG_TYPE_LSM, &mut self.data)
+        load_program(BPF_PROG_TYPE_LSM, &mut self.data, btf_fd)
     }
 
     /// Attaches the program.
diff --git a/aya/src/programs/mod.rs b/aya/src/programs/mod.rs
index b52f76f8..a1456c2a 100644
--- a/aya/src/programs/mod.rs
+++ b/aya/src/programs/mod.rs
@@ -18,8 +18,8 @@
 //!
 //! let mut bpf = Bpf::load_file("ebpf_programs.o")?;
 //! // intercept_wakeups is the name of the program we want to load
-//! let program: &mut KProbe = bpf.program_mut("intercept_wakeups").unwrap().try_into()?;
-//! program.load()?;
+//! let program: &mut KProbe = bpf.programs.get_mut("intercept_wakeups").unwrap().try_into()?;
+//! program.load(bpf.btf_fd.as_ref())?;
 //! // intercept_wakeups will be called every time try_to_wake_up() is called
 //! // inside the kernel
 //! program.attach("try_to_wake_up", 0)?;
@@ -69,7 +69,7 @@ use libc::ENOSPC;
 use std::{
     ffi::CString,
     io,
-    os::fd::{AsRawFd, RawFd},
+    os::fd::{AsFd, AsRawFd, RawFd},
     path::{Path, PathBuf},
 };
 use thiserror::Error;
@@ -413,7 +413,6 @@ pub(crate) struct ProgramData<T: Link> {
     pub(crate) attach_btf_obj_fd: Option<u32>,
     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: VerifierLogLevel,
     pub(crate) path: Option<PathBuf>,
     pub(crate) flags: u32,
@@ -423,7 +422,6 @@ impl<T: Link> ProgramData<T> {
     pub(crate) fn new(
         name: Option<String>,
         obj: (obj::Program, obj::Function),
-        btf_fd: Option<RawFd>,
         verifier_log_level: VerifierLogLevel,
     ) -> ProgramData<T> {
         ProgramData {
@@ -435,7 +433,6 @@ impl<T: Link> ProgramData<T> {
             attach_btf_obj_fd: None,
             attach_btf_id: None,
             attach_prog_fd: None,
-            btf_fd,
             verifier_log_level,
             path: None,
             flags: 0,
@@ -475,7 +472,6 @@ impl<T: Link> ProgramData<T> {
             attach_btf_obj_fd,
             attach_btf_id,
             attach_prog_fd: None,
-            btf_fd: None,
             verifier_log_level,
             path: Some(path.to_path_buf()),
             flags: 0,
@@ -546,6 +542,7 @@ fn pin_program<T: Link, P: AsRef<Path>>(data: &ProgramData<T>, path: P) -> Resul
 fn load_program<T: Link>(
     prog_type: bpf_prog_type,
     data: &mut ProgramData<T>,
+    btf_fd: Option<impl AsFd>,
 ) -> Result<(), ProgramError> {
     let ProgramData {
         name,
@@ -556,7 +553,6 @@ fn load_program<T: Link>(
         attach_btf_obj_fd,
         attach_btf_id,
         attach_prog_fd,
-        btf_fd,
         verifier_log_level,
         path: _,
         flags,
@@ -613,7 +609,7 @@ fn load_program<T: Link>(
         license,
         kernel_version: target_kernel_version,
         expected_attach_type: *expected_attach_type,
-        prog_btf_fd: *btf_fd,
+        prog_btf_fd: btf_fd.as_ref().map(|f| f.as_fd()),
         attach_btf_obj_fd: *attach_btf_obj_fd,
         attach_btf_id: *attach_btf_id,
         attach_prog_fd: *attach_prog_fd,
diff --git a/aya/src/programs/perf_event.rs b/aya/src/programs/perf_event.rs
index 5de7eeef..70fe929a 100644
--- a/aya/src/programs/perf_event.rs
+++ b/aya/src/programs/perf_event.rs
@@ -1,5 +1,7 @@
 //! Perf event programs.
 
+use std::os::fd::AsFd;
+
 pub use crate::generated::{
     perf_hw_cache_id, perf_hw_cache_op_id, perf_hw_cache_op_result_id, perf_hw_id, perf_sw_ids,
 };
@@ -105,8 +107,8 @@ pub enum PerfEventScope {
 ///     perf_sw_ids::PERF_COUNT_SW_CPU_CLOCK, PerfEvent, PerfEventScope, PerfTypeId, SamplePolicy,
 /// };
 ///
-/// let prog: &mut PerfEvent = bpf.program_mut("observe_cpu_clock").unwrap().try_into()?;
-/// prog.load()?;
+/// let prog: &mut PerfEvent = bpf.programs.get_mut("observe_cpu_clock").unwrap().try_into()?;
+/// prog.load(bpf.btf_fd.as_ref())?;
 ///
 /// for cpu in online_cpus()? {
 ///     prog.attach(
@@ -126,8 +128,8 @@ pub struct PerfEvent {
 
 impl PerfEvent {
     /// Loads the program inside the kernel.
-    pub fn load(&mut self) -> Result<(), ProgramError> {
-        load_program(BPF_PROG_TYPE_PERF_EVENT, &mut self.data)
+    pub fn load(&mut self, btf_fd: Option<impl AsFd>) -> Result<(), ProgramError> {
+        load_program(BPF_PROG_TYPE_PERF_EVENT, &mut self.data, btf_fd)
     }
 
     /// Attaches to the given perf event.
diff --git a/aya/src/programs/raw_trace_point.rs b/aya/src/programs/raw_trace_point.rs
index af451e59..89b3d654 100644
--- a/aya/src/programs/raw_trace_point.rs
+++ b/aya/src/programs/raw_trace_point.rs
@@ -1,5 +1,5 @@
 //! Raw tracepoints.
-use std::ffi::CString;
+use std::{ffi::CString, os::fd::AsFd};
 
 use crate::{
     generated::bpf_prog_type::BPF_PROG_TYPE_RAW_TRACEPOINT,
@@ -27,8 +27,8 @@ use crate::{
 /// # let mut bpf = Bpf::load_file("ebpf_programs.o")?;
 /// use aya::{Bpf, programs::RawTracePoint};
 ///
-/// let program: &mut RawTracePoint = bpf.program_mut("sys_enter").unwrap().try_into()?;
-/// program.load()?;
+/// let program: &mut RawTracePoint = bpf.programs.get_mut("sys_enter").unwrap().try_into()?;
+/// program.load(bpf.btf_fd.as_ref())?;
 /// program.attach("sys_enter")?;
 /// # Ok::<(), aya::BpfError>(())
 /// ```
@@ -40,8 +40,8 @@ pub struct RawTracePoint {
 
 impl RawTracePoint {
     /// Loads the program inside the kernel.
-    pub fn load(&mut self) -> Result<(), ProgramError> {
-        load_program(BPF_PROG_TYPE_RAW_TRACEPOINT, &mut self.data)
+    pub fn load(&mut self, btf_fd: Option<impl AsFd>) -> Result<(), ProgramError> {
+        load_program(BPF_PROG_TYPE_RAW_TRACEPOINT, &mut self.data, btf_fd)
     }
 
     /// Attaches the program to the given tracepoint.
diff --git a/aya/src/programs/sk_lookup.rs b/aya/src/programs/sk_lookup.rs
index 27d60b8f..a3199da0 100644
--- a/aya/src/programs/sk_lookup.rs
+++ b/aya/src/programs/sk_lookup.rs
@@ -1,4 +1,4 @@
-use std::os::fd::{AsRawFd, RawFd};
+use std::os::fd::{AsFd, AsRawFd, RawFd};
 
 use crate::{
     generated::{bpf_attach_type::BPF_SK_LOOKUP, bpf_prog_type::BPF_PROG_TYPE_SK_LOOKUP},
@@ -39,8 +39,8 @@ use super::links::FdLink;
 /// use aya::programs::SkLookup;
 ///
 /// let file = File::open("/var/run/netns/test")?;
-/// let program: &mut SkLookup = bpf.program_mut("sk_lookup").unwrap().try_into()?;
-/// program.load()?;
+/// let program: &mut SkLookup = bpf.programs.get_mut("sk_lookup").unwrap().try_into()?;
+/// program.load(bpf.btf_fd.as_ref())?;
 /// program.attach(file)?;
 /// # Ok::<(), Error>(())
 /// ```
@@ -52,9 +52,9 @@ pub struct SkLookup {
 
 impl SkLookup {
     /// Loads the program inside the kernel.
-    pub fn load(&mut self) -> Result<(), ProgramError> {
+    pub fn load(&mut self, btf_fd: Option<impl AsFd>) -> Result<(), ProgramError> {
         self.data.expected_attach_type = Some(BPF_SK_LOOKUP);
-        load_program(BPF_PROG_TYPE_SK_LOOKUP, &mut self.data)
+        load_program(BPF_PROG_TYPE_SK_LOOKUP, &mut self.data, btf_fd)
     }
 
     /// Attaches the program to the given network namespace.
diff --git a/aya/src/programs/sk_msg.rs b/aya/src/programs/sk_msg.rs
index fc6202ba..18fe8a03 100644
--- a/aya/src/programs/sk_msg.rs
+++ b/aya/src/programs/sk_msg.rs
@@ -1,6 +1,6 @@
 //! Skmsg programs.
 
-use std::os::fd::AsRawFd;
+use std::os::fd::{AsFd, AsRawFd};
 
 use crate::{
     generated::{bpf_attach_type::BPF_SK_MSG_VERDICT, bpf_prog_type::BPF_PROG_TYPE_SK_MSG},
@@ -46,8 +46,8 @@ use crate::{
 /// let intercept_egress: SockHash<_, u32> = bpf.map("INTERCEPT_EGRESS").unwrap().try_into()?;
 /// let map_fd = intercept_egress.fd()?;
 ///
-/// let prog: &mut SkMsg = bpf.program_mut("intercept_egress_packet").unwrap().try_into()?;
-/// prog.load()?;
+/// let prog: &mut SkMsg = bpf.programs.get_mut("intercept_egress_packet").unwrap().try_into()?;
+/// prog.load(bpf.btf_fd.as_ref())?;
 /// prog.attach(map_fd)?;
 ///
 /// let mut client = TcpStream::connect("127.0.0.1:1234")?;
@@ -71,8 +71,8 @@ pub struct SkMsg {
 
 impl SkMsg {
     /// Loads the program inside the kernel.
-    pub fn load(&mut self) -> Result<(), ProgramError> {
-        load_program(BPF_PROG_TYPE_SK_MSG, &mut self.data)
+    pub fn load(&mut self, btf_fd: Option<impl AsFd>) -> Result<(), ProgramError> {
+        load_program(BPF_PROG_TYPE_SK_MSG, &mut self.data, btf_fd)
     }
 
     /// Attaches the program to the given sockmap.
diff --git a/aya/src/programs/sk_skb.rs b/aya/src/programs/sk_skb.rs
index 162d74d0..5a8db29f 100644
--- a/aya/src/programs/sk_skb.rs
+++ b/aya/src/programs/sk_skb.rs
@@ -1,6 +1,9 @@
 //! Skskb programs.
 
-use std::{os::fd::AsRawFd, path::Path};
+use std::{
+    os::fd::{AsFd, AsRawFd},
+    path::Path,
+};
 
 use crate::{
     generated::{
@@ -45,8 +48,8 @@ pub enum SkSkbKind {
 /// let intercept_ingress: SockMap<_> = bpf.map("INTERCEPT_INGRESS").unwrap().try_into()?;
 /// let map_fd = intercept_ingress.fd()?;
 ///
-/// let prog: &mut SkSkb = bpf.program_mut("intercept_ingress_packet").unwrap().try_into()?;
-/// prog.load()?;
+/// let prog: &mut SkSkb = bpf.programs.get_mut("intercept_ingress_packet").unwrap().try_into()?;
+/// prog.load(bpf.btf_fd.as_ref())?;
 /// prog.attach(map_fd)?;
 ///
 /// # Ok::<(), aya::BpfError>(())
@@ -64,8 +67,8 @@ pub struct SkSkb {
 
 impl SkSkb {
     /// Loads the program inside the kernel.
-    pub fn load(&mut self) -> Result<(), ProgramError> {
-        load_program(BPF_PROG_TYPE_SK_SKB, &mut self.data)
+    pub fn load(&mut self, btf_fd: Option<impl AsFd>) -> Result<(), ProgramError> {
+        load_program(BPF_PROG_TYPE_SK_SKB, &mut self.data, btf_fd)
     }
 
     /// Attaches the program to the given socket map.
diff --git a/aya/src/programs/sock_ops.rs b/aya/src/programs/sock_ops.rs
index fe8dccc0..a4fffbff 100644
--- a/aya/src/programs/sock_ops.rs
+++ b/aya/src/programs/sock_ops.rs
@@ -1,5 +1,5 @@
 //! Socket option programs.
-use std::os::fd::AsRawFd;
+use std::os::fd::{AsFd, AsRawFd};
 
 use crate::{
     generated::{bpf_attach_type::BPF_CGROUP_SOCK_OPS, bpf_prog_type::BPF_PROG_TYPE_SOCK_OPS},
@@ -39,8 +39,8 @@ use crate::{
 /// use aya::programs::SockOps;
 ///
 /// let file = File::open("/sys/fs/cgroup/unified")?;
-/// let prog: &mut SockOps = bpf.program_mut("intercept_active_sockets").unwrap().try_into()?;
-/// prog.load()?;
+/// let prog: &mut SockOps = bpf.programs.get_mut("intercept_active_sockets").unwrap().try_into()?;
+/// prog.load(bpf.btf_fd.as_ref())?;
 /// prog.attach(file)?;
 /// # Ok::<(), Error>(())
 #[derive(Debug)]
@@ -51,8 +51,8 @@ pub struct SockOps {
 
 impl SockOps {
     /// Loads the program inside the kernel.
-    pub fn load(&mut self) -> Result<(), ProgramError> {
-        load_program(BPF_PROG_TYPE_SOCK_OPS, &mut self.data)
+    pub fn load(&mut self, btf_fd: Option<impl AsFd>) -> Result<(), ProgramError> {
+        load_program(BPF_PROG_TYPE_SOCK_OPS, &mut self.data, btf_fd)
     }
 
     /// Attaches the program to the given cgroup.
diff --git a/aya/src/programs/socket_filter.rs b/aya/src/programs/socket_filter.rs
index 5ff9a301..fb2fdc66 100644
--- a/aya/src/programs/socket_filter.rs
+++ b/aya/src/programs/socket_filter.rs
@@ -2,7 +2,7 @@
 use libc::{setsockopt, SOL_SOCKET};
 use std::{
     io, mem,
-    os::fd::{AsRawFd, RawFd},
+    os::fd::{AsFd, AsRawFd, RawFd},
 };
 use thiserror::Error;
 
@@ -52,8 +52,8 @@ pub enum SocketFilterError {
 /// use aya::programs::SocketFilter;
 ///
 /// let mut client = TcpStream::connect("127.0.0.1:1234")?;
-/// let prog: &mut SocketFilter = bpf.program_mut("filter_packets").unwrap().try_into()?;
-/// prog.load()?;
+/// let prog: &mut SocketFilter = bpf.programs.get_mut("filter_packets").unwrap().try_into()?;
+/// prog.load(bpf.btf_fd.as_ref())?;
 /// prog.attach(client.as_raw_fd())?;
 /// # Ok::<(), Error>(())
 /// ```
@@ -65,8 +65,8 @@ pub struct SocketFilter {
 
 impl SocketFilter {
     /// Loads the program inside the kernel.
-    pub fn load(&mut self) -> Result<(), ProgramError> {
-        load_program(BPF_PROG_TYPE_SOCKET_FILTER, &mut self.data)
+    pub fn load(&mut self, btf_fd: Option<impl AsFd>) -> Result<(), ProgramError> {
+        load_program(BPF_PROG_TYPE_SOCKET_FILTER, &mut self.data, btf_fd)
     }
 
     /// Attaches the filter on the given socket.
diff --git a/aya/src/programs/tc.rs b/aya/src/programs/tc.rs
index fad633a3..860bff38 100644
--- a/aya/src/programs/tc.rs
+++ b/aya/src/programs/tc.rs
@@ -4,6 +4,7 @@ use thiserror::Error;
 use std::{
     ffi::{CStr, CString},
     io,
+    os::fd::AsFd,
     path::Path,
 };
 
@@ -63,8 +64,8 @@ pub enum TcAttachType {
 /// // attached
 /// tc::qdisc_add_clsact("eth0")?;
 ///
-/// let prog: &mut SchedClassifier = bpf.program_mut("redirect_ingress").unwrap().try_into()?;
-/// prog.load()?;
+/// let prog: &mut SchedClassifier = bpf.programs.get_mut("redirect_ingress").unwrap().try_into()?;
+/// prog.load(bpf.btf_fd.as_ref())?;
 /// prog.attach("eth0", TcAttachType::Ingress)?;
 ///
 /// # Ok::<(), Error>(())
@@ -114,8 +115,8 @@ pub struct TcOptions {
 
 impl SchedClassifier {
     /// Loads the program inside the kernel.
-    pub fn load(&mut self) -> Result<(), ProgramError> {
-        load_program(BPF_PROG_TYPE_SCHED_CLS, &mut self.data)
+    pub fn load(&mut self, btf_fd: Option<impl AsFd>) -> Result<(), ProgramError> {
+        load_program(BPF_PROG_TYPE_SCHED_CLS, &mut self.data, btf_fd)
     }
 
     /// Attaches the program to the given `interface` using the default options.
diff --git a/aya/src/programs/tp_btf.rs b/aya/src/programs/tp_btf.rs
index eb419ab1..302a78ff 100644
--- a/aya/src/programs/tp_btf.rs
+++ b/aya/src/programs/tp_btf.rs
@@ -1,5 +1,7 @@
 //! BTF-enabled raw tracepoints.
 
+use std::os::fd::AsFd;
+
 use crate::{
     generated::{bpf_attach_type::BPF_TRACE_RAW_TP, bpf_prog_type::BPF_PROG_TYPE_TRACING},
     obj::btf::{Btf, BtfKind},
@@ -36,8 +38,8 @@ use crate::{
 /// use aya::{Bpf, programs::BtfTracePoint, BtfError, Btf};
 ///
 /// let btf = Btf::from_sys_fs()?;
-/// let program: &mut BtfTracePoint = bpf.program_mut("sched_process_fork").unwrap().try_into()?;
-/// program.load("sched_process_fork", &btf)?;
+/// let program: &mut BtfTracePoint = bpf.programs.get_mut("sched_process_fork").unwrap().try_into()?;
+/// program.load("sched_process_fork", &btf, bpf.btf_fd.as_ref())?;
 /// program.attach()?;
 /// # Ok::<(), Error>(())
 /// ```
@@ -57,12 +59,17 @@ impl BtfTracePoint {
     ///
     /// * `tracepoint` - full name of the tracepoint that we should attach to
     /// * `btf` - btf information for the target system
-    pub fn load(&mut self, tracepoint: &str, btf: &Btf) -> Result<(), ProgramError> {
+    pub fn load(
+        &mut self,
+        tracepoint: &str,
+        btf: &Btf,
+        btf_fd: Option<impl AsFd>,
+    ) -> Result<(), ProgramError> {
         self.data.expected_attach_type = Some(BPF_TRACE_RAW_TP);
         let type_name = format!("btf_trace_{tracepoint}");
         self.data.attach_btf_id =
             Some(btf.id_by_type_name_kind(type_name.as_str(), BtfKind::Typedef)?);
-        load_program(BPF_PROG_TYPE_TRACING, &mut self.data)
+        load_program(BPF_PROG_TYPE_TRACING, &mut self.data, btf_fd)
     }
 
     /// Attaches the program.
diff --git a/aya/src/programs/trace_point.rs b/aya/src/programs/trace_point.rs
index b23594b7..2f522240 100644
--- a/aya/src/programs/trace_point.rs
+++ b/aya/src/programs/trace_point.rs
@@ -1,5 +1,5 @@
 //! Tracepoint programs.
-use std::{fs, io, path::Path};
+use std::{fs, io, os::fd::AsFd, path::Path};
 use thiserror::Error;
 
 use crate::{
@@ -54,8 +54,8 @@ pub enum TracePointError {
 /// # let mut bpf = aya::Bpf::load(&[])?;
 /// use aya::programs::TracePoint;
 ///
-/// let prog: &mut TracePoint = bpf.program_mut("trace_context_switch").unwrap().try_into()?;
-/// prog.load()?;
+/// let prog: &mut TracePoint = bpf.programs.get_mut("trace_context_switch").unwrap().try_into()?;
+/// prog.load(bpf.btf_fd.as_ref())?;
 /// prog.attach("sched", "sched_switch")?;
 /// # Ok::<(), Error>(())
 /// ```
@@ -67,8 +67,8 @@ pub struct TracePoint {
 
 impl TracePoint {
     /// Loads the program inside the kernel.
-    pub fn load(&mut self) -> Result<(), ProgramError> {
-        load_program(BPF_PROG_TYPE_TRACEPOINT, &mut self.data)
+    pub fn load(&mut self, btf_fd: Option<impl AsFd>) -> Result<(), ProgramError> {
+        load_program(BPF_PROG_TYPE_TRACEPOINT, &mut self.data, btf_fd)
     }
 
     /// Attaches to a given trace point.
diff --git a/aya/src/programs/uprobe.rs b/aya/src/programs/uprobe.rs
index bab82d4b..60bfdf83 100644
--- a/aya/src/programs/uprobe.rs
+++ b/aya/src/programs/uprobe.rs
@@ -7,7 +7,7 @@ use std::{
     fs,
     io::{self, BufRead, Cursor, Read},
     mem,
-    os::raw::c_char,
+    os::{fd::AsFd, raw::c_char},
     path::{Path, PathBuf},
     sync::Arc,
 };
@@ -50,8 +50,8 @@ pub struct UProbe {
 
 impl UProbe {
     /// Loads the program inside the kernel.
-    pub fn load(&mut self) -> Result<(), ProgramError> {
-        load_program(BPF_PROG_TYPE_KPROBE, &mut self.data)
+    pub fn load(&mut self, btf_fd: Option<impl AsFd>) -> Result<(), ProgramError> {
+        load_program(BPF_PROG_TYPE_KPROBE, &mut self.data, btf_fd)
     }
 
     /// Returns `UProbe` if the program is a `uprobe`, or `URetProbe` if the
diff --git a/aya/src/programs/xdp.rs b/aya/src/programs/xdp.rs
index 9372b281..36c2adc8 100644
--- a/aya/src/programs/xdp.rs
+++ b/aya/src/programs/xdp.rs
@@ -3,7 +3,13 @@
 use crate::util::KernelVersion;
 use bitflags;
 use libc::if_nametoindex;
-use std::{convert::TryFrom, ffi::CString, hash::Hash, io, mem, os::fd::RawFd};
+use std::{
+    convert::TryFrom,
+    ffi::CString,
+    hash::Hash,
+    io, mem,
+    os::fd::{AsFd, RawFd},
+};
 use thiserror::Error;
 
 use crate::{
@@ -78,9 +84,9 @@ pub struct Xdp {
 
 impl Xdp {
     /// Loads the program inside the kernel.
-    pub fn load(&mut self) -> Result<(), ProgramError> {
+    pub fn load(&mut self, btf_fd: Option<impl AsFd>) -> Result<(), ProgramError> {
         self.data.expected_attach_type = Some(bpf_attach_type::BPF_XDP);
-        load_program(BPF_PROG_TYPE_XDP, &mut self.data)
+        load_program(BPF_PROG_TYPE_XDP, &mut self.data, btf_fd)
     }
 
     /// Attaches the program to the given `interface`.
diff --git a/aya/src/sys/bpf.rs b/aya/src/sys/bpf.rs
index 32a55016..3bc0c655 100644
--- a/aya/src/sys/bpf.rs
+++ b/aya/src/sys/bpf.rs
@@ -3,7 +3,7 @@ use std::{
     ffi::{CStr, CString},
     io,
     mem::{self, MaybeUninit},
-    os::fd::RawFd,
+    os::fd::{AsFd, AsRawFd, BorrowedFd, FromRawFd, OwnedFd, RawFd},
     slice,
 };
 
@@ -35,7 +35,7 @@ use crate::{
 pub(crate) fn bpf_create_map(
     name: &CStr,
     def: &obj::Map,
-    btf_fd: Option<RawFd>,
+    btf_fd: Option<impl AsFd>,
     kernel_version: KernelVersion,
 ) -> SysResult<c_long> {
     let mut attr = unsafe { mem::zeroed::<bpf_attr>() };
@@ -75,7 +75,7 @@ pub(crate) fn bpf_create_map(
             _ => {
                 u.btf_key_type_id = m.def.btf_key_type_id;
                 u.btf_value_type_id = m.def.btf_value_type_id;
-                u.btf_fd = btf_fd.unwrap_or_default() as u32;
+                u.btf_fd = btf_fd.map(|fd| fd.as_fd().as_raw_fd()).unwrap_or_default() as u32;
             }
         }
     }
@@ -115,7 +115,7 @@ pub(crate) struct BpfLoadProgramAttrs<'a> {
     pub(crate) license: &'a CStr,
     pub(crate) kernel_version: u32,
     pub(crate) expected_attach_type: Option<bpf_attach_type>,
-    pub(crate) prog_btf_fd: Option<RawFd>,
+    pub(crate) prog_btf_fd: Option<BorrowedFd<'a>>,
     pub(crate) attach_btf_obj_fd: Option<u32>,
     pub(crate) attach_btf_id: Option<u32>,
     pub(crate) attach_prog_fd: Option<RawFd>,
@@ -161,7 +161,7 @@ pub(crate) fn bpf_load_program(
     let func_info_buf = aya_attr.func_info.func_info_bytes();
 
     if let Some(btf_fd) = aya_attr.prog_btf_fd {
-        u.prog_btf_fd = btf_fd as u32;
+        u.prog_btf_fd = btf_fd.as_raw_fd() as u32;
         if aya_attr.line_info_rec_size > 0 {
             u.line_info = line_info_buf.as_ptr() as *const _ as u64;
             u.line_info_cnt = aya_attr.line_info.len() as u32;
@@ -551,7 +551,7 @@ pub(crate) fn bpf_load_btf(
     raw_btf: &[u8],
     log_buf: &mut [u8],
     verifier_log_level: VerifierLogLevel,
-) -> SysResult<c_long> {
+) -> SysResult<OwnedFd> {
     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;
@@ -561,7 +561,9 @@ pub(crate) fn bpf_load_btf(
         u.btf_log_buf = log_buf.as_mut_ptr() as u64;
         u.btf_log_size = log_buf.len() as u32;
     }
-    sys_bpf(bpf_cmd::BPF_BTF_LOAD, &attr)
+    let raw_fd = sys_bpf(bpf_cmd::BPF_BTF_LOAD, &attr)? as RawFd;
+    // SAFETY: BPF_BTF_LOAD returns a newly created file descriptor
+    Ok(unsafe { OwnedFd::from_raw_fd(raw_fd) })
 }
 
 pub(crate) fn bpf_btf_get_fd_by_id(id: u32) -> Result<RawFd, io::Error> {
@@ -701,10 +703,9 @@ pub(crate) fn is_bpf_global_data_supported() -> bool {
         }),
         fd: None,
         pinned: false,
-        btf_fd: None,
     };
 
-    if let Ok(map_fd) = map_data.create("aya_global") {
+    if let Ok(map_fd) = map_data.create("aya_global", Option::<BorrowedFd>::None) {
         insns[0].imm = map_fd;
 
         let gpl = b"GPL\0";
diff --git a/test/integration-test/src/tests/bpf_probe_read.rs b/test/integration-test/src/tests/bpf_probe_read.rs
index 20e6a134..abaf748f 100644
--- a/test/integration-test/src/tests/bpf_probe_read.rs
+++ b/test/integration-test/src/tests/bpf_probe_read.rs
@@ -105,8 +105,8 @@ fn result_bytes(bpf: &Bpf) -> Vec<u8> {
 fn load_and_attach_uprobe(prog_name: &str, func_name: &str, bytes: &[u8]) -> Bpf {
     let mut bpf = Bpf::load(bytes).unwrap();
 
-    let prog: &mut UProbe = bpf.program_mut(prog_name).unwrap().try_into().unwrap();
-    prog.load().unwrap();
+    let prog: &mut UProbe = bpf.programs.get_mut(prog_name).unwrap().try_into().unwrap();
+    prog.load(bpf.btf_fd.as_ref()).unwrap();
 
     prog.attach(Some(func_name), 0, "/proc/self/exe", None)
         .unwrap();
diff --git a/test/integration-test/src/tests/btf_relocations.rs b/test/integration-test/src/tests/btf_relocations.rs
index 75f36223..3a8b53e9 100644
--- a/test/integration-test/src/tests/btf_relocations.rs
+++ b/test/integration-test/src/tests/btf_relocations.rs
@@ -385,11 +385,14 @@ impl RelocationTestRunner {
         }
         let mut bpf = loader.load(&self.ebpf).context("Loading eBPF failed")?;
         let program: &mut TracePoint = bpf
-            .program_mut("bpf_prog")
+            .programs
+            .get_mut("bpf_prog")
             .context("bpf_prog not found")?
             .try_into()
             .context("program not a tracepoint")?;
-        program.load().context("Loading tracepoint failed")?;
+        program
+            .load(bpf.btf_fd.as_ref())
+            .context("Loading tracepoint failed")?;
         // Attach to sched_switch and wait some time to make sure it executed at least once
         program
             .attach("sched", "sched_switch")
diff --git a/test/integration-test/src/tests/load.rs b/test/integration-test/src/tests/load.rs
index 8958ae59..92b9ddc9 100644
--- a/test/integration-test/src/tests/load.rs
+++ b/test/integration-test/src/tests/load.rs
@@ -17,11 +17,12 @@ const RETRY_DURATION_MS: u64 = 10;
 fn long_name() {
     let mut bpf = Bpf::load(crate::NAME_TEST).unwrap();
     let name_prog: &mut Xdp = bpf
-        .program_mut("ihaveaverylongname")
+        .programs
+        .get_mut("ihaveaverylongname")
         .unwrap()
         .try_into()
         .unwrap();
-    name_prog.load().unwrap();
+    name_prog.load(bpf.btf_fd.as_ref()).unwrap();
     name_prog.attach("lo", XdpFlags::default()).unwrap();
 
     // We used to be able to assert with bpftool that the program name was short.
@@ -36,8 +37,13 @@ fn multiple_btf_maps() {
     let map_1: Array<_, u64> = bpf.take_map("map_1").unwrap().try_into().unwrap();
     let map_2: Array<_, u64> = bpf.take_map("map_2").unwrap().try_into().unwrap();
 
-    let prog: &mut TracePoint = bpf.program_mut("tracepoint").unwrap().try_into().unwrap();
-    prog.load().unwrap();
+    let prog: &mut TracePoint = bpf
+        .programs
+        .get_mut("tracepoint")
+        .unwrap()
+        .try_into()
+        .unwrap();
+    prog.load(bpf.btf_fd.as_ref()).unwrap();
     prog.attach("sched", "sched_switch").unwrap();
 
     thread::sleep(time::Duration::from_secs(3));
@@ -110,8 +116,13 @@ macro_rules! assert_loaded {
 #[test]
 fn unload_xdp() {
     let mut bpf = Bpf::load(crate::TEST).unwrap();
-    let prog: &mut Xdp = bpf.program_mut("test_xdp").unwrap().try_into().unwrap();
-    prog.load().unwrap();
+    let prog: &mut Xdp = bpf
+        .programs
+        .get_mut("test_xdp")
+        .unwrap()
+        .try_into()
+        .unwrap();
+    prog.load(bpf.btf_fd.as_ref()).unwrap();
     assert_loaded!("test_xdp", true);
     let link = prog.attach("lo", XdpFlags::default()).unwrap();
     {
@@ -121,7 +132,7 @@ fn unload_xdp() {
     };
 
     assert_loaded!("test_xdp", false);
-    prog.load().unwrap();
+    prog.load(bpf.btf_fd.as_ref()).unwrap();
 
     assert_loaded!("test_xdp", true);
     prog.attach("lo", XdpFlags::default()).unwrap();
@@ -135,8 +146,13 @@ fn unload_xdp() {
 #[test]
 fn unload_kprobe() {
     let mut bpf = Bpf::load(crate::TEST).unwrap();
-    let prog: &mut KProbe = bpf.program_mut("test_kprobe").unwrap().try_into().unwrap();
-    prog.load().unwrap();
+    let prog: &mut KProbe = bpf
+        .programs
+        .get_mut("test_kprobe")
+        .unwrap()
+        .try_into()
+        .unwrap();
+    prog.load(bpf.btf_fd.as_ref()).unwrap();
     assert_loaded!("test_kprobe", true);
     let link = prog.attach("try_to_wake_up", 0).unwrap();
     {
@@ -146,7 +162,7 @@ fn unload_kprobe() {
     };
 
     assert_loaded!("test_kprobe", false);
-    prog.load().unwrap();
+    prog.load(bpf.btf_fd.as_ref()).unwrap();
 
     assert_loaded!("test_kprobe", true);
     prog.attach("try_to_wake_up", 0).unwrap();
@@ -161,12 +177,13 @@ fn unload_kprobe() {
 fn basic_tracepoint() {
     let mut bpf = Bpf::load(crate::TEST).unwrap();
     let prog: &mut TracePoint = bpf
-        .program_mut("test_tracepoint")
+        .programs
+        .get_mut("test_tracepoint")
         .unwrap()
         .try_into()
         .unwrap();
 
-    prog.load().unwrap();
+    prog.load(bpf.btf_fd.as_ref()).unwrap();
     assert_loaded!("test_tracepoint", true);
     let link = prog.attach("syscalls", "sys_enter_kill").unwrap();
 
@@ -177,7 +194,7 @@ fn basic_tracepoint() {
     };
 
     assert_loaded!("test_tracepoint", false);
-    prog.load().unwrap();
+    prog.load(bpf.btf_fd.as_ref()).unwrap();
 
     assert_loaded!("test_tracepoint", true);
     prog.attach("syscalls", "sys_enter_kill").unwrap();
@@ -191,9 +208,14 @@ fn basic_tracepoint() {
 #[test]
 fn basic_uprobe() {
     let mut bpf = Bpf::load(crate::TEST).unwrap();
-    let prog: &mut UProbe = bpf.program_mut("test_uprobe").unwrap().try_into().unwrap();
+    let prog: &mut UProbe = bpf
+        .programs
+        .get_mut("test_uprobe")
+        .unwrap()
+        .try_into()
+        .unwrap();
 
-    prog.load().unwrap();
+    prog.load(bpf.btf_fd.as_ref()).unwrap();
     assert_loaded!("test_uprobe", true);
     let link = prog.attach(Some("sleep"), 0, "libc", None).unwrap();
 
@@ -204,7 +226,7 @@ fn basic_uprobe() {
     };
 
     assert_loaded!("test_uprobe", false);
-    prog.load().unwrap();
+    prog.load(bpf.btf_fd.as_ref()).unwrap();
 
     assert_loaded!("test_uprobe", true);
     prog.attach(Some("sleep"), 0, "libc", None).unwrap();
@@ -224,8 +246,13 @@ fn pin_link() {
     }
 
     let mut bpf = Bpf::load(crate::TEST).unwrap();
-    let prog: &mut Xdp = bpf.program_mut("test_xdp").unwrap().try_into().unwrap();
-    prog.load().unwrap();
+    let prog: &mut Xdp = bpf
+        .programs
+        .get_mut("test_xdp")
+        .unwrap()
+        .try_into()
+        .unwrap();
+    prog.load(bpf.btf_fd.as_ref()).unwrap();
     let link_id = prog.attach("lo", XdpFlags::default()).unwrap();
     let link = prog.take_link(link_id).unwrap();
     assert_loaded!("test_xdp", true);
@@ -257,8 +284,8 @@ fn pin_lifecycle() {
     // 1. Load Program and Pin
     {
         let mut bpf = Bpf::load(crate::PASS).unwrap();
-        let prog: &mut Xdp = bpf.program_mut("pass").unwrap().try_into().unwrap();
-        prog.load().unwrap();
+        let prog: &mut Xdp = bpf.programs.get_mut("pass").unwrap().try_into().unwrap();
+        prog.load(bpf.btf_fd.as_ref()).unwrap();
         prog.pin("/sys/fs/bpf/aya-xdp-test-prog").unwrap();
     }
 
@@ -291,8 +318,8 @@ fn pin_lifecycle() {
     // 4. Load a new version of the program, unpin link, and atomically replace old program
     {
         let mut bpf = Bpf::load(crate::PASS).unwrap();
-        let prog: &mut Xdp = bpf.program_mut("pass").unwrap().try_into().unwrap();
-        prog.load().unwrap();
+        let prog: &mut Xdp = bpf.programs.get_mut("pass").unwrap().try_into().unwrap();
+        prog.load(bpf.btf_fd.as_ref()).unwrap();
 
         let link = PinnedLink::from_pin("/sys/fs/bpf/aya-xdp-test-lo")
             .unwrap()
@@ -312,11 +339,12 @@ fn pin_lifecycle_tracepoint() {
     {
         let mut bpf = Bpf::load(crate::TEST).unwrap();
         let prog: &mut TracePoint = bpf
-            .program_mut("test_tracepoint")
+            .programs
+            .get_mut("test_tracepoint")
             .unwrap()
             .try_into()
             .unwrap();
-        prog.load().unwrap();
+        prog.load(bpf.btf_fd.as_ref()).unwrap();
         prog.pin("/sys/fs/bpf/aya-tracepoint-test-prog").unwrap();
     }
 
@@ -365,8 +393,13 @@ fn pin_lifecycle_kprobe() {
     // 1. Load Program and Pin
     {
         let mut bpf = Bpf::load(crate::TEST).unwrap();
-        let prog: &mut KProbe = bpf.program_mut("test_kprobe").unwrap().try_into().unwrap();
-        prog.load().unwrap();
+        let prog: &mut KProbe = bpf
+            .programs
+            .get_mut("test_kprobe")
+            .unwrap()
+            .try_into()
+            .unwrap();
+        prog.load(bpf.btf_fd.as_ref()).unwrap();
         prog.pin("/sys/fs/bpf/aya-kprobe-test-prog").unwrap();
     }
 
@@ -423,8 +456,13 @@ fn pin_lifecycle_uprobe() {
     // 1. Load Program and Pin
     {
         let mut bpf = Bpf::load(crate::TEST).unwrap();
-        let prog: &mut UProbe = bpf.program_mut("test_uprobe").unwrap().try_into().unwrap();
-        prog.load().unwrap();
+        let prog: &mut UProbe = bpf
+            .programs
+            .get_mut("test_uprobe")
+            .unwrap()
+            .try_into()
+            .unwrap();
+        prog.load(bpf.btf_fd.as_ref()).unwrap();
         prog.pin("/sys/fs/bpf/aya-uprobe-test-prog").unwrap();
     }
 
diff --git a/test/integration-test/src/tests/log.rs b/test/integration-test/src/tests/log.rs
index 9033fcc0..79af60cf 100644
--- a/test/integration-test/src/tests/log.rs
+++ b/test/integration-test/src/tests/log.rs
@@ -58,8 +58,13 @@ async fn log() {
         .unwrap();
     }
 
-    let prog: &mut UProbe = bpf.program_mut("test_log").unwrap().try_into().unwrap();
-    prog.load().unwrap();
+    let prog: &mut UProbe = bpf
+        .programs
+        .get_mut("test_log")
+        .unwrap()
+        .try_into()
+        .unwrap();
+    prog.load(bpf.btf_fd.as_ref()).unwrap();
     prog.attach(Some("trigger_ebpf_program"), 0, "/proc/self/exe", None)
         .unwrap();
 
diff --git a/test/integration-test/src/tests/relocations.rs b/test/integration-test/src/tests/relocations.rs
index 8642dc4b..8e1d2227 100644
--- a/test/integration-test/src/tests/relocations.rs
+++ b/test/integration-test/src/tests/relocations.rs
@@ -33,8 +33,8 @@ fn text_64_64_reloc() {
 fn load_and_attach(name: &str, bytes: &[u8]) -> Bpf {
     let mut bpf = Bpf::load(bytes).unwrap();
 
-    let prog: &mut UProbe = bpf.program_mut(name).unwrap().try_into().unwrap();
-    prog.load().unwrap();
+    let prog: &mut UProbe = bpf.programs.get_mut(name).unwrap().try_into().unwrap();
+    prog.load(bpf.btf_fd.as_ref()).unwrap();
 
     prog.attach(
         Some("trigger_relocations_program"),
diff --git a/test/integration-test/src/tests/smoke.rs b/test/integration-test/src/tests/smoke.rs
index 8b09ac92..855e353d 100644
--- a/test/integration-test/src/tests/smoke.rs
+++ b/test/integration-test/src/tests/smoke.rs
@@ -13,8 +13,8 @@ fn xdp() {
     }
 
     let mut bpf = Bpf::load(crate::PASS).unwrap();
-    let dispatcher: &mut Xdp = bpf.program_mut("pass").unwrap().try_into().unwrap();
-    dispatcher.load().unwrap();
+    let dispatcher: &mut Xdp = bpf.programs.get_mut("pass").unwrap().try_into().unwrap();
+    dispatcher.load(bpf.btf_fd.as_ref()).unwrap();
     dispatcher.attach("lo", XdpFlags::default()).unwrap();
 }
 
@@ -26,11 +26,13 @@ fn extension() {
         return;
     }
     let mut bpf = Bpf::load(crate::MAIN).unwrap();
-    let pass: &mut Xdp = bpf.program_mut("pass").unwrap().try_into().unwrap();
-    pass.load().unwrap();
+    let pass: &mut Xdp = bpf.programs.get_mut("pass").unwrap().try_into().unwrap();
+    pass.load(bpf.btf_fd.as_ref()).unwrap();
     pass.attach("lo", XdpFlags::default()).unwrap();
 
     let mut bpf = BpfLoader::new().extension("drop").load(crate::EXT).unwrap();
-    let drop_: &mut Extension = bpf.program_mut("drop").unwrap().try_into().unwrap();
-    drop_.load(pass.fd().unwrap(), "xdp_pass").unwrap();
+    let drop_: &mut Extension = bpf.programs.get_mut("drop").unwrap().try_into().unwrap();
+    drop_
+        .load(pass.fd().unwrap(), "xdp_pass", bpf.btf_fd.as_ref())
+        .unwrap();
 }