Skip to main content

solana_sbpf/
elf.rs

1//! This module relocates a BPF ELF
2
3// Note: Typically ELF shared objects are loaded using the program headers and
4// not the section headers.  Since we are leveraging the elfkit crate its much
5// easier to use the section headers.  There are cases (reduced size, obfuscation)
6// where the section headers may be removed from the ELF.  If that happens then
7// this loader will need to be re-written to use the program headers instead.
8
9use crate::{
10    aligned_memory::{is_memory_aligned, AlignedMemory},
11    ebpf::{self, HOST_ALIGN, INSN_SIZE},
12    elf_parser::{
13        consts::{
14            ELFCLASS64, ELFDATA2LSB, ELFOSABI_NONE, EM_BPF, EM_SBPF, ET_DYN, R_X86_64_32,
15            R_X86_64_64, R_X86_64_NONE, R_X86_64_RELATIVE,
16        },
17        types::{Elf64Phdr, Elf64Shdr, Elf64Word},
18        Elf64, ElfParserError,
19    },
20    error::EbpfError,
21    memory_region::MemoryRegion,
22    program::{BuiltinProgram, FunctionRegistry, SBPFVersion},
23    verifier::Verifier,
24    vm::{Config, ContextObject},
25};
26
27#[cfg(all(feature = "jit", not(target_os = "windows"), target_arch = "x86_64"))]
28use crate::jit::{JitCompiler, JitProgram};
29use byteorder::{ByteOrder, LittleEndian};
30use std::{collections::BTreeMap, fmt::Debug, mem, ops::Range, str};
31
32#[cfg(not(feature = "shuttle-test"))]
33use std::sync::Arc;
34
35#[cfg(feature = "shuttle-test")]
36use shuttle::sync::Arc;
37
38/// Error definitions
39#[derive(Debug, thiserror::Error, PartialEq, Eq)]
40pub enum ElfError {
41    /// Failed to parse ELF file
42    #[error("Failed to parse ELF file: {0}")]
43    FailedToParse(String),
44    /// Entrypoint out of bounds
45    #[error("Entrypoint out of bounds")]
46    EntrypointOutOfBounds,
47    /// Invalid entrypoint
48    #[error("Invalid entrypoint")]
49    InvalidEntrypoint,
50    /// Failed to get section
51    #[error("Failed to get section {0}")]
52    FailedToGetSection(String),
53    /// Unresolved symbol
54    #[error("Unresolved symbol ({0}) at instruction #{1:?} (ELF file offset {2:#x})")]
55    UnresolvedSymbol(String, usize, usize),
56    /// Section not found
57    #[error("Section not found: {0}")]
58    SectionNotFound(String),
59    /// Relative jump out of bounds
60    #[error("Relative jump out of bounds at instruction #{0}")]
61    RelativeJumpOutOfBounds(usize),
62    /// Symbol hash collision
63    #[error("Symbol hash collision {0:#x}")]
64    SymbolHashCollision(u32),
65    /// Incompatible ELF: wrong endianess
66    #[error("Incompatible ELF: wrong endianess")]
67    WrongEndianess,
68    /// Incompatible ELF: wrong ABI
69    #[error("Incompatible ELF: wrong ABI")]
70    WrongAbi,
71    /// Incompatible ELF: wrong machine
72    #[error("Incompatible ELF: wrong machine")]
73    WrongMachine,
74    /// Incompatible ELF: wrong class
75    #[error("Incompatible ELF: wrong class")]
76    WrongClass,
77    /// Not one text section
78    #[error("Multiple or no text sections, consider removing llc option: -function-sections")]
79    NotOneTextSection,
80    /// Read-write data not supported
81    #[error("Found writable section ({0}) in ELF, read-write data not supported")]
82    WritableSectionNotSupported(String),
83    /// Relocation failed, no loadable section contains virtual address
84    #[error("Relocation failed, no loadable section contains virtual address {0:#x}")]
85    AddressOutsideLoadableSection(u64),
86    /// Relocation failed, invalid referenced virtual address
87    #[error("Relocation failed, invalid referenced virtual address {0:#x}")]
88    InvalidVirtualAddress(u64),
89    /// Relocation failed, unknown type
90    #[error("Relocation failed, unknown type {0:?}")]
91    UnknownRelocation(u32),
92    /// Failed to read relocation info
93    #[error("Failed to read relocation info")]
94    FailedToReadRelocationInfo,
95    /// Incompatible ELF: wrong type
96    #[error("Incompatible ELF: wrong type")]
97    WrongType,
98    /// Unknown symbol
99    #[error("Unknown symbol with index {0}")]
100    UnknownSymbol(usize),
101    /// Offset or value is out of bounds
102    #[error("Offset or value is out of bounds")]
103    ValueOutOfBounds,
104    /// Detected sbpf_version required by the executable which are not enabled
105    #[error("Detected sbpf_version required by the executable which are not enabled")]
106    UnsupportedSBPFVersion,
107    /// Invalid program header
108    #[error("Invalid ELF program header")]
109    InvalidProgramHeader,
110}
111
112impl From<ElfParserError> for ElfError {
113    fn from(err: ElfParserError) -> Self {
114        match err {
115            ElfParserError::InvalidSectionHeader
116            | ElfParserError::InvalidString
117            | ElfParserError::InvalidSize
118            | ElfParserError::Overlap
119            | ElfParserError::SectionNotInOrder
120            | ElfParserError::NoSectionNameStringTable
121            | ElfParserError::InvalidDynamicSectionTable
122            | ElfParserError::InvalidRelocationTable
123            | ElfParserError::InvalidAlignment
124            | ElfParserError::NoStringTable
125            | ElfParserError::NoDynamicStringTable
126            | ElfParserError::InvalidFileHeader
127            | ElfParserError::StringTooLong(_, _) => ElfError::FailedToParse(err.to_string()),
128            ElfParserError::InvalidProgramHeader => ElfError::InvalidProgramHeader,
129            ElfParserError::OutOfBounds => ElfError::ValueOutOfBounds,
130        }
131    }
132}
133
134fn get_section(elf: &Elf64, name: &[u8]) -> Result<Elf64Shdr, ElfError> {
135    for section_header in elf.section_header_table() {
136        if elf.section_name(section_header.sh_name)? == name {
137            return Ok(section_header.clone());
138        }
139    }
140
141    Err(ElfError::SectionNotFound(
142        std::str::from_utf8(name)
143            .unwrap_or("UTF-8 error")
144            .to_string(),
145    ))
146}
147
148// For more information on the BPF instruction set:
149// https://github.com/iovisor/bpf-docs/blob/master/eBPF.md
150
151// msb                                                        lsb
152// +------------------------+----------------+----+----+--------+
153// |immediate               |offset          |src |dst |opcode  |
154// +------------------------+----------------+----+----+--------+
155
156// From least significant to most significant bit:
157//   8 bit opcode
158//   4 bit destination register (dst)
159//   4 bit source register (src)
160//   16 bit offset
161//   32 bit immediate (imm)
162
163/// Byte offset of the immediate field in the instruction
164const BYTE_OFFSET_IMMEDIATE: usize = 4;
165/// Byte length of the immediate field
166const BYTE_LENGTH_IMMEDIATE: usize = 4;
167
168/// BPF relocation types.
169#[allow(non_camel_case_types)]
170#[derive(Debug, PartialEq, Copy, Clone)]
171enum BpfRelocationType {
172    /// No relocation, placeholder
173    R_Bpf_None = 0,
174    /// R_BPF_64_64 relocation type is used for ld_imm64 instruction.
175    /// The actual to-be-relocated data (0 or section offset) is
176    /// stored at r_offset + 4 and the read/write data bitsize is 32
177    /// (4 bytes). The relocation can be resolved with the symbol
178    /// value plus implicit addend.
179    R_Bpf_64_64 = 1,
180    /// 64 bit relocation of a ldxdw instruction.  The ldxdw
181    /// instruction occupies two instruction slots. The 64-bit address
182    /// to load from is split into the 32-bit imm field of each
183    /// slot. The first slot's pre-relocation imm field contains the
184    /// virtual address (typically same as the file offset) of the
185    /// location to load. Relocation involves calculating the
186    /// post-load 64-bit physical address referenced by the imm field
187    /// and writing that physical address back into the imm fields of
188    /// the ldxdw instruction.
189    R_Bpf_64_Relative = 8,
190    /// Relocation of a call instruction.  The existing imm field
191    /// contains either an offset of the instruction to jump to (think
192    /// local function call) or a special value of "-1".  If -1 the
193    /// symbol must be looked up in the symbol table.  The relocation
194    /// entry contains the symbol number to call.  In order to support
195    /// both local jumps and calling external symbols a 32-bit hash is
196    /// computed and stored in the the call instruction's 32-bit imm
197    /// field.  The hash is used later to look up the 64-bit address
198    /// to jump to.  In the case of a local jump the hash is
199    /// calculated using the current program counter and in the case
200    /// of a symbol the hash is calculated using the name of the
201    /// symbol.
202    R_Bpf_64_32 = 10,
203}
204impl BpfRelocationType {
205    fn from_x86_relocation_type(from: u32) -> Option<BpfRelocationType> {
206        match from {
207            R_X86_64_NONE => Some(BpfRelocationType::R_Bpf_None),
208            R_X86_64_64 => Some(BpfRelocationType::R_Bpf_64_64),
209            R_X86_64_RELATIVE => Some(BpfRelocationType::R_Bpf_64_Relative),
210            R_X86_64_32 => Some(BpfRelocationType::R_Bpf_64_32),
211            _ => None,
212        }
213    }
214}
215
216/// ELF section
217#[derive(Debug, PartialEq)]
218pub enum Section {
219    /// Owned section data.
220    ///
221    /// The first field is virtual address of the section.
222    /// The second field is the actual section data.
223    Owned(usize, Vec<u8>),
224    /// Borrowed section data.
225    ///
226    /// The first field is virtual address of the section.
227    /// The second field can be used to index the input ELF buffer to
228    /// retrieve the section data.
229    Borrowed(usize, Range<usize>),
230}
231
232/// Elf loader/relocator
233#[derive(Debug)]
234pub struct Executable<C: ContextObject> {
235    /// Loaded and executable elf
236    elf_bytes: AlignedMemory<{ HOST_ALIGN }>,
237    /// Required SBPF capabilities
238    sbpf_version: SBPFVersion,
239    /// Read-only section
240    ro_section: Section,
241    /// Text section virtual address
242    text_section_vaddr: u64,
243    /// Text section range in `elf_bytes`
244    text_section_range: Range<usize>,
245    /// Address of the entry point
246    entry_pc: usize,
247    /// Call resolution map (hash, pc, name)
248    function_registry: FunctionRegistry<usize>,
249    /// Loader built-in program
250    loader: Arc<BuiltinProgram<C>>,
251    /// Compiled program and argument
252    #[cfg(all(feature = "jit", not(target_os = "windows"), target_arch = "x86_64"))]
253    compiled_program: std::sync::Mutex<Option<Arc<JitProgram>>>,
254}
255
256impl<C: PartialEq + ContextObject> PartialEq for Executable<C> {
257    fn eq(&self, other: &Self) -> bool {
258        self.elf_bytes == other.elf_bytes
259            && self.sbpf_version == other.sbpf_version
260            && self.ro_section == other.ro_section
261            && self.text_section_vaddr == other.text_section_vaddr
262            && self.text_section_range == other.text_section_range
263            && self.entry_pc == other.entry_pc
264            && self.function_registry == other.function_registry
265            && *self.loader == *other.loader
266            && {
267                #[cfg(all(feature = "jit", not(target_os = "windows"), target_arch = "x86_64"))]
268                {
269                    // In order to avoid a deadlock comparing self with self, we gotta make sure
270                    // that we clone at least one Arc out of the lock before comparing...
271                    let other = other.get_compiled_program();
272                    let this = self
273                        .compiled_program
274                        .lock()
275                        .unwrap_or_else(|e| e.into_inner());
276                    *this == other
277                }
278                #[cfg(not(all(
279                    feature = "jit",
280                    not(target_os = "windows"),
281                    target_arch = "x86_64"
282                )))]
283                true
284            }
285    }
286}
287
288impl<C: ContextObject> Executable<C> {
289    /// Get the configuration settings
290    pub fn get_config(&self) -> &Config {
291        self.loader.get_config()
292    }
293
294    /// Get the executable sbpf_version
295    pub fn get_sbpf_version(&self) -> SBPFVersion {
296        self.sbpf_version
297    }
298
299    /// Get the .text section virtual address and bytes
300    pub fn get_text_bytes(&self) -> (u64, &[u8]) {
301        (
302            self.text_section_vaddr,
303            &self.elf_bytes.as_slice()[self.text_section_range.clone()],
304        )
305    }
306
307    /// Get the concatenated read-only sections (including the text section)
308    pub fn get_ro_section(&self) -> &[u8] {
309        match &self.ro_section {
310            Section::Owned(_offset, data) => data.as_slice(),
311            Section::Borrowed(_offset, byte_range) => {
312                &self.elf_bytes.as_slice()[byte_range.clone()]
313            }
314        }
315    }
316
317    /// Get a memory region that can be used to access the merged readonly section
318    pub fn get_ro_region(&self) -> MemoryRegion {
319        get_ro_region(&self.ro_section, self.elf_bytes.as_slice())
320    }
321
322    /// Get the entry point offset into the text section
323    pub fn get_entrypoint_instruction_offset(&self) -> usize {
324        self.entry_pc
325    }
326
327    /// Get the text section offset in the ELF file
328    #[cfg(feature = "debugger")]
329    pub fn get_text_section_offset(&self) -> u64 {
330        self.text_section_range.start as u64
331    }
332
333    /// Get the loader built-in program
334    pub fn get_loader(&self) -> &Arc<BuiltinProgram<C>> {
335        &self.loader
336    }
337
338    /// Get the JIT compiled program
339    ///
340    /// This function will not block the calling thread even if there is a concurrent ongoing call
341    /// to [`Self::jit_compile`].
342    #[cfg(all(feature = "jit", not(target_os = "windows"), target_arch = "x86_64"))]
343    pub fn get_compiled_program(&self) -> Option<Arc<JitProgram>> {
344        let guard = self
345            .compiled_program
346            .lock()
347            .unwrap_or_else(|e| e.into_inner());
348        guard.as_ref().map(Arc::clone)
349    }
350
351    /// Verify the executable
352    pub fn verify<V: Verifier>(&self) -> Result<(), EbpfError> {
353        <V as Verifier>::verify(
354            self.get_text_bytes().1,
355            self.get_config(),
356            self.get_sbpf_version(),
357        )?;
358        Ok(())
359    }
360
361    /// JIT compile the executable
362    ///
363    /// This function does not ensure fully sequentially consistent execution ordering between calls
364    /// to it and related calls such as [`Self::get_compiled_program`] or
365    /// [`Self::take_compiled_program`].
366    ///
367    /// This means that there can be some non-trivial interactions in ordering between calls to this
368    /// function and a `get_compiled_program`: concurrent calls to `get_compiled_program` will
369    /// return the previous compiled program or `None` for the duration of the compilation process
370    /// and is only guaranteed to start returning the newly compiled `JitProgram` after this
371    /// function returns.
372    #[cfg(all(feature = "jit", not(target_os = "windows"), target_arch = "x86_64"))]
373    pub fn jit_compile(&self) -> Result<(), crate::error::EbpfError> {
374        let jit = JitCompiler::<C>::new(self)?;
375        let compiled = Arc::new(jit.compile()?);
376        let mut guard = self
377            .compiled_program
378            .lock()
379            .unwrap_or_else(|e| e.into_inner());
380        *guard = Some(compiled);
381        Ok(())
382    }
383
384    /// Remove the compiled program.
385    ///
386    /// Note that the results can be unpredictable in presence of concurrent ongoing calls to
387    /// [`Self::jit_compile`]: based on exact execution ordering this function may take out the
388    /// previous program (or `None`) that shorly afterwards gets replaced by a compiled program.
389    #[cfg(all(feature = "jit", not(target_os = "windows"), target_arch = "x86_64"))]
390    pub fn take_compiled_program(&self) -> Option<Arc<JitProgram>> {
391        let mut guard = self
392            .compiled_program
393            .lock()
394            .unwrap_or_else(|e| e.into_inner());
395        guard.take()
396    }
397
398    /// Get the function registry
399    pub fn get_function_registry(&self) -> &FunctionRegistry<usize> {
400        &self.function_registry
401    }
402
403    /// Create from raw text section bytes (list of instructions)
404    pub fn new_from_text_bytes(
405        text_bytes: &[u8],
406        loader: Arc<BuiltinProgram<C>>,
407        sbpf_version: SBPFVersion,
408        mut function_registry: FunctionRegistry<usize>,
409    ) -> Result<Self, ElfError> {
410        let elf_bytes = AlignedMemory::from_slice(text_bytes);
411        let entry_pc = if let Some((_name, pc)) = function_registry.lookup_by_name(b"entrypoint") {
412            pc
413        } else {
414            function_registry.register_function_hashed_legacy(
415                &loader,
416                !sbpf_version.static_syscalls(),
417                *b"entrypoint",
418                0,
419            )?;
420            0
421        };
422        Ok(Self {
423            elf_bytes,
424            sbpf_version,
425            ro_section: Section::Borrowed(
426                if sbpf_version.enable_lower_rodata_vaddr() {
427                    ebpf::MM_RODATA_START
428                } else {
429                    ebpf::MM_BYTECODE_START
430                } as usize,
431                0..text_bytes.len(),
432            ),
433            text_section_vaddr: ebpf::MM_BYTECODE_START,
434            text_section_range: 0..text_bytes.len(),
435            entry_pc,
436            function_registry,
437            loader,
438            #[cfg(all(feature = "jit", not(target_os = "windows"), target_arch = "x86_64"))]
439            compiled_program: None.into(),
440        })
441    }
442
443    /// Fully loads an ELF
444    pub fn load(bytes: &[u8], loader: Arc<BuiltinProgram<C>>) -> Result<Self, ElfError> {
445        const E_FLAGS_OFFSET: usize = 48;
446        let e_flags = LittleEndian::read_u32(
447            bytes
448                .get(E_FLAGS_OFFSET..E_FLAGS_OFFSET.saturating_add(std::mem::size_of::<u32>()))
449                .ok_or(ElfParserError::OutOfBounds)?,
450        );
451        let config = loader.get_config();
452        let sbpf_version = match e_flags {
453            0 => SBPFVersion::V0,
454            1 => SBPFVersion::V1,
455            2 => SBPFVersion::V2,
456            3 => SBPFVersion::V3,
457            4 => SBPFVersion::V4,
458            _ => SBPFVersion::Reserved,
459        };
460        if !config.enabled_sbpf_versions.contains(&sbpf_version) {
461            return Err(ElfError::UnsupportedSBPFVersion);
462        }
463
464        let mut executable = if sbpf_version.enable_stricter_elf_headers() {
465            Self::load_with_strict_parser(bytes, loader)?
466        } else {
467            Self::load_with_lenient_parser(bytes, loader)?
468        };
469        executable.sbpf_version = sbpf_version;
470        Ok(executable)
471    }
472
473    /// Loads an ELF without relocation
474    pub fn load_with_strict_parser(
475        bytes: &[u8],
476        loader: Arc<BuiltinProgram<C>>,
477    ) -> Result<Self, ElfParserError> {
478        use crate::elf_parser::{
479            consts::{ELFMAG, EV_CURRENT, PF_R, PF_X, PT_LOAD, SHN_UNDEF, STT_FUNC},
480            types::{Elf64Ehdr, Elf64Sym},
481        };
482
483        let aligned_memory = AlignedMemory::<{ HOST_ALIGN }>::from_slice(bytes);
484        let elf_bytes = aligned_memory.as_slice();
485
486        let (file_header_range, file_header) = Elf64::parse_file_header(elf_bytes)?;
487        let program_header_table_range = mem::size_of::<Elf64Ehdr>()
488            ..mem::size_of::<Elf64Phdr>()
489                .saturating_mul(file_header.e_phnum as usize)
490                .saturating_add(mem::size_of::<Elf64Ehdr>());
491        if file_header.e_ident.ei_mag != ELFMAG
492            || file_header.e_ident.ei_class != ELFCLASS64
493            || file_header.e_ident.ei_data != ELFDATA2LSB
494            || file_header.e_ident.ei_version != EV_CURRENT as u8
495            || file_header.e_ident.ei_osabi != ELFOSABI_NONE
496            || file_header.e_ident.ei_abiversion != 0x00
497            || file_header.e_ident.ei_pad != [0x00; 7]
498            // file_header.e_type
499            || file_header.e_machine != EM_BPF
500            || file_header.e_version != EV_CURRENT
501            // file_header.e_entry
502            || file_header.e_phoff != mem::size_of::<Elf64Ehdr>() as u64
503            // file_header.e_shoff
504            // file_header.e_flags
505            || file_header.e_ehsize != mem::size_of::<Elf64Ehdr>() as u16
506            || file_header.e_phentsize != mem::size_of::<Elf64Phdr>() as u16
507            || file_header.e_phnum == 0
508            || program_header_table_range.end > elf_bytes.len()
509        // file_header.e_shentsize
510        // file_header.e_shnum
511        // file_header.e_shstrndx
512        {
513            return Err(ElfParserError::InvalidFileHeader);
514        }
515
516        const EXPECTED_PROGRAM_HEADERS: [(u32, u64); 2] = [
517            (PF_R, ebpf::MM_RODATA_START),   // read only data
518            (PF_X, ebpf::MM_BYTECODE_START), // byte code
519        ];
520        let program_header_table =
521            Elf64::slice_from_bytes::<Elf64Phdr>(elf_bytes, program_header_table_range.clone())?;
522        let mut expected_program_headers = EXPECTED_PROGRAM_HEADERS.iter();
523        let skip_rodata_program_header =
524            program_header_table[0].p_flags != EXPECTED_PROGRAM_HEADERS[0].0;
525        if skip_rodata_program_header {
526            // If the first program header is not marked as PF_R (readonly),
527            // then expect to start at the second program header instead.
528            expected_program_headers.next();
529        } else if file_header.e_phnum < 2 {
530            return Err(ElfParserError::InvalidFileHeader);
531        }
532        let mut expected_offset = program_header_table_range.end as u64;
533        for (program_header, (p_flags, p_vaddr)) in
534            program_header_table.iter().zip(expected_program_headers)
535        {
536            if program_header.p_type != PT_LOAD
537                || program_header.p_flags != *p_flags
538                || program_header.p_offset != expected_offset
539                || program_header.p_offset >= elf_bytes.len() as u64
540                || program_header.p_offset.checked_rem(ebpf::INSN_SIZE as u64) != Some(0)
541                || program_header.p_vaddr != *p_vaddr
542                || program_header.p_paddr != *p_vaddr
543                || program_header.p_filesz != program_header.p_memsz
544                || program_header.p_filesz
545                    > (elf_bytes.len() as u64).saturating_sub(program_header.p_offset)
546                || program_header.p_filesz.checked_rem(ebpf::INSN_SIZE as u64) != Some(0)
547                || program_header.p_memsz >= ebpf::MM_REGION_SIZE
548            {
549                return Err(ElfParserError::InvalidProgramHeader);
550            }
551            expected_offset = expected_offset.saturating_add(program_header.p_filesz);
552        }
553
554        let (ro_section_range, bytecode_header) = if skip_rodata_program_header {
555            (
556                program_header_table_range.end..program_header_table_range.end,
557                &program_header_table[0],
558            )
559        } else {
560            (
561                program_header_table[0].file_range().unwrap_or_default(),
562                &program_header_table[1],
563            )
564        };
565        let ro_section = Section::Borrowed(ebpf::MM_RODATA_START as usize, ro_section_range);
566        let text_section_vaddr = bytecode_header.p_vaddr;
567        let text_section_range = bytecode_header.file_range().unwrap_or_default();
568
569        if !bytecode_header.vm_range().contains(
570            &file_header
571                .e_entry
572                .saturating_add(ebpf::INSN_SIZE as u64)
573                .saturating_sub(1),
574        ) || file_header.e_entry.checked_rem(ebpf::INSN_SIZE as u64) != Some(0)
575        {
576            return Err(ElfParserError::InvalidFileHeader);
577        }
578        let entry_pc = file_header
579            .e_entry
580            .saturating_sub(bytecode_header.p_vaddr)
581            .checked_div(ebpf::INSN_SIZE as u64)
582            .unwrap_or_default() as usize;
583
584        let mut function_registry = FunctionRegistry::<usize>::default();
585        let config = loader.get_config();
586        if config.enable_symbol_and_section_labels {
587            let (_section_header_table_range, section_header_table) =
588                Elf64::parse_section_header_table(
589                    elf_bytes,
590                    file_header_range.clone(),
591                    file_header,
592                    program_header_table_range.clone(),
593                )
594                .unwrap();
595            let section_names_section_header = (file_header.e_shstrndx != SHN_UNDEF)
596                .then(|| {
597                    section_header_table
598                        .get(file_header.e_shstrndx as usize)
599                        .ok_or(ElfParserError::OutOfBounds)
600                })
601                .transpose()?
602                .unwrap();
603            let mut symbol_names_section_header = None;
604            let mut symbol_table_section_header = None;
605            for section_header in section_header_table.iter() {
606                let section_name = Elf64::get_string_in_section(
607                    elf_bytes,
608                    section_names_section_header,
609                    section_header.sh_name,
610                    64,
611                )
612                .unwrap();
613                if section_name == b".strtab" {
614                    symbol_names_section_header = Some(section_header);
615                }
616                if section_name == b".symtab" {
617                    symbol_table_section_header = Some(section_header);
618                }
619            }
620            let symbol_names_section_header = symbol_names_section_header.unwrap();
621            let symbol_table: &[Elf64Sym] =
622                Elf64::slice_from_section_header(elf_bytes, symbol_table_section_header.unwrap())
623                    .unwrap();
624            for symbol in symbol_table {
625                if symbol.st_info & STT_FUNC == 0 {
626                    continue;
627                }
628                let target_pc = symbol
629                    .st_value
630                    .saturating_sub(bytecode_header.p_vaddr)
631                    .checked_div(ebpf::INSN_SIZE as u64)
632                    .unwrap_or_default() as usize;
633                let name = Elf64::get_string_in_section(
634                    elf_bytes,
635                    symbol_names_section_header,
636                    symbol.st_name as Elf64Word,
637                    u8::MAX as usize,
638                )
639                .unwrap();
640                function_registry
641                    .register_function(target_pc as u32, name, target_pc)
642                    .unwrap();
643            }
644        }
645
646        Ok(Self {
647            elf_bytes: aligned_memory,
648            sbpf_version: SBPFVersion::Reserved, // Is set in Self::load()
649            ro_section,
650            text_section_vaddr,
651            text_section_range,
652            entry_pc,
653            function_registry,
654            loader,
655            #[cfg(all(feature = "jit", not(target_os = "windows"), target_arch = "x86_64"))]
656            compiled_program: None.into(),
657        })
658    }
659
660    /// Loads an ELF with relocation
661    fn load_with_lenient_parser(
662        bytes: &[u8],
663        loader: Arc<BuiltinProgram<C>>,
664    ) -> Result<Self, ElfError> {
665        // We always need one memory copy to take ownership and for relocations
666        let aligned_memory = AlignedMemory::<{ HOST_ALIGN }>::from_slice(bytes);
667        let (mut elf_bytes, unrelocated_elf_bytes) =
668            if is_memory_aligned(bytes.as_ptr() as usize, HOST_ALIGN) {
669                (aligned_memory, bytes)
670            } else {
671                // We might need another memory copy to ensure alignment
672                (aligned_memory.clone(), aligned_memory.as_slice())
673            };
674        let elf = Elf64::parse(unrelocated_elf_bytes)?;
675
676        let config = loader.get_config();
677        let header = elf.file_header();
678
679        Self::validate(&elf, elf_bytes.as_slice())?;
680
681        // calculate the text section info
682        let text_section = get_section(&elf, b".text")?;
683        let text_section_vaddr = text_section.sh_addr.saturating_add(ebpf::MM_REGION_SIZE);
684        if (config.reject_broken_elfs && text_section.sh_addr != text_section.sh_offset)
685            || text_section_vaddr > ebpf::MM_STACK_START
686        {
687            return Err(ElfError::ValueOutOfBounds);
688        }
689
690        // relocate symbols
691        let mut function_registry = FunctionRegistry::default();
692        Self::relocate(
693            &mut function_registry,
694            &loader,
695            &elf,
696            elf_bytes.as_slice_mut(),
697        )?;
698
699        // calculate entrypoint offset into the text section
700        let offset = header.e_entry.saturating_sub(text_section.sh_addr);
701        if offset.checked_rem(ebpf::INSN_SIZE as u64) != Some(0) {
702            return Err(ElfError::InvalidEntrypoint);
703        }
704        let entry_pc = if let Some(entry_pc) = (offset as usize).checked_div(ebpf::INSN_SIZE) {
705            function_registry.unregister_function(ebpf::hash_symbol_name(b"entrypoint"));
706            function_registry.register_function_hashed_legacy(
707                &loader,
708                true,
709                *b"entrypoint",
710                entry_pc,
711            )?;
712            entry_pc
713        } else {
714            return Err(ElfError::InvalidEntrypoint);
715        };
716
717        let ro_section = Self::parse_ro_sections(
718            config,
719            elf.section_header_table()
720                .iter()
721                .map(|s| (elf.section_name(s.sh_name).ok(), s)),
722            elf_bytes.as_slice(),
723        )?;
724        let ro_section_vaddr = match &ro_section {
725            Section::Owned(offset, _data) => *offset,
726            Section::Borrowed(offset, _byte_range) => *offset,
727        } as u64;
728        if config.optimize_rodata {
729            let ro_section_index = ro_section_vaddr
730                .checked_shr(ebpf::VIRTUAL_ADDRESS_BITS as u32)
731                .unwrap_or(0);
732            if ro_section_index != 1 {
733                return Err(ElfError::ValueOutOfBounds);
734            }
735        } else {
736            debug_assert_eq!(ro_section_vaddr, ebpf::MM_REGION_SIZE);
737        }
738
739        Ok(Self {
740            elf_bytes,
741            sbpf_version: SBPFVersion::Reserved, // Is set in Self::load()
742            ro_section,
743            text_section_vaddr,
744            text_section_range: text_section.file_range().unwrap_or_default(),
745            entry_pc,
746            function_registry,
747            loader,
748            #[cfg(all(feature = "jit", not(target_os = "windows"), target_arch = "x86_64"))]
749            compiled_program: None.into(),
750        })
751    }
752
753    /// Calculate the total memory size of the executable
754    #[rustfmt::skip]
755    #[allow(clippy::size_of_ref)]
756    pub fn mem_size(&self) -> usize {
757        let mut total = mem::size_of::<Self>();
758        total = total
759            // elf bytes
760            .saturating_add(self.elf_bytes.mem_size())
761            // ro section
762            .saturating_add(match &self.ro_section {
763                Section::Owned(_, data) => data.capacity(),
764                Section::Borrowed(_, _) => 0,
765            })
766            // bpf functions
767            .saturating_add(self.function_registry.mem_size());
768
769        #[cfg(all(feature = "jit", not(target_os = "windows"), target_arch = "x86_64"))]
770        {
771            // compiled programs
772            let prog = self.compiled_program.lock().unwrap_or_else(|e| e.into_inner());
773            total = total.saturating_add(prog.as_ref().map_or(0, |program| program.mem_size()));
774        }
775
776        total
777    }
778
779    // Functions exposed for tests
780
781    /// Validates the ELF
782    pub fn validate(elf: &Elf64, elf_bytes: &[u8]) -> Result<(), ElfError> {
783        let header = elf.file_header();
784        if header.e_ident.ei_class != ELFCLASS64 {
785            return Err(ElfError::WrongClass);
786        }
787        if header.e_ident.ei_data != ELFDATA2LSB {
788            return Err(ElfError::WrongEndianess);
789        }
790        if header.e_ident.ei_osabi != ELFOSABI_NONE {
791            return Err(ElfError::WrongAbi);
792        }
793        if header.e_machine != EM_BPF && header.e_machine != EM_SBPF {
794            return Err(ElfError::WrongMachine);
795        }
796        if header.e_type != ET_DYN {
797            return Err(ElfError::WrongType);
798        }
799
800        let num_text_sections =
801            elf.section_header_table()
802                .iter()
803                .fold(0, |count: usize, section_header| {
804                    if let Ok(this_name) = elf.section_name(section_header.sh_name) {
805                        if this_name == b".text" {
806                            return count.saturating_add(1);
807                        }
808                    }
809                    count
810                });
811        if 1 != num_text_sections {
812            return Err(ElfError::NotOneTextSection);
813        }
814
815        for section_header in elf.section_header_table().iter() {
816            if let Ok(name) = elf.section_name(section_header.sh_name) {
817                if name.starts_with(b".bss")
818                    || (section_header.is_writable()
819                        && (name.starts_with(b".data") && !name.starts_with(b".data.rel")))
820                {
821                    return Err(ElfError::WritableSectionNotSupported(
822                        String::from_utf8_lossy(name).to_string(),
823                    ));
824                }
825            }
826        }
827
828        for section_header in elf.section_header_table().iter() {
829            let start = section_header.sh_offset as usize;
830            let end = section_header
831                .sh_offset
832                .checked_add(section_header.sh_size)
833                .ok_or(ElfError::ValueOutOfBounds)? as usize;
834            let _ = elf_bytes
835                .get(start..end)
836                .ok_or(ElfError::ValueOutOfBounds)?;
837        }
838        let text_section = get_section(elf, b".text")?;
839        if !text_section.vm_range().contains(&header.e_entry) {
840            return Err(ElfError::EntrypointOutOfBounds);
841        }
842
843        Ok(())
844    }
845
846    /// Parses and concatenates the readonly data sections
847    pub fn parse_ro_sections<'a, S: IntoIterator<Item = (Option<&'a [u8]>, &'a Elf64Shdr)>>(
848        config: &Config,
849        sections: S,
850        elf_bytes: &[u8],
851    ) -> Result<Section, ElfError> {
852        // the lowest section address
853        let mut lowest_addr = usize::MAX;
854        // the highest section address
855        let mut highest_addr = 0;
856        // the aggregated section length, not including gaps between sections
857        let mut ro_fill_length = 0usize;
858        let mut invalid_offsets = false;
859
860        // keep track of where ro sections are so we can tell whether they're
861        // contiguous
862        let mut first_ro_section = 0;
863        let mut last_ro_section = 0;
864        let mut n_ro_sections = 0usize;
865
866        let mut ro_slices = vec![];
867        for (i, (name, section_header)) in sections.into_iter().enumerate() {
868            match name {
869                Some(name)
870                    if name == b".text"
871                        || name == b".rodata"
872                        || name == b".data.rel.ro"
873                        || name == b".eh_frame" => {}
874                _ => continue,
875            }
876
877            if n_ro_sections == 0 {
878                first_ro_section = i;
879            }
880            last_ro_section = i;
881            n_ro_sections = n_ro_sections.saturating_add(1);
882
883            let section_addr = section_header.sh_addr;
884
885            // sh_offset handling:
886            // section_addr must match sh_offset
887            if !invalid_offsets && section_addr != section_header.sh_offset {
888                invalid_offsets = true;
889            }
890
891            let vaddr_end = section_addr.saturating_add(ebpf::MM_REGION_SIZE);
892            if (config.reject_broken_elfs && invalid_offsets) || vaddr_end > ebpf::MM_STACK_START {
893                return Err(ElfError::ValueOutOfBounds);
894            }
895
896            let section_data = elf_bytes
897                .get(section_header.file_range().unwrap_or_default())
898                .ok_or(ElfError::ValueOutOfBounds)?;
899
900            let section_addr = section_addr as usize;
901            lowest_addr = lowest_addr.min(section_addr);
902            highest_addr = highest_addr.max(section_addr.saturating_add(section_data.len()));
903            ro_fill_length = ro_fill_length.saturating_add(section_data.len());
904
905            ro_slices.push((section_addr, section_data));
906        }
907
908        if config.reject_broken_elfs && lowest_addr.saturating_add(ro_fill_length) > highest_addr {
909            return Err(ElfError::ValueOutOfBounds);
910        }
911
912        let can_borrow = !invalid_offsets
913            && last_ro_section
914                .saturating_add(1)
915                .saturating_sub(first_ro_section)
916                == n_ro_sections;
917        let ro_section = if config.optimize_rodata && can_borrow {
918            // Read only sections are grouped together with no intermixed non-ro
919            // sections. We can borrow.
920
921            let addr_offset = if lowest_addr >= ebpf::MM_REGION_SIZE as usize {
922                // The first field of Section::Borrowed is an offset from
923                // ebpf::MM_REGION_SIZE * 1 so if the linker has already put the
924                // sections within ebpf::MM_REGION_SIZE * 1, we need to subtract
925                // it now.
926                lowest_addr
927            } else {
928                lowest_addr.saturating_add(ebpf::MM_REGION_SIZE as usize)
929            };
930
931            Section::Borrowed(addr_offset, lowest_addr..highest_addr)
932        } else {
933            // Read only and other non-ro sections are mixed. Zero the non-ro
934            // sections and and copy the ro ones at their intended offsets.
935
936            if config.optimize_rodata {
937                // The rodata region starts at MM_REGION_SIZE * 1 + offset,
938                // [MM_REGION_SIZE * 1, MM_REGION_SIZE * 1 + offset) is not
939                // mappable. We only need to allocate highest_addr - lowest_addr
940                // bytes.
941                highest_addr = highest_addr.saturating_sub(lowest_addr);
942            } else {
943                // For backwards compatibility, the whole [MM_REGION_SIZE * 1,
944                // MM_REGION_SIZE * 1 + highest_addr) range is mappable. We need
945                // to allocate the whole address range.
946                lowest_addr = 0;
947            };
948
949            let buf_len = highest_addr;
950            if buf_len > elf_bytes.len() {
951                return Err(ElfError::ValueOutOfBounds);
952            }
953
954            let mut ro_section = vec![0; buf_len];
955            for (section_addr, slice) in ro_slices.iter() {
956                let buf_offset_start = section_addr.saturating_sub(lowest_addr);
957                ro_section[buf_offset_start..buf_offset_start.saturating_add(slice.len())]
958                    .copy_from_slice(slice);
959            }
960
961            let addr_offset = if lowest_addr >= ebpf::MM_REGION_SIZE as usize {
962                lowest_addr
963            } else {
964                lowest_addr.saturating_add(ebpf::MM_REGION_SIZE as usize)
965            };
966            Section::Owned(addr_offset, ro_section)
967        };
968
969        Ok(ro_section)
970    }
971
972    /// Relocates the ELF in-place
973    fn relocate(
974        function_registry: &mut FunctionRegistry<usize>,
975        loader: &BuiltinProgram<C>,
976        elf: &Elf64,
977        elf_bytes: &mut [u8],
978    ) -> Result<(), ElfError> {
979        let mut syscall_cache = BTreeMap::new();
980        let text_section = get_section(elf, b".text")?;
981
982        // Fixup all program counter relative call instructions
983        let config = loader.get_config();
984        let text_bytes = elf_bytes
985            .get_mut(text_section.file_range().unwrap_or_default())
986            .ok_or(ElfError::ValueOutOfBounds)?;
987        let instruction_count = text_bytes
988            .len()
989            .checked_div(ebpf::INSN_SIZE)
990            .ok_or(ElfError::ValueOutOfBounds)?;
991        for i in 0..instruction_count {
992            let insn = ebpf::get_insn(text_bytes, i);
993            if insn.opc == ebpf::CALL_IMM && insn.imm != -1 {
994                let target_pc = (i as isize)
995                    .saturating_add(1)
996                    .saturating_add(insn.imm as isize);
997                if target_pc < 0 || target_pc >= instruction_count as isize {
998                    return Err(ElfError::RelativeJumpOutOfBounds(i));
999                }
1000                let name = if config.enable_symbol_and_section_labels {
1001                    format!("function_{target_pc}")
1002                } else {
1003                    String::default()
1004                };
1005                let key = function_registry.register_function_hashed_legacy(
1006                    loader,
1007                    true,
1008                    name.as_bytes(),
1009                    target_pc as usize,
1010                )?;
1011                let offset = i.saturating_mul(ebpf::INSN_SIZE).saturating_add(4);
1012                let checked_slice = text_bytes
1013                    .get_mut(offset..offset.saturating_add(4))
1014                    .ok_or(ElfError::ValueOutOfBounds)?;
1015                LittleEndian::write_u32(checked_slice, key);
1016            }
1017        }
1018
1019        // Fixup all the relocations in the relocation section if exists
1020        for relocation in elf.dynamic_relocations_table().unwrap_or_default().iter() {
1021            let r_offset = relocation.r_offset as usize;
1022
1023            match BpfRelocationType::from_x86_relocation_type(relocation.r_type()) {
1024                Some(BpfRelocationType::R_Bpf_64_64) => {
1025                    // Offset of the immediate field
1026                    let imm_offset = r_offset.saturating_add(BYTE_OFFSET_IMMEDIATE);
1027
1028                    // Read the instruction's immediate field which contains virtual
1029                    // address to convert to physical
1030                    let checked_slice = elf_bytes
1031                        .get(imm_offset..imm_offset.saturating_add(BYTE_LENGTH_IMMEDIATE))
1032                        .ok_or(ElfError::ValueOutOfBounds)?;
1033                    let refd_addr = LittleEndian::read_u32(checked_slice) as u64;
1034
1035                    let symbol = elf
1036                        .dynamic_symbol_table()
1037                        .and_then(|table| table.get(relocation.r_sym() as usize).cloned())
1038                        .ok_or_else(|| ElfError::UnknownSymbol(relocation.r_sym() as usize))?;
1039
1040                    // The relocated address is relative to the address of the
1041                    // symbol at index `r_sym`
1042                    let mut addr = symbol.st_value.saturating_add(refd_addr);
1043
1044                    // The "physical address" from the VM's perspective is rooted
1045                    // at ebpf::MM_REGION_SIZE * 1. If the linker hasn't already put
1046                    // the symbol within ebpf::MM_REGION_SIZE * 1, we need to do so
1047                    // now.
1048                    if addr < ebpf::MM_REGION_SIZE {
1049                        addr = ebpf::MM_REGION_SIZE.saturating_add(addr);
1050                    }
1051
1052                    let imm_low_offset = imm_offset;
1053                    let imm_high_offset = imm_low_offset.saturating_add(INSN_SIZE);
1054
1055                    // Write the low side of the relocate address
1056                    let imm_slice = elf_bytes
1057                        .get_mut(
1058                            imm_low_offset..imm_low_offset.saturating_add(BYTE_LENGTH_IMMEDIATE),
1059                        )
1060                        .ok_or(ElfError::ValueOutOfBounds)?;
1061                    LittleEndian::write_u32(imm_slice, (addr & 0xFFFFFFFF) as u32);
1062
1063                    // Write the high side of the relocate address
1064                    let imm_slice = elf_bytes
1065                        .get_mut(
1066                            imm_high_offset..imm_high_offset.saturating_add(BYTE_LENGTH_IMMEDIATE),
1067                        )
1068                        .ok_or(ElfError::ValueOutOfBounds)?;
1069                    LittleEndian::write_u32(
1070                        imm_slice,
1071                        addr.checked_shr(32).unwrap_or_default() as u32,
1072                    );
1073                }
1074                Some(BpfRelocationType::R_Bpf_64_Relative) => {
1075                    // Relocation between different sections, where the target
1076                    // memory is not associated to a symbol (eg some compiler
1077                    // generated rodata that doesn't have an explicit symbol).
1078
1079                    // Offset of the immediate field
1080                    let imm_offset = r_offset.saturating_add(BYTE_OFFSET_IMMEDIATE);
1081
1082                    if text_section
1083                        .file_range()
1084                        .unwrap_or_default()
1085                        .contains(&r_offset)
1086                    {
1087                        // We're relocating a lddw instruction, which spans two
1088                        // instruction slots. The address to be relocated is
1089                        // split in two halves in the two imms of the
1090                        // instruction slots.
1091                        let imm_low_offset = imm_offset;
1092                        let imm_high_offset = r_offset
1093                            .saturating_add(INSN_SIZE)
1094                            .saturating_add(BYTE_OFFSET_IMMEDIATE);
1095
1096                        // Read the low side of the address
1097                        let imm_slice = elf_bytes
1098                            .get(
1099                                imm_low_offset
1100                                    ..imm_low_offset.saturating_add(BYTE_LENGTH_IMMEDIATE),
1101                            )
1102                            .ok_or(ElfError::ValueOutOfBounds)?;
1103                        let va_low = LittleEndian::read_u32(imm_slice) as u64;
1104
1105                        // Read the high side of the address
1106                        let imm_slice = elf_bytes
1107                            .get(
1108                                imm_high_offset
1109                                    ..imm_high_offset.saturating_add(BYTE_LENGTH_IMMEDIATE),
1110                            )
1111                            .ok_or(ElfError::ValueOutOfBounds)?;
1112                        let va_high = LittleEndian::read_u32(imm_slice) as u64;
1113
1114                        // Put the address back together
1115                        let mut refd_addr = va_high.checked_shl(32).unwrap_or_default() | va_low;
1116
1117                        if refd_addr == 0 {
1118                            return Err(ElfError::InvalidVirtualAddress(refd_addr));
1119                        }
1120
1121                        if refd_addr < ebpf::MM_REGION_SIZE {
1122                            // The linker hasn't already placed rodata within
1123                            // ebpf::MM_REGION_SIZE * 1, so we do so now
1124                            refd_addr = ebpf::MM_REGION_SIZE.saturating_add(refd_addr);
1125                        }
1126
1127                        // Write back the low half
1128                        let imm_slice = elf_bytes
1129                            .get_mut(
1130                                imm_low_offset
1131                                    ..imm_low_offset.saturating_add(BYTE_LENGTH_IMMEDIATE),
1132                            )
1133                            .ok_or(ElfError::ValueOutOfBounds)?;
1134                        LittleEndian::write_u32(imm_slice, (refd_addr & 0xFFFFFFFF) as u32);
1135
1136                        // Write back the high half
1137                        let imm_slice = elf_bytes
1138                            .get_mut(
1139                                imm_high_offset
1140                                    ..imm_high_offset.saturating_add(BYTE_LENGTH_IMMEDIATE),
1141                            )
1142                            .ok_or(ElfError::ValueOutOfBounds)?;
1143                        LittleEndian::write_u32(
1144                            imm_slice,
1145                            refd_addr.checked_shr(32).unwrap_or_default() as u32,
1146                        );
1147                    } else {
1148                        // There used to be a bug in toolchains before
1149                        // https://github.com/solana-labs/llvm-project/pull/35 where for 64 bit
1150                        // relocations we were encoding only the low 32 bits, shifted 32 bits to
1151                        // the left. Our relocation code used to be compatible with that, so we
1152                        // need to keep supporting this case for backwards compatibility.
1153                        let addr_slice = elf_bytes
1154                            .get(imm_offset..imm_offset.saturating_add(BYTE_LENGTH_IMMEDIATE))
1155                            .ok_or(ElfError::ValueOutOfBounds)?;
1156                        let mut refd_addr = LittleEndian::read_u32(addr_slice) as u64;
1157                        refd_addr = ebpf::MM_REGION_SIZE.saturating_add(refd_addr);
1158
1159                        let addr_slice = elf_bytes
1160                            .get_mut(r_offset..r_offset.saturating_add(mem::size_of::<u64>()))
1161                            .ok_or(ElfError::ValueOutOfBounds)?;
1162                        LittleEndian::write_u64(addr_slice, refd_addr);
1163                    }
1164                }
1165                Some(BpfRelocationType::R_Bpf_64_32) => {
1166                    // The .text section has an unresolved call to symbol instruction
1167                    // Hash the symbol name and stick it into the call instruction's imm
1168                    // field.  Later that hash will be used to look up the function location.
1169
1170                    // Offset of the immediate field
1171                    let imm_offset = r_offset.saturating_add(BYTE_OFFSET_IMMEDIATE);
1172
1173                    let symbol = elf
1174                        .dynamic_symbol_table()
1175                        .and_then(|table| table.get(relocation.r_sym() as usize).cloned())
1176                        .ok_or_else(|| ElfError::UnknownSymbol(relocation.r_sym() as usize))?;
1177
1178                    let name = elf
1179                        .dynamic_symbol_name(symbol.st_name as Elf64Word)
1180                        .map_err(|_| ElfError::UnknownSymbol(symbol.st_name as usize))?;
1181
1182                    // If the symbol is defined, this is a bpf-to-bpf call
1183                    let key = if symbol.is_function() && symbol.st_value != 0 {
1184                        if !text_section.vm_range().contains(&symbol.st_value) {
1185                            return Err(ElfError::ValueOutOfBounds);
1186                        }
1187                        let target_pc = (symbol.st_value.saturating_sub(text_section.sh_addr)
1188                            as usize)
1189                            .checked_div(ebpf::INSN_SIZE)
1190                            .unwrap_or_default();
1191                        function_registry
1192                            .register_function_hashed_legacy(loader, true, name, target_pc)?
1193                    } else {
1194                        // Else it's a syscall
1195                        let hash = *syscall_cache
1196                            .entry(symbol.st_name)
1197                            .or_insert_with(|| ebpf::hash_symbol_name(name));
1198                        if config.reject_broken_elfs
1199                            && loader.get_function_registry().lookup_by_key(hash).is_none()
1200                        {
1201                            return Err(ElfError::UnresolvedSymbol(
1202                                String::from_utf8_lossy(name).to_string(),
1203                                r_offset.checked_div(ebpf::INSN_SIZE).unwrap_or(0),
1204                                r_offset,
1205                            ));
1206                        }
1207                        hash
1208                    };
1209
1210                    let checked_slice = elf_bytes
1211                        .get_mut(imm_offset..imm_offset.saturating_add(BYTE_LENGTH_IMMEDIATE))
1212                        .ok_or(ElfError::ValueOutOfBounds)?;
1213                    LittleEndian::write_u32(checked_slice, key);
1214                }
1215                _ => return Err(ElfError::UnknownRelocation(relocation.r_type())),
1216            }
1217        }
1218
1219        if config.enable_symbol_and_section_labels {
1220            // Register all known function names from the symbol table
1221            for symbol in elf.symbol_table().ok().flatten().unwrap_or_default().iter() {
1222                if symbol.st_info & 0xEF != 0x02 {
1223                    continue;
1224                }
1225                if !text_section.vm_range().contains(&symbol.st_value) {
1226                    return Err(ElfError::ValueOutOfBounds);
1227                }
1228                let target_pc = (symbol.st_value.saturating_sub(text_section.sh_addr) as usize)
1229                    .checked_div(ebpf::INSN_SIZE)
1230                    .unwrap_or_default();
1231                let name = elf
1232                    .symbol_name(symbol.st_name as Elf64Word)
1233                    .map_err(|_| ElfError::UnknownSymbol(symbol.st_name as usize))?;
1234                function_registry.register_function_hashed_legacy(loader, true, name, target_pc)?;
1235            }
1236        }
1237
1238        Ok(())
1239    }
1240
1241    #[allow(dead_code)]
1242    fn dump_data(name: &str, prog: &[u8]) {
1243        let mut eight_bytes: Vec<u8> = Vec::new();
1244        println!("{name}");
1245        for i in prog.iter() {
1246            if eight_bytes.len() >= 7 {
1247                println!("{eight_bytes:02X?}");
1248                eight_bytes.clear();
1249            } else {
1250                eight_bytes.push(*i);
1251            }
1252        }
1253    }
1254}
1255
1256/// Creates a [MemoryRegion] for the given [Section]
1257pub fn get_ro_region(ro_section: &Section, elf: &[u8]) -> MemoryRegion {
1258    let (offset, ro_data) = match ro_section {
1259        Section::Owned(offset, data) => (*offset, data.as_slice()),
1260        Section::Borrowed(offset, byte_range) => (*offset, &elf[byte_range.clone()]),
1261    };
1262
1263    // If offset > 0, the region will start at ebpf::MM_REGION_SIZE * 1 + the offset of
1264    // the first read only byte. [ebpf::MM_REGION_SIZE * 1, ebpf::MM_REGION_SIZE * 1 + offset)
1265    // will be unmappable, see MemoryRegion::vm_to_host.
1266    MemoryRegion::new_readonly(ro_data, offset as u64)
1267}