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