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#[derive(thiserror::Error, Debug)]
28#[error("error relocating `{section}`")]
29pub struct BtfRelocationError {
30 pub section: String,
32 #[source]
33 error: RelocationError,
35}
36
37#[derive(thiserror::Error, Debug)]
39enum RelocationError {
40 #[error(transparent)]
42 IOError(#[from] std::io::Error),
43
44 #[error("section not found")]
46 SectionNotFound,
47
48 #[error("function not found")]
50 FunctionNotFound,
51
52 #[error("invalid relocation access string {access_str}")]
54 InvalidAccessString {
55 access_str: String,
57 },
58
59 #[error(
61 "invalid instruction index #{index} referenced by relocation #{relocation_number}, the program contains {num_instructions} instructions"
62 )]
63 InvalidInstructionIndex {
64 index: usize,
66 num_instructions: usize,
68 relocation_number: usize,
70 },
71
72 #[error(
74 "error relocating {type_name}, multiple candidate target types found with different memory layouts: {candidates:?}"
75 )]
76 ConflictingCandidates {
77 type_name: String,
79 candidates: Vec<String>,
81 },
82
83 #[error("maximum nesting level reached evaluating candidate type `{}`", err_type_name(.type_name))]
85 MaximumNestingLevelReached {
86 type_name: Option<String>,
88 },
89
90 #[error("invalid access string `{spec}` for type `{}`: {error}", err_type_name(.type_name))]
92 InvalidAccessIndex {
93 type_name: Option<String>,
95 spec: String,
97 index: usize,
99 max_index: usize,
101 error: &'static str,
103 },
104
105 #[error(
107 "relocation #{relocation_number} of kind `{relocation_kind}` not valid for type `{type_kind}`: {error}"
108 )]
109 InvalidRelocationKindForType {
110 relocation_number: usize,
112 relocation_kind: String,
114 type_kind: String,
116 error: &'static str,
118 },
119
120 #[error(
122 "instruction #{index} referenced by relocation #{relocation_number} is invalid: {error}"
123 )]
124 InvalidInstruction {
125 relocation_number: usize,
127 index: usize,
129 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 #[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 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(§ion_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 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 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 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 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 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 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 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 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 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 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 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 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 #[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 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 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 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 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}