Skip to main content

wazabin_binary/
elf.rs

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