aya_obj/btf/
relocation.rs

1use alloc::{
2    borrow::{Cow, ToOwned as _},
3    collections::BTreeMap,
4    format,
5    string::{String, ToString as _},
6    vec,
7    vec::Vec,
8};
9use core::{mem, ops::Bound::Included, ptr};
10
11use object::SectionIndex;
12
13use crate::{
14    Function, Object,
15    btf::{
16        Array, Btf, BtfError, BtfKind, BtfMember, BtfType, IntEncoding, MAX_SPEC_LEN, Struct,
17        Union, fields_are_compatible, types_are_compatible,
18    },
19    generated::{
20        BPF_ALU, BPF_ALU64, BPF_B, BPF_CALL, BPF_DW, BPF_H, BPF_JMP, BPF_K, BPF_LD, BPF_LDX,
21        BPF_ST, BPF_STX, BPF_W, BTF_INT_SIGNED, bpf_core_relo, bpf_core_relo_kind::*, bpf_insn,
22    },
23    util::HashMap,
24};
25
26/// The error type returned by [`Object::relocate_btf`].
27#[derive(thiserror::Error, Debug)]
28#[error("error relocating `{section}`")]
29pub struct BtfRelocationError {
30    /// The function name
31    pub section: String,
32    #[source]
33    /// The original error
34    error: RelocationError,
35}
36
37/// Relocation failures
38#[derive(thiserror::Error, Debug)]
39enum RelocationError {
40    #[cfg(feature = "std")]
41    /// I/O error
42    #[error(transparent)]
43    IOError(#[from] std::io::Error),
44
45    /// Section not found
46    #[error("section not found")]
47    SectionNotFound,
48
49    /// Function not found
50    #[error("function not found")]
51    FunctionNotFound,
52
53    /// Invalid relocation access string
54    #[error("invalid relocation access string {access_str}")]
55    InvalidAccessString {
56        /// The access string
57        access_str: String,
58    },
59
60    /// Invalid instruction index referenced by relocation
61    #[error(
62        "invalid instruction index #{index} referenced by relocation #{relocation_number}, the program contains {num_instructions} instructions"
63    )]
64    InvalidInstructionIndex {
65        /// The invalid instruction index
66        index: usize,
67        /// Number of instructions in the program
68        num_instructions: usize,
69        /// The relocation number
70        relocation_number: usize,
71    },
72
73    /// Multiple candidate target types found with different memory layouts
74    #[error(
75        "error relocating {type_name}, multiple candidate target types found with different memory layouts: {candidates:?}"
76    )]
77    ConflictingCandidates {
78        /// The type name
79        type_name: String,
80        /// The candidates
81        candidates: Vec<String>,
82    },
83
84    /// Maximum nesting level reached evaluating candidate type
85    #[error("maximum nesting level reached evaluating candidate type `{}`", err_type_name(.type_name))]
86    MaximumNestingLevelReached {
87        /// The type name
88        type_name: Option<String>,
89    },
90
91    /// Invalid access string
92    #[error("invalid access string `{spec}` for type `{}`: {error}", err_type_name(.type_name))]
93    InvalidAccessIndex {
94        /// The type name
95        type_name: Option<String>,
96        /// The access string
97        spec: String,
98        /// The index
99        index: usize,
100        /// The max index
101        max_index: usize,
102        /// The error message
103        error: &'static str,
104    },
105
106    /// Relocation not valid for type
107    #[error(
108        "relocation #{relocation_number} of kind `{relocation_kind}` not valid for type `{type_kind}`: {error}"
109    )]
110    InvalidRelocationKindForType {
111        /// The relocation number
112        relocation_number: usize,
113        /// The relocation kind
114        relocation_kind: String,
115        /// The type kind
116        type_kind: String,
117        /// The error message
118        error: &'static str,
119    },
120
121    /// Invalid instruction referenced by relocation
122    #[error(
123        "instruction #{index} referenced by relocation #{relocation_number} is invalid: {error}"
124    )]
125    InvalidInstruction {
126        /// The relocation number
127        relocation_number: usize,
128        /// The instruction index
129        index: usize,
130        /// The error message
131        error: Cow<'static, str>,
132    },
133
134    #[error(
135        "applying relocation `{kind:?}` missing target BTF info for type `{type_id}` at instruction #{ins_index}"
136    )]
137    MissingTargetDefinition {
138        kind: RelocationKind,
139        type_id: u32,
140        ins_index: usize,
141    },
142
143    /// BTF error
144    #[error("invalid BTF")]
145    BtfError(#[from] BtfError),
146}
147
148fn err_type_name(name: &Option<String>) -> &str {
149    name.as_deref().unwrap_or("[unknown name]")
150}
151
152#[derive(Copy, Clone, Debug)]
153#[repr(u32)]
154enum RelocationKind {
155    FieldByteOffset = BPF_CORE_FIELD_BYTE_OFFSET,
156    FieldByteSize = BPF_CORE_FIELD_BYTE_SIZE,
157    FieldExists = BPF_CORE_FIELD_EXISTS,
158    FieldSigned = BPF_CORE_FIELD_SIGNED,
159    FieldLShift64 = BPF_CORE_FIELD_LSHIFT_U64,
160    FieldRShift64 = BPF_CORE_FIELD_RSHIFT_U64,
161    TypeIdLocal = BPF_CORE_TYPE_ID_LOCAL,
162    TypeIdTarget = BPF_CORE_TYPE_ID_TARGET,
163    TypeExists = BPF_CORE_TYPE_EXISTS,
164    TypeSize = BPF_CORE_TYPE_SIZE,
165    EnumVariantExists = BPF_CORE_ENUMVAL_EXISTS,
166    EnumVariantValue = BPF_CORE_ENUMVAL_VALUE,
167}
168
169impl TryFrom<u32> for RelocationKind {
170    type Error = BtfError;
171
172    fn try_from(v: u32) -> Result<Self, Self::Error> {
173        use RelocationKind::*;
174
175        Ok(match v {
176            BPF_CORE_FIELD_BYTE_OFFSET => FieldByteOffset,
177            BPF_CORE_FIELD_BYTE_SIZE => FieldByteSize,
178            BPF_CORE_FIELD_EXISTS => FieldExists,
179            BPF_CORE_FIELD_SIGNED => FieldSigned,
180            BPF_CORE_FIELD_LSHIFT_U64 => FieldLShift64,
181            BPF_CORE_FIELD_RSHIFT_U64 => FieldRShift64,
182            BPF_CORE_TYPE_ID_LOCAL => TypeIdLocal,
183            BPF_CORE_TYPE_ID_TARGET => TypeIdTarget,
184            BPF_CORE_TYPE_EXISTS => TypeExists,
185            BPF_CORE_TYPE_SIZE => TypeSize,
186            BPF_CORE_ENUMVAL_EXISTS => EnumVariantExists,
187            BPF_CORE_ENUMVAL_VALUE => EnumVariantValue,
188            kind => return Err(BtfError::InvalidRelocationKind { kind }),
189        })
190    }
191}
192
193#[derive(Debug, Copy, Clone)]
194pub(crate) struct Relocation {
195    kind: RelocationKind,
196    ins_offset: usize,
197    type_id: u32,
198    access_str_offset: u32,
199    number: usize,
200}
201
202impl Relocation {
203    pub(crate) unsafe fn parse(data: &[u8], number: usize) -> Result<Self, BtfError> {
204        if mem::size_of::<bpf_core_relo>() > data.len() {
205            return Err(BtfError::InvalidRelocationInfo);
206        }
207
208        let rel = unsafe { ptr::read_unaligned::<bpf_core_relo>(data.as_ptr().cast()) };
209
210        Ok(Self {
211            kind: rel.kind.try_into()?,
212            ins_offset: rel.insn_off as usize,
213            type_id: rel.type_id,
214            access_str_offset: rel.access_str_off,
215            number,
216        })
217    }
218}
219
220impl Object {
221    /// Relocates programs inside this object file with loaded BTF info.
222    pub fn relocate_btf(&mut self, target_btf: &Btf) -> Result<(), BtfRelocationError> {
223        let (local_btf, btf_ext) = match (&self.btf, &self.btf_ext) {
224            (Some(btf), Some(btf_ext)) => (btf, btf_ext),
225            _ => return Ok(()),
226        };
227
228        let mut candidates_cache = HashMap::<u32, Vec<Candidate<'_>>>::new();
229        for (sec_name_off, relos) in btf_ext.relocations() {
230            let section_name =
231                local_btf
232                    .string_at(*sec_name_off)
233                    .map_err(|e| BtfRelocationError {
234                        section: format!("section@{sec_name_off}"),
235                        error: RelocationError::BtfError(e),
236                    })?;
237
238            let (section_index, _) = self
239                .section_infos
240                .get(&section_name.to_string())
241                .ok_or_else(|| BtfRelocationError {
242                    section: section_name.to_string(),
243                    error: RelocationError::SectionNotFound,
244                })?;
245
246            match relocate_btf_functions(
247                section_index,
248                &mut self.functions,
249                relos,
250                local_btf,
251                target_btf,
252                &mut candidates_cache,
253            ) {
254                Ok(()) => {}
255                Err(error) => {
256                    return Err(BtfRelocationError {
257                        section: section_name.to_string(),
258                        error,
259                    });
260                }
261            }
262        }
263
264        Ok(())
265    }
266}
267
268fn is_relocation_inside_function(
269    section_index: &SectionIndex,
270    func: &Function,
271    rel: &Relocation,
272) -> bool {
273    if section_index.0 != func.section_index.0 {
274        return false;
275    }
276
277    let ins_offset = rel.ins_offset / mem::size_of::<bpf_insn>();
278    let func_offset = func.section_offset / mem::size_of::<bpf_insn>();
279    let func_size = func.instructions.len();
280
281    (func_offset..func_offset + func_size).contains(&ins_offset)
282}
283
284fn function_by_relocation<'a>(
285    section_index: &SectionIndex,
286    functions: &'a mut BTreeMap<(usize, u64), Function>,
287    rel: &Relocation,
288) -> Option<&'a mut Function> {
289    functions
290        .range_mut((
291            Included(&(section_index.0, 0)),
292            Included(&(section_index.0, u64::MAX)),
293        ))
294        .map(|(_, func)| func)
295        .find(|func| is_relocation_inside_function(section_index, func, rel))
296}
297
298fn relocate_btf_functions<'target>(
299    section_index: &SectionIndex,
300    functions: &mut BTreeMap<(usize, u64), Function>,
301    relos: &[Relocation],
302    local_btf: &Btf,
303    target_btf: &'target Btf,
304    candidates_cache: &mut HashMap<u32, Vec<Candidate<'target>>>,
305) -> Result<(), RelocationError> {
306    let mut last_function_opt: Option<&mut Function> = None;
307
308    for rel in relos {
309        let function = match last_function_opt.take() {
310            Some(func) if is_relocation_inside_function(section_index, func, rel) => func,
311            _ => function_by_relocation(section_index, functions, rel)
312                .ok_or(RelocationError::FunctionNotFound)?,
313        };
314
315        let instructions = &mut function.instructions;
316        let ins_index = (rel.ins_offset - function.section_offset) / mem::size_of::<bpf_insn>();
317        if ins_index >= instructions.len() {
318            return Err(RelocationError::InvalidInstructionIndex {
319                index: ins_index,
320                num_instructions: instructions.len(),
321                relocation_number: rel.number,
322            });
323        }
324
325        let local_ty = local_btf.type_by_id(rel.type_id)?;
326        let local_name = &*local_btf.type_name(local_ty)?;
327        let access_str = &*local_btf.string_at(rel.access_str_offset)?;
328        let local_spec = AccessSpec::new(local_btf, rel.type_id, access_str, *rel)?;
329
330        let matches = match rel.kind {
331            RelocationKind::TypeIdLocal => Vec::new(), // we don't need to look at target types to relocate this value
332            _ => {
333                let candidates = match candidates_cache.get(&rel.type_id) {
334                    Some(cands) => cands,
335                    None => {
336                        candidates_cache.insert(
337                            rel.type_id,
338                            find_candidates(local_ty, local_name, target_btf)?,
339                        );
340                        candidates_cache.get(&rel.type_id).unwrap()
341                    }
342                };
343
344                let mut matches = Vec::new();
345                for candidate in candidates {
346                    if let Some(candidate_spec) = match_candidate(&local_spec, candidate)? {
347                        let comp_rel =
348                            ComputedRelocation::new(rel, &local_spec, Some(&candidate_spec))?;
349                        matches.push((candidate.name.clone(), candidate_spec, comp_rel));
350                    }
351                }
352
353                matches
354            }
355        };
356
357        let comp_rel = if !matches.is_empty() {
358            let mut matches = matches.into_iter();
359            let (_, target_spec, target_comp_rel) = matches.next().unwrap();
360
361            // if there's more than one candidate, make sure that they all resolve to the
362            // same value, else the relocation is ambiguous and can't be applied
363            let conflicts = matches
364                .filter_map(|(cand_name, cand_spec, cand_comp_rel)| {
365                    if cand_spec.bit_offset != target_spec.bit_offset {
366                        return Some(cand_name);
367                    } else if let (Some(cand_comp_rel_target), Some(target_comp_rel_target)) = (
368                        cand_comp_rel.target.as_ref(),
369                        target_comp_rel.target.as_ref(),
370                    ) {
371                        if cand_comp_rel_target.value != target_comp_rel_target.value {
372                            return Some(cand_name);
373                        }
374                    }
375
376                    None
377                })
378                .collect::<Vec<_>>();
379            if !conflicts.is_empty() {
380                return Err(RelocationError::ConflictingCandidates {
381                    type_name: local_name.to_string(),
382                    candidates: conflicts,
383                });
384            }
385            target_comp_rel
386        } else {
387            // there are no candidate matches and therefore no target_spec. This might mean
388            // that matching failed, or that the relocation can be applied looking at local
389            // types only (eg with EnumVariantExists, FieldExists etc)
390            ComputedRelocation::new(rel, &local_spec, None)?
391        };
392
393        comp_rel.apply(function, rel, local_btf, target_btf)?;
394
395        last_function_opt = Some(function);
396    }
397
398    Ok(())
399}
400
401fn flavorless_name(name: &str) -> &str {
402    name.split_once("___").map_or(name, |x| x.0)
403}
404
405fn find_candidates<'target>(
406    local_ty: &BtfType,
407    local_name: &str,
408    target_btf: &'target Btf,
409) -> Result<Vec<Candidate<'target>>, BtfError> {
410    let mut candidates = Vec::new();
411    let local_name = flavorless_name(local_name);
412    let local_kind = local_ty.kind();
413
414    // When we downgrade an ENUM64 to a UNION we still want to match the enum
415    // definition recorded in the target BTF. If the sanitized type has a
416    // fallback, allow ENUM64 candidates through the kind check.
417    //
418    // Note that we do not sanitize the target BTF so the kinds will not
419    // naturally match!
420    let allow_enum_match = matches!(
421        local_ty,
422        BtfType::Union(Union {
423            enum64_fallback: Some(_),
424            ..
425        })
426    );
427
428    for (type_id, ty) in target_btf.types().enumerate().filter(|(_, ty)| {
429        let candidate_kind = ty.kind();
430        candidate_kind == local_kind || (allow_enum_match && candidate_kind == BtfKind::Enum64)
431    }) {
432        let name = &*target_btf.type_name(ty)?;
433        if local_name != flavorless_name(name) {
434            continue;
435        }
436
437        candidates.push(Candidate {
438            name: name.to_owned(),
439            btf: target_btf,
440            _ty: ty,
441            type_id: type_id as u32,
442        });
443    }
444
445    Ok(candidates)
446}
447
448fn match_candidate<'target>(
449    local_spec: &AccessSpec<'_>,
450    candidate: &'target Candidate<'_>,
451) -> Result<Option<AccessSpec<'target>>, RelocationError> {
452    let mut target_spec = AccessSpec {
453        btf: candidate.btf,
454        root_type_id: candidate.type_id,
455        relocation: local_spec.relocation,
456        parts: Vec::new(),
457        accessors: Vec::new(),
458        bit_offset: 0,
459    };
460
461    match local_spec.relocation.kind {
462        RelocationKind::TypeIdLocal
463        | RelocationKind::TypeIdTarget
464        | RelocationKind::TypeExists
465        | RelocationKind::TypeSize => {
466            if types_are_compatible(
467                local_spec.btf,
468                local_spec.root_type_id,
469                candidate.btf,
470                candidate.type_id,
471            )? {
472                Ok(Some(target_spec))
473            } else {
474                Ok(None)
475            }
476        }
477        RelocationKind::EnumVariantExists | RelocationKind::EnumVariantValue => {
478            let target_id = candidate.btf.resolve_type(candidate.type_id)?;
479
480            let match_enum = |iterator: &mut dyn Iterator<Item = u32>| {
481                // the first accessor is guaranteed to have a name by construction
482                let local_variant_name = local_spec.accessors[0].name.as_ref().unwrap();
483
484                for (index, name_offset) in iterator.enumerate() {
485                    let target_variant_name = candidate.btf.string_at(name_offset)?;
486                    if flavorless_name(local_variant_name) == flavorless_name(&target_variant_name)
487                    {
488                        target_spec.parts.push(index);
489                        target_spec.accessors.push(Accessor {
490                            index,
491                            type_id: target_id,
492                            name: None,
493                        });
494                        return Ok(Some(target_spec));
495                    }
496                }
497                Ok(None)
498            };
499
500            match candidate.btf.type_by_id(target_id)? {
501                BtfType::Enum(en) => {
502                    match_enum(&mut en.variants.iter().map(|member| member.name_offset))
503                }
504                BtfType::Enum64(en) => {
505                    match_enum(&mut en.variants.iter().map(|member| member.name_offset))
506                }
507                BtfType::Union(Union {
508                    enum64_fallback: Some(fallback),
509                    ..
510                }) => {
511                    // Local ENUM64 types become UNIONs during sanitisation; the fallback retains
512                    // their original variant names so we can line them up with target enums.
513                    match_enum(&mut fallback.variants.iter().map(|variant| variant.name_offset))
514                }
515                _ => Ok(None),
516            }
517        }
518        RelocationKind::FieldByteOffset
519        | RelocationKind::FieldByteSize
520        | RelocationKind::FieldExists
521        | RelocationKind::FieldSigned
522        | RelocationKind::FieldLShift64
523        | RelocationKind::FieldRShift64 => {
524            let mut target_id = candidate.type_id;
525            for (i, accessor) in local_spec.accessors.iter().enumerate() {
526                target_id = candidate.btf.resolve_type(target_id)?;
527
528                if accessor.name.is_some() {
529                    if let Some(next_id) = match_member(
530                        local_spec.btf,
531                        local_spec,
532                        accessor,
533                        candidate.btf,
534                        target_id,
535                        &mut target_spec,
536                    )? {
537                        target_id = next_id;
538                    } else {
539                        return Ok(None);
540                    }
541                } else {
542                    // i = 0 is the base struct. for i > 0, we need to potentially do bounds checking
543                    if i > 0 {
544                        let target_ty = candidate.btf.type_by_id(target_id)?;
545                        let array = match target_ty {
546                            BtfType::Array(Array { array, .. }) => array,
547                            _ => return Ok(None),
548                        };
549
550                        let var_len = array.len == 0 && {
551                            // an array is potentially variable length if it's the last field
552                            // of the parent struct and has 0 elements
553                            let parent = target_spec.accessors.last().unwrap();
554                            let parent_ty = candidate.btf.type_by_id(parent.type_id)?;
555                            match parent_ty {
556                                BtfType::Struct(s) => parent.index == s.members.len() - 1,
557                                _ => false,
558                            }
559                        };
560                        if !var_len && accessor.index >= array.len as usize {
561                            return Ok(None);
562                        }
563                        target_id = candidate.btf.resolve_type(array.element_type)?;
564                    }
565
566                    if target_spec.parts.len() == MAX_SPEC_LEN {
567                        return Err(RelocationError::MaximumNestingLevelReached {
568                            type_name: Some(candidate.name.clone()),
569                        });
570                    }
571
572                    target_spec.parts.push(accessor.index);
573                    target_spec.accessors.push(Accessor {
574                        index: accessor.index,
575                        type_id: target_id,
576                        name: None,
577                    });
578                    target_spec.bit_offset +=
579                        accessor.index * candidate.btf.type_size(target_id)? * 8;
580                }
581            }
582            Ok(Some(target_spec))
583        }
584    }
585}
586
587fn match_member<'target>(
588    local_btf: &Btf,
589    local_spec: &AccessSpec<'_>,
590    local_accessor: &Accessor,
591    target_btf: &'target Btf,
592    target_id: u32,
593    target_spec: &mut AccessSpec<'target>,
594) -> Result<Option<u32>, RelocationError> {
595    let local_ty = local_btf.type_by_id(local_accessor.type_id)?;
596    let local_member = match local_ty {
597        // this won't panic, bounds are checked when local_spec is built in AccessSpec::new
598        BtfType::Struct(s) => s.members.get(local_accessor.index).unwrap(),
599        BtfType::Union(u) => u.members.get(local_accessor.index).unwrap(),
600        local_ty => panic!("unexpected type {:?}", local_ty),
601    };
602
603    let local_name = &*local_btf.string_at(local_member.name_offset)?;
604    let target_id = target_btf.resolve_type(target_id)?;
605    let target_ty = target_btf.type_by_id(target_id)?;
606
607    let target_members: Vec<&BtfMember> = match target_ty.members() {
608        Some(members) => members.collect(),
609        // not a fields type, no match
610        None => return Ok(None),
611    };
612
613    for (index, target_member) in target_members.iter().enumerate() {
614        if target_spec.parts.len() == MAX_SPEC_LEN {
615            let root_ty = target_spec.btf.type_by_id(target_spec.root_type_id)?;
616            return Err(RelocationError::MaximumNestingLevelReached {
617                type_name: target_spec.btf.err_type_name(root_ty),
618            });
619        }
620
621        // this will not panic as we've already established these are fields types
622        let bit_offset = target_ty.member_bit_offset(target_member).unwrap();
623        let target_name = &*target_btf.string_at(target_member.name_offset)?;
624
625        if target_name.is_empty() {
626            let ret = match_member(
627                local_btf,
628                local_spec,
629                local_accessor,
630                target_btf,
631                target_member.btf_type,
632                target_spec,
633            )?;
634            if ret.is_some() {
635                target_spec.bit_offset += bit_offset;
636                target_spec.parts.push(index);
637                return Ok(ret);
638            }
639        } else if local_name == target_name {
640            if fields_are_compatible(
641                local_spec.btf,
642                local_member.btf_type,
643                target_btf,
644                target_member.btf_type,
645            )? {
646                target_spec.bit_offset += bit_offset;
647                target_spec.parts.push(index);
648                target_spec.accessors.push(Accessor {
649                    type_id: target_id,
650                    index,
651                    name: Some(target_name.to_owned()),
652                });
653                return Ok(Some(target_member.btf_type));
654            } else {
655                return Ok(None);
656            }
657        }
658    }
659
660    Ok(None)
661}
662
663#[derive(Debug)]
664struct AccessSpec<'a> {
665    btf: &'a Btf,
666    root_type_id: u32,
667    parts: Vec<usize>,
668    accessors: Vec<Accessor>,
669    relocation: Relocation,
670    bit_offset: usize,
671}
672
673impl<'a> AccessSpec<'a> {
674    fn new(
675        btf: &'a Btf,
676        root_type_id: u32,
677        spec: &str,
678        relocation: Relocation,
679    ) -> Result<Self, RelocationError> {
680        let parts = spec
681            .split(':')
682            .map(|s| s.parse::<usize>())
683            .collect::<Result<Vec<_>, _>>()
684            .map_err(|_| RelocationError::InvalidAccessString {
685                access_str: spec.to_string(),
686            })?;
687
688        let mut type_id = btf.resolve_type(root_type_id)?;
689        let ty = btf.type_by_id(type_id)?;
690
691        let spec = match relocation.kind {
692            RelocationKind::TypeIdLocal
693            | RelocationKind::TypeIdTarget
694            | RelocationKind::TypeExists
695            | RelocationKind::TypeSize => {
696                if parts != [0] {
697                    return Err(RelocationError::InvalidAccessString {
698                        access_str: spec.to_string(),
699                    });
700                }
701                AccessSpec {
702                    btf,
703                    root_type_id,
704                    relocation,
705                    parts,
706                    accessors: Vec::new(),
707                    bit_offset: 0,
708                }
709            }
710            RelocationKind::EnumVariantExists | RelocationKind::EnumVariantValue => {
711                let index = || match parts.as_slice() {
712                    [index] => Ok(*index),
713                    _ => Err(RelocationError::InvalidAccessString {
714                        access_str: spec.to_string(),
715                    }),
716                };
717
718                let (n_variants, name_offset, index) = match ty {
719                    BtfType::Enum(en) => {
720                        let index = index()?;
721                        (
722                            en.variants.len(),
723                            en.variants.get(index).map(|v| v.name_offset),
724                            index,
725                        )
726                    }
727                    BtfType::Enum64(en) => {
728                        let index = index()?;
729                        (
730                            en.variants.len(),
731                            en.variants.get(index).map(|v| v.name_offset),
732                            index,
733                        )
734                    }
735                    BtfType::Union(Union {
736                        enum64_fallback: Some(fallback),
737                        ..
738                    }) => {
739                        let index = index()?;
740                        (
741                            fallback.variants.len(),
742                            fallback.variants.get(index).map(|v| v.name_offset),
743                            index,
744                        )
745                    }
746                    _ => {
747                        return Err(RelocationError::InvalidRelocationKindForType {
748                            relocation_number: relocation.number,
749                            relocation_kind: format!("{:?}", relocation.kind),
750                            type_kind: format!("{:?}", ty.kind()),
751                            error: "enum relocation on non-enum type",
752                        });
753                    }
754                };
755                let name_offset =
756                    name_offset.ok_or_else(|| RelocationError::InvalidAccessIndex {
757                        type_name: btf.err_type_name(ty),
758                        spec: spec.to_string(),
759                        index,
760                        max_index: n_variants,
761                        error: "tried to access nonexistant enum variant",
762                    })?;
763                let name = btf.string_at(name_offset)?;
764                let accessors = vec![Accessor {
765                    type_id,
766                    index,
767                    name: Some(name.to_string()),
768                }];
769
770                AccessSpec {
771                    btf,
772                    root_type_id,
773                    relocation,
774                    parts,
775                    accessors,
776                    bit_offset: 0,
777                }
778            }
779
780            RelocationKind::FieldByteOffset
781            | RelocationKind::FieldByteSize
782            | RelocationKind::FieldExists
783            | RelocationKind::FieldSigned
784            | RelocationKind::FieldLShift64
785            | RelocationKind::FieldRShift64 => {
786                let mut accessors = vec![Accessor {
787                    type_id,
788                    index: parts[0],
789                    name: None,
790                }];
791                let mut bit_offset = accessors[0].index * btf.type_size(type_id)?;
792                for index in parts.iter().skip(1).cloned() {
793                    type_id = btf.resolve_type(type_id)?;
794                    let ty = btf.type_by_id(type_id)?;
795
796                    match ty {
797                        BtfType::Struct(Struct { members, .. })
798                        | BtfType::Union(Union { members, .. }) => {
799                            if index >= members.len() {
800                                return Err(RelocationError::InvalidAccessIndex {
801                                    type_name: btf.err_type_name(ty),
802                                    spec: spec.to_string(),
803                                    index,
804                                    max_index: members.len(),
805                                    error: "out of bounds struct or union access",
806                                });
807                            }
808
809                            let member = &members[index];
810                            bit_offset += ty.member_bit_offset(member).unwrap();
811
812                            if member.name_offset != 0 {
813                                accessors.push(Accessor {
814                                    type_id,
815                                    index,
816                                    name: Some(btf.string_at(member.name_offset)?.to_string()),
817                                });
818                            }
819
820                            type_id = member.btf_type;
821                        }
822
823                        BtfType::Array(Array { array, .. }) => {
824                            type_id = btf.resolve_type(array.element_type)?;
825                            let var_len = array.len == 0 && {
826                                // an array is potentially variable length if it's the last field
827                                // of the parent struct and has 0 elements
828                                let parent = accessors.last().unwrap();
829                                let parent_ty = btf.type_by_id(parent.type_id)?;
830                                match parent_ty {
831                                    BtfType::Struct(s) => index == s.members.len() - 1,
832                                    _ => false,
833                                }
834                            };
835                            if !var_len && index >= array.len as usize {
836                                return Err(RelocationError::InvalidAccessIndex {
837                                    type_name: btf.err_type_name(ty),
838                                    spec: spec.to_string(),
839                                    index,
840                                    max_index: array.len as usize,
841                                    error: "array index out of bounds",
842                                });
843                            }
844                            accessors.push(Accessor {
845                                type_id,
846                                index,
847                                name: None,
848                            });
849                            let size = btf.type_size(type_id)?;
850                            bit_offset += index * size * 8;
851                        }
852                        rel_kind => {
853                            return Err(RelocationError::InvalidRelocationKindForType {
854                                relocation_number: relocation.number,
855                                relocation_kind: format!("{rel_kind:?}"),
856                                type_kind: format!("{:?}", ty.kind()),
857                                error: "field relocation on a type that doesn't have fields",
858                            });
859                        }
860                    };
861                }
862
863                AccessSpec {
864                    btf,
865                    root_type_id,
866                    parts,
867                    accessors,
868                    relocation,
869                    bit_offset,
870                }
871            }
872        };
873
874        Ok(spec)
875    }
876}
877
878#[derive(Debug)]
879struct Accessor {
880    type_id: u32,
881    index: usize,
882    name: Option<String>,
883}
884
885#[derive(Debug)]
886struct Candidate<'a> {
887    name: String,
888    btf: &'a Btf,
889    _ty: &'a BtfType,
890    type_id: u32,
891}
892
893#[derive(Debug)]
894struct ComputedRelocation {
895    local: ComputedRelocationValue,
896    target: Option<ComputedRelocationValue>,
897}
898
899#[derive(Clone, Copy, Debug)]
900struct ComputedRelocationValue {
901    value: u64,
902    size: u32,
903    type_id: Option<u32>,
904}
905
906fn poison_insn(ins: &mut bpf_insn) {
907    ins.code = (BPF_JMP | BPF_CALL) as u8;
908    ins.set_dst_reg(0);
909    ins.set_src_reg(0);
910    ins.off = 0;
911    ins.imm = 0xBAD2310;
912}
913
914impl ComputedRelocation {
915    fn new(
916        rel: &Relocation,
917        local_spec: &AccessSpec<'_>,
918        target_spec: Option<&AccessSpec<'_>>,
919    ) -> Result<Self, RelocationError> {
920        use RelocationKind::*;
921        let ret = match rel.kind {
922            FieldByteOffset | FieldByteSize | FieldExists | FieldSigned | FieldLShift64
923            | FieldRShift64 => Self {
924                local: Self::compute_field_relocation(rel, Some(local_spec))?,
925                target: Self::compute_field_relocation(rel, target_spec).ok(),
926            },
927            TypeIdLocal | TypeIdTarget | TypeExists | TypeSize => Self {
928                local: Self::compute_type_relocation(rel, local_spec, target_spec)?,
929                target: Self::compute_type_relocation(rel, local_spec, target_spec).ok(),
930            },
931            EnumVariantExists | EnumVariantValue => Self {
932                local: Self::compute_enum_relocation(rel, Some(local_spec))?,
933                target: Self::compute_enum_relocation(rel, target_spec).ok(),
934            },
935        };
936
937        Ok(ret)
938    }
939
940    fn apply(
941        &self,
942        function: &mut Function,
943        rel: &Relocation,
944        local_btf: &Btf,
945        target_btf: &Btf,
946    ) -> Result<(), RelocationError> {
947        let instructions = &mut function.instructions;
948        let num_instructions = instructions.len();
949        let ins_index = (rel.ins_offset - function.section_offset) / mem::size_of::<bpf_insn>();
950        let ins =
951            instructions
952                .get_mut(ins_index)
953                .ok_or(RelocationError::InvalidInstructionIndex {
954                    index: rel.ins_offset,
955                    num_instructions,
956                    relocation_number: rel.number,
957                })?;
958
959        let target = if let Some(target) = self.target.as_ref() {
960            target
961        } else {
962            let is_ld_imm64 = ins.code == (BPF_LD | BPF_DW) as u8;
963
964            poison_insn(ins);
965
966            if is_ld_imm64 {
967                let next_ins = instructions.get_mut(ins_index + 1).ok_or(
968                    RelocationError::InvalidInstructionIndex {
969                        index: (ins_index + 1) * mem::size_of::<bpf_insn>(),
970                        num_instructions,
971                        relocation_number: rel.number,
972                    },
973                )?;
974
975                poison_insn(next_ins);
976            }
977
978            return Ok(());
979        };
980
981        let class = u32::from(ins.code & 0x07);
982
983        let target_value = target.value;
984
985        match class {
986            BPF_ALU | BPF_ALU64 => {
987                let src_reg = ins.src_reg();
988                if src_reg != BPF_K as u8 {
989                    return Err(RelocationError::InvalidInstruction {
990                        relocation_number: rel.number,
991                        index: ins_index,
992                        error: format!("invalid src_reg={src_reg:x} expected {BPF_K:x}").into(),
993                    });
994                }
995
996                ins.imm = target_value as i32;
997            }
998            BPF_LDX | BPF_ST | BPF_STX => {
999                if target_value > i16::MAX as u64 {
1000                    return Err(RelocationError::InvalidInstruction {
1001                        relocation_number: rel.number,
1002                        index: ins_index,
1003                        error: format!("value `{target_value}` overflows 16 bits offset field")
1004                            .into(),
1005                    });
1006                }
1007
1008                ins.off = target_value as i16;
1009
1010                if self.local.size != target.size {
1011                    let local_ty = local_btf.type_by_id(self.local.type_id.unwrap())?;
1012                    let target_ty = target_btf.type_by_id(target.type_id.unwrap())?;
1013                    let unsigned = |info: u32| ((info >> 24) & 0x0F) & BTF_INT_SIGNED == 0;
1014                    use BtfType::*;
1015                    match (local_ty, target_ty) {
1016                        (Ptr(_), Ptr(_)) => {}
1017                        (Int(local), Int(target))
1018                            if unsigned(local.data) && unsigned(target.data) => {}
1019                        _ => {
1020                            return Err(RelocationError::InvalidInstruction {
1021                                relocation_number: rel.number,
1022                                index: ins_index,
1023                                error: format!(
1024                                    "original type {} has size {} but target type {} has size {}",
1025                                    err_type_name(&local_btf.err_type_name(local_ty)),
1026                                    self.local.size,
1027                                    err_type_name(&target_btf.err_type_name(target_ty)),
1028                                    target.size,
1029                                )
1030                                .into(),
1031                            });
1032                        }
1033                    }
1034
1035                    let size = match target.size {
1036                        8 => BPF_DW,
1037                        4 => BPF_W,
1038                        2 => BPF_H,
1039                        1 => BPF_B,
1040                        size => {
1041                            return Err(RelocationError::InvalidInstruction {
1042                                relocation_number: rel.number,
1043                                index: ins_index,
1044                                error: format!("invalid target size {size}").into(),
1045                            });
1046                        }
1047                    } as u8;
1048                    ins.code = ins.code & 0xE0 | size | ins.code & 0x07;
1049                }
1050            }
1051            BPF_LD => {
1052                ins.imm = target_value as i32;
1053                let next_ins = instructions.get_mut(ins_index + 1).ok_or(
1054                    RelocationError::InvalidInstructionIndex {
1055                        index: ins_index + 1,
1056                        num_instructions,
1057                        relocation_number: rel.number,
1058                    },
1059                )?;
1060
1061                next_ins.imm = (target_value >> 32) as i32;
1062            }
1063            class => {
1064                return Err(RelocationError::InvalidInstruction {
1065                    relocation_number: rel.number,
1066                    index: ins_index,
1067                    error: format!("invalid instruction class {class:x}").into(),
1068                });
1069            }
1070        };
1071
1072        Ok(())
1073    }
1074
1075    fn compute_enum_relocation(
1076        rel: &Relocation,
1077        spec: Option<&AccessSpec<'_>>,
1078    ) -> Result<ComputedRelocationValue, RelocationError> {
1079        use RelocationKind::*;
1080        let value = match (rel.kind, spec) {
1081            (EnumVariantExists, spec) => u64::from(spec.is_some()),
1082            (EnumVariantValue, Some(spec)) => {
1083                let accessor = &spec.accessors[0];
1084                match spec.btf.type_by_id(accessor.type_id)? {
1085                    BtfType::Enum(en) => {
1086                        let value = en.variants[accessor.index].value;
1087                        if en.is_signed() {
1088                            value as i32 as u64
1089                        } else {
1090                            u64::from(value)
1091                        }
1092                    }
1093                    BtfType::Enum64(en) => {
1094                        let variant = &en.variants[accessor.index];
1095                        (u64::from(variant.value_high) << 32) | u64::from(variant.value_low)
1096                    }
1097                    BtfType::Union(Union {
1098                        enum64_fallback: Some(fallback),
1099                        ..
1100                    }) => {
1101                        let variant = &fallback.variants[accessor.index];
1102                        if fallback.signed {
1103                            (variant.value as i64) as u64
1104                        } else {
1105                            variant.value
1106                        }
1107                    }
1108                    // candidate selection ensures that rel_kind == local_kind == target_kind
1109                    _ => unreachable!(),
1110                }
1111            }
1112            _ => {
1113                return Err(RelocationError::MissingTargetDefinition {
1114                    kind: rel.kind,
1115                    type_id: rel.type_id,
1116                    ins_index: rel.ins_offset / mem::size_of::<bpf_insn>(),
1117                })?;
1118            }
1119        };
1120
1121        Ok(ComputedRelocationValue {
1122            value,
1123            size: 0,
1124            type_id: None,
1125        })
1126    }
1127
1128    fn compute_field_relocation(
1129        rel: &Relocation,
1130        spec: Option<&AccessSpec<'_>>,
1131    ) -> Result<ComputedRelocationValue, RelocationError> {
1132        use RelocationKind::*;
1133
1134        if let FieldExists = rel.kind {
1135            // this is the bpf_preserve_field_info(member_access, FIELD_EXISTENCE) case. If we
1136            // managed to build a spec, it means the field exists.
1137            return Ok(ComputedRelocationValue {
1138                value: u64::from(spec.is_some()),
1139                size: 0,
1140                type_id: None,
1141            });
1142        }
1143
1144        let spec = match spec {
1145            Some(spec) => spec,
1146            None => {
1147                return Err(RelocationError::MissingTargetDefinition {
1148                    kind: rel.kind,
1149                    type_id: rel.type_id,
1150                    ins_index: rel.ins_offset / mem::size_of::<bpf_insn>(),
1151                })?;
1152            }
1153        };
1154
1155        let accessor = spec.accessors.last().unwrap();
1156        if accessor.name.is_none() {
1157            // the last accessor is unnamed, meaning that this is an array access
1158            return match rel.kind {
1159                FieldByteOffset => Ok(ComputedRelocationValue {
1160                    value: (spec.bit_offset / 8) as u64,
1161                    size: spec.btf.type_size(accessor.type_id)? as u32,
1162                    type_id: Some(accessor.type_id),
1163                }),
1164                FieldByteSize => Ok(ComputedRelocationValue {
1165                    value: spec.btf.type_size(accessor.type_id)? as u64,
1166                    size: 0,
1167                    type_id: Some(accessor.type_id),
1168                }),
1169                rel_kind => {
1170                    let ty = spec.btf.type_by_id(accessor.type_id)?;
1171                    return Err(RelocationError::InvalidRelocationKindForType {
1172                        relocation_number: rel.number,
1173                        relocation_kind: format!("{rel_kind:?}"),
1174                        type_kind: format!("{:?}", ty.kind()),
1175                        error: "invalid relocation kind for array type",
1176                    });
1177                }
1178            };
1179        }
1180
1181        let ty = spec.btf.type_by_id(accessor.type_id)?;
1182        let (ll_ty, member) = match ty {
1183            BtfType::Struct(t) => (ty, t.members.get(accessor.index).unwrap()),
1184            BtfType::Union(t) => (ty, t.members.get(accessor.index).unwrap()),
1185            _ => {
1186                return Err(RelocationError::InvalidRelocationKindForType {
1187                    relocation_number: rel.number,
1188                    relocation_kind: format!("{:?}", rel.kind),
1189                    type_kind: format!("{:?}", ty.kind()),
1190                    error: "field relocation on a type that doesn't have fields",
1191                });
1192            }
1193        };
1194
1195        let bit_off = spec.bit_offset as u32;
1196        let member_type_id = spec.btf.resolve_type(member.btf_type)?;
1197        let member_ty = spec.btf.type_by_id(member_type_id)?;
1198
1199        let mut byte_size;
1200        let mut byte_off;
1201        let mut bit_size = ll_ty.member_bit_field_size(member).unwrap() as u32;
1202        let is_bitfield = bit_size > 0;
1203        if is_bitfield {
1204            // find out the smallest int size to load the bitfield
1205            byte_size = member_ty.size().unwrap();
1206            byte_off = bit_off / 8 / byte_size * byte_size;
1207            while bit_off + bit_size - byte_off * 8 > byte_size * 8 {
1208                if byte_size >= 8 {
1209                    // the bitfield is larger than 8 bytes!?
1210                    return Err(BtfError::InvalidTypeInfo.into());
1211                }
1212                byte_size *= 2;
1213                byte_off = bit_off / 8 / byte_size * byte_size;
1214            }
1215        } else {
1216            byte_size = spec.btf.type_size(member_type_id)? as u32;
1217            bit_size = byte_size * 8;
1218            byte_off = spec.bit_offset as u32 / 8;
1219        }
1220
1221        let mut value = ComputedRelocationValue {
1222            value: 0,
1223            size: 0,
1224            type_id: None,
1225        };
1226
1227        match rel.kind {
1228            FieldByteOffset => {
1229                value.value = u64::from(byte_off);
1230                if !is_bitfield {
1231                    value.size = byte_size;
1232                    value.type_id = Some(member_type_id);
1233                }
1234            }
1235            FieldByteSize => {
1236                value.value = u64::from(byte_size);
1237            }
1238            FieldSigned => match member_ty {
1239                BtfType::Enum(en) => value.value = u64::from(en.is_signed()),
1240                BtfType::Enum64(en) => value.value = u64::from(en.is_signed()),
1241                BtfType::Int(i) => value.value = i.encoding() as u64 & IntEncoding::Signed as u64,
1242                _ => (),
1243            },
1244            FieldLShift64 => {
1245                value.value = if cfg!(target_endian = "little") {
1246                    64 - u64::from(bit_off + bit_size - byte_off * 8)
1247                } else {
1248                    u64::from((8 - byte_size) * 8 + (bit_off - byte_off * 8))
1249                }
1250            }
1251            FieldRShift64 => {
1252                value.value = 64 - u64::from(bit_size);
1253            }
1254            kind @ (FieldExists | TypeIdLocal | TypeIdTarget | TypeExists | TypeSize
1255            | EnumVariantExists | EnumVariantValue) => {
1256                panic!("unexpected relocation kind {:?}", kind)
1257            }
1258        }
1259
1260        Ok(value)
1261    }
1262
1263    fn compute_type_relocation(
1264        rel: &Relocation,
1265        local_spec: &AccessSpec<'_>,
1266        target_spec: Option<&AccessSpec<'_>>,
1267    ) -> Result<ComputedRelocationValue, RelocationError> {
1268        use RelocationKind::*;
1269
1270        let value = match (rel.kind, target_spec) {
1271            (TypeIdLocal, _) => u64::from(local_spec.root_type_id),
1272            (TypeIdTarget, Some(target_spec)) => u64::from(target_spec.root_type_id),
1273            (TypeExists, target_spec) => u64::from(target_spec.is_some()),
1274            (TypeSize, Some(target_spec)) => {
1275                target_spec.btf.type_size(target_spec.root_type_id)? as u64
1276            }
1277            _ => {
1278                return Err(RelocationError::MissingTargetDefinition {
1279                    kind: rel.kind,
1280                    type_id: rel.type_id,
1281                    ins_index: rel.ins_offset / mem::size_of::<bpf_insn>(),
1282                })?;
1283            }
1284        };
1285
1286        Ok(ComputedRelocationValue {
1287            value,
1288            size: 0,
1289            type_id: None,
1290        })
1291    }
1292}