aya_obj/
relocation.rs

1//! Program relocation handling.
2
3use 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/// The error type returned by [`Object::relocate_maps`] and [`Object::relocate_calls`]
28#[derive(thiserror::Error, Debug)]
29#[error("error relocating `{function}`")]
30pub struct EbpfRelocationError {
31    /// The function name
32    function: String,
33    #[source]
34    /// The original error
35    error: RelocationError,
36}
37
38/// Relocation failures
39#[derive(Debug, thiserror::Error)]
40pub enum RelocationError {
41    /// Unknown symbol
42    #[error("unknown symbol, index `{index}`")]
43    UnknownSymbol {
44        /// The symbol index
45        index: usize,
46    },
47
48    /// Section not found
49    #[error("section `{section_index}` not found, referenced by symbol `{}` #{symbol_index}",
50            .symbol_name.clone().unwrap_or_default())]
51    SectionNotFound {
52        /// The section index
53        section_index: usize,
54        /// The symbol index
55        symbol_index: usize,
56        /// The symbol name
57        symbol_name: Option<String>,
58    },
59
60    /// Unknown function
61    #[error("function {address:#x} not found while relocating `{caller_name}`")]
62    UnknownFunction {
63        /// The function address
64        address: u64,
65        /// The caller name
66        caller_name: String,
67    },
68
69    /// Unknown function
70    #[error(
71        "program at section {section_index} and address {address:#x} was not found while relocating"
72    )]
73    UnknownProgram {
74        /// The function section index
75        section_index: usize,
76        /// The function address
77        address: u64,
78    },
79
80    /// Invalid relocation offset
81    #[error("invalid offset `{offset}` applying relocation #{relocation_number}")]
82    InvalidRelocationOffset {
83        /// The relocation offset
84        offset: u64,
85        /// The relocation number
86        relocation_number: usize,
87    },
88}
89
90#[derive(Debug, Copy, Clone)]
91pub(crate) struct Relocation {
92    // byte offset of the instruction to be relocated
93    pub(crate) offset: u64,
94    pub(crate) size: u8,
95    // index of the symbol to relocate to
96    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    /// Relocates the map references
112    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    /// Relocates function calls
147    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            // the relocation doesn't apply to this function
200            continue;
201        }
202
203        // make sure that the relocation offset is properly aligned
204        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        // a map relocation points to the ELF section that contains the map
214        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            // this is not a map relocation
222            continue;
223        };
224
225        // calls and relocation to .text symbols are handled in a separate step
226        if insn_is_call(&instructions[ins_index]) || text_sections.contains(&section_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(&section_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        // relocate calls in the program's main function. As relocation happens,
305        // it will trigger linking in all the callees.
306        self.relocate(&mut fun, program_function)?;
307
308        // this now includes the program function plus all the other functions called during
309        // execution
310        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        // append fun.instructions to the program and record that `fun.address` has been inserted
323        // at `start_ins`. We'll use `start_ins` to do pc-relative calls.
324        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        // link func and line info into the main program
332        // the offset needs to be adjusted
333        self.link_func_and_line_info(program, fun, start_ins)?;
334
335        self.linked_functions.insert(fun.address, start_ins);
336
337        // relocate `fun`, recursively linking in all the callees
338        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        // process all the instructions. We can't only loop over relocations since we need to
355        // patch pc-relative calls too.
356        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                    // get the symbol for the relocation
367                    self.symbol_table
368                        .get(&rel.symbol_index)
369                        .map(|sym| (rel, sym))
370                })
371                .filter(|(_rel, sym)| {
372                    // only consider text relocations, data relocations are
373                    // relocated in relocate_maps()
374                    sym.kind == SymbolKind::Text
375                        || sym
376                            .section_index
377                            .map(|section_index| self.text_sections.contains(&section_index))
378                            .unwrap_or(false)
379                });
380
381            // not a call and not a text relocation, we don't need to do anything
382            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                    // R_BPF_64_32 this is a call
390                    SymbolKind::Section if rel.size == 32 => {
391                        sym.address + (ins.imm + 1) as u64 * INS_SIZE as u64
392                    }
393                    // R_BPF_64_64 this is a ld_imm64 text relocation
394                    SymbolKind::Section if rel.size == 64 => sym.address + ins.imm as u64,
395                    _ => todo!(), // FIXME: return an error here,
396                };
397                (sym.section_index.unwrap(), address)
398            } else {
399                // The caller and the callee are in the same ELF section and this is a pc-relative
400                // call. Resolve the pc-relative imm to an absolute address.
401                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            // lookup and link the callee if it hasn't been linked already. `callee_ins_index` will
423            // contain the instruction index of the callee inside the program.
424            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            // `start` is the new instruction offset of `fun` within `program`
465            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            // this is the original offset
474            let original_start_off = line_info[0].insn_off;
475
476            let line_info = line_info.iter().cloned().map(|mut info| {
477                // rebase offsets on top of start, which is the offset of the
478                // function in the program being linked
479                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}