Skip to main content

sbpf_disassembler/
relocation.rs

1use {
2    crate::errors::DisassemblerError,
3    object::{Endianness, Object, ObjectSection, read::elf::ElfFile64},
4    serde::{Deserialize, Serialize},
5};
6
7#[allow(non_camel_case_types)]
8#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
9#[repr(u32)]
10pub enum RelocationType {
11    R_BPF_NONE = 0x00,        // No relocation
12    R_BPF_64_64 = 0x01,       // Relocation of a ld_imm64 instruction
13    R_BPF_64_RELATIVE = 0x08, // Relocation of a ldxdw instruction
14    R_BPF_64_32 = 0x0a,       // Relocation of a call instruction
15}
16
17impl TryFrom<u32> for RelocationType {
18    type Error = DisassemblerError;
19
20    fn try_from(value: u32) -> Result<Self, Self::Error> {
21        Ok(match value {
22            0x00 => Self::R_BPF_NONE,
23            0x01 => Self::R_BPF_64_64,
24            0x08 => Self::R_BPF_64_RELATIVE,
25            0x0a => Self::R_BPF_64_32,
26            _ => return Err(DisassemblerError::InvalidRelocationType(value)),
27        })
28    }
29}
30
31#[derive(Debug, Clone, Serialize, Deserialize)]
32pub struct Relocation {
33    pub offset: u64,
34    pub rel_type: RelocationType,
35    pub symbol_index: u32,
36    pub symbol_name: Option<String>,
37}
38
39impl Relocation {
40    pub fn from_elf_file(
41        elf_file: &ElfFile64<Endianness>,
42    ) -> Result<Vec<Self>, Vec<DisassemblerError>> {
43        let mut errors = Vec::new();
44        // Find .rel.dyn section
45        let rel_dyn_section = match elf_file.section_by_name(".rel.dyn") {
46            Some(s) => s,
47            None => return Ok(Vec::new()),
48        };
49
50        let rel_dyn_data = match rel_dyn_section.data() {
51            Ok(d) => d,
52            Err(source) => {
53                errors.push(DisassemblerError::SectionDataError {
54                    section: ".rel.dyn",
55                    source,
56                });
57                return Err(errors);
58            }
59        };
60
61        // Extract .dynsym and .dynstr data for symbol resolution.
62        let dynsym_data = elf_file
63            .section_by_name(".dynsym")
64            .and_then(|s| s.data().ok());
65        let dynstr_data = elf_file
66            .section_by_name(".dynstr")
67            .and_then(|s| s.data().ok());
68
69        let mut relocations = Vec::new();
70
71        // Parse relocation entries
72        for chunk in rel_dyn_data.chunks_exact(16) {
73            let offset = u64::from_le_bytes(chunk[0..8].try_into().unwrap());
74            let rel_type_val = u32::from_le_bytes(chunk[8..12].try_into().unwrap());
75            let rel_type = match RelocationType::try_from(rel_type_val) {
76                Ok(t) => t,
77                Err(e) => {
78                    errors.push(e);
79                    continue;
80                }
81            };
82            let symbol_index = u32::from_le_bytes(chunk[12..16].try_into().unwrap());
83
84            // Resolve symbol name if this is a syscall relocation
85            let symbol_name = if rel_type == RelocationType::R_BPF_64_32 {
86                match (&dynsym_data, &dynstr_data) {
87                    (Some(dynsym), Some(dynstr)) => {
88                        resolve_symbol_name(dynsym, dynstr, symbol_index as usize).ok()
89                    }
90                    _ => None,
91                }
92            } else {
93                None
94            };
95
96            relocations.push(Relocation {
97                offset,
98                rel_type,
99                symbol_index,
100                symbol_name,
101            });
102        }
103
104        if errors.is_empty() {
105            Ok(relocations)
106        } else {
107            Err(errors)
108        }
109    }
110
111    /// Return this relocation's offset relative to the provided base offset
112    pub fn relative_offset(&self, base_offset: u64) -> u64 {
113        self.offset.saturating_sub(base_offset)
114    }
115
116    /// Check if this is a syscall relocation
117    pub fn is_syscall(&self) -> bool {
118        self.rel_type == RelocationType::R_BPF_64_32
119    }
120}
121
122/// Resolve symbol name for the provided index using .dynsym and .dynstr data
123fn resolve_symbol_name(
124    dynsym_data: &[u8],
125    dynstr_data: &[u8],
126    symbol_index: usize,
127) -> Result<String, DisassemblerError> {
128    const DYNSYM_ENTRY_SIZE: usize = 24;
129
130    // Calculate offset into .dynsym for this symbol.
131    let symbol_entry_offset = symbol_index * DYNSYM_ENTRY_SIZE;
132    if symbol_entry_offset + 4 > dynsym_data.len() {
133        return Err(DisassemblerError::InvalidDataLength(dynsym_data.len()));
134    }
135
136    let dynstr_offset = u32::from_le_bytes(
137        dynsym_data[symbol_entry_offset..symbol_entry_offset + 4]
138            .try_into()
139            .unwrap(),
140    ) as usize;
141    if dynstr_offset >= dynstr_data.len() {
142        return Err(DisassemblerError::InvalidDynstrOffset {
143            offset: dynstr_offset,
144            data_len: dynstr_data.len(),
145        });
146    }
147
148    // Read symbol name from .dynstr data.
149    let end = dynstr_data[dynstr_offset..]
150        .iter()
151        .position(|&b| b == 0)
152        .ok_or(DisassemblerError::InvalidDynstrOffset {
153            offset: dynstr_offset,
154            data_len: dynstr_data.len(),
155        })?;
156
157    String::from_utf8(dynstr_data[dynstr_offset..dynstr_offset + end].to_vec())
158        .map_err(DisassemblerError::InvalidUtf8InDynstr)
159}
160
161#[cfg(test)]
162mod tests {
163    use {super::*, hex_literal::hex, object::read::elf::ElfFile64};
164
165    // Test program:
166    // .globl entrypoint
167    // entrypoint:
168    //   lddw r1, 0x1
169    //   lddw r2, 0x2
170    //   call sol_log_64_
171    //   call sol_log_compute_units_
172    //   exit
173    const TEST_PROGRAM: &[u8] = &hex!(
174        "7F454C460201010000000000000000000300F70001000000E8000000000000004000000000000000A002000000000000000000004000380003004000070006000100000005000000E800000000000000E800000000000000E8000000000000003800000000000000380000000000000000100000000000000100000004000000C001000000000000C001000000000000C001000000000000B000000000000000B00000000000000000100000000000000200000006000000200100000000000020010000000000002001000000000000A000000000000000A0000000000000000800000000000000180100000100000000000000000000001802000002000000000000000000000085100000FFFFFFFF85100000FFFFFFFF95000000000000001E0000000000000004000000000000001100000000000000500200000000000012000000000000002000000000000000130000000000000010000000000000000600000000000000C0010000000000000B000000000000001800000000000000050000000000000020020000000000000A00000000000000300000000000000016000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000010000100E80000000000000000000000000000000C000000100000000000000000000000000000000000000018000000100000000000000000000000000000000000000000656E747279706F696E7400736F6C5F6C6F675F36345F00736F6C5F6C6F675F636F6D707574655F756E6974735F000008010000000000000A0000000200000010010000000000000A00000003000000002E74657874002E64796E616D6963002E64796E73796D002E64796E737472002E72656C2E64796E002E7300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000010000000600000000000000E800000000000000E80000000000000038000000000000000000000000000000040000000000000000000000000000000700000006000000030000000000000020010000000000002001000000000000A000000000000000040000000000000008000000000000001000000000000000100000000B0000000200000000000000C001000000000000C0010000000000006000000000000000040000000100000008000000000000001800000000000000180000000300000002000000000000002002000000000000200200000000000030000000000000000000000000000000010000000000000000000000000000002000000009000000020000000000000050020000000000005002000000000000200000000000000003000000000000000800000000000000100000000000000029000000030000000000000000000000000000000000000070020000000000002C00000000000000000000000000000001000000000000000000000000000000"
175    );
176
177    #[test]
178    fn test_relocation_parsing() {
179        let elf_file = ElfFile64::<Endianness>::parse(TEST_PROGRAM).expect("Failed to parse ELF");
180        let relocations = Relocation::from_elf_file(&elf_file).unwrap();
181
182        // Should have 2 relocations.
183        assert_eq!(relocations.len(), 2, "Expected 2 relocations");
184
185        // Both should be syscall relocations.
186        assert!(relocations[0].is_syscall());
187        assert!(relocations[1].is_syscall());
188
189        // Verify symbol names are resolved.
190        assert_eq!(relocations[0].symbol_name.as_deref(), Some("sol_log_64_"));
191        assert_eq!(
192            relocations[1].symbol_name.as_deref(),
193            Some("sol_log_compute_units_")
194        );
195
196        // Verify symbol indices.
197        // 0 -> null
198        // 1 -> entrypoint
199        // 2 -> sol_log_64_
200        // 3 -> sol_log_compute_units_
201        assert_eq!(relocations[0].symbol_index, 2);
202        assert_eq!(relocations[1].symbol_index, 3);
203    }
204
205    #[test]
206    fn test_relocation_relative_offset() {
207        let elf_file = ElfFile64::<Endianness>::parse(TEST_PROGRAM).expect("Failed to parse ELF");
208        let relocations = Relocation::from_elf_file(&elf_file).unwrap();
209
210        // Get .text section base address from the ELF.
211        let text_section = elf_file
212            .section_by_name(".text")
213            .expect("Failed to find .text section");
214        let text_section_offset = text_section.address();
215
216        // Test relative_offset calculation.
217        let rel0_offset = relocations[0].relative_offset(text_section_offset);
218        let rel1_offset = relocations[1].relative_offset(text_section_offset);
219
220        // Verify relative offsets.
221        // lddw r1, 0x1 -> 0x00
222        // lddw r2, 0x2 -> 0x10
223        // call sol_log_64_ -> 0x20
224        // call sol_log_compute_units_ -> 0x28
225        assert_eq!(rel0_offset, 0x20);
226        assert_eq!(rel1_offset, 0x28);
227    }
228
229    #[test]
230    fn test_no_relocations() {
231        // Simple program with no relocations
232        let test_program = &hex!(
233            "7F454C460201010000000000000000000300F700010000002001000000000000400000000000000028020000000000000000000040003800030040000600050001000000050000002001000000000000200100000000000020010000000000003000000000000000300000000000000000100000000000000100000004000000C001000000000000C001000000000000C0010000000000003C000000000000003C000000000000000010000000000000020000000600000050010000000000005001000000000000500100000000000070000000000000007000000000000000080000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007912A000000000007911182900000000B7000000010000002D21010000000000B70000000000000095000000000000001E0000000000000004000000000000000600000000000000C0010000000000000B0000000000000018000000000000000500000000000000F0010000000000000A000000000000000C00000000000000160000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000120001002001000000000000300000000000000000656E747279706F696E7400002E74657874002E64796E737472002E64796E73796D002E64796E616D6963002E73687374727461620000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000010000000600000000000000200100000000000020010000000000003000000000000000000000000000000008000000000000000000000000000000170000000600000003000000000000005001000000000000500100000000000070000000000000000400000000000000080000000000000010000000000000000F0000000B0000000200000000000000C001000000000000C001000000000000300000000000000004000000010000000800000000000000180000000000000007000000030000000200000000000000F001000000000000F0010000000000000C00000000000000000000000000000001000000000000000000000000000000200000000300000000000000000000000000000000000000FC010000000000002A00000000000000000000000000000001000000000000000000000000000000"
234        );
235
236        let elf_file = ElfFile64::<Endianness>::parse(test_program).expect("Failed to parse ELF");
237        let relocations = Relocation::from_elf_file(&elf_file).unwrap();
238
239        assert!(relocations.is_empty());
240    }
241
242    #[test]
243    fn test_invalid_relocation_type() {
244        // Locate .rel.dyn in the file and corrupt the rel_type field (bytes
245        // 8..12 of the first 16-byte entry).
246        let elf_file = ElfFile64::<Endianness>::parse(TEST_PROGRAM).expect("Failed to parse ELF");
247        let (rel_dyn_offset, _) = elf_file
248            .section_by_name(".rel.dyn")
249            .expect("Failed to find .rel.dyn section")
250            .file_range()
251            .expect("Failed to get .rel.dyn file range");
252
253        let mut bytes = TEST_PROGRAM.to_vec();
254        bytes[rel_dyn_offset as usize + 8] = 0x05;
255
256        let elf_file = ElfFile64::<Endianness>::parse(bytes.as_slice()).expect("Failed to parse");
257        let errors = Relocation::from_elf_file(&elf_file).unwrap_err();
258        match errors.as_slice() {
259            [DisassemblerError::InvalidRelocationType(rel_type)] => assert_eq!(*rel_type, 0x05),
260            other => panic!("expected InvalidRelocationType, got {other:?}"),
261        }
262    }
263}