aya_obj/btf/
relocation.rs

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