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