Skip to main content

wazabin_binary/
elf.rs

1use elf::{
2    ElfBytes,
3    abi::{
4        DT_NEEDED, EM_386, EM_X86_64, ET_CORE, ET_DYN, ET_EXEC, ET_REL, PF_W, PF_X, PT_LOAD,
5        PT_PHDR, PT_TLS, R_X86_64_GLOB_DAT, R_X86_64_JUMP_SLOT, SHT_DYNSYM, SHT_REL, SHT_RELA,
6        SHT_SYMTAB, STT_FUNC,
7    },
8    endian::AnyEndian,
9};
10
11use crate::Arch;
12
13use crate::BinaryFormat;
14
15/// A known function start address extracted from an ELF symbol table.
16#[derive(Debug, Clone)]
17pub struct FunctionSymbol {
18    /// Virtual address of the function.
19    pub address: u64,
20    /// Name from the string table, if present.
21    pub name: Option<String>,
22    /// Whether this is an external (imported) function stub (e.g. a PLT thunk).
23    pub is_external: bool,
24    /// For an external stub, the shared library its symbol-version requirement
25    /// (`.gnu.version_r`) names, e.g. `libc.so.6`. `None` for unversioned
26    /// imports (the format does not tie those to a specific `DT_NEEDED` entry).
27    pub library: Option<String>,
28}
29
30/// A resolver slot for an imported function.
31#[derive(Debug, Clone)]
32pub struct ImportSymbol {
33    /// Virtual address of the GOT / PLT relocation slot.
34    pub address: u64,
35    /// Imported function name.
36    pub name: String,
37    /// The shared library the symbol's version requirement (`.gnu.version_r`)
38    /// names, e.g. `libc.so.6`. `None` for unversioned imports.
39    pub library: Option<String>,
40}
41
42/// Results of the ELF analysis passes.
43#[derive(Debug, Clone)]
44pub struct ElfAnalysis {
45    /// The binary entry point (`e_entry` from the ELF header).
46    pub entrypoint: u64,
47    /// Function start addresses discovered via `.symtab` / `.dynsym`.
48    pub known_functions: Vec<FunctionSymbol>,
49    /// Imported functions keyed by resolver slot address.
50    pub imported_symbols: Vec<ImportSymbol>,
51}
52
53#[derive(Debug, Clone)]
54pub struct LoadSegment {
55    pub start: u64,
56    pub mem_size: u64,
57    pub data: Vec<u8>,
58    /// Whether the segment is executable (`PF_X` set in `p_flags`).
59    pub executable: bool,
60    /// Whether the segment is writable (`PF_W` set in `p_flags`).
61    pub writable: bool,
62    /// The segment's alignment (`p_align`); zero or one means none.
63    pub align: u64,
64}
65
66/// The file type from `e_type`: what kind of object this is, and so whether
67/// a loader may place it where it likes.
68#[derive(Debug, Clone, Copy, PartialEq, Eq)]
69pub enum ElfKind {
70    /// `ET_EXEC`: linked to run at its segments' addresses.
71    Executable,
72    /// `ET_DYN`: a shared object or a position-independent executable; the
73    /// loader chooses the base and adds it to every address.
74    SharedObject,
75    /// `ET_REL`: an unlinked object file.
76    Relocatable,
77    /// `ET_CORE`: a core dump.
78    Core,
79    /// Anything else, with the raw `e_type`.
80    Other(u16),
81}
82
83impl ElfKind {
84    fn from_e_type(value: u16) -> Self {
85        match value {
86            ET_EXEC => ElfKind::Executable,
87            ET_DYN => ElfKind::SharedObject,
88            ET_REL => ElfKind::Relocatable,
89            ET_CORE => ElfKind::Core,
90            other => ElfKind::Other(other),
91        }
92    }
93}
94
95/// Where the program header table is, in the file and, when a segment
96/// covers it, in memory. A loader passes the virtual address to the program
97/// as `AT_PHDR`; libc startup and the dynamic linker walk the table from
98/// there.
99#[derive(Debug, Clone, Copy, PartialEq, Eq)]
100pub struct ProgramHeaderTable {
101    /// `e_phoff`.
102    pub offset: u64,
103    /// `e_phentsize`.
104    pub entry_size: u16,
105    /// `e_phnum`.
106    pub count: u16,
107    /// The table's unrelocated virtual address: what `PT_PHDR` names, else
108    /// the address inside the `PT_LOAD` segment whose file bytes cover
109    /// `offset`. `None` when no segment maps it.
110    pub vaddr: Option<u64>,
111}
112
113/// The `PT_TLS` segment: the thread-local storage template a runtime copies
114/// for each thread. At most one per file.
115#[derive(Debug, Clone, Copy, PartialEq, Eq)]
116pub struct TlsSegment {
117    /// Unrelocated virtual address of the template.
118    pub vaddr: u64,
119    /// Bytes of the template that come from the file (`.tdata`).
120    pub file_size: u64,
121    /// Total size, the tail being zero-filled (`.tbss`).
122    pub mem_size: u64,
123    /// `p_align`.
124    pub align: u64,
125}
126
127impl LoadSegment {
128    fn end(&self) -> u64 {
129        self.start + self.mem_size
130    }
131
132    fn contains(&self, addr: u64) -> bool {
133        addr >= self.start && addr < self.end()
134    }
135
136    fn byte_at(&self, addr: u64) -> Option<u8> {
137        if !self.contains(addr) {
138            return None;
139        }
140        let offset = (addr - self.start) as usize;
141        Some(self.data.get(offset).copied().unwrap_or(0))
142    }
143
144    fn bytes_at(&self, addr: u64) -> Option<&[u8]> {
145        if !self.contains(addr) {
146            return None;
147        }
148
149        let offset = (addr - self.start) as usize;
150
151        self.data.get(offset..)
152    }
153}
154
155/// A parsed and loaded ELF binary.
156///
157/// Every `PT_LOAD` segment is recorded as a mapped region. File-backed bytes
158/// are preserved per segment, and any `p_memsz > p_filesz` tail is modeled as
159/// implicit zero-fill rather than materialized up front.
160///
161/// [`ElfBinary::analysis`] is populated by two analysis passes run at parse
162/// time:
163/// - **Entrypoint**: `e_entry` from the ELF header.
164/// - **Known functions**: all `STT_FUNC` symbols found in `.symtab` or
165///   `.dynsym`.
166#[derive(Debug, Clone)]
167pub struct ElfBinary {
168    pub load_address: u64,
169    pub segments: Vec<LoadSegment>,
170    pub analysis: ElfAnalysis,
171    pub architecture: Arch,
172    /// Shared-library sonames from the `.dynamic` section's `DT_NEEDED` entries,
173    /// in link order (e.g. `libc.so.6`).
174    pub needed_libraries: Vec<String>,
175    /// The file type.
176    pub kind: ElfKind,
177    /// The program header table's location.
178    pub program_headers: ProgramHeaderTable,
179    /// The thread-local storage template, if the file has one.
180    pub tls: Option<TlsSegment>,
181}
182
183/// Errors that can occur while parsing an ELF binary.
184#[derive(Debug)]
185pub enum ElfError {
186    /// The underlying `elf` crate returned an error.
187    Parse(elf::ParseError),
188    /// The ELF file contains no loadable (`PT_LOAD`) segment.
189    NoLoadableSegment,
190    /// The ELF file header number does not match any arch
191    UnknownArch,
192}
193
194impl std::fmt::Display for ElfError {
195    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
196        match self {
197            ElfError::Parse(e) => write!(f, "ELF parse error: {e}"),
198            ElfError::NoLoadableSegment => write!(f, "ELF contains no PT_LOAD segment"),
199            ElfError::UnknownArch => write!(f, "ELF has an unknown arch"),
200        }
201    }
202}
203
204impl std::error::Error for ElfError {}
205
206impl From<elf::ParseError> for ElfError {
207    fn from(e: elf::ParseError) -> Self {
208        ElfError::Parse(e)
209    }
210}
211
212impl ElfBinary {
213    /// Parse an ELF binary from raw file bytes.
214    ///
215    /// Records every `PT_LOAD` segment as a mapped region, reads the entrypoint
216    /// from the ELF header, and collects all `STT_FUNC` symbols from `.symtab`
217    /// and `.dynsym`.
218    pub fn parse(file_bytes: &[u8]) -> Result<Self, ElfError> {
219        let elf = ElfBytes::<AnyEndian>::minimal_parse(file_bytes)?;
220
221        // --- Load the first PT_LOAD segment ---------------------------------
222        let segments = elf.segments().ok_or(ElfError::NoLoadableSegment)?;
223
224        let mut load_segments: Vec<_> = segments
225            .iter()
226            .filter(|p| p.p_type == PT_LOAD && p.p_memsz > 0)
227            .map(|p| {
228                let offset = p.p_offset as usize;
229                let filesz = p.p_filesz as usize;
230                let data = file_bytes
231                    .get(offset..offset + filesz)
232                    .unwrap_or(&[])
233                    .to_vec();
234                LoadSegment {
235                    start: p.p_vaddr,
236                    mem_size: p.p_memsz,
237                    data,
238                    executable: p.p_flags & PF_X != 0,
239                    writable: p.p_flags & PF_W != 0,
240                    align: p.p_align,
241                }
242            })
243            .collect();
244
245        if load_segments.is_empty() {
246            return Err(ElfError::NoLoadableSegment);
247        }
248        load_segments.sort_by_key(|s| s.start);
249        let load_address = load_segments[0].start;
250
251        // --- Analysis pass 1: entrypoint ------------------------------------
252        let entrypoint = elf.ehdr.e_entry;
253
254        // --- Analysis pass 2: known function starts -------------------------
255        let mut known_functions: Vec<FunctionSymbol> = Vec::new();
256        collect_func_symbols(&elf, SHT_SYMTAB, &mut known_functions);
257        collect_func_symbols(&elf, SHT_DYNSYM, &mut known_functions);
258
259        // --- Analysis pass 3: PLT stub names -----------------------------------
260        collect_plt_symbols(&elf, &mut known_functions);
261
262        // --- Analysis pass 4: GOT / PLT resolver slots -------------------------
263        let mut imported_symbols = Vec::new();
264        collect_import_symbols(&elf, &mut imported_symbols);
265
266        // --- Analysis pass 5: DT_NEEDED shared-library sonames -----------------
267        let needed_libraries = collect_needed_libraries(&elf);
268
269        // --- What a loader needs beyond the segments ---------------------------
270        let kind = ElfKind::from_e_type(elf.ehdr.e_type);
271        let phoff = elf.ehdr.e_phoff;
272        let phdr_vaddr = segments
273            .iter()
274            .find(|p| p.p_type == PT_PHDR)
275            .map(|p| p.p_vaddr)
276            .or_else(|| {
277                segments
278                    .iter()
279                    .filter(|p| p.p_type == PT_LOAD)
280                    .find(|p| phoff >= p.p_offset && phoff < p.p_offset + p.p_filesz)
281                    .map(|p| p.p_vaddr + (phoff - p.p_offset))
282            });
283        let program_headers = ProgramHeaderTable {
284            offset: phoff,
285            entry_size: elf.ehdr.e_phentsize,
286            count: elf.ehdr.e_phnum,
287            vaddr: phdr_vaddr,
288        };
289        let tls = segments
290            .iter()
291            .find(|p| p.p_type == PT_TLS)
292            .map(|p| TlsSegment {
293                vaddr: p.p_vaddr,
294                file_size: p.p_filesz,
295                mem_size: p.p_memsz,
296                align: p.p_align,
297            });
298
299        // Deduplicate by address (symtab may overlap dynsym).
300        known_functions.sort_by_key(|f| f.address);
301        known_functions.dedup_by_key(|f| f.address);
302        imported_symbols.sort_by_key(|f| f.address);
303        imported_symbols.dedup_by_key(|f| f.address);
304
305        Ok(ElfBinary {
306            load_address,
307            segments: load_segments,
308            analysis: ElfAnalysis {
309                entrypoint,
310                known_functions,
311                imported_symbols,
312            },
313            architecture: from_elf_machine(elf.ehdr.e_machine).ok_or(ElfError::UnknownArch)?,
314            needed_libraries,
315            kind,
316            program_headers,
317            tls,
318        })
319    }
320}
321
322/// Collect `DT_NEEDED` shared-library sonames from the `.dynamic` section,
323/// resolving each entry's string-table offset against `.dynstr`. Returns an
324/// empty vector for statically linked binaries (no dynamic section).
325fn collect_needed_libraries(elf: &ElfBytes<AnyEndian>) -> Vec<String> {
326    let mut out = Vec::new();
327    let Some((_symtab, dynstr)) = elf.dynamic_symbol_table().ok().flatten() else {
328        return out;
329    };
330    let Ok(Some(dynamic)) = elf.dynamic() else {
331        return out;
332    };
333    for entry in dynamic.iter() {
334        if entry.d_tag == DT_NEEDED
335            && let Ok(name) = dynstr.get(entry.d_val() as usize)
336            && !name.is_empty()
337        {
338            out.push(name.to_owned());
339        }
340    }
341    out
342}
343
344/// Collect all `STT_FUNC` symbols from the section matching `section_type`
345/// (either `SHT_SYMTAB` or `SHT_DYNSYM`) into `out`.
346fn collect_func_symbols(
347    elf: &ElfBytes<AnyEndian>,
348    section_type: u32,
349    out: &mut Vec<FunctionSymbol>,
350) {
351    if section_type == SHT_SYMTAB {
352        let Ok(Some((symtab, strtab))) = elf.symbol_table() else {
353            return;
354        };
355        for sym in symtab.iter() {
356            if sym.st_symtype() == STT_FUNC && sym.st_value != 0 {
357                let name = strtab.get(sym.st_name as usize).ok().map(str::to_owned);
358                out.push(FunctionSymbol {
359                    address: sym.st_value,
360                    name,
361                    is_external: false,
362                    library: None,
363                });
364            }
365        }
366    } else if section_type == SHT_DYNSYM {
367        let Ok(Some((dynsymtab, dynstrtab))) = elf.dynamic_symbol_table() else {
368            return;
369        };
370        for sym in dynsymtab.iter() {
371            if sym.st_symtype() == STT_FUNC && sym.st_value != 0 {
372                let name = dynstrtab.get(sym.st_name as usize).ok().map(str::to_owned);
373                out.push(FunctionSymbol {
374                    address: sym.st_value,
375                    name,
376                    is_external: false,
377                    library: None,
378                });
379            }
380        }
381    }
382}
383
384/// Collect PLT stub addresses and names from `.rel[a].plt` + `.dynsym`.
385///
386/// Each relocation entry at index `i` (0-based) corresponds to the PLT stub at
387/// `plt_start + (i + 1) * entry_size`. The stub is named after the imported
388/// symbol, not `symbol@plt`, so direct calls render as calls to the external.
389fn collect_plt_symbols(elf: &ElfBytes<AnyEndian>, out: &mut Vec<FunctionSymbol>) {
390    let _ = collect_plt_symbols_inner(elf, out);
391}
392
393fn collect_plt_symbols_inner(
394    elf: &ElfBytes<AnyEndian>,
395    out: &mut Vec<FunctionSymbol>,
396) -> Option<()> {
397    let (shdrs, shstrtab) = match elf.section_headers_with_strtab() {
398        Ok((Some(s), Some(st))) => (s, st),
399        _ => return None,
400    };
401
402    let section_addr = |name: &str| {
403        shdrs
404            .iter()
405            .find(|s| shstrtab.get(s.sh_name as usize).ok() == Some(name))
406            .map(|s| s.sh_addr)
407    };
408
409    let plt_start = section_addr(".plt")?;
410    let plt_entry_size = plt_entry_size(elf.ehdr.e_machine)?;
411    // CET-enabled binaries emit a second, parallel stub table `.plt.sec`: one
412    // 16-byte `endbr64; bnd jmp *GOT` entry per import (no reserved entry 0), in
413    // the same order as `.rela.plt`. Direct `call`s go through `.plt.sec`, while
414    // `.plt` is reached only by the lazy resolver — so when it exists, the import
415    // name belongs on the `.plt.sec` stub the callers reference.
416    let plt_sec_start = section_addr(".plt.sec");
417
418    let (dynsymtab, dynstrtab) = elf.dynamic_symbol_table().ok().flatten()?;
419    // Symbol-version table (`.gnu.version` / `.gnu.version_r`): maps a dynsym
420    // index to the shared library its version requirement names. Absent in
421    // unversioned binaries, in which case imports carry no library.
422    let version_table = elf.symbol_version_table().ok().flatten();
423    let import_library = |sym_idx: u32| {
424        version_table
425            .as_ref()
426            .and_then(|vt| vt.get_requirement(sym_idx as usize).ok().flatten())
427            .map(|req| req.file.to_string())
428    };
429
430    // Import relocations from `.rela.plt` (x86-64 / RELA) or `.rel.plt`
431    // (x86-32 / REL), as `(index, symbol_name, library)` tuples in section order.
432    let mut import_relocs: Vec<(usize, Option<String>, Option<String>)> = Vec::new();
433    if let Some(rela_plt_shdr) = shdrs
434        .iter()
435        .find(|s| shstrtab.get(s.sh_name as usize).ok() == Some(".rela.plt"))
436    {
437        for (i, rela) in elf.section_data_as_relas(&rela_plt_shdr).ok()?.enumerate() {
438            if is_import_relocation(elf.ehdr.e_machine, rela.r_type) {
439                let name = dyn_symbol_name(&dynsymtab, &dynstrtab, rela.r_sym).map(str::to_string);
440                import_relocs.push((i, name, import_library(rela.r_sym)));
441            }
442        }
443    } else if let Some(rel_plt_shdr) = shdrs
444        .iter()
445        .find(|s| shstrtab.get(s.sh_name as usize).ok() == Some(".rel.plt"))
446    {
447        for (i, rel) in elf.section_data_as_rels(&rel_plt_shdr).ok()?.enumerate() {
448            if is_import_relocation(elf.ehdr.e_machine, rel.r_type) {
449                let name = dyn_symbol_name(&dynsymtab, &dynstrtab, rel.r_sym).map(str::to_string);
450                import_relocs.push((i, name, import_library(rel.r_sym)));
451            }
452        }
453    }
454
455    for (i, name, library) in import_relocs {
456        // `.plt` stub for relocation `i` sits after the reserved entry 0.
457        let plt_stub_addr = plt_start + ((i + 1) as u64) * plt_entry_size;
458        match plt_sec_start {
459            // With a `.plt.sec`, that stub bears the import name (callers target
460            // it); the `.plt` stub stays external but nameless, so it is not
461            // mistaken for a pure local — and so its name does not collide with
462            // the `.plt.sec` stub's.
463            Some(sec_start) => {
464                out.push(FunctionSymbol {
465                    address: sec_start + (i as u64) * plt_entry_size,
466                    name,
467                    is_external: true,
468                    library: library.clone(),
469                });
470                out.push(FunctionSymbol {
471                    address: plt_stub_addr,
472                    name: None,
473                    is_external: true,
474                    library,
475                });
476            }
477            None => out.push(FunctionSymbol {
478                address: plt_stub_addr,
479                name,
480                is_external: true,
481                library,
482            }),
483        }
484    }
485
486    Some(())
487}
488
489fn dyn_symbol_name<'a>(
490    dynsymtab: &elf::symbol::SymbolTable<'a, AnyEndian>,
491    dynstrtab: &elf::string_table::StringTable<'a>,
492    sym_idx: u32,
493) -> Option<&'a str> {
494    let sym = dynsymtab.get(sym_idx as usize).ok()?;
495    let name = dynstrtab.get(sym.st_name as usize).ok()?;
496    if name.is_empty() { None } else { Some(name) }
497}
498
499fn is_import_relocation(machine: u16, r_type: u32) -> bool {
500    match machine {
501        EM_386 | EM_X86_64 => r_type == R_X86_64_JUMP_SLOT,
502        _ => false,
503    }
504}
505
506fn plt_entry_size(machine: u16) -> Option<u64> {
507    match machine {
508        EM_386 | EM_X86_64 => Some(16),
509        _ => None,
510    }
511}
512
513fn collect_import_symbols(elf: &ElfBytes<AnyEndian>, out: &mut Vec<ImportSymbol>) {
514    let _ = collect_import_symbols_inner(elf, out);
515}
516
517fn collect_import_symbols_inner(
518    elf: &ElfBytes<AnyEndian>,
519    out: &mut Vec<ImportSymbol>,
520) -> Option<()> {
521    let (shdrs, _) = match elf.section_headers_with_strtab() {
522        Ok((Some(s), _)) => (s, ()),
523        _ => return None,
524    };
525
526    let (dynsymtab, dynstrtab) = elf.dynamic_symbol_table().ok().flatten()?;
527    let version_table = elf.symbol_version_table().ok().flatten();
528    let import_library = |sym_idx: u32| {
529        version_table
530            .as_ref()
531            .and_then(|vt| vt.get_requirement(sym_idx as usize).ok().flatten())
532            .map(|req| req.file.to_string())
533    };
534
535    for shdr in shdrs.iter() {
536        match shdr.sh_type {
537            SHT_RELA => {
538                let relas = elf.section_data_as_relas(&shdr).ok()?;
539                for rela in relas {
540                    if !is_got_import_relocation(rela.r_type) {
541                        continue;
542                    }
543                    let sym = dynsymtab.get(rela.r_sym as usize).ok()?;
544                    let name = dynstrtab.get(sym.st_name as usize).ok()?.to_owned();
545                    out.push(ImportSymbol {
546                        address: rela.r_offset,
547                        name,
548                        library: import_library(rela.r_sym),
549                    });
550                }
551            }
552            SHT_REL => {
553                let rels = elf.section_data_as_rels(&shdr).ok()?;
554                for rel in rels {
555                    if !is_got_import_relocation(rel.r_type) {
556                        continue;
557                    }
558                    let sym = dynsymtab.get(rel.r_sym as usize).ok()?;
559                    let name = dynstrtab.get(sym.st_name as usize).ok()?.to_owned();
560                    out.push(ImportSymbol {
561                        address: rel.r_offset,
562                        name,
563                        library: import_library(rel.r_sym),
564                    });
565                }
566            }
567            _ => {}
568        }
569    }
570
571    Some(())
572}
573
574fn is_got_import_relocation(r_type: u32) -> bool {
575    matches!(r_type, R_X86_64_GLOB_DAT | R_X86_64_JUMP_SLOT)
576}
577
578impl BinaryFormat for ElfBinary {
579    fn load_address(&self) -> u64 {
580        self.load_address
581    }
582
583    fn architecture(&self) -> Arch {
584        self.architecture
585    }
586
587    fn os(&self) -> crate::TargetOs {
588        crate::TargetOs::Linux
589    }
590
591    fn byte_at(&self, addr: u64) -> Option<u8> {
592        self.segments
593            .iter()
594            .find(|segment| segment.contains(addr))
595            .and_then(|segment| segment.byte_at(addr))
596    }
597
598    fn bytes_at(&self, addr: u64) -> Option<&[u8]> {
599        self.segments
600            .iter()
601            .find(|segment| segment.contains(addr))
602            .and_then(|segment| segment.bytes_at(addr))
603    }
604
605    fn is_executable(&self, addr: u64) -> bool {
606        self.segments
607            .iter()
608            .any(|segment| segment.executable && segment.contains(addr))
609    }
610
611    fn segment_bounds(&self, addr: u64) -> Option<(u64, u64)> {
612        self.segments
613            .iter()
614            .find(|segment| segment.contains(addr))
615            .map(|segment| (segment.start, segment.end()))
616    }
617
618    fn is_known_writable(&self, addr: u64) -> bool {
619        self.segments
620            .iter()
621            .any(|segment| segment.writable && segment.contains(addr))
622    }
623
624    /// ELF program headers carry `PF_W`, so a mapped segment without it is
625    /// proven read-only for the lifetime of the process.
626    fn is_known_read_only(&self, addr: u64) -> bool {
627        self.segments
628            .iter()
629            .any(|segment| !segment.writable && segment.contains(addr))
630    }
631
632    fn linked_libraries(&self) -> Vec<String> {
633        self.needed_libraries.clone()
634    }
635
636    fn mapped_regions(&self) -> Vec<(u64, Vec<u8>, bool, bool)> {
637        self.segments
638            .iter()
639            .map(|seg| {
640                // Materialize the `p_memsz > p_filesz` zero-fill tail so reads
641                // line up with `byte_at` (which returns 0 there).
642                let mut bytes = seg.data.clone();
643                bytes.resize(seg.mem_size as usize, 0);
644                (seg.start, bytes, seg.executable, seg.writable)
645            })
646            .collect()
647    }
648
649    fn symbol_name(&self, addr: u64) -> Option<&str> {
650        self.analysis
651            .known_functions
652            .iter()
653            .find(|f| f.address == addr)
654            .and_then(|f| f.name.as_deref())
655            .filter(|name| !name.is_empty())
656            .or_else(|| {
657                (addr == self.analysis.entrypoint && self.is_executable(addr)).then_some("_start")
658            })
659    }
660
661    fn is_external_symbol(&self, addr: u64) -> bool {
662        self.analysis
663            .known_functions
664            .iter()
665            .find(|f| f.address == addr)
666            .map(|f| f.is_external)
667            .unwrap_or(false)
668    }
669
670    fn import_symbol_name(&self, addr: u64) -> Option<&str> {
671        self.analysis
672            .imported_symbols
673            .iter()
674            .find(|f| f.address == addr)
675            .map(|f| f.name.as_str())
676    }
677
678    fn import_library(&self, addr: u64) -> Option<&str> {
679        // Externals are minted either at a PLT stub's address or (for GOT-
680        // indirect calls with no PLT stub) at the relocation slot itself.
681        self.analysis
682            .known_functions
683            .iter()
684            .find(|f| f.address == addr)
685            .and_then(|f| f.library.as_deref())
686            .or_else(|| {
687                self.analysis
688                    .imported_symbols
689                    .iter()
690                    .find(|f| f.address == addr)
691                    .and_then(|f| f.library.as_deref())
692            })
693    }
694
695    /// Entry points are the ELF entrypoint plus all known function starts.
696    fn entry_points(&self) -> Vec<u64> {
697        let mut entries = vec![self.analysis.entrypoint];
698        for f in &self.analysis.known_functions {
699            entries.push(f.address);
700        }
701        entries.sort();
702        entries.dedup();
703        entries
704    }
705
706    /// The ELF entry point (`e_entry` from the header).
707    fn entrypoint(&self) -> Option<u64> {
708        Some(self.analysis.entrypoint)
709    }
710}
711
712pub fn from_elf_machine(value: u16) -> Option<Arch> {
713    match value {
714        1 => Some(Arch::M32),
715        2 => Some(Arch::Sparc),
716        3 => Some(Arch::I386),
717        4 => Some(Arch::M68K),
718        5 => Some(Arch::M88K),
719        7 => Some(Arch::I860),
720        8 => Some(Arch::Mips),
721        9 => Some(Arch::S370),
722        10 => Some(Arch::MipsRs3Le),
723        15 => Some(Arch::PaRisc),
724        17 => Some(Arch::Vpp500),
725        18 => Some(Arch::Sparc32Plus),
726        19 => Some(Arch::I960),
727        20 => Some(Arch::Ppc),
728        21 => Some(Arch::Ppc64),
729        22 => Some(Arch::S390),
730        // 23-35 reserved
731        36 => Some(Arch::V800),
732        37 => Some(Arch::Fr20),
733        38 => Some(Arch::Rh32),
734        39 => Some(Arch::Rce),
735        40 => Some(Arch::Arm),
736        41 => Some(Arch::Alpha),
737        42 => Some(Arch::Sh),
738        43 => Some(Arch::SparcV9),
739        44 => Some(Arch::Tricore),
740        45 => Some(Arch::Arc),
741        46 => Some(Arch::H8300),
742        47 => Some(Arch::H8300H),
743        48 => Some(Arch::H8S),
744        49 => Some(Arch::H8500),
745        50 => Some(Arch::Ia64),
746        51 => Some(Arch::MipsX),
747        52 => Some(Arch::ColdFire),
748        53 => Some(Arch::M68Hc12),
749        54 => Some(Arch::Mma),
750        55 => Some(Arch::Pcp),
751        56 => Some(Arch::Ncpu),
752        57 => Some(Arch::Ndr1),
753        58 => Some(Arch::StarCore),
754        59 => Some(Arch::Me16),
755        60 => Some(Arch::St100),
756        61 => Some(Arch::TinyJ),
757        62 => Some(Arch::X86_64),
758        63 => Some(Arch::Pdsp),
759        64 => Some(Arch::Pdp10),
760        65 => Some(Arch::Pdp11),
761        66 => Some(Arch::Fx66),
762        67 => Some(Arch::St9Plus),
763        68 => Some(Arch::St7),
764        69 => Some(Arch::M68Hc16),
765        70 => Some(Arch::M68Hc11),
766        71 => Some(Arch::M68Hc08),
767        72 => Some(Arch::M68Hc05),
768        73 => Some(Arch::Svx),
769        74 => Some(Arch::St19),
770        75 => Some(Arch::Vax),
771        76 => Some(Arch::Cris),
772        77 => Some(Arch::Javelin),
773        78 => Some(Arch::FirePath),
774        79 => Some(Arch::Zsp),
775        80 => Some(Arch::Mmix),
776        81 => Some(Arch::Huany),
777        82 => Some(Arch::Prism),
778        83 => Some(Arch::Avr),
779        84 => Some(Arch::Fr30),
780        85 => Some(Arch::D10V),
781        86 => Some(Arch::D30V),
782        87 => Some(Arch::V850),
783        88 => Some(Arch::M32R),
784        89 => Some(Arch::Mn10300),
785        90 => Some(Arch::Mn10200),
786        91 => Some(Arch::PicoJava),
787        92 => Some(Arch::OpenRisc),
788        93 => Some(Arch::ArcA5),
789        94 => Some(Arch::Xtensa),
790        95 => Some(Arch::VideoCore),
791        96 => Some(Arch::TmmGpp),
792        97 => Some(Arch::Ns32K),
793        98 => Some(Arch::Tpc),
794        99 => Some(Arch::Snp1K),
795        100 => Some(Arch::St200),
796        106 => Some(Arch::Blackfin),
797        110 => Some(Arch::Unicore),
798        113 => Some(Arch::AlteraNios2),
799        140 => Some(Arch::TIC6000),
800        164 => Some(Arch::Hexagon),
801        167 => Some(Arch::NDS32),
802        183 => Some(Arch::AArch64),
803        188 => Some(Arch::TILEPro),
804        189 => Some(Arch::Microblaze),
805        191 => Some(Arch::TILEGx),
806        195 => Some(Arch::ARCv2),
807        243 => Some(Arch::RISCV),
808        247 => Some(Arch::BPF),
809        252 => Some(Arch::CSKY),
810        258 => Some(Arch::LoongArch),
811        0x5441 => Some(Arch::FRV),
812        _ => None,
813    }
814}