@@ -113,7 +113,7 @@ pub fn features() -> &'static Features {
113113/// ```
114114#[ derive( Debug ) ]
115115pub struct EbpfLoader < ' a > {
116- btf : Option < Cow < ' a , Btf > > ,
116+ btf : TargetBtf < ' a > ,
117117 default_map_pin_directory : Option < PathBuf > ,
118118 globals : HashMap < & ' a str , ( & ' a [ u8 ] , bool ) > ,
119119 // Max entries overrides the max_entries field of the map that matches the provided name
@@ -128,6 +128,34 @@ pub struct EbpfLoader<'a> {
128128 allow_unsupported_maps : bool ,
129129}
130130
131+ #[ derive( Debug ) ]
132+ enum TargetBtf < ' a > {
133+ System ,
134+ Parsed ( Cow < ' a , Btf > ) ,
135+ Source ( BtfSource ) ,
136+ }
137+
138+ type BtfParser = dyn Fn ( & [ u8 ] ) -> Result < Btf , BtfError > ;
139+ // Use `Fn` instead of `FnOnce`: when target BTF loading fails for an object with
140+ // only weak typed ksyms, loading continues, so a reused loader must be able to
141+ // retry the source.
142+ type BtfSourceFn = dyn Fn ( & BtfParser ) -> Result < Btf , EbpfError >
143+ // Preserve `EbpfLoader`'s existing auto traits.
144+ + Send
145+ + Sync
146+ + std:: panic:: RefUnwindSafe
147+ + std:: panic:: UnwindSafe
148+ // Keep captured state independent of `EbpfLoader`'s borrowed configuration.
149+ + ' static ;
150+
151+ struct BtfSource ( Box < BtfSourceFn > ) ;
152+
153+ impl std:: fmt:: Debug for BtfSource {
154+ fn fmt ( & self , f : & mut std:: fmt:: Formatter < ' _ > ) -> std:: fmt:: Result {
155+ f. debug_struct ( "BtfSource" ) . finish_non_exhaustive ( )
156+ }
157+ }
158+
131159/// Builder style API for advanced loading of eBPF programs.
132160#[ deprecated( since = "0.13.0" , note = "use `EbpfLoader` instead" ) ]
133161pub type BpfLoader < ' a > = EbpfLoader < ' a > ;
@@ -157,7 +185,7 @@ impl<'a> EbpfLoader<'a> {
157185 /// Creates a new loader instance.
158186 pub fn new ( ) -> Self {
159187 Self {
160- btf : None ,
188+ btf : TargetBtf :: System ,
161189 default_map_pin_directory : None ,
162190 globals : HashMap :: new ( ) ,
163191 max_entries : HashMap :: new ( ) ,
@@ -174,6 +202,10 @@ impl<'a> EbpfLoader<'a> {
174202 /// the object contains CO-RE relocations or typed kernel symbols. Use this
175203 /// method to load `BTF` from a custom location.
176204 ///
205+ /// The parsed `BTF` can be shared across multiple loaders, avoiding repeated
206+ /// parsing. To lazily read and parse target `BTF`, use
207+ /// [`EbpfLoader::btf_source`].
208+ ///
177209 /// # Example
178210 ///
179211 /// ```no_run
@@ -190,7 +222,45 @@ impl<'a> EbpfLoader<'a> {
190222 /// # Ok::<(), aya::EbpfError>(())
191223 /// ```
192224 pub fn btf ( & mut self , btf : & ' a Btf ) -> & mut Self {
193- self . btf = Some ( Cow :: Borrowed ( btf) ) ;
225+ self . btf = TargetBtf :: Parsed ( Cow :: Borrowed ( btf) ) ;
226+ self
227+ }
228+
229+ /// Sets a callback for lazily loading the target [BTF](Btf) info.
230+ ///
231+ /// The callback is called only when the object contains CO-RE relocations or
232+ /// typed kernel symbols. It receives a parser configured for the object's
233+ /// endianness, allowing BTF to be parsed from a borrowed byte slice. Once
234+ /// parsed, the target `BTF` is cached and reused by this loader. To share one
235+ /// parsed value across multiple loaders, use [`EbpfLoader::btf`] instead.
236+ ///
237+ /// # Example
238+ ///
239+ /// ```no_run
240+ /// use std::fs;
241+ ///
242+ /// use aya::{EbpfError, EbpfLoader};
243+ ///
244+ /// let bpf = EbpfLoader::new()
245+ /// .btf_source(|parse| {
246+ /// let data =
247+ /// fs::read("/custom_btf_file").map_err(EbpfError::BtfSourceError)?;
248+ /// Ok(parse(&data)?)
249+ /// })
250+ /// .load_file("file.o")?;
251+ ///
252+ /// # Ok::<(), aya::EbpfError>(())
253+ /// ```
254+ pub fn btf_source < F > ( & mut self , source : F ) -> & mut Self
255+ where
256+ F : Fn ( & BtfParser ) -> Result < Btf , EbpfError >
257+ + Send
258+ + Sync
259+ + std:: panic:: RefUnwindSafe
260+ + std:: panic:: UnwindSafe
261+ + ' static ,
262+ {
263+ self . btf = TargetBtf :: Source ( BtfSource ( Box :: new ( source) ) ) ;
194264 self
195265 }
196266
@@ -493,30 +563,48 @@ impl<'a> EbpfLoader<'a> {
493563 None
494564 } ;
495565
496- if btf. is_none ( ) && ( obj. has_btf_relocations ( ) || obj. has_typed_ksyms ( ) ) {
497- match Btf :: from_sys_fs ( ) {
498- Ok ( kernel_btf) => * btf = Some ( Cow :: Owned ( kernel_btf) ) ,
566+ if obj. has_btf_relocations ( ) || obj. has_typed_ksyms ( ) {
567+ let endianness = obj. endianness ;
568+ let target_btf: Result < Cow < ' _ , Btf > , EbpfError > = match btf {
569+ TargetBtf :: System => Btf :: from_sys_fs ( ) . map ( Cow :: Owned ) . map_err ( EbpfError :: from) ,
570+ TargetBtf :: Parsed ( btf) => Ok ( Cow :: Borrowed ( btf) ) ,
571+ TargetBtf :: Source ( BtfSource ( source) ) => {
572+ source ( & move |data| Btf :: parse ( data, endianness) ) . map ( Cow :: Owned )
573+ }
574+ } ;
575+
576+ match target_btf {
577+ Ok ( target_btf) => {
578+ let result = obj
579+ . relocate_btf ( & target_btf)
580+ . map_err ( EbpfError :: from)
581+ . and_then ( |( ) | {
582+ obj. resolve_externs ( Some ( & target_btf) )
583+ . map_err ( EbpfError :: from)
584+ } ) ;
585+
586+ if let Cow :: Owned ( target_btf) = target_btf {
587+ * btf = TargetBtf :: Parsed ( Cow :: Owned ( target_btf) ) ;
588+ }
589+
590+ result?;
591+ }
499592 Err ( err) => {
500- // CO-RE relocations and strong typed ksyms cannot be resolved without kernel
593+ // CO-RE relocations and strong typed ksyms cannot be resolved without target
501594 // BTF, so preserve the original loading error.
502595 if obj. has_btf_relocations ( ) || obj. has_strong_typed_ksyms ( ) {
503- return Err ( err. into ( ) ) ;
596+ return Err ( err) ;
504597 }
505598
506- // Unresolved weak typed ksyms are allowed and handled during extern
507- // relocation.
508- warn ! ( "kernel BTF is unavailable; weak typed ksyms will be unresolved: {err}" )
599+ // Unresolved weak typed ksyms are allowed and handled during extern relocation.
600+ warn ! ( "target BTF is unavailable; weak typed ksyms will be unresolved: {err}" ) ;
601+ obj . resolve_externs ( None ) ? ;
509602 }
510603 }
604+ } else {
605+ obj. resolve_externs ( None ) ?;
511606 }
512607
513- let btf = btf. as_deref ( ) ;
514- if let Some ( btf) = btf {
515- obj. relocate_btf ( btf) ?;
516- }
517-
518- obj. resolve_externs ( btf) ?;
519-
520608 const fn is_map_of_maps ( map_type : bpf_map_type ) -> bool {
521609 matches ! (
522610 map_type,
@@ -928,6 +1016,8 @@ const fn adjust_to_page_size(byte_size: u32, page_size: u32) -> u32 {
9281016mod tests {
9291017 use aya_obj:: generated:: bpf_map_type:: * ;
9301018
1019+ use super :: { Btf , BtfSource , EbpfLoader , TargetBtf } ;
1020+
9311021 const PAGE_SIZE : u32 = 4096 ;
9321022 const NUM_CPUS : u32 = 4 ;
9331023
@@ -975,6 +1065,26 @@ mod tests {
9751065 ) ;
9761066 }
9771067 }
1068+
1069+ #[ test]
1070+ fn test_btf_source_can_parse_borrowed_subslice ( ) {
1071+ let mut loader = EbpfLoader :: new ( ) ;
1072+ loader. btf_source ( |parse| {
1073+ // This models an mmap opened by the callback: its backing storage
1074+ // can be dropped once the borrowed subslice has been parsed.
1075+ let data = [ 0 , 1 , 2 , 3 ] ;
1076+ Ok ( parse ( & data[ 1 ..3 ] ) ?)
1077+ } ) ;
1078+
1079+ let TargetBtf :: Source ( BtfSource ( source) ) = & loader. btf else {
1080+ panic ! ( "expected a BTF source" ) ;
1081+ } ;
1082+ source ( & |data| {
1083+ assert_eq ! ( data, [ 1 , 2 ] ) ;
1084+ Ok ( Btf :: new ( ) )
1085+ } )
1086+ . unwrap ( ) ;
1087+ }
9781088}
9791089
9801090impl Default for EbpfLoader < ' _ > {
@@ -1244,6 +1354,10 @@ pub enum EbpfError {
12441354 error : io:: Error ,
12451355 } ,
12461356
1357+ /// Error reading target BTF
1358+ #[ error( "error reading BTF source" ) ]
1359+ BtfSourceError ( #[ source] io:: Error ) ,
1360+
12471361 /// Unexpected pinning type
12481362 #[ error( "unexpected pinning type {name}" ) ]
12491363 UnexpectedPinningType {
0 commit comments