1use std::{borrow::ToOwned as _, collections::BTreeMap};
4
5use log::debug;
6use object::{SectionIndex, SymbolKind};
7
8use crate::{
9 EbpfSectionKind,
10 generated::{
11 BPF_CALL, BPF_JMP, BPF_K, BPF_PSEUDO_CALL, BPF_PSEUDO_FUNC, BPF_PSEUDO_MAP_FD,
12 BPF_PSEUDO_MAP_VALUE, bpf_insn,
13 },
14 maps::Map,
15 obj::{Function, Object},
16 util::{HashMap, HashSet},
17};
18
19type RawFd = std::os::fd::RawFd;
20
21pub(crate) const INS_SIZE: usize = size_of::<bpf_insn>();
22
23#[derive(thiserror::Error, Debug)]
25#[error("error relocating `{function}`")]
26pub struct EbpfRelocationError {
27 function: String,
29 #[source]
30 error: RelocationError,
32}
33
34#[derive(Debug, thiserror::Error)]
36pub enum RelocationError {
37 #[error("unknown symbol, index `{index}`")]
39 UnknownSymbol {
40 index: usize,
42 },
43
44 #[error("section `{section_index}` not found, referenced by symbol `{}` #{symbol_index}",
46 .symbol_name.clone().unwrap_or_default())]
47 SectionNotFound {
48 section_index: usize,
50 symbol_index: usize,
52 symbol_name: Option<String>,
54 },
55
56 #[error("function {address:#x} not found while relocating `{caller_name}`")]
58 UnknownFunction {
59 address: u64,
61 caller_name: String,
63 },
64
65 #[error(
67 "program at section {section_index} and address {address:#x} was not found while relocating"
68 )]
69 UnknownProgram {
70 section_index: usize,
72 address: u64,
74 },
75
76 #[error("invalid offset `{offset}` applying relocation #{relocation_number}")]
78 InvalidRelocationOffset {
79 offset: u64,
81 relocation_number: usize,
83 },
84}
85
86#[derive(Debug, Copy, Clone)]
87pub(crate) struct Relocation {
88 pub(crate) offset: u64,
90 pub(crate) size: u8,
91 pub(crate) symbol_index: usize,
93}
94
95#[derive(Debug, Clone)]
96pub(crate) struct Symbol {
97 pub(crate) index: usize,
98 pub(crate) section_index: Option<usize>,
99 pub(crate) name: Option<String>,
100 pub(crate) address: u64,
101 pub(crate) size: u64,
102 pub(crate) is_definition: bool,
103 pub(crate) kind: SymbolKind,
104}
105
106impl Object {
107 pub fn relocate_maps<'a, I: Iterator<Item = (&'a str, RawFd, &'a Map)>>(
109 &mut self,
110 maps: I,
111 text_sections: &HashSet<usize>,
112 ) -> Result<(), EbpfRelocationError> {
113 let mut maps_by_section = HashMap::new();
114 let mut maps_by_symbol = HashMap::new();
115 for (name, fd, map) in maps {
116 maps_by_section.insert(map.section_index(), (name, fd, map));
117 if let Some(index) = map.symbol_index() {
118 maps_by_symbol.insert(index, (name, fd, map));
119 }
120 }
121
122 for function in self.functions.values_mut() {
123 if let Some(relocations) = self.relocations.get(&function.section_index) {
124 relocate_maps(
125 function,
126 relocations.values(),
127 &maps_by_section,
128 &maps_by_symbol,
129 &self.symbol_table,
130 text_sections,
131 )
132 .map_err(|error| EbpfRelocationError {
133 function: function.name.clone(),
134 error,
135 })?;
136 }
137 }
138
139 Ok(())
140 }
141
142 pub fn relocate_calls(
144 &mut self,
145 text_sections: &HashSet<usize>,
146 ) -> Result<(), EbpfRelocationError> {
147 for (name, program) in &self.programs {
148 let linker = FunctionLinker::new(
149 &self.functions,
150 &self.relocations,
151 &self.symbol_table,
152 text_sections,
153 );
154
155 let func_orig =
156 self.functions
157 .get(&program.function_key())
158 .ok_or_else(|| EbpfRelocationError {
159 function: name.clone(),
160 error: RelocationError::UnknownProgram {
161 section_index: program.section_index,
162 address: program.address,
163 },
164 })?;
165
166 let func = linker
167 .link(func_orig)
168 .map_err(|error| EbpfRelocationError {
169 function: name.to_owned(),
170 error,
171 })?;
172
173 self.functions.insert(program.function_key(), func);
174 }
175
176 Ok(())
177 }
178}
179
180fn relocate_maps<'a, I: Iterator<Item = &'a Relocation>>(
181 fun: &mut Function,
182 relocations: I,
183 maps_by_section: &HashMap<usize, (&str, RawFd, &Map)>,
184 maps_by_symbol: &HashMap<usize, (&str, RawFd, &Map)>,
185 symbol_table: &HashMap<usize, Symbol>,
186 text_sections: &HashSet<usize>,
187) -> Result<(), RelocationError> {
188 let section_offset = fun.section_offset;
189 let instructions = &mut fun.instructions;
190 let function_size = instructions.len() * INS_SIZE;
191
192 for (rel_n, rel) in relocations.enumerate() {
193 let rel_offset = rel.offset as usize;
194 if rel_offset < section_offset || rel_offset >= section_offset + function_size {
195 continue;
197 }
198
199 let ins_offset = rel_offset - section_offset;
201 if !ins_offset.is_multiple_of(INS_SIZE) {
202 return Err(RelocationError::InvalidRelocationOffset {
203 offset: rel.offset,
204 relocation_number: rel_n,
205 });
206 }
207 let ins_index = ins_offset / INS_SIZE;
208
209 let sym = symbol_table
211 .get(&rel.symbol_index)
212 .ok_or(RelocationError::UnknownSymbol {
213 index: rel.symbol_index,
214 })?;
215
216 let Some(section_index) = sym.section_index else {
217 continue;
219 };
220
221 if insn_is_call(instructions[ins_index]) || text_sections.contains(§ion_index) {
223 continue;
224 }
225
226 let (_name, fd, map) = if let Some(m) = maps_by_symbol.get(&rel.symbol_index) {
227 let map = &m.2;
228 debug!(
229 "relocating map by symbol index {:?}, kind {:?} at insn {ins_index} in section {}",
230 map.symbol_index(),
231 map.section_kind(),
232 fun.section_index.0
233 );
234 debug_assert_eq!(map.symbol_index().unwrap(), rel.symbol_index);
235 m
236 } else {
237 let Some(m) = maps_by_section.get(§ion_index) else {
238 debug!("failed relocating map by section index {section_index}");
239 return Err(RelocationError::SectionNotFound {
240 symbol_index: rel.symbol_index,
241 symbol_name: sym.name.clone(),
242 section_index,
243 });
244 };
245 let map = &m.2;
246 debug!(
247 "relocating map by section index {}, kind {:?} at insn {ins_index} in section {}",
248 map.section_index(),
249 map.section_kind(),
250 fun.section_index.0,
251 );
252
253 debug_assert_eq!(map.symbol_index(), None);
254 debug_assert!(matches!(
255 map.section_kind(),
256 EbpfSectionKind::Bss | EbpfSectionKind::Data | EbpfSectionKind::Rodata
257 ));
258 m
259 };
260 debug_assert_eq!(map.section_index(), section_index);
261
262 if map.data().is_empty() {
263 instructions[ins_index].set_src_reg(BPF_PSEUDO_MAP_FD as u8);
264 } else {
265 instructions[ins_index].set_src_reg(BPF_PSEUDO_MAP_VALUE as u8);
266 instructions[ins_index + 1].imm = instructions[ins_index].imm + sym.address as i32;
267 }
268 instructions[ins_index].imm = *fd;
269 }
270
271 Ok(())
272}
273
274struct FunctionLinker<'a> {
275 functions: &'a BTreeMap<(usize, u64), Function>,
276 linked_functions: HashMap<u64, usize>,
277 relocations: &'a HashMap<SectionIndex, HashMap<u64, Relocation>>,
278 symbol_table: &'a HashMap<usize, Symbol>,
279 text_sections: &'a HashSet<usize>,
280}
281
282impl<'a> FunctionLinker<'a> {
283 fn new(
284 functions: &'a BTreeMap<(usize, u64), Function>,
285 relocations: &'a HashMap<SectionIndex, HashMap<u64, Relocation>>,
286 symbol_table: &'a HashMap<usize, Symbol>,
287 text_sections: &'a HashSet<usize>,
288 ) -> Self {
289 Self {
290 functions,
291 linked_functions: HashMap::new(),
292 relocations,
293 symbol_table,
294 text_sections,
295 }
296 }
297
298 fn link(mut self, program_function: &Function) -> Result<Function, RelocationError> {
299 let mut fun = program_function.clone();
300 self.relocate(&mut fun, program_function)?;
303
304 Ok(fun)
307 }
308
309 fn link_function(
310 &mut self,
311 program: &mut Function,
312 fun: &Function,
313 ) -> Result<usize, RelocationError> {
314 if let Some(fun_ins_index) = self.linked_functions.get(&fun.address) {
315 return Ok(*fun_ins_index);
316 }
317
318 let start_ins = program.instructions.len();
321 program.instructions.extend(&fun.instructions);
322 debug!(
323 "linked function `{}` at instruction {}",
324 fun.name, start_ins
325 );
326
327 Self::link_func_and_line_info(program, fun, start_ins);
330
331 self.linked_functions.insert(fun.address, start_ins);
332
333 self.relocate(program, fun)?;
335
336 Ok(start_ins)
337 }
338
339 fn relocate(&mut self, program: &mut Function, fun: &Function) -> Result<(), RelocationError> {
340 let relocations = self.relocations.get(&fun.section_index);
341
342 let n_instructions = fun.instructions.len();
343 let start_ins = program.instructions.len() - n_instructions;
344
345 debug!(
346 "relocating program `{}` function `{}` size {}",
347 program.name, fun.name, n_instructions
348 );
349
350 for ins_index in start_ins..start_ins + n_instructions {
353 let ins = program.instructions[ins_index];
354 let is_call = insn_is_call(ins);
355
356 let rel = relocations
357 .and_then(|relocations| {
358 relocations
359 .get(&((fun.section_offset + (ins_index - start_ins) * INS_SIZE) as u64))
360 })
361 .and_then(|rel| {
362 self.symbol_table
364 .get(&rel.symbol_index)
365 .map(|sym| (rel, sym))
366 })
367 .filter(|(_rel, sym)| {
368 sym.kind == SymbolKind::Text
371 || sym.section_index.is_some_and(|section_index| {
372 self.text_sections.contains(§ion_index)
373 })
374 });
375
376 if !is_call && rel.is_none() {
378 continue;
379 }
380
381 let (callee_section_index, callee_address) = if let Some((rel, sym)) = rel {
382 let address = match sym.kind {
383 SymbolKind::Text => sym.address,
384 SymbolKind::Section if rel.size == 32 => {
386 sym.address + (ins.imm + 1) as u64 * INS_SIZE as u64
387 }
388 SymbolKind::Section if rel.size == 64 => sym.address + ins.imm as u64,
390 #[expect(clippy::todo, reason = "TODO")]
391 kind => todo!("FIXME: return an error here for kind={kind:?}"),
392 };
393 (sym.section_index.unwrap(), address)
394 } else {
395 let ins_size = INS_SIZE as i64;
398 (
399 fun.section_index.0,
400 (fun.section_offset as i64
401 + ((ins_index - start_ins) as i64) * ins_size
402 + i64::from(ins.imm + 1) * ins_size) as u64,
403 )
404 };
405
406 debug!(
407 "relocating {} to callee address {:#x} in section {} ({}) at instruction {ins_index}",
408 if is_call { "call" } else { "reference" },
409 callee_address,
410 callee_section_index,
411 if rel.is_some() {
412 "relocation"
413 } else {
414 "pc-relative"
415 },
416 );
417
418 let callee = self
421 .functions
422 .get(&(callee_section_index, callee_address))
423 .ok_or_else(|| RelocationError::UnknownFunction {
424 address: callee_address,
425 caller_name: fun.name.clone(),
426 })?;
427
428 debug!("callee is `{}`", callee.name);
429
430 let callee_ins_index = self.link_function(program, callee)? as i32;
431
432 let ins = &mut program.instructions[ins_index];
433 let ins_index = ins_index as i32;
434 ins.imm = callee_ins_index - ins_index - 1;
435 debug!(
436 "callee `{}` is at ins {callee_ins_index}, {} from current instruction {ins_index}",
437 callee.name, ins.imm
438 );
439 if !is_call {
440 ins.set_src_reg(BPF_PSEUDO_FUNC as u8);
441 }
442 }
443
444 debug!(
445 "finished relocating program `{}` function `{}`",
446 program.name, fun.name
447 );
448
449 Ok(())
450 }
451
452 fn link_func_and_line_info(program: &mut Function, fun: &Function, start: usize) {
453 let func_info = &fun.func_info.func_info;
454 let func_info = func_info.iter().copied().map(|mut info| {
455 info.insn_off = start as u32;
457 info
458 });
459 program.func_info.func_info.extend(func_info);
460 program.func_info.num_info = program.func_info.func_info.len() as u32;
461
462 let line_info = &fun.line_info.line_info;
463 if !line_info.is_empty() {
464 let original_start_off = line_info[0].insn_off;
466
467 let line_info = line_info.iter().copied().map(|mut info| {
468 info.insn_off = start as u32 + (info.insn_off - original_start_off);
471 info
472 });
473
474 program.line_info.line_info.extend(line_info);
475 program.line_info.num_info = program.line_info.line_info.len() as u32;
476 }
477 }
478}
479
480fn insn_is_call(ins: bpf_insn) -> bool {
481 let klass = u32::from(ins.code & 0x07);
482 let op = u32::from(ins.code & 0xF0);
483 let src = u32::from(ins.code & 0x08);
484
485 klass == BPF_JMP
486 && op == BPF_CALL
487 && src == BPF_K
488 && u32::from(ins.src_reg()) == BPF_PSEUDO_CALL
489 && ins.dst_reg() == 0
490 && ins.off == 0
491}
492
493#[cfg(test)]
494mod test {
495 use std::string::ToString as _;
496
497 use super::*;
498 use crate::maps::{BtfMap, LegacyMap};
499
500 fn fake_sym(index: usize, section_index: usize, address: u64, name: &str, size: u64) -> Symbol {
501 Symbol {
502 index,
503 section_index: Some(section_index),
504 name: Some(name.to_string()),
505 address,
506 size,
507 is_definition: false,
508 kind: SymbolKind::Data,
509 }
510 }
511
512 fn ins(bytes: &[u8]) -> bpf_insn {
513 unsafe { core::ptr::read_unaligned(bytes.as_ptr().cast()) }
514 }
515
516 fn fake_legacy_map(symbol_index: usize) -> Map {
517 Map::Legacy(LegacyMap {
518 def: Default::default(),
519 inner_def: None,
520 section_index: 0,
521 section_kind: EbpfSectionKind::Undefined,
522 symbol_index: Some(symbol_index),
523 data: Vec::new(),
524 })
525 }
526
527 fn fake_btf_map(symbol_index: usize) -> Map {
528 Map::Btf(BtfMap {
529 def: Default::default(),
530 inner_def: None,
531 section_index: 0,
532 symbol_index,
533 data: Vec::new(),
534 })
535 }
536
537 fn fake_func(name: &str, instructions: Vec<bpf_insn>) -> Function {
538 Function {
539 address: Default::default(),
540 name: name.to_string(),
541 section_index: SectionIndex(0),
542 section_offset: Default::default(),
543 instructions,
544 func_info: Default::default(),
545 line_info: Default::default(),
546 func_info_rec_size: Default::default(),
547 line_info_rec_size: Default::default(),
548 }
549 }
550
551 #[test]
552 fn test_single_legacy_map_relocation() {
553 let mut fun = fake_func(
554 "test",
555 vec![ins(&[
556 0x18, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
557 0x00, 0x00,
558 ])],
559 );
560
561 let symbol_table = HashMap::from([(1, fake_sym(1, 0, 0, "test_map", 0))]);
562
563 let relocations = [Relocation {
564 offset: 0x0,
565 symbol_index: 1,
566 size: 64,
567 }];
568 let maps_by_section = HashMap::new();
569
570 let map = fake_legacy_map(1);
571 let maps_by_symbol = HashMap::from([(1, ("test_map", 1, &map))]);
572
573 relocate_maps(
574 &mut fun,
575 relocations.iter(),
576 &maps_by_section,
577 &maps_by_symbol,
578 &symbol_table,
579 &HashSet::new(),
580 )
581 .unwrap();
582
583 assert_eq!(fun.instructions[0].src_reg(), BPF_PSEUDO_MAP_FD as u8);
584 assert_eq!(fun.instructions[0].imm, 1);
585 }
586
587 #[test]
588 fn test_multiple_legacy_map_relocation() {
589 let mut fun = fake_func(
590 "test",
591 vec![
592 ins(&[
593 0x18, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
594 0x00, 0x00, 0x00,
595 ]),
596 ins(&[
597 0x18, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
598 0x00, 0x00, 0x00,
599 ]),
600 ],
601 );
602
603 let symbol_table = HashMap::from([
604 (1, fake_sym(1, 0, 0, "test_map_1", 0)),
605 (2, fake_sym(2, 0, 0, "test_map_2", 0)),
606 ]);
607
608 let relocations = [
609 Relocation {
610 offset: 0x0,
611 symbol_index: 1,
612 size: 64,
613 },
614 Relocation {
615 offset: size_of::<bpf_insn>() as u64,
616 symbol_index: 2,
617 size: 64,
618 },
619 ];
620 let maps_by_section = HashMap::new();
621
622 let map_1 = fake_legacy_map(1);
623 let map_2 = fake_legacy_map(2);
624 let maps_by_symbol = HashMap::from([
625 (1, ("test_map_1", 1, &map_1)),
626 (2, ("test_map_2", 2, &map_2)),
627 ]);
628
629 relocate_maps(
630 &mut fun,
631 relocations.iter(),
632 &maps_by_section,
633 &maps_by_symbol,
634 &symbol_table,
635 &HashSet::new(),
636 )
637 .unwrap();
638
639 assert_eq!(fun.instructions[0].src_reg(), BPF_PSEUDO_MAP_FD as u8);
640 assert_eq!(fun.instructions[0].imm, 1);
641
642 assert_eq!(fun.instructions[1].src_reg(), BPF_PSEUDO_MAP_FD as u8);
643 assert_eq!(fun.instructions[1].imm, 2);
644 }
645
646 #[test]
647 fn test_single_btf_map_relocation() {
648 let mut fun = fake_func(
649 "test",
650 vec![ins(&[
651 0x18, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
652 0x00, 0x00,
653 ])],
654 );
655
656 let symbol_table = HashMap::from([(1, fake_sym(1, 0, 0, "test_map", 0))]);
657
658 let relocations = [Relocation {
659 offset: 0x0,
660 symbol_index: 1,
661 size: 64,
662 }];
663 let maps_by_section = HashMap::new();
664
665 let map = fake_btf_map(1);
666 let maps_by_symbol = HashMap::from([(1, ("test_map", 1, &map))]);
667
668 relocate_maps(
669 &mut fun,
670 relocations.iter(),
671 &maps_by_section,
672 &maps_by_symbol,
673 &symbol_table,
674 &HashSet::new(),
675 )
676 .unwrap();
677
678 assert_eq!(fun.instructions[0].src_reg(), BPF_PSEUDO_MAP_FD as u8);
679 assert_eq!(fun.instructions[0].imm, 1);
680 }
681
682 #[test]
683 fn test_multiple_btf_map_relocation() {
684 let mut fun = fake_func(
685 "test",
686 vec![
687 ins(&[
688 0x18, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
689 0x00, 0x00, 0x00,
690 ]),
691 ins(&[
692 0x18, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
693 0x00, 0x00, 0x00,
694 ]),
695 ],
696 );
697
698 let symbol_table = HashMap::from([
699 (1, fake_sym(1, 0, 0, "test_map_1", 0)),
700 (2, fake_sym(2, 0, 0, "test_map_2", 0)),
701 ]);
702
703 let relocations = [
704 Relocation {
705 offset: 0x0,
706 symbol_index: 1,
707 size: 64,
708 },
709 Relocation {
710 offset: size_of::<bpf_insn>() as u64,
711 symbol_index: 2,
712 size: 64,
713 },
714 ];
715 let maps_by_section = HashMap::new();
716
717 let map_1 = fake_btf_map(1);
718 let map_2 = fake_btf_map(2);
719 let maps_by_symbol = HashMap::from([
720 (1, ("test_map_1", 1, &map_1)),
721 (2, ("test_map_2", 2, &map_2)),
722 ]);
723
724 relocate_maps(
725 &mut fun,
726 relocations.iter(),
727 &maps_by_section,
728 &maps_by_symbol,
729 &symbol_table,
730 &HashSet::new(),
731 )
732 .unwrap();
733
734 assert_eq!(fun.instructions[0].src_reg(), BPF_PSEUDO_MAP_FD as u8);
735 assert_eq!(fun.instructions[0].imm, 1);
736
737 assert_eq!(fun.instructions[1].src_reg(), BPF_PSEUDO_MAP_FD as u8);
738 assert_eq!(fun.instructions[1].imm, 2);
739 }
740}