Skip to main content

aya_obj/
relocation.rs

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