Skip to main content

aya_obj/btf/
relocation.rs

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