aya_obj/
relocation.rs

1//! Program relocation handling.
2
3use 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/// The error type returned by [`Object::relocate_maps`] and [`Object::relocate_calls`]
25#[derive(thiserror::Error, Debug)]
26#[error("error relocating `{function}`")]
27pub struct EbpfRelocationError {
28    /// The function name
29    function: String,
30    #[source]
31    /// The original error
32    error: RelocationError,
33}
34
35/// Relocation failures
36#[derive(Debug, thiserror::Error)]
37pub enum RelocationError {
38    /// Unknown symbol
39    #[error("unknown symbol, index `{index}`")]
40    UnknownSymbol {
41        /// The symbol index
42        index: usize,
43    },
44
45    /// Section not found
46    #[error("section `{section_index}` not found, referenced by symbol `{}` #{symbol_index}",
47            .symbol_name.clone().unwrap_or_default())]
48    SectionNotFound {
49        /// The section index
50        section_index: usize,
51        /// The symbol index
52        symbol_index: usize,
53        /// The symbol name
54        symbol_name: Option<String>,
55    },
56
57    /// Unknown function
58    #[error("function {address:#x} not found while relocating `{caller_name}`")]
59    UnknownFunction {
60        /// The function address
61        address: u64,
62        /// The caller name
63        caller_name: String,
64    },
65
66    /// Unknown function
67    #[error("program at section {section_index} and address {address:#x} was not found while relocating")]
68    UnknownProgram {
69        /// The function section index
70        section_index: usize,
71        /// The function address
72        address: u64,
73    },
74
75    /// Invalid relocation offset
76    #[error("invalid offset `{offset}` applying relocation #{relocation_number}")]
77    InvalidRelocationOffset {
78        /// The relocation offset
79        offset: u64,
80        /// The relocation number
81        relocation_number: usize,
82    },
83}
84
85#[derive(Debug, Copy, Clone)]
86pub(crate) struct Relocation {
87    // byte offset of the instruction to be relocated
88    pub(crate) offset: u64,
89    pub(crate) size: u8,
90    // index of the symbol to relocate to
91    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    /// Relocates the map references
107    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    /// Relocates function calls
142    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            // the relocation doesn't apply to this function
195            continue;
196        }
197
198        // make sure that the relocation offset is properly aligned
199        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        // a map relocation points to the ELF section that contains the map
209        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            // this is not a map relocation
217            continue;
218        };
219
220        // calls and relocation to .text symbols are handled in a separate step
221        if insn_is_call(&instructions[ins_index]) || text_sections.contains(&section_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(&section_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        // relocate calls in the program's main function. As relocation happens,
300        // it will trigger linking in all the callees.
301        self.relocate(&mut fun, program_function)?;
302
303        // this now includes the program function plus all the other functions called during
304        // execution
305        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        // append fun.instructions to the program and record that `fun.address` has been inserted
318        // at `start_ins`. We'll use `start_ins` to do pc-relative calls.
319        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        // link func and line info into the main program
327        // the offset needs to be adjusted
328        self.link_func_and_line_info(program, fun, start_ins)?;
329
330        self.linked_functions.insert(fun.address, start_ins);
331
332        // relocate `fun`, recursively linking in all the callees
333        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        // process all the instructions. We can't only loop over relocations since we need to
350        // patch pc-relative calls too.
351        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                    // get the symbol for the relocation
362                    self.symbol_table
363                        .get(&rel.symbol_index)
364                        .map(|sym| (rel, sym))
365                })
366                .filter(|(_rel, sym)| {
367                    // only consider text relocations, data relocations are
368                    // relocated in relocate_maps()
369                    sym.kind == SymbolKind::Text
370                        || sym
371                            .section_index
372                            .map(|section_index| self.text_sections.contains(&section_index))
373                            .unwrap_or(false)
374                });
375
376            // not a call and not a text relocation, we don't need to do anything
377            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                    // R_BPF_64_32 this is a call
385                    SymbolKind::Section if rel.size == 32 => {
386                        sym.address + (ins.imm + 1) as u64 * INS_SIZE as u64
387                    }
388                    // R_BPF_64_64 this is a ld_imm64 text relocation
389                    SymbolKind::Section if rel.size == 64 => sym.address + ins.imm as u64,
390                    _ => todo!(), // FIXME: return an error here,
391                };
392                (sym.section_index.unwrap(), address)
393            } else {
394                // The caller and the callee are in the same ELF section and this is a pc-relative
395                // call. Resolve the pc-relative imm to an absolute address.
396                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            // lookup and link the callee if it hasn't been linked already. `callee_ins_index` will
418            // contain the instruction index of the callee inside the program.
419            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            // `start` is the new instruction offset of `fun` within `program`
460            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            // this is the original offset
469            let original_start_off = line_info[0].insn_off;
470
471            let line_info = line_info.iter().cloned().map(|mut info| {
472                // rebase offsets on top of start, which is the offset of the
473                // function in the program being linked
474                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}