diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a5dbe46..272528e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -3,21 +3,38 @@ name: CI on: pull_request: +# "cargo" default package results in v1.75, which doesn't support v4 lock files +# "cargo test uses lib and bins only, as doc test breaks for some reason +# chmod vmlinuz-`uname -r` as it's 0600 by default +# chmod /dev/kvm to allow non-root QEMU to use it jobs: test: runs-on: ubuntu-latest - container: - image: registry.opensuse.org/home/mkoutny/containers/opensuse_containers_tumbleweed_containers/rapido-ci:latest - env: - KERNEL_INSTALL_MOD_PATH: /usr/ - options: -w /test - volumes: - - ${{ github.workspace }}:/test - steps: - uses: actions/checkout@v4 - - - name: Run selftest + - name: install rust run: | - KERNEL_RELEASE=$(cat /usr/src/linux-obj/x86_64/default/include/config/kernel.release) /test/selftest/selftest.sh - + sudo apt-get update -y + sudo apt-get install -y --no-install-recommends --no-install-suggests cargo-1.85 rustc qemu-system-x86 + - name: cargo test + run: | + cargo-1.85 test --offline --lib --bins + pushd src/cpio + cargo-1.85 test --offline --lib --bins + popd + pushd src/kv-conf/ + cargo-1.85 test --offline --lib --bins + popd + - name: build and setup rapido net + run: | + cargo-1.85 build --offline --release + cp -r net-conf.example/ net-conf + touch rapido.conf + sudo EDITOR=cat ./rapido setup-network -o $USER -b rapido-br -a "192.168.155.10/24" + sudo chmod 0644 /boot/vmlinuz-* + sudo chmod 0666 /dev/kvm + - name: boot VM test + run: | + ./rapido cut -B -x "ping -c3 192.168.155.10 && echo test passed; exit" simple-network + ./target/release/lscpio initrds/myinitrd + ./rapido boot diff --git a/.gitignore b/.gitignore index 1fcce32..0964f41 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,4 @@ initrds/myinitrd *~ *.pid initrds/ +target/ diff --git a/COPYING b/COPYING index 0447f3d..1039f90 100644 --- a/COPYING +++ b/COPYING @@ -1,3 +1,6 @@ +Note: Rust source code (src/, excluding src/third_party) is GPL-2.0 OR +GPL-3.0 licensed (your choice). See per-file license headers. + ---------------------------------------------------------------------- GNU LESSER GENERAL PUBLIC LICENSE diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..e6273de --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,29 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "cpio" +version = "0.1.0" + +[[package]] +name = "crosvm" +version = "0.1.0" + +[[package]] +name = "elf" +version = "0.8.0" + +[[package]] +name = "kv-conf" +version = "0.1.0" + +[[package]] +name = "rapido" +version = "0.1.0" +dependencies = [ + "cpio", + "crosvm", + "elf", + "kv-conf", +] diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..3df9a8a --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,22 @@ +[package] +name = "rapido" +description = "Quickly test Linux kernel changes" +# The following license covers the src directory, excluding third_party. +# SPDX-License-Identifier headers are attached to individual source files. +license = "(GPL-2.0 OR GPL-3.0)" +version = "0.1.0" +edition = "2018" +default-run = "rapido-cut" + +# non-lib source for binaries is carried under src/bin/ + +[dependencies] +# internal +kv-conf = { path = "src/kv-conf" } +cpio = { path = "src/cpio" } + +# minimal command line parameter passing +crosvm = { path = "src/third_party/crosvm" } +# Parse ELF to determine library dependencies. "std" provides file seek API. +elf = { default-features = false, features = ["std"], path = "src/third_party/rust-elf" } +# please avoid adding any more external dependencies diff --git a/cut/fstests_btrfs.sh b/cut/fstests_btrfs.sh index 50078d7..166a12e 100755 --- a/cut/fstests_btrfs.sh +++ b/cut/fstests_btrfs.sh @@ -1,42 +1,42 @@ #!/bin/bash # SPDX-License-Identifier: (LGPL-2.1 OR LGPL-3.0) -# Copyright (C) SUSE LLC 2018-2022, all rights reserved. +# Copyright (C) SUSE S.A. 2018-2025, all rights reserved. RAPIDO_DIR="$(realpath -e ${0%/*})/.." . "${RAPIDO_DIR}/runtime.vars" -_rt_require_dracut_args "$RAPIDO_DIR/autorun/lib/fstests.sh" \ - "$RAPIDO_DIR/autorun/fstests_btrfs.sh" "$@" _rt_require_fstests -req_inst=() +# rapido-cut doesn't support globbing, so do it here. +req_inst=(${FSTESTS_SRC}/ltp/* ${FSTESTS_SRC}/src/* \ + ${FSTESTS_SRC}/src/log-writes/* ${FSTESTS_SRC}/src/aio-dio-regress/*) _rt_require_btrfs_progs req_inst _rt_require_pam_mods req_inst "pam_rootok.so" "pam_limits.so" _rt_human_size_in_b "${FSTESTS_ZRAM_SIZE:-1G}" zram_bytes \ || _fail "failed to calculate memory resources" # need enough memory for five zram devices -_rt_mem_resources_set "$((3072 + (zram_bytes * 5 / 1048576)))M" +mem_rsc="$((3072 + (zram_bytes * 5 / 1048576)))M" -"$DRACUT" --install "tail blockdev ps rmdir resize dd vim grep find df sha256sum \ +PATH="target/release:${PATH}" +rapido-cut \ + --autorun "autorun/lib/fstests.sh autorun/fstests_btrfs.sh $*" \ + --include "dracut.conf.d/.empty /rapido-rsc/mem/${mem_rsc}" \ + --install "ls cat mkdir cp mv rm ln sed readlink sleep \ + umount findmnt dmesg uname \ + tail blockdev ps rmdir resize dd grep find df sha256sum \ strace mkfs mkfs.ext4 e2fsck tune2fs shuf free ip su \ which perl awk bc touch cut chmod true false unlink \ mktemp getfattr setfattr chacl attr killall hexdump sync \ id sort uniq date expr tac diff head dirname seq \ basename tee egrep yes mkswap timeout realpath blkdiscard \ - fstrim fio logger dmsetup chattr lsattr cmp stat \ - dbench /usr/share/dbench/client.txt hostname getconf md5sum \ + fstrim logger dmsetup chattr lsattr cmp stat \ + hostname getconf md5sum \ od wc getfacl setfacl tr xargs sysctl link truncate quota \ repquota setquota quotacheck quotaon pvremove vgremove \ xfs_mkfile xfs_db xfs_io wipefs filefrag losetup \ - chgrp du fgrep pgrep tar rev kill duperemove \ - fsverity keyctl openssl /etc/ssl/openssl.cnf \ - swapon swapoff xfs_freeze fsck ${req_inst[*]} \ - ${FSTESTS_SRC}/ltp/* ${FSTESTS_SRC}/src/* \ - ${FSTESTS_SRC}/src/log-writes/* \ - ${FSTESTS_SRC}/src/aio-dio-regress/*" \ - --include "$FSTESTS_SRC" "$FSTESTS_SRC" \ - --add-drivers "zram lzo lzo-rle dm-snapshot dm-flakey btrfs raid6_pq \ - loop scsi_debug dm-log-writes xxhash_generic ext4 \ - virtio_blk" \ - --modules "base" \ - "${DRACUT_RAPIDO_ARGS[@]}" \ - "$DRACUT_OUT" || _fail "dracut failed" + chgrp du fgrep pgrep tar rev kill \ + swapon swapoff xfs_freeze fsck ${req_inst[*]}" \ + --try-install "resize dbench /usr/share/dbench/client.txt duperemove \ + fsverity keyctl openssl /etc/ssl/openssl.cnf nano fio" \ + --include "$FSTESTS_SRC $FSTESTS_SRC" \ + --kmods "zram lzo lzo_rle dm_snapshot dm_flakey btrfs raid6_pq \ + loop scsi_debug dm_log_writes xxhash_generic ext4 virtio_blk" diff --git a/cut/simple_example.sh b/cut/simple_example.sh index 6c7394a..0211f34 100755 --- a/cut/simple_example.sh +++ b/cut/simple_example.sh @@ -1,40 +1,38 @@ #!/bin/bash # SPDX-License-Identifier: (LGPL-2.1 OR LGPL-3.0) -# Copyright (C) SUSE LLC 2018-2021, all rights reserved. - -RAPIDO_DIR="$(realpath -e ${0%/*})/.." -. "${RAPIDO_DIR}/runtime.vars" +# Copyright (C) SUSE S.A. 2018-2025, all rights reserved. # The job of Rapido cut scripts is to generate a VM image. This is done using -# Dracut with a number of parameters... - -# Call _rt_require_dracut_args() providing script paths that will be included -# and run on VM boot. It exports variables used in the dracut invocation below. -_rt_require_dracut_args "$RAPIDO_DIR/autorun/simple_example.sh" "$@" - -# _rt_require_networking() flags that VMs using this image should have a network -# adapter. Binaries and configuration required for networking are appended to -# DRACUT_RAPIDO_ARGS. -#_rt_require_networking +# rapido-cut with a number of parameters... -# VMs are booted with 2 vCPUs and 512M RAM by default. These defaults can be -# changed, e.g. 1 vCPU + 1G RAM could be specified via: -#_rt_cpu_resources_set 1 -#_rt_mem_resources_set 1G +# --autorun specifies script paths that will be installed and run (in order) on +# VM boot. # --install provides a list of binaries that should be included in the VM image. # Dracut will resolve shared object dependencies and add them automatically. -# --include copies a specific file or directory to the given image destination. - -# --add-drivers provides a list of kernel modules, which will be obtained from -# the rapido.conf KERNEL_INSTALL_MOD_PATH - -# --modules provides a list of *Dracut* modules. See Dracut documentation for -# details -"$DRACUT" \ - --install "resize ps rmdir dd mkfs.xfs" \ - --add-drivers "zram lzo lzo-rle" \ - --modules "base" \ - "${DRACUT_RAPIDO_ARGS[@]}" \ - "$DRACUT_OUT" || _fail "dracut failed" +# --include copies a specific file or directory-tree to the given image +# destination. --try-install is similar but won't abort if missing. + +# --kmods provides a list of kernel modules, which will be obtained from +# the rapido.conf KERNEL_INSTALL_MOD_PATH directory, or host kernel modules +# directory if unset. +PATH="target/release:${PATH}" +rapido-cut \ + --autorun "autorun/simple_example.sh $*" \ + --install "ls cat sleep ps rmdir dd mkfs.xfs" \ + --try-install "resize" \ + --kmods "zram lzo lzo_rle" + +# rapido-cut writes the initramfs image to the rapido.conf DRACUT_OUT specified +# path, or an explicit path provided via --output parameter. + +# rapido-vm boots images with 2 vCPUs and 512M RAM by default. These +# defaults can be changed, e.g. 1 vCPU + 1G RAM could be specified via: +#--include "dracut.conf.d/.empty /rapido-rsc/cpu/1" +#--include "dracut.conf.d/.empty /rapido-rsc/mem/1G" + +# rapido VMs are not networked by default. To add networking configuration +# (see rapido.conf NET_CONF) and systemd-network dependencies to the image, +# a manifest/net.fest file is provided as a convenience. It can be used via: +#--manifest net.fest diff --git a/cut/simple_network.sh b/cut/simple_network.sh index 0836d09..e4799d5 100755 --- a/cut/simple_network.sh +++ b/cut/simple_network.sh @@ -1,17 +1,12 @@ #!/bin/bash # SPDX-License-Identifier: (LGPL-2.1 OR LGPL-3.0) -# Copyright (C) SUSE LLC 2022, all rights reserved. +# Copyright (C) SUSE S.A. 2022-2025, all rights reserved. -RAPIDO_DIR="$(realpath -e ${0%/*})/.." -. "${RAPIDO_DIR}/runtime.vars" - -_rt_require_dracut_args "$RAPIDO_DIR/autorun/simple_network.sh" "$@" -_rt_require_networking -_rt_cpu_resources_set "1" -_rt_mem_resources_set "512M" - -"$DRACUT" \ - --install "resize ps nc hostname" \ - --modules "base" \ - "${DRACUT_RAPIDO_ARGS[@]}" \ - "$DRACUT_OUT" || _fail "dracut failed" +PATH="target/release:${PATH}" +rapido-cut \ + --autorun "autorun/simple_network.sh $*" \ + --install "ps nc hostname cat ls" \ + --try-install "resize" \ + --manifest net.fest \ + --include "dracut.conf.d/.empty /rapido-rsc/cpu/1" \ + --include "dracut.conf.d/.empty /rapido-rsc/mem/512M" diff --git a/manifest/net.fest b/manifest/net.fest new file mode 100644 index 0000000..f4b7d40 --- /dev/null +++ b/manifest/net.fest @@ -0,0 +1,12 @@ +install="udevadm systemd-udevd systemd-networkd systemd-networkd-wait-online ip + ping" + +# the /rapido-rsc/net destination path is special in that it tells +# rapido-vm to also assign tap devices to the VM +include="${VM_NET_CONF} /rapido-rsc/net" + +# rapido-init includes logic to bring up network. +# we could split it out and do something like: +#autorun="rapido-init-network" + +kmods="virtio_net af_packet" diff --git a/rapido b/rapido index 09fa0ba..e1b92f1 100755 --- a/rapido +++ b/rapido @@ -21,13 +21,14 @@ rapido_setup_network() # vm is an alias for boot rapido_vm() { - ${RAPIDO_DIR}/vm.sh + rapido_boot } short_help["boot"]="Boot previously prepared test" rapido_boot() { - ${RAPIDO_DIR}/vm.sh + PATH="target/release:${PATH}" + rapido-vm } cut_usage() @@ -98,7 +99,7 @@ rapido_cut() local cut_status=$? [ $cut_status -ne 0 ] && exit $cut_status [ -n "$boot_img" ] || exit 0 - "${RAPIDO_DIR}/vm.sh" + rapido_boot } short_help["list"]="List testcases" diff --git a/runtime.vars b/runtime.vars index 89a233a..923e00c 100644 --- a/runtime.vars +++ b/runtime.vars @@ -280,11 +280,12 @@ _rt_require_dracut_args() { # destination filename. local init_src=$1 init_dst kver kmods=() - # Specify core init scripts responsible for starting autorun. - # The Dracut "cmdline" hook is executed prior to root parameter parsing. + # Specify core init responsible for starting autorun. local env_src="$RAPIDO_DIR/vm_autorun.env" - local rinit_src="$RAPIDO_DIR/autorun/00-rapido-init.sh" - local rinit_dst="/lib/dracut/hooks/cmdline/00-rapido-init.sh" + local rinit_src="$RAPIDO_DIR/target/release/rapido-init" + [[ -f $rinit_src ]] \ + || _fail "$rinit_src missing. Build via: cargo build --release" + local rinit_dst="/rdinit" local conf_src="$RAPIDO_CONF" [[ -f $RAPIDO_CONF ]] || conf_src="${RAPIDO_DIR}/dracut.conf.d/.empty" DRACUT_RAPIDO_ARGS+=(--include "$conf_src" /rapido.conf \ diff --git a/selftest/test/002 b/selftest/test/002 index 897763f..a973e80 100755 --- a/selftest/test/002 +++ b/selftest/test/002 @@ -8,17 +8,7 @@ set timeout 60 spawn ./rapido cut -B simple-example expect { timeout {exit 1} - "*** Creating initramfs image file" -} -expect { - timeout {exit 1} - eof -} -# confirm that image was written at DRACUT_OUT path -spawn lsinitrd $::env(RAPIDO_SELFTEST_TMPDIR)/myinitrd -expect { - timeout {exit 1} - "Version: dracut" + "myinitrd written" } expect { timeout {exit 1} diff --git a/src/bin/dracut-cpio.rs b/src/bin/dracut-cpio.rs new file mode 100644 index 0000000..c7fb56a --- /dev/null +++ b/src/bin/dracut-cpio.rs @@ -0,0 +1,1190 @@ +// SPDX-License-Identifier: (GPL-2.0 OR GPL-3.0) +// Copyright (C) 2021 SUSE LLC +use std::convert::TryInto; +use std::env; +use std::ffi::OsStr; +use std::fs; +use std::io; +use std::io::prelude::*; +use std::os::unix::ffi::OsStrExt; +use std::path::{Path, PathBuf}; + +use crosvm::argument::{self, Argument}; + +const LIST_SEPARATOR: u8 = b'\n'; + +fn archive_loop( + mut reader: R, + mut writer: W, + props: &cpio::ArchiveProperties, +) -> io::Result { + if props.data_align > 0 && (props.initial_data_off + u64::from(props.data_align)) % 4 != 0 { + // must satisfy both data_align and cpio 4-byte padding alignment + return Err(io::Error::new( + io::ErrorKind::InvalidInput, + "data alignment must be a multiple of 4", + )); + } + + let mut state = cpio::ArchiveState::new(props); + loop { + let mut linebuf: Vec = Vec::new(); + let mut r = reader.by_ref().take(cpio::PATH_MAX); + match r.read_until(LIST_SEPARATOR, &mut linebuf) { + Ok(l) => { + if l == 0 { + break; // EOF + } + if l >= cpio::PATH_MAX.try_into().unwrap() { + return Err(io::Error::new(io::ErrorKind::InvalidInput, "path too long")); + } + } + Err(e) => { + println!("read_until() failed: {}", e); + return Err(e); + } + }; + + // trim separator. len > 0 already checked. + let last_byte = linebuf.last().unwrap(); + if *last_byte == LIST_SEPARATOR { + linebuf.pop().unwrap(); + if linebuf.len() == 0 { + continue; + } + } else { + println!( + "\'{:0x}\' ending not separator \'{:0x}\' terminated", + last_byte, LIST_SEPARATOR + ); + } + + let linestr = OsStr::from_bytes(linebuf.as_slice()); + let path = Path::new(linestr); + let amd = match fs::symlink_metadata(path) { + Err(e) => { + println!("failed to get metadata for {}: {}", path.display(), e); + return Err(e); + } + Ok(md) => cpio::ArchiveMd::from(&state, &md)?, + }; + match amd.mode & cpio::S_IFMT { + cpio::S_IFREG => { + let f = fs::OpenOptions::new().read(true).open(&path)?; + cpio::archive_file(&mut state, path, &amd, &f, &mut writer)?; + }, + cpio::S_IFLNK => { + let tgt = fs::read_link(path)?; + cpio::archive_symlink(&mut state, path, &amd, &tgt, &mut writer)?; + }, + _ => { + cpio::archive_path(&mut state, path, &amd, &mut writer)?; + }, + }; + } + let mut final_off = cpio::archive_trailer(&mut state, &mut writer)?; + + // GNU cpio pads the end of an archive out to blocklen with zeros + let block_padlen = cpio::archive_padlen(final_off, 512); + if block_padlen > 0 { + let z = vec![0u8; block_padlen.try_into().unwrap()]; + writer.write_all(&z)?; + final_off += block_padlen; + } + writer.flush()?; + + Ok(final_off) +} + +fn params_usage(params: &[Argument]) { + argument::print_help("dracut-cpio", "OUTPUT", params); + println!("\nExample: find fs-tree/ | dracut-cpio archive.cpio\n"); +} + +fn params_process( + props: &mut cpio::ArchiveProperties, +) -> argument::Result<(PathBuf, bool)> { + // If OUTPUT file exists, then zero-truncate it instead of appending. The + // default append behaviour chains archives back-to-back, i.e. multiple + // archives will be separated by a TRAILER and 512-byte padding. + // See Linux's Documentation/driver-api/early-userspace/buffer-format.rst + // for details on how chained initramfs archives are handled. + let mut truncate_existing = false; + let params = &[ + Argument::positional("OUTPUT", "Write cpio archive to this file path."), + Argument::value( + "data-align", + "ALIGNMENT", + "Attempt to pad archive to achieve ALIGNMENT for file data.", + ), + Argument::short_flag( + '0', + "null", + "Expect null delimeters in stdin filename list instead of newline.", + ), + Argument::value( + "mtime", + "EPOCH", + "Use EPOCH for archived mtime instead of filesystem reported values.", + ), + Argument::value( + "owner", + "UID:GID", + "Use UID and GID instead of filesystem reported owner values.", + ), + Argument::flag( + "truncate-existing", + "Truncate and overwrite any existing OUTPUT file, instead of appending.", + ), + Argument::short_flag('h', "help", "Print help message."), + ]; + + let mut positional_args = 0; + let args = env::args().skip(1); // skip binary name + let match_res = argument::set_arguments(args, params, |name, value| { + match name { + "" => positional_args += 1, + "data-align" => { + let v: u32 = value + .unwrap() + .parse() + .map_err(|_| argument::Error::InvalidValue { + value: value.unwrap().to_owned(), + expected: String::from("data-align must be an integer"), + })?; + if v > props.namesize_max { + println!( + concat!( + "Requested data-align {} larger than namesize maximum {}.", + " This will likely result in misalignment." + ), + v, props.namesize_max + ); + } + props.data_align = v; + } + "mtime" => { + let v: u32 = value + .unwrap() + .parse() + .map_err(|_| argument::Error::InvalidValue { + value: value.unwrap().to_owned(), + expected: String::from("mtime must be an integer"), + })?; + props.fixed_mtime = Some(v); + } + "owner" => { + let ugv_parsed: argument::Result> = value + .unwrap() + .split(':') + .map(|id| { + id.parse().map_err(|_| argument::Error::InvalidValue { + value: id.to_owned(), + expected: String::from("uid/gid must be an integer"), + }) + }) + .collect(); + + let ugv_parsed = ugv_parsed?; + if ugv_parsed.len() != 2 { + return Err(argument::Error::InvalidValue { + value: value.unwrap().to_owned(), + expected: String::from("owner must be UID:GID"), + }); + } + props.fixed_uid = Some(ugv_parsed[0]); + props.fixed_gid = Some(ugv_parsed[1]); + } + "truncate-existing" => truncate_existing = true, + "help" => return Err(argument::Error::PrintHelp), + _ => unreachable!(), + }; + Ok(()) + }); + + match match_res { + Ok(_) => { + if positional_args != 1 { + params_usage(params); + return Err(argument::Error::ExpectedArgument( + "one OUTPUT parameter required".to_string(), + )); + } + } + Err(e) => { + params_usage(params); + return Err(e); + } + } + + let last_arg = env::args_os().last().unwrap(); + Ok((PathBuf::from(&last_arg), truncate_existing)) +} + +fn main() -> io::Result<()> { + let mut props = cpio::ArchiveProperties::default(); + let (output_path, truncate_existing) = match params_process(&mut props) { + Ok(p) => p, + Err(argument::Error::PrintHelp) => return Ok(()), + Err(e) => return Err(io::Error::new(io::ErrorKind::InvalidInput, e.to_string())), + }; + + let mut f = fs::OpenOptions::new() + .read(false) + .write(true) + .create(true) + .truncate(truncate_existing) + .open(&output_path)?; + if !truncate_existing { + props.initial_data_off = f.seek(io::SeekFrom::End(0))?; + } + let mut writer = io::BufWriter::new(f); + + let stdin = io::stdin(); + let mut reader = io::BufReader::new(stdin); + + let _wrote = archive_loop(&mut reader, &mut writer, &props)?; + + if props.initial_data_off > 0 { + println!( + "appended {} bytes to archive {} at offset {}", + _wrote, + output_path.display(), + props.initial_data_off + ); + } else { + println!( + "wrote {} bytes to archive {}", + _wrote, + output_path.display() + ); + } + + Ok(()) +} + +#[cfg(test)] +mod tests { + use super::*; + use std::cmp; + use std::os::unix::fs as unixfs; + use std::os::unix::fs::MetadataExt; + use std::path::{self, PathBuf}; + use std::process::{Command, Stdio}; + + pub static TEST_LOCK: std::sync::Mutex = std::sync::Mutex::new(0); + + struct TempWorkDir<'a> { + // Hold a mutex alongside directory change, to avoid failures due to + // multi-threaded "cargo test". + cwd_lock: std::sync::MutexGuard<'a, u32>, + prev_dir: PathBuf, + parent_tmp_dir: PathBuf, + cleanup_files: Vec, + cleanup_dirs: Vec, + ignore_cleanup: bool, // useful for debugging + } + + impl TempWorkDir<'_> { + // create a temporary directory under CWD and cd into it. + // The directory will be cleaned up when twd goes out of scope. + pub fn new() -> TempWorkDir<'static> { + let mut buf = [0u8; 16]; + let mut s = String::from("cpio-selftest-"); + fs::File::open("/dev/urandom") + .unwrap() + .read_exact(&mut buf) + .unwrap(); + for i in &buf { + s.push_str(&format!("{:02x}", i)); + } + + let mut twd = TempWorkDir { + cwd_lock: TEST_LOCK.lock().unwrap_or_else(|mut e| { + // another test panicked while holding the lock + **e.get_mut() = 1; + TEST_LOCK.clear_poison(); + e.into_inner() + }), + prev_dir: env::current_dir().unwrap(), + parent_tmp_dir: { + let mut t = env::current_dir().unwrap().clone(); + t.push(s); + println!("parent_tmp_dir: {}", t.display()); + t + }, + cleanup_files: Vec::new(), + cleanup_dirs: Vec::new(), + ignore_cleanup: false, + }; + fs::create_dir(&twd.parent_tmp_dir).unwrap(); + twd.cleanup_dirs.push(twd.parent_tmp_dir.clone()); + env::set_current_dir(&twd.parent_tmp_dir).unwrap(); + + twd + } + + pub fn create_tmp_file(&mut self, name: &str, len_bytes: u64) { + let mut bytes = len_bytes; + let f = fs::File::create(name).unwrap(); + self.cleanup_files.push(PathBuf::from(name)); + let mut writer = io::BufWriter::new(f); + let mut buf = [0u8; 512]; + + for (i, elem) in buf.iter_mut().enumerate() { + *elem = !(i & 0xFF) as u8; + } + + while bytes > 0 { + let this_len = cmp::min(buf.len(), bytes.try_into().unwrap()); + writer.write_all(&buf[0..this_len]).unwrap(); + bytes -= this_len as u64; + } + + writer.flush().unwrap(); + } + + pub fn create_tmp_dir(&mut self, name: &str) { + fs::create_dir(name).unwrap(); + self.cleanup_dirs.push(PathBuf::from(name)); + } + + // execute coreutils mknod NAME TYPE [MAJOR MINOR] + pub fn create_tmp_mknod(&mut self, name: &str, typ: char, + maj_min: Option<(u32, u32)>) { + let t = typ.to_string(); + let proc = match maj_min { + Some(maj_min) => { + let (maj, min) = maj_min; + Command::new("mknod") + .args(&[name, &t, &maj.to_string(), &min.to_string()]) + .spawn() + }, + None => Command::new("mknod").args(&[name, &t]).spawn() + }; + let status = proc.expect("mknod failed to start").wait().unwrap(); + assert!(status.success()); + + self.cleanup_files.push(PathBuf::from(name)); + } + } + + impl Drop for TempWorkDir<'_> { + fn drop(&mut self) { + for f in self.cleanup_files.iter().rev() { + if self.ignore_cleanup { + println!("ignoring cleanup of file {}", f.display()); + continue; + } + println!("cleaning up test file at {}", f.display()); + match fs::remove_file(f) { + Err(e) => println!("file removal failed {}", e), + Ok(_) => {} + }; + } + for f in self.cleanup_dirs.iter().rev() { + if self.ignore_cleanup { + println!("ignoring cleanup of dir {}", f.display()); + continue; + } + println!("cleaning up test dir at {}", f.display()); + match fs::remove_dir(f) { + Err(e) => println!("dir removal failed {}", e), + Ok(_) => {} + }; + } + println!("returning cwd to {}", self.prev_dir.display()); + env::set_current_dir(self.prev_dir.as_path()).unwrap(); + // cwd_lock should be dropped automatically + } + } + + fn gnu_cpio_create(stdinput: &[u8], out: &str) { + let mut proc = Command::new("cpio") + // As of GNU cpio commit 6a94d5e ("New option --ignore-dirnlink"), + // the --reproducible option hardcodes archived directory nlink + // values as 2. Omit it and use the dir.st_nlink value. + .args(&[ + "--quiet", + "-o", + "-H", + "newc", + "--ignore-devno", + "--renumber-inodes", + "-F", + out, + ]) + .stdin(Stdio::piped()) + .spawn() + .expect("GNU cpio failed to start"); + { + let mut stdin = proc.stdin.take().unwrap(); + stdin.write_all(stdinput).expect("Failed to write to stdin"); + } + + let status = proc.wait().unwrap(); + assert!(status.success()); + } + + #[test] + fn test_archive_empty_file() { + let mut twd = TempWorkDir::new(); + twd.create_tmp_file("file.txt", 0); + + gnu_cpio_create("file.txt\n".as_bytes(), "gnu.cpio"); + twd.cleanup_files.push(PathBuf::from("gnu.cpio")); + + // use dracut-cpio to archive file.txt + let f = fs::File::create("dracut.cpio").unwrap(); + let mut writer = io::BufWriter::new(f); + let mut reader = io::BufReader::new("file.txt\n".as_bytes()); + let wrote = archive_loop(&mut reader, &mut writer, &cpio::ArchiveProperties::default()).unwrap(); + twd.cleanup_files.push(PathBuf::from("dracut.cpio")); + assert_eq!(wrote, 512); + + let status = Command::new("diff") + .args(&["gnu.cpio", "dracut.cpio"]) + .status() + .expect("diff failed to start"); + assert!(status.success()); + } + + #[test] + fn test_archive_small_file() { + let mut twd = TempWorkDir::new(); + twd.create_tmp_file("file.txt", 33); + + gnu_cpio_create("file.txt\n".as_bytes(), "gnu.cpio"); + twd.cleanup_files.push(PathBuf::from("gnu.cpio")); + + let f = fs::File::create("dracut.cpio").unwrap(); + let mut writer = io::BufWriter::new(f); + let mut reader = io::BufReader::new("file.txt\n".as_bytes()); + let wrote = archive_loop(&mut reader, &mut writer, &cpio::ArchiveProperties::default()).unwrap(); + twd.cleanup_files.push(PathBuf::from("dracut.cpio")); + assert!(wrote > cpio::NEWC_HDR_LEN * 2 + 33); + + let status = Command::new("diff") + .args(&["gnu.cpio", "dracut.cpio"]) + .status() + .expect("diff failed to start"); + assert!(status.success()); + } + + #[test] + fn test_archive_prefixed_path() { + let mut twd = TempWorkDir::new(); + twd.create_tmp_file("file.txt", 0); + + gnu_cpio_create("./file.txt\n".as_bytes(), "gnu.cpio"); + twd.cleanup_files.push(PathBuf::from("gnu.cpio")); + + let f = fs::File::create("dracut.cpio").unwrap(); + let mut writer = io::BufWriter::new(f); + let mut reader = io::BufReader::new("./file.txt\n".as_bytes()); + let wrote = archive_loop(&mut reader, &mut writer, &cpio::ArchiveProperties::default()).unwrap(); + twd.cleanup_files.push(PathBuf::from("dracut.cpio")); + assert_eq!(wrote, 512); + + let status = Command::new("diff") + .args(&["gnu.cpio", "dracut.cpio"]) + .status() + .expect("diff failed to start"); + assert!(status.success()); + } + + #[test] + fn test_archive_dir() { + let mut twd = TempWorkDir::new(); + twd.create_tmp_dir("dir"); + + gnu_cpio_create("dir\n".as_bytes(), "gnu.cpio"); + twd.cleanup_files.push(PathBuf::from("gnu.cpio")); + + let f = fs::File::create("dracut.cpio").unwrap(); + let mut writer = io::BufWriter::new(f); + let mut reader = io::BufReader::new("dir\n".as_bytes()); + let wrote = archive_loop(&mut reader, &mut writer, &cpio::ArchiveProperties::default()).unwrap(); + twd.cleanup_files.push(PathBuf::from("dracut.cpio")); + assert_eq!(wrote, 512); + + let status = Command::new("diff") + .args(&["gnu.cpio", "dracut.cpio"]) + .status() + .expect("diff failed to start"); + assert!(status.success()); + } + + #[test] + fn test_archive_dir_file() { + let mut twd = TempWorkDir::new(); + twd.create_tmp_dir("dir"); + twd.create_tmp_file("dir/file.txt", 512 * 32); + let file_list: &str = "dir\n\ndir/file.txt\n"; // double separator + + gnu_cpio_create(file_list.as_bytes(), "gnu.cpio"); + twd.cleanup_files.push(PathBuf::from("gnu.cpio")); + + let f = fs::File::create("dracut.cpio").unwrap(); + let mut writer = io::BufWriter::new(f); + let mut reader = io::BufReader::new(file_list.as_bytes()); + let wrote = archive_loop(&mut reader, &mut writer, &cpio::ArchiveProperties::default()).unwrap(); + twd.cleanup_files.push(PathBuf::from("dracut.cpio")); + assert!(wrote > cpio::NEWC_HDR_LEN * 3 + 512 * 32); + + let status = Command::new("diff") + .args(&["gnu.cpio", "dracut.cpio"]) + .status() + .expect("diff failed to start"); + assert!(status.success()); + } + + #[test] + fn test_archive_dot_path() { + let mut twd = TempWorkDir::new(); + twd.create_tmp_dir("dir"); + twd.create_tmp_file("dir/file.txt", 512 * 32); + let file_list: &str = ".\ndir\ndir/file.txt\n"; + + gnu_cpio_create(file_list.as_bytes(), "gnu.cpio"); + twd.cleanup_files.push(PathBuf::from("gnu.cpio")); + + let f = fs::File::create("dracut.cpio").unwrap(); + let mut writer = io::BufWriter::new(f); + let mut reader = io::BufReader::new(file_list.as_bytes()); + let wrote = archive_loop(&mut reader, &mut writer, &cpio::ArchiveProperties::default()).unwrap(); + twd.cleanup_files.push(PathBuf::from("dracut.cpio")); + assert!(wrote > cpio::NEWC_HDR_LEN * 4 + 512 * 32); + + let status = Command::new("diff") + .args(&["gnu.cpio", "dracut.cpio"]) + .status() + .expect("diff failed to start"); + assert!(status.success()); + } + + #[test] + fn test_archive_dot_slash_path() { + let mut twd = TempWorkDir::new(); + twd.create_tmp_dir("dir"); + twd.create_tmp_file("dir/file.txt", 512 * 32); + let file_list: &str = "./\ndir\ndir/file.txt\n"; + + gnu_cpio_create(file_list.as_bytes(), "gnu.cpio"); + twd.cleanup_files.push(PathBuf::from("gnu.cpio")); + + let f = fs::File::create("dracut.cpio").unwrap(); + let mut writer = io::BufWriter::new(f); + let mut reader = io::BufReader::new(file_list.as_bytes()); + let wrote = archive_loop(&mut reader, &mut writer, &cpio::ArchiveProperties::default()).unwrap(); + twd.cleanup_files.push(PathBuf::from("dracut.cpio")); + assert!(wrote > cpio::NEWC_HDR_LEN * 4 + 512 * 32); + + let status = Command::new("diff") + .args(&["gnu.cpio", "dracut.cpio"]) + .status() + .expect("diff failed to start"); + assert!(status.success()); + } + + #[test] + fn test_archive_symlink() { + let mut twd = TempWorkDir::new(); + twd.create_tmp_file("file.txt", 0); + unixfs::symlink("file.txt", "symlink.txt").unwrap(); + twd.cleanup_files.push(PathBuf::from("symlink.txt")); + + gnu_cpio_create("file.txt\nsymlink.txt\n".as_bytes(), "gnu.cpio"); + twd.cleanup_files.push(PathBuf::from("gnu.cpio")); + + let f = fs::File::create("dracut.cpio").unwrap(); + let mut writer = io::BufWriter::new(f); + let mut reader = io::BufReader::new("file.txt\nsymlink.txt\n".as_bytes()); + let wrote = archive_loop(&mut reader, &mut writer, &cpio::ArchiveProperties::default()).unwrap(); + twd.cleanup_files.push(PathBuf::from("dracut.cpio")); + assert_eq!(wrote, 512); + + let status = Command::new("diff") + .args(&["gnu.cpio", "dracut.cpio"]) + .status() + .expect("diff failed to start"); + assert!(status.success()); + } + + #[test] + fn test_archive_fifo() { + let mut twd = TempWorkDir::new(); + + twd.create_tmp_mknod("fifo", 'p', None); + + gnu_cpio_create("fifo\n".as_bytes(), "gnu.cpio"); + twd.cleanup_files.push(PathBuf::from("gnu.cpio")); + + let f = fs::File::create("dracut.cpio").unwrap(); + let mut writer = io::BufWriter::new(f); + let mut reader = io::BufReader::new("fifo\n".as_bytes()); + let wrote = archive_loop(&mut reader, &mut writer, &cpio::ArchiveProperties::default()).unwrap(); + twd.cleanup_files.push(PathBuf::from("dracut.cpio")); + assert_eq!(wrote, 512); + + let status = Command::new("diff") + .args(&["gnu.cpio", "dracut.cpio"]) + .status() + .expect("diff failed to start"); + assert!(status.success()); + } + + #[test] + fn test_archive_char() { + let mut twd = TempWorkDir::new(); + + let gout = path::absolute("gnu.cpio").unwrap(); + let drout = path::absolute("dracut.cpio").unwrap(); + + // cpio.rs now strips '/' prefixes, so cd to root for 'dev/zero' + // twd Drop will bring us back to the original working directory. + env::set_current_dir("/").unwrap(); + + gnu_cpio_create("dev/zero\n".as_bytes(), gout.to_str().unwrap()); + twd.cleanup_files.push(gout); + + let f = fs::File::create(&drout).unwrap(); + let mut writer = io::BufWriter::new(f); + let mut reader = io::Cursor::new("/dev/zero\n".as_bytes()); + let wrote = archive_loop(&mut reader, &mut writer, &cpio::ArchiveProperties::default()).unwrap(); + twd.cleanup_files.push(drout); + assert_eq!(wrote, 512); + + env::set_current_dir(&twd.parent_tmp_dir).unwrap(); + let status = Command::new("diff") + .args(&["gnu.cpio", "dracut.cpio"]) + .status() + .expect("diff failed to start"); + assert!(status.success()); + } + + #[test] + fn test_archive_data_align() { + let mut twd = TempWorkDir::new(); + twd.create_tmp_dir("dir"); + twd.create_tmp_file("dir/file.txt", 1024 * 1024); // 1M + + twd.create_tmp_dir("extractor"); + let f = fs::File::create("extractor/dracut.cpio").unwrap(); + let mut writer = io::BufWriter::new(f); + let mut reader = io::BufReader::new("dir\ndir/file.txt\n".as_bytes()); + // 4k cpio data segment alignment injects zeros after filename nullterm + let wrote = archive_loop( + &mut reader, + &mut writer, + &cpio::ArchiveProperties { + data_align: 4096, + ..cpio::ArchiveProperties::default() + }, + ) + .unwrap(); + twd.cleanup_files + .push(PathBuf::from("extractor/dracut.cpio")); + assert!(wrote > cpio::NEWC_HDR_LEN * 3 + 1024 * 1024); + + // check 4k data segment alignment + let mut proc = Command::new("diff") + .args(&["dir/file.txt", "-"]) + .stdin(Stdio::piped()) + .spawn() + .expect("diff failed to start"); + { + let f = fs::File::open("extractor/dracut.cpio").unwrap(); + let mut reader = io::BufReader::new(f); + reader.seek(io::SeekFrom::Start(4096)).unwrap(); + let mut take = reader.take(1024 * 1024 as u64); + let mut stdin = proc.stdin.take().unwrap(); + let copied = io::copy(&mut take, &mut stdin).unwrap(); + assert_eq!(copied, 1024 * 1024); + } + let status = proc.wait().unwrap(); + assert!(status.success()); + + // confirm that GNU cpio can extract fname-zeroed paths + let status = Command::new("cpio") + .current_dir("extractor") + .args(&["--quiet", "-i", "-H", "newc", "-F", "dracut.cpio"]) + .status() + .expect("GNU cpio failed to start"); + assert!(status.success()); + twd.cleanup_files + .push(PathBuf::from("extractor/dir/file.txt")); + twd.cleanup_dirs.push(PathBuf::from("extractor/dir")); + + let status = Command::new("diff") + .args(&["dir/file.txt", "extractor/dir/file.txt"]) + .status() + .expect("diff failed to start"); + assert!(status.success()); + } + + #[test] + fn test_archive_data_align_off() { + let mut twd = TempWorkDir::new(); + twd.create_tmp_dir("dir1"); + twd.create_tmp_dir("dir2"); + twd.create_tmp_dir("dir3"); + twd.create_tmp_file("dir1/file.txt", 514 * 1024); + + twd.create_tmp_dir("extractor"); + let data_before_cpio = [5u8; 16384 + 4]; + let f = fs::File::create("extractor/dracut.cpio").unwrap(); + twd.cleanup_files + .push(PathBuf::from("extractor/dracut.cpio")); + let mut writer = io::BufWriter::new(f); + writer.write_all(&data_before_cpio).unwrap(); + let mut reader = io::BufReader::new("dir1\ndir2\ndir3\ndir1/file.txt\n".as_bytes()); + let wrote = archive_loop( + &mut reader, + &mut writer, + &cpio::ArchiveProperties { + data_align: 4096, + initial_data_off: data_before_cpio.len() as u64, + ..cpio::ArchiveProperties::default() + }, + ) + .unwrap(); + assert!(wrote > cpio::NEWC_HDR_LEN * 5 + 514 * 1024); + + let mut proc = Command::new("diff") + .args(&["dir1/file.txt", "-"]) + .stdin(Stdio::piped()) + .spawn() + .expect("diff failed to start"); + { + let f = fs::File::open("extractor/dracut.cpio").unwrap(); + let mut reader = io::BufReader::new(f); + reader.seek(io::SeekFrom::Start(16384 + 4096)).unwrap(); + let mut take = reader.take(514 * 1024 as u64); + let mut stdin = proc.stdin.take().unwrap(); + let copied = io::copy(&mut take, &mut stdin).unwrap(); + assert_eq!(copied, 514 * 1024); + } + let status = proc.wait().unwrap(); + assert!(status.success()); + } + + #[test] + fn test_archive_data_align_off_bad() { + let mut twd = TempWorkDir::new(); + twd.create_tmp_file("file.txt", 514 * 1024); + + let data_before_cpio = [5u8; 16384 + 3]; + let f = fs::File::create("dracut.cpio").unwrap(); + twd.cleanup_files.push(PathBuf::from("dracut.cpio")); + let mut writer = io::BufWriter::new(f); + writer.write_all(&data_before_cpio).unwrap(); + let mut reader = io::BufReader::new("file.txt\n".as_bytes()); + let res = archive_loop( + &mut reader, + &mut writer, + &cpio::ArchiveProperties { + data_align: 4096, + initial_data_off: data_before_cpio.len() as u64, + ..cpio::ArchiveProperties::default() + }, + ); + assert!(res.is_err()); + assert_eq!(io::ErrorKind::InvalidInput, res.unwrap_err().kind()); + } + + #[test] + // dracut-cpio now duplicates any hardlink data and assigns unique inode + // numbers, so we're not cpio-output compatible with GNU cpio. Instead, + // just test to confirm that data segments match. + fn test_archive_hardlinks() { + let mut twd = TempWorkDir::new(); + twd.create_tmp_file("file.txt", 512 * 4); + fs::hard_link("file.txt", "link1.txt").unwrap(); + twd.cleanup_files.push(PathBuf::from("link1.txt")); + fs::hard_link("file.txt", "link2.txt").unwrap(); + twd.cleanup_files.push(PathBuf::from("link2.txt")); + twd.create_tmp_file("another.txt", 512 * 4); + let file_list: &str = "file.txt\nanother.txt\nlink1.txt\nlink2.txt\n"; + + twd.create_tmp_dir("gnu"); + twd.create_tmp_dir("dracut"); + + gnu_cpio_create(file_list.as_bytes(), "gnu/gnu.cpio"); + twd.cleanup_files.push(PathBuf::from("gnu/gnu.cpio")); + + let f = fs::File::create("dracut/dracut.cpio").unwrap(); + let mut writer = io::BufWriter::new(f); + let mut reader = io::BufReader::new(file_list.as_bytes()); + let wrote = archive_loop(&mut reader, &mut writer, &cpio::ArchiveProperties::default()).unwrap(); + twd.cleanup_files.push(PathBuf::from("dracut/dracut.cpio")); + assert!(wrote > cpio::NEWC_HDR_LEN * 5 + 512 * 8); + + // extract gnu archive + let status = Command::new("cpio") + .current_dir("gnu") + .args(&["--quiet", "-i", "-H", "newc", "-F", "gnu.cpio"]) + .status() + .expect("GNU cpio failed to start"); + assert!(status.success()); + + // extract dracut archive + let status = Command::new("cpio") + .current_dir("dracut") + .args(&["--quiet", "-i", "-H", "newc", "-F", "dracut.cpio"]) + .status() + .expect("GNU cpio failed to start"); + assert!(status.success()); + + + for f in file_list.split('\n') { + if f == "" { + continue; + } + let g_path = format!("{}/{}", "gnu", &f); + let d_path = format!("{}/{}", "dracut", &f); + + twd.cleanup_files.push(PathBuf::from(&g_path)); + twd.cleanup_files.push(PathBuf::from(&d_path)); + + let status = Command::new("diff") + .args(&[d_path, g_path]) + .status() + .expect("diff failed to start"); + assert!(status.success()); + } + } + + #[test] + fn test_archive_duplicates() { + let mut twd = TempWorkDir::new(); + twd.create_tmp_file("file.txt", 512 * 4); + twd.create_tmp_file("another.txt", 512 * 4); + // file.txt is listed twice + let file_list: &str = "file.txt\nanother.txt\nfile.txt\n"; + + gnu_cpio_create(file_list.as_bytes(), "gnu.cpio"); + twd.cleanup_files.push(PathBuf::from("gnu.cpio")); + + let f = fs::File::create("dracut.cpio").unwrap(); + let mut writer = io::BufWriter::new(f); + let mut reader = io::BufReader::new(file_list.as_bytes()); + let wrote = archive_loop(&mut reader, &mut writer, &cpio::ArchiveProperties::default()).unwrap(); + twd.cleanup_files.push(PathBuf::from("dracut.cpio")); + assert!(wrote > cpio::NEWC_HDR_LEN * 4 + 512 * 12); + + let status = Command::new("diff") + .args(&["gnu.cpio", "dracut.cpio"]) + .status() + .expect("diff failed to start"); + assert!(status.success()); + } + + #[test] + fn test_archive_fixed_mtime() { + let mut twd = TempWorkDir::new(); + twd.create_tmp_file("file1", 33); + twd.create_tmp_file("file2", 55); + let file_list: &str = "file1\nfile2\n"; + + twd.create_tmp_dir("extractor"); + let f = fs::File::create("extractor/dracut.cpio").unwrap(); + let mut writer = io::BufWriter::new(f); + let mut reader = io::BufReader::new(file_list.as_bytes()); + let wrote = archive_loop( + &mut reader, + &mut writer, + &cpio::ArchiveProperties { + fixed_mtime: Some(0), + ..cpio::ArchiveProperties::default() + }, + ) + .unwrap(); + twd.cleanup_files + .push(PathBuf::from("extractor/dracut.cpio")); + assert!(wrote > cpio::NEWC_HDR_LEN * 3 + 33 + 55); + + let status = Command::new("cpio") + .current_dir("extractor") + .args(&[ + "--quiet", + "-i", + "--preserve-modification-time", + "-H", + "newc", + "-F", + "dracut.cpio", + ]) + .status() + .expect("GNU cpio failed to start"); + assert!(status.success()); + twd.cleanup_files.push(PathBuf::from("extractor/file1")); + twd.cleanup_files.push(PathBuf::from("extractor/file2")); + + let md = fs::symlink_metadata("extractor/file1").unwrap(); + assert_eq!(md.mtime(), 0); + let md = fs::symlink_metadata("extractor/file2").unwrap(); + assert_eq!(md.mtime(), 0); + } + + #[test] + fn test_archive_stat_mtime() { + let mut twd = TempWorkDir::new(); + twd.create_tmp_file("file1", 33); + twd.create_tmp_file("file2", 55); + let file_list: &str = "file1\nfile2\n"; + + twd.create_tmp_dir("extractor"); + let f = fs::File::create("extractor/dracut.cpio").unwrap(); + let mut writer = io::BufWriter::new(f); + let mut reader = io::BufReader::new(file_list.as_bytes()); + assert_eq!(cpio::ArchiveProperties::default().fixed_mtime, None); + let wrote = archive_loop(&mut reader, &mut writer, &cpio::ArchiveProperties::default()).unwrap(); + twd.cleanup_files + .push(PathBuf::from("extractor/dracut.cpio")); + assert!(wrote > cpio::NEWC_HDR_LEN * 3 + 33 + 55); + + let status = Command::new("cpio") + .current_dir("extractor") + .args(&[ + "--quiet", + "-i", + "--preserve-modification-time", + "-H", + "newc", + "-F", + "dracut.cpio", + ]) + .status() + .expect("GNU cpio failed to start"); + assert!(status.success()); + twd.cleanup_files.push(PathBuf::from("extractor/file1")); + twd.cleanup_files.push(PathBuf::from("extractor/file2")); + + let src_md = fs::symlink_metadata("file1").unwrap(); + let ex_md = fs::symlink_metadata("extractor/file1").unwrap(); + assert_eq!(src_md.mtime(), ex_md.mtime()); + let src_md = fs::symlink_metadata("file2").unwrap(); + let ex_md = fs::symlink_metadata("extractor/file2").unwrap(); + assert_eq!(src_md.mtime(), ex_md.mtime()); + } + + #[test] + fn test_archive_fixed_owner() { + let mut twd = TempWorkDir::new(); + twd.create_tmp_file("file1", 33); + twd.create_tmp_file("file2", 55); + let file_list: &str = "file1\nfile2\n"; + + let md = fs::symlink_metadata("file1").unwrap(); + // ideally we should check the process euid, but this will do... + if md.uid() != 0 { + println!("SKIPPED: this test requires root"); + return; + } + + twd.create_tmp_dir("extractor"); + let f = fs::File::create("extractor/dracut.cpio").unwrap(); + let mut writer = io::BufWriter::new(f); + let mut reader = io::BufReader::new(file_list.as_bytes()); + let wrote = archive_loop( + &mut reader, + &mut writer, + &cpio::ArchiveProperties { + fixed_uid: Some(65534), + fixed_gid: Some(65534), + ..cpio::ArchiveProperties::default() + }, + ) + .unwrap(); + twd.cleanup_files + .push(PathBuf::from("extractor/dracut.cpio")); + assert!(wrote > cpio::NEWC_HDR_LEN * 3 + 33 + 55); + + let status = Command::new("cpio") + .current_dir("extractor") + .args(&["--quiet", "-i", "-H", "newc", "-F", "dracut.cpio"]) + .status() + .expect("GNU cpio failed to start"); + assert!(status.success()); + twd.cleanup_files.push(PathBuf::from("extractor/file1")); + twd.cleanup_files.push(PathBuf::from("extractor/file2")); + + let md = fs::symlink_metadata("extractor/file1").unwrap(); + assert_eq!(md.uid(), 65534); + assert_eq!(md.gid(), 65534); + let md = fs::symlink_metadata("extractor/file2").unwrap(); + assert_eq!(md.uid(), 65534); + assert_eq!(md.gid(), 65534); + } + + #[test] + fn test_archive_stat_owner() { + let mut twd = TempWorkDir::new(); + twd.create_tmp_file("file1", 33); + twd.create_tmp_file("file2", 55); + let file_list: &str = "file1\nfile2\n"; + + twd.create_tmp_dir("extractor"); + let f = fs::File::create("extractor/dracut.cpio").unwrap(); + let mut writer = io::BufWriter::new(f); + let mut reader = io::BufReader::new(file_list.as_bytes()); + assert_eq!(cpio::ArchiveProperties::default().fixed_uid, None); + assert_eq!(cpio::ArchiveProperties::default().fixed_gid, None); + let wrote = archive_loop(&mut reader, &mut writer, &cpio::ArchiveProperties::default()).unwrap(); + twd.cleanup_files + .push(PathBuf::from("extractor/dracut.cpio")); + assert!(wrote > cpio::NEWC_HDR_LEN * 3 + 33 + 55); + + let status = Command::new("cpio") + .current_dir("extractor") + .args(&["--quiet", "-i", "-H", "newc", "-F", "dracut.cpio"]) + .status() + .expect("GNU cpio failed to start"); + assert!(status.success()); + twd.cleanup_files.push(PathBuf::from("extractor/file1")); + twd.cleanup_files.push(PathBuf::from("extractor/file2")); + + let src_md = fs::symlink_metadata("file1").unwrap(); + let ex_md = fs::symlink_metadata("extractor/file1").unwrap(); + assert_eq!(src_md.uid(), ex_md.uid()); + assert_eq!(src_md.gid(), ex_md.gid()); + let src_md = fs::symlink_metadata("file2").unwrap(); + let ex_md = fs::symlink_metadata("extractor/file2").unwrap(); + assert_eq!(src_md.uid(), ex_md.uid()); + assert_eq!(src_md.gid(), ex_md.gid()); + } + + #[test] + fn test_archive_dev_maj_min() { + let mut twd = TempWorkDir::new(); + twd.create_tmp_file("file1", 0); + + let md = fs::symlink_metadata("file1").unwrap(); + if md.uid() != 0 { + println!("SKIPPED: this test requires root"); + return; + } + + twd.create_tmp_mknod("bdev1", 'b', Some((0x01, 0x01))); + twd.create_tmp_mknod("bdev2", 'b', Some((0x02, 0x100))); + twd.create_tmp_mknod("bdev3", 'b', Some((0x03, 0x1000))); + twd.create_tmp_mknod("bdev4", 'b', Some((0x04, 0x10000))); + twd.create_tmp_mknod("bdev5", 'b', Some((0x100, 0x05))); + twd.create_tmp_mknod("bdev6", 'b', Some((0x100, 0x06))); + let file_list: &str = "file1\nbdev1\nbdev2\nbdev3\nbdev4\nbdev5\nbdev6\n"; + + // create GNU cpio archive + twd.create_tmp_dir("gnucpio_xtr"); + gnu_cpio_create(file_list.as_bytes(), "gnucpio_xtr/gnu.cpio"); + twd.cleanup_files.push(PathBuf::from("gnucpio_xtr/gnu.cpio")); + + // create Dracut cpio archive + twd.create_tmp_dir("dracut_xtr"); + let f = fs::File::create("dracut_xtr/dracut.cpio").unwrap(); + let mut writer = io::BufWriter::new(f); + let mut reader = io::BufReader::new(file_list.as_bytes()); + let wrote = archive_loop( + &mut reader, + &mut writer, + &cpio::ArchiveProperties::default() + ) + .unwrap(); + twd.cleanup_files.push(PathBuf::from("dracut_xtr/dracut.cpio")); + + let file_list_count = file_list.split_terminator('\n').count() as u64; + assert!(wrote >= cpio::NEWC_HDR_LEN * file_list_count + + (file_list.len() as u64)); + + let status = Command::new("cpio") + .current_dir("gnucpio_xtr") + .args(&["--quiet", "-i", "-H", "newc", "-F", "gnu.cpio"]) + .status() + .expect("GNU cpio failed to start"); + assert!(status.success()); + for s in file_list.split_terminator('\n') { + let p = PathBuf::from("gnucpio_xtr/".to_owned() + s); + twd.cleanup_files.push(p); + } + + let status = Command::new("cpio") + .current_dir("dracut_xtr") + .args(&["--quiet", "-i", "-H", "newc", "-F", "dracut.cpio"]) + .status() + .expect("GNU cpio failed to start"); + assert!(status.success()); + for s in file_list.split_terminator('\n') { + let dp = PathBuf::from("dracut_xtr/".to_owned() + s); + twd.cleanup_files.push(dp); + } + + // diff extracted major/minor between dracut and GNU cpio created archives + for s in file_list.split_terminator('\n') { + let gmd = fs::symlink_metadata("gnucpio_xtr/".to_owned() + s).unwrap(); + let dmd = fs::symlink_metadata("dracut_xtr/".to_owned() + s).unwrap(); + print!("{}: cpio extracted dev_t gnu: {:#x}, dracut: {:#x}\n", + s, gmd.rdev(), dmd.rdev()); + assert!(gmd.rdev() == dmd.rdev()); + } + } + + // Inode numbers are unique (for non-hardlinks) within the archive, so + // device ID mapping is unnecessary. Confirm that dracut-cpio behaves like + // GNU cpio --ignore-devno. Check this by archiving the /tmp directory + // alongside a working-directory nested file; despite differing source + // device IDs, the archived major/minor numbers should be zero. + #[test] + fn test_archive_major_minor() { + let mut twd = TempWorkDir::new(); + twd.create_tmp_file("file1", 0); + let fin = path::absolute("file1").unwrap(); + let twd_md = fs::symlink_metadata(&fin).unwrap(); + + let slash_tmp_md = match fs::symlink_metadata(PathBuf::from("/tmp")) { + Err(_) => { + println!("SKIPPED: this test requires /tmp stat access"); + return; + }, + Ok(md) => md, + }; + + if twd_md.dev() == slash_tmp_md.dev() { + println!("SKIPPED: this test requires a unique /tmp device ID"); + return; + } + + let gout = path::absolute("gnu.cpio").unwrap(); + let drout = path::absolute("dracut.cpio").unwrap(); + + // cpio.rs now strips '/' prefixes, so cd to root and use stripped path + // for GNU. twd Drop brings us back to the original working directory. + env::set_current_dir("/").unwrap(); + + let file_list = format!( + "{}\ntmp\n", + fin.strip_prefix("/").unwrap().to_str().unwrap() + ); + + gnu_cpio_create(file_list.as_bytes(), gout.to_str().unwrap()); + twd.cleanup_files.push(gout); + + let f = fs::File::create(&drout).unwrap(); + let mut writer = io::BufWriter::new(f); + let mut reader = io::Cursor::new(file_list.as_bytes()); + let wrote = archive_loop( + &mut reader, + &mut writer, + &cpio::ArchiveProperties::default() + ) + .unwrap(); + twd.cleanup_files.push(drout); + assert!(wrote > cpio::NEWC_HDR_LEN); + + env::set_current_dir(&twd.parent_tmp_dir).unwrap(); + let status = Command::new("diff") + .args(&["gnu.cpio", "dracut.cpio"]) + .status() + .expect("diff failed to start"); + assert!(status.success()); + } +} diff --git a/src/bin/kmod/kmod_context.rs b/src/bin/kmod/kmod_context.rs new file mode 100644 index 0000000..59a04ef --- /dev/null +++ b/src/bin/kmod/kmod_context.rs @@ -0,0 +1,940 @@ +// SPDX-License-Identifier: (GPL-2.0 OR GPL-3.0) +// Copyright (C) 2025 SUSE S.A. +use std::collections::HashMap; +use std::fs::File; +use std::io::{self, BufRead, BufReader}; +use std::path::Path; +use std::path::PathBuf; + +// --- Constants --- + +pub const MODULE_DB_FILES: [&str; 15] = [ + "modules.dep", + "modules.dep.bin", + "modules.alias", + "modules.alias.bin", + "modules.softdep", + "modules.weakdep", + "modules.builtin", + "modules.builtin.bin", + "modules.builtin.alias.bin", + "modules.builtin.modinfo", + "modules.symbols", + "modules.symbols.bin", + "modules.order", + "modules.fips", + "modules.devname", +]; + +// --- Module Data Structures --- + +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum ModuleStatus { + Builtin, + LoadableModule, +} + +#[derive(Debug, Clone, PartialEq)] +pub struct KmodModule { + pub status: ModuleStatus, + // rel_path is relative to KmodContext.module_root + pub rel_path: String, + pub hard_deps_paths: Vec, + pub soft_deps_pre: Vec, + pub soft_deps_post: Vec, + pub weak_deps: Vec, +} + +// --- Context and APIs --- + +pub struct KmodContext { + modules_hash: HashMap, + alias_map: HashMap, + pub module_root: PathBuf, +} + +fn read_lines(filename: &Path) -> io::Result>> { + let file = File::open(filename)?; + Ok(BufReader::new(file).lines()) +} + +// extract: from _path: 'kernel/sub/module.ko{.xz,.zst,.gz}' -> name: 'module' +fn extract_module_name(path_str: &str) -> String { + let path = PathBuf::from(path_str); + // file_stem: 'kernel/sub/module.ko{.xz,.zst,.gz}' -> file_stem: 'module{.ko}' + let file_stem = path + .file_stem() + .and_then(|s| s.to_str()) + .unwrap_or(path_str); + + // file_stem_strip_suffix: 'module{.ko}' -> name: 'module' + let base_name = file_stem.strip_suffix(".ko").unwrap_or(file_stem); + // aligns with libkmod: 'kmod_module_get_name' logic. + // name is always normalized (dashes are replaced with underscores). + base_name.replace('-', "_") +} + +impl KmodModule { + pub fn name(&self) -> String { + extract_module_name(&self.rel_path) + } + + pub fn hard_deps(&self) -> Vec { + self.hard_deps_paths.iter().map(|p| extract_module_name(&p)).collect() + } +} + +impl KmodContext { + pub fn new(dirname: &Path) -> Result { + let mut ctx = KmodContext { + modules_hash: HashMap::new(), + alias_map: HashMap::new(), + module_root: PathBuf::from(dirname), + }; + + ctx.load_hard_dependencies() + .map_err(|e| format!("Failed to load modules.dep: {}", e))?; + + ctx.load_soft_dependencies() + .map_err(|e| format!("Failed to load modules.softdep: {}", e))?; + + if let Err(e) = ctx.load_weak_dependencies() { + eprintln!("Old kernel? modules.weakdep load failure ignored: {}", e); + } + + ctx.load_builtin_modules() + .map_err(|e| format!("Failed to load modules.builtin: {}", e))?; + + ctx.load_aliases() + .map_err(|e| format!("Failed to load modules.alias: {}", e))?; + + Ok(ctx) + } + + // Parses **modules.dep** (hard dependencies and module paths). + fn load_hard_dependencies(&mut self) -> io::Result<()> { + let path = self.module_root.join("modules.dep"); + + for line in read_lines(&path)? { + let line = line?; + let parts: Vec<&str> = line.splitn(2, ':').collect(); + if parts.len() != 2 { + continue; + } + + let module_path_str = parts[0].trim(); + let module_name = extract_module_name(module_path_str); + + let dep_paths: Vec = parts[1] + .trim() + .split_whitespace() + .map(|p| p.to_string()) + .collect(); + + match self.modules_hash.insert( + module_name, + KmodModule { + status: ModuleStatus::LoadableModule, + rel_path: String::from(module_path_str), + hard_deps_paths: dep_paths, + soft_deps_pre: Vec::new(), + soft_deps_post: Vec::new(), + weak_deps: Vec::new(), + }, + ) { + // modules.dep and modules.builtin entries should be unique + Some(_) => { + return Err(io::Error::new( + io::ErrorKind::InvalidData, + format!("duplicate entry: {:?}", module_path_str), + )) + } + None => {} + }; + } + + Ok(()) + } + + // Parses **modules.softdep** + fn load_soft_dependencies(&mut self) -> io::Result<()> { + let path = self.module_root.join("modules.softdep"); + + for line in read_lines(&path)? { + let line = line?; + // softdep mod_name pre: pre_mod1 pre_mod2 post: post_mod1 + let parts: Vec<&str> = line.split_whitespace().collect(); + + if parts.len() < 3 || parts[0] != "softdep" { + continue; + } + + let module_name = parts[1].to_string(); + + if let Some(module) = self.modules_hash.get_mut(&module_name) { + let mut current_list = &mut module.soft_deps_pre; + + for &part in parts.iter().skip(2) { + match part { + "pre:" => current_list = &mut module.soft_deps_pre, + "post:" => current_list = &mut module.soft_deps_post, + _ => current_list.push(part.to_string()), + } + } + } + } + + Ok(()) + } + + // Parses **modules.weakdep**. + fn load_weak_dependencies(&mut self) -> io::Result<()> { + let path = self.module_root.join("modules.weakdep"); + + for line in read_lines(&path)? { + let line = line?; + // Format: as per depmod::output_weakdeps() + // weakdep mod_name dep1 + // weakdep mod_name dep2 + let parts: Vec<&str> = line.split_whitespace().collect(); + + if parts.len() != 3 || parts[0] != "weakdep" { + continue; + } + + let module_name = parts[1].to_string(); + let dep_name = parts[2].to_string(); + + // Update the existing module + if let Some(module) = self.modules_hash.get_mut(&module_name) { + module.weak_deps.push(dep_name); + } + } + + Ok(()) + } + + // Parses **modules.builtin**. + fn load_builtin_modules(&mut self) -> io::Result<()> { + let path = self.module_root.join("modules.builtin"); + + for line in read_lines(&path)? { + let line = line?; + let path_str = line.trim(); + + if path_str.is_empty() { + continue; + } + + let module_name = extract_module_name(path_str); + match self.modules_hash.insert( + module_name, + KmodModule { + status: ModuleStatus::Builtin, + rel_path: String::new(), + hard_deps_paths: Vec::new(), + soft_deps_pre: Vec::new(), + soft_deps_post: Vec::new(), + weak_deps: Vec::new(), + }, + ) { + // modules.dep and modules.builtin entries should be unique + Some(_) => { + return Err(io::Error::new( + io::ErrorKind::InvalidData, + format!("duplicate entry: {:?}", path_str), + )) + } + None => {} + }; + } + Ok(()) + } + + fn load_aliases(&mut self) -> io::Result<()> { + let path = self.module_root.join("modules.alias"); + if !path.exists() { + // missing aliases not considered an error + return Ok(()); + } + + for line in read_lines(&path)? { + let line = line?; + let parts: Vec<&str> = line.split_whitespace().collect(); + // Format: alias + if parts.len() >= 3 && parts[0] == "alias" { + let alias = parts[1].to_string(); + let module_name = parts[2].to_string(); + // A -> B + self.alias_map.insert(alias.clone(), module_name.clone()); + } + } + Ok(()) + } + + pub fn find(&self, name: &str) -> Option<&KmodModule> { + if let Some(module) = self.modules_hash.get(name) { + return Some(module); + } + + if let Some(actual_name) = self.alias_map.get(name) { + if let Some(module) = self.modules_hash.get(actual_name) { + return Some(module); + } + } + + // fallback once in case caller requested non-normalized name + let normname = name.replace('-', "_"); + if normname != name { + return self.find(&normname); + } + + None + } +} + +#[cfg(test)] +mod tests { + use super::*; + use std::fs; + const TEST_ROOT_DIR: &str = "target/kmod_test_root"; + + fn setup_test_dir(test_name: &str) -> PathBuf { + let root_path = PathBuf::from(TEST_ROOT_DIR).join(test_name); + if root_path.exists() { + fs::remove_dir_all(&root_path).unwrap(); + } + fs::create_dir_all(&root_path).unwrap(); + root_path + } + + fn cleanup_test_dir(root_path: &PathBuf) { + if root_path.exists() { + fs::remove_dir_all(root_path).unwrap(); + } + } + + fn write_test_file(base_path: &Path, filename: &str, content: &str) -> PathBuf { + let path = base_path.join(filename); + if let Some(parent) = path.parent() { + fs::create_dir_all(parent).unwrap(); + } + fs::write(&path, content).unwrap(); + path + } + + fn set_context(root_path: &Path) -> KmodContext { + KmodContext { + modules_hash: HashMap::new(), + alias_map: HashMap::new(), + module_root: root_path.to_path_buf(), + } + } + + #[test] + fn test_new_with_valid_dir() { + let root_path = setup_test_dir("test_new_success"); + write_test_file(&root_path, "modules.dep", ""); + write_test_file(&root_path, "modules.softdep", ""); + write_test_file(&root_path, "modules.weakdep", ""); + write_test_file(&root_path, "modules.builtin", ""); + + match KmodContext::new(&root_path) { + Ok(context) => { + assert_eq!(context.module_root, root_path, "Module root path mismatch"); + } + Err(e) => panic!("KmodContext::new failed unexpectedly on valid input: {}", e), + } + + cleanup_test_dir(&root_path); + } + + #[test] + fn test_kmod_context_new_error() { + let root_path = setup_test_dir("missing_dep_dir"); + // modules.*dep is missing (first hit load_hard_dependencies) + match KmodContext::new(&root_path) { + Ok(_) => panic!("Context should fail because modules.dep is missing"), + Err(e) => assert!(e.contains("Failed to load modules.dep")), + } + cleanup_test_dir(&root_path); + } + + #[test] + fn test_extract_module_name() { + // direct case + assert_eq!( + extract_module_name("drivers/sub1/sub2/sub3/module_name.ko.zst"), + "module_name" + ); + // different compression extensions + assert_eq!( + extract_module_name("drivers/sub1/sub2/sub3/module_name.ko.xz"), + "module_name" + ); + assert_eq!( + extract_module_name("drivers/sub1/sub2/sub3/module_name.ko.zst"), + "module_name" + ); + // normalization + assert_eq!( + extract_module_name("drivers/sub1/sub2/sub3/module-name.ko.zst"), + "module_name" + ); + // No path and .ko suffix + assert_eq!(extract_module_name("module_name"), "module_name"); + // full path provided + assert_eq!( + extract_module_name("/lib/modules/x.y.z/drivers/sub1/sub2/sub3/module-name.ko.zst"), + "module_name" + ); + } + + #[test] + fn test_load_harddeps() { + let root_path = setup_test_dir("harddeps"); + let mut ctx = set_context(&root_path); + + // define modules and hard dependencies + let modules_dep_content = format!( + "kernel/mod_a.ko: kernel/dep1.ko kernel/dep2.ko.xz\n\ + kernel/mod-b.ko:\n" // mod-b => mod_b + ); + write_test_file(&root_path, "modules.dep", &modules_dep_content); + write_test_file(&root_path, "kernel/dep1.ko", ""); + + ctx.load_hard_dependencies().unwrap(); + + // Check mod_a + let mod_a = ctx.modules_hash.get("mod_a").expect("mod_a not found"); + assert_eq!(mod_a.status, ModuleStatus::LoadableModule); + assert_eq!(mod_a.rel_path, "kernel/mod_a.ko"); + assert_eq!( + mod_a.hard_deps_paths, + vec!["kernel/dep1.ko", "kernel/dep2.ko.xz"], + "Hard deps paths for mod_a incorrect" + ); + assert_eq!( + mod_a.hard_deps(), + vec!["dep1", "dep2"], + "Hard deps for mod_a incorrect" + ); + + // Check mod_b (normalization and no deps) + let mod_b = ctx.modules_hash.get("mod_b").expect("mod_b not found"); + assert_eq!(mod_b.status, ModuleStatus::LoadableModule); + assert_eq!(mod_b.rel_path, "kernel/mod-b.ko"); + assert!(mod_b.hard_deps().is_empty(), "mod_b should have no hard deps"); + + cleanup_test_dir(&root_path); + } + + #[test] + fn test_load_harddeps_dup() { + let root_path = setup_test_dir("harddeps_dup"); + let mut ctx = set_context(&root_path); + + // define modules and hard dependencies + let modules_dep_content = format!( + "kernel/mod_a.ko: kernel/dep1.ko kernel/dep2.ko.xz\n\ + kernel/mod_b.ko: kernel/dep1.ko\n\ + kernel/mod-a.ko:\n" // duplicate entry for mod_a + ); + write_test_file(&root_path, "modules.dep", &modules_dep_content); + assert_eq!( + ctx.load_hard_dependencies().unwrap_err().kind(), + io::ErrorKind::InvalidData + ); + } + + #[test] + fn test_load_softdeps() { + let root_path = setup_test_dir("softdeps"); + let mut ctx = set_context(&root_path); + + // setup KmodContext with the KmodModule(direct|harddep) that will receive softdeps + ctx.modules_hash.insert( + "mod_a".to_string(), + KmodModule { + status: ModuleStatus::LoadableModule, + rel_path: String::new(), + hard_deps_paths: Vec::new(), + soft_deps_pre: Vec::new(), + soft_deps_post: Vec::new(), + weak_deps: Vec::new(), + }, + ); + + // Define soft dependencies + let modules_softdep_content = format!( + "softdep mod_a pre: softdep_pre_1 softdep_pre_2 post: softdep_post_1\n\ + softdep mod_b pre: softdep_b_pre post: softdep_b_post\n" // mod_b should be None as it's not setup with KmodModule + ); + write_test_file(&root_path, "modules.softdep", &modules_softdep_content); + + ctx.load_soft_dependencies().unwrap(); + + // Check mod_a + let mod_a = ctx.modules_hash.get("mod_a").expect("mod_a not found"); + assert_eq!( + mod_a.soft_deps_pre, + vec!["softdep_pre_1", "softdep_pre_2"], + "Soft pre-deps incorrect" + ); + assert_eq!( + mod_a.soft_deps_post, + vec!["softdep_post_1"], + "Soft post-deps incorrect" + ); + + // check that mod_b is None (as it was not setup with KmodModule struct) + assert!(ctx.modules_hash.get("mod_b").is_none()); + + cleanup_test_dir(&root_path); + } + + #[test] + fn test_load_weakdeps() { + let root_path = setup_test_dir("weakdeps"); + let mut ctx = set_context(&root_path); + + // Setup KmodContext with the module that will receive weakdeps + ctx.modules_hash.insert( + "mod_a".to_string(), + KmodModule { + status: ModuleStatus::LoadableModule, + rel_path: String::new(), + hard_deps_paths: Vec::new(), + soft_deps_pre: Vec::new(), + soft_deps_post: Vec::new(), + weak_deps: Vec::new(), + }, + ); + ctx.modules_hash.insert( + "mod_b".to_string(), + KmodModule { + status: ModuleStatus::LoadableModule, + rel_path: String::new(), + hard_deps_paths: Vec::new(), + soft_deps_pre: Vec::new(), + soft_deps_post: Vec::new(), + weak_deps: Vec::new(), + }, + ); + + // Define weak dependencies + let modules_weakdep_content = format!( + "weakdep mod_a weakdep_1\n\ + weakdep mod_a weakdep_2\n\ + weakdep mod_b weakdep_3\n" + ); + write_test_file(&root_path, "modules.weakdep", &modules_weakdep_content); + + ctx.load_weak_dependencies().unwrap(); + + // Check mod_a + let mod_a = ctx.modules_hash.get("mod_a").expect("mod_a not found"); + assert_eq!( + mod_a.weak_deps, + vec!["weakdep_1", "weakdep_2"], + "Weak deps for mod_a incorrect" + ); + + // Check mod_b + let mod_b = ctx.modules_hash.get("mod_b").expect("mod_b not found"); + assert_eq!( + mod_b.weak_deps, + vec!["weakdep_3"], + "Weak deps for mod_b incorrect" + ); + + cleanup_test_dir(&root_path); + } + + #[test] + fn test_load_builtin() { + let root_path = setup_test_dir("builtin"); + let mut ctx = set_context(&root_path); + + // Define builtin modules + let modules_builtin_content = format!( + "kernel/builtin_mod1.ko\n\ + kernel/builtin-mod2.ko\n" // builtin-mod2 => builtin_mod2 + ); + write_test_file(&root_path, "modules.builtin", &modules_builtin_content); + + ctx.load_builtin_modules().unwrap(); + + // Check builtin_mod1 + let mod1 = ctx + .modules_hash + .get("builtin_mod1") + .expect("builtin_mod1 not found"); + assert_eq!( + mod1.status, + ModuleStatus::Builtin, + "builtin_mod1 status incorrect" + ); + assert!( + mod1.rel_path.is_empty(), + "Builtin path should be empty" + ); + + // Check builtin_mod2 (normalization) + let mod2 = ctx + .modules_hash + .get("builtin_mod2") + .expect("builtin_mod2 not found"); + assert_eq!( + mod2.status, + ModuleStatus::Builtin, + "builtin_mod2 status incorrect" + ); + + cleanup_test_dir(&root_path); + } + + #[test] + fn test_load_harddeps_builtin_dup() { + let root_path = setup_test_dir("harddeps_builtin_dup"); + let mut ctx = set_context(&root_path); + + let modules_dep_content = format!( + "kernel/mod_a.ko: kernel/dep1.ko kernel/dep2.ko.xz\n\ + kernel/mod_b.ko: kernel/dep1.ko\n" + ); + write_test_file(&root_path, "modules.dep", &modules_dep_content); + + // builtin mod_b collides with modules.dep entry + let modules_builtin_content = format!( + "kernel/builtin_mod1.ko\n\ + kernel/mod_b.ko\n" + ); + write_test_file(&root_path, "modules.builtin", &modules_builtin_content); + + ctx.load_hard_dependencies().unwrap(); + assert_eq!( + ctx.load_builtin_modules().unwrap_err().kind(), + io::ErrorKind::InvalidData + ); + } + + #[test] + fn test_load_aliases() { + let root_path = setup_test_dir("aliases"); + let mut ctx = set_context(&root_path); + + // Define module for alias to point to + ctx.modules_hash.insert( + "mod_target".to_string(), + KmodModule { + status: ModuleStatus::LoadableModule, + rel_path: String::new(), + hard_deps_paths: Vec::new(), + soft_deps_pre: Vec::new(), + soft_deps_post: Vec::new(), + weak_deps: Vec::new(), + }, + ); + + // Define aliases + let modules_alias_content = format!( + "alias alias_1 mod_target\n\ + alias alias_2 mod_target\n\ + # this line should be ignored: alias bad_line mod_target\n\ + alias complex_alias:v*d* mod_target\n\ + alias mod_name_is_alias mod_target\n" + ); + write_test_file(&root_path, "modules.alias", &modules_alias_content); + + ctx.load_aliases().unwrap(); + + // Check alias map contents + assert_eq!(ctx.alias_map.get("alias_1").unwrap(), "mod_target"); + assert_eq!(ctx.alias_map.get("alias_2").unwrap(), "mod_target"); + assert_eq!( + ctx.alias_map.get("complex_alias:v*d*").unwrap(), + "mod_target" + ); + assert!(ctx.alias_map.get("bad_line").is_none()); + + cleanup_test_dir(&root_path); + } + + #[test] + fn test_find() { + let mut ctx = set_context(&PathBuf::new()); + + // setup KmodModule for find calls + let target_module = KmodModule { + status: ModuleStatus::LoadableModule, + rel_path: String::from("kernel/target.ko"), + hard_deps_paths: vec!["kernel/dep1.ko".to_string()], + soft_deps_pre: Vec::new(), + soft_deps_post: Vec::new(), + weak_deps: Vec::new(), + }; + ctx.modules_hash + .insert("mod_target".to_string(), target_module.clone()); + ctx.alias_map + .insert("alias_target".to_string(), "mod_target".to_string()); + ctx.modules_hash.insert( + "builtin_mod".to_string(), + KmodModule { + status: ModuleStatus::Builtin, + rel_path: String::new(), + hard_deps_paths: Vec::new(), + soft_deps_pre: Vec::new(), + soft_deps_post: Vec::new(), + weak_deps: Vec::new(), + }, + ); + + // Direct hit (Loadable Module) + let found_direct = ctx + .find("mod_target") + .expect("Should find mod_target directly"); + assert_eq!(found_direct.status, ModuleStatus::LoadableModule); + assert_eq!(found_direct.hard_deps_paths.len(), 1); + + // alias hit + let found_alias = ctx + .find("alias_target") + .expect("Should find mod_target via alias"); + assert_eq!(*found_alias, target_module); + + // Builtin Module + let found_builtin = ctx.find("builtin_mod").expect("Should find builtin_mod"); + assert_eq!(found_builtin.status, ModuleStatus::Builtin); + + // Not Found + assert!( + ctx.find("non_existent").is_none(), + "Should not find non_existent module" + ); + + // alias that points to a non-existent module (should fail gracefully) + ctx.alias_map + .insert("bad_alias".to_string(), "ghost_mod".to_string()); + assert!( + ctx.find("bad_alias").is_none(), + "alias pointing to a ghost module should return None" + ); + } + + #[test] + fn test_kmod_context_full_load() { + // -- SETUP -- + let root_path = setup_test_dir("full_load"); + + // create module files + // mod_a and mod_b are loadable modules. + write_test_file(&root_path, "kernel/mod_a.ko", ""); + write_test_file(&root_path, "kernel/mod_b.ko.xz", ""); + + // modules.dep (hard deps) + let modules_dep_content = format!( + "kernel/mod_a.ko: kernel/mod_b.ko.xz kernel/mod_c.ko\n\ + kernel/mod_b.ko.xz:\n" + ); + write_test_file(&root_path, "modules.dep", &modules_dep_content); + + // modules.softdep (soft deps) + let modules_softdep_content = "softdep mod_a pre: mod_d post: mod_e mod_f\n"; + write_test_file(&root_path, "modules.softdep", modules_softdep_content); + + // modules.weakdep (weak deps) + let modules_weakdep_content = + "weakdep mod_a mod_g\nweakdep mod_a mod_h\nweakdep mod_b mod_i\nweakdep mod_b mod_j\n"; + write_test_file(&root_path, "modules.weakdep", modules_weakdep_content); + + // modules.builtin + let modules_builtin_content = "kernel/mod_builtin.ko\n"; + write_test_file(&root_path, "modules.builtin", modules_builtin_content); + + // modules.alias + let modules_alias_content = + "alias alias_for_b mod_b\nalias mod-b mod_b\nalias mod-intel-b mod_b\n"; + write_test_file(&root_path, "modules.alias", modules_alias_content); + + // -- LOAD KmodContext -- + + let context = KmodContext::new(&root_path).unwrap(); + + // -- ASSERTIONS -- + + // Check mod_a (loadable-module, hard/soft/weak dependencies) + let mod_a = context.find("mod_a").expect("mod_a should be found"); + assert_eq!(mod_a.status, ModuleStatus::LoadableModule); + assert_eq!( + mod_a.rel_path, + "kernel/mod_a.ko", + "Path should point to the module file" + ); + assert_eq!( + mod_a.hard_deps_paths, + vec!["kernel/mod_b.ko.xz", "kernel/mod_c.ko"], + "hard dependencies check failed" + ); + assert_eq!( + mod_a.hard_deps(), + vec!["mod_b", "mod_c"], + "hard dependencies check failed" + ); + assert_eq!( + mod_a.soft_deps_pre, + vec!["mod_d"], + "soft pre-dependencies check failed" + ); + assert_eq!( + mod_a.soft_deps_post, + vec!["mod_e", "mod_f"], + "soft post-dependencies check failed" + ); + assert_eq!( + mod_a.weak_deps, + vec!["mod_g", "mod_h"], + "weak dependencies check failed" + ); + + // Check mod_b (loadable-module, weak dep) + let mod_b = context.find("mod_b").expect("mod_b should be found"); + assert_eq!(mod_b.status, ModuleStatus::LoadableModule); + assert_eq!( + mod_b.rel_path, + "kernel/mod_b.ko.xz", + "Path should point to the compressed module file" + ); + assert_eq!( + mod_b.hard_deps_paths.len(), + 0, + "mod_b should have no hard dependencies" + ); + assert_eq!( + mod_b.weak_deps, + vec!["mod_i", "mod_j"], + "mod_b weak dependency check failed" + ); + + // Check builtin + let builtin_mod = context + .find("mod_builtin") + .expect("mod_builtin should be found"); + assert_eq!( + builtin_mod.status, + ModuleStatus::Builtin, + "Builtin status check failed" + ); + + // Check alias Lookup + let aliased_mod = context + .find("alias_for_b") + .expect("alias_for_b should resolve"); + assert_eq!( + aliased_mod, mod_b, + "alias should resolve to the correct module" + ); + + // Check ModuleNotFound + assert!( + context.find("non_existent_mod").is_none(), + "non-existent module should return None" + ); + + // -- CLEANUP -- + cleanup_test_dir(&root_path); + } + + #[test] + fn test_kmod_context_complex_alias_resolve() { + // -- SETUP -- + let root_path = setup_test_dir("alias_test"); + + // Path: kernel/arch/x86/sub/mod32c-intel.ko.zst -> name: mod32c_intel + write_test_file(&root_path, "kernel/arch/x86/sub/mod32c-intel.ko.zst", ""); + + // write module file (the one with the softdep) + // Path: kernel/fs/fsmodule/fsmodule.ko.zst -> name: fsmodule + write_test_file(&root_path, "kernel/fs/fsmodule/fsmodule.ko.zst", ""); + + // modules.dep: contains all loadable modules + let modules_dep_content = format!( + "kernel/fs/fsmodule/fsmodule.ko.zst: kernel/lib/other_dep.ko\n\ + kernel/arch/x86/sub/mod32c-intel.ko.zst:\n\ + kernel/drivers/char/hw_random/virtio-rng.ko.zst:\n\ + kernel/lib/other_dep.ko:\n" + ); + write_test_file(&root_path, "modules.dep", &modules_dep_content); + + // modules.softdep: fsmodule has a soft dependency 'mod32c' (the alias) + let modules_softdep_content = "softdep fsmodule pre: mod32c\n"; + write_test_file(&root_path, "modules.softdep", modules_softdep_content); + + // modules.alias: alias 'mod32c' points to 'mod32c_intel' + let modules_alias_content = format!( + "alias sub-mod32c-intel mod32c_intel\n\ + alias mod32c-intel mod32c_intel\n\ + alias sub-mod32c mod32c_intel\n\ + alias mod32c mod32c_intel\n\ + alias cpu:type:x86,ven*fam*mod*:feature:*1234* mod32c_intel\n" + ); + write_test_file(&root_path, "modules.alias", &modules_alias_content); + + // modules.builtin is empty. modules.weakdep is omitted (like Leap 15.6). + write_test_file(&root_path, "modules.builtin", ""); + + // -- LOAD KmodContext -- + let context = KmodContext::new(&root_path).unwrap(); + + // -- ASSERTIONS -- + + // fsmodule soft dependency + let fsmodule = context.find("fsmodule").expect("fsmodule should be found"); + assert_eq!( + fsmodule.soft_deps_pre, + vec!["mod32c"], + "fsmodule should softdep on 'mod32c' (the alias name)" + ); + + // Check alias_map for expected entry + assert_eq!( + context.alias_map.get("mod32c").unwrap(), + "mod32c_intel", + "alias 'mod32c' should map to 'mod32c_intel'" + ); + + // finding alias "mod32c" should resolve to the real module + let aliased_mod = context + .find("mod32c") + .expect("Finding by alias 'mod32c' should resolve"); + + // The returned module should be the actual module, mod32c_intel + assert_eq!( + aliased_mod.name(), + "mod32c_intel", + "alias lookup should return the real module name" + ); + assert_eq!( + aliased_mod.status, + ModuleStatus::LoadableModule, + "Resolved module status should be LoadableModule" + ); + assert_eq!( + aliased_mod.rel_path, + "kernel/arch/x86/sub/mod32c-intel.ko.zst", + "Resolved module path is incorrect" + ); + + // find() performs normalization, so handles either - or _ form + assert_eq!( + context.find("virtio_rng").unwrap().rel_path, + "kernel/drivers/char/hw_random/virtio-rng.ko.zst", + ); + assert_eq!( + context.find("virtio-rng").unwrap().rel_path, + "kernel/drivers/char/hw_random/virtio-rng.ko.zst", + ); + + cleanup_test_dir(&root_path); + } +} diff --git a/src/bin/kmod/main.rs b/src/bin/kmod/main.rs new file mode 100644 index 0000000..0afd855 --- /dev/null +++ b/src/bin/kmod/main.rs @@ -0,0 +1,262 @@ +// SPDX-License-Identifier: (GPL-2.0 OR GPL-3.0) +// Copyright (C) 2025 SUSE S.A. +use std::collections::{HashMap, HashSet}; +use std::env; +use std::path::{Path, PathBuf}; +use std::process; + +mod kmod_context; +use kmod_context::{KmodContext, KmodModule, ModuleStatus, MODULE_DB_FILES}; + +struct CliArgs { + pub module_names: Vec, + pub kmod_dir: Option, +} + +fn print_usage() { + // TEST: cargo run --bin kmod -- --install-kmod 'xfs ext4 btrfs' [--kmod-dir MODULE_PATH] + eprintln!("Usage: kmod-parser [OPTIONS]"); + eprintln!("\nOptions:"); + eprintln!(" --install-kmod MODULES Space separated list of kernel modules to install with dependencies."); + eprintln!( + " --kmod-dir MODULE_PATH Specify the root path for modules (ex: /lib/modules/x.y.z)." + ); + eprintln!(" -h, --help Print help message."); +} + +fn parse_all_args() -> Result { + let mut args_iter = env::args().skip(1); + + let mut parsed_args: CliArgs = CliArgs { + module_names: Vec::new(), + kmod_dir: None, + }; + + while let Some(arg) = args_iter.next() { + match arg.as_str() { + "-h" | "--help" => return Err("PrintHelp".to_string()), + "--install-kmod" => { + if let Some(value) = args_iter.next() { + let kmod_names: Vec = + value.split_whitespace().map(|s| s.to_string()).collect(); + parsed_args.module_names.extend(kmod_names); + } + } + + "--kmod-dir" => { + if let Some(value) = args_iter.next() { + parsed_args.kmod_dir = Some(value); + } + } + _ => { + return Err(format!( + "Unknown argument or positional argument not allowed: {}", + arg + )); + } + } + } + Ok(parsed_args) +} + +fn print_dep_line(dep_mod: &KmodModule, prefix: &str) { + let dep_icon = if prefix == "harddep" { + "├──" + } else { + "├────" + }; + println!( + " {dep_icon} {prefix}: {} ({:?})", + dep_mod.name(), dep_mod.status + ); +} + +fn print_direct_deps(context: &KmodContext, root_name: &str) { + let root_mod = match context.find(root_name) { + Some(m) => m, + None => { + println!("🔗 {} (NotFound)", root_name); + return; + } + }; + + println!("🔗 {} ({:?})", root_name, root_mod.status); + + for dep_mod_name in &root_mod.hard_deps() { + if let Some(dep_mod) = context.find(dep_mod_name) { + print_dep_line(dep_mod, "harddep"); + } + } + + let soft_weak_deps_names = root_mod + .soft_deps_pre + .iter() + .chain(root_mod.soft_deps_post.iter()) + .chain(root_mod.weak_deps.iter()); + + for dep_mod_name in soft_weak_deps_names { + if let Some(dep_mod) = context.find(dep_mod_name) { + print_dep_line(dep_mod, "softdep"); + } + } +} + +fn print_dependency_graph(context: &KmodContext, initial_modules: &[String]) { + println!("\n--- Dependency Graph (Tree) ---"); + for name in initial_modules { + print_direct_deps(context, name); + } + println!("-------------------------------------------"); +} + +fn print_paths_summary(title: &str, paths: Result, String>) { + match paths { + Ok(mut paths) => { + println!("\n--- {} ({} found) ---", title, paths.len()); + paths.sort(); + for path in paths { + println!("{}", path.display()); + } + println!("------------------------------------------------------"); + } + Err(e) => eprintln!("Error during {} collection: {}", title, e), + } +} + +fn collect_dependencies<'a>( + context: &'a KmodContext, + modules: &[String], +) -> Result, String> { + let mut collected: HashMap = HashMap::new(); + + for name in modules { + if let Some(kmodule) = context.find(name) { + let hard_deps = kmodule.hard_deps(); + let all_deps: Vec<&String> = hard_deps + .iter() + .chain(kmodule.soft_deps_pre.iter()) + .chain(kmodule.soft_deps_post.iter()) + .chain(kmodule.weak_deps.iter()) + .collect(); + for dep_mod_name in all_deps { + if let Some(dep_mod) = context.find(dep_mod_name) { + collected.entry(dep_mod_name.clone()).or_insert(dep_mod); + } + } + collected.entry(name.clone()).or_insert(kmodule); + } + } + Ok(collected) +} + +fn collect_module_paths(context: &KmodContext, modules: &[String]) -> Result, String> { + let collected = collect_dependencies(context, modules)?; + let paths: HashSet = collected + .into_values() + .into_iter() + // filter out built-in modules + .filter(|kmod| kmod.status != ModuleStatus::Builtin) + // filter out if path exists + .filter(|kmod| context.module_root.join(&kmod.rel_path).exists()) + .map(|kmod| context.module_root.join(&kmod.rel_path)) + .collect(); + + Ok(paths.into_iter().collect()) +} + +fn collect_module_data_paths(context: &KmodContext) -> Result, String> { + let root = &context.module_root; + let mut paths: Vec = Vec::new(); + + for file_name in MODULE_DB_FILES.iter() { + let path = root.join(file_name); + if path.is_file() { + paths.push(path); + } + } + + Ok(paths) +} + +fn collect_all_initrd_paths( + context: &KmodContext, + initial_modules: &[String], +) -> Result, String> { + let mut all_paths = collect_module_paths(context, initial_modules)?; + let mut data_paths = collect_module_data_paths(context)?; + all_paths.append(&mut data_paths); + Ok(all_paths) +} + +fn main() { + let args = match parse_all_args() { + Ok(res) => res, + Err(e) if e == "PrintHelp" => { + print_usage(); + process::exit(0); + } + Err(e) => { + eprintln!("Error: {}", e); + print_usage(); + process::exit(1); + } + }; + + let initial_modules = args.module_names; + let kmod_dir = match args.kmod_dir { + None => { + print_usage(); + process::exit(0); + }, + Some(d) => d, + }; + + if initial_modules.is_empty() { + print_usage(); + process::exit(0); + } + + println!("--- Starting dependency collection ---"); + println!("Initial modules: {:?}", initial_modules); + + // The KmodContext::new logic handles the kernel directory derivation based on kmod_dir + match KmodContext::new(&Path::new(&kmod_dir)) { + Ok(context) => { + print_dependency_graph(&context, &initial_modules); + + // Collect module names + match collect_dependencies(&context, &initial_modules) { + Ok(modules) => { + println!( + "\n--- Collected UNIQUE module names ({} found) ---", + modules.len() + ); + for (name, module) in modules.iter() { + println!(" - {}: {:?}", name, module.status); + } + } + Err(e) => eprintln!("Error during name collection: {}", e), + } + + print_paths_summary( + "Required loadable module paths", + collect_module_paths(&context, &initial_modules), + ); + + print_paths_summary( + "Required module data paths", + collect_module_data_paths(&context), + ); + + print_paths_summary( + "Required Initrd paths (loadable + data)", + collect_all_initrd_paths(&context, &initial_modules), + ); + } + Err(e) => { + eprintln!("\nInitialization error: {}", e); + eprintln!("Check if kernel directory exists or use --kmod-dir to specify."); + process::exit(1); + } + } +} diff --git a/src/bin/kmod/mod.rs b/src/bin/kmod/mod.rs new file mode 100644 index 0000000..1f75a43 --- /dev/null +++ b/src/bin/kmod/mod.rs @@ -0,0 +1,3 @@ +// SPDX-License-Identifier: (GPL-2.0 OR GPL-3.0) +// Copyright (C) 2025 SUSE S.A. +pub mod kmod_context; diff --git a/src/bin/lscpio.rs b/src/bin/lscpio.rs new file mode 100644 index 0000000..8b083b3 --- /dev/null +++ b/src/bin/lscpio.rs @@ -0,0 +1,34 @@ +// SPDX-License-Identifier: (GPL-2.0 OR GPL-3.0) +// Copyright (C) 2025 SUSE S.A. +use std::env; +use std::fs; +use std::io; +use std::str; + +fn main() -> io::Result<()> { + let mut args = env::args_os(); + if args.len() != 2 { + println!("Usage: lscpio INITRAMFS"); + return Err(io::Error::from(io::ErrorKind::InvalidInput)); + } + + // avoid BufReader: regular fh benchmarked slightly faster, despite fewer + // syscalls: read(hdr)+read(name)+seek() vs read(hdr+name+readahead)+seek() + let f = fs::OpenOptions::new().read(true).open(args.nth(1).unwrap())?; + let mut archive_walker = cpio::archive_walk(f)?; + while let Some(archive_ent) = archive_walker.next() { + let archive_ent = archive_ent?; + let name = match str::from_utf8(&archive_ent.name) { + Ok(s) => { + // per spec, name must be zero terminated + match s.split_once('\0') { + None => Err(io::Error::from(io::ErrorKind::InvalidData)), + Some((n_before_term, _)) => Ok(n_before_term), + } + } + Err(_) => Err(io::Error::from(io::ErrorKind::InvalidData)), + }?; + println!("{: <9} {}", archive_ent.md.len, name); + } + Ok(()) +} diff --git a/src/bin/rapido-cut.rs b/src/bin/rapido-cut.rs new file mode 100644 index 0000000..2a1499d --- /dev/null +++ b/src/bin/rapido-cut.rs @@ -0,0 +1,1387 @@ +// SPDX-License-Identifier: (GPL-2.0 OR GPL-3.0) +// Copyright (C) 2025 SUSE LLC +use std::collections::{HashMap, HashSet}; +use std::convert::TryFrom; +use std::env; +use std::fs; +use std::io; +use std::io::Seek; +use std::io::Write; +use std::path::{self, Path, PathBuf, Component}; + +use elf::abi; +use elf::ElfStream; +use elf::endian::AnyEndian; + +use crosvm::argument::{self, Argument}; +mod kmod; +use kmod::kmod_context::{KmodContext, ModuleStatus, MODULE_DB_FILES}; +extern crate kv_conf; + +// We should probably allow default search paths to be set at build time. +// On usr-merge systems, /X may be a symlink to /usr/X . +const BIN_PATHS: [&str; 5] = ["/usr/bin", "/usr/sbin", "/usr/lib/systemd", "/bin", "/sbin"]; +// Extra search paths may be added at runtime via ELF RUNPATH/LibRunPath. +// x86_64-linux-gnu is for Debian/Ubuntu. +const LIB_PATHS: [&str; 5] = ["/usr/lib64", "/usr/lib", "/lib64", "/lib", "/usr/lib/x86_64-linux-gnu"]; +// FIXME: don't assume cwd parent location +const MANIFEST_PATHS: [&str; 1] = ["manifest"]; +// keys which can be used in manifest files (matching cli parameters) +const MANIFEST_KEYS: [&str; 5] = ["install", "try_install", "include", "kmods", "autorun"]; +// FIXME: we shouldn't assume rapido-init location +const RAPIDO_INIT_PATH: &str = "target/release/rapido-init"; +// FIXME: don't assume cwd location +const RAPIDO_BASH_RC_PATH: &str = "vm_autorun.env"; + +// XXX use next: 1<<0; +const GATHER_ITEM_IGNORE_PARENT: u32 = 1<<1; + +// Don't print debug messages on release builds... +#[cfg(debug_assertions)] +macro_rules! dout { + ($($l:tt)*) => { println!($($l)*); } +} +#[cfg(not(debug_assertions))] +macro_rules! dout { + ($($l:tt)*) => {}; +} + +struct Fsent { + path: PathBuf, + md: fs::Metadata, +} + +// TODO: merge with GatherEnt, with a static option too? +#[derive(PartialEq, Debug)] +struct GatherItem { + src: PathBuf, + dst: PathBuf, + flags: u32, +} + +struct GatherData { + items: Vec, + // offset that we are currently processing + off: usize, +} + +#[derive(PartialEq, Debug)] +enum GatherEnt { + // Name String may be an absolute host-source-path or a relative path + // resolved via path_stat(). Destination matches source. + Name(String), + NameStatic(&'static str), + // Same as above, but destination is explicitly provided. + NameDst(&'static str, &'static str), + // Ignore if missing, instead of aborting. + NameTry(String), + // library with extra search path(s) from ELF RUNPATH + LibRunPath(String, Vec), +} + +struct Gather { + // Dependencies (elf, kmod, etc.) are added to the end of the gather + // list as they are found. + names: Vec, + // offset that we are currently processing + off: usize, +} + +// We *should* be running as an unprivileged process, so don't filter or block +// access to parent or special paths; this should all be handled by the OS. +fn path_stat(ent: &GatherEnt, search_paths: &[&str]) -> Result { + + let name: &str = match ent { + GatherEnt::Name(n) => &n, + GatherEnt::NameDst(n, _) => n, + GatherEnt::NameStatic(n) => n, + GatherEnt::NameTry(n) => &n, + // it might be cleaner to add an extra enum for paths with separator... + GatherEnt::LibRunPath(n, _) if n.contains(path::MAIN_SEPARATOR_STR) => &n, + GatherEnt::LibRunPath(n, paths) => { + for dir in paths.iter() { + let p = PathBuf::from(dir).join(n); + if let Ok(md) = fs::symlink_metadata(&p) { + return Ok(Fsent {path: p, md: md}); + } + } + // fallback to search_paths + &n + } + }; + + dout!("resolving path for {:?}", name); + // if name has any separator in it then we should handle it as a relative + // or absolute path. This should be close enough as a check. + if name.contains(path::MAIN_SEPARATOR_STR) { + dout!("using relative / absolute path {:?} as-is", name); + return match fs::symlink_metadata(name) { + Ok(md) => Ok(Fsent { + path: path::absolute(name).expect("absolute failed for good path"), + md: md + }), + Err(_) => { + return Err(io::Error::new( + io::ErrorKind::NotFound, + format!("{} missing", name) + )); + } + } + } + + // TODO: search all paths and prefer non-symlink if multiple? + for dir in search_paths.iter() { + let p = PathBuf::from(dir).join(name); + if let Ok(md) = fs::symlink_metadata(&p) { + return Ok(Fsent {path: p, md: md}); + } + } + + return Err(io::Error::new( + io::ErrorKind::NotFound, + match ent { + GatherEnt::LibRunPath(_, p) => { + format!("{} missing from: {:?} & {:?}", name, p, search_paths) + } + _ => format!("{} missing from: {:?}", name, search_paths), + } + )); +} + +// Parse ELF DT_NEEDED entries to gather shared object dependencies. +// DT_RUNPATH entries are retained as extra library search paths. +fn elf_deps( + f: &fs::File, + path: &Path, + dups_filter: &mut HashSet +) -> Result, io::Error> { + let mut ret: Vec = vec![]; + + let mut file = match ElfStream::::open_stream(f) { + Ok(f) => f, + Err(e) => { + // ParseError::BadOffset / ParseError::BadMagic is returned + // immediately for empty / non-elf, which we want to ignore. + return Err(io::Error::new(io::ErrorKind::InvalidInput, + e.to_string())); + }, + }; + + let dynamics = match file.dynamic() { + Ok(d) => { + if d.is_none() { + eprintln!("Failed to find .dynamic for {:?}", path); + return Ok(ret); + } + d.unwrap() + }, + Err(e) => { + return Err(io::Error::new(io::ErrorKind::Other, e.to_string())); + }, + }; + + let mut runpath_offs: Vec = vec!(); + let mut needed_offs: Vec = vec!(); + for dyna in dynamics.iter() { + let v = match dyna.d_tag { + abi::DT_NEEDED => &mut needed_offs, + abi::DT_RUNPATH => &mut runpath_offs, + _ => continue, + }; + + match usize::try_from(dyna.d_val()) { + Err(e) => { + return Err(io::Error::new(io::ErrorKind::Other, e.to_string())); + } + Ok(str_off) => v.push(str_off), + } + } + + let dynsyms_strs = match file.dynamic_symbol_table() { + Err(e) => { + return Err(io::Error::new(io::ErrorKind::Other, e.to_string())); + }, + Ok(tup) => { + if tup.is_none() { + dout!("no tables for {:?}", path); + return Ok(ret); + } + let (_, strs) = tup.unwrap(); + strs + }, + }; + + // get full list of runpaths first + let runpaths: Vec = runpath_offs.into_iter().filter_map(|o| { + match dynsyms_strs.get(o) { + Err(_) => None, + Ok(s) => Some(s.to_string()), + } + }).collect(); + + for str_off in needed_offs { + match dynsyms_strs.get(str_off) { + Ok(sraw) => { + let s = sraw.to_string(); + if dups_filter.insert(s.clone()) { + dout!("new elf dependency({:?}): {:?}", str_off, s); + if runpaths.len() > 0 { + // would be nice to avoid cloning for every lib here: + // perhaps add a single GatherEnt::PathPush/Pop pair? + // RUNPATH rare enough that it's prob not worth it. + ret.push(GatherEnt::LibRunPath(s, runpaths.clone())); + } else { + ret.push(GatherEnt::Name(s)); + } + } else { + dout!("duplicate elf dependency({:?}): {:?}", str_off, sraw); + } + }, + Err(e) => { + return Err(io::Error::new(io::ErrorKind::InvalidData, + e.to_string())); + }, + }; + } + + Ok(ret) +} + +// XXX: symlinks in parent ancestry will be archived as dirs +// FIXME: how does this handle relative "" parents? +// @child_amd provides the child metadata, which is used to mock up metadata +// for parent directories. +fn gather_archive_dirs( + path: Option<&Path>, + child_amd: &cpio::ArchiveMd, + paths_seen: &mut HashSet, + cpio_state: &mut cpio::ArchiveState, + mut cpio_writer: W, +) -> io::Result<()> { + // path may come from parent(), hence Option + let p = match path { + None => return Ok(()), + // don't canonicalize dirs: dest path may not match host + Some(p) => p, + }; + + // path_stat() and dst assignment call absolute() + if !p.is_absolute() { + panic!("non-absolute path, check path_stat and dst paths"); + } + + if paths_seen.contains(p) { + dout!("ignoring seen directory and parents: {:?}", p); + return Ok(()); + } + + // mock up md to use for any parent directories. 0111: allow traversal + let parent_dirs_amd = cpio::ArchiveMd{ + mode: match child_amd.mode & cpio::S_IFMT { + cpio::S_IFDIR => child_amd.mode, + _ => (child_amd.mode & !cpio::S_IFMT) | cpio::S_IFDIR | 0111, + }, + nlink: 2, + rmajor: 0, + rminor: 0, + len: 0, + ..*child_amd + }; + + // order is important: parent dirs must be archived before children + let mut here = PathBuf::from("/"); + for comp in p.components() { + match comp { + Component::RootDir => continue, + Component::CurDir | Component::ParentDir => { + // FIXME: absolute() does leave ParentDir components! + panic!("got CurDir or ParentDir after canonicalization"); + }, + Component::Prefix(_) => { + eprintln!("non-Unix path prefixes not supported"); + return Err(io::Error::from(io::ErrorKind::InvalidInput)); + }, + Component::Normal(c) => here.push(c), + } + + if !paths_seen.insert(here.clone()) { + dout!("ignoring seen directory: {:?}", here); + continue; + } + + cpio::archive_path(cpio_state, &here, &parent_dirs_amd, &mut cpio_writer)?; + dout!("archived dir: {:?}", here); + } + + Ok(()) +} + +fn gather_archive_file( + src: &Path, + dst: &Path, + amd: &cpio::ArchiveMd, + mode_mask: Option, + libs_names: &mut Vec, + libs_seen: &mut HashSet, + cpio_state: &mut cpio::ArchiveState, + mut cpio_writer: W, +) -> io::Result<()> { + let mut f = fs::OpenOptions::new().read(true).open(src)?; + if mode_mask.is_none() || mode_mask.unwrap() & amd.mode != 0 { + // XXX NameTry bins do *not* result in NameTry libs; if a binary is + // installed then it's reasonable to assume presence of libs. + match elf_deps(&f, src, libs_seen) { + Ok(mut d) => libs_names.append(&mut d), + Err(ref e) if e.kind() == io::ErrorKind::InvalidInput => { + dout!("executable {:?} not an elf", src); + }, + Err(e) => { + dout!("failed to obtain dependencies for elf {:?}: {:?}", src, e); + }, + } + } + // don't check for '#!' interpreters like Dracut, it's messy + + f.seek(io::SeekFrom::Start(0))?; + cpio::archive_file(cpio_state, dst, &amd, &f, &mut cpio_writer)?; + + Ok(()) +} + +fn gather_archive_bins( + bins: &mut Gather, + libs: &mut Gather, + libs_seen: &mut HashSet, + paths_seen: &mut HashSet, + cpio_state: &mut cpio::ArchiveState, + mut cpio_writer: W, +) -> io::Result<()> { + + while let Some(ent) = bins.names.get(bins.off) { + bins.off += 1; + + let got = match path_stat(&ent, &BIN_PATHS) { + Err(e) => { + if let GatherEnt::NameTry(_) = ent { + continue; + } + return Err(e); + } + Ok(g) => g, + }; + let dst = match ent { + GatherEnt::NameDst(_, d) => &path::absolute(d)?, + _ => &got.path, + }; + + let amd = cpio::ArchiveMd::from(&cpio_state, &got.md)?; + gather_archive_dirs( + dst.parent(), + &amd, + paths_seen, + cpio_state, + &mut cpio_writer + )?; + match amd.mode & cpio::S_IFMT { + cpio::S_IFLNK => { + // symlinks are tricky, so provide some restrictions: + // - the host path must match the initramfs dest path + // - targets be resolvable; no dangling / arbitrary links + // - multiple indirect links will be collapsed + if let GatherEnt::NameDst(_, _) = ent { + eprintln!("symlink source and cpio dest paths must match"); + return Err(io::Error::from(io::ErrorKind::InvalidInput)); + } + let canon_tgt = match got.path.canonicalize() { + Err(e) => { + eprintln!("{:?} canonicalize failed: {:?}", got.path, e); + continue; + }, + Ok(t) => t, + }; + cpio::archive_symlink( + cpio_state, + &got.path, + &amd, + &canon_tgt, + &mut cpio_writer + )?; + dout!("archived symlink: {:?} ({:?})", got.path, canon_tgt); + + if let Ok(t) = canon_tgt.into_os_string().into_string() { + bins.names.push(GatherEnt::Name(t)); + } else { + eprintln!("non utf-8 symlink target {:?}", &got.path); + return Err(io::Error::from(io::ErrorKind::InvalidInput)); + } + }, + cpio::S_IFREG => { + gather_archive_file( + &got.path, + &dst, + &amd, + Some(0o111), + &mut libs.names, + libs_seen, + cpio_state, + &mut cpio_writer + )?; + dout!("archived bin: {:?}→{:?}", got.path, dst); + }, + _ => { + cpio::archive_path(cpio_state, &dst, &amd, &mut cpio_writer)?; + dout!("archived other: {:?}→{:?}", got.path, dst); + }, + }; + } + + Ok(()) +} + +// TODO: this is very similar to gather_archive_bins; combine? +fn gather_archive_libs( + libs: &mut Gather, + libs_seen: &mut HashSet, + paths_seen: &mut HashSet, + cpio_state: &mut cpio::ArchiveState, + mut cpio_writer: W, +) -> io::Result<()> { + + while let Some(ent) = libs.names.get(libs.off) { + libs.off += 1; + + let got = match path_stat(&ent, &LIB_PATHS) { + Err(e) => { + if let GatherEnt::NameTry(_) = ent { + continue; + } + return Err(e); + } + Ok(g) => g, + }; + let dst = match ent { + GatherEnt::NameDst(_, d) => &path::absolute(d)?, + _ => &got.path, + }; + + let amd = cpio::ArchiveMd::from(&cpio_state, &got.md)?; + gather_archive_dirs( + dst.parent(), + &amd, + paths_seen, + cpio_state, + &mut cpio_writer + )?; + match amd.mode & cpio::S_IFMT { + cpio::S_IFLNK => { + if let GatherEnt::NameDst(_, _) = ent { + eprintln!("symlink source and cpio dest paths must match"); + return Err(io::Error::from(io::ErrorKind::InvalidInput)); + } + let canon_tgt = match got.path.canonicalize() { + Err(e) => { + eprintln!("{:?} canonicalize failed: {:?}", got.path, e); + continue; + }, + Ok(t) => t, + }; + cpio::archive_symlink( + cpio_state, + &got.path, + &amd, + &canon_tgt, + &mut cpio_writer + )?; + dout!("archived lib symlink: {:?} ({:?})", got.path, canon_tgt); + + if let Ok(t) = canon_tgt.into_os_string().into_string() { + libs.names.push(GatherEnt::Name(t)); + } else { + eprintln!("non utf-8 symlink target {:?}", &got.path); + return Err(io::Error::from(io::ErrorKind::InvalidInput)); + } + }, + cpio::S_IFREG => { + gather_archive_file( + &got.path, + &dst, + &amd, + None, + &mut libs.names, + libs_seen, + cpio_state, + &mut cpio_writer + )?; + dout!("archived lib: {:?}", got.path); + }, + _ => { + eprintln!( + "{:?}: libs gathering only supports symlinks or files, not {:o}", + got.path, amd.mode + ); + return Err(io::Error::from(io::ErrorKind::InvalidInput)); + }, + }; + } + + Ok(()) +} + +fn archive_kmod_path( + src: &Path, + dst: &Path, + paths_seen: &mut HashSet, + cpio_state: &mut cpio::ArchiveState, + mut cpio_writer: W, +) -> io::Result<()> { + let md = fs::symlink_metadata(src)?; + let amd = cpio::ArchiveMd::from(cpio_state, &md)?; + gather_archive_dirs( + dst.parent(), + &amd, + paths_seen, + cpio_state, + &mut cpio_writer + )?; + + let kmod_f = fs::File::open(src)?; + cpio::archive_file( + cpio_state, + dst, + &amd, + &kmod_f, + cpio_writer, + )?; + dout!("archived kmod: {:?} -> {:?}", src, dst); + Ok(()) +} + +// XXX: Tumbleweed kmod is patched to use "/usr/lib/modules", while +// mainline kernel and Leap 15 use "/lib/modules". Worse still, there's no +// easy way to specify the directory for modprobe, so we use symlinks. Booo. +// See https://src.opensuse.org/pool/kmod/src/branch/factory/README.usrmerge +fn archive_kmods_symlink( + paths_seen: &mut HashSet, + cpio_state: &mut cpio::ArchiveState, + mut cpio_writer: W, +) -> io::Result<()> { + let libp = Path::new("/lib"); + let (p, tgt) = match paths_seen.contains(libp) { + false => (libp, Path::new("/usr/lib")), + true => (Path::new("/lib/modules"), Path::new("/usr/lib/modules")), + }; + let amd = cpio::ArchiveMd{ + nlink: 1, + mode: cpio::S_IFLNK | 0o777, + uid: 0, + gid: 0, + mtime: 0, + rmajor: 0, + rminor: 0, + len: 0, + }; + cpio::archive_symlink(cpio_state, &p, &amd, &tgt, &mut cpio_writer)?; + dout!("archived kmod symlink {:?} ({:?})", p, tgt); + Ok(()) +} + +fn gather_archive_kmod_and_deps( + name: &str, + kmod_src_root: &Path, + kmod_dst_root: &Path, + context: &KmodContext, + paths_seen: &mut HashSet, + cpio_state: &mut cpio::ArchiveState, + mut cpio_writer: W, +) -> io::Result<()> { + let root_mod = match context.find(name) { + None => return Err(io::Error::from(io::ErrorKind::NotFound)), + Some(m) if m.status == ModuleStatus::Builtin => { + dout!("{} builtin", name); + return Ok(()); + }, + Some(m) => m, + }; + + // Fail if root mod or hard deps are missing. + // No recursive checking here; modules.dep entry is complete + // and doesn't contain Builtins. + let kmod_dst = kmod_dst_root.join(&root_mod.rel_path); + if paths_seen.insert(kmod_dst.clone()) { + let kmod_src = kmod_src_root.join(&root_mod.rel_path); + archive_kmod_path( + &kmod_src, + &kmod_dst, + paths_seen, + cpio_state, + &mut cpio_writer + )?; + } else { + dout!("skipping duplicate kmod {:?} and all deps", &kmod_dst); + return Ok(()); + } + + for dep_path in root_mod.hard_deps_paths.iter() { + let kmod_dst = kmod_dst_root.join(&dep_path); + if paths_seen.insert(kmod_dst.clone()) { + let kmod_src = kmod_src_root.join(&dep_path); + archive_kmod_path( + &kmod_src, + &kmod_dst, + paths_seen, + cpio_state, + &mut cpio_writer + )?; + } else { + dout!("skipping duplicate kmod {:?}", &kmod_dst); + } + } + + // Attempt to pull in soft and weak dependencies for root_mod. + // Not sure if we should be checking root_mod dependents. + for soft_mod in root_mod.soft_deps_pre.iter() + .chain(root_mod.soft_deps_post.iter()) + .chain(root_mod.weak_deps.iter()) { + let m = match context.find(soft_mod) { + None => { + dout!("{:?} soft / weak kernel dep not found", soft_mod); + continue; + }, + Some(m) if m.status == ModuleStatus::Builtin => continue, + Some(m) => m, + }; + let kmod_dst = kmod_dst_root.join(&m.rel_path); + if paths_seen.insert(kmod_dst.clone()) { + let kmod_src = kmod_src_root.join(&m.rel_path); + match archive_kmod_path( + &kmod_src, + &kmod_dst, + paths_seen, + cpio_state, + &mut cpio_writer + ) { + Err(e) if e.kind() == io::ErrorKind::NotFound => { + dout!("{:?} soft / weak kernel dep missing", &kmod_src); + continue; + }, + Err(e) => return Err(e), + Ok(_) => {}, + } + } else { + dout!("skipping duplicate kmod {:?}", &kmod_dst); + } + } + Ok(()) +} + +fn gather_archive_kmods( + conf: &HashMap, + kmods: &Vec, + paths_seen: &mut HashSet, + cpio_state: &mut cpio::ArchiveState, + mut cpio_writer: W, +) -> io::Result<()> { + let krel = rapido::conf_src_or_host_kernel_vers(&conf)?; + let kmod_dst_root = PathBuf::from("/usr/lib/modules/").join(&krel); + let kmod_src_root = match conf.get("KERNEL_INSTALL_MOD_PATH") { + // should assert that KERNEL_SRC is set? + Some(kmp) if !kmp.is_empty() => PathBuf::from(kmp).join(format!("lib/modules/{krel}")), + None | Some(_) if kmod_dst_root.exists() => kmod_dst_root.clone(), + None | Some(_) => { + // assume that we have a non-Tumbleweed system + PathBuf::from("/lib/modules/").join(&krel) + }, + }; + + archive_kmods_symlink(paths_seen, cpio_state, &mut cpio_writer)?; + + let kmod_ctx = match KmodContext::new(&kmod_src_root) { + Err(e) => return Err(io::Error::new(io::ErrorKind::InvalidInput, e)), + Ok(ctx) => ctx, + }; + + for name in kmods.iter() { + match gather_archive_kmod_and_deps( + &name, + &kmod_src_root, + &kmod_dst_root, + &kmod_ctx, + paths_seen, + cpio_state, + &mut cpio_writer + ) { + Err(e) if e.kind() == io::ErrorKind::NotFound => { + return Err(io::Error::new( + io::ErrorKind::NotFound, + format!("{} missing from: {:?}", name, kmod_src_root) + )); + }, + Err(e) => return Err(e), + Ok(_) => {}, + }; + } + + // add module_data_paths inside initrd + for file_name in MODULE_DB_FILES.iter() { + let data_dst_path = kmod_dst_root.join(file_name); + if paths_seen.insert(data_dst_path.clone()) { + let data_src_path = kmod_src_root.join(file_name); + match archive_kmod_path( + &data_src_path, + &data_dst_path, + paths_seen, + cpio_state, + &mut cpio_writer + ) { + Err(e) if e.kind() == io::ErrorKind::NotFound => { + dout!("Module data path {:?} missing", data_src_path); + // TODO: only install required, and return error if missing + }, + Err(e) => return Err(e), + Ok(_) => {}, + } + } + } + + Ok(()) +} + +fn gather_archive_data( + data: &mut GatherData, + paths_seen: &mut HashSet, + cpio_state: &mut cpio::ArchiveState, + mut cpio_writer: W, +) -> io::Result<()> { + + // walk each src entry and append any newly found dirs for later traversal + while let Some(item) = data.items.get(data.off) { + data.off += 1; + + let src_md = match fs::symlink_metadata(&item.src) { + Ok(md) => md, + Err(e) if e.kind() == io::ErrorKind::NotFound => { + return Err(io::Error::new( + io::ErrorKind::NotFound, + format!("{:?} missing", item.src) + )); + } + Err(e) => return Err(e), + }; + let src_amd = cpio::ArchiveMd::from(cpio_state, &src_md)?; + + if !paths_seen.insert(item.dst.clone()) { + dout!("ignoring seen data path: {:?}", &item.dst); + continue; + } + if item.flags & GATHER_ITEM_IGNORE_PARENT == 0 { + gather_archive_dirs( + item.dst.parent(), + &src_amd, + paths_seen, + cpio_state, + &mut cpio_writer + )?; + } + + match src_amd.mode & cpio::S_IFMT { + // add any subdirs to gather list + cpio::S_IFDIR => { + cpio::archive_path( + cpio_state, + &item.dst, + &src_amd, + &mut cpio_writer + )?; + dout!("archived data dir: {:?}→{:?}", item.src, item.dst); + + let mut entries = fs::read_dir(&item.src)? + .map(|res| res.map(|e| e.file_name())) + .collect::, io::Error>>()?; + // sort for reproducibility + entries.sort(); + + let cs = item.src.clone(); + let cd = item.dst.clone(); + for entry in entries { + // "." and ".." are filtered by fs::read_dir() + data.items.push(GatherItem { + src: cs.join(&entry), + dst: cd.join(&entry), + // we don't need to check for parent dir existence, as + // we just archived it. + flags: GATHER_ITEM_IGNORE_PARENT, + }); + } + }, + // dataless files can use archive_path + cpio::S_IFREG if src_amd.len > 0 => { + let f = fs::OpenOptions::new().read(true).open(&item.src)?; + cpio::archive_file( + cpio_state, + &item.dst, + &src_amd, + &f, + &mut cpio_writer + )?; + dout!("archived data file: {:?}→{:?}", item.src, item.dst); + }, + cpio::S_IFLNK => { + let tgt = fs::read_link(&item.src)?; + // XXX don't follow data symlinks to archive their targets + cpio::archive_symlink( + cpio_state, + &item.dst, + &src_amd, + &tgt, + &mut cpio_writer + )?; + dout!("archived data symlink: {:?}→{:?}", item.src, item.dst); + }, + _ => { + cpio::archive_path( + cpio_state, + &item.dst, + &src_amd, + &mut cpio_writer + )?; + dout!("archived data path: {:?}→{:?}", item.src, item.dst); + }, + }; + } + + Ok(()) +} + +fn gather_manifest_entries( + conf: &HashMap, + state: &mut CutState, +) -> io::Result<()> { + while let Some(fest) = state.manifests.names.get(state.manifests.off) { + state.manifests.off += 1; + + let got = match path_stat(&fest, &MANIFEST_PATHS) { + Err(e) => { + if let GatherEnt::NameTry(_) = fest { + continue; + } + return Err(e); + } + Ok(g) => g, + }; + let f = fs::OpenOptions::new().read(true).open(&got.path)?; + let mut fest_map = HashMap::new(); + if let Err(e) = kv_conf::kv_conf_process_append_separate( + io::BufReader::new(f), + &conf, + &mut fest_map, + ) { + eprintln!("failed to process manifest {:?}: {:?}", got.path, e); + return Err(e); + } + + // Only a few options are supported ATM. In future we may wish to + // support manifests with dependency manifests, but that might get + // a little ugly WRT ordering (e.g. autorun sequence in reverse). + for k in MANIFEST_KEYS { + match fest_map.remove(k) { + Some(v) => match args_process_one(k, Some(&v), state) { + Err(e) => { + eprintln!("manifest {:?} entry {}={}: {}", got.path, k, v, e); + return Err(io::Error::new( + io::ErrorKind::InvalidInput, + e.to_string() + )); + } + Ok(()) => {}, + } + None => {}, + } + } + if !fest_map.is_empty() { + eprintln!( + "Manifest {:?} unsupported: {:?}\nSupported keys: {:?}", + got.path, + fest_map, + MANIFEST_KEYS + ); + return Err(io::Error::from(io::ErrorKind::InvalidInput)); + } + } + Ok(()) +} + +fn populate_default_symlinks( + paths_seen: &HashSet, + cpio_state: &mut cpio::ArchiveState, + mut cpio_writer: W, +) -> io::Result<()> { + let amd = cpio::ArchiveMd{ + nlink: 1, + mode: cpio::S_IFLNK | 0o777, + uid: 0, + gid: 0, + mtime: 0, + rmajor: 0, + rminor: 0, + len: 0, + }; + + // on Tumbleweed, bash won't start without this symlink. /lib64 may already + // exist (e.g. it's a regular dir on Leap), so conditionally create it. + let p = Path::new("/lib64"); + if !paths_seen.contains(p) { + let tgt = Path::new("/usr/lib64"); + cpio::archive_symlink(cpio_state, p, &amd, tgt, &mut cpio_writer)?; + } + // xfstests (and others) hardcode /bin/bash + let p = Path::new("/bin"); + if !paths_seen.contains(p) { + let tgt = Path::new("/usr/bin"); + cpio::archive_symlink(cpio_state, p, &amd, tgt, &mut cpio_writer)?; + } + // kernel request_module() runs CONFIG_MODPROBE_PATH. On 15.6 it's under... + let p = Path::new("/sbin"); + if !paths_seen.contains(p) { + let tgt = Path::new("/usr/sbin"); + cpio::archive_symlink(cpio_state, p, &amd, tgt, &mut cpio_writer)?; + } else { + let p = Path::new("/sbin/modprobe"); + let tgt = Path::new("/usr/sbin/modprobe"); + if !paths_seen.contains(p) && paths_seen.contains(tgt) { + cpio::archive_symlink(cpio_state, p, &amd, tgt, &mut cpio_writer)?; + } + } + Ok(()) +} + +struct CutState { + cpio_output_arg: Option, + bins: Gather, + libs: Gather, + kmods: Vec, + data: GatherData, + autoruns: u32, + manifests: Gather, +} + +fn args_usage(params: &[Argument]) { + argument::print_help("rapido-cut", "", params); +} + +fn args_process_one(name: &str, value: Option<&str>, state: &mut CutState) -> argument::Result<()> { + // unwrap: callers ensure value is Some if arg requires one + match name { + "output" => state.cpio_output_arg = Some(PathBuf::from(value.unwrap())), + "install" => { + let mut files: Vec = value + .unwrap() + .split_whitespace() + .map(|f| GatherEnt::Name(f.to_string())) + .collect(); + state.bins.names.append(&mut files); + } + "try-install" | "try_install" => { + let mut files: Vec = value + .unwrap() + .split_whitespace() + .map(|f| GatherEnt::NameTry(f.to_string())) + .collect(); + state.bins.names.append(&mut files); + } + "kmods" => { + let kmod_parsed: argument::Result> = value + .unwrap() + .split_whitespace() + .map(|f| { + f.parse().map_err(|_| argument::Error::InvalidValue { + value: f.to_owned(), + expected: String::from("MODULES must be utf-8 strings"), + }) + }) + .collect(); + state.kmods.append(&mut kmod_parsed?); + } + "include" => { + let mut iter = value.unwrap().split_whitespace(); + while let Some(src) = iter.next() { + match iter.next() { + None => return Err(argument::Error::InvalidValue { + value: src.to_string(), + expected: String::from("SRC DEST pairs"), + }), + Some(d) => { + let dst = PathBuf::from(d); + if !dst.is_absolute() { + return Err(argument::Error::InvalidValue { + value: d.to_string(), + expected: String::from( + "DEST paths must be absolute" + ), + }); + } + let gi = GatherItem { + src: PathBuf::from(src), + dst, + flags: 0, + }; + // optimization: rsc paths first to speed up rapido-vm + if gi.dst.starts_with("/rapido-rsc") { + state.data.items.insert(0, gi); + } else { + state.data.items.push(gi); + } + }, + }; + } + } + "autorun" => { + for file in value.unwrap().split_whitespace() { + let src = PathBuf::from(file); + if !src.is_file() { + return Err(argument::Error::InvalidValue { + value: file.to_owned(), + expected: String::from("file missing"), + }); + } + let dst = match src.file_name() { + None => return Err( + argument::Error::InvalidValue { + value: file.to_owned(), + expected: String::from("bad file"), + } + ), + Some(n) if n.to_str().is_none() => return Err( + argument::Error::InvalidValue { + value: file.to_owned(), + expected: String::from("bad file"), + } + ), + Some(n) => PathBuf::from(&format!( + "/rapido_autorun/{:03}-{}", + state.autoruns, + n.to_str().unwrap() + )), + }; + // TODO: it'd be better if we place these after the rapido-rsc + // entries, so that the boot time rsc check is faster. + + state.data.items.push( + GatherItem { src, dst, flags: 0 } + ); + state.autoruns += 1; + } + } + "manifest" => { + let mut manifests: Vec = value + .unwrap() + .split_whitespace() + .map(|f| GatherEnt::Name(f.to_string())) + .collect(); + state.manifests.names.append(&mut manifests); + } + "help" => return Err(argument::Error::PrintHelp), + _ => unreachable!(), + }; + Ok(()) +} + +fn args_process(state: &mut CutState) -> argument::Result<()> { + let params = &[ + Argument::value( + "output", + "INITRAMFS", + "Write initramfs archive to this file path." + ), + Argument::value( + "install", + "FILES", + "Space separated list of files to archive. ELF dependencies are gathered too." + ), + Argument::value( + "try-install", + "FILES", + "List of files to archive, if present, along with ELF dependencies." + ), + Argument::value( + "kmods", + "MODULES", + "List of kernel modules to install with dependencies.", + ), + Argument::value( + "include", + "SRC_PATH DEST_PATH", + "List of path pairs to install recursively.", + ), + Argument::value( + "autorun", + "PROGRAM", + "List of files to execute on VM boot, in order.", + ), + Argument::value( + "manifest", + "FILES", + "List of manifest files, as an alternative to params, e.g. install=bash", + ), + Argument::short_flag('h', "help", "Print help message."), + ]; + + let args = env::args().skip(1); // skip binary name + let match_res = argument::set_arguments(args, params, |name, value| { + args_process_one(name, value, state) + }); + + if let Err(e) = match_res { + args_usage(params); + return Err(e); + } + + Ok(()) +} + +fn main() -> io::Result<()> { + let mut state = CutState { + cpio_output_arg: None, + bins: Gather { + names: vec!( + GatherEnt::NameDst(RAPIDO_INIT_PATH, "/rdinit"), + // rapido-init core deps + GatherEnt::NameStatic("mount"), + GatherEnt::NameStatic("setsid"), + GatherEnt::NameStatic("bash"), + ), + off: 0, + }, + libs: Gather { + names: vec!(), + off: 0, + }, + // kmods currently only tracks user-requested modules. + // Dependencies are omitted and missing mods aren't tracked. + kmods: vec!(), + data: GatherData { + items: vec!( + GatherItem { + src: PathBuf::from(RAPIDO_BASH_RC_PATH), + dst: PathBuf::from("/rapido.rc"), + flags: GATHER_ITEM_IGNORE_PARENT, + }, + ), + off: 0, + }, + autoruns: 0, + manifests: Gather { + names: vec!(), + off: 0, + }, + }; + + let conf = match rapido::host_rapido_conf_open(rapido::RAPIDO_CONF_PATH) { + Err(e) if e.kind() == io::ErrorKind::NotFound => { + eprintln!("no rapido.conf, using defaults"); + rapido::conf_defaults() + // TODO: archive empty rapido.conf? + }, + Err(e) => { + eprintln!("failed to open rapido.conf: {:?}", e); + return Err(e); + }, + Ok((f, p)) => { + let mut conf = rapido::conf_defaults(); + if let Err(e) = kv_conf::kv_conf_process_append( + io::BufReader::new(f), + &mut conf + ) { + eprintln!("failed to process {:?}: {:?}", p, e); + return Err(e); + } + + // TODO: immediately archive rapido.conf while still open here? + state.data.items.push( + GatherItem { + src: p, + dst: PathBuf::from("/rapido.conf"), + flags: GATHER_ITEM_IGNORE_PARENT, + } + ); + conf + }, + }; + + match args_process(&mut state) { + Ok(p) => p, + Err(argument::Error::PrintHelp) => return Ok(()), + Err(e) => return Err(io::Error::new(io::ErrorKind::InvalidInput, e.to_string())), + }; + + // manifests files carry install/include/kmod/autorun directives + gather_manifest_entries(&conf, &mut state)?; + + state.kmods.extend( + rapido::conf_kmod_deps(&conf).into_iter().map(|s| s.to_string()) + ); + if state.kmods.len() > 0 { + // TODO only install if we have non-builtin kmods! + state.bins.names.extend([GatherEnt::NameStatic("modprobe")]); + } + + let cpio_props = cpio::ArchiveProperties{ + // Attempt 4K file data alignment within archive for Btrfs/XFS reflinks + data_align: 4096, + ..cpio::ArchiveProperties::default() + }; + let mut cpio_state = cpio::ArchiveState::new(&cpio_props); + let cpio_out_path = match state.cpio_output_arg { + Some(p) => p, + // unwrap: DRACUT_OUT set in conf_defaults() + None => PathBuf::from(&conf.get("DRACUT_OUT").unwrap()), + }; + + let mut cpio_writer = match fs::OpenOptions::new() + .read(false) + .write(true) + .create(true) + // for rapido we normally want to truncate any existing output file + .truncate(true) + .open(&cpio_out_path) { + Err(e) => { + eprintln!("failed to open output at {:?}: {}", cpio_out_path, e); + return Err(e); + }, + Ok(f) => io::BufWriter::new(f), + }; + + // @libs_seen is an optimization to avoid resolving already-seen elf deps. + let mut libs_seen: HashSet = HashSet::new(); + // avoid archiving already-archived paths + let mut paths_seen: HashSet = HashSet::new(); + + // optimization: rapido-rsc paths are parsed by rapido-vm so put them first + gather_archive_data( + &mut state.data, + &mut paths_seen, + &mut cpio_state, + &mut cpio_writer + )?; + + // process bins before libs, as they may add to libs *and* bins + gather_archive_bins( + &mut state.bins, + &mut state.libs, + &mut libs_seen, + &mut paths_seen, + &mut cpio_state, + &mut cpio_writer + )?; + + gather_archive_libs( + &mut state.libs, + &mut libs_seen, + &mut paths_seen, + &mut cpio_state, + &mut cpio_writer + )?; + + gather_archive_kmods( + &conf, + &state.kmods, + &mut paths_seen, + &mut cpio_state, + &mut cpio_writer + )?; + + populate_default_symlinks(&paths_seen, &mut cpio_state, &mut cpio_writer)?; + + let len = cpio::archive_trailer(&mut cpio_state, &mut cpio_writer)?; + cpio_writer.flush()?; + println!("initramfs {} written ({} bytes)", cpio_out_path.display(), len); + + Ok(()) +} + +#[cfg(test)] +mod tests { + use super::*; + use std::io::Read; + + struct TempDir { + pub dir: PathBuf, + pub dirname: String, + } + impl TempDir { + pub fn new() -> TempDir { + let mut b = [0u8; 16]; + let mut dirname = String::from("test-rapido-cut-"); + fs::File::open("/dev/urandom").unwrap().read_exact(&mut b).unwrap(); + for i in &b { + dirname.push_str(&format!("{:02x}", i)); + } + fs::create_dir(&dirname).unwrap(); + TempDir { dir: PathBuf::from(&dirname), dirname } + } + } + + impl Drop for TempDir { + fn drop(&mut self) { + assert!(self.dir.is_dir()); + fs::remove_dir_all(&self.dir).unwrap(); + } + } + + #[test] + fn test_gather_manifest() { + let conf = rapido::conf_defaults(); + let td = TempDir::new(); + let tfest = format!("{}/testmanifest.fest", td.dirname); + fs::write( + &tfest, + "install=\"bash\"\ninclude=\"${VM_NET_CONF} /net\"\n" + ).expect("failed to write manifest"); + + let mut state = CutState{ + cpio_output_arg: None, + bins: Gather { + names: vec!(GatherEnt::NameStatic("ls")), + off: 0, + }, + libs: Gather { + names: vec!(), + off: 0, + }, + kmods: vec!(), + data: GatherData { + items: vec!(), + off: 0, + }, + autoruns: 0, + manifests: Gather { + names: vec!(GatherEnt::Name(tfest.clone())), + off: 0, + }, + }; + gather_manifest_entries(&conf, &mut state) + .expect("failed to parse manifest"); + + assert_eq!( + state.bins.names, + vec!( + GatherEnt::NameStatic("ls"), + GatherEnt::Name("bash".to_string()), + ) + ); + assert_eq!( + state.data.items, + vec!(GatherItem{ + src: PathBuf::from(conf.get("VM_NET_CONF").unwrap()), + dst: PathBuf::from("/net"), + flags: 0, + }) + ); + + // parse again, this time with one unsupported key + fs::write(&tfest, "unsupported=v\ninstall=mkdir") + .expect("failed to write manifest"); + state.manifests.off = 0; + gather_manifest_entries(&conf, &mut state) + .expect_err("parse bad manifest"); + } +} diff --git a/src/bin/rapido-init.rs b/src/bin/rapido-init.rs new file mode 100644 index 0000000..10bc30d --- /dev/null +++ b/src/bin/rapido-init.rs @@ -0,0 +1,444 @@ +// SPDX-License-Identifier: (GPL-2.0 OR GPL-3.0) +// Copyright (C) 2025 SUSE LLC +use std::io::{self, Write}; +use std::ffi::OsString; +use std::fs; +use std::collections::HashMap; +use std::os::unix; +use std::process::Command; +use std::str; + +// we expect it in root on VMs +const RAPIDO_CONF: &str = "/rapido.conf"; + +fn init_mount(do_debugfs: bool, do_virtfs: bool) -> io::Result<()> { + let mounts = [ + ["-t", "proc", "-o", "nosuid,noexec,nodev", "proc", "/proc"], + ["-t", "sysfs", "-o", "nosuid,noexec,nodev", "sysfs", "/sys"], + ["-t", "devtmpfs", "-o", "nosuid,noexec", "devtmpfs", "/dev"], + ["-t", "devpts", "-o", "nosuid,noexec", "devpts", "/dev/pts"], // needed? + ["-t", "tmpfs", "-o", "mode=1777,noexec,nosuid,nodev", "tmpfs", "/dev/shm"], // needed? + ["-t", "tmpfs", "-o", "mode=755,noexec,nosuid,nodev", "tmpfs", "/run"], + ["-t", "tmpfs", "-o", "noexec,nosuid,nodev", "tmpfs", "/tmp"], + ]; + // sys_fsmount in the future, when we can do it without dependency bloat? + for mount_args in mounts { + fs::create_dir_all(mount_args.last().unwrap())?; + let status = Command::new("mount") + .args(&mount_args) + .status() + .expect("failed to execute mount command"); + if !status.success() { + println!("mount failed"); + return Err(io::Error::from(io::ErrorKind::BrokenPipe)); + } + } + + if do_debugfs { + let status = Command::new("mount") + .args(&["-t", "debugfs", "debugfs", "/sys/kernel/debug/"]) + .status() + .expect("failed to execute mount command"); + if !status.success() { + println!("debugfs mount failed - ignoring"); + } + } + + if do_virtfs { + fs::create_dir_all("/host")?; + let status = Command::new("mount") + .args(&["-t", "9p", "host0", "/host"]) + .status() + .expect("failed to execute mount command"); + if !status.success() { + println!("9p mount failed - ignoring"); + } + } + + Ok(()) +} + +#[derive(PartialEq)] +#[derive(Debug)] +struct KcliArgs<'a> { + rapido_hostname: Option<&'a str>, + rapido_vm_num: Option<&'a str>, + rapido_tap_mac: Option>, + systemd_machine_id: Option<&'a str>, + console: Option<&'a str>, +} + +fn kcli_parse(kcmdline: &[u8]) -> io::Result { + let mut args = KcliArgs { + rapido_hostname: None, + rapido_vm_num: None, + rapido_tap_mac: None, + systemd_machine_id: None, + console: None, + }; + + // We know exactly what we're looking for, so don't bother with flexible + // parsing via e.g. kv-conf. + // It'd be nice if we could construct these match arrays at compile time + // from the corresponding "key = " strings. For now they're vim compiled + // via: s/\(.\)/b'\1', /g + + for w in kcmdline.split(|c| matches!(c, b' ')) { + match w { + // rapido.hostname + [b'r', b'a', b'p', b'i', b'd', b'o', b'.', + b'h', b'o', b's', b't', b'n', b'a', b'm', b'e', b'=', val @ ..] => { + args.rapido_hostname = match str::from_utf8(val) { + Err(_) => { + return Err(io::Error::from(io::ErrorKind::InvalidData)); + }, + Ok(s) => Some(s), + }; + }, + // rapido.vm_num + [b'r', b'a', b'p', b'i', b'd', b'o', b'.', + b'v', b'm', b'_', b'n', b'u', b'm', b'=', val @ ..] => { + args.rapido_vm_num = match str::from_utf8(val) { + Err(_) => { + return Err(io::Error::from(io::ErrorKind::InvalidData)); + }, + Ok(s) => Some(s), + }; + + }, + // rapido.mac.= + [b'r', b'a', b'p', b'i', b'd', b'o', b'.', + b'm', b'a', b'c', b'.', tap_mac_kv @ ..] => { + let (tap, mac) = match str::from_utf8(tap_mac_kv) { + Err(_) => { + return Err(io::Error::from(io::ErrorKind::InvalidData)); + }, + Ok(s) if !s.contains('=') => { + return Err(io::Error::from(io::ErrorKind::InvalidData)); + }, + Ok(s) => s.split_once('=').unwrap(), + }; + let map = match args.rapido_tap_mac { + None => HashMap::from([ (tap, mac) ]), + Some(mut m) => { + m.insert(tap, mac); + m + }, + }; + args.rapido_tap_mac = Some(map); + }, + // systemd.machine_id + [b's', b'y', b's', b't', b'e', b'm', b'd', b'.', + b'm', b'a', b'c', b'h', b'i', b'n', b'e', b'_', b'i', b'd', b'=', + val @ ..] => { + args.systemd_machine_id = match str::from_utf8(val) { + Err(_) => { + return Err(io::Error::from(io::ErrorKind::InvalidData)); + }, + Ok(s) => Some(s), + }; + }, + // console + [b'c', b'o', b'n', b's', b'o', b'l', b'e', b'=', val @ ..] => { + args.console = match str::from_utf8(val) { + Err(_) => { + return Err(io::Error::from(io::ErrorKind::InvalidData)); + }, + Ok(s) => Some(s), + }; + }, + [ _unused @ .. ] => {}, + }; + } + + Ok(args) +} + +// FIXME: if all modules are builtin then rapido-cut won't install modprobe +fn kmods_load(kmods: &Vec<&str>) -> io::Result<()> { + if kmods.len() > 0 { + match Command::new("modprobe") + .env("PATH", "/usr/sbin:/usr/bin:/sbin:/bin") + .arg("-a") + .args(kmods) + .status() { + Err(e) => { + eprintln!("modprobe error: {:?}", e); + return Err(io::Error::from(io::ErrorKind::BrokenPipe)); + }, + Ok(status) if !status.success() => { + println!("modprobe failed: {:?}", status); + return Err(io::Error::from(io::ErrorKind::BrokenPipe)); + }, + Ok(_) => {}, + }; + } + + Ok(()) +} + +fn init_hostname(kcli_args: &KcliArgs) -> io::Result { + let hostname: String = match kcli_args.rapido_hostname { + None => { + let mut h = String::from("rapido"); + h.push_str(kcli_args.rapido_vm_num.unwrap()); + h + }, + Some(hd) => { + match hd.split_once('.') { + Some((h, d)) => { + fs::write("/proc/sys/kernel/domainname", d)?; + h.to_string() + }, + None => hd.to_string(), + } + }, + }; + + fs::write("/proc/sys/kernel/hostname", &hostname)?; + // don't set_env(HOSTNAME), pass it to new processes via Command::env + + Ok(hostname) +} + +fn init_network(kcli_args: &KcliArgs) -> io::Result<()> { + // TODO: add dirs to cpio + fs::create_dir_all("/run/systemd/")?; + fs::create_dir_all("/etc/systemd/")?; + + let mut vm_netdir = String::from("/rapido-rsc/net/vm"); + vm_netdir.push_str(kcli_args.rapido_vm_num.unwrap()); + unix::fs::symlink(&vm_netdir, "/etc/systemd/network")?; + + match &kcli_args.rapido_tap_mac { + Some(map) => for (tap, mac) in map { + let mut f = match fs::OpenOptions::new() + .write(true) + .append(true) + .create(false) + .open(format!("{}/{}.network", vm_netdir, tap)) { + Err(_) => continue, + Ok(f) => f, + }; + + write!(f, "\n[Match]\nMACAddress={}", mac)?; + }, + None => {}, + } + + let mut f = fs::OpenOptions::new() + .write(true) + .append(true) + .create(true) + .open("/etc/systemd/network/lo.network")?; + write!(f, "[Match]\nName=lo")?; + + match kcli_args.systemd_machine_id { + None => { + eprintln!("systemd.machine_id missing from kcli"); + Err(io::Error::from(io::ErrorKind::InvalidInput)) + }, + Some(mid) => fs::write("/etc/machine-id", mid), + }?; + + let status = Command::new("/usr/lib/systemd/systemd-udevd") + .args(&["--daemon"]) + .status() + .expect("failed to execute systemd-udevd"); + if !status.success() { + eprintln!("systemd-udevd failed to start"); + return Err(io::Error::from(io::ErrorKind::BrokenPipe)); + } + + let mut entries = fs::read_dir("/sys/class/net/")? + .map(|res| res.map(|e| e.path().into_os_string())) + .collect::, io::Error>>()?; + + let mut udevadm_args = vec!(OsString::from("trigger")); + udevadm_args.append(&mut entries); + let status = Command::new("udevadm") + .args(udevadm_args) + .status() + .expect("failed to execute udevadm"); + if !status.success() { + eprintln!("udevadm failed"); + return Err(io::Error::from(io::ErrorKind::BrokenPipe)); + } + + let mut f = fs::OpenOptions::new() + .write(true) + .append(true) + .create(true) + .open("/etc/passwd")?; + write!( + f, + "systemd-network:x:482:482:systemd Network Management:/:/sbin/nologin\n" + )?; + + let status = Command::new("setsid") + .args(&["--fork", "/usr/lib/systemd/systemd-networkd"]) + .status() + .expect("failed to execute systemd-networkd via setsid"); + if !status.success() { + eprintln!("systemd-networkd failed to start"); + return Err(io::Error::from(io::ErrorKind::BrokenPipe)); + } + + println!("Waiting for network to come online..."); + let status = Command::new("/usr/lib/systemd/systemd-networkd-wait-online") + .args(&["--timeout=20"]) + .status() + .expect("failed to execute systemd-networkd-wait-online"); + if !status.success() { + eprintln!("systemd-networkd-wait-online failed"); + return Err(io::Error::from(io::ErrorKind::BrokenPipe)); + } + + Ok(()) +} + +fn init_shell(hostname: String) -> io::Result<()> { + // rapido.rc starts subsequent autorun scripts + // TODO future: allow for starting binary autorun payloads instead + let mut spawned = Command::new("setsid") + .args(&["--ctty", "--", "bash", "--rcfile", "/rapido.rc", "-i"]) + .envs([ + // RAPIDO_INIT indicates this (non-Dracut) init to vm_autorun, etc. + ("RAPIDO_INIT", "0.1"), + ("PATH", "/usr/sbin:/usr/bin:/sbin:/bin:."), + ("TERM", "linux"), + ("HOSTNAME", &hostname), + ("PS1", format!("{}:${{PWD}}# ", hostname).as_str()) + ]) + .spawn() + .expect("failed to execute bash via setsid"); + match spawned.wait() { + Err(e) => { + eprintln!("bash error {:?}", e); + return Err(io::Error::from(io::ErrorKind::BrokenPipe)); + }, + Ok(status) if status.success() => {}, + Ok(status) => eprintln!("bash ended with status {}", status), + } + Ok(()) +} + +fn init_shutdown() -> io::Result<()> { + fs::write("/proc/sys/kernel/sysrq", "1\n")?; + fs::write("/proc/sysrq-trigger", "o\n")?; + std::thread::sleep(std::time::Duration::from_secs(20)); + Ok(()) +} + +fn init_main() -> io::Result<()> { + eprintln!("Starting rapido-init..."); + + let f = match fs::File::open(RAPIDO_CONF) { + Ok(f) => f, + Err(e) => { + println!("failed to open {}: {}", RAPIDO_CONF, e); + return Err(e); + }, + }; + let mut reader = io::BufReader::new(f); + let conf = match kv_conf::kv_conf_process(&mut reader) { + Ok(c) => c, + Err(e) => { + println!("failed to process {}: {:?}", RAPIDO_CONF, e); + return Err(e); + }, + }; + + let has_net = match fs::symlink_metadata("/rapido-rsc/net") { + Err(_) => false, + Ok(md) => md.is_dir(), + }; + let has_dyn_debug = conf.contains_key("DYN_DEBUG_MODULES") || conf.contains_key("DYN_DEBUG_FILES"); + let has_virtfs = conf.contains_key("VIRTFS_SHARE_PATH"); + + let mut kmods = rapido::conf_kmod_deps(&conf); + if has_net { + kmods.extend(&["virtio_net", "af_packet"]); + } + kmods_load(&kmods)?; + + init_mount(has_dyn_debug, has_virtfs)?; + + let kcmdline = fs::read("/proc/cmdline")?; + let kcli_args = kcli_parse(&kcmdline)?; + + if kcli_args.rapido_vm_num.is_none() { + println!("/proc/cmdline missing rapido.vm_num"); + return Err(io::Error::from(io::ErrorKind::InvalidInput)); + } + + let hostname = init_hostname(&kcli_args)?; + + if has_net { + init_network(&kcli_args)?; + } + + init_shell(hostname)?; + + Ok(()) +} + +fn main() -> io::Result<()> { + match init_main() { + Err(e) => { + eprintln!("init failed: {:?}", e); + }, + Ok(_) => { + eprintln!("rapido-init completed, shutting down..."); + }, + } + + init_shutdown() +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_kcli_parse() { + let kcli = b"rapido.vm_num=3"; + assert_eq!( + kcli_parse(kcli).expect("kcli_parse failed"), + KcliArgs { + rapido_vm_num: Some("3"), + rapido_hostname: None, + rapido_tap_mac: None, + systemd_machine_id: None, + console: None, + } + ); + + let kcli = b"rapido.vm_num=3 rapido.hostname=rapido1 rapido.vm_num=4 console=ttyS0"; + assert_eq!( + kcli_parse(kcli).expect("kcli_parse failed"), + KcliArgs { + rapido_vm_num: Some("4"), + rapido_hostname: Some("rapido1"), + rapido_tap_mac: None, + systemd_machine_id: None, + console: Some("ttyS0"), + } + ); + + let kcli = b"rapido.mac.tap1=b8:ac:24:45:c5:01 rapido.mac.tap2=b8:ac:24:45:c5:02"; + assert_eq!( + kcli_parse(kcli).expect("kcli_parse failed"), + KcliArgs { + rapido_vm_num: None, + rapido_hostname: None, + rapido_tap_mac: Some(HashMap::from([ + ("tap1", "b8:ac:24:45:c5:01"), + ("tap2", "b8:ac:24:45:c5:02"), + ])), + systemd_machine_id: None, + console: None, + } + ); + } +} diff --git a/src/bin/rapido-vm.rs b/src/bin/rapido-vm.rs new file mode 100644 index 0000000..6dcfcea --- /dev/null +++ b/src/bin/rapido-vm.rs @@ -0,0 +1,479 @@ +// SPDX-License-Identifier: (GPL-2.0 OR GPL-3.0) +// Copyright (C) 2025 SUSE LLC +use std::collections::HashMap; +use std::fs; +use std::hash::{DefaultHasher, Hasher}; +use std::io::{self, BufRead}; +use std::os::unix::fs::FileTypeExt; +use std::path; +use std::process; +use std::str; + +use rapido::host_kernel_vers; + +fn vm_is_running(vm_pid_file: &str) -> io::Result { + let mut pid = String::new(); + let n = match fs::File::open(vm_pid_file) { + Err(e) if e.kind() == io::ErrorKind::NotFound => return Ok(false), + Err(e) => return Err(e), + Ok(f) => io::BufReader::new(f).read_line(&mut pid)?, + }; + + let pid = pid.trim_end(); + if n < 1 || n > 16 || usize::from_str_radix(pid, 10).is_err() { + eprintln!("bad qemu pid file data ({} bytes): {}", n, pid); + return Err(io::Error::from(io::ErrorKind::InvalidInput)); + } + + return match fs::symlink_metadata(&format!("/proc/{}", pid)) { + Err(e) if e.kind() == io::ErrorKind::NotFound => return Ok(false), + Err(e) => return Err(e), + Ok(_) => Ok(true), + }; +} + +// Generate a reproducible MAC address based on vm_num and vm_tap IDs. +// We can reuse the generic hashmap hash lib for this \o/ +fn vm_mac_gen(vm_num: u64, vm_tap: &str) -> String { + let mut hasher = DefaultHasher::new(); + hasher.write_u64(vm_num); + hasher.write(vm_tap.as_bytes()); + let h: u64 = hasher.finish(); + format!("b8:{:02x}:{:02x}:{:02x}:{:02x}:{:02x}", + h & 0xff, (h >> 8) & 0xff, (h >> 16) & 0xff, (h >> 24) & 0xff, + (h >> 32) & 0xff) +} + +struct VmResources { + cpus: u32, + mem: String, + net: bool, +} + +// Process a cpio path. @VmResources is updated for any corresponding rapido-rsc +// path. Any path under rapido-rsc returns true (inc. parent), otherwise false. +// XXX these paths assume leading '/' are stripped, which cpio.rs does before +// writing cpio entries. Dracut also lacks leading '/' due to its staging area. +fn vm_resource_line_process(line: &[u8], rscs: &mut VmResources) -> io::Result { + match line { + // vim compiled from string via: s/\(.\)/b'\1', /g + // rapido-rsc/cpu/ + [b'r', b'a', b'p', b'i', b'd', b'o', b'-', b'r', b's', b'c', b'/', + b'c', b'p', b'u', b'/', val @ ..] => { + rscs.cpus = match str::from_utf8(val) { + Ok(s) => match s.parse::() { + Err(_) => Err(io::Error::from(io::ErrorKind::InvalidData)), + Ok(sp) => Ok(sp), + }, + Err(_) => Err(io::Error::from(io::ErrorKind::InvalidData)), + }?; + } + // rapido-rsc/mem/ + [b'r', b'a', b'p', b'i', b'd', b'o', b'-', b'r', b's', b'c', b'/', + b'm', b'e', b'm', b'/', val @ ..] => { + rscs.mem = match str::from_utf8(val) { + Err(_) => Err(io::Error::from(io::ErrorKind::InvalidData)), + Ok(s) => { + match s.rsplit_once(['m', 'M', 'g', 'G']) { + None if s.parse::().is_ok() => Ok(s.to_string()), + Some((n, u)) if n.parse::().is_ok() && u == "" => { + Ok(s.to_string()) + }, + None | Some((_, _)) => { + Err(io::Error::from(io::ErrorKind::InvalidData)) + }, + } + }, + }?; + }, + // rapido-rsc/qemu/custom_args + [b'r', b'a', b'p', b'i', b'd', b'o', b'-', b'r', b's', b'c', b'/', + b'q', b'e', b'm', b'u', b'/', + b'c', b'u', b's', b't', b'o', b'm', b'_', b'a', b'r', b'g', b's'] => { + // obsolete way for images to inject their own qemu params. + // cut scripts should instead assert that the args required are set. + eprintln!("ignoring qemu custom_args presence"); + }, + // rapido-rsc/net + [b'r', b'a', b'p', b'i', b'd', b'o', b'-', b'r', b's', b'c', b'/', + b'n', b'e', b't'] => { + rscs.net = true; + }, + // catch any unprocessed rapido-rsc path, so we return true. + [b'r', b'a', b'p', b'i', b'd', b'o', b'-', b'r', b's', b'c', .. ] => {}, + // not a rapido-rsc path. + _ => return Ok(false), + } + + // got a valid rapido-rsc path or parent directory + Ok(true) +} + +fn vm_resources_get(initramfs_img: &str) -> io::Result { + // rapido defaults + let mut rscs = VmResources{ + cpus: 2, + mem: "512M".to_string(), + net: false, + }; + + let f = fs::OpenOptions::new().read(true).open(&initramfs_img)?; + // BufReader I/O is ugly for archive_walk: read(8k) + seek(next_hdr_off) + // next_hdr_off is negative unless file len > 8k - (HDR_LEN + namesize) + let reader = io::BufReader::new(f); + let mut archive_walker = cpio::archive_walk(reader)?; + let mut in_rapido_rsc_path = false; + while let Some(archive_ent) = archive_walker.next() { + let ent = match archive_ent { + Err(e) => { + eprintln!("archive traversal failed"); + return Err(e); + }, + Ok(ent) => ent, + }; + + match vm_resource_line_process( + // namesize includes nul. cpio ensures 0< namesize < PATH_MAX+1 + &ent.name[0 .. (ent.namesize as usize) - 1], + &mut rscs + )? { + true => in_rapido_rsc_path = true, + // optimization: break loop when leaving rapido-rsc/ paths. + // rsc entries must be placed together in the archive and can be + // placed at the start to minimise traversal. + false if in_rapido_rsc_path => break, + false => {}, + } + } + + Ok(rscs) +} + +struct QemuArgs<'a> { + qemu_bin: &'a str, + kernel_img: String, + console: &'a str, + params: Vec<&'a str>, +} + +fn vm_qemu_args_get(conf: &HashMap) -> io::Result { + let mut params = vec!(); + let mut qemu_args: Option = None; + + //let (kconfig: String, krel: Option<&str>) = match conf.get("KERNEL_SRC") { + let (kconfig, krel) = match conf.get("KERNEL_SRC") { + Some(ks) if !ks.is_empty() => (format!("{ks}/.config"), None), + None | Some(_) => match conf.get("KERNEL_RELEASE") { + Some(rel) => (format!("/boot/config-{rel}"), Some(rel.clone())), + None => { + let rel = host_kernel_vers()?; + (format!("/boot/config-{rel}"), Some(rel.to_string())) + }, + }, + }; + + match fs::symlink_metadata("/dev/kvm") { + Ok(md) if md.file_type().is_char_device() => { + params.extend(["-machine", "accel=kvm"]) + }, + Err(_) | Ok(_) => {}, + }; + + let ksrc = conf.get("KERNEL_SRC"); + + let f = fs::OpenOptions::new().read(true).open(&kconfig)?; + for line in io::BufReader::new(f).lines().map_while(Result::ok) { + if line == "CONFIG_X86_64=y" { + qemu_args = Some(QemuArgs{ + kernel_img: match ksrc { + Some(ks) if !ks.is_empty() => format!("{ks}/arch/x86/boot/bzImage"), + // krel always set without KERNEL_SRC + None | Some(_) => format!("/boot/vmlinuz-{}", krel.unwrap()), + }, + qemu_bin: "qemu-system-x86_64", + console: "ttyS0", + params, + }); + break; + } else if line == "CONFIG_ARM64=y" { + params.extend([ + "-machine", "virt,gic-version=host", + "-cpu", "host" + ]); + qemu_args = Some(QemuArgs{ + kernel_img: match ksrc { + Some(ks) => format!("{ks}/arch/arm64/boot/Image"), + None => format!("/boot/Image-{}", krel.unwrap()), + }, + qemu_bin: "qemu-system-aarch64", + console: "ttyAMA0", + params, + }); + break; + } else if line == "CONFIG_PPC64=y" { + qemu_args = Some(QemuArgs{ + kernel_img: match ksrc { + Some(ks) => format!("{ks}/arch/powerpc/boot/zImage"), + None => format!("/boot/vmlinux-{}", krel.unwrap()), + }, + qemu_bin: "qemu-system-ppc64", + console: "hvc0", + params, + }); + break; + } else if line == "CONFIG_S390=y" { + qemu_args = Some(QemuArgs{ + kernel_img: match ksrc { + Some(ks) => format!("{ks}/arch/s390/boot/bzImage"), + None => format!("/boot/bzImage-{}", krel.unwrap()), + }, + qemu_bin: "qemu-system-s390x", + console: "ttysclp0", + params, + }); + break; + } + } + + if qemu_args.is_none() { + eprintln!("architecture not yet supported, please add it"); + return Err(io::Error::from(io::ErrorKind::Unsupported)); + } + + let qemu_args = qemu_args.unwrap(); + if fs::symlink_metadata(&qemu_args.kernel_img).is_err() { + eprintln!( + "no kernel image present at {}, wrong detection or build needed", + qemu_args.kernel_img + ); + return Err(io::Error::from(io::ErrorKind::NotFound)); + } + + return Ok(qemu_args); +} + +fn vm_start(vm_num: u64, vm_pid_file: &str, initramfs_img: &str, conf: &HashMap) -> io::Result<()> { + let mut qemu_args = vm_qemu_args_get(conf)?; + let mut kcmdline = format!( + "rdinit=/rdinit console={} rapido.vm_num={}", + qemu_args.console, + vm_num + ); + let net_conf_dir = format!( + "{}/vm{}", + conf.get("VM_NET_CONF").expect("VM_NET_CONF not set"), + vm_num + ); + + match fs::read_to_string(format!("{net_conf_dir}/hostname")) { + Err(e) if e.kind() == io::ErrorKind::NotFound => {}, + Err(e) => return Err(e), + Ok(hn) => { + kcmdline.push_str(&format!(" rapido.hostname={}", hn.trim_end())); + }, + } + + let rscs = match vm_resources_get(&initramfs_img) { + Err(e) if e.kind() == io::ErrorKind::NotFound => { + eprintln!("no initramfs image at {initramfs_img}. Run \"cut_X\" script?"); + return Err(e); + }, + Err(e) => return Err(e), + Ok(r) => r, + }; + + let cpus = format!("{},sockets={},cores=1,threads=1", rscs.cpus, rscs.cpus); + qemu_args.params.extend([ + "-smp", &cpus, + "-m", &rscs.mem, + "-kernel", &qemu_args.kernel_img, + "-initrd", initramfs_img, + "-pidfile", vm_pid_file, + ]); + + // params is Vec<&str>, so stash generated net Strings elsewhere + let mut net_params_stash: Vec = vec!(); + + if !rscs.net { + qemu_args.params.extend(["-net", "none"]); + } else { + // networkd needs a hex unique ID (for dhcp leases, etc.) + // TODO not sure about length, but vm.sh uses md5sum of vm_num, so + // prepend some garbage :shrug: + kcmdline.push_str( + &format!(" net.ifnames=0 systemd.machine_id=2af1d0cafe2afid0{:016x}", vm_num) + ); + + let mut i = 0; + for entry in fs::read_dir(&net_conf_dir)? { + let entry = entry?; + let path = entry.path(); + match path.extension() { + None => continue, + Some(e) if e.as_encoded_bytes() != b"network" => continue, + Some(_) => {}, + } + let vm_tap = match path.file_stem() { + None => continue, + Some(t) => match t.to_str() { + None => continue, + Some(t_str) => t_str, + }, + }; + // Only attempt to add host IFF_TAP (0x02) devices as + // qemu netdevs. This allows for extra VM virtual device + // creation and configuration via net-conf. + const IFF_TAP: usize = 0x02; + let mut tp = path::PathBuf::from("/sys/class/net/"); + tp.push(vm_tap); + tp.push("tun_flags"); + let tun_flags = match fs::read(&tp) { + Err(_) => continue, + Ok(flags) => match str::from_utf8(&flags) { + Err(_) => continue, + Ok(flags_str) => { + if let Some(s) = flags_str.strip_prefix("0x") { + usize::from_str_radix(s.trim_end(), 16) + } else { + eprintln!("{:?} missing expected 0x flags prefix", tp); + return Err(io::Error::from(io::ErrorKind::InvalidData)); + } + }, + }, + }; + match tun_flags { + Err(_) => { + eprintln!("unexpected tun_flags at {:?}", tp); + return Err(io::Error::from(io::ErrorKind::InvalidData)); + }, + Ok(flags_val) if flags_val & IFF_TAP != IFF_TAP => continue, + Ok(_) => {}, + } + + let tap_mac = vm_mac_gen(vm_num, vm_tap); + + // TODO append net conf MAC [match] to cpio, instead of at boot + // time via kcmdline. + kcmdline.push_str(&format!(" rapido.mac.{vm_tap}={tap_mac}")); + + net_params_stash.extend([ + "-device".to_string(), + format!("virtio-net,netdev=if{i},mac={tap_mac}"), + "-netdev".to_string(), + format!("tap,id=if{i},script=no,downscript=no,ifname={vm_tap}"), + ]); + i += 1; + } + if i == 0 { + eprintln!("no valid TAP devices found in {net_conf_dir}"); + } + } + + if let Some(kp) = conf.get("QEMU_EXTRA_KERNEL_PARAMS") { + kcmdline.push_str(&format!(" {kp}")); + } + + qemu_args.params.extend(["-append", &kcmdline]); + + let virtfs_sp: String; + if let Some(vsp) = conf.get("VIRTFS_SHARE_PATH") { + virtfs_sp = format!("local,path={vsp},mount_tag=host0,security_model=mapped,id=host0"); + qemu_args.params.extend(["-virtfs", &virtfs_sp]); + } + + if let Some(qea) = conf.get("QEMU_EXTRA_ARGS") { + qemu_args.params.extend(qea.split(&[' ', '\n'])); + } + + let mut spawned_vm = process::Command::new(qemu_args.qemu_bin) + .args(qemu_args.params) + .args(net_params_stash) + .spawn() + .expect("failed to execute qemu"); + match spawned_vm.wait() { + Err(e) => { + // TODO stdout / stderr lost here? + eprintln!("{} failed: {:?}", qemu_args.qemu_bin, e); + Err(io::Error::from(io::ErrorKind::BrokenPipe)) + }, + Ok(status) if !status.success() => { + eprintln!("{} exited with status: {}", qemu_args.qemu_bin, status); + Ok(()) + }, + Ok(_) => Ok(()), + } +} + +fn main() -> io::Result<()> { + let conf = match rapido::host_rapido_conf_open(rapido::RAPIDO_CONF_PATH) { + Err(e) if e.kind() == io::ErrorKind::NotFound => { + rapido::conf_defaults() + }, + Err(e) => return Err(e), + Ok((f, p)) => { + let mut conf = rapido::conf_defaults(); + if let Err(e) = kv_conf::kv_conf_process_append( + io::BufReader::new(f), + &mut conf + ) { + eprintln!("failed to process {:?}: {:?}", p, e); + return Err(e); + } + conf + }, + }; + // unwrap: both keys have defaults set + let pid_dir = conf.get("QEMU_PID_DIR").unwrap(); + let initramfs_img = conf.get("DRACUT_OUT").unwrap(); + + // 1k rapido VM limit is arbitrary + for vm_num in 1..1000 { + let vm_pid_file = format!("{}/rapido_vm{}.pid", pid_dir, vm_num); + if !vm_is_running(&vm_pid_file)? { + return vm_start(vm_num, &vm_pid_file, &initramfs_img, &conf); + } + } + + Ok(()) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_vm_resources_parse() { + let line = b"rapido-rsc/cpu/5"; + let mut rscs = VmResources{ + cpus: 0, + mem: String::new(), + net: false, + }; + assert_eq!(vm_resource_line_process(line, &mut rscs).unwrap(), true); + assert_eq!(rscs.cpus, 5); + + let line = b"rapido-rsc/mem/5G"; + assert_eq!(vm_resource_line_process(line, &mut rscs).unwrap(), true); + assert_eq!(rscs.mem, "5G"); + + let line = b"rapido-rsc/mem/5m"; + assert_eq!(vm_resource_line_process(line, &mut rscs).unwrap(), true); + assert_eq!(rscs.mem, "5m"); + + let line = b"rapido-rsc/mem/5t"; + assert!(vm_resource_line_process(line, &mut rscs).is_err()); + + let line = b"rapido-rsc/net"; + assert_eq!(vm_resource_line_process(line, &mut rscs).unwrap(), true); + assert_eq!(rscs.net, true); + + let line = b"not/a/root/rapido-rsc/path"; + assert_eq!(vm_resource_line_process(line, &mut rscs).unwrap(), false); + + let line = b"rapido-rsc/mem/5GG"; + assert!(vm_resource_line_process(line, &mut rscs).is_err()); + + let line = b"rapido-rsc/mem/5mG"; + assert!(vm_resource_line_process(line, &mut rscs).is_err()); + } +} diff --git a/src/cpio/Cargo.lock b/src/cpio/Cargo.lock new file mode 100644 index 0000000..83f9b25 --- /dev/null +++ b/src/cpio/Cargo.lock @@ -0,0 +1,7 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "cpio" +version = "0.1.0" diff --git a/src/cpio/Cargo.toml b/src/cpio/Cargo.toml new file mode 100644 index 0000000..6c6dce4 --- /dev/null +++ b/src/cpio/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "cpio" +version = "0.1.0" +edition = "2021" +license = "(GPL-2.0 OR GPL-3.0)" + +[dependencies] +# std only diff --git a/src/cpio/src/lib.rs b/src/cpio/src/lib.rs new file mode 100644 index 0000000..3544d13 --- /dev/null +++ b/src/cpio/src/lib.rs @@ -0,0 +1,842 @@ +// SPDX-License-Identifier: (GPL-2.0 OR GPL-3.0) +// Copyright (C) 2021-2025 SUSE S.A. + +use std::convert::TryInto; +use std::convert::TryFrom; +use std::fs; +use std::io; +use std::io::prelude::*; +use std::os::unix::ffi::OsStrExt; +use std::os::unix::fs::MetadataExt as UnixMetadataExt; +use std::path::Path; +use std::str; + +macro_rules! NEWC_HDR_FMT { + () => { + concat!( + "{magic}{ino:08X}{mode:08X}{uid:08X}{gid:08X}{nlink:08X}", + "{mtime:08X}{filesize:08X}{major:08X}{minor:08X}{rmajor:08X}", + "{rminor:08X}{namesize:08X}{chksum:08X}" + ) + }; +} + +// Don't print debug messages on release builds... +#[cfg(debug_assertions)] +macro_rules! dout { + ($($l:tt)*) => { println!($($l)*); } +} +#[cfg(not(debug_assertions))] +macro_rules! dout { + ($($l:tt)*) => {}; +} + +pub const NEWC_HDR_LEN: u64 = 110; +pub const PATH_MAX: u64 = 4096; + +// format: octal posix mode bits +pub const S_IFIFO: u32 = 0o010000; +pub const S_IFCHR: u32 = 0o020000; +pub const S_IFDIR: u32 = 0o040000; +pub const S_IFBLK: u32 = 0o060000; +pub const S_IFREG: u32 = 0o100000; +pub const S_IFLNK: u32 = 0o120000; +pub const S_IFSOCK: u32 = 0o140000; +pub const S_IFMT: u32 = 0o170000; + +pub struct ArchiveProperties { + // first inode number to use. @ArchiveState.ino increments from this. + pub initial_ino: u32, + // if non-zero, then align file data segments to this offset by injecting + // extra zeros after the filename string terminator. + pub data_align: u32, + // When injecting extra zeros into the filename field for data alignment, + // ensure that it doesn't exceed this size. The linux kernel will ignore + // files where namesize is larger than PATH_MAX, hence the need for this. + pub namesize_max: u32, + // if the archive is being appended to the end of an existing file, then + // @initial_data_off is used when calculating @data_align alignment. + pub initial_data_off: u64, + // mtime, uid and gid to use for archived inodes, instead of the value + // reported by stat. + pub fixed_mtime: Option, + pub fixed_uid: Option, + pub fixed_gid: Option, +} + +impl ArchiveProperties { + pub fn default() -> ArchiveProperties { + ArchiveProperties { + initial_ino: 0, // match GNU cpio numbering + data_align: 0, + namesize_max: PATH_MAX as u32, + initial_data_off: 0, + fixed_mtime: None, + fixed_uid: None, + fixed_gid: None, + } + } +} + +pub struct ArchiveState<'a> { + // static properties, provided during initialization + props: &'a ArchiveProperties, + // offset from the start of this archive + off: u64, + // next mapped inode number, used instead of source file inode numbers to + // ensure reproducibility. Inode numbers all share the same dev (major=0 + // minor=0) namespace. + ino: u32, +} + +impl ArchiveState<'_> { + pub fn new(props: &ArchiveProperties) -> ArchiveState { + ArchiveState { + off: 0, + ino: props.initial_ino, + props, + } + } +} + +// fs::Metadata is private. This allows callers to explicitly set md +#[derive(PartialEq, Debug)] +pub struct ArchiveMd { + // ino increments for each cpio entry from props.initial_ino + // nlink is hardcoded 1 for files, retained for dirs + pub nlink: u32, + pub mode: u32, + // may be overridden by props.fixed_uid/gid/mtime + pub uid: u32, + pub gid: u32, + pub mtime: u32, + // major and minor hardcoded 0 + pub rmajor: u32, + pub rminor: u32, + pub len: u32, +} + +impl ArchiveMd { + pub fn from(state: &ArchiveState, md: &fs::Metadata) -> io::Result { + let mtime: u32 = match state.props.fixed_mtime { + Some(t) => t, + None => match u32::try_from(md.mtime()) { + // check for 2106 epoch overflow + Err(_) => return Err(io::Error::new( + io::ErrorKind::InvalidInput, + "mtime too large for cpio", + )), + Ok(m) => m, + } + }; + + let mode = md.mode(); + let (nlink, rmajor, rminor) = match mode & S_IFMT { + // careful, this is confusingly not a bitwise or + S_IFBLK | S_IFCHR => { + // Linux kernel uses 32-bit dev_t, encoded as mmmM MMmm. glibc + // uses 64-bit MMMM Mmmm mmmM MMmm, which is compatible. + let rd = md.rdev(); + ( + u32::try_from(md.nlink()).ok(), + (((rd >> 32) & 0xfffff000) | ((rd >> 8) & 0x00000fff)) as u32, + (((rd >> 12) & 0xffffff00) | (rd & 0x000000ff)) as u32, + ) + }, + S_IFREG => { + if md.nlink() > 1 { + // For simplicity's sake, hardlinks are archived like + // regular files, i.e. they're always assigned a unique + // inode number and carry a corresponding data segment (if + // present). Use symlinks, or if you really need hardlinks + // then create them during init. + eprintln!( + "(nlink={}) hardlink file data may be duplicated", + md.nlink() + ); + } + (Some(1), 0, 0) + }, + _ => (u32::try_from(md.nlink()).ok(), 0, 0), + }; + + let len = match u32::try_from(md.len()) { + Err(_) => return Err(io::Error::new( + io::ErrorKind::InvalidInput, + "file too large for newc", + )), + Ok(l) => l, + }; + + if nlink.is_none() { + return Err(io::Error::new( + io::ErrorKind::InvalidInput, + "nlink too large", + )); + } + + Ok(ArchiveMd { + mode, + uid: match state.props.fixed_uid { + Some(u) => u, + None => md.uid(), + }, + gid: match state.props.fixed_gid { + Some(g) => g, + None => md.gid(), + }, + nlink: nlink.unwrap(), + mtime, + rmajor, + rminor, + len, + }) + } +} + +fn path_trim_prefixes(path: &Path) -> io::Result<&[u8]> { + let outpath = match path.strip_prefix("/") { + Ok(p) => { + if p.as_os_str().as_bytes().len() == 0 { + path // retain '/' + } else { + p + } + } + Err(_) => path, + }; + + let fname = match outpath.strip_prefix("./") { + Ok(p) => { + let out = p.as_os_str().as_bytes(); + if out.len() == 0 { + outpath.as_os_str().as_bytes() // retain './' and '.' paths + } else { + out + } + } + Err(_) => outpath.as_os_str().as_bytes(), + }; + + if fname.len() + 1 >= PATH_MAX.try_into().unwrap() { + return Err(io::Error::new(io::ErrorKind::InvalidInput, "path too long")); + } + + Ok(fname) +} + +pub fn archive_path( + state: &mut ArchiveState, + path: &Path, + md: &ArchiveMd, + mut writer: W, +) -> io::Result<()> { + let fname = path_trim_prefixes(path)?; + + if (md.mode & S_IFMT == S_IFREG && md.len > 0) || md.mode & S_IFMT == S_IFLNK { + return Err(io::Error::new( + io::ErrorKind::InvalidInput, + "archive_path does not support data files or symlinks", + )); + } + + dout!("archiving {} with mode {:o}", path.display(), md.mode); + + write!( + writer, + NEWC_HDR_FMT!(), + magic = "070701", + ino = { + let i = state.ino; + state.ino += 1; + i + }, + mode = md.mode, + uid = md.uid, + gid = md.gid, + nlink = md.nlink, + mtime = md.mtime, + filesize = 0, + major = 0, + minor = 0, + rmajor = md.rmajor, + rminor = md.rminor, + namesize = fname.len() + 1, + chksum = 0 + )?; + state.off += NEWC_HDR_LEN; + + writer.write_all(fname)?; + state.off += fname.len() as u64; + + let mut seek_len: i64 = 1; // fname nulterm + let padding_len = archive_padlen(state.off + seek_len as u64, 4); + seek_len += padding_len as i64; + { + let z = vec![0u8; seek_len.try_into().unwrap()]; + writer.write_all(&z)?; + } + state.off += seek_len as u64; + + Ok(()) +} + +pub fn archive_symlink( + state: &mut ArchiveState, + path: &Path, + md: &ArchiveMd, + symlink_tgt: &Path, + mut writer: W, +) -> io::Result<()> { + let fname = path_trim_prefixes(path)?; + let tgt_bytes = symlink_tgt.as_os_str().as_bytes(); + let datalen: u32 = { + let d: usize = tgt_bytes.len(); + if d >= PATH_MAX.try_into().unwrap() { + return Err(io::Error::new( + io::ErrorKind::InvalidInput, + "symlink path too long", + )); + } + d.try_into().unwrap() + }; + // no zero terminator for symlink target path + + if md.mode & S_IFMT != S_IFLNK { + return Err(io::Error::from(io::ErrorKind::InvalidInput)); + } + + dout!("archiving {} with mode {:o}", path.display(), md.mode); + + write!( + writer, + NEWC_HDR_FMT!(), + magic = "070701", + ino = { + let i = state.ino; + state.ino += 1; + i + }, + mode = md.mode, + uid = md.uid, + gid = md.gid, + nlink = md.nlink, + mtime = md.mtime, + filesize = datalen, + major = 0, + minor = 0, + rmajor = md.rmajor, + rminor = md.rminor, + namesize = fname.len() + 1, + chksum = 0 + )?; + state.off += NEWC_HDR_LEN; + + writer.write_all(fname)?; + state.off += fname.len() as u64; + + let mut seek_len: i64 = 1; // fname nulterm + let padding_len = archive_padlen(state.off + seek_len as u64, 4); + seek_len += padding_len as i64; + { + let z = vec![0u8; seek_len.try_into().unwrap()]; + writer.write_all(&z)?; + } + state.off += seek_len as u64; + + writer.write_all(tgt_bytes)?; + state.off += u64::from(datalen); + let dpad_len: usize = archive_padlen(state.off, 4).try_into().unwrap(); + write!(writer, "{pad:.padlen$}", padlen = dpad_len, pad = "\0\0\0")?; + state.off += dpad_len as u64; + + Ok(()) +} + +pub fn archive_file( + state: &mut ArchiveState, + path: &Path, + md: &ArchiveMd, + mut reader: R, + mut writer: W, +) -> io::Result<()> { + let fname = path_trim_prefixes(path)?; + let mut data_align_seek: u32 = 0; + + if md.mode & S_IFMT != S_IFREG { + return Err(io::Error::new(io::ErrorKind::InvalidInput, "not a file")); + } + + dout!("archiving file {} with mode {:o}", path.display(), md.mode); + + if state.props.data_align > 0 && md.len > state.props.data_align { + // XXX we're "bending" the newc spec a bit here to inject zeros + // after fname to provide data segment alignment. These zeros are + // accounted for in the namesize, but some applications may only + // expect a single zero-terminator (and 4 byte alignment). GNU cpio + // and Linux initramfs handle this fine as long as PATH_MAX isn't + // exceeded. + data_align_seek = { + let len: u64 = archive_padlen( + state.props.initial_data_off + state.off + NEWC_HDR_LEN + fname.len() as u64 + 1, + u64::from(state.props.data_align), + ); + let padded_namesize = len + fname.len() as u64 + 1; + if padded_namesize > u64::from(state.props.namesize_max) { + dout!( + "Suboptimal {} alignment: padded {} > {} namesize maximum", + path.display(), + padded_namesize, + state.props.namesize_max + ); + 0 + } else { + len.try_into().unwrap() + } + }; + } + + write!( + writer, + NEWC_HDR_FMT!(), + magic = "070701", + ino = { + let i = state.ino; + state.ino += 1; + i + }, + mode = md.mode, + uid = md.uid, + gid = md.gid, + // see hardlink note in ArchiveMd + nlink = md.nlink, + mtime = md.mtime, + filesize = md.len, + major = 0, + minor = 0, + rmajor = md.rmajor, + rminor = md.rminor, + namesize = fname.len() + 1 + data_align_seek as usize, + chksum = 0 + )?; + state.off += NEWC_HDR_LEN; + + writer.write_all(fname)?; + state.off += fname.len() as u64; + + let mut seek_len: i64 = 1; // fname nulterm + if data_align_seek > 0 { + seek_len += data_align_seek as i64; + assert_eq!(archive_padlen(state.off + seek_len as u64, 4), 0); + } else { + let padding_len = archive_padlen(state.off + seek_len as u64, 4); + seek_len += padding_len as i64; + } + { + let z = vec![0u8; seek_len.try_into().unwrap()]; + writer.write_all(&z)?; + } + state.off += seek_len as u64; + + // io::copy() can reflink: https://github.com/rust-lang/rust/pull/75272 \o/ + if md.len > 0 { + let copied = io::copy(&mut reader, &mut writer)?; + if copied != u64::from(md.len) { + dout!("copied {}, expected {}", copied, md.len); + return Err(io::Error::new( + io::ErrorKind::UnexpectedEof, + "copy returned unexpected length", + )); + } + state.off += u64::from(md.len); + let dpad_len: usize = archive_padlen(state.off, 4).try_into().unwrap(); + write!(writer, "{pad:.padlen$}", padlen = dpad_len, pad = "\0\0\0")?; + state.off += dpad_len as u64; + } + + Ok(()) +} + +pub fn archive_padlen(off: u64, alignment: u64) -> u64 { + (alignment - (off & (alignment - 1))) % alignment +} + +pub fn archive_trailer( + state: &mut ArchiveState, + mut writer: W +) -> io::Result { + const FNAME: &str = "TRAILER!!!"; + const FNAME_LEN: usize = FNAME.len() + 1; + + write!( + writer, + NEWC_HDR_FMT!(), + magic = "070701", + ino = 0, + mode = 0, + uid = 0, + gid = 0, + nlink = 1, + mtime = 0, + filesize = 0, + major = 0, + minor = 0, + rmajor = 0, + rminor = 0, + namesize = FNAME_LEN, + chksum = 0 + )?; + state.off += NEWC_HDR_LEN; + + let padding_len = archive_padlen(state.off + FNAME_LEN as u64, 4); + write!( + writer, + "{}\0{pad:.padlen$}", + FNAME, + padlen = padding_len as usize, + pad = "\0\0\0" + )?; + state.off += FNAME_LEN as u64 + padding_len as u64; + + Ok(state.off) +} + +#[derive(PartialEq, Debug)] +pub struct ArchiveEnt { + // no ino in ArchiveMd. Could place here if needed. + pub md: ArchiveMd, + // namesize includes the nul-term + pub namesize: u32, + pub name: [u8; (PATH_MAX + 1) as usize], + // add &name[0 .. namesize] slice here, or leave it up to caller? +} + +pub struct ArchiveWalker { + reader: R, +} + +pub fn archive_walk( + reader: R, +) -> io::Result> { + // kernel extraction skips zeros until header. we don't. + Ok(ArchiveWalker{ + reader, + }) +} + +fn archive_read_newc_md(hdr_md: &[u8]) -> io::Result<(ArchiveMd, u32)> { + // 8 hex chars per field. + let mut md_iter = hdr_md.chunks_exact(8).map(|f| { + if let Ok(s) = str::from_utf8(f) { + if let Ok(u) = u32::from_str_radix(s, 16) { + return Ok(u); + } + } + Err(io::Error::new(io::ErrorKind::InvalidData, "invalid hdr field")) + }); + + // unwrap here because successfully read NEWC_HDR_LEN bytes + let md = ArchiveMd{ + // skip ino + mode: md_iter.nth(1).unwrap()?, + uid: md_iter.next().unwrap()?, + gid: md_iter.next().unwrap()?, + nlink: md_iter.next().unwrap()?, + mtime: md_iter.next().unwrap()?, + len: md_iter.next().unwrap()?, + // skip major/minor + rmajor: md_iter.nth(2).unwrap()?, + rminor: md_iter.next().unwrap()?, + }; + let namesize = md_iter.next().unwrap()?; + if namesize == 0 || namesize > (PATH_MAX + 1) as u32 { + return Err( + io::Error::new(io::ErrorKind::InvalidData, "invalid namesize") + ); + } + + Ok((md, namesize)) +} + +impl Iterator for ArchiveWalker { + type Item = io::Result; + fn next(&mut self) -> Option { + let mut hdr_buf = [0u8; NEWC_HDR_LEN as usize]; + // return None if we hit EOF while reading a hdr + if let Err(e) = self.reader.read_exact(&mut hdr_buf) { + return match e.kind() { + io::ErrorKind::UnexpectedEof => None, + _ => Some(Err(e)), + } + } + match hdr_buf { + // we only support newc + [b'0', b'7', b'0', b'7', b'0', b'1', hdr_md @ ..] => { + let (md, namesize) = match archive_read_newc_md(&hdr_md) { + Err(e) => return Some(Err(e)), + Ok((md, ns)) => (md, ns), + }; + let mut buf = [0u8; (PATH_MAX + 1) as usize]; + let mut fbuf = &mut buf[0 .. namesize as usize]; + if let Err(e) = self.reader.read_exact(&mut fbuf) { + return Some(Err(e)); + } + + let npad = archive_padlen(NEWC_HDR_LEN + namesize as u64, 4); + let dlen = md.len as u64; + let dpad = archive_padlen(dlen, 4); + let seeklen: i64 = (npad + dlen + dpad).try_into().unwrap(); + if let Err(e) = self.reader.seek(io::SeekFrom::Current(seeklen)) { + return Some(Err(e)); + } + if &buf[0 .. (namesize as usize) - 1] == b"TRAILER!!!" { + // cpio trailer treated the same as EOF + return None; + } + let ae = ArchiveEnt{ + md, + namesize, + name: buf, + // provide data offset here, to allow callers to grab it? + }; + Some(Ok(ae)) + }, + [ _bad_hdr @ .. ] => { + return Some(Err( + io::Error::new(io::ErrorKind::InvalidInput, "invalid newc hdr") + )); + }, + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use std::io::{Seek, Write}; + + #[test] + fn test_archive_iter() { + let mut c = io::Cursor::new(Vec::new()); + let props = ArchiveProperties::default(); + let amd = ArchiveMd{ + nlink: 1, + mode: S_IFDIR | 0o777, + uid: 1, + gid: 2, + mtime: 3, + rmajor: 4, + rminor: 5, + len: 0, + }; + let p = Path::new("hello"); + + let mut state = ArchiveState::new(&props); + archive_path(&mut state, &p, &amd, &mut c).unwrap(); + archive_trailer(&mut state, &mut c).unwrap(); + c.seek(io::SeekFrom::Start(0)).unwrap(); + + let mut aw = archive_walk(c).unwrap(); + + assert_eq!( + aw.next().unwrap().unwrap(), + ArchiveEnt{ + md: amd, + namesize: ("hello".len() + 1).try_into().unwrap(), + name: { + let mut buf = [0u8; (PATH_MAX + 1) as usize]; + let mut fbuf = &mut buf[0 .. (PATH_MAX + 1) as usize]; + fbuf.write_all("hello".as_bytes()).unwrap(); + buf + }, + } + ); + + // cpio trailer entry returns None + assert!(aw.next().is_none()); + } + + #[test] + fn test_archive_iter_bogus() { + let mut c = io::Cursor::new(Vec::new()); + + // bad magic + write!( + c, + concat!(NEWC_HDR_FMT!(), "{fname}\0"), + magic = "370701", + ino = 0, + mode = 1, + uid = 2, + gid = 3, + nlink = 4, + mtime = 5, + filesize = 0, + major = 6, + minor = 7, + rmajor = 8, + rminor = 9, + namesize = 2, + chksum = 0, + fname = "A", + ).unwrap(); + // namesize=2 is 4-byte aligned: (110 + 2) + c.seek(io::SeekFrom::Start(0)).unwrap(); + let mut aw = archive_walk(c).unwrap(); + assert_eq!( + aw.next().unwrap().unwrap_err().kind(), + io::ErrorKind::InvalidInput); + + // good magic, corrupt each field with non-hex + for corrupt_field in 0..12 { + let mut c = io::Cursor::new(Vec::new()); + + write!( + c, + concat!(NEWC_HDR_FMT!(), "{fname}\0"), + magic = "070701", + ino = 0, + mode = 1, + uid = 2, + gid = 3, + nlink = 4, + mtime = 5, + filesize = 0, + major = 6, + minor = 7, + rmajor = 8, + rminor = 9, + namesize = 2, + chksum = 0, + fname = "A", + ).unwrap(); + // namesize=2 is 4-byte aligned: (110 + 2) + + // matches above + let amd = ArchiveMd{ + mode: 1, + uid: 2, + gid: 3, + nlink: 4, + mtime: 5, + rmajor: 8, + rminor: 9, + len: 0, + }; + + let corrupt_off: u64 = "070701".len() as u64 + corrupt_field * 8; + c.seek(io::SeekFrom::Start(corrupt_off)).unwrap(); + write!(c, "not hex").unwrap(); + + c.seek(io::SeekFrom::Start(0)).unwrap(); + let mut aw = archive_walk(c).unwrap(); + let ent = aw.next().unwrap(); + + // ino, major, minor and chksum are ignored, so won't cause an error + match corrupt_field { + 0 | 7 | 8 | 12 => assert_eq!( + ent.unwrap(), + ArchiveEnt{ + md: amd, + namesize: 2, + name: { + let mut buf = [0u8; (PATH_MAX + 1) as usize]; + let mut fbuf = &mut buf[0 .. (PATH_MAX + 1) as usize]; + fbuf.write_all("A".as_bytes()).unwrap(); + buf + }, + } + ), + _ => assert_eq!( + ent.unwrap_err().kind(), + io::ErrorKind::InvalidData + ), + } + } + + // no cpio trailer; EOF should result in None + assert!(aw.next().is_none()); + } + + #[test] + fn test_archive_path_trim() { + assert_eq!( + b"hello", + path_trim_prefixes(Path::new("hello")).unwrap() + ); + assert_eq!( + b"hello", + path_trim_prefixes(Path::new("./hello")).unwrap() + ); + assert_eq!( + b"hello", + path_trim_prefixes(Path::new("//hello")).unwrap() + ); + assert_eq!(b"/", path_trim_prefixes(Path::new("/")).unwrap()); + // should prob return a single '/' for this... + assert_eq!(b"//", path_trim_prefixes(Path::new("//")).unwrap()); + } + + // archive a file with and without data, via archive_file and archive_path + #[test] + fn test_archive_file() { + let mut c = io::Cursor::new(Vec::new()); + let data = b"this is some file data"; + let amd1 = ArchiveMd{ + nlink: 1, + mode: S_IFREG | 0o777, + uid: 1, + gid: 2, + mtime: 3, + rmajor: 4, + rminor: 5, + len: 0, + }; + let amd2 = ArchiveMd{ + len: data.len() as u32, + ..amd1 + }; + + let p1 = Path::new("hello"); + let p2 = Path::new("bye"); + let props = ArchiveProperties::default(); + let mut state = ArchiveState::new(&props); + archive_path(&mut state, &p1, &amd1, &mut c).unwrap(); + archive_file(&mut state, &p2, &amd2, io::Cursor::new(data), &mut c).unwrap(); + // archive path should fail for len > 0 files + assert!(archive_path(&mut state, &p2, &amd2, &mut c).is_err()); + + c.seek(io::SeekFrom::Start(0)).unwrap(); + let mut aw = archive_walk(c).unwrap(); + + assert_eq!( + aw.next().unwrap().unwrap(), + ArchiveEnt{ + md: amd1, + namesize: (p1.as_os_str().len() + 1).try_into().unwrap(), + name: { + let mut buf = [0u8; (PATH_MAX + 1) as usize]; + let mut fbuf = &mut buf[0 .. (PATH_MAX + 1) as usize]; + fbuf.write_all(p1.as_os_str().as_encoded_bytes()).unwrap(); + buf + }, + } + ); + + assert_eq!( + aw.next().unwrap().unwrap(), + ArchiveEnt{ + md: amd2, + namesize: (p2.as_os_str().len() + 1).try_into().unwrap(), + name: { + let mut buf = [0u8; (PATH_MAX + 1) as usize]; + let mut fbuf = &mut buf[0 .. (PATH_MAX + 1) as usize]; + fbuf.write_all(p2.as_os_str().as_encoded_bytes()).unwrap(); + buf + }, + } + ); + + assert!(aw.next().is_none()); + } +} diff --git a/src/kv-conf/Cargo.toml b/src/kv-conf/Cargo.toml new file mode 100644 index 0000000..b4f821a --- /dev/null +++ b/src/kv-conf/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "kv-conf" +version = "0.1.0" +edition = "2021" +license = "(GPL-2.0 OR GPL-3.0)" + +[dependencies] +# std only diff --git a/src/kv-conf/src/lib.rs b/src/kv-conf/src/lib.rs new file mode 100644 index 0000000..c4f599f --- /dev/null +++ b/src/kv-conf/src/lib.rs @@ -0,0 +1,620 @@ +// SPDX-License-Identifier: (GPL-2.0 OR GPL-3.0) +// Copyright (C) 2025 SUSE LLC +use std::collections::HashMap; +use std::io; + +const CONF_LINE_MAX: usize = 1024 * 100; + +// substitute ${VAR} strings with values from previously seen keys in @map. +// no support for env var substitution. No callouts via $(), etc. +fn kv_var_sub(block: &str, map: &HashMap) -> Result { + // only support {} wrapped variables that we've already encountered + let mut varblock = block.split_inclusive(&['{', '}']); + if varblock.next() != Some("{") { + return Err("variables must be wrapped in {} braces"); + } + let var = varblock.next(); + if var.is_none() || !var.unwrap().ends_with("}") { + return Err("no closing brace for variable"); + } + let key = var.unwrap().strip_suffix("}").unwrap(); + + let subbed = match map.get(key) { + Some(val) => val.clone(), + None => { + return Err("invalid variable substitution: not seen"); + } + }; + //println!("subbed for {}: {}", key, subbed); + // need to retain anything that comes after the var-closing '}' + Ok(varblock.next().iter().fold(subbed, |a, b| a + b)) +} + +// process a single conf line. +// Roughly attempts to work similar to Bash variable assignment. +// Doesn't support multiple assignments on a single line. +// @line: line buffer to process. Processed data removed on return. +// @varmap: hashmap to use for any ${} variable substitution. +// returns: error or processed key-value pair. @line retains unprocessed portions. +fn kv_process( + line: &mut String, + varmap: &HashMap, +) -> Result, &'static str> { + // ignore empty / comment lines + let trimmed_line = line.trim_start(); + if trimmed_line == "" || trimmed_line.starts_with("#") { + line.clear(); + return Ok(None); + } + + // split at first '=' + let (key, val) = match trimmed_line.split_once('=') { + None => { + return Err("missing ="); + } + Some((k, v)) => (k, v), + }; + + if key == "" { + return Err("empty key"); + } + if key.contains(&['\\', '/', '\"', '\'', ' ', '\t', '$', '#', '.', '`']) { + return Err("invalid key"); + } + + #[derive(PartialEq)] + enum Quoted { + No, + Single, + Double, + } + let mut inquote = Quoted::No; + let mut escape_next = false; + let mut comment_next = false; + let mut var_next = false; + + let mut unquoted_val = String::new(); + for mut quoteblock in val.split_inclusive(&['\\', '\"', '\'', ' ', '\t', '$']) { + if escape_next { + if quoteblock.starts_with(&['\\', '\"', '\'', ' ', '\t', '$']) { + // keep as is + } else if quoteblock.starts_with('\n') && inquote != Quoted::No { + // multiline + break; + } else { + // bad escape. on bash the \ is kept if quoted, or dropped if not + return Err("bad escape"); + } + escape_next = false; + unquoted_val.push_str(quoteblock); + + if quoteblock.len() != 1 { + return Err("expected escaped char at split boundary"); + } + continue; + } + + // comment after unquoted space is valid, otherwise invalid + if comment_next { + assert!(inquote == Quoted::No); // should only get here outside of quotes + if quoteblock.starts_with("#") { + // comment + break; + } else if quoteblock.trim_start() == "" { + continue; + } else { + return Err("unexpected whitespace"); + } + } + + let var_got: String; + if var_next { + var_got = kv_var_sub(quoteblock, &varmap)?; + quoteblock = &var_got; + var_next = false; + } + + if quoteblock.ends_with("\\") && inquote != Quoted::Single { + // next special char is escaped, unless single quoted + escape_next = true; + unquoted_val.push_str(quoteblock.strip_suffix("\\").unwrap()); + } else if quoteblock.ends_with("\"") { + let mut strip_sfx = "\""; + inquote = match inquote { + Quoted::No => Quoted::Double, + Quoted::Double => Quoted::No, + Quoted::Single => { + strip_sfx = ""; + Quoted::Single + } + }; + unquoted_val.push_str(quoteblock.strip_suffix(strip_sfx).unwrap()); + } else if quoteblock.ends_with("\'") { + let mut strip_sfx = "\'"; + inquote = match inquote { + Quoted::No => Quoted::Single, + Quoted::Single => Quoted::No, + Quoted::Double => { + strip_sfx = ""; + Quoted::Double + } + }; + unquoted_val.push_str(quoteblock.strip_suffix(strip_sfx).unwrap()); + } else if quoteblock.ends_with(&[' ', '\t']) && inquote == Quoted::No { + comment_next = true; + unquoted_val.push_str(quoteblock.strip_suffix(&[' ', '\t']).unwrap()); + } else if quoteblock.ends_with("$") && inquote != Quoted::Single { + // variable substitution unless single quoted + var_next = true; + unquoted_val.push_str(quoteblock.strip_suffix("$").unwrap()); + } else if quoteblock.ends_with("\n") { + unquoted_val.push_str(quoteblock.strip_suffix("\n").unwrap()); + } else { + // EOF without newline, unquoted space, single quoted '$' + unquoted_val.push_str(quoteblock); + } + } + + // handle multi-line with possible '\' terminator + if inquote != Quoted::No { + // newline and any spaces collapsed into one space, unless escaped within " + let mut push_space = " "; + let mut ml = trimmed_line.trim_end(); + if ml.ends_with("\\") && inquote == Quoted::Double { + ml = ml.strip_suffix("\\").unwrap(); + let l = ml.len(); + ml = ml.trim_end(); + if l == ml.len() { + push_space = ""; + } + } + + *line = ml.to_string(); + line.push_str(push_space); + return Ok(None); + } + + let k = key.to_string(); + line.clear(); + Ok(Some((k, unquoted_val))) +} + +fn kv_read_line(mut rdr: R, linenum: u64, buf: &mut String) -> io::Result { + let n = rdr.read_line(buf)?; + if n == 0 && buf.len() != 0 { + return Err(io::Error::new( + io::ErrorKind::InvalidInput, + format!("line {}: unexpected EOF", linenum), + )); + } else if n > CONF_LINE_MAX { + return Err(io::Error::new( + io::ErrorKind::InvalidInput, + format!("line {}: too long", linenum), + )); + } + Ok(n) +} + +// +// Example: +// fn main() { +// let f = File::open("rapido.conf").expect("failed to open conf file"); +// let mut map: HashMap = HashMap::new(); +// let _res = kv_conf_process_append(io::BufReader::new(f), &mut map); +// } +pub fn kv_conf_process_append( + mut rdr: R, + map: &mut HashMap, +) -> io::Result<()> { + let mut buffer = String::new(); + + for linenum in 1.. { + let n = kv_read_line(&mut rdr, linenum, &mut buffer)?; + if n == 0 { + break; + } + + match kv_process(&mut buffer, map) { + Err(m) => { + return Err(io::Error::new( + io::ErrorKind::InvalidInput, + format!("line {}: {}", linenum, m), + )) + } + // buffer may be retain unprocessed data for multiline + Ok(kv) => match kv { + Some((k, v)) => map.insert(k, v), + None => None, + }, + }; + } + Ok(()) +} + +// same as kv_conf_process_append() except variable lookup is done from +// @varmap, while kv entries are stored separately in @map. +pub fn kv_conf_process_append_separate( + mut rdr: R, + varmap: &HashMap, + map: &mut HashMap, +) -> io::Result<()> { + let mut buffer = String::new(); + + for linenum in 1.. { + let n = kv_read_line(&mut rdr, linenum, &mut buffer)?; + if n == 0 { + break; + } + + match kv_process(&mut buffer, varmap) { + Err(m) => { + return Err(io::Error::new( + io::ErrorKind::InvalidInput, + format!("line {}: {}", linenum, m), + )) + } + Ok(kv) => match kv { + Some((k, v)) => map.insert(k, v), + None => None, + }, + }; + } + Ok(()) +} + +// Example: +// fn main() { +// let f = File::open("rapido.conf").expect("failed to open conf file"); +// let _kv_map = kv_conf_process(io::BufReader::new(f)); +// } +pub fn kv_conf_process(rdr: R) -> io::Result> { + let mut map: HashMap = HashMap::new(); + + kv_conf_process_append(rdr, &mut map)?; + + Ok(map) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_simple() { + let c = io::Cursor::new("key=val"); + assert_eq!( + kv_conf_process(c).expect("kv_conf_process failed"), + HashMap::from([("key".to_string(), "val".to_string())]) + ); + } + + #[test] + fn test_quoted() { + let c = io::Cursor::new("key=\"val\""); + assert_eq!( + kv_conf_process(c).expect("kv_conf_process failed"), + HashMap::from([("key".to_string(), "val".to_string())]) + ); + + let c = io::Cursor::new("key=\"val spaced\""); + assert_eq!( + kv_conf_process(c).expect("kv_conf_process failed"), + HashMap::from([("key".to_string(), "val spaced".to_string())]) + ); + + let c = io::Cursor::new("key=\'val spaced\'"); + assert_eq!( + kv_conf_process(c).expect("kv_conf_process failed"), + HashMap::from([("key".to_string(), "val spaced".to_string())]) + ); + + // XXX intentional bash divergence: don't turn quoted tabs into spaces + let c = io::Cursor::new("key=\'val\ttabbed\'"); + assert_eq!( + kv_conf_process(c).expect("kv_conf_process failed"), + HashMap::from([("key".to_string(), "val\ttabbed".to_string())]) + ); + + let c = io::Cursor::new("key=\'qu\"ot\"ed\'"); + assert_eq!( + kv_conf_process(c).expect("kv_conf_process failed"), + HashMap::from([("key".to_string(), "qu\"ot\"ed".to_string())]) + ); + + let c = io::Cursor::new("key=\"qu\'oted\""); + assert_eq!( + kv_conf_process(c).expect("kv_conf_process failed"), + HashMap::from([("key".to_string(), "qu\'oted".to_string())]) + ); + + let c = io::Cursor::new("key=\"eof_before_closing_quote"); + let e = kv_conf_process(c).expect_err("kv_conf_process passed"); + assert_eq!( + format!("{:?}", e), + "Custom { kind: InvalidInput, error: \"line 2: unexpected EOF\" }" + ); + } + + #[test] + fn test_comments() { + let c = io::Cursor::new("# this is a comment\nkey=val\n #key=newval"); + assert_eq!( + kv_conf_process(c).expect("kv_conf_process failed"), + HashMap::from([("key".to_string(), "val".to_string())]) + ); + + let c = io::Cursor::new("key=val # this is a trailing comment"); + assert_eq!( + kv_conf_process(c).expect("kv_conf_process failed"), + HashMap::from([("key".to_string(), "val".to_string())]) + ); + + let c = io::Cursor::new("key=val # this is a trailing comment "); + assert_eq!( + kv_conf_process(c).expect("kv_conf_process failed"), + HashMap::from([("key".to_string(), "val".to_string())]) + ); + + let c = io::Cursor::new("key=val\t\t# this is a trailing comment "); + assert_eq!( + kv_conf_process(c).expect("kv_conf_process failed"), + HashMap::from([("key".to_string(), "val".to_string())]) + ); + + let c = io::Cursor::new("k=not#a#comment #comment"); + assert_eq!( + kv_conf_process(c).expect("kv_conf_process failed"), + HashMap::from([("k".to_string(), "not#a#comment".to_string())]) + ); + } + + #[test] + fn test_multiple() { + let c = io::Cursor::new("key=val\nnextkey=nextval"); + assert_eq!( + kv_conf_process(c).expect("kv_conf_process failed"), + HashMap::from([ + ("key".to_string(), "val".to_string()), + ("nextkey".to_string(), "nextval".to_string()), + ]) + ); + } + + #[test] + fn test_overwrite() { + let c = io::Cursor::new("key=val\nkey=newval"); + assert_eq!( + kv_conf_process(c).expect("kv_conf_process failed"), + HashMap::from([("key".to_string(), "newval".to_string())]) + ); + } + + #[test] + fn test_var() { + let c = io::Cursor::new("k=val\nnextk=${k}"); + assert_eq!( + kv_conf_process(c).expect("kv_conf_process failed"), + HashMap::from([ + ("k".to_string(), "val".to_string()), + ("nextk".to_string(), "val".to_string()), + ]) + ); + + let c = io::Cursor::new("k=val\nnextk=abc${k}def"); + assert_eq!( + kv_conf_process(c).expect("kv_conf_process failed"), + HashMap::from([ + ("k".to_string(), "val".to_string()), + ("nextk".to_string(), "abcvaldef".to_string()), + ]) + ); + + let c = io::Cursor::new("k=val\nnextk=abc\"${k}\"def"); + assert_eq!( + kv_conf_process(c).expect("kv_conf_process failed"), + HashMap::from([ + ("k".to_string(), "val".to_string()), + ("nextk".to_string(), "abcvaldef".to_string()), + ]) + ); + + let c = io::Cursor::new("k=val\nnextk=123\nfink=a${k}b${nextk}c"); + assert_eq!( + kv_conf_process(c).expect("kv_conf_process failed"), + HashMap::from([ + ("k".to_string(), "val".to_string()), + ("nextk".to_string(), "123".to_string()), + ("fink".to_string(), "avalb123c".to_string()), + ]) + ); + + let c = io::Cursor::new("k=val\nnextk=abc\'${k}\'def"); + assert_eq!( + kv_conf_process(c).expect("kv_conf_process failed"), + HashMap::from([ + ("k".to_string(), "val".to_string()), + ("nextk".to_string(), "abc${k}def".to_string()), + ]) + ); + } + + #[test] + fn test_escaped() { + let c = io::Cursor::new("key=val\\ spaced"); + assert_eq!( + kv_conf_process(c).expect("kv_conf_process failed"), + HashMap::from([("key".to_string(), "val spaced".to_string())]) + ); + let c = io::Cursor::new("key=val\\ #spaced\t"); + assert_eq!( + kv_conf_process(c).expect("kv_conf_process failed"), + HashMap::from([("key".to_string(), "val #spaced".to_string())]) + ); + let c = io::Cursor::new("key=val\\\ttabbed "); + assert_eq!( + kv_conf_process(c).expect("kv_conf_process failed"), + HashMap::from([("key".to_string(), "val\ttabbed".to_string())]) + ); + + let c = io::Cursor::new("key=val\\\\"); + assert_eq!( + kv_conf_process(c).expect("kv_conf_process failed"), + HashMap::from([("key".to_string(), "val\\".to_string())]) + ); + } + + #[test] + fn test_multiline_kv() { + // multiline only works with quoting, with or without trailing \ + let c = io::Cursor::new("key=\"line1 \\\nline2\""); + assert_eq!( + kv_conf_process(c).expect("kv_conf_process failed"), + HashMap::from([("key".to_string(), "line1 line2".to_string())]) + ); + + let c = io::Cursor::new("key=\"line1 \nline2\""); + assert_eq!( + kv_conf_process(c).expect("kv_conf_process failed"), + HashMap::from([("key".to_string(), "line1 line2".to_string())]) + ); + + let c = io::Cursor::new("key=\"line1\\\nline2\""); + assert_eq!( + kv_conf_process(c).expect("kv_conf_process failed"), + HashMap::from([("key".to_string(), "line1line2".to_string())]) + ); + + let c = io::Cursor::new("key=\'line1\\\nline2\'"); + assert_eq!( + kv_conf_process(c).expect("kv_conf_process failed"), + HashMap::from([("key".to_string(), "line1\\ line2".to_string())]) + ); + + let c = io::Cursor::new("key=\'line1\\ \nline2\'"); + assert_eq!( + kv_conf_process(c).expect("kv_conf_process failed"), + HashMap::from([("key".to_string(), "line1\\ line2".to_string())]) + ); + + let c = io::Cursor::new("key=\"line1\nline2\""); + assert_eq!( + kv_conf_process(c).expect("kv_conf_process failed"), + HashMap::from([("key".to_string(), "line1 line2".to_string())]) + ); + } + + #[test] + fn test_bad_key() { + let c = io::Cursor::new("ke/y=val"); + assert!(kv_conf_process(c).is_err()); + let c = io::Cursor::new("ke$y=val"); + assert!(kv_conf_process(c).is_err()); + let c = io::Cursor::new("key =val"); + assert!(kv_conf_process(c).is_err()); + let c = io::Cursor::new("\"key\"=val"); + assert!(kv_conf_process(c).is_err()); + let c = io::Cursor::new("key#=val"); + assert!(kv_conf_process(c).is_err()); + let c = io::Cursor::new("key.=val"); + assert!(kv_conf_process(c).is_err()); + let c = io::Cursor::new("key`=val"); + assert!(kv_conf_process(c).is_err()); + } + + #[test] + fn test_empty() { + let c = io::Cursor::new("key="); + assert_eq!( + kv_conf_process(c).expect("kv_conf_process failed"), + HashMap::from([("key".to_string(), "".to_string())]) + ); + + let c = io::Cursor::new(""); + assert_eq!( + kv_conf_process(c).expect("kv_conf_process failed"), + HashMap::from([]) + ); + + let c = io::Cursor::new("\n\n# comment"); + assert_eq!( + kv_conf_process(c).expect("kv_conf_process failed"), + HashMap::from([]) + ); + + let c = io::Cursor::new("="); + assert!(kv_conf_process(c).is_err()); + } + + #[test] + fn test_limit() { + let mut vec: Vec = vec![b'k', b'=', b'v', b' ', b'#']; + vec.resize(CONF_LINE_MAX - 1, b'#'); + vec.extend_from_slice(&[b'\n']); + let c = io::Cursor::new(&vec); + assert_eq!( + kv_conf_process(c).expect("kv_conf_process failed"), + HashMap::from([("k".to_string(), "v".to_string())]) + ); + + vec.truncate(CONF_LINE_MAX - 1); + vec.resize(CONF_LINE_MAX + 1, b'#'); + let c = io::Cursor::new(&vec); + assert!(kv_conf_process(c).is_err()); + } + + #[test] + fn test_append() { + let c = io::Cursor::new("key="); + let mut map = kv_conf_process(c).expect("kv_conf_process failed"); + assert_eq!(map, HashMap::from([("key".to_string(), "".to_string())])); + + map.insert("extra".to_string(), "val".to_string()); + let c = io::Cursor::new("key=overwritten\nnextkey=stuff${extra}"); + kv_conf_process_append(c, &mut map).expect("kv_conf_process_append failed"); + assert_eq!( + map, + HashMap::from([ + ("key".to_string(), "overwritten".to_string()), + ("extra".to_string(), "val".to_string()), + ("nextkey".to_string(), "stuffval".to_string()), + ]) + ); + } + + // check that a variable without braces fails with line number in Err + #[test] + fn test_err_line() { + let c = io::Cursor::new("k=val\nnextk=123\nfink=a$k"); + let e = kv_conf_process(c).expect_err("variable without braces passed"); + // we probably shouldn't rely on error fmt output stability + assert_eq!( + format!("{:?}", e), + "Custom { kind: InvalidInput, error: \"line 3: variables must be wrapped in {} braces\" }" + ); + } + + #[test] + fn test_append_varmap() { + let c = io::Cursor::new("key=val\nextra=overhere"); + let varmap = kv_conf_process(c).expect("kv_conf_process failed"); + assert_eq!( + varmap, + HashMap::from([ + ("key".to_string(), "val".to_string()), + ("extra".to_string(), "overhere".to_string()), + ]) + ); + + let mut sepmap: HashMap = HashMap::new(); + let c = io::Cursor::new("key=separate\nnextkey=stuff${extra}"); + kv_conf_process_append_separate(c, &varmap, &mut sepmap) + .expect("kv_conf_process_append_separate failed"); + assert_eq!( + sepmap, + HashMap::from([ + ("key".to_string(), "separate".to_string()), + ("nextkey".to_string(), "stuffoverhere".to_string()), + ]) + ); + } +} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..9256523 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,178 @@ +// SPDX-License-Identifier: (GPL-2.0 OR GPL-3.0) +// Copyright (C) 2025 SUSE LLC +use std::env; +use std::fs; +use std::io; +use std::collections::HashMap; +use std::path::PathBuf; +use std::str; + +// ./rapido.conf default path can be overridden by RAPIDO_CONF env var +pub const RAPIDO_CONF_PATH: &str = "rapido.conf"; +// parameters below may be overridden by rapido.conf... +// default VM network config path, also used for tap provisioning +const RAPIDO_NET_CONF_PATH: &str = "net-conf"; +// rapido-cut initramfs output path and rapido-vm QEMU input +const RAPIDO_DRACUT_OUT: &str = "initrds/myinitrd"; +// default directory to write QEMU pidfiles +const RAPIDO_QEMU_PID_DIR: &str = "initrds"; +// QEMU defaults: CLI with console redirection. Provide VMs with an RNG device. +const RAPIDO_QEMU_EXTRA_ARGS: &str = "-nographic -device virtio-rng-pci"; + +// parse /proc/version string, e.g. Linux version 6.17.0-2-default ... +fn host_kernel_vers_parse(kvers: &[u8]) -> io::Result { + match str::from_utf8(kvers) { + Err(_) => Err(io::Error::from(io::ErrorKind::InvalidData)), + Ok(s) => match s.strip_prefix("Linux version ") { + None => Err(io::Error::from(io::ErrorKind::InvalidData)), + Some(rel) => match rel.split_once([' ']) { + Some((rel, _)) => Ok(rel.to_string()), + None => Err(io::Error::from(io::ErrorKind::InvalidData)), + }, + }, + } +} + +// return the host kernel version based on /proc/version contents +pub fn host_kernel_vers() -> io::Result { + let kvers = fs::read("/proc/version")?; + host_kernel_vers_parse(&kvers) +} + +pub fn conf_src_or_host_kernel_vers( + conf: &HashMap +) -> io::Result { + match conf.get("KERNEL_SRC") { + Some(ksrc) if !ksrc.is_empty() => { + let b = fs::read(format!("{ksrc}/include/config/kernel.release"))?; + let btrimmed = match b.strip_suffix(&[b'\n']) { + Some(bt) => bt, + None => &b, + }; + Ok(String::from_utf8_lossy(btrimmed).to_string()) + }, + None | Some(_) => match conf.get("KERNEL_RELEASE") { + Some(krel) => Ok(krel.clone()), + None => host_kernel_vers(), + }, + } +} + +// return kmod dependencies based on rapido @conf qemu parameters +pub fn conf_kmod_deps(conf: &HashMap) -> Vec<&str> { + let mut deps = match conf.get("QEMU_EXTRA_ARGS") { + Some(v) if v.contains("virtio-rng-pci") => vec!("virtio_rng"), + Some(_) | None => vec!(), + }; + + if conf.get("VIRTFS_SHARE_PATH").is_some() { + deps.extend(&["9pnet", "9pnet_virtio", "9p"]); + } + + deps +} + +// return an open file handle and path for rapido.conf, which may +// be @rapido_conf_path or overridden by RAPIDO_CONF env +pub fn host_rapido_conf_open( + rapido_conf_path: &str, +) -> io::Result<(fs::File, PathBuf)> { + // env file takes precedence + match env::var("RAPIDO_CONF") { + Ok(c) => { + match fs::File::open(&c) { + Err(e) if e.kind() == io::ErrorKind::NotFound => { + Err(io::Error::new( + io::ErrorKind::InvalidInput, + format!("RAPIDO_CONF missing at {}", c) + )) + }, + Err(e) => Err(e), + Ok(f) => Ok((f, PathBuf::from(c))), + } + }, + Err(env::VarError::NotPresent) => { + let f = fs::File::open(rapido_conf_path)?; + Ok((f, PathBuf::from(rapido_conf_path))) + }, + Err(env::VarError::NotUnicode(_)) => { + Err(io::Error::from(io::ErrorKind::InvalidInput)) + }, + } +} + +pub fn conf_defaults() -> HashMap { + HashMap::from([ + ("DRACUT_OUT".to_string(), RAPIDO_DRACUT_OUT.to_string()), + ("QEMU_PID_DIR".to_string(), RAPIDO_QEMU_PID_DIR.to_string()), + ("VM_NET_CONF".to_string(), RAPIDO_NET_CONF_PATH.to_string()), + ("QEMU_EXTRA_ARGS".to_string(), RAPIDO_QEMU_EXTRA_ARGS.to_string()), + ]) +} + +#[cfg(test)] +mod tests { + use super::*; + use std::io::Read; + + struct TempDir { + pub dir: PathBuf, + pub dirname: String, + } + impl TempDir { + // create a random temporary directory under CWD. + // The directory will be cleaned up when TempDir goes out of scope. + pub fn new() -> TempDir { + let mut b = [0u8; 16]; + let mut dirname = String::from("test-rapido-lib-"); + fs::File::open("/dev/urandom").unwrap().read_exact(&mut b).unwrap(); + for i in &b { + dirname.push_str(&format!("{:02x}", i)); + } + + fs::create_dir(&dirname).unwrap(); + eprintln!("created tmp dir: {}", dirname); + TempDir { dir: PathBuf::from(&dirname), dirname } + } + } + + impl Drop for TempDir { + fn drop(&mut self) { + assert!(self.dir.is_dir()); + // scary but does not follow symlinks so should be okay + fs::remove_dir_all(&self.dir).unwrap(); + eprintln!("removed tmp dir: {}", self.dir.display()); + } + } + + #[test] + fn test_host_kernel_vers_parse() { + let line = b"Linux version 6.17.0-2-default (geeko@buildhost) (gcc (SUSE Linux) 15.2.0, GNU ld (GNU Binutils; openSUSE Tumbleweed) 2.43.1.20241209-10) #1 SMP PREEMPT_DYNAMIC Thu Oct 2 08:12:40 UTC 2025 (190326b)"; + assert_eq!(host_kernel_vers_parse(line).unwrap(), "6.17.0-2-default"); + } + + #[test] + fn test_conf_kmod_deps() { + let conf: HashMap = HashMap::from([ + ("QEMU_EXTRA_ARGS".to_string(), "-device virtio-rng-pci".to_string()) + ]); + let kmods = conf_kmod_deps(&conf); + assert!(kmods.contains(&"virtio_rng")); + } + + #[test] + fn test_conf_parse_from_defaults() { + let td = TempDir::new(); + let conf_path = format!("{}/rapido.conf", td.dirname); + fs::write(&conf_path, b"DRACUT_OUT=thisfile").unwrap(); + let mut c = conf_defaults(); + let (f, p) = host_rapido_conf_open(&conf_path).unwrap(); + kv_conf::kv_conf_process_append(io::BufReader::new(f), &mut c) + .expect("failed to process conf"); + // explicitly set by rapido.conf + assert_eq!(c.get("DRACUT_OUT"), Some("thisfile".to_string()).as_ref()); + // set as default + assert!(c.get("QEMU_EXTRA_ARGS").unwrap().contains("-nographic")); + assert_eq!(p, PathBuf::from(conf_path)); + } +} diff --git a/src/third_party/crosvm/Cargo.toml b/src/third_party/crosvm/Cargo.toml new file mode 100644 index 0000000..a56ff2d --- /dev/null +++ b/src/third_party/crosvm/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "crosvm" +version = "0.1.0" +authors = ["The Chromium OS Authors"] +edition = "2018" + +[lib] +path = "crosvm.rs" diff --git a/src/third_party/crosvm/LICENSE b/src/third_party/crosvm/LICENSE new file mode 100644 index 0000000..8bafca3 --- /dev/null +++ b/src/third_party/crosvm/LICENSE @@ -0,0 +1,27 @@ +// Copyright 2017 The Chromium OS Authors. All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/src/third_party/crosvm/argument.rs b/src/third_party/crosvm/argument.rs new file mode 100644 index 0000000..64be182 --- /dev/null +++ b/src/third_party/crosvm/argument.rs @@ -0,0 +1,549 @@ +// Copyright 2017 The Chromium OS Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +//! Handles argument parsing. +//! +//! # Example +//! +//! ``` +//! # use crosvm::argument::{Argument, Error, print_help, set_arguments}; +//! # let args: std::slice::Iter = [].iter(); +//! let arguments = &[ +//! Argument::positional("FILES", "files to operate on"), +//! Argument::short_value('p', "program", "PROGRAM", "Program to apply to each file"), +//! Argument::short_value('c', "cpus", "N", "Number of CPUs to use. (default: 1)"), +//! Argument::flag("unmount", "Unmount the root"), +//! Argument::short_flag('h', "help", "Print help message."), +//! ]; +//! +//! let match_res = set_arguments(args, arguments, |name, value| { +//! match name { +//! "" => println!("positional arg! {}", value.unwrap()), +//! "program" => println!("gonna use program {}", value.unwrap()), +//! "cpus" => { +//! let v: u32 = value.unwrap().parse().map_err(|_| { +//! Error::InvalidValue { +//! value: value.unwrap().to_owned(), +//! expected: String::from("this value for `cpus` needs to be integer"), +//! } +//! })?; +//! } +//! "unmount" => println!("gonna unmount"), +//! "help" => return Err(Error::PrintHelp), +//! _ => unreachable!(), +//! } +//! unreachable!(); +//! }); +//! +//! match match_res { +//! Ok(_) => println!("running with settings"), +//! Err(Error::PrintHelp) => print_help("best_program", "FILES", arguments), +//! Err(e) => println!("{}", e), +//! } +//! ``` + +use std::fmt::{self, Display}; +use std::result; + +/// An error with argument parsing. +#[derive(Debug)] +pub enum Error { + /// There was a syntax error with the argument. + Syntax(String), + /// The argument's name is unused. + UnknownArgument(String), + /// The argument was required. + ExpectedArgument(String), + /// The argument's given value is invalid. + InvalidValue { value: String, expected: String }, + /// The argument was already given and none more are expected. + TooManyArguments(String), + /// The argument expects a value. + ExpectedValue(String), + /// The argument does not expect a value. + UnexpectedValue(String), + /// The help information was requested + PrintHelp, +} + +impl Display for Error { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + use self::Error::*; + + match self { + Syntax(s) => write!(f, "syntax error: {}", s), + UnknownArgument(s) => write!(f, "unknown argument: {}", s), + ExpectedArgument(s) => write!(f, "expected argument: {}", s), + InvalidValue { value, expected } => { + write!(f, "invalid value {:?}: {}", value, expected) + } + TooManyArguments(s) => write!(f, "too many arguments: {}", s), + ExpectedValue(s) => write!(f, "expected parameter value: {}", s), + UnexpectedValue(s) => write!(f, "unexpected parameter value: {}", s), + PrintHelp => write!(f, "help was requested"), + } + } +} + +/// Result of a argument parsing. +pub type Result = result::Result; + +#[derive(Debug, PartialEq)] +pub enum ArgumentValueMode { + /// Specifies that an argument requires a value and that an error should be generated if + /// no value is provided during parsing. + Required, + + /// Specifies that an argument does not allow a value and that an error should be returned + /// if a value is provided during parsing. + Disallowed, + + /// Specifies that an argument may have a value during parsing but is not required to. + Optional, +} + +/// Information about an argument expected from the command line. +/// +/// # Examples +/// +/// To indicate a flag style argument: +/// +/// ``` +/// # use crosvm::argument::Argument; +/// Argument::short_flag('f', "flag", "enable awesome mode"); +/// ``` +/// +/// To indicate a parameter style argument that expects a value: +/// +/// ``` +/// # use crosvm::argument::Argument; +/// // "VALUE" and "NETMASK" are placeholder values displayed in the help message for these +/// // arguments. +/// Argument::short_value('v', "val", "VALUE", "how much do you value this usage information"); +/// Argument::value("netmask", "NETMASK", "hides your netface"); +/// ``` +/// +/// To indicate an argument with no short version: +/// +/// ``` +/// # use crosvm::argument::Argument; +/// Argument::flag("verbose", "this option is hard to type quickly"); +/// ``` +/// +/// To indicate a positional argument: +/// +/// ``` +/// # use crosvm::argument::Argument; +/// Argument::positional("VALUES", "these are positional arguments"); +/// ``` +pub struct Argument { + /// The name of the value to display in the usage information. + pub value: Option<&'static str>, + /// Specifies how values should be handled for this this argument. + pub value_mode: ArgumentValueMode, + /// Optional single character shortened argument name. + pub short: Option, + /// The long name of this argument. + pub long: &'static str, + /// Helpfuly usage information for this argument to display to the user. + pub help: &'static str, +} + +impl Argument { + pub fn positional(value: &'static str, help: &'static str) -> Argument { + Argument { + value: Some(value), + value_mode: ArgumentValueMode::Required, + short: None, + long: "", + help, + } + } + + pub fn value(long: &'static str, value: &'static str, help: &'static str) -> Argument { + Argument { + value: Some(value), + value_mode: ArgumentValueMode::Required, + short: None, + long, + help, + } + } + + pub fn short_value( + short: char, + long: &'static str, + value: &'static str, + help: &'static str, + ) -> Argument { + Argument { + value: Some(value), + value_mode: ArgumentValueMode::Required, + short: Some(short), + long, + help, + } + } + + pub fn flag(long: &'static str, help: &'static str) -> Argument { + Argument { + value: None, + value_mode: ArgumentValueMode::Disallowed, + short: None, + long, + help, + } + } + + pub fn short_flag(short: char, long: &'static str, help: &'static str) -> Argument { + Argument { + value: None, + value_mode: ArgumentValueMode::Disallowed, + short: Some(short), + long, + help, + } + } + + pub fn flag_or_value(long: &'static str, value: &'static str, help: &'static str) -> Argument { + Argument { + value: Some(value), + value_mode: ArgumentValueMode::Optional, + short: None, + long, + help, + } + } +} + +fn parse_arguments(args: I, mut f: F) -> Result<()> +where + I: Iterator, + R: AsRef, + F: FnMut(&str, Option<&str>) -> Result<()>, +{ + enum State { + // Initial state at the start and after finishing a single argument/value. + Top, + // The remaining arguments are all positional. + Positional, + // The next string is the value for the argument `name`. + Value { name: String }, + } + let mut s = State::Top; + for arg in args { + let arg = arg.as_ref(); + loop { + let mut arg_consumed = true; + s = match s { + State::Top => { + if arg == "--" { + State::Positional + } else if arg.starts_with("--") { + let param = arg.trim_start_matches('-'); + if param.contains('=') { + let mut iter = param.splitn(2, '='); + let name = iter.next().unwrap(); + let value = iter.next().unwrap(); + if name.is_empty() { + return Err(Error::Syntax( + "expected parameter name before `=`".to_owned(), + )); + } + if value.is_empty() { + return Err(Error::Syntax( + "expected parameter value after `=`".to_owned(), + )); + } + f(name, Some(value))?; + State::Top + } else { + State::Value { + name: param.to_owned(), + } + } + } else if arg.starts_with('-') { + if arg.len() == 1 { + return Err(Error::Syntax( + "expected argument short name after `-`".to_owned(), + )); + } + let name = &arg[1..2]; + let value = if arg.len() > 2 { Some(&arg[2..]) } else { None }; + if let Err(e) = f(name, value) { + if let Error::ExpectedValue(_) = e { + State::Value { + name: name.to_owned(), + } + } else { + return Err(e); + } + } else { + State::Top + } + } else { + f("", Some(&arg))?; + State::Positional + } + } + State::Positional => { + f("", Some(&arg))?; + State::Positional + } + State::Value { name } => { + if arg.starts_with('-') { + arg_consumed = false; + f(&name, None)?; + } else if let Err(e) = f(&name, Some(&arg)) { + arg_consumed = false; + f(&name, None).map_err(|_| e)?; + } + State::Top + } + }; + + if arg_consumed { + break; + } + } + } + + // If we ran out of arguments while parsing the last parameter, which may be either a + // value parameter or a flag, try to parse it as a flag. This will produce "missing value" + // error if the parameter is in fact a value parameter, which is the desired outcome. + match s { + State::Value { name } => f(&name, None), + _ => Ok(()), + } +} + +/// Parses the given `args` against the list of know arguments `arg_list` and calls `f` with each +/// present argument and value if required. +/// +/// This function guarantees that only valid long argument names from `arg_list` are sent to the +/// callback `f`. It is also guaranteed that if an arg requires a value (i.e. +/// `arg.value.is_some()`), the value will be `Some` in the callbacks arguments. If the callback +/// returns `Err`, this function will end parsing and return that `Err`. +/// +/// See the [module level](index.html) example for a usage example. +pub fn set_arguments(args: I, arg_list: &[Argument], mut f: F) -> Result<()> +where + I: Iterator, + R: AsRef, + F: FnMut(&str, Option<&str>) -> Result<()>, +{ + parse_arguments(args, |name, value| { + let mut matches = None; + for arg in arg_list { + if let Some(short) = arg.short { + if name.len() == 1 && name.starts_with(short) { + if value.is_some() != arg.value.is_some() { + return Err(Error::ExpectedValue(short.to_string())); + } + matches = Some(arg.long); + } + } + if matches.is_none() && arg.long == name { + if value.is_none() && arg.value_mode == ArgumentValueMode::Required { + return Err(Error::ExpectedValue(arg.long.to_owned())); + } + if value.is_some() && arg.value_mode == ArgumentValueMode::Disallowed { + return Err(Error::UnexpectedValue(arg.long.to_owned())); + } + matches = Some(arg.long); + } + } + match matches { + Some(long) => f(long, value), + None => Err(Error::UnknownArgument(name.to_owned())), + } + }) +} + +/// Prints command line usage information to stdout. +/// +/// Usage information is printed according to the help fields in `args` with a leading usage line. +/// The usage line is of the format "`program_name` \[ARGUMENTS\] `required_arg`". +pub fn print_help(program_name: &str, required_arg: &str, args: &[Argument]) { + println!( + "Usage: {} {}{}\n", + program_name, + if args.is_empty() { "" } else { "[ARGUMENTS] " }, + required_arg + ); + if args.is_empty() { + return; + } + println!("Argument{}:", if args.len() > 1 { "s" } else { "" }); + for arg in args { + match arg.short { + Some(s) => print!(" -{}, ", s), + None => print!(" "), + } + if arg.long.is_empty() { + print!(" "); + } else { + print!("--"); + } + print!("{:<12}", arg.long); + if let Some(v) = arg.value { + if arg.long.is_empty() { + print!(" "); + } else { + print!("="); + } + print!("{:<10}", v); + } else { + print!("{:<11}", ""); + } + println!("{}", arg.help); + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn request_help() { + let arguments = [Argument::short_flag('h', "help", "Print help message.")]; + + let match_res = set_arguments(["-h"].iter(), &arguments[..], |name, _| { + match name { + "help" => return Err(Error::PrintHelp), + _ => unreachable!(), + }; + }); + match match_res { + Err(Error::PrintHelp) => {} + _ => unreachable!(), + } + } + + #[test] + fn mixed_args() { + let arguments = [ + Argument::positional("FILES", "files to operate on"), + Argument::short_value('p', "program", "PROGRAM", "Program to apply to each file"), + Argument::short_value('c', "cpus", "N", "Number of CPUs to use. (default: 1)"), + Argument::flag("unmount", "Unmount the root"), + Argument::short_flag('h', "help", "Print help message."), + ]; + + let mut unmount = false; + let match_res = set_arguments( + ["--cpus", "3", "--program", "hello", "--unmount", "file"].iter(), + &arguments[..], + |name, value| { + match name { + "" => assert_eq!(value.unwrap(), "file"), + "program" => assert_eq!(value.unwrap(), "hello"), + "cpus" => { + let c: u32 = value.unwrap().parse().map_err(|_| Error::InvalidValue { + value: value.unwrap().to_owned(), + expected: String::from("this value for `cpus` needs to be integer"), + })?; + assert_eq!(c, 3); + } + "unmount" => unmount = true, + "help" => return Err(Error::PrintHelp), + _ => unreachable!(), + }; + Ok(()) + }, + ); + assert!(match_res.is_ok()); + assert!(unmount); + } + + #[test] + fn name_value_pair() { + let arguments = [Argument::short_value( + 'c', + "cpus", + "N", + "Number of CPUs to use. (default: 1)", + )]; + let match_res = set_arguments( + ["-c", "5", "--cpus", "5", "-c5", "--cpus=5"].iter(), + &arguments[..], + |name, value| { + assert_eq!(name, "cpus"); + assert_eq!(value, Some("5")); + Ok(()) + }, + ); + assert!(match_res.is_ok()); + let not_match_res = set_arguments( + ["-c", "5", "--cpus"].iter(), + &arguments[..], + |name, value| { + assert_eq!(name, "cpus"); + assert_eq!(value, Some("5")); + Ok(()) + }, + ); + assert!(not_match_res.is_err()); + } + + #[test] + fn flag_or_value() { + let run_case = |args| -> Option { + let arguments = [ + Argument::positional("FILES", "files to operate on"), + Argument::flag_or_value("gpu", "[2D|3D]", "Enable or configure gpu"), + Argument::flag("foo", "Enable foo."), + Argument::value("bar", "stuff", "Configure bar."), + ]; + + let mut gpu_value: Option = None; + let match_res = + set_arguments(args, &arguments[..], |name: &str, value: Option<&str>| { + match name { + "" => assert_eq!(value.unwrap(), "file1"), + "foo" => assert!(value.is_none()), + "bar" => assert_eq!(value.unwrap(), "stuff"), + "gpu" => match value { + Some(v) => match v { + "2D" | "3D" => { + gpu_value = Some(v.to_string()); + } + _ => { + return Err(Error::InvalidValue { + value: v.to_string(), + expected: String::from("2D or 3D"), + }) + } + }, + None => { + gpu_value = None; + } + }, + _ => unreachable!(), + }; + Ok(()) + }); + + assert!(match_res.is_ok()); + gpu_value + }; + + // Used as flag and followed by positional + assert_eq!(run_case(["--gpu", "file1"].iter()), None); + // Used as flag and followed by flag + assert_eq!(run_case(["--gpu", "--foo", "file1",].iter()), None); + // Used as flag and followed by value + assert_eq!(run_case(["--gpu", "--bar=stuff", "file1"].iter()), None); + + // Used as value and followed by positional + assert_eq!(run_case(["--gpu=2D", "file1"].iter()).unwrap(), "2D"); + // Used as value and followed by flag + assert_eq!(run_case(["--gpu=2D", "--foo"].iter()).unwrap(), "2D"); + // Used as value and followed by value + assert_eq!( + run_case(["--gpu=2D", "--bar=stuff", "file1"].iter()).unwrap(), + "2D" + ); + } +} diff --git a/src/third_party/crosvm/crosvm.rs b/src/third_party/crosvm/crosvm.rs new file mode 100644 index 0000000..ddccbfe --- /dev/null +++ b/src/third_party/crosvm/crosvm.rs @@ -0,0 +1,2 @@ +// ensure that existing crosvm::argument imports work without modification... +pub mod argument; diff --git a/src/third_party/rust-elf/.github/workflows/fuzz.yml b/src/third_party/rust-elf/.github/workflows/fuzz.yml new file mode 100644 index 0000000..cbe5121 --- /dev/null +++ b/src/third_party/rust-elf/.github/workflows/fuzz.yml @@ -0,0 +1,45 @@ +name: Fuzz + +on: + push: + branches: [ "master" ] + pull_request: + branches: [ "master" ] + +env: + CARGO_TERM_COLOR: always + +jobs: + build: + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + - name: Install nightly Rust + shell: bash + run: | + curl -sSL https://sh.rustup.rs | sh -s -- -y --profile=minimal --default-toolchain=nightly + export PATH="$HOME/.cargo/bin:$PATH" + echo "$HOME/.cargo/bin" >> $GITHUB_PATH + - name: Install cargo fuzz + run: cargo install cargo-fuzz + - name: Build + run: cargo build --verbose + - name: Cache fuzzy corpus + uses: actions/cache@v4 + with: + path: fuzz/corpus + key: fuzz-corpus-${{ github.run_id }} + restore-keys: | + fuzz-corpus- + - name: fuzzy common + run: cargo fuzz run common -- -max_total_time=60 + - name: fuzzy notes + run: cargo fuzz run notes -- -max_total_time=60 + - name: fuzzy symver + run: cargo fuzz run symver -- -max_total_time=60 + - name: fuzzy symbol_table + run: cargo fuzz run symbol_table -- -max_total_time=60 + - name: fuzzy stream + run: cargo fuzz run stream -- -max_total_time=60 diff --git a/src/third_party/rust-elf/.github/workflows/rust.yml b/src/third_party/rust-elf/.github/workflows/rust.yml new file mode 100644 index 0000000..2a7abdd --- /dev/null +++ b/src/third_party/rust-elf/.github/workflows/rust.yml @@ -0,0 +1,33 @@ +name: Rust + +on: + push: + branches: [ "master" ] + pull_request: + branches: [ "master" ] + +env: + CARGO_TERM_COLOR: always + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Build + run: cargo build --verbose + - name: Run tests + run: cargo test --verbose + - name: Build no_std + run: cargo build --no-default-features + + msrv-all: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Install MSRV + run: rustup update 1.81.0 && rustup default 1.81.0 + - name: Test + run: cargo test --verbose + - name: Build no_std + run: cargo build --no-default-features diff --git a/src/third_party/rust-elf/.gitignore b/src/third_party/rust-elf/.gitignore new file mode 100644 index 0000000..a93127b --- /dev/null +++ b/src/third_party/rust-elf/.gitignore @@ -0,0 +1,14 @@ +# Compiled files +*.o +*.so +*.rlib +*.dll +*.swp + +# Executables +*.exe + +# Generated by Cargo +/target/ + +Cargo.lock diff --git a/src/third_party/rust-elf/CHANGELOG.md b/src/third_party/rust-elf/CHANGELOG.md new file mode 100644 index 0000000..aab1b5f --- /dev/null +++ b/src/third_party/rust-elf/CHANGELOG.md @@ -0,0 +1,214 @@ +# Change Log +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](http://keepachangelog.com/) + +## [0.8.0] - 2025-05-13 + +### Breaking Improvements + +- Drop "nightly" feature in favor of stabilized core::error::Error for ParseError when building for no_std. This bumps the MSRV to 1.81.0 (Sept. 5, 2024). +- Change dyn.d_val() and dyn.d_ptr() to take self by ref +- Relax NoteAny parsing to allow notes with names that contain invalid utf8 sequences + +### New Features + +- Add ElfStream::segment_data() to get raw segment data +- Add ABI constants for MIPS +- Drop "nightly" feature in favor of stabilized core::error::Error for ParseError when building for no_std +- Add EM_CSKY, EM_LOONGARCH EM_FRV, EM_LANAI, and EM_VE +- Make to_str no_std compatible +- Make Symbol fields st_info and st_other fully public +- Add NoteAny::name_str() helper to parse the note name as valid UTF8 + +### Bug Fixes + +- Update e_machine_to_str to return proper value for EM_RISCV +- Various docstring fixes + +## [0.7.4] - 2023-11-22 + +### Bug Fixes + +- Fix note parsing for notes with n_namesz == (align * x + 1) + +## [0.7.3] - 2023-10-09 + +### New Features + +- Derive Debug on LittleEndian and BigEndian + +### Misc Improvements + +- Enable #![forbid(unsafe_code)] +- Enable #![deny(missing_debug_implementations)] +- Enable #![warn(rust_2018_idioms)] +- Fix doc comment on file::Class +- Fix README example so it compiles + +## [0.7.2] - 2023-02-15 + +### New Features + +- Implement core::error::Error for ParsingError accessible via a new non-default "nightly" cargo feature +- Add abi constants for note descriptor types (n_type) +- Add C-style struct definitions for various abi structs (Elf[32|64]_Ehdr etc). These aren't used by the parser, but are useful definitions for folks wanting to manually muck with elf bytes. + +### Bug Fixes + +- Fix an 'attempt to shift right with overflow' panic in the GnuHashTable if nshift is wider than the bloom filter word size + +### Misc Improvements + +- Add doc comments for EM_* abi constants +- Tweak formatting and update language for various doc comments + +## [0.7.1] - 2023-01-08 + +### Bug Fixes + +- Fix a divide by zero panic in GnuHashTable.find() for tables with nbloom = 0 + +## [0.7.0] - 2022-11-14 + +### New Features + +- Add new ElfBytes type with better ergonomics for parsing from a &[u8] +- Add GnuHashTable which interprets the contents of a SHT_GNU_HASH section +- Add convenience method section_header_by_name to ElfBytes and ElfStream +- Add GnuBuildIdNote and parsing for NT_GNU_BUILD_ID note contents +- Add GnuAbiTagNote and parsing for NT_GNU_ABI_TAG note contents +- Add ElfBytes::symbol_version_table() to get the GNU extension symbol version table. +- Add ElfBytes::find_common_data() to efficiently discover common ELF structures +- Add a new endian-aware integer parsing trait impl +- Add ParsingTable.is_empty() +- Add abi constants for powerpc and powerpc64 +- Add abi constants for RISC-V +- Add abi constants for x86_64 +- Add abi constants for ARM32 and ARM64 (AARCH64) +- Add abi constant for GNU-extension ELF note name ELF_NOTE_GNU +- Add abi constant for PT_GNU_PROPERTY +- Add abi constants for SHN_ABS and SHN_COMMON +- Add elf::to_str::d_tag_to_str() +- Add elf::to_str::note_abi_tag_os_to_str() + +### Changed Interfaces + +- Rename elf::File -> elf::ElfStream and make it specific to the Read + Seek interface +- Rename gabi -> abi since it also includes extension constants +- Make ELF structures generic across the new endian-aware integer parsing trait EndianParse +- Refactor parsed Note type to be a typed enum +- Rename ElfStream::dynamic_section() -> dynamic() to match ElfBytes +- Change ElfStream::dynamic() to yield a DynamicTable just like in ElfBytes +- Standardize ElfBytes' interfaces for the .dynamic contents to return a DynamicTable +- Export the parsing utilities ParsingTable, ParsingIterator in the public interface +- Refactor section_headers_with_strtab to work with files that have shdrs but no shstrtab +- Remove redundant hash arg from SysVHashTable.find() +- Expose Class in the public interface alongside FileHeader +- Remove opinionated Display impl for file::Class +- Remove section_data_as_symbol_table() from public ElfBytes interface +- Change SymbolVersionTable::new() to take Options instead of Default-empty iterators +- Change ElfStream to parse out the ProgramHeaders into an allocated vec as part of ElfStream::open_stream() +- Change ElfStream to parse out the SectionHeaders into an allocated Vec as part of ElfStream::open_stream() + +### Bug Fixes + +- Properly parse program header table when ehdr.e_phnum > 0xffff +- Fix OOM in ElfStream parsing when parsing corrupted files +- Fix a divide by zero panic in SysVHashTable.find() for empty tables + +### Misc Improvements + +- Add more fuzz testing +- Add some simple parsing smoke tests for the various sample architecture objects +- Add sample object and testing with > 0xff00 section headers +- Add a lot more doc comments to each of the modules + +## [0.6.1] - 2022-11-05 + +### New Features +- Expose Class and Endian in the public interface. These types are exposed in the FileHeader type and so they should also be accessible for users to inspect. + +## [0.6.0] - 2022-11-01 + +### New Features + +- Add fuzz targets for parts of our ELF parsing interface via cargo-fuzz +- Add SysVHashTable which interprets the contents of a SHT_HASH section +- Add StringTable::get_raw() to get an uninterpreted &[u8] +- Add ParsingTable.len() method to get the number of elements in the table +- Add some note n_type constants for GNU extension notes. +- Add default "to_str" feature to get &str for gabi constant names + +### Changed Interfaces + +- Change File::segments() to return a ParsingTable instead of just a ParsingIterator +- Change File's SectionHeader interfaces to provide a ParsingTable instead of just a ParsingIterator +- Remove deprecated File::section_data_for_header() in favor of File::section_data() +- Remove FileHeader wrapper types OSABI, Architecture, and ObjectFileType +- Remove ProgramHeader wrapper types ProgType and ProgFlag +- Remove Symbol wrapper types SymbolType SymbolBind SymbolVis +- Remove wrapper type SectionType +- Remove unhelpful SectionFlag wrapper type +- Remove Display impl for FileHeader, SectionHeader, ProgramHeader, Symbol +- Remove ParseError::UnsupportedElfVersion in favor of more general ParseError::UnsupportedVersion + +### Bug Fixes + +- Fix divide by zero panic when parsing a note with alignment of 0 (Error instead of panic) +- Use checked integer math all over the parsing code (Error instead of panic or overflow) +- Fix note parsing for 8-byte aligned .note.gnu.property sections (Successfully parse instead of Erroring) +- Add size validation when parsing tables with entsizes (Error instead of panic) + +## [0.5.0] - 2022-10-30 + +### New Features + +- Add File::symbol_version_table() interface to get the GNU extension symbol versioning table +- Add Symbol.is_undefined() helper to check if a symbol is defined in this object +- Add File::section_data() which opportunistically parses the CompressionHeader if present + +### Bug Fixes + +- Fix StringTable to return a ParseError on index out of bounds instead of panicking +- Fix File::section_data_as_rels to properly parse Rels (not Relas) + +## [0.4.0] - 2022-10-24 + +### New Features + +- Add .note section and segment parsing +- Add .dynamic section and segment parsing +- Add .rel and .rela section parsing +- Add File::section_headers_with_strtab to get both a header iter and strtab concurrently. + +### Changed Interfaces + +- The ReadBytesAt trait was changed to be implemented for an owned CachedReadBytes. This means that File::open_stream now expects to move-own the CachedReadBytes as opposed to taking a mutable reference. + +## [0.3.1] - 2022-10-21 + +### New Features +- Add File::section_data_for_header() to get raw section data for a given section + +### Bug fixes +- Fix section header table parsing when ehdr.e_shnum > 0xff00 + +## [0.3.0] - 2022-10-20 + +### New Features +- Add a `no_std` option by fully moving the parser over to lazy zero-alloc parsing patterns. + + +[0.8.0]: https://github.com/cole14/rust-elf/compare/v0.7.4...v0.8.0 +[0.7.4]: https://github.com/cole14/rust-elf/compare/v0.7.3...v0.7.4 +[0.7.3]: https://github.com/cole14/rust-elf/compare/v0.7.2...v0.7.3 +[0.7.2]: https://github.com/cole14/rust-elf/compare/v0.7.1...v0.7.2 +[0.7.1]: https://github.com/cole14/rust-elf/compare/v0.7.0...v0.7.1 +[0.7.0]: https://github.com/cole14/rust-elf/compare/v0.6.1...v0.7.0 +[0.6.1]: https://github.com/cole14/rust-elf/compare/v0.6.0...v0.6.1 +[0.6.0]: https://github.com/cole14/rust-elf/compare/v0.5.0...v0.6.0 +[0.5.0]: https://github.com/cole14/rust-elf/compare/v0.4.0...v0.5.0 +[0.4.0]: https://github.com/cole14/rust-elf/compare/v0.3.1...v0.4.0 +[0.3.1]: https://github.com/cole14/rust-elf/compare/v0.3.0...v0.3.1 +[0.3.0]: https://github.com/cole14/rust-elf/compare/v0.2.0...v0.3.0 diff --git a/src/third_party/rust-elf/COPYRIGHT b/src/third_party/rust-elf/COPYRIGHT new file mode 100644 index 0000000..767cb81 --- /dev/null +++ b/src/third_party/rust-elf/COPYRIGHT @@ -0,0 +1,12 @@ +rust-elf is licensed under either of + + * Apache License, Version 2.0 (LICENSE-APACHE or + http://www.apache.org/licenses/LICENSE-2.0) + * MIT license ([LICENSE-MIT](LICENSE-MIT) or + http://opensource.org/licenses/MIT) + +at your option. + +Unless you explicitly state otherwise, any contribution intentionally submitted +for inclusion in the work by you, as defined in the Apache-2.0 license, shall +be dual licensed as above, without any additional terms or conditions. diff --git a/src/third_party/rust-elf/Cargo.toml b/src/third_party/rust-elf/Cargo.toml new file mode 100644 index 0000000..dd31751 --- /dev/null +++ b/src/third_party/rust-elf/Cargo.toml @@ -0,0 +1,25 @@ +[package] +name = "elf" +version = "0.8.0" +authors = ["Christopher Cole "] +license = "MIT/Apache-2.0" +repository = "https://github.com/cole14/rust-elf/" +documentation = "https://docs.rs/elf/latest/elf/" +description = "A pure-rust library for parsing ELF files" +keywords = ["binary", "elf", "object", "parser"] +categories = ["no-std", "os", "embedded"] +exclude = [".gitignore", "/.github", "/sample-objects"] +readme = "README.md" +edition = "2021" +rust-version = "1.81.0" + +[lib] +name = "elf" + +[dependencies] + +[features] +default = ["alloc" , "std", "to_str"] +alloc = [] +std = ["alloc"] +to_str = [] diff --git a/src/third_party/rust-elf/LICENSE-APACHE b/src/third_party/rust-elf/LICENSE-APACHE new file mode 100644 index 0000000..ebe4b41 --- /dev/null +++ b/src/third_party/rust-elf/LICENSE-APACHE @@ -0,0 +1,202 @@ +Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2016 Christopher Cole + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + diff --git a/src/third_party/rust-elf/LICENSE-MIT b/src/third_party/rust-elf/LICENSE-MIT new file mode 100644 index 0000000..cdd6fa3 --- /dev/null +++ b/src/third_party/rust-elf/LICENSE-MIT @@ -0,0 +1,25 @@ +Copyright (c) 2016 Christopher Cole + +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. diff --git a/src/third_party/rust-elf/README.md b/src/third_party/rust-elf/README.md new file mode 100644 index 0000000..eae313f --- /dev/null +++ b/src/third_party/rust-elf/README.md @@ -0,0 +1,136 @@ +[![](https://img.shields.io/crates/v/elf.svg)](https://crates.io/crates/elf) +[![](https://img.shields.io/crates/d/elf.svg)](https://crates.io/crates/elf) +[![Build Status](https://github.com/cole14/rust-elf/actions/workflows/rust.yml/badge.svg)](https://github.com/cole14/rust-elf/actions) +[![](https://docs.rs/elf/badge.svg)](https://docs.rs/elf/) + +# rust-elf + +The `elf` crate provides a pure-safe-rust interface for reading ELF object files. + +[Documentation](https://docs.rs/elf/) + +# Capabilities + +### ✨ Works in `no_std` environments ✨ +This crate provides an elf parsing interface which does not allocate or use any std +features, so it can be used in `no_std` environments such as kernels and bootloaders. +The no_std variant merely disables the additional stream-oriented `std:: Read + Seek` interface. +All core parsing functionality is the same! + +### ✨ Endian-aware ✨ +This crate handles translating between file and host endianness when +parsing the ELF contents and provides four endian parsing implementations +optimized to support the different common use-cases for an ELF parsing library. +Parsing is generic across the specifications and each trait impl represents a +specification that encapsulates an interface for parsing integers from some +set of allowed byte orderings. + +* `AnyEndian`: Dynamically parsing either byte order at runtime based on the type of ELF object being parsed. +* `BigEndian`/`LittleEndian`: For tools that know they only want to parse a single given byte order known at compile time. +* `NativeEndian`: For tools that know they want to parse the same byte order as the compilation target's byte order. + +When the limited specifications are used, errors are properly returned when asked to parse an ELF file +with an unexpected byte ordering. + +### ✨ Zero-alloc parser ✨ +This crate implements parsing in a way that avoids heap allocations. ELF structures +are parsed and stored on the stack and provided by patterns such as lazily parsed iterators +that yield stack allocated rust types, or lazily parsing tables that only parse out a particular +entry on table.get(index). The structures are copy-converted as needed from the underlying file +data into Rust's native struct representation. + +### ✨ Fuzz Tested ✨ +Various parts of the library are fuzz tested for panics and crashes (see `fuzz/`). + +Memory safety is a core goal, as is providing a safe interface that errors on bad data +over crashing or panicking. Checked integer math is used where appropriate, and ParseErrors are +returned when bad or corrupted ELF structures are encountered. + +### ✨ Uses only safe interfaces ✨ +With memory safety a core goal, this crate contains zero unsafe code blocks of +its own and only uses safe interface methods from core and std, so you can +trust in rust's memory safety guarantees without also having to trust this +library developer as having truly been "right" in why some unsafe block was +safe. 💃 + +Note: I'd love to see this crate be enhanced further once rust provides safe transmutes. + +See: + +### ✨ Some zero-copy interfaces ✨ +The StringTable, for instance, yields `&[u8]` and `&str` backed by the raw string table bytes. + +The `ElfBytes` parser type also does not make raw copies of the underlying file data to back +the parser lazy parser interfaces `ParsingIterator` and `ParsingTable`. They merely wrap byte slices +internally, and yield rust repr values on demand, which does entail copying of the bytes into the +parsed rust-native format. + +Depending on the use-case, it can be more efficient to restructure the raw ELF into different layouts +for more efficient interpretation, say, by re-indexing a flat table into a HashMap. `ParsingIterator`s +make that easy and rustily-intuitive. + +The `ParsingIterator`s are also nice in that you can easily zip/enumerate/filter/collect them +how you wish. Do you know that you want to do multiple passes over pairs from different tables? Just +zip/collect them into another type so you only parse/endian-flip each entry once! + +### ✨ Stream-based lazy i/o interface ✨ +The `ElfStream` parser type takes a `std:: Read + Seek` (such as `std::fs::File`) where ranges of +file contents are read lazily on-demand based on what the user wants to parse. + +This, alongside the bytes-oriented interface, allow you to decide which tradeoffs +you want to make. If you're going to be working with the whole file contents, +then the byte slice approach is probably worthwhile to minimize i/o overhead by +streaming the whole file into memory at once. If you're only going to be +inspecting part of the file, then the `ElfStream` approach would help avoid the +overhead of reading a bunch of unused file data just to parse out a few things, (like +grabbing the `.gnu.note.build-id`) + +### ✨ Tiny library with no dependencies and fast compilation times ✨ +Release-target compilation times on this developer's 2021 m1 macbook are sub-second. + +## Example using `ElfBytes`: + +```rust +use elf::ElfBytes; +use elf::endian::AnyEndian; +use elf::note::Note; +use elf::note::NoteGnuBuildId; +use elf::section::SectionHeader; + +let path = std::path::PathBuf::from("sample-objects/symver.x86_64.so"); +let file_data = std::fs::read(path).expect("Could not read file."); +let slice = file_data.as_slice(); +let file = ElfBytes::::minimal_parse(slice).expect("Open test1"); + +// Get the ELF file's build-id +let abi_shdr: SectionHeader = file + .section_header_by_name(".note.gnu.build-id") + .expect("section table should be parseable") + .expect("file should have a .note.ABI-tag section"); + +let notes: Vec = file + .section_data_as_notes(&abi_shdr) + .expect("Should be able to get note section data") + .collect(); +assert_eq!( + notes[0], + Note::GnuBuildId(NoteGnuBuildId( + &[140, 51, 19, 23, 221, 90, 215, 131, 169, 13, + 210, 183, 215, 77, 216, 175, 167, 110, 3, 209])) +); + +// Find lazy-parsing types for the common ELF sections (we want .dynsym, .dynstr, .hash) +let common = file.find_common_data().expect("shdrs should parse"); +let (dynsyms, strtab) = (common.dynsyms.unwrap(), common.dynsyms_strs.unwrap()); +let hash_table = common.sysv_hash.unwrap(); + +// Use the hash table to find a given symbol in it. +let name = b"memset"; +let (sym_idx, sym) = hash_table.find(name, &dynsyms, &strtab) + .expect("hash table and symbols should parse").unwrap(); + +// Verify that we got the same symbol from the hash table we expected +assert_eq!(sym_idx, 2); +assert_eq!(strtab.get(sym.st_name as usize).unwrap(), "memset"); +assert_eq!(sym, dynsyms.get(sym_idx).unwrap()); +``` diff --git a/src/third_party/rust-elf/fuzz/.gitignore b/src/third_party/rust-elf/fuzz/.gitignore new file mode 100644 index 0000000..d00668f --- /dev/null +++ b/src/third_party/rust-elf/fuzz/.gitignore @@ -0,0 +1,4 @@ +artifacts +corpus +target +coverage diff --git a/src/third_party/rust-elf/fuzz/Cargo.toml b/src/third_party/rust-elf/fuzz/Cargo.toml new file mode 100644 index 0000000..2f29a8b --- /dev/null +++ b/src/third_party/rust-elf/fuzz/Cargo.toml @@ -0,0 +1,51 @@ +[package] +name = "elf-fuzz" +version = "0.0.0" +publish = false +edition = "2021" + +[package.metadata] +cargo-fuzz = true + +[dependencies] +libfuzzer-sys = "0.4" + +[dependencies.elf] +path = ".." + +# Prevent this from interfering with workspaces +[workspace] +members = ["."] + +[profile.release] +debug = 1 + +[[bin]] +name = "common" +path = "fuzz_targets/common.rs" +test = false +doc = false + +[[bin]] +name = "notes" +path = "fuzz_targets/notes.rs" +test = false +doc = false + +[[bin]] +name = "symbol_table" +path = "fuzz_targets/symbol_table.rs" +test = false +doc = false + +[[bin]] +name = "symver" +path = "fuzz_targets/symver.rs" +test = false +doc = false + +[[bin]] +name = "stream" +path = "fuzz_targets/stream.rs" +test = false +doc = false diff --git a/src/third_party/rust-elf/fuzz/fuzz_targets/common.rs b/src/third_party/rust-elf/fuzz/fuzz_targets/common.rs new file mode 100644 index 0000000..c4ca482 --- /dev/null +++ b/src/third_party/rust-elf/fuzz/fuzz_targets/common.rs @@ -0,0 +1,49 @@ +#![no_main] + +use elf::endian::AnyEndian; +use elf::ElfBytes; +use libfuzzer_sys::fuzz_target; + +fuzz_target!(|data: &[u8]| { + if let Ok(file) = ElfBytes::::minimal_parse(data) { + if let Some(shdrs) = file.section_headers() { + let _: Vec<_> = shdrs.iter().collect(); + } + + if let Ok(common) = file.find_common_data() { + // parse the symbol table + if let Some(symtab) = common.symtab { + let _: Vec<_> = symtab.iter().collect(); + } + + // parse the dynamic symbol table + if let Some(dynsyms) = common.dynsyms { + if let Some(dynstrs) = common.dynsyms_strs { + let sym_names: Vec<&[u8]> = dynsyms + .iter() + .map(|sym| dynstrs.get_raw(sym.st_name as usize).unwrap_or(b"unk")) + .collect(); + + // use the hash table + if let Some(hash) = common.sysv_hash { + for name in sym_names.iter() { + let _ = hash.find(name, &dynsyms, &dynstrs); + } + } + + // use the gnu hash table + if let Some(hash) = common.gnu_hash { + for name in sym_names.iter() { + let _ = hash.find(name, &dynsyms, &dynstrs); + } + } + } + } + + // parse the .dynamic table + if let Some(dyns) = common.dynamic { + let _: Vec<_> = dyns.iter().collect(); + } + } + } +}); diff --git a/src/third_party/rust-elf/fuzz/fuzz_targets/notes.rs b/src/third_party/rust-elf/fuzz/fuzz_targets/notes.rs new file mode 100644 index 0000000..8b97a5c --- /dev/null +++ b/src/third_party/rust-elf/fuzz/fuzz_targets/notes.rs @@ -0,0 +1,17 @@ +#![no_main] + +use elf::endian::NativeEndian; +use elf::file::Class; +use elf::note::{Note, NoteIterator}; +use libfuzzer_sys::fuzz_target; + +fuzz_target!(|data: &[u8]| { + if data.is_empty() { + return; + } + + let (head, tail) = data.split_at(1); + + let iter = NoteIterator::new(NativeEndian, Class::ELF64, head[0] as usize, tail); + let _: Vec = iter.collect(); +}); diff --git a/src/third_party/rust-elf/fuzz/fuzz_targets/stream.rs b/src/third_party/rust-elf/fuzz/fuzz_targets/stream.rs new file mode 100644 index 0000000..3d9f610 --- /dev/null +++ b/src/third_party/rust-elf/fuzz/fuzz_targets/stream.rs @@ -0,0 +1,27 @@ +#![no_main] + +use elf::endian::AnyEndian; +use elf::ElfStream; +use libfuzzer_sys::fuzz_target; + +fuzz_target!(|data: &[u8]| { + let mut cur = std::io::Cursor::new(data); + if let Ok(mut file) = ElfStream::::open_stream(&mut cur) { + // parse the symbol table + if let Ok(Some((symtab, _))) = file.symbol_table() { + let _: Vec<_> = symtab.iter().collect(); + } + + // parse any notes + if let Some(shdr) = file + .section_headers() + .iter() + .find(|shdr| shdr.sh_type == elf::abi::SHT_NOTE) + { + let shdr = *shdr; + if let Ok(notes) = file.section_data_as_notes(&shdr) { + let _: Vec<_> = notes.collect(); + } + } + } +}); diff --git a/src/third_party/rust-elf/fuzz/fuzz_targets/symbol_table.rs b/src/third_party/rust-elf/fuzz/fuzz_targets/symbol_table.rs new file mode 100644 index 0000000..6a5fb60 --- /dev/null +++ b/src/third_party/rust-elf/fuzz/fuzz_targets/symbol_table.rs @@ -0,0 +1,24 @@ +#![no_main] + +use elf::endian::AnyEndian; +use elf::symbol::Symbol; +use elf::ElfBytes; +use libfuzzer_sys::fuzz_target; + +fuzz_target!(|data: &[u8]| { + if let Ok(file) = ElfBytes::::minimal_parse(data) { + if let Ok(Some((symtab, strtab))) = file.symbol_table() { + let _: Vec<(&str, Symbol)> = symtab + .iter() + .map(|sym| (strtab.get(sym.st_name as usize).unwrap_or("unknown"), sym)) + .collect(); + } + + if let Ok(Some((dynsym, dynstr))) = file.dynamic_symbol_table() { + let _: Vec<(&str, Symbol)> = dynsym + .iter() + .map(|sym| (dynstr.get(sym.st_name as usize).unwrap_or("unknown"), sym)) + .collect(); + } + } +}); diff --git a/src/third_party/rust-elf/fuzz/fuzz_targets/symver.rs b/src/third_party/rust-elf/fuzz/fuzz_targets/symver.rs new file mode 100644 index 0000000..d50e272 --- /dev/null +++ b/src/third_party/rust-elf/fuzz/fuzz_targets/symver.rs @@ -0,0 +1,25 @@ +#![no_main] + +use elf::endian::AnyEndian; +use elf::symbol::Symbol; +use elf::ElfBytes; +use libfuzzer_sys::fuzz_target; + +fuzz_target!(|data: &[u8]| { + if let Ok(file) = ElfBytes::::minimal_parse(data) { + if let Ok(Some((dynsym, _))) = file.dynamic_symbol_table() { + let symbols: Vec = dynsym.iter().collect(); + + if let Ok(Some(table)) = file.symbol_version_table() { + for (idx, _) in symbols.iter().enumerate() { + if let Ok(Some(def)) = table.get_definition(idx) { + let _: Vec<&str> = + def.names.map(|name| name.unwrap_or("unknown")).collect(); + } + + if let Ok(Some(_)) = table.get_requirement(idx) {} + } + } + } + } +}); diff --git a/src/third_party/rust-elf/sample-objects/basic.x86_64 b/src/third_party/rust-elf/sample-objects/basic.x86_64 new file mode 100644 index 0000000..589ad09 Binary files /dev/null and b/src/third_party/rust-elf/sample-objects/basic.x86_64 differ diff --git a/src/third_party/rust-elf/sample-objects/shnum.x86_64 b/src/third_party/rust-elf/sample-objects/shnum.x86_64 new file mode 100644 index 0000000..9c6e1b4 Binary files /dev/null and b/src/third_party/rust-elf/sample-objects/shnum.x86_64 differ diff --git a/src/third_party/rust-elf/sample-objects/symver.c b/src/third_party/rust-elf/sample-objects/symver.c new file mode 100644 index 0000000..5270bc6 --- /dev/null +++ b/src/third_party/rust-elf/sample-objects/symver.c @@ -0,0 +1,13 @@ + +#include + +int use_memset(unsigned char *buf, size_t n) { + memset(buf, 0, n); +} + +int use_memset_v2(unsigned char *buf) { + memset(buf, 0, 42); +} + +__asm__(".symver use_memset, use_memset@HELLO_1.0"); +__asm__(".symver use_memset_v2, use_memset_v2@HELLO_1.42"); diff --git a/src/third_party/rust-elf/sample-objects/symver.sh b/src/third_party/rust-elf/sample-objects/symver.sh new file mode 100644 index 0000000..7b22846 --- /dev/null +++ b/src/third_party/rust-elf/sample-objects/symver.sh @@ -0,0 +1,2 @@ +#!/bin/bash +gcc -o symver.x86_64.so symver.c -Wl,--as-needed -shared -fPIC -Xlinker --hash-style=both -Wl,--version-script=symver.ver diff --git a/src/third_party/rust-elf/sample-objects/symver.ver b/src/third_party/rust-elf/sample-objects/symver.ver new file mode 100644 index 0000000..8687eed --- /dev/null +++ b/src/third_party/rust-elf/sample-objects/symver.ver @@ -0,0 +1,2 @@ +HELLO_1.0 {}; +HELLO_1.42 {}; diff --git a/src/third_party/rust-elf/src/abi.rs b/src/third_party/rust-elf/src/abi.rs new file mode 100644 index 0000000..3ffcfc3 --- /dev/null +++ b/src/third_party/rust-elf/src/abi.rs @@ -0,0 +1,3180 @@ +//! Contains ELF constants defined in the ELF gABI and various extensions +// See +// Note: At least in 2022, it seems like the above site is not being updated. Official communication +// occurs on the Generic System V Application Binary Interface mailing list: +// + +// EI_* define indexes into the ELF File Header's e_ident[] byte array. +// We define them as usize in order to use them to easily index into [u8]. + +/// Location of first ELF magic number byte +pub const EI_MAG0: usize = 0; +/// Location of second ELF magic number byte +pub const EI_MAG1: usize = 1; +/// Location of third ELF magic number byte +pub const EI_MAG2: usize = 2; +/// Location of fourth ELF magic number byte +pub const EI_MAG3: usize = 3; +/// Location of ELF class field in ELF file header ident array +pub const EI_CLASS: usize = 4; +/// Location of data format field in ELF file header ident array +pub const EI_DATA: usize = 5; +/// Location of ELF version field in ELF file header ident array +pub const EI_VERSION: usize = 6; +/// Location of OS ABI field in ELF file header ident array +pub const EI_OSABI: usize = 7; +/// Location of ABI version field in ELF file header ident array +pub const EI_ABIVERSION: usize = 8; +/// Start of padding bytes +pub const EI_PAD: usize = 9; +/// Length of ELF file header platform-independent identification fields (e_ident[]) +pub const EI_NIDENT: usize = 16; + +/// ELF magic number byte 1 +pub const ELFMAG0: u8 = 0x7f; +/// ELF magic number byte 2 +pub const ELFMAG1: u8 = 0x45; +/// ELF magic number byte 3 +pub const ELFMAG2: u8 = 0x4c; +/// ELF magic number byte 4 +pub const ELFMAG3: u8 = 0x46; +pub const ELFMAGIC: [u8; 4] = [ELFMAG0, ELFMAG1, ELFMAG2, ELFMAG3]; + +// ELFCLASS* define constants for e_ident[EI_CLASS] + +/// Invalid ELF file class +pub const ELFCLASSNONE: u8 = 0; +/// 32-bit ELF file +pub const ELFCLASS32: u8 = 1; +/// 64-bit ELF file +pub const ELFCLASS64: u8 = 2; + +// ELFDATA* define constants for e_ident[EI_DATA] + +/// Invalid ELF data format +pub const ELFDATANONE: u8 = 0; +/// 2's complement values, with the least significant byte occupying the lowest address. +pub const ELFDATA2LSB: u8 = 1; +/// 2's complement values, with the most significant byte occupying the lowest address. +pub const ELFDATA2MSB: u8 = 2; + +// ELFOSABI* define constants for e_ident[EI_OSABI] + +/// No extensions or unspecified +pub const ELFOSABI_NONE: u8 = 0; +/// Alias of unspecified for UNIX System V ABI +pub const ELFOSABI_SYSV: u8 = 0; +/// Hewlett-Packard HP-UX +pub const ELFOSABI_HPUX: u8 = 1; +/// NetBSD +pub const ELFOSABI_NETBSD: u8 = 2; +/// GNU +pub const ELFOSABI_GNU: u8 = 3; +/// Linux historical - alias for ELFOSABI_GNU +pub const ELFOSABI_LINUX: u8 = 3; +/// Sun Solaris +pub const ELFOSABI_SOLARIS: u8 = 6; +/// AIX +pub const ELFOSABI_AIX: u8 = 7; +/// IRIX +pub const ELFOSABI_IRIX: u8 = 8; +/// FreeBSD +pub const ELFOSABI_FREEBSD: u8 = 9; +/// Compaq TRU64 UNIX +pub const ELFOSABI_TRU64: u8 = 10; +/// Novell Modesto +pub const ELFOSABI_MODESTO: u8 = 11; +/// Open BSD +pub const ELFOSABI_OPENBSD: u8 = 12; +/// Open VMS +pub const ELFOSABI_OPENVMS: u8 = 13; +/// Hewlett-Packard Non-Stop Kernel +pub const ELFOSABI_NSK: u8 = 14; +/// Amiga Research OS +pub const ELFOSABI_AROS: u8 = 15; +/// The FenixOS highly scalable multi-core OS +pub const ELFOSABI_FENIXOS: u8 = 16; +/// Nuxi CloudABI +pub const ELFOSABI_CLOUDABI: u8 = 17; +/// Stratus Technologies OpenVOS +pub const ELFOSABI_OPENVOS: u8 = 18; +// 64-255 Architecture-specific value range + +// ET_* define constants for the ELF File Header's e_type field. +// Represented as Elf32_Half in Elf32_Ehdr and Elf64_Half in Elf64_Ehdr which +// are both are 2-byte unsigned integers with 2-byte alignment + +/// No file type +pub const ET_NONE: u16 = 0; +/// Relocatable file +pub const ET_REL: u16 = 1; +/// Executable file +pub const ET_EXEC: u16 = 2; +/// Shared object file +pub const ET_DYN: u16 = 3; +/// Core file +pub const ET_CORE: u16 = 4; +/// Operating system-specific +pub const ET_LOOS: u16 = 0xfe00; +/// Operating system-specific +pub const ET_HIOS: u16 = 0xfeff; +/// Processor-specific +pub const ET_LOPROC: u16 = 0xff00; +/// Processor-specific +pub const ET_HIPROC: u16 = 0xffff; + +// EM_* define constants for the ELF File Header's e_machine field. +// Represented as Elf32_Half in Elf32_Ehdr and Elf64_Half in Elf64_Ehdr which +// are both 2-byte unsigned integers with 2-byte alignment + +/// No machine +pub const EM_NONE: u16 = 0; +/// AT&T WE 32100 +pub const EM_M32: u16 = 1; +/// SPARC +pub const EM_SPARC: u16 = 2; +/// Intel 80386 +pub const EM_386: u16 = 3; +/// Motorola 68000 +pub const EM_68K: u16 = 4; +/// Motorola 88000 +pub const EM_88K: u16 = 5; +/// Intel MCU +pub const EM_IAMCU: u16 = 6; +/// Intel 80860 +pub const EM_860: u16 = 7; +/// MIPS I Architecture +pub const EM_MIPS: u16 = 8; +/// IBM System/370 Processor +pub const EM_S370: u16 = 9; +/// MIPS RS3000 Little-endian +pub const EM_MIPS_RS3_LE: u16 = 10; +// 11-14 Reserved for future use +/// Hewlett-Packard PA-RISC +pub const EM_PARISC: u16 = 15; +// 16 Reserved for future use +/// Fujitsu VPP500 +pub const EM_VPP500: u16 = 17; +/// Enhanced instruction set SPARC +pub const EM_SPARC32PLUS: u16 = 18; +/// Intel 80960 +pub const EM_960: u16 = 19; +/// PowerPC +pub const EM_PPC: u16 = 20; +/// 64-bit PowerPC +pub const EM_PPC64: u16 = 21; +/// IBM System/390 Processor +pub const EM_S390: u16 = 22; +/// IBM SPU/SPC +pub const EM_SPU: u16 = 23; +// 24-35 Reserved for future use +/// NEC V800 +pub const EM_V800: u16 = 36; +/// Fujitsu FR20 +pub const EM_FR20: u16 = 37; +/// TRW RH-32 +pub const EM_RH32: u16 = 38; +/// Motorola RCE +pub const EM_RCE: u16 = 39; +/// ARM 32-bit architecture (AARCH32) +pub const EM_ARM: u16 = 40; +/// Digital Alpha +pub const EM_ALPHA: u16 = 41; +/// Hitachi SH +pub const EM_SH: u16 = 42; +/// SPARC Version 9 +pub const EM_SPARCV9: u16 = 43; +/// Siemens TriCore embedded processor +pub const EM_TRICORE: u16 = 44; +/// Argonaut RISC Core, Argonaut Technologies Inc. +pub const EM_ARC: u16 = 45; +/// Hitachi H8/300 +pub const EM_H8_300: u16 = 46; +/// Hitachi H8/300H +pub const EM_H8_300H: u16 = 47; +/// Hitachi H8S +pub const EM_H8S: u16 = 48; +/// Hitachi H8/500 +pub const EM_H8_500: u16 = 49; +/// Intel IA-64 processor architecture +pub const EM_IA_64: u16 = 50; +/// Stanford MIPS-X +pub const EM_MIPS_X: u16 = 51; +/// Motorola ColdFire +pub const EM_COLDFIRE: u16 = 52; +/// Motorola M68HC12 +pub const EM_68HC12: u16 = 53; +/// Fujitsu MMA Multimedia Accelerator +pub const EM_MMA: u16 = 54; +/// Siemens PCP +pub const EM_PCP: u16 = 55; +/// Sony nCPU embedded RISC processor +pub const EM_NCPU: u16 = 56; +/// Denso NDR1 microprocessor +pub const EM_NDR1: u16 = 57; +/// Motorola Star*Core processor +pub const EM_STARCORE: u16 = 58; +/// Toyota ME16 processor +pub const EM_ME16: u16 = 59; +/// STMicroelectronics ST100 processor +pub const EM_ST100: u16 = 60; +/// Advanced Logic Corp. TinyJ embedded processor family +pub const EM_TINYJ: u16 = 61; +/// AMD x86-64 architecture +pub const EM_X86_64: u16 = 62; +/// Sony DSP Processor +pub const EM_PDSP: u16 = 63; +/// Digital Equipment Corp. PDP-10 +pub const EM_PDP10: u16 = 64; +/// Digital Equipment Corp. PDP-11 +pub const EM_PDP11: u16 = 65; +/// Siemens FX66 microcontroller +pub const EM_FX66: u16 = 66; +/// STMicroelectronics ST9+ 8/16 bit microcontroller +pub const EM_ST9PLUS: u16 = 67; +/// STMicroelectronics ST7 8-bit microcontroller +pub const EM_ST7: u16 = 68; +/// Motorola MC68HC16 Microcontroller +pub const EM_68HC16: u16 = 69; +/// Motorola MC68HC11 Microcontroller +pub const EM_68HC11: u16 = 70; +/// Motorola MC68HC08 Microcontroller +pub const EM_68HC08: u16 = 71; +/// Motorola MC68HC05 Microcontroller +pub const EM_68HC05: u16 = 72; +/// Silicon Graphics SVx +pub const EM_SVX: u16 = 73; +/// STMicroelectronics ST19 8-bit microcontroller +pub const EM_ST19: u16 = 74; +/// Digital VAX +pub const EM_VAX: u16 = 75; +/// Axis Communications 32-bit embedded processor +pub const EM_CRIS: u16 = 76; +/// Infineon Technologies 32-bit embedded processor +pub const EM_JAVELIN: u16 = 77; +/// Element 14 64-bit DSP Processor +pub const EM_FIREPATH: u16 = 78; +/// LSI Logic 16-bit DSP Processor +pub const EM_ZSP: u16 = 79; +/// Donald Knuth's educational 64-bit processor +pub const EM_MMIX: u16 = 80; +/// Harvard University machine-independent object files +pub const EM_HUANY: u16 = 81; +/// SiTera Prism +pub const EM_PRISM: u16 = 82; +/// Atmel AVR 8-bit microcontroller +pub const EM_AVR: u16 = 83; +/// Fujitsu FR30 +pub const EM_FR30: u16 = 84; +/// Mitsubishi D10V +pub const EM_D10V: u16 = 85; +/// Mitsubishi D30V +pub const EM_D30V: u16 = 86; +/// NEC v850 +pub const EM_V850: u16 = 87; +/// Mitsubishi M32R +pub const EM_M32R: u16 = 88; +/// Matsushita MN10300 +pub const EM_MN10300: u16 = 89; +/// Matsushita MN10200 +pub const EM_MN10200: u16 = 90; +/// picoJava +pub const EM_PJ: u16 = 91; +/// OpenRISC 32-bit embedded processor +pub const EM_OPENRISC: u16 = 92; +/// ARC International ARCompact processor (old spelling/synonym: EM_ARC_A5) +pub const EM_ARC_COMPACT: u16 = 93; +/// Tensilica Xtensa Architecture +pub const EM_XTENSA: u16 = 94; +/// Alphamosaic VideoCore processor +pub const EM_VIDEOCORE: u16 = 95; +/// Thompson Multimedia General Purpose Processor +pub const EM_TMM_GPP: u16 = 96; +/// National Semiconductor 32000 series +pub const EM_NS32K: u16 = 97; +/// Tenor Network TPC processor +pub const EM_TPC: u16 = 98; +/// Trebia SNP 1000 processor +pub const EM_SNP1K: u16 = 99; +/// STMicroelectronics (www.st.com) ST200 microcontroller +pub const EM_ST200: u16 = 100; +/// Ubicom IP2xxx microcontroller family +pub const EM_IP2K: u16 = 101; +/// MAX Processor +pub const EM_MAX: u16 = 102; +/// National Semiconductor CompactRISC microprocessor +pub const EM_CR: u16 = 103; +/// Fujitsu F2MC16 +pub const EM_F2MC16: u16 = 104; +/// Texas Instruments embedded microcontroller msp430 +pub const EM_MSP430: u16 = 105; +/// Analog Devices Blackfin (DSP) processor +pub const EM_BLACKFIN: u16 = 106; +/// S1C33 Family of Seiko Epson processors +pub const EM_SE_C33: u16 = 107; +/// Sharp embedded microprocessor +pub const EM_SEP: u16 = 108; +/// Arca RISC Microprocessor +pub const EM_ARCA: u16 = 109; +/// Microprocessor series from PKU-Unity Ltd. and MPRC of Peking University +pub const EM_UNICORE: u16 = 110; +/// eXcess: 16/32/64-bit configurable embedded CPU +pub const EM_EXCESS: u16 = 111; +/// Icera Semiconductor Inc. Deep Execution Processor +pub const EM_DXP: u16 = 112; +/// Altera Nios II soft-core processor +pub const EM_ALTERA_NIOS2: u16 = 113; +/// National Semiconductor CompactRISC CRX microprocessor +pub const EM_CRX: u16 = 114; +/// Motorola XGATE embedded processor +pub const EM_XGATE: u16 = 115; +/// Infineon C16x/XC16x processor +pub const EM_C166: u16 = 116; +/// Renesas M16C series microprocessors +pub const EM_M16C: u16 = 117; +/// Microchip Technology dsPIC30F Digital Signal Controller +pub const EM_DSPIC30F: u16 = 118; +/// Freescale Communication Engine RISC core +pub const EM_CE: u16 = 119; +/// Renesas M32C series microprocessors +pub const EM_M32C: u16 = 120; +// 121-130 Reserved for future use +/// Altium TSK3000 core +pub const EM_TSK3000: u16 = 131; +/// Freescale RS08 embedded processor +pub const EM_RS08: u16 = 132; +/// Analog Devices SHARC family of 32-bit DSP processors +pub const EM_SHARC: u16 = 133; +/// Cyan Technology eCOG2 microprocessor +pub const EM_ECOG2: u16 = 134; +/// Sunplus S+core7 RISC processor +pub const EM_SCORE7: u16 = 135; +/// New Japan Radio (NJR) 24-bit DSP Processor +pub const EM_DSP24: u16 = 136; +/// Broadcom VideoCore III processor +pub const EM_VIDEOCORE3: u16 = 137; +/// RISC processor for Lattice FPGA architecture +pub const EM_LATTICEMICO32: u16 = 138; +/// Seiko Epson C17 family +pub const EM_SE_C17: u16 = 139; +/// The Texas Instruments TMS320C6000 DSP family +pub const EM_TI_C6000: u16 = 140; +/// The Texas Instruments TMS320C2000 DSP family +pub const EM_TI_C2000: u16 = 141; +/// The Texas Instruments TMS320C55x DSP family +pub const EM_TI_C5500: u16 = 142; +/// Texas Instruments Application Specific RISC Processor, 32bit fetch +pub const EM_TI_ARP32: u16 = 143; +/// Texas Instruments Programmable Realtime Unit +pub const EM_TI_PRU: u16 = 144; +// 145-159 Reserved for future use +/// STMicroelectronics 64bit VLIW Data Signal Processor +pub const EM_MMDSP_PLUS: u16 = 160; +/// Cypress M8C microprocessor +pub const EM_CYPRESS_M8C: u16 = 161; +/// Renesas R32C series microprocessors +pub const EM_R32C: u16 = 162; +/// NXP Semiconductors TriMedia architecture family +pub const EM_TRIMEDIA: u16 = 163; +/// QUALCOMM DSP6 Processor +pub const EM_QDSP6: u16 = 164; +/// Intel 8051 and variants +pub const EM_8051: u16 = 165; +/// STMicroelectronics STxP7x family of configurable and extensible RISC processors +pub const EM_STXP7X: u16 = 166; +/// Andes Technology compact code size embedded RISC processor family +pub const EM_NDS32: u16 = 167; +/// Cyan Technology eCOG1X family +pub const EM_ECOG1: u16 = 168; +/// Cyan Technology eCOG1X family +pub const EM_ECOG1X: u16 = 168; +/// Dallas Semiconductor MAXQ30 Core Micro-controllers +pub const EM_MAXQ30: u16 = 169; +/// New Japan Radio (NJR) 16-bit DSP Processor +pub const EM_XIMO16: u16 = 170; +/// M2000 Reconfigurable RISC Microprocessor +pub const EM_MANIK: u16 = 171; +/// Cray Inc. NV2 vector architecture +pub const EM_CRAYNV2: u16 = 172; +/// Renesas RX family +pub const EM_RX: u16 = 173; +/// Imagination Technologies META processor architecture +pub const EM_METAG: u16 = 174; +/// MCST Elbrus general purpose hardware architecture +pub const EM_MCST_ELBRUS: u16 = 175; +/// Cyan Technology eCOG16 family +pub const EM_ECOG16: u16 = 176; +/// National Semiconductor CompactRISC CR16 16-bit microprocessor +pub const EM_CR16: u16 = 177; +/// Freescale Extended Time Processing Unit +pub const EM_ETPU: u16 = 178; +/// Infineon Technologies SLE9X core +pub const EM_SLE9X: u16 = 179; +/// Intel L10M +pub const EM_L10M: u16 = 180; +/// Intel K10M +pub const EM_K10M: u16 = 181; +// 182 Reserved for future Intel use +/// ARM 64-bit architecture (AARCH64) +pub const EM_AARCH64: u16 = 183; +// 184 Reserved for future ARM use +/// Atmel Corporation 32-bit microprocessor family +pub const EM_AVR32: u16 = 185; +/// STMicroeletronics STM8 8-bit microcontroller +pub const EM_STM8: u16 = 186; +/// Tilera TILE64 multicore architecture family +pub const EM_TILE64: u16 = 187; +/// Tilera TILEPro multicore architecture family +pub const EM_TILEPRO: u16 = 188; +/// Xilinx MicroBlaze 32-bit RISC soft processor core +pub const EM_MICROBLAZE: u16 = 189; +/// NVIDIA CUDA architecture +pub const EM_CUDA: u16 = 190; +/// Tilera TILE-Gx multicore architecture family +pub const EM_TILEGX: u16 = 191; +/// CloudShield architecture family +pub const EM_CLOUDSHIELD: u16 = 192; +/// KIPO-KAIST Core-A 1st generation processor family +pub const EM_COREA_1ST: u16 = 193; +/// KIPO-KAIST Core-A 2nd generation processor family +pub const EM_COREA_2ND: u16 = 194; +/// Synopsys ARCompact V2 +pub const EM_ARC_COMPACT2: u16 = 195; +/// Open8 8-bit RISC soft processor core +pub const EM_OPEN8: u16 = 196; +/// Renesas RL78 family +pub const EM_RL78: u16 = 197; +/// Broadcom VideoCore V processor +pub const EM_VIDEOCORE5: u16 = 198; +/// Renesas 78KOR family +pub const EM_78KOR: u16 = 199; +/// Freescale 56800EX Digital Signal Controller (DSC) +pub const EM_56800EX: u16 = 200; +/// Beyond BA1 CPU architecture +pub const EM_BA1: u16 = 201; +/// Beyond BA2 CPU architecture +pub const EM_BA2: u16 = 202; +/// XMOS xCORE processor family +pub const EM_XCORE: u16 = 203; +/// Microchip 8-bit PIC(r) family +pub const EM_MCHP_PIC: u16 = 204; +/// Reserved by Intel +pub const EM_INTEL205: u16 = 205; +/// Reserved by Intel +pub const EM_INTEL206: u16 = 206; +/// Reserved by Intel +pub const EM_INTEL207: u16 = 207; +/// Reserved by Intel +pub const EM_INTEL208: u16 = 208; +/// Reserved by Intel +pub const EM_INTEL209: u16 = 209; +/// KM211 KM32 32-bit processor +pub const EM_KM32: u16 = 210; +/// KM211 KMX32 32-bit processor +pub const EM_KMX32: u16 = 211; +/// KM211 KMX16 16-bit processor +pub const EM_KMX16: u16 = 212; +/// KM211 KMX8 8-bit processor +pub const EM_KMX8: u16 = 213; +/// KM211 KVARC processor +pub const EM_KVARC: u16 = 214; +/// Paneve CDP architecture family +pub const EM_CDP: u16 = 215; +/// Cognitive Smart Memory Processor +pub const EM_COGE: u16 = 216; +/// Bluechip Systems CoolEngine +pub const EM_COOL: u16 = 217; +/// Nanoradio Optimized RISC +pub const EM_NORC: u16 = 218; +/// CSR Kalimba architecture family +pub const EM_CSR_KALIMBA: u16 = 219; +/// Zilog Z80 +pub const EM_Z80: u16 = 220; +/// Controls and Data Services VISIUMcore processor +pub const EM_VISIUM: u16 = 221; +/// FTDI Chip FT32 high performance 32-bit RISC architecture +pub const EM_FT32: u16 = 222; +/// Moxie processor family +pub const EM_MOXIE: u16 = 223; +/// AMD GPU architecture +pub const EM_AMDGPU: u16 = 224; +/// RISC-V +pub const EM_RISCV: u16 = 243; +/// Lanai 32-bit processor +pub const EM_LANAI: u16 = 244; +/// Linux BPF +pub const EM_BPF: u16 = 247; +/// NEC SX-Aurora VE +pub const EM_VE: u16 = 251; +/// C-Sky +pub const EM_CSKY: u16 = 252; +/// LoongArch +pub const EM_LOONGARCH: u16 = 258; +/// Fujitsu FR-V +pub const EM_FRV: u16 = 0x5441; + +// EV_* define constants for the ELF File Header's e_version field. +// Represented as Elf32_Word in Elf32_Ehdr and Elf64_Word in Elf64_Ehdr which +// are both 4-byte unsigned integers with 4-byte alignment + +/// Invalid version +pub const EV_NONE: u8 = 0; +/// Current version +pub const EV_CURRENT: u8 = 1; + +/// If the number of program headers is greater than or equal to PN_XNUM (0xffff), +/// this member has the value PN_XNUM (0xffff). The actual number of +/// program header table entries is contained in the sh_info field of the +/// section header at index 0. Otherwise, the sh_info member of the initial +/// section header entry contains the value zero. +pub const PN_XNUM: u16 = 0xffff; + +// PF_* define constants for the ELF Program Header's p_flags field. +// Represented as Elf32_Word in Elf32_Ehdr and Elf64_Word in Elf64_Ehdr which +// are both 4-byte unsigned integers with 4-byte alignment + +pub const PF_NONE: u32 = 0; +/// Executable program segment +pub const PF_X: u32 = 1; +/// Writable program segment +pub const PF_W: u32 = 2; +/// Readable program segment +pub const PF_R: u32 = 4; +// All bits included in the PF_MASKOS mask are reserved for operating system-specific semantics. +pub const PF_MASKOS: u32 = 0x0ff00000; +// All bits included in the PF_MASKPROC mask are reserved for processor-specific semantics. +pub const PF_MASKPROC: u32 = 0xf0000000; + +// PT_* define constants for the ELF Program Header's p_type field. +// Represented as Elf32_Word in Elf32_Ehdr and Elf64_Word in Elf64_Ehdr which +// are both 4-byte unsigned integers with 4-byte alignment + +/// Program header table entry unused +pub const PT_NULL: u32 = 0; +/// Loadable program segment +pub const PT_LOAD: u32 = 1; +/// Dynamic linking information +pub const PT_DYNAMIC: u32 = 2; +/// Program interpreter +pub const PT_INTERP: u32 = 3; +/// Auxiliary information +pub const PT_NOTE: u32 = 4; +/// Unused +pub const PT_SHLIB: u32 = 5; +/// The program header table +pub const PT_PHDR: u32 = 6; +/// Thread-local storage segment +pub const PT_TLS: u32 = 7; +/// GCC .eh_frame_hdr segment +pub const PT_GNU_EH_FRAME: u32 = 0x6474e550; +/// Indicates stack executability +pub const PT_GNU_STACK: u32 = 0x6474e551; +/// Read-only after relocation +pub const PT_GNU_RELRO: u32 = 0x6474e552; +/// The segment contains .note.gnu.property section +pub const PT_GNU_PROPERTY: u32 = 0x6474e553; +/// Values between [PT_LOOS, PT_HIOS] in this inclusive range are reserved for +/// operating system-specific semantics. +pub const PT_LOOS: u32 = 0x60000000; +/// Values between [PT_LOOS, PT_HIOS] in this inclusive range are reserved for +/// operating system-specific semantics. +pub const PT_HIOS: u32 = 0x6fffffff; +/// Values between [PT_LOPROC, PT_HIPROC] in this inclusive range are reserved +/// for processor-specific semantics. +pub const PT_LOPROC: u32 = 0x70000000; +/// Values between [PT_LOPROC, PT_HIPROC] in this inclusive range are reserved +/// for processor-specific semantics. +pub const PT_HIPROC: u32 = 0x7fffffff; + +// SHT_* define constants for the ELF Section Header's p_type field. +// Represented as Elf32_Word in Elf32_Ehdr and Elf64_Word in Elf64_Ehdr which +// are both 4-byte unsigned integers with 4-byte alignment + +/// Inactive section with undefined values +pub const SHT_NULL: u32 = 0; +/// Information defined by the program, includes executable code and data +pub const SHT_PROGBITS: u32 = 1; +/// Section data contains a symbol table +pub const SHT_SYMTAB: u32 = 2; +/// Section data contains a string table +pub const SHT_STRTAB: u32 = 3; +/// Section data contains relocation entries with explicit addends +pub const SHT_RELA: u32 = 4; +/// Section data contains a symbol hash table. Must be present for dynamic linking +pub const SHT_HASH: u32 = 5; +/// Section data contains information for dynamic linking +pub const SHT_DYNAMIC: u32 = 6; +/// Section data contains information that marks the file in some way +pub const SHT_NOTE: u32 = 7; +/// Section data occupies no space in the file but otherwise resembles SHT_PROGBITS +pub const SHT_NOBITS: u32 = 8; +/// Section data contains relocation entries without explicit addends +pub const SHT_REL: u32 = 9; +/// Section is reserved but has unspecified semantics +pub const SHT_SHLIB: u32 = 10; +/// Section data contains a minimal set of dynamic linking symbols +pub const SHT_DYNSYM: u32 = 11; +/// Section data contains an array of constructors +pub const SHT_INIT_ARRAY: u32 = 14; +/// Section data contains an array of destructors +pub const SHT_FINI_ARRAY: u32 = 15; +/// Section data contains an array of pre-constructors +pub const SHT_PREINIT_ARRAY: u32 = 16; +/// Section group +pub const SHT_GROUP: u32 = 17; +/// Extended symbol table section index +pub const SHT_SYMTAB_SHNDX: u32 = 18; +/// Values in [SHT_LOOS, SHT_HIOS] are reserved for operating system-specific semantics. +pub const SHT_LOOS: u32 = 0x60000000; +/// Object attributes +pub const SHT_GNU_ATTRIBUTES: u32 = 0x6ffffff5; +/// GNU-style hash section +pub const SHT_GNU_HASH: u32 = 0x6ffffff6; +/// Pre-link library list +pub const SHT_GNU_LIBLIST: u32 = 0x6ffffff7; +/// Version definition section +pub const SHT_GNU_VERDEF: u32 = 0x6ffffffd; +/// Version needs section +pub const SHT_GNU_VERNEED: u32 = 0x6ffffffe; +/// Version symbol table +pub const SHT_GNU_VERSYM: u32 = 0x6fffffff; +/// Values in [SHT_LOOS, SHT_HIOS] are reserved for operating system-specific semantics. +pub const SHT_HIOS: u32 = 0x6fffffff; +/// Values in [SHT_LOPROC, SHT_HIPROC] are reserved for processor-specific semantics. +pub const SHT_LOPROC: u32 = 0x70000000; +/// IA_64 extension bits +pub const SHT_IA_64_EXT: u32 = 0x70000000; // SHT_LOPROC + 0; +/// IA_64 unwind section +pub const SHT_IA_64_UNWIND: u32 = 0x70000001; // SHT_LOPROC + 1; +/// Values in [SHT_LOPROC, SHT_HIPROC] are reserved for processor-specific semantics. +pub const SHT_HIPROC: u32 = 0x7fffffff; +/// Values in [SHT_LOUSER, SHT_HIUSER] are reserved for application-specific semantics. +pub const SHT_LOUSER: u32 = 0x80000000; +/// Values in [SHT_LOUSER, SHT_HIUSER] are reserved for application-specific semantics. +pub const SHT_HIUSER: u32 = 0x8fffffff; + +/// This value marks an undefined, missing, irrelevant, or otherwise meaningless +/// section reference. +pub const SHN_UNDEF: u16 = 0; +/// Symbols with st_shndx=SHN_ABS are absolute and are not affected by relocation. +pub const SHN_ABS: u16 = 0xfff1; +/// Symbols with st_shndx=SHN_COMMON are sometimes used for unallocated C external variables. +pub const SHN_COMMON: u16 = 0xfff2; +pub const SHN_XINDEX: u16 = 0xffff; + +// SHF_* define constants for the ELF Section Header's sh_flags field. +// Represented as Elf32_Word in Elf32_Ehdr and Elf64_Xword in Elf64_Ehdr which +// are both 4-byte and 8-byte unsigned integers, respectively. +// All of the constants are < 32-bits, so we use a u32 to represent these in order +// to make working with them easier. + +/// Empty flags +pub const SHF_NONE: u32 = 0; +/// The section contains data that should be writable during process execution. +pub const SHF_WRITE: u32 = 1; +/// The section occupies memory during process execution. Some control sections +/// do not reside in the memory image of an object file; this attribute is off for +/// those sections. +pub const SHF_ALLOC: u32 = 1 << 1; +/// The section contains executable machine instructions. +pub const SHF_EXECINSTR: u32 = 1 << 2; +/// The data in the section may be merged to eliminate duplication. Unless the +/// SHF_STRINGS flag is also set, the data elements in the section are of a uniform size. +/// The size of each element is specified in the section header's sh_entsize field. If +/// the SHF_STRINGS flag is also set, the data elements consist of null-terminated +/// character strings. The size of each character is specified in the section header's +/// sh_entsize field. +/// +/// Each element in the section is compared against other elements in sections with the +/// same name, type and flags. Elements that would have identical values at program +/// run-time may be merged. Relocations referencing elements of such sections must be +/// resolved to the merged locations of the referenced values. Note that any relocatable +/// values, including values that would result in run-time relocations, must be analyzed +/// to determine whether the run-time values would actually be identical. An +/// ABI-conforming object file may not depend on specific elements being merged, and an +/// ABI-conforming link editor may choose not to merge specific elements. +pub const SHF_MERGE: u32 = 1 << 4; +/// The data elements in the section consist of null-terminated character strings. +/// The size of each character is specified in the section header's sh_entsize field. +pub const SHF_STRINGS: u32 = 1 << 5; +/// The sh_info field of this section header holds a section header table index. +pub const SHF_INFO_LINK: u32 = 1 << 6; +/// This flag adds special ordering requirements for link editors. The requirements +/// apply if the sh_link field of this section's header references another section (the +/// linked-to section). If this section is combined with other sections in the output +/// file, it must appear in the same relative order with respect to those sections, +/// as the linked-to section appears with respect to sections the linked-to section is +/// combined with. +pub const SHF_LINK_ORDER: u32 = 1 << 7; +/// This section requires special OS-specific processing (beyond the standard linking +/// rules) to avoid incorrect behavior. If this section has either an sh_type value or +/// contains sh_flags bits in the OS-specific ranges for those fields, and a link +/// editor processing this section does not recognize those values, then the link editor +/// should reject the object file containing this section with an error. +pub const SHF_OS_NONCONFORMING: u32 = 1 << 8; +/// This section is a member (perhaps the only one) of a section group. The section must +/// be referenced by a section of type SHT_GROUP. The SHF_GROUP flag may be set only for +/// sections contained in relocatable objects (objects with the ELF header e_type member +/// set to ET_REL). +pub const SHF_GROUP: u32 = 1 << 9; +/// This section holds Thread-Local Storage, meaning that each separate execution flow +/// has its own distinct instance of this data. Implementations need not support this flag. +pub const SHF_TLS: u32 = 1 << 10; +/// This flag identifies a section containing compressed data. SHF_COMPRESSED applies only +/// to non-allocable sections, and cannot be used in conjunction with SHF_ALLOC. In +/// addition, SHF_COMPRESSED cannot be applied to sections of type SHT_NOBITS. +/// +/// All relocations to a compressed section specifiy offsets to the uncompressed section +/// data. It is therefore necessary to decompress the section data before relocations can +/// be applied. Each compressed section specifies the algorithm independently. It is +/// permissible for different sections in a given ELF object to employ different +/// compression algorithms. +/// +/// Compressed sections begin with a compression header structure that identifies the +/// compression algorithm. +pub const SHF_COMPRESSED: u32 = 1 << 11; +/// Masked bits are reserved for operating system-specific semantics. +pub const SHF_MASKOS: u32 = 0x0ff00000; +/// Masked bits are reserved for processor-specific semantics. +pub const SHF_MASKPROC: u32 = 0xf0000000; + +// STT_* define constants for the ELF Symbol's st_type (encoded in the st_info field). + +/// Unspecified symbol type +pub const STT_NOTYPE: u8 = 0; +/// Data object symbol +pub const STT_OBJECT: u8 = 1; +/// Code object symbol +pub const STT_FUNC: u8 = 2; +/// Section symbol +pub const STT_SECTION: u8 = 3; +/// File name symbol +pub const STT_FILE: u8 = 4; +/// Common data object symbol +pub const STT_COMMON: u8 = 5; +/// Thread-local data object symbol +pub const STT_TLS: u8 = 6; +/// Indirect code object symbol +pub const STT_GNU_IFUNC: u8 = 10; +/// Values between [STT_LOOS, STT_HIOS] in this inclusive range are reserved for +/// operating system-specific semantics. +pub const STT_LOOS: u8 = 10; +/// Values between [STT_LOOS, STT_HIOS] in this inclusive range are reserved for +/// operating system-specific semantics. +pub const STT_HIOS: u8 = 12; +/// Values between [STT_LOPROC, STT_HIPROC] in this inclusive range are reserved +/// for processor-specific semantics. +pub const STT_LOPROC: u8 = 13; +/// Values between [STT_LOPROC, STT_HIPROC] in this inclusive range are reserved +/// for processor-specific semantics. +pub const STT_HIPROC: u8 = 15; + +// STB_* define constants for the ELF Symbol's st_bind (encoded in the st_info field). + +/// Local symbols are not visible outside the object file containing their +/// definition. Local symbols of the same name may exist in multiple files +/// without interfering with each other. +pub const STB_LOCAL: u8 = 0; +/// Global symbols are visible to all object files being combined. One file's +/// definition of a global symbol will satisfy another file's undefined +/// reference to the same global symbol. +pub const STB_GLOBAL: u8 = 1; +/// Weak symbols resemble global symbols, but their definitions have lower +/// precedence. +pub const STB_WEAK: u8 = 2; +/// Unique symbol +pub const STB_GNU_UNIQUE: u8 = 10; +/// Values between [STB_LOOS, STB_HIOS] in this inclusive range are reserved for +/// operating system-specific semantics. +pub const STB_LOOS: u8 = 10; +/// Values between [STB_LOOS, STB_HIOS] in this inclusive range are reserved for +/// operating system-specific semantics. +pub const STB_HIOS: u8 = 12; +/// Values between [STB_LOPROC, STB_HIPROC] in this inclusive range are reserved +/// for processor-specific semantics. +pub const STB_LOPROC: u8 = 13; +/// GLIBC alias for `STB_LOPROC` +pub const STB_MIPS_SPLIT_COMMON: u8 = STB_LOPROC; +/// Values between [STB_LOPROC, STB_HIPROC] in this inclusive range are reserved +/// for processor-specific semantics. +pub const STB_HIPROC: u8 = 15; + +// STV_* define constants for the ELF Symbol's st_visibility (encoded in the st_other field). + +/// The visibility of symbols with the STV_DEFAULT attribute is as specified by +/// the symbol's binding type. That is, global and weak symbols are visible +/// outside of their defining component (executable file or shared object). +/// Local symbols are hidden, as described below. Global and weak symbols are +/// also preemptable, that is, they may by preempted by definitions of the same +/// name in another component. +pub const STV_DEFAULT: u8 = 0; +/// The meaning of this visibility attribute may be defined by processor +/// supplements to further constrain hidden symbols. A processor supplement's +/// definition should be such that generic tools can safely treat internal +/// symbols as hidden. +pub const STV_INTERNAL: u8 = 1; +/// A symbol defined in the current component is hidden if its name is not +/// visible to other components. Such a symbol is necessarily protected. This +/// attribute may be used to control the external interface of a component. Note +/// that an object named by such a symbol may still be referenced from another +/// component if its address is passed outside. +pub const STV_HIDDEN: u8 = 2; +/// A symbol defined in the current component is protected if it is visible in +/// other components but not preemptable, meaning that any reference to such a +/// symbol from within the defining component must be resolved to the definition +/// in that component, even if there is a definition in another component that +/// would preempt by the default rules. +pub const STV_PROTECTED: u8 = 3; + +/// An entry with a DT_NULL tag marks the end of the _DYNAMIC array. +pub const DT_NULL: i64 = 0; +/// This element holds the string table offset of a null-terminated string, +/// giving the name of a needed library. The offset is an index into the table +/// recorded in the DT_STRTAB code. The dynamic array may contain multiple +/// entries with this type. These entries' relative order is significant, though +/// their relation to entries of other types is not. +pub const DT_NEEDED: i64 = 1; +/// This element holds the total size, in bytes, of the relocation entries +/// associated with the procedure linkage table. If an entry of type DT_JMPREL +/// is present, a DT_PLTRELSZ must accompany it. +pub const DT_PLTRELSZ: i64 = 2; +/// This element holds an address associated with the procedure linkage table +/// and/or the global offset table. +pub const DT_PLTGOT: i64 = 3; +/// This element holds the address of the symbol hash table. This hash table +/// refers to the symbol table referenced by the DT_SYMTAB element. +pub const DT_HASH: i64 = 4; +/// This element holds the address of the string table. Symbol names, library +/// names, and other strings reside in this table. +pub const DT_STRTAB: i64 = 5; +/// This element holds the address of the symbol table. +pub const DT_SYMTAB: i64 = 6; +/// This element holds the address of a relocation table. Entries in the table +/// have explicit addends, (Rela). An object file may have multiple relocation +/// sections. When building the relocation table for an executable or shared +/// object file, the link editor catenates those sections to form a single +/// table. Although the sections remain independent in the object file, the +/// dynamic linker sees a single table. When the dynamic linker creates the +/// process image for an executable file or adds a shared object to the process +/// image, it reads the relocation table and performs the associated actions. +/// If this element is present, the dynamic structure must also have DT_RELASZ +/// and DT_RELAENT elements. When relocation is mandatory for a file, either +/// DT_RELA or DT_REL may occur (both are permitted but not required). +pub const DT_RELA: i64 = 7; +/// This element holds the total size, in bytes, of the DT_RELA relocation table. +pub const DT_RELASZ: i64 = 8; +/// This element holds the size, in bytes, of the DT_RELA relocation entry. +pub const DT_RELAENT: i64 = 9; +/// This element holds the size, in bytes, of the string table. +pub const DT_STRSZ: i64 = 10; +/// This element holds the size, in bytes, of a symbol table entry. +pub const DT_SYMENT: i64 = 11; +/// This element holds the address of the initialization function. +pub const DT_INIT: i64 = 12; +/// This element holds the address of the termination function. +pub const DT_FINI: i64 = 13; +/// This element holds the string table offset of a null-terminated string, +/// giving the name of the shared object. The offset is an index into the table +/// recorded in the DT_STRTAB entry. +pub const DT_SONAME: i64 = 14; +/// This element holds the string table offset of a null-terminated search +/// library search path string. The offset is an index into the table recorded +/// in the DT_STRTAB entry. Its use has been superseded by DT_RUNPATH. +pub const DT_RPATH: i64 = 15; +/// This element's presence in a shared object library alters the dynamic +/// linker's symbol resolution algorithm for references within the library. +/// Instead of starting a symbol search with the executable file, the dynamic +/// linker starts from the shared object itself. If the shared object fails to +/// supply the referenced symbol, the dynamic linker then searches the +/// executable file and other shared objects as usual. Its use has been +/// superseded by the DF_SYMBOLIC flag. +pub const DT_SYMBOLIC: i64 = 16; +/// This element is similar to DT_RELA, except its table has implicit addends (Rel). +/// If this element is present, the dynamic structure must also have DT_RELSZ +/// and DT_RELENT elements. +pub const DT_REL: i64 = 17; +/// This element holds the total size, in bytes, of the DT_REL relocation table. +pub const DT_RELSZ: i64 = 18; +/// This element holds the size, in bytes, of the DT_REL relocation entry. +pub const DT_RELENT: i64 = 19; +/// This member specifies the type of relocation entry to which the procedure +/// linkage table refers. The d_val member holds DT_REL or DT_RELA, as +/// appropriate. All relocations in a procedure linkage table must use the same +/// relocation. +pub const DT_PLTREL: i64 = 20; +/// This member is used for debugging. Its contents are not specified for the +/// ABI; programs that access this entry are not ABI-conforming. +pub const DT_DEBUG: i64 = 21; +/// This member's absence signifies that no relocation entry should cause a +/// modification to a non-writable segment, as specified by the segment +/// permissions in the program header table. If this member is present, one or +/// more relocation entries might request modifications to a non-writable +/// segment, and the dynamic linker can prepare accordingly. Its use has been +/// superseded by the DF_TEXTREL flag. +pub const DT_TEXTREL: i64 = 22; +/// If present, this entry's d_ptr member holds the address of relocation +/// entries associated solely with the procedure linkage table. Separating these +/// relocation entries lets the dynamic linker ignore them during process +/// initialization, if lazy binding is enabled. If this entry is present, the +/// related entries of types DT_PLTRELSZ and DT_PLTREL must also be present. +pub const DT_JMPREL: i64 = 23; +/// If present in a shared object or executable, this entry instructs the +/// dynamic linker to process all relocations for the object containing this +/// entry before transferring control to the program. The presence of this entry +/// takes precedence over a directive to use lazy binding for this object when +/// specified through the environment or via dlopen(BA_LIB). Its use has been +/// superseded by the DF_BIND_NOW flag. +pub const DT_BIND_NOW: i64 = 24; +/// This element holds the address of the array of pointers to initialization functions. +pub const DT_INIT_ARRAY: i64 = 25; +/// This element holds the address of the array of pointers to termination functions. +pub const DT_FINI_ARRAY: i64 = 26; +/// This element holds the size in bytes of the array of initialization +/// functions pointed to by the DT_INIT_ARRAY entry. If an object has a +/// DT_INIT_ARRAY entry, it must also have a DT_INIT_ARRAYSZ entry. +pub const DT_INIT_ARRAYSZ: i64 = 27; +/// This element holds the size in bytes of the array of termination functions +/// pointed to by the DT_FINI_ARRAY entry. If an object has a DT_FINI_ARRAY +/// entry, it must also have a DT_FINI_ARRAYSZ entry. +pub const DT_FINI_ARRAYSZ: i64 = 28; +/// This element holds the string table offset of a null-terminated library +/// search path string. The offset is an index into the table recorded in the +/// DT_STRTAB entry. +pub const DT_RUNPATH: i64 = 29; +/// This element holds flag values specific to the object being loaded. Each +/// flag value will have the name DF_flag_name. Defined values and their +/// meanings are described below. All other values are reserved. +pub const DT_FLAGS: i64 = 30; +/// This element holds the address of the array of pointers to +/// pre-initialization functions. The DT_PREINIT_ARRAY table is processed only +/// in an executable file; it is ignored if contained in a shared object. +pub const DT_PREINIT_ARRAY: i64 = 32; +/// This element holds the size in bytes of the array of pre-initialization +/// functions pointed to by the DT_PREINIT_ARRAY entry. If an object has a +/// DT_PREINIT_ARRAY entry, it must also have a DT_PREINIT_ARRAYSZ entry. As +/// with DT_PREINIT_ARRAY, this entry is ignored if it appears in a shared +/// object. +pub const DT_PREINIT_ARRAYSZ: i64 = 33; +/// This element holds the address of the SHT_SYMTAB_SHNDX section associated +/// with the dynamic symbol table referenced by the DT_SYMTAB element. +pub const DT_SYMTAB_SHNDX: i64 = 34; +/// Guile offset of GC roots +pub const DT_GUILE_GC_ROOT: i64 = 0x37146000; +/// Guile size in machine words of GC roots +pub const DT_GUILE_GC_ROOT_SZ: i64 = 0x37146001; +/// Guile address of entry thunk +pub const DT_GUILE_ENTRY: i64 = 0x37146002; +/// Guile bytecode version +pub const DT_GUILE_VM_VERSION: i64 = 0x37146003; +/// Guile frame maps +pub const DT_GUILE_FRAME_MAPS: i64 = 0x37146004; +/// Values in [DT_LOOS, DT_HIOS] are reserved for operating system-specific semantics. +pub const DT_LOOS: i64 = 0x6000000D; +/// Prelinking timestamp +pub const DT_GNU_PRELINKED: i64 = 0x6ffffdf5; +/// Size of conflict section +pub const DT_GNU_CONFLICTSZ: i64 = 0x6ffffdf6; +/// Size of library list +pub const DT_GNU_LIBLISTSZ: i64 = 0x6ffffdf7; +pub const DT_CHECKSUM: i64 = 0x6ffffdf8; +pub const DT_PLTPADSZ: i64 = 0x6ffffdf9; +pub const DT_MOVEENT: i64 = 0x6ffffdfa; +pub const DT_MOVESZ: i64 = 0x6ffffdfb; +/// Feature selection (DTF_*) +pub const DT_FEATURE_1: i64 = 0x6ffffdfc; +/// Flags for DT_* entries, effecting the following DT_* entry +pub const DT_POSFLAG_1: i64 = 0x6ffffdfd; +/// Size of syminfo table (in bytes) +pub const DT_SYMINSZ: i64 = 0x6ffffdfe; +/// Entry size of syminfo table +pub const DT_SYMINENT: i64 = 0x6ffffdff; +/// GNU-style hash table +pub const DT_GNU_HASH: i64 = 0x6ffffef5; +pub const DT_TLSDESC_PLT: i64 = 0x6ffffef6; +pub const DT_TLSDESC_GOT: i64 = 0x6ffffef7; +/// Start of conflict section +pub const DT_GNU_CONFLICT: i64 = 0x6ffffef8; +/// Library list +pub const DT_GNU_LIBLIST: i64 = 0x6ffffef9; +/// Configuration information +pub const DT_CONFIG: i64 = 0x6ffffefa; +/// Dependency auditing +pub const DT_DEPAUDIT: i64 = 0x6ffffefb; +/// Object auditing +pub const DT_AUDIT: i64 = 0x6ffffefc; +/// PLT padding +pub const DT_PLTPAD: i64 = 0x6ffffefd; +/// Move table +pub const DT_MOVETAB: i64 = 0x6ffffefe; +/// Syminfo table +pub const DT_SYMINFO: i64 = 0x6ffffeff; +pub const DT_VERSYM: i64 = 0x6ffffff0; +pub const DT_RELACOUNT: i64 = 0x6ffffff9; +pub const DT_RELCOUNT: i64 = 0x6ffffffa; +/// State flags, see DF_1_* below. +pub const DT_FLAGS_1: i64 = 0x6ffffffb; +/// Address of version definition table +pub const DT_VERDEF: i64 = 0x6ffffffc; +/// Number of version definitions +pub const DT_VERDEFNUM: i64 = 0x6ffffffd; +/// Address of table with needed versions +pub const DT_VERNEED: i64 = 0x6ffffffe; +/// Number of needed versions +pub const DT_VERNEEDNUM: i64 = 0x6fffffff; +/// Values in [DT_LOOS, DT_HIOS] are reserved for operating system-specific semantics. +pub const DT_HIOS: i64 = 0x6ffff000; +/// Values in [DT_LOPROC, DT_HIPROC] are reserved for processor-specific semantics. +pub const DT_LOPROC: i64 = 0x70000000; +/// Values in [DT_LOPROC, DT_HIPROC] are reserved for processor-specific semantics. +pub const DT_HIPROC: i64 = 0x7fffffff; + +/// This flag signifies that the object being loaded may make reference to the +/// $ORIGIN substitution string. The dynamic linker must determine the pathname +/// of the object containing this entry when the object is loaded. +pub const DF_ORIGIN: i64 = 0x1; +/// If this flag is set in a shared object library, the dynamic linker's symbol +/// resolution algorithm for references within the library is changed. Instead +/// of starting a symbol search with the executable file, the dynamic linker +/// starts from the shared object itself. If the shared object fails to supply +/// the referenced symbol, the dynamic linker then searches the executable file +/// and other shared objects as usual. +pub const DF_SYMBOLIC: i64 = 0x2; +/// If this flag is not set, no relocation entry should cause a modification to +/// a non-writable segment, as specified by the segment permissions in the +/// program header table. If this flag is set, one or more relocation entries +/// might request modifications to a non-writable segment, and the dynamic +/// linker can prepare accordingly. +pub const DF_TEXTREL: i64 = 0x4; +/// If set in a shared object or executable, this flag instructs the dynamic +/// linker to process all relocations for the object containing this entry +/// before transferring control to the program. The presence of this entry takes +/// precedence over a directive to use lazy binding for this object when +/// specified through the environment or via dlopen(BA_LIB). +pub const DF_BIND_NOW: i64 = 0x8; +/// If set in a shared object or executable, this flag instructs the dynamic +/// linker to reject attempts to load this file dynamically. It indicates that +/// the shared object or executable contains code using a static thread-local +/// storage scheme. Implementations need not support any form of thread-local +/// storage. +pub const DF_STATIC_TLS: i64 = 0x10; + +// State flags selectable in Dyn.d_val() of the DT_FLAGS_1 entries in the dynamic section + +/// Set RTLD_NOW for this object +pub const DF_1_NOW: i64 = 0x00000001; +/// Set RTLD_GLOBAL for this object +pub const DF_1_GLOBAL: i64 = 0x00000002; +/// Set RTLD_GROUP for this object +pub const DF_1_GROUP: i64 = 0x00000004; +/// Set RTLD_NODELETE for this object +pub const DF_1_NODELETE: i64 = 0x00000008; +/// Trigger filtee loading at runtime +pub const DF_1_LOADFLTR: i64 = 0x00000010; +/// Set RTLD_INITFIRST for this object +pub const DF_1_INITFIRST: i64 = 0x00000020; +/// Set RTLD_NOOPEN for this object +pub const DF_1_NOOPEN: i64 = 0x00000040; +/// $ORIGIN must be handled +pub const DF_1_ORIGIN: i64 = 0x00000080; +/// Direct binding enabled +pub const DF_1_DIRECT: i64 = 0x00000100; +pub const DF_1_TRANS: i64 = 0x00000200; +/// Object is used to interpose +pub const DF_1_INTERPOSE: i64 = 0x00000400; +/// Ignore default lib search path +pub const DF_1_NODEFLIB: i64 = 0x00000800; +/// Object can't be dldump'ed +pub const DF_1_NODUMP: i64 = 0x00001000; +/// Configuration alternative created +pub const DF_1_CONFALT: i64 = 0x00002000; +/// Filtee terminates filters search +pub const DF_1_ENDFILTEE: i64 = 0x00004000; +/// Disp reloc applied at build time +pub const DF_1_DISPRELDNE: i64 = 0x00008000; +/// Disp reloc applied at run-time +pub const DF_1_DISPRELPND: i64 = 0x00010000; +/// Object has no-direct binding +pub const DF_1_NODIRECT: i64 = 0x00020000; +pub const DF_1_IGNMULDEF: i64 = 0x00040000; +pub const DF_1_NOKSYMS: i64 = 0x00080000; +pub const DF_1_NOHDR: i64 = 0x00100000; +/// Object is modified after built +pub const DF_1_EDITED: i64 = 0x00200000; +pub const DF_1_NORELOC: i64 = 0x00400000; +/// Object has individual interposers +pub const DF_1_SYMINTPOSE: i64 = 0x00800000; +/// Global auditing required +pub const DF_1_GLOBAUDIT: i64 = 0x01000000; +/// Singleton symbols are used +pub const DF_1_SINGLETON: i64 = 0x02000000; +pub const DF_1_STUB: i64 = 0x04000000; +pub const DF_1_PIE: i64 = 0x08000000; +pub const DF_1_KMOD: i64 = 0x10000000; +pub const DF_1_WEAKFILTER: i64 = 0x20000000; +pub const DF_1_NOCOMMON: i64 = 0x40000000; + +// Flags for the feature selection in DT_FEATURE_1 +pub const DTF_1_PARINIT: i64 = 0x00000001; +pub const DTF_1_CONFEXP: i64 = 0x00000002; + +// Flags in the DT_POSFLAG_1 entry effecting only the next DT_* entry +/// Lazyload following object +pub const DF_P1_LAZYLOAD: i64 = 0x00000001; +/// Symbols from next object are not generally available +pub const DF_P1_GROUPPERM: i64 = 0x00000002; + +// .gnu.version index reserved values +/// Symbol is local +pub const VER_NDX_LOCAL: u16 = 0; +/// Symbol is global +pub const VER_NDX_GLOBAL: u16 = 1; +/// .gnu.version index mask +pub const VER_NDX_VERSION: u16 = 0x7fff; +/// Symbol is hidden +pub const VER_NDX_HIDDEN: u16 = 0x8000; + +// .gnu.version_d VerDef.vd_version reserved values +/// Only defined valid vd_version value +pub const VER_DEF_CURRENT: u16 = 1; + +// .gnu.version_r VerNeed.vn_version reserved values +/// Only defined valid vn_version value +pub const VER_NEED_CURRENT: u16 = 1; + +// Bit flags which appear in vd_flags of VerDef and vna_flags of VerNeedAux. +pub const VER_FLG_BASE: u16 = 0x1; +pub const VER_FLG_WEAK: u16 = 0x2; +pub const VER_FLG_INFO: u16 = 0x4; + +/// ZLIB/DEFLATE +pub const ELFCOMPRESS_ZLIB: u32 = 1; +/// zstd algorithm +pub const ELFCOMPRESS_ZSTD: u32 = 2; +/// Values in [ELFCOMPRESS_LOOS, ELFCOMPRESS_HIOS] are reserved for operating system-specific semantics. +pub const ELFCOMPRESS_LOOS: u32 = 0x60000000; +/// Values in [ELFCOMPRESS_LOOS, ELFCOMPRESS_HIOS] are reserved for operating system-specific semantics. +pub const ELFCOMPRESS_HIOS: u32 = 0x6fffffff; +/// Values in [ELFCOMPRESS_LOPROC, ELFCOMPRESS_HIPROC] are reserved for processor-specific semantics. +pub const ELFCOMPRESS_LOPROC: u32 = 0x70000000; +/// Values in [ELFCOMPRESS_LOPROC, ELFCOMPRESS_HIPROC] are reserved for processor-specific semantics. +pub const ELFCOMPRESS_HIPROC: u32 = 0x7fffffff; + +/// GNU-extension notes have this name +pub const ELF_NOTE_GNU: &[u8] = b"GNU\0"; + +// Note header descriptor types constants (n_type) + +/// Contains copy of prstatus struct +pub const NT_PRSTATUS: u64 = 1; +/// Contains copy of fpregset struct +pub const NT_PRFPREG: u64 = 2; +/// Contains copy of fpregset struct +pub const NT_FPREGSET: u64 = 2; +/// Contains copy of prpsinfo struct +pub const NT_PRPSINFO: u64 = 3; +/// Contains copy of prxregset struct +pub const NT_PRXREG: u64 = 4; +/// Contains copy of task structure +pub const NT_TASKSTRUCT: u64 = 4; +/// String from sysinfo(SI_PLATFORM) +pub const NT_PLATFORM: u64 = 5; +/// Contains copy of auxv array +pub const NT_AUXV: u64 = 6; +/// Contains copy of gwindows struct +pub const NT_GWINDOWS: u64 = 7; +/// Contains copy of asrset struct +pub const NT_ASRS: u64 = 8; +/// Contains copy of pstatus struct +pub const NT_PSTATUS: u64 = 10; +/// Contains copy of psinfo struct +pub const NT_PSINFO: u64 = 13; +/// Contains copy of prcred struct +pub const NT_PRCRED: u64 = 14; +/// Contains copy of utsname struct +pub const NT_UTSNAME: u64 = 15; +/// Contains copy of lwpstatus struct +pub const NT_LWPSTATUS: u64 = 16; +/// Contains copy of lwpinfo struct +pub const NT_LWPSINFO: u64 = 17; +/// Contains copy of fprxregset struct +pub const NT_PRFPXREG: u64 = 20; +/// Contains copy of siginfo_t, size might increase +pub const NT_SIGINFO: u64 = 0x53494749; +/// Contains information about mapped files +pub const NT_FILE: u64 = 0x46494c45; +/// Contains copy of user_fxsr_struct +pub const NT_PRXFPREG: u64 = 0x46e62b7f; +/// PowerPC Altivec/VMX registers +pub const NT_PPC_VMX: u64 = 0x100; +/// PowerPC SPE/EVR registers +pub const NT_PPC_SPE: u64 = 0x101; +/// PowerPC VSX registers +pub const NT_PPC_VSX: u64 = 0x102; +/// Target Address Register +pub const NT_PPC_TAR: u64 = 0x103; +/// Program Priority Register +pub const NT_PPC_PPR: u64 = 0x104; +/// Data Stream Control Register +pub const NT_PPC_DSCR: u64 = 0x105; +/// Event Based Branch Registers +pub const NT_PPC_EBB: u64 = 0x106; +/// Performance Monitor Registers +pub const NT_PPC_PMU: u64 = 0x107; +/// TM checkpointed GPR Registers +pub const NT_PPC_TM_CGPR: u64 = 0x108; +/// TM checkpointed FPR Registers +pub const NT_PPC_TM_CFPR: u64 = 0x109; +/// TM checkpointed VMX Registers +pub const NT_PPC_TM_CVMX: u64 = 0x10a; +/// TM checkpointed VSX Registers +pub const NT_PPC_TM_CVSX: u64 = 0x10b; +/// TM Special Purpose Registers +pub const NT_PPC_TM_SPR: u64 = 0x10c; +/// TM checkpointed Target Address Register +pub const NT_PPC_TM_CTAR: u64 = 0x10d; +/// TM checkpointed Program Priority Register +pub const NT_PPC_TM_CPPR: u64 = 0x10e; +/// TM checkpointed Data Stream Control Register +pub const NT_PPC_TM_CDSCR: u64 = 0x10f; +/// Memory Protection Keys registers +pub const NT_PPC_PKEY: u64 = 0x110; +/// i386 TLS slots (struct user_desc) +pub const NT_386_TLS: u64 = 0x200; +/// x86 io permission bitmap (1=deny) +pub const NT_386_IOPERM: u64 = 0x201; +/// x86 extended state using xsave +pub const NT_X86_XSTATE: u64 = 0x202; +/// ARM VFP/NEON registers +pub const NT_ARM_VFP: u64 = 0x400; +/// ARM TLS register +pub const NT_ARM_TLS: u64 = 0x401; +/// ARM hardware breakpoint registers +pub const NT_ARM_HW_BREAK: u64 = 0x402; +/// ARM hardware watchpoint registers +pub const NT_ARM_HW_WATCH: u64 = 0x403; +/// ARM system call number +pub const NT_ARM_SYSTEM_CALL: u64 = 0x404; +/// ARM Scalable Vector Extension registers +pub const NT_ARM_SVE: u64 = 0x405; +/// ARM pointer authentication code masks +pub const NT_ARM_PAC_MASK: u64 = 0x406; +/// ARM pointer authentication address keys +pub const NT_ARM_PACA_KEYS: u64 = 0x407; +/// ARM pointer authentication generic key +pub const NT_ARM_PACG_KEYS: u64 = 0x408; +/// AArch64 tagged address control. +pub const NT_ARM_TAGGED_ADDR_CTRL: u64 = 0x409; +/// AArch64 pointer authentication enabled keys +pub const NT_ARM_PAC_ENABLED_KEYS: u64 = 0x40a; +/// Vmcore Device Dump Note +pub const NT_VMCOREDD: u64 = 0x700; + +/// ABI information +/// The descriptor consists of words: +/// word 0: OS descriptor +/// word 1: major version of the ABI +/// word 2: minor version of the ABI +/// word 3: subminor version of the ABI +pub const NT_GNU_ABI_TAG: u64 = 1; +/// Synthetic hwcap information +pub const NT_GNU_HWCAP: u64 = 2; +/// Build ID bits as generated by ld --build-id. +pub const NT_GNU_BUILD_ID: u64 = 3; +/// Version note generated by GNU gold containing a version string +pub const NT_GNU_GOLD_VERSION: u64 = 4; +/// Program property note which describes special handling requirements for linker and run-time loader. +pub const NT_GNU_PROPERTY_TYPE_0: u64 = 5; + +// These values can appear in word 0 of an NT_GNU_ABI_TAG note section entry. +pub const ELF_NOTE_GNU_ABI_TAG_OS_LINUX: u32 = 0; +pub const ELF_NOTE_GNU_ABI_TAG_OS_GNU: u32 = 1; +pub const ELF_NOTE_GNU_ABI_TAG_OS_SOLARIS2: u32 = 2; +pub const ELF_NOTE_GNU_ABI_TAG_OS_FREEBSD: u32 = 3; + +// _ ____ __ __ +// / \ | _ \| \/ | +// / _ \ | |_) | |\/| | +// / ___ \| _ <| | | | +// /_/ \_\_| \_\_| |_| +// +// ARM-specific declarations (ABI v5) +// See: https://github.com/ARM-software/abi-aa/blob/main/aaelf32/aaelf32.rst +// See: https://github.com/ARM-software/abi-aa/blob/main/aaelf64/aaelf64.rst + +/// Set in executable file headers (e_type = ET_EXEC or ET_DYN) to note explicitly that the +/// executable file was built to conform to the software floating-point procedure-call standard +/// (the base standard). If both EF_ARM_ABI_FLOAT_XXXX bits are clear, conformance to the base +/// procedure-call standard is implied. +pub const EF_ARM_ABI_FLOAT_SOFT: u32 = 0x200; +/// Compatible with legacy (pre version 5) gcc use as EF_ARM_SOFT_FLOAT +pub const EF_ARM_SOFT_FLOAT: u32 = EF_ARM_ABI_FLOAT_SOFT; +/// Set in executable file headers (e_type = ET_EXEC or ET_DYN) to note that the executable file +/// was built to conform to the hardware floating-point procedure-call standard. +pub const EF_ARM_ABI_FLOAT_HARD: u32 = 0x400; +/// Compatible with legacy (pre version 5) gcc use as EF_ARM_VFP_FLOAT. +pub const EF_ARM_VFP_FLOAT: u32 = EF_ARM_ABI_FLOAT_HARD; + +/// The ELF file contains BE-8 code, suitable for execution on an Arm Architecture v6 processor. +/// This flag must only be set on an executable file. +pub const EF_ARM_BE8: u32 = 0x00800000; + +/// Legacy code (ABI version 4 and earlier) generated by gcc-arm-xxx might use these bits. +pub const EF_ARM_GCCMASK: u32 = 0x00400FFF; + +/// This masks an 8-bit version number, the version of the ABI to which this ELF +/// file conforms. This ABI is version 5. A value of 0 denotes unknown conformance. +pub const EF_ARM_EABIMASK: u32 = 0xFF000000; +pub const EF_ARM_EABI_UNKNOWN: u32 = 0x00000000; +pub const EF_ARM_EABI_VER1: u32 = 0x01000000; +pub const EF_ARM_EABI_VER2: u32 = 0x02000000; +pub const EF_ARM_EABI_VER3: u32 = 0x03000000; +pub const EF_ARM_EABI_VER4: u32 = 0x04000000; +pub const EF_ARM_EABI_VER5: u32 = 0x05000000; + +/// Section contains index information for exception unwinding +pub const SHT_ARM_EXIDX: u32 = 0x70000001; +/// BPABI DLL dynamic linking pre-emption map +pub const SHT_ARM_PREEMPTMAP: u32 = 0x70000002; +/// Object file compatibility attributes +pub const SHT_ARM_ATTRIBUTES: u32 = 0x70000003; +/// See +pub const SHT_ARM_DEBUGOVERLAY: u32 = 0x70000004; +/// See +pub const SHT_ARM_OVERLAYSECTION: u32 = 0x70000005; + +/// The contents of this section contains only program instructions and no program data. +/// +/// If any section contained by a segment does not have the SHF_ARM_PURECODE +/// section flag set, the PF_R segment flag must be set in the program header +/// for the segment. If all sections contained by a segment have the +/// SHF_ARM_PURECODE section flag, a linker may optionally clear the PF_R +/// segment flag in the program header of the segment, to signal to the runtime +/// that the program does not rely on being able to read that segment. +pub const SHF_ARM_PURECODE: u64 = 0x20000000; + +/// Architecture compatibility information. +/// +/// A segment of type PT_ARM_ARCHEXT contains information describing the +/// platform capabilities required by the executable file. The segment is +/// optional, but if present it must appear before segment of type PT_LOAD. +pub const PT_ARM_ARCHEXT: u32 = 0x70000000; +/// alias for unwind +pub const PT_ARM_EXIDX: u32 = 0x70000001; +/// Exception unwind tables +pub const PT_ARM_UNWIND: u32 = 0x70000001; + +// Contents of a PT_ARM_ARCHEXT segment shall contain at least one 32-bit word with meanings: +/// Masks bits describing the format of data in subsequent words. +pub const PT_ARM_ARCHEXT_FMTMSK: u32 = 0xff000000; +/// There are no additional words of data. However, if EF_OSABI is non-zero, the +/// relevant platform ABI may define additional data that follows the initial word. +pub const PT_ARM_ARCHEXT_FMT_OS: u32 = 0x00000000; +/// See +/// and +pub const PT_ARM_ARCHEXT_FMT_ABI: u32 = 0x01000000; + +/// Masks bits describing the architecture profile required by the executable. +pub const PT_ARM_ARCHEXT_PROFMSK: u32 = 0x00ff0000; +/// The architecture has no profile variants, or the image has no profile-specific constraints +pub const PT_ARM_ARCHEXT_PROF_NONE: u32 = 0x00000000; +/// (‘A’<<16) The executable file requires the Application profile +pub const PT_ARM_ARCHEXT_PROF_ARM: u32 = 0x00410000; +/// (‘R’<<16) The executable file requires the Real-Time profile +pub const PT_ARM_ARCHEXT_PROF_RT: u32 = 0x00520000; +/// (‘M’<<16) The executable file requires the Microcontroller profile +pub const PT_ARM_ARCHEXT_PROF_MC: u32 = 0x004D0000; +/// (‘S’<<16) The executable file requires the ‘classic’ (‘A’ or ‘R’ profile) exception model. +pub const PT_ARM_ARCHEXT_PROF_CLASSIC: u32 = 0x00530000; + +/// Masks bits describing the base architecture required by the executable. +pub const PT_ARM_ARCHEXT_ARCHMSK: u32 = 0x000000ff; +/// The needed architecture is unknown or specified in some other way +pub const PT_ARM_ARCHEXT_ARCH_UNKN: u32 = 0x00; +/// Architecture v4 +pub const PT_ARM_ARCHEXT_ARCHV4: u32 = 0x01; +/// Architecture v4T +pub const PT_ARM_ARCHEXT_ARCHV4T: u32 = 0x02; +/// Architecture v5T +pub const PT_ARM_ARCHEXT_ARCHV5T: u32 = 0x03; +/// Architecture v5TE +pub const PT_ARM_ARCHEXT_ARCHV5TE: u32 = 0x04; +/// Architecture v5TEJ +pub const PT_ARM_ARCHEXT_ARCHV5TEJ: u32 = 0x05; +/// Architecture v6 +pub const PT_ARM_ARCHEXT_ARCHV6: u32 = 0x06; +/// Architecture v6KZ +pub const PT_ARM_ARCHEXT_ARCHV6KZ: u32 = 0x07; +/// Architecture v6T2 +pub const PT_ARM_ARCHEXT_ARCHV6T2: u32 = 0x08; +/// Architecture v6K +pub const PT_ARM_ARCHEXT_ARCHV6K: u32 = 0x09; +/// Architecture v7 (in this case the architecture profile may also be +/// required to fully specify the needed execution environment). +pub const PT_ARM_ARCHEXT_ARCHV7: u32 = 0x0A; +/// Architecture v6M (e.g. Cortex-M0) +pub const PT_ARM_ARCHEXT_ARCHV6M: u32 = 0x0B; +/// Architecture v6S-M (e.g. Cortex-M0) +pub const PT_ARM_ARCHEXT_ARCHV6SM: u32 = 0x0C; +/// Architecture v7E-M +pub const PT_ARM_ARCHEXT_ARCHV7EM: u32 = 0x0D; + +/// gives the number of entries in the dynamic symbol table, including the initial dummy symbol +pub const DT_ARM_SYMTABSZ: i64 = 0x70000001; +/// holds the address of the pre-emption map for platforms that use the DLL static binding mode +pub const DT_ARM_PREEMPTMAP: i64 = 0x70000002; + +// ARM relocs +// +// * S (when used on its own) is the address of the symbol. +// * A is the addend for the relocation. +// * P is the address of the place being relocated (derived from r_offset). +// * Pa is the adjusted address of the place being relocated, defined as (P & 0xFFFFFFFC). +// * T is 1 if the target symbol S has type STT_FUNC and the symbol addresses a Thumb instruction; 0 otherwise. +// * B(S) is the addressing origin of the output segment defining the symbol S. +// The origin is not required to be the base address of the segment. This value must always be word-aligned. +// * GOT_ORG is the addressing origin of the Global Offset Table (the indirection table for imported data addresses). +// This value must always be word-aligned. +// * GOT(S) is the address of the GOT entry for the symbol S + +/// no reloc +pub const R_ARM_NONE: u32 = 0; +/// Deprecated PC relative 26 bit branch. `((S + A) | T) – P` +pub const R_ARM_PC24: u32 = 1; +/// Direct 32 bit. `(S + A) | T` +pub const R_ARM_ABS32: u32 = 2; +/// PC relative 32 bit. `((S + A) | T) | – P` +pub const R_ARM_REL32: u32 = 3; +/// `S + A – P` +pub const R_ARM_LDR_PC_G0: u32 = 4; +/// Direct 16 bit. `S + A` +pub const R_ARM_ABS16: u32 = 5; +/// Direct 12 bit. `S + A` +pub const R_ARM_ABS12: u32 = 6; +/// Direct & 0x7C `(LDR, STR). S + A` +pub const R_ARM_THM_ABS5: u32 = 7; +/// Direct 8 bit. `S + A` +pub const R_ARM_ABS8: u32 = 8; +/// `((S + A) | T) – B(S)` +pub const R_ARM_SBREL32: u32 = 9; +/// PC relative 24 bit (Thumb32 BL). `((S + A) | T) – P` +pub const R_ARM_THM_CALL: u32 = 10; +/// PC relative & 0x3FC (Thumb16 LDR, ADD, ADR). `S + A – Pa` +pub const R_ARM_THM_PC8: u32 = 11; +pub const R_ARM_BREL_ADJ: u32 = 12; +/// dynamic reloc +pub const R_ARM_TLS_DESC: u32 = 13; +/// obsolete/reserved +pub const R_ARM_THM_SWI8: u32 = 14; +/// obsolete/reserved +pub const R_ARM_XPC25: u32 = 15; +/// obsolete/reserved +pub const R_ARM_THM_XPC22: u32 = 16; +/// ID of module containing symbol. +pub const R_ARM_TLS_DTPMOD32: u32 = 17; +/// Offset in TLS block. `S + A – TLS` +pub const R_ARM_TLS_DTPOFF32: u32 = 18; +/// Offset in static TLS block. `S + A – Tp` +pub const R_ARM_TLS_TPOFF32: u32 = 19; +/// dynamic reloc Copy symbol at runtime. +pub const R_ARM_COPY: u32 = 20; +/// Create GOT entry. `(S + A) | T` +pub const R_ARM_GLOB_DAT: u32 = 21; +/// Create PLT entry. `(S + A) | T` +pub const R_ARM_JUMP_SLOT: u32 = 22; +/// Adjust by program base. `B(S) + A` +pub const R_ARM_RELATIVE: u32 = 23; +/// 32 bit offset to GOT. `((S + A) | T) – GOT_ORG` +pub const R_ARM_GOTOFF32: u32 = 24; +/// 32 bit PC relative offset to GOT. `B(S) + A – P` +pub const R_ARM_BASE_PREL: u32 = 25; +/// 32 bit GOT entry. `GOT(S) + A – GOT_ORG` +pub const R_ARM_BASE_BREL: u32 = 26; +/// Deprecated, 32 bit PLT address. +pub const R_ARM_PLT32: u32 = 27; +/// PC relative 24 bit (BL, BLX). `((S + A) | T) – P` +pub const R_ARM_CALL: u32 = 28; +/// PC relative 24 bit (B, BL{cond}). `((S + A) | T) – P` +pub const R_ARM_JUMP24: u32 = 29; +/// PC relative 24 bit (Thumb32 B.W). `((S + A) | T) – P` +pub const R_ARM_THM_JUMP24: u32 = 30; +/// Adjust by program base. `B(S) + A` +pub const R_ARM_BASE_ABS: u32 = 31; +/// Obsolete. +pub const R_ARM_ALU_PCREL_7_0: u32 = 32; +/// Obsolete. +pub const R_ARM_ALU_PCREL_15_8: u32 = 33; +/// Obsolete. +pub const R_ARM_ALU_PCREL_23_15: u32 = 34; +/// Deprecated, prog. base relative. +pub const R_ARM_LDR_SBREL_11_0: u32 = 35; +/// Deprecated, prog. base relative. +pub const R_ARM_ALU_SBREL_19_12: u32 = 36; +/// Deprecated, prog. base relative. +pub const R_ARM_ALU_SBREL_27_20: u32 = 37; +pub const R_ARM_TARGET1: u32 = 38; +/// Program base relative. `((S + A) | T) – B(S)` +pub const R_ARM_SBREL31: u32 = 39; +pub const R_ARM_V4BX: u32 = 40; +pub const R_ARM_TARGET2: u32 = 41; +/// 32 bit PC relative. `((S + A) | T) – P` +pub const R_ARM_PREL31: u32 = 42; +/// Direct 16-bit (MOVW). `(S + A) | T` +pub const R_ARM_MOVW_ABS_NC: u32 = 43; +/// Direct high 16-bit (MOVT). `S + A` +pub const R_ARM_MOVT_ABS: u32 = 44; +/// PC relative 16-bit (MOVW). `((S + A) | T) – P` +pub const R_ARM_MOVW_PREL_NC: u32 = 45; +/// PC relative (MOVT). `S + A - P` +pub const R_ARM_MOVT_PREL: u32 = 46; +/// Direct 16 bit (Thumb32 MOVW). `(S + A) | T` +pub const R_ARM_THM_MOVW_ABS_NC: u32 = 47; +/// Direct high 16 bit (Thumb32 MOVT). `S + A` +pub const R_ARM_THM_MOVT_ABS: u32 = 48; +/// PC relative 16 bit (Thumb32 MOVW). `((S + A) | T) – P` +pub const R_ARM_THM_MOVW_PREL_NC: u32 = 49; +/// PC relative high 16 bit (Thumb32 MOVT). `S + A – P` +pub const R_ARM_THM_MOVT_PREL: u32 = 50; +/// PC relative 20 bit (Thumb32 B{cond}.W). `((S + A) | T) – P` +pub const R_ARM_THM_JUMP19: u32 = 51; +/// PC relative X & 0x7E (Thumb16 CBZ, CBNZ). `S + A – P` +pub const R_ARM_THM_JUMP6: u32 = 52; +/// PC relative 12 bit (Thumb32 ADR.W). `((S + A) | T) – Pa` +pub const R_ARM_THM_ALU_PREL_11_0: u32 = 53; +/// PC relative 12 bit (Thumb32 LDR{D,SB,H,SH}). `S + A – Pa` +pub const R_ARM_THM_PC12: u32 = 54; +/// Direct 32-bit. `S + A` +pub const R_ARM_ABS32_NOI: u32 = 55; +/// PC relative 32-bit. `S + A - P` +pub const R_ARM_REL32_NOI: u32 = 56; +/// PC relative (ADD, SUB). `((S + A) | T) – P` +pub const R_ARM_ALU_PC_G0_NC: u32 = 57; +/// PC relative (ADD, SUB). `((S + A) | T) – P` +pub const R_ARM_ALU_PC_G0: u32 = 58; +/// PC relative (ADD, SUB). `((S + A) | T) – P` +pub const R_ARM_ALU_PC_G1_NC: u32 = 59; +/// PC relative (ADD, SUB). `((S + A) | T) – P` +pub const R_ARM_ALU_PC_G1: u32 = 60; +/// PC relative (ADD, SUB). `((S + A) | T) – P` +pub const R_ARM_ALU_PC_G2: u32 = 61; +/// PC relative (LDR,STR,LDRB,STRB). `S + A – P` +pub const R_ARM_LDR_PC_G1: u32 = 62; +/// PC relative (LDR,STR,LDRB,STRB). `S + A – P` +pub const R_ARM_LDR_PC_G2: u32 = 63; +/// PC relative (STR{D,H}, LDR{D,SB,H,SH}). `S + A – P` +pub const R_ARM_LDRS_PC_G0: u32 = 64; +/// PC relative (STR{D,H}, LDR{D,SB,H,SH}). `S + A – P` +pub const R_ARM_LDRS_PC_G1: u32 = 65; +/// PC relative (STR{D,H}, LDR{D,SB,H,SH}). `S + A – P` +pub const R_ARM_LDRS_PC_G2: u32 = 66; +/// PC relative (LDC, STC). `S + A – P` +pub const R_ARM_LDC_PC_G0: u32 = 67; +/// PC relative (LDC, STC). `S + A – P` +pub const R_ARM_LDC_PC_G1: u32 = 68; +/// PC relative (LDC, STC). `S + A – P` +pub const R_ARM_LDC_PC_G2: u32 = 69; +/// Program base relative (ADD,SUB). `((S + A) | T) – B(S)` +pub const R_ARM_ALU_SB_G0_NC: u32 = 70; +/// Program base relative (ADD,SUB). `((S + A) | T) – B(S)` +pub const R_ARM_ALU_SB_G0: u32 = 71; +/// Program base relative (ADD,SUB). `((S + A) | T) – B(S)` +pub const R_ARM_ALU_SB_G1_NC: u32 = 72; +/// Program base relative (ADD,SUB). `((S + A) | T) – B(S)` +pub const R_ARM_ALU_SB_G1: u32 = 73; +/// Program base relative (ADD,SUB). `((S + A) | T) – B(S)` +pub const R_ARM_ALU_SB_G2: u32 = 74; +/// Program base relative (LDR, STR, LDRB, STRB). `S + A – B(S)` +pub const R_ARM_LDR_SB_G0: u32 = 75; +/// Program base relative (LDR, STR, LDRB, STRB). `S + A – B(S)` +pub const R_ARM_LDR_SB_G1: u32 = 76; +/// Program base relative (LDR, STR, LDRB, STRB). `S + A – B(S)` +pub const R_ARM_LDR_SB_G2: u32 = 77; +/// Program base relative (LDR, STR, LDRB, STRB). `S + A – B(S)` +pub const R_ARM_LDRS_SB_G0: u32 = 78; +/// Program base relative (LDR, STR, LDRB, STRB). `S + A – B(S)` +pub const R_ARM_LDRS_SB_G1: u32 = 79; +/// Program base relative (LDR, STR, LDRB, STRB). `S + A – B(S)` +pub const R_ARM_LDRS_SB_G2: u32 = 80; +/// Program base relative (LDC,STC). `S + A – B(S)` +pub const R_ARM_LDC_SB_G0: u32 = 81; +/// Program base relative (LDC,STC). `S + A – B(S)` +pub const R_ARM_LDC_SB_G1: u32 = 82; +/// Program base relative (LDC,STC). `S + A – B(S)` +pub const R_ARM_LDC_SB_G2: u32 = 83; +/// Program base relative 16 bit (MOVW). `((S + A) | T) – B(S)` +pub const R_ARM_MOVW_BREL_NC: u32 = 84; +/// Program base relative high 16 bit (MOVT). `S + A – B(S)` +pub const R_ARM_MOVT_BREL: u32 = 85; +/// Program base relative 16 bit (MOVW). `((S + A) | T) – B(S)` +pub const R_ARM_MOVW_BREL: u32 = 86; +/// Program base relative 16 bit (Thumb32 MOVW). `((S + A) | T) – B(S)` +pub const R_ARM_THM_MOVW_BREL_NC: u32 = 87; +/// Program base relative high 16 bit (Thumb32 MOVT). `S + A – B(S)` +pub const R_ARM_THM_MOVT_BREL: u32 = 88; +/// Program base relative 16 bit (Thumb32 MOVW). `((S + A) | T) – B(S)` +pub const R_ARM_THM_MOVW_BREL: u32 = 89; +pub const R_ARM_TLS_GOTDESC: u32 = 90; +pub const R_ARM_TLS_CALL: u32 = 91; +/// TLS relaxation. +pub const R_ARM_TLS_DESCSEQ: u32 = 92; +pub const R_ARM_THM_TLS_CALL: u32 = 93; +/// `PLT(S) + A` +pub const R_ARM_PLT32_ABS: u32 = 94; +/// GOT entry. `GOT(S) + A` +pub const R_ARM_GOT_ABS: u32 = 95; +/// PC relative GOT entry. `GOT(S) + A – P` +pub const R_ARM_GOT_PREL: u32 = 96; +/// GOT entry relative to GOT origin (LDR). `GOT(S) + A – GOT_ORG` +pub const R_ARM_GOT_BREL12: u32 = 97; +/// 12 bit, GOT entry relative to GOT origin (LDR, STR). `S + A – GOT_ORG` +pub const R_ARM_GOTOFF12: u32 = 98; +pub const R_ARM_GOTRELAX: u32 = 99; +pub const R_ARM_GNU_VTENTRY: u32 = 100; +pub const R_ARM_GNU_VTINHERIT: u32 = 101; +/// PC relative & 0xFFE (Thumb16 B). `S + A – P` +pub const R_ARM_THM_JUMP11: u32 = 102; +/// PC relative & 0x1FE (Thumb16 B/B{cond}). `S + A – P` +pub const R_ARM_THM_JUMP8: u32 = 103; +/// PC-rel 32 bit for global dynamic thread local data. `GOT(S) + A – P` +pub const R_ARM_TLS_GD32: u32 = 104; +/// PC-rel 32 bit for local dynamic thread local data. `GOT(S) + A – P` +pub const R_ARM_TLS_LDM32: u32 = 105; +/// 32 bit offset relative to TLS block. `S + A – TLS` +pub const R_ARM_TLS_LDO32: u32 = 106; +/// PC-rel 32 bit for GOT entry of static TLS block offset. `GOT(S) + A – P` +pub const R_ARM_TLS_IE32: u32 = 107; +/// 32 bit offset relative to static TLS block. `S + A – tp` +pub const R_ARM_TLS_LE32: u32 = 108; +/// 12 bit relative to TLS block (LDR, STR). `S + A – TLS` +pub const R_ARM_TLS_LDO12: u32 = 109; +/// 12 bit relative to static TLS block (LDR, STR). `S + A – tp` +pub const R_ARM_TLS_LE12: u32 = 110; +/// 12 bit GOT entry relative to GOT origin (LDR). `GOT(S) + A – GOT_ORG` +pub const R_ARM_TLS_IE12GP: u32 = 111; +/// Obsolete. +pub const R_ARM_ME_TOO: u32 = 128; +pub const R_ARM_THM_TLS_DESCSEQ16: u32 = 129; +pub const R_ARM_THM_TLS_DESCSEQ32: u32 = 130; +/// GOT entry relative to GOT origin, 12 bit (Thumb32 LDR). `GOT(S) + A – GOT_ORG` +pub const R_ARM_THM_GOT_BREL12: u32 = 131; +/// Static Thumb16 `(S + A) | T` +pub const R_ARM_THM_ALU_ABS_G0_NC: u32 = 132; +/// Static Thumb16 `S + A` +pub const R_ARM_THM_ALU_ABS_G1_NC: u32 = 133; +/// Static Thumb16 `S + A` +pub const R_ARM_THM_ALU_ABS_G2_NC: u32 = 134; +/// Static Thumb16 `S + A` +pub const R_ARM_THM_ALU_ABS_G3: u32 = 135; +/// Static Arm `((S + A) | T) – P` +pub const R_ARM_THM_BF16: u32 = 136; +/// Static Arm `((S + A) | T) – P` +pub const R_ARM_THM_BF12: u32 = 137; +/// Static Arm `((S + A) | T) – P` +pub const R_ARM_THM_BF18: u32 = 138; +pub const R_ARM_IRELATIVE: u32 = 160; + +/// Object file compatibility attributes +pub const SHT_AARCH64_ATTRIBUTES: u32 = 0x70000003; +pub const SHT_AARCH64_ATTRIBUTES_SECTION_NAME: &str = ".ARM.attributes"; + +/// Architecture compatibility information. +/// +/// A segment of type PT_AARCH64_ARCHEXT (if present) contains information +/// describing the architecture capabilities required by the executable file. +/// Not all platform ABIs require this segment; the Linux ABI does not. If the +/// segment is present it must appear before segment of type PT_LOAD. +pub const PT_AARCH64_ARCHEXT: u32 = 0x70000000; +/// (if present) describes the location of a program’s exception unwind tables. +pub const PT_AARCH64_UNWIND: u32 = 0x70000001; +/// Reserved for MTE memory tag data dumps in core files +/// (if present) hold MTE memory tags for a particular memory range. +/// At present they are defined for core dump files of type ET_CORE +/// See +pub const PT_AARCH64_MEMTAG_MTE: u32 = 0x70000002; + +/// The function associated with the symbol may follow a variant procedure call +/// standard with different register usage convention. +/// Found in Symbol's st_other field +pub const STO_AARCH64_VARIANT_PCS: u8 = 0x80; + +pub const GNU_PROPERTY_AARCH64_FEATURE_1_AND: u32 = 0xc0000000; +pub const GNU_PROPERTY_AARCH64_FEATURE_1_BTI: u32 = 0x1; +pub const GNU_PROPERTY_AARCH64_FEATURE_1_PAC: u32 = 0x2; + +// AArch64 specific values for the Dyn d_tag field. +/// indicates PLTs enabled with Branch Target Identification mechanism +pub const DT_AARCH64_BTI_PLT: i64 = 0x70000001; +/// indicates PLTs enabled with Pointer Authentication. +pub const DT_AARCH64_PAC_PLT: i64 = 0x70000003; +/// must be present if there are R_{CLS}_JUMP_SLOT relocations that reference +/// symbols marked with the STO_AARCH64_VARIANT_PCS flag set in their st_other field +pub const DT_AARCH64_VARIANT_PCS: i64 = 0x70000005; + +/// No relocation. +pub const R_AARCH64_NONE: u32 = 0; +/// Direct 32 bit. +pub const R_AARCH64_P32_ABS32: u32 = 1; +/// Copy symbol at runtime. +pub const R_AARCH64_P32_COPY: u32 = 180; +/// Create GOT entry. +pub const R_AARCH64_P32_GLOB_DAT: u32 = 181; +/// Create PLT entry. +pub const R_AARCH64_P32_JUMP_SLOT: u32 = 182; +/// Adjust by program base. +pub const R_AARCH64_P32_RELATIVE: u32 = 183; +/// Module number, 32 bit. +pub const R_AARCH64_P32_TLS_DTPMOD: u32 = 184; +/// Module-relative offset, 32 bit. +pub const R_AARCH64_P32_TLS_DTPREL: u32 = 185; +/// TP-relative offset, 32 bit. +pub const R_AARCH64_P32_TLS_TPREL: u32 = 186; +/// TLS Descriptor. +pub const R_AARCH64_P32_TLSDESC: u32 = 187; +/// STT_GNU_IFUNC relocation +pub const R_AARCH64_P32_IRELATIVE: u32 = 188; +/// Direct 64 bit. +pub const R_AARCH64_ABS64: u32 = 257; +/// Direct 32 bit. +pub const R_AARCH64_ABS32: u32 = 258; +/// Direct 16-bit. +pub const R_AARCH64_ABS16: u32 = 259; +/// PC-relative 64-bit. +pub const R_AARCH64_PREL64: u32 = 260; +/// PC-relative 32-bit. +pub const R_AARCH64_PREL32: u32 = 261; +/// PC-relative 16-bit. +pub const R_AARCH64_PREL16: u32 = 262; +/// Dir. MOVZ imm. from bits 15:0. +pub const R_AARCH64_MOVW_UABS_G0: u32 = 263; +/// Likewise for MOVK; no check. +pub const R_AARCH64_MOVW_UABS_G0_NC: u32 = 264; +/// Dir. MOVZ imm. from bits 31:16. +pub const R_AARCH64_MOVW_UABS_G1: u32 = 265; +/// Likewise for MOVK; no check. +pub const R_AARCH64_MOVW_UABS_G1_NC: u32 = 266; +/// Dir. MOVZ imm. from bits 47:32. +pub const R_AARCH64_MOVW_UABS_G2: u32 = 267; +/// Likewise for MOVK; no check. +pub const R_AARCH64_MOVW_UABS_G2_NC: u32 = 268; +/// Dir. MOV{K,Z} imm. from 63:48. +pub const R_AARCH64_MOVW_UABS_G3: u32 = 269; +/// Dir. MOV{N,Z} imm. from 15:0. +pub const R_AARCH64_MOVW_SABS_G0: u32 = 270; +/// Dir. MOV{N,Z} imm. from 31:16. +pub const R_AARCH64_MOVW_SABS_G1: u32 = 271; +/// Dir. MOV{N,Z} imm. from 47:32. +pub const R_AARCH64_MOVW_SABS_G2: u32 = 272; +/// PC-rel. LD imm. from bits 20:2. +pub const R_AARCH64_LD_PREL_LO19: u32 = 273; +/// PC-rel. ADR imm. from bits 20:0. +pub const R_AARCH64_ADR_PREL_LO21: u32 = 274; +/// Page-rel. ADRP imm. from 32:12. +pub const R_AARCH64_ADR_PREL_PG_HI21: u32 = 275; +/// Likewise; no overflow check. +pub const R_AARCH64_ADR_PREL_PG_HI21_NC: u32 = 276; +/// Dir. ADD imm. from bits 11:0. +pub const R_AARCH64_ADD_ABS_LO12_NC: u32 = 277; +/// Likewise for LD/ST; no check. +pub const R_AARCH64_LDST8_ABS_LO12_NC: u32 = 278; +/// PC-rel. TBZ/TBNZ imm. from 15:2. +pub const R_AARCH64_TSTBR14: u32 = 279; +/// PC-rel. cond. br. imm. from 20:2. +pub const R_AARCH64_CONDBR19: u32 = 280; +/// PC-rel. B imm. from bits 27:2. +pub const R_AARCH64_JUMP26: u32 = 282; +/// Likewise for CALL. +pub const R_AARCH64_CALL26: u32 = 283; +/// Dir. ADD imm. from bits 11:1. +pub const R_AARCH64_LDST16_ABS_LO12_NC: u32 = 284; +/// Likewise for bits 11:2. +pub const R_AARCH64_LDST32_ABS_LO12_NC: u32 = 285; +/// Likewise for bits 11:3. +pub const R_AARCH64_LDST64_ABS_LO12_NC: u32 = 286; +/// PC-rel. MOV{N,Z} imm. from 15:0. +pub const R_AARCH64_MOVW_PREL_G0: u32 = 287; +/// Likewise for MOVK; no check. +pub const R_AARCH64_MOVW_PREL_G0_NC: u32 = 288; +/// PC-rel. MOV{N,Z} imm. from 31:16. +pub const R_AARCH64_MOVW_PREL_G1: u32 = 289; +/// Likewise for MOVK; no check. +pub const R_AARCH64_MOVW_PREL_G1_NC: u32 = 290; +/// PC-rel. MOV{N,Z} imm. from 47:32. +pub const R_AARCH64_MOVW_PREL_G2: u32 = 291; +/// Likewise for MOVK; no check. +pub const R_AARCH64_MOVW_PREL_G2_NC: u32 = 292; +/// PC-rel. MOV{N,Z} imm. from 63:48. +pub const R_AARCH64_MOVW_PREL_G3: u32 = 293; +/// Dir. ADD imm. from bits 11:4. +pub const R_AARCH64_LDST128_ABS_LO12_NC: u32 = 299; +/// GOT-rel. off. MOV{N,Z} imm. 15:0. +pub const R_AARCH64_MOVW_GOTOFF_G0: u32 = 300; +/// Likewise for MOVK; no check. +pub const R_AARCH64_MOVW_GOTOFF_G0_NC: u32 = 301; +/// GOT-rel. o. MOV{N,Z} imm. 31:16. +pub const R_AARCH64_MOVW_GOTOFF_G1: u32 = 302; +/// Likewise for MOVK; no check. +pub const R_AARCH64_MOVW_GOTOFF_G1_NC: u32 = 303; +/// GOT-rel. o. MOV{N,Z} imm. 47:32. +pub const R_AARCH64_MOVW_GOTOFF_G2: u32 = 304; +/// Likewise for MOVK; no check. +pub const R_AARCH64_MOVW_GOTOFF_G2_NC: u32 = 305; +/// GOT-rel. o. MOV{N,Z} imm. 63:48. +pub const R_AARCH64_MOVW_GOTOFF_G3: u32 = 306; +/// GOT-relative 64-bit. +pub const R_AARCH64_GOTREL64: u32 = 307; +/// GOT-relative 32-bit. +pub const R_AARCH64_GOTREL32: u32 = 308; +/// PC-rel. GOT off. load imm. 20:2. +pub const R_AARCH64_GOT_LD_PREL19: u32 = 309; +/// GOT-rel. off. LD/ST imm. 14:3. +pub const R_AARCH64_LD64_GOTOFF_LO15: u32 = 310; +/// P-page-rel. GOT off. ADRP: u32 = 32:12. +pub const R_AARCH64_ADR_GOT_PAGE: u32 = 311; +/// Dir. GOT off. LD/ST imm. 11:3. +pub const R_AARCH64_LD64_GOT_LO12_NC: u32 = 312; +/// GOT-page-rel. GOT off. LD/ST: u32 = 14:3 +pub const R_AARCH64_LD64_GOTPAGE_LO15: u32 = 313; +/// PC-relative ADR imm. 20:0. +pub const R_AARCH64_TLSGD_ADR_PREL21: u32 = 512; +/// page-rel. ADRP imm. 32:12. +pub const R_AARCH64_TLSGD_ADR_PAGE21: u32 = 513; +/// direct ADD imm. from 11:0. +pub const R_AARCH64_TLSGD_ADD_LO12_NC: u32 = 514; +/// GOT-rel. MOV{N,Z} 31:16. +pub const R_AARCH64_TLSGD_MOVW_G1: u32 = 515; +/// GOT-rel. MOVK imm. 15:0. +pub const R_AARCH64_TLSGD_MOVW_G0_NC: u32 = 516; +/// Like 512; local dynamic model. +pub const R_AARCH64_TLSLD_ADR_PREL21: u32 = 517; +/// Like 513; local dynamic model. +pub const R_AARCH64_TLSLD_ADR_PAGE21: u32 = 518; +/// Like 514; local dynamic model. +pub const R_AARCH64_TLSLD_ADD_LO12_NC: u32 = 519; +/// Like 515; local dynamic model. +pub const R_AARCH64_TLSLD_MOVW_G1: u32 = 520; +/// Like 516; local dynamic model. +pub const R_AARCH64_TLSLD_MOVW_G0_NC: u32 = 521; +/// TLS PC-rel. load imm. 20:2. +pub const R_AARCH64_TLSLD_LD_PREL19: u32 = 522; +/// TLS DTP-rel. MOV{N,Z} 47:32. +pub const R_AARCH64_TLSLD_MOVW_DTPREL_G2: u32 = 523; +/// TLS DTP-rel. MOV{N,Z} 31:16. +pub const R_AARCH64_TLSLD_MOVW_DTPREL_G1: u32 = 524; +/// Likewise; MOVK; no check. +pub const R_AARCH64_TLSLD_MOVW_DTPREL_G1_NC: u32 = 525; +/// TLS DTP-rel. MOV{N,Z} 15:0. +pub const R_AARCH64_TLSLD_MOVW_DTPREL_G0: u32 = 526; +/// Likewise; MOVK; no check. +pub const R_AARCH64_TLSLD_MOVW_DTPREL_G0_NC: u32 = 527; +/// DTP-rel. ADD imm. from 23:12. +pub const R_AARCH64_TLSLD_ADD_DTPREL_HI12: u32 = 528; +/// DTP-rel. ADD imm. from 11:0. +pub const R_AARCH64_TLSLD_ADD_DTPREL_LO12: u32 = 529; +/// Likewise; no ovfl. check. +pub const R_AARCH64_TLSLD_ADD_DTPREL_LO12_NC: u32 = 530; +/// DTP-rel. LD/ST imm. 11:0. +pub const R_AARCH64_TLSLD_LDST8_DTPREL_LO12: u32 = 531; +/// Likewise; no check. +pub const R_AARCH64_TLSLD_LDST8_DTPREL_LO12_NC: u32 = 532; +/// DTP-rel. LD/ST imm. 11:1. +pub const R_AARCH64_TLSLD_LDST16_DTPREL_LO12: u32 = 533; +/// Likewise; no check. +pub const R_AARCH64_TLSLD_LDST16_DTPREL_LO12_NC: u32 = 534; +/// DTP-rel. LD/ST imm. 11:2. +pub const R_AARCH64_TLSLD_LDST32_DTPREL_LO12: u32 = 535; +/// Likewise; no check. +pub const R_AARCH64_TLSLD_LDST32_DTPREL_LO12_NC: u32 = 536; +/// DTP-rel. LD/ST imm. 11:3. +pub const R_AARCH64_TLSLD_LDST64_DTPREL_LO12: u32 = 537; +/// Likewise; no check. +pub const R_AARCH64_TLSLD_LDST64_DTPREL_LO12_NC: u32 = 538; +/// GOT-rel. MOV{N,Z} 31:16. +pub const R_AARCH64_TLSIE_MOVW_GOTTPREL_G1: u32 = 539; +/// GOT-rel. MOVK: u32 = 15:0. +pub const R_AARCH64_TLSIE_MOVW_GOTTPREL_G0_NC: u32 = 540; +/// Page-rel. ADRP: u32 = 32:12. +pub const R_AARCH64_TLSIE_ADR_GOTTPREL_PAGE21: u32 = 541; +/// Direct LD off. 11:3. +pub const R_AARCH64_TLSIE_LD64_GOTTPREL_LO12_NC: u32 = 542; +/// PC-rel. load imm. 20:2. +pub const R_AARCH64_TLSIE_LD_GOTTPREL_PREL19: u32 = 543; +/// TLS TP-rel. MOV{N,Z} 47:32. +pub const R_AARCH64_TLSLE_MOVW_TPREL_G2: u32 = 544; +/// TLS TP-rel. MOV{N,Z} 31:16. +pub const R_AARCH64_TLSLE_MOVW_TPREL_G1: u32 = 545; +/// Likewise; MOVK; no check. +pub const R_AARCH64_TLSLE_MOVW_TPREL_G1_NC: u32 = 546; +/// TLS TP-rel. MOV{N,Z} 15:0. +pub const R_AARCH64_TLSLE_MOVW_TPREL_G0: u32 = 547; +/// Likewise; MOVK; no check. +pub const R_AARCH64_TLSLE_MOVW_TPREL_G0_NC: u32 = 548; +/// TP-rel. ADD imm. 23:12. +pub const R_AARCH64_TLSLE_ADD_TPREL_HI12: u32 = 549; +/// TP-rel. ADD imm. 11:0. +pub const R_AARCH64_TLSLE_ADD_TPREL_LO12: u32 = 550; +/// Likewise; no ovfl. check. +pub const R_AARCH64_TLSLE_ADD_TPREL_LO12_NC: u32 = 551; +/// TP-rel. LD/ST off. 11:0. +pub const R_AARCH64_TLSLE_LDST8_TPREL_LO12: u32 = 552; +/// Likewise; no ovfl. check. +pub const R_AARCH64_TLSLE_LDST8_TPREL_LO12_NC: u32 = 553; +/// TP-rel. LD/ST off. 11:1. +pub const R_AARCH64_TLSLE_LDST16_TPREL_LO12: u32 = 554; +/// Likewise; no check. +pub const R_AARCH64_TLSLE_LDST16_TPREL_LO12_NC: u32 = 555; +/// TP-rel. LD/ST off. 11:2. +pub const R_AARCH64_TLSLE_LDST32_TPREL_LO12: u32 = 556; +/// Likewise; no check. +pub const R_AARCH64_TLSLE_LDST32_TPREL_LO12_NC: u32 = 557; +/// TP-rel. LD/ST off. 11:3. +pub const R_AARCH64_TLSLE_LDST64_TPREL_LO12: u32 = 558; +/// Likewise; no check. +pub const R_AARCH64_TLSLE_LDST64_TPREL_LO12_NC: u32 = 559; +/// PC-rel. load immediate 20:2. +pub const R_AARCH64_TLSDESC_LD_PREL19: u32 = 560; +/// PC-rel. ADR immediate 20:0. +pub const R_AARCH64_TLSDESC_ADR_PREL21: u32 = 561; +/// Page-rel. ADRP imm. 32:12. +pub const R_AARCH64_TLSDESC_ADR_PAGE21: u32 = 562; +/// Direct LD off. from 11:3. +pub const R_AARCH64_TLSDESC_LD64_LO12: u32 = 563; +/// Direct ADD imm. from 11:0. +pub const R_AARCH64_TLSDESC_ADD_LO12: u32 = 564; +/// GOT-rel. MOV{N,Z} imm. 31:16. +pub const R_AARCH64_TLSDESC_OFF_G1: u32 = 565; +/// GOT-rel. MOVK imm. 15:0; no ck. +pub const R_AARCH64_TLSDESC_OFF_G0_NC: u32 = 566; +/// Relax LDR. +pub const R_AARCH64_TLSDESC_LDR: u32 = 567; +/// Relax ADD. +pub const R_AARCH64_TLSDESC_ADD: u32 = 568; +/// Relax BLR. +pub const R_AARCH64_TLSDESC_CALL: u32 = 569; +/// TP-rel. LD/ST off. 11:4. +pub const R_AARCH64_TLSLE_LDST128_TPREL_LO12: u32 = 570; +/// Likewise; no check. +pub const R_AARCH64_TLSLE_LDST128_TPREL_LO12_NC: u32 = 571; +/// DTP-rel. LD/ST imm. 11:4. +pub const R_AARCH64_TLSLD_LDST128_DTPREL_LO12: u32 = 572; +/// Likewise; no check. +pub const R_AARCH64_TLSLD_LDST128_DTPREL_LO12_NC: u32 = 573; +/// Copy symbol at runtime. +pub const R_AARCH64_COPY: u32 = 1024; +/// Create GOT entry. +pub const R_AARCH64_GLOB_DAT: u32 = 1025; +/// Create PLT entry. +pub const R_AARCH64_JUMP_SLOT: u32 = 1026; +/// Adjust by program base. +pub const R_AARCH64_RELATIVE: u32 = 1027; +/// Module number, 64 bit. +pub const R_AARCH64_TLS_DTPMOD: u32 = 1028; +/// Module-relative offset, 64 bit. +pub const R_AARCH64_TLS_DTPREL: u32 = 1029; +/// TP-relative offset, 64 bit. +pub const R_AARCH64_TLS_TPREL: u32 = 1030; +/// TLS Descriptor. +pub const R_AARCH64_TLSDESC: u32 = 1031; +/// STT_GNU_IFUNC relocation. +pub const R_AARCH64_IRELATIVE: u32 = 1032; + +// ____ ____ ____ +// | _ \ _____ _____ _ __| _ \ / ___| +// | |_) / _ \ \ /\ / / _ \ '__| |_) | | +// | __/ (_) \ V V / __/ | | __/| |___ +// |_| \___/ \_/\_/ \___|_| |_| \____| +// +// See: https://refspecs.linuxfoundation.org/ELF/ppc64/PPC-elf64abi.html#ELF-HEAD + +/// PowerPC embedded flag +pub const EF_PPC_EMB: u32 = 0x80000000; +/// PowerPC -mrelocatable flag +pub const EF_PPC_RELOCATABLE: u32 = 0x00010000; +/// PowerPC -mrelocatable-lib flag +pub const EF_PPC_RELOCATABLE_LIB: u32 = 0x00008000; + +// PowerPC relocations types +pub const R_PPC_NONE: u32 = 0; +/// 32bit absolute address +pub const R_PPC_ADDR32: u32 = 1; +/// 26bit address, 2 bits ignored. +pub const R_PPC_ADDR24: u32 = 2; +/// 16bit absolute address +pub const R_PPC_ADDR16: u32 = 3; +/// lower 16bit of absolute address +pub const R_PPC_ADDR16_LO: u32 = 4; +/// high 16bit of absolute address +pub const R_PPC_ADDR16_HI: u32 = 5; +/// adjusted high 16bit +pub const R_PPC_ADDR16_HA: u32 = 6; +/// 16bit address, 2 bits ignored +pub const R_PPC_ADDR14: u32 = 7; +pub const R_PPC_ADDR14_BRTAKEN: u32 = 8; +pub const R_PPC_ADDR14_BRNTAKEN: u32 = 9; +/// PC relative 26 bit +pub const R_PPC_REL24: u32 = 10; +/// PC relative 16 bit +pub const R_PPC_REL14: u32 = 11; +pub const R_PPC_REL14_BRTAKEN: u32 = 12; +pub const R_PPC_REL14_BRNTAKEN: u32 = 13; +pub const R_PPC_GOT16: u32 = 14; +pub const R_PPC_GOT16_LO: u32 = 15; +pub const R_PPC_GOT16_HI: u32 = 16; +pub const R_PPC_GOT16_HA: u32 = 17; +pub const R_PPC_PLTREL24: u32 = 18; +pub const R_PPC_COPY: u32 = 19; +pub const R_PPC_GLOB_DAT: u32 = 20; +pub const R_PPC_JMP_SLOT: u32 = 21; +pub const R_PPC_RELATIVE: u32 = 22; +pub const R_PPC_LOCAL24PC: u32 = 23; +pub const R_PPC_UADDR32: u32 = 24; +pub const R_PPC_UADDR16: u32 = 25; +pub const R_PPC_REL32: u32 = 26; +pub const R_PPC_PLT32: u32 = 27; +pub const R_PPC_PLTREL32: u32 = 28; +pub const R_PPC_PLT16_LO: u32 = 29; +pub const R_PPC_PLT16_HI: u32 = 30; +pub const R_PPC_PLT16_HA: u32 = 31; +pub const R_PPC_SDAREL16: u32 = 32; +pub const R_PPC_SECTOFF: u32 = 33; +pub const R_PPC_SECTOFF_LO: u32 = 34; +pub const R_PPC_SECTOFF_HI: u32 = 35; +pub const R_PPC_SECTOFF_HA: u32 = 36; + +/// (sym+add)@tls +pub const R_PPC_TLS: u32 = 67; +/// (sym+add)@dtpmod +pub const R_PPC_DTPMOD32: u32 = 68; +/// (sym+add)@tprel +pub const R_PPC_TPREL16: u32 = 69; +/// (sym+add)@tprel@l +pub const R_PPC_TPREL16_LO: u32 = 70; +/// (sym+add)@tprel@h +pub const R_PPC_TPREL16_HI: u32 = 71; +/// (sym+add)@tprel@ha +pub const R_PPC_TPREL16_HA: u32 = 72; +/// (sym+add)@tprel +pub const R_PPC_TPREL32: u32 = 73; +/// (sym+add)@dtprel +pub const R_PPC_DTPREL16: u32 = 74; +/// (sym+add)@dtprel@l +pub const R_PPC_DTPREL16_LO: u32 = 75; +/// (sym+add)@dtprel@h +pub const R_PPC_DTPREL16_HI: u32 = 76; +/// (sym+add)@dtprel@ha +pub const R_PPC_DTPREL16_HA: u32 = 77; +/// (sym+add)@dtprel +pub const R_PPC_DTPREL32: u32 = 78; +/// (sym+add)@got@tlsgd +pub const R_PPC_GOT_TLSGD16: u32 = 79; +/// (sym+add)@got@tlsgd@l +pub const R_PPC_GOT_TLSGD16_LO: u32 = 80; +/// (sym+add)@got@tlsgd@h +pub const R_PPC_GOT_TLSGD16_HI: u32 = 81; +/// (sym+add)@got@tlsgd@ha +pub const R_PPC_GOT_TLSGD16_HA: u32 = 82; +/// (sym+add)@got@tlsld +pub const R_PPC_GOT_TLSLD16: u32 = 83; +/// (sym+add)@got@tlsld@l +pub const R_PPC_GOT_TLSLD16_LO: u32 = 84; +/// (sym+add)@got@tlsld@h +pub const R_PPC_GOT_TLSLD16_HI: u32 = 85; +/// (sym+add)@got@tlsld@ha +pub const R_PPC_GOT_TLSLD16_HA: u32 = 86; +/// (sym+add)@got@tprel +pub const R_PPC_GOT_TPREL16: u32 = 87; +/// (sym+add)@got@tprel@l +pub const R_PPC_GOT_TPREL16_LO: u32 = 88; +/// (sym+add)@got@tprel@h +pub const R_PPC_GOT_TPREL16_HI: u32 = 89; +/// (sym+add)@got@tprel@ha +pub const R_PPC_GOT_TPREL16_HA: u32 = 90; +/// (sym+add)@got@dtprel +pub const R_PPC_GOT_DTPREL16: u32 = 91; +/// (sym+add)@got@dtprel@l +pub const R_PPC_GOT_DTPREL16_LO: u32 = 92; +/// (sym+add)@got@dtprel@h +pub const R_PPC_GOT_DTPREL16_HI: u32 = 93; +/// (sym+add)@got@dtprel@ha +pub const R_PPC_GOT_DTPREL16_HA: u32 = 94; +/// (sym+add)@tlsgd +pub const R_PPC_TLSGD: u32 = 95; +/// (sym+add)@tlsld +pub const R_PPC_TLSLD: u32 = 96; + +// The remaining relocs are from the Embedded ELF ABI, and are not in the SVR4 ELF ABI. +pub const R_PPC_EMB_NADDR32: u32 = 101; +pub const R_PPC_EMB_NADDR16: u32 = 102; +pub const R_PPC_EMB_NADDR16_LO: u32 = 103; +pub const R_PPC_EMB_NADDR16_HI: u32 = 104; +pub const R_PPC_EMB_NADDR16_HA: u32 = 105; +pub const R_PPC_EMB_SDAI16: u32 = 106; +pub const R_PPC_EMB_SDA2I16: u32 = 107; +pub const R_PPC_EMB_SDA2REL: u32 = 108; +/// 16 bit offset in SDA +pub const R_PPC_EMB_SDA21: u32 = 109; +pub const R_PPC_EMB_MRKREF: u32 = 110; +pub const R_PPC_EMB_RELSEC16: u32 = 111; +pub const R_PPC_EMB_RELST_LO: u32 = 112; +pub const R_PPC_EMB_RELST_HI: u32 = 113; +pub const R_PPC_EMB_RELST_HA: u32 = 114; +pub const R_PPC_EMB_BIT_FLD: u32 = 115; +pub const R_PPC_EMB_RELSDA: u32 = 116; + +/// like EMB_SDA21, but lower 16 bit +pub const R_PPC_DIAB_SDA21_LO: u32 = 180; +/// like EMB_SDA21, but high 16 bit +pub const R_PPC_DIAB_SDA21_HI: u32 = 181; +/// like EMB_SDA21, adjusted high 16 +pub const R_PPC_DIAB_SDA21_HA: u32 = 182; +/// like EMB_RELSDA, but lower 16 bit +pub const R_PPC_DIAB_RELSDA_LO: u32 = 183; +/// like EMB_RELSDA, but high 16 bit +pub const R_PPC_DIAB_RELSDA_HI: u32 = 184; +/// like EMB_RELSDA, adjusted high 16 +pub const R_PPC_DIAB_RELSDA_HA: u32 = 185; + +// GNU extension to support local ifunc. +pub const R_PPC_IRELATIVE: u32 = 248; + +// GNU relocs used in PIC code sequences. +/// (sym+add-.) +pub const R_PPC_REL16: u32 = 249; +/// (sym+add-.)@l +pub const R_PPC_REL16_LO: u32 = 250; +/// (sym+add-.)@h +pub const R_PPC_REL16_HI: u32 = 251; +/// (sym+add-.)@ha +pub const R_PPC_REL16_HA: u32 = 252; + +/// This is a phony reloc to handle any old fashioned TOC16 references that may still be in object files. +pub const R_PPC_TOC16: u32 = 255; + +// PowerPC specific values for the Dyn d_tag field. +pub const DT_PPC_GOT: i64 = 0x70000000; +pub const DT_PPC_OPT: i64 = 0x70000001; + +/// PowerPC specific values for the DT_PPC_OPT Dyn entry. +pub const PPC_OPT_TLS: u64 = 1; + +// e_flags bits specifying ABI. +// 1 for original function descriptor using ABI, +// 2 for revised ABI without function descriptors, +// 0 for unspecified or not using any features affected by the differences. +pub const EF_PPC64_ABI: u32 = 3; + +// PowerPC64 specific values for the Dyn d_tag field. +pub const DT_PPC64_GLINK: i64 = 0x70000000; +pub const DT_PPC64_OPD: i64 = 0x70000001; +pub const DT_PPC64_OPDSZ: i64 = 0x70000002; +pub const DT_PPC64_OPT: i64 = 0x70000003; + +// PowerPC64 specific bits in the DT_PPC64_OPT Dyn entry. +pub const PPC64_OPT_TLS: u64 = 1; +pub const PPC64_OPT_MULTI_TOC: u64 = 2; +pub const PPC64_OPT_LOCALENTRY: u64 = 4; + +// PowerPC64 specific values for the Elf64_Sym st_other field. +pub const STO_PPC64_LOCAL_BIT: u8 = 5; +pub const STO_PPC64_LOCAL_MASK: u8 = 7 << STO_PPC64_LOCAL_BIT; + +// Relocation types +// +// A Represents the addend used to compute the value of the relocatable field. +// B Represents the base address at which a shared object has been loaded into memory during execution. +// G Represents the offset into the global offset table, relative to +// the TOC base, at which the address of the relocation entry's symbol +// plus addend will reside during execution. +// L Represents the section offset or address of the procedure linkage +// table entry for the symbol plus addend. +// M Similar to G, except that the address which is stored may be the +// address of the procedure linkage table entry for the symbol. +// P Represents the place (section offset or address) of the storage +// unit being relocated (computed using r_offset). +// R Represents the offset of the symbol within the section in which +// the symbol is defined (its section-relative address). +// S Represents the value of the symbol whose index resides in the relocation entry. + +/// none +pub const R_PPC64_NONE: u32 = 0; +/// `S + A` +pub const R_PPC64_ADDR32: u32 = 1; +/// `(S + A) >> 2` +pub const R_PPC64_ADDR24: u32 = 2; +/// `S + A` +pub const R_PPC64_ADDR16: u32 = 3; +/// `#lo(S + A)` +pub const R_PPC64_ADDR16_LO: u32 = 4; +/// `#hi(S + A)` +pub const R_PPC64_ADDR16_HI: u32 = 5; +/// `#ha(S + A)` +pub const R_PPC64_ADDR16_HA: u32 = 6; +/// `(S + A) >> 2` +pub const R_PPC64_ADDR14: u32 = 7; +/// `(S + A) >> 2` +pub const R_PPC64_ADDR14_BRTAKEN: u32 = 8; +/// `(S + A) >> 2` +pub const R_PPC64_ADDR14_BRNTAKEN: u32 = 9; +/// `(S + A - P) >> 2` +pub const R_PPC64_REL24: u32 = 10; +/// `(S + A - P) >> 2` +pub const R_PPC64_REL14: u32 = 11; +/// `(S + A - P) >> 2` +pub const R_PPC64_REL14_BRTAKEN: u32 = 12; +/// `(S + A - P) >> 2` +pub const R_PPC64_REL14_BRNTAKEN: u32 = 13; +/// `G` +pub const R_PPC64_GOT16: u32 = 14; +/// `#lo(G)` +pub const R_PPC64_GOT16_LO: u32 = 15; +/// `#hi(G)` +pub const R_PPC64_GOT16_HI: u32 = 16; +/// `#ha(G)` +pub const R_PPC64_GOT16_HA: u32 = 17; +/// none +pub const R_PPC64_COPY: u32 = 19; +/// `S + A` +pub const R_PPC64_GLOB_DAT: u32 = 20; +/// see below +pub const R_PPC64_JMP_SLOT: u32 = 21; +/// `B + A` +pub const R_PPC64_RELATIVE: u32 = 22; +/// `S + A` +pub const R_PPC64_UADDR32: u32 = 24; +/// `S + A` +pub const R_PPC64_UADDR16: u32 = 25; +/// `S + A - P` +pub const R_PPC64_REL32: u32 = 26; +/// `L` +pub const R_PPC64_PLT32: u32 = 27; +/// `L - P` +pub const R_PPC64_PLTREL32: u32 = 28; +/// `#lo(L)` +pub const R_PPC64_PLT16_LO: u32 = 29; +/// `#hi(L)` +pub const R_PPC64_PLT16_HI: u32 = 30; +/// `#ha(L)` +pub const R_PPC64_PLT16_HA: u32 = 31; +/// `R + A` +pub const R_PPC64_SECTOFF: u32 = 33; +/// `#lo(R + A)` +pub const R_PPC64_SECTOFF_LO: u32 = 34; +/// `#hi(R + A)` +pub const R_PPC64_SECTOFF_HI: u32 = 35; +/// `#ha(R + A)` +pub const R_PPC64_SECTOFF_HA: u32 = 36; +/// `(S + A - P) >> 2` +pub const R_PPC64_ADDR30: u32 = 37; +/// `S + A` +pub const R_PPC64_ADDR64: u32 = 38; +/// `#higher(S + A)` +pub const R_PPC64_ADDR16_HIGHER: u32 = 39; +/// `#highera(S + A)` +pub const R_PPC64_ADDR16_HIGHERA: u32 = 40; +/// `#highest(S + A)` +pub const R_PPC64_ADDR16_HIGHEST: u32 = 41; +/// `#highesta(S + A)` +pub const R_PPC64_ADDR16_HIGHESTA: u32 = 42; +/// `S + A` +pub const R_PPC64_UADDR64: u32 = 43; +/// `S + A - P` +pub const R_PPC64_REL64: u32 = 44; +/// `L` +pub const R_PPC64_PLT64: u32 = 45; +/// `L - P` +pub const R_PPC64_PLTREL64: u32 = 46; +/// `S + A - .TOC.` +pub const R_PPC64_TOC16: u32 = 47; +/// `#lo(S + A - .TOC.)` +pub const R_PPC64_TOC16_LO: u32 = 48; +/// `#hi(S + A - .TOC.)` +pub const R_PPC64_TOC16_HI: u32 = 49; +/// `#ha(S + A - .TOC.)` +pub const R_PPC64_TOC16_HA: u32 = 50; +/// `.TOC.` +pub const R_PPC64_TOC: u32 = 51; +/// `M` +pub const R_PPC64_PLTGOT16: u32 = 52; +/// `#lo(M)` +pub const R_PPC64_PLTGOT16_LO: u32 = 53; +/// `#hi(M)` +pub const R_PPC64_PLTGOT16_HI: u32 = 54; +/// `#ha(M)` +pub const R_PPC64_PLTGOT16_HA: u32 = 55; +/// `(S + A) >> 2` +pub const R_PPC64_ADDR16_DS: u32 = 56; +/// `#lo(S + A) >> 2` +pub const R_PPC64_ADDR16_LO_DS: u32 = 57; +/// `G >> 2` +pub const R_PPC64_GOT16_DS: u32 = 58; +/// `#lo(G) >> 2` +pub const R_PPC64_GOT16_LO_DS: u32 = 59; +/// `#lo(L) >> 2` +pub const R_PPC64_PLT16_LO_DS: u32 = 60; +/// `(R + A) >> 2` +pub const R_PPC64_SECTOFF_DS: u32 = 61; +/// `#lo(R + A) >> 2` +pub const R_PPC64_SECTOFF_LO_DS: u32 = 62; +/// `(S + A - .TOC.) >> 2` +pub const R_PPC64_TOC16_DS: u32 = 63; +/// `#lo(S + A - .TOC.) >> 2` +pub const R_PPC64_TOC16_LO_DS: u32 = 64; +/// `M >> 2` +pub const R_PPC64_PLTGOT16_DS: u32 = 65; +/// `#lo(M) >> 2` +pub const R_PPC64_PLTGOT16_LO_DS: u32 = 66; +/// none +pub const R_PPC64_TLS: u32 = 67; +/// `@dtpmod` +pub const R_PPC64_DTPMOD64: u32 = 68; +/// `@tprel` +pub const R_PPC64_TPREL16: u32 = 69; +/// `#lo(@tprel)` +pub const R_PPC64_TPREL16_LO: u32 = 60; +/// `#hi(@tprel)` +pub const R_PPC64_TPREL16_HI: u32 = 71; +/// `#ha(@tprel)` +pub const R_PPC64_TPREL16_HA: u32 = 72; +/// `@tprel` +pub const R_PPC64_TPREL64: u32 = 73; +/// `@dtprel` +pub const R_PPC64_DTPREL16: u32 = 74; +/// `#lo(@dtprel)` +pub const R_PPC64_DTPREL16_LO: u32 = 75; +/// `#hi(@dtprel)` +pub const R_PPC64_DTPREL16_HI: u32 = 76; +/// `#ha(@dtprel)` +pub const R_PPC64_DTPREL16_HA: u32 = 77; +/// `@dtprel` +pub const R_PPC64_DTPREL64: u32 = 78; +/// `@got@tlsgd` +pub const R_PPC64_GOT_TLSGD16: u32 = 79; +/// `#lo(@got@tlsgd)` +pub const R_PPC64_GOT_TLSGD16_LO: u32 = 80; +/// `#hi(@got@tlsgd)` +pub const R_PPC64_GOT_TLSGD16_HI: u32 = 81; +/// `#ha(@got@tlsgd)` +pub const R_PPC64_GOT_TLSGD16_HA: u32 = 82; +/// `@got@tlsld` +pub const R_PPC64_GOT_TLSLD16: u32 = 83; +/// `#lo(@got@tlsld)` +pub const R_PPC64_GOT_TLSLD16_LO: u32 = 84; +/// `#hi(@got@tlsld)` +pub const R_PPC64_GOT_TLSLD16_HI: u32 = 85; +/// `#ha(@got@tlsld)` +pub const R_PPC64_GOT_TLSLD16_HA: u32 = 86; +/// `@got@tprel` +pub const R_PPC64_GOT_TPREL16_DS: u32 = 87; +/// `#lo(@got@tprel)` +pub const R_PPC64_GOT_TPREL16_LO_DS: u32 = 88; +/// `#hi(@got@tprel)` +pub const R_PPC64_GOT_TPREL16_HI: u32 = 89; +/// `#ha(@got@tprel)` +pub const R_PPC64_GOT_TPREL16_HA: u32 = 90; +/// `@got@dtprel` +pub const R_PPC64_GOT_DTPREL16_DS: u32 = 91; +/// `#lo(@got@dtprel)` +pub const R_PPC64_GOT_DTPREL16_LO_DS: u32 = 92; +/// `#hi(@got@dtprel)` +pub const R_PPC64_GOT_DTPREL16_HI: u32 = 93; +/// `#ha(@got@dtprel)` +pub const R_PPC64_GOT_DTPREL16_HA: u32 = 94; +/// `@tprel` +pub const R_PPC64_TPREL16_DS: u32 = 95; +/// `#lo(@tprel)` +pub const R_PPC64_TPREL16_LO_DS: u32 = 96; +/// `#higher(@tprel)` +pub const R_PPC64_TPREL16_HIGHER: u32 = 97; +/// `#highera(@tprel)` +pub const R_PPC64_TPREL16_HIGHERA: u32 = 98; +/// `#highest(@tprel)` +pub const R_PPC64_TPREL16_HIGHEST: u32 = 99; +/// `#highesta(@tprel)` +pub const R_PPC64_TPREL16_HIGHESTA: u32 = 100; +/// `@dtprel` +pub const R_PPC64_DTPREL16_DS: u32 = 101; +/// `#lo(@dtprel)` +pub const R_PPC64_DTPREL16_LO_DS: u32 = 102; +/// `#higher(@dtprel)` +pub const R_PPC64_DTPREL16_HIGHER: u32 = 103; +/// `#highera(@dtprel)` +pub const R_PPC64_DTPREL16_HIGHERA: u32 = 104; +/// `#highest(@dtprel)` +pub const R_PPC64_DTPREL16_HIGHEST: u32 = 105; +/// `#highesta(@dtprel)` +pub const R_PPC64_DTPREL16_HIGHESTA: u32 = 106; +/// `(sym+add)@tlsgd` +pub const R_PPC64_TLSGD: u32 = 107; +/// `(sym+add)@tlsld` +pub const R_PPC64_TLSLD: u32 = 108; +pub const R_PPC64_TOCSAVE: u32 = 109; +pub const R_PPC64_ADDR16_HIGH: u32 = 110; +pub const R_PPC64_ADDR16_HIGHA: u32 = 111; +pub const R_PPC64_TPREL16_HIGH: u32 = 112; +pub const R_PPC64_TPREL16_HIGHA: u32 = 113; +pub const R_PPC64_DTPREL16_HIGH: u32 = 114; +pub const R_PPC64_DTPREL16_HIGHA: u32 = 115; + +// GNU extension to support local ifunc. +pub const R_PPC64_JMP_IREL: u32 = 247; +pub const R_PPC64_IRELATIVE: u32 = 248; +/// `(sym+add-.)` +pub const R_PPC64_REL16: u32 = 249; +/// `(sym+add-.)@l` +pub const R_PPC64_REL16_LO: u32 = 250; +/// `(sym+add-.)@h` +pub const R_PPC64_REL16_HI: u32 = 251; +/// `(sym+add-.)@ha` +pub const R_PPC64_REL16_HA: u32 = 252; + +// ____ ___ ____ ____ __ __ +// | _ \|_ _/ ___| / ___| \ \ / / +// | |_) || |\___ \| | ____\ \ / / +// | _ < | | ___) | |__|_____\ V / +// |_| \_\___|____/ \____| \_/ +// +// See: https://github.com/riscv-non-isa/riscv-elf-psabi-doc + +/// This bit is set when the binary targets the C ABI. +pub const EF_RISCV_RVC: u32 = 0x0001; +pub const EF_RISCV_FLOAT_ABI_SOFT: u32 = 0x0000; +pub const EF_RISCV_FLOAT_ABI_SINGLE: u32 = 0x0002; +pub const EF_RISCV_FLOAT_ABI_DOUBLE: u32 = 0x0004; +pub const EF_RISCV_FLOAT_ABI_QUAD: u32 = 0x0006; +/// This is used as a mask to test for one of the above floating-point ABIs, +/// e.g., (e_flags & EF_RISCV_FLOAT_ABI) == EF_RISCV_FLOAT_ABI_DOUBLE. +pub const EF_RISCV_FLOAT_ABI_MASK: u32 = 0x0006; +/// This bit is set when the binary targets the E ABI. +pub const EF_RISCV_RVE: u32 = 0x0008; +/// This bit is set when the binary requires the RVTSO memory consistency model. +pub const EF_RISCV_TSO: u32 = 0x0010; + +pub const SHT_RISCV_ATTRIBUTES: u32 = 0x70000003; // SHT_LOPROC + 3; +pub const SHT_RISCV_ATTRIBUTES_SECTION_NAME: &str = ".riscv.attributes"; + +pub const PT_RISCV_ATTRIBUTES: u32 = 0x70000003; + +/// Any functions that use registers in a way that is incompatible with the +/// calling convention of the ABI in use must be annotated with STO_RISCV_VARIANT_CC +pub const STO_RISCV_VARIANT_CC: u8 = 0x80; + +/// An object must have the dynamic tag DT_RISCV_VARIANT_CC if it has one or more R_RISCV_JUMP_SLOT +/// relocations against symbols with the STO_RISCV_VARIANT_CC attribute. +pub const DT_RISCV_VARIANT_CC: i64 = 0x70000001; + +// RISC-V relocation types +// +// A Addend field in the relocation entry associated with the symbol +// B Base address of a shared object loaded into memory +// G Offset of the symbol into the GOT (Global Offset Table) +// GOT Address of the GOT (Global Offset Table) +// P Position of the relocation +// S Value of the symbol in the symbol table +// V Value at the position of the relocation +// GP Value of __global_pointer$ symbol +// TLSMODULE TLS module index for the object containing the symbol +// TLSOFFSET TLS static block offset (relative to tp) for the object containing the symbol + +pub const R_RISCV_NONE: u32 = 0; +/// 32-bit relocation: `S + A` +pub const R_RISCV_32: u32 = 1; +/// 64-bit relocation: `S + A` +pub const R_RISCV_64: u32 = 2; +/// Adjust a link address (A) to its load address: `(B + A).` +pub const R_RISCV_RELATIVE: u32 = 3; +/// Must be in executable; not allowed in shared library +pub const R_RISCV_COPY: u32 = 4; +/// Indicates the symbol associated with a PLT entry: `S` +pub const R_RISCV_JUMP_SLOT: u32 = 5; +/// `TLSMODULE` +pub const R_RISCV_TLS_DTPMOD32: u32 = 6; +/// `TLSMODULE` +pub const R_RISCV_TLS_DTPMOD64: u32 = 7; +/// `S + A - TLS_DTV_OFFSET` +pub const R_RISCV_TLS_DTPREL32: u32 = 8; +/// `S + A - TLS_DTV_OFFSET` +pub const R_RISCV_TLS_DTPREL64: u32 = 9; +/// `S + A + TLSOFFSET` +pub const R_RISCV_TLS_TPREL32: u32 = 10; +/// `S + A + TLSOFFSET` +pub const R_RISCV_TLS_TPREL64: u32 = 11; +/// 12-bit PC-relative branch offset `S + A - P` +pub const R_RISCV_BRANCH: u32 = 16; +/// 20-bit PC-relative jump offset `S + A - P` +pub const R_RISCV_JAL: u32 = 17; +/// Deprecated, please use CALL_PLT instead 32-bit PC-relative function call, macros call, tail: `S + A - P` +pub const R_RISCV_CALL: u32 = 18; +/// 32-bit PC-relative function call, macros call, tail (PIC): `S + A - P` +pub const R_RISCV_CALL_PLT: u32 = 19; +/// High 20 bits of 32-bit PC-relative GOT access, %got_pcrel_hi(symbol): `G + GOT + A - P` +pub const R_RISCV_GOT_HI20: u32 = 20; +/// High 20 bits of 32-bit PC-relative TLS IE GOT access, macro la.tls.ie +pub const R_RISCV_TLS_GOT_HI20: u32 = 21; +/// High 20 bits of 32-bit PC-relative TLS GD GOT reference, macro la.tls.gd +pub const R_RISCV_TLS_GD_HI20: u32 = 22; +/// High 20 bits of 32-bit PC-relative reference, %pcrel_hi(symbol): `S + A - P` +pub const R_RISCV_PCREL_HI20: u32 = 23; +/// Low 12 bits of a 32-bit PC-relative, %pcrel_lo(address of %pcrel_hi), the addend must be 0: `S - P` +pub const R_RISCV_PCREL_LO12_I: u32 = 24; +/// Low 12 bits of a 32-bit PC-relative, %pcrel_lo(address of %pcrel_hi), the addend must be 0: `S - P` +pub const R_RISCV_PCREL_LO12_S: u32 = 25; +/// High 20 bits of 32-bit absolute address, %hi(symbol): `S + A` +pub const R_RISCV_HI20: u32 = 26; +/// Low 12 bits of 32-bit absolute address, %lo(symbol): `S + A` +pub const R_RISCV_LO12_I: u32 = 27; +/// Low 12 bits of 32-bit absolute address, %lo(symbol): `S + A` +pub const R_RISCV_LO12_S: u32 = 28; +/// High 20 bits of TLS LE thread pointer offset, `%tprel_hi(symbol)` +pub const R_RISCV_TPREL_HI20: u32 = 29; +/// Low 12 bits of TLS LE thread pointer offset, `%tprel_lo(symbol)` +pub const R_RISCV_TPREL_LO12_I: u32 = 30; +/// Low 12 bits of TLS LE thread pointer offset, `%tprel_lo(symbol)` +pub const R_RISCV_TPREL_LO12_S: u32 = 31; +/// TLS LE thread pointer usage, `%tprel_add(symbol)` +pub const R_RISCV_TPREL_ADD: u32 = 32; +/// 8-bit label addition: `V + S + A` +pub const R_RISCV_ADD8: u32 = 33; +/// 16-bit label addition: `V + S + A` +pub const R_RISCV_ADD16: u32 = 34; +/// 32-bit label addition: `V + S + A` +pub const R_RISCV_ADD32: u32 = 35; +/// 64-bit label addition: `V + S + A` +pub const R_RISCV_ADD64: u32 = 36; +/// 8-bit label subtraction: `V - S - A` +pub const R_RISCV_SUB8: u32 = 37; +/// 16-bit label subtraction: `V - S - A` +pub const R_RISCV_SUB16: u32 = 38; +/// 32-bit label subtraction: `V - S - A` +pub const R_RISCV_SUB32: u32 = 39; +/// 64-bit label subtraction: `V - S - A` +pub const R_RISCV_SUB64: u32 = 40; +/// Alignment statement. The addend indicates the number of bytes occupied by +/// nop instructions at the relocation offset. The alignment boundary is +/// specified by the addend rounded up to the next power of two. +pub const R_RISCV_ALIGN: u32 = 43; +/// 8-bit PC-relative branch offset: `S + A - P` +pub const R_RISCV_RVC_BRANCH: u32 = 44; +/// 11-bit PC-relative jump offset: `S + A - P` +pub const R_RISCV_RVC_JUMP: u32 = 45; +/// High 6 bits of 18-bit absolute address: `S + A` +pub const R_RISCV_RVC_LUI: u32 = 46; +/// Instruction can be relaxed, paired with a normal relocation at the same address +pub const R_RISCV_RELAX: u32 = 51; +/// Local label subtraction: `V - S - A` +pub const R_RISCV_SUB6: u32 = 52; +/// Local label assignment: `S + A` +pub const R_RISCV_SET6: u32 = 53; +/// Local label assignment: `S + A` +pub const R_RISCV_SET8: u32 = 54; +/// Local label assignment: `S + A` +pub const R_RISCV_SET16: u32 = 55; +/// Local label assignment: `S + A` +pub const R_RISCV_SET32: u32 = 56; +/// 32-bit PC relative: `S + A - P` +pub const R_RISCV_32_PCREL: u32 = 57; +/// Relocation against a non-preemptible ifunc symbolifunc_resolver: `(B + A)` +pub const R_RISCV_IRELATIVE: u32 = 58; + +// ___ __ __ _ _ +// __ _( _ ) / /_ / /_ | || | +// \ \/ / _ \| '_ \ | '_ \| || |_ +// > < (_) | (_) | | (_) |__ _| +// /_/\_\___/ \___/___\___/ |_| +// |_____| +// +// See: https://gitlab.com/x86-psABIs/x86-64-ABI + +/// If an object file section does not have this flag set, then it may not hold +/// more than 2GB and can be freely referred to in objects using smaller code models. +pub const SHF_X86_64_LARGE: u64 = 0x10000000; + +/// This section contains unwind function table entries for stack unwinding. +pub const SHT_X86_64_UNWIND: u32 = 0x70000001; // SHT_LOPROC + 1; + +// x86_64 reloc types +// +// A Represents the addend used to compute the value of the relocatable field. +// B Represents the base address at which a shared object has been loaded into memory +// during execution. Generally, a shared object is built with a 0 base virtual address, +// but the execution address will be different. +// G Represents the offset into the global offset table at which the relocation entry’s symbol +// will reside during execution. +// GOT Represents the address of the global offset table. +// L Represents the place (section offset or address) of the Procedure Linkage Table entry for a symbol. +// P Represents the place (section offset or address) of the storage unit being relocated (computed using r_offset). +// S Represents the value of the symbol whose index resides in the relocation entry. +// Z Represents the size of the symbol whose index resides in the relocation entry. + +pub const R_X86_64_NONE: u32 = 0; +/// `S + A` +pub const R_X86_64_64: u32 = 1; +/// `S + A - P` +pub const R_X86_64_PC32: u32 = 2; +/// `G + A` +pub const R_X86_64_GOT32: u32 = 3; +/// `L + A - P` +pub const R_X86_64_PLT32: u32 = 4; +pub const R_X86_64_COPY: u32 = 5; +/// `S` +pub const R_X86_64_GLOB_DAT: u32 = 6; +/// `S` +pub const R_X86_64_JUMP_SLOT: u32 = 7; +/// `B + A` +pub const R_X86_64_RELATIVE: u32 = 8; +/// `G + GOT + A - P` +pub const R_X86_64_GOTPCREL: u32 = 9; +/// `S + A` +pub const R_X86_64_32: u32 = 10; +/// `S + A` +pub const R_X86_64_32S: u32 = 11; +/// `S + A` +pub const R_X86_64_16: u32 = 12; +/// `S + A - P` +pub const R_X86_64_PC16: u32 = 13; +/// `S + A` +pub const R_X86_64_8: u32 = 14; +/// `S + A - P` +pub const R_X86_64_PC8: u32 = 15; +pub const R_X86_64_DTPMOD64: u32 = 16; +pub const R_X86_64_DTPOFF64: u32 = 17; +pub const R_X86_64_TPOFF64: u32 = 18; +pub const R_X86_64_TLSGD: u32 = 19; +pub const R_X86_64_TLSLD: u32 = 20; +pub const R_X86_64_DTPOFF32: u32 = 21; +pub const R_X86_64_GOTTPOFF: u32 = 22; +pub const R_X86_64_TPOFF32: u32 = 23; +/// `S + A - P` +pub const R_X86_64_PC64: u32 = 24; +/// `S + A - GOT` +pub const R_X86_64_GOTOFF64: u32 = 25; +/// `GOT + A - P` +pub const R_X86_64_GOTPC32: u32 = 26; +/// `G + A` +pub const R_X86_64_GOT64: u32 = 27; +/// `G + GOT - P + A` +pub const R_X86_64_GOTPCREL64: u32 = 28; +/// `GOT - P + A` +pub const R_X86_64_GOTPC64: u32 = 29; +/// `L - GOT + A` +pub const R_X86_64_PLTOFF64: u32 = 31; +/// `Z + A` +pub const R_X86_64_SIZE32: u32 = 32; +/// `Z + A` +pub const R_X86_64_SIZE64: u32 = 33; +pub const R_X86_64_GOTPC32_TLSDESC: u32 = 34; +pub const R_X86_64_TLSDESC_CALL: u32 = 35; +pub const R_X86_64_TLSDESC: u32 = 36; +/// `indirect (B + A)` +pub const R_X86_64_IRELATIVE: u32 = 37; +/// `B + A` +pub const R_X86_64_RELATIVE64: u32 = 38; +/// `G + GOT + A - P` +pub const R_X86_64_GOTPCRELX: u32 = 41; +/// `G + GOT + A - P` +pub const R_X86_64_REX_GOTPCRELX: u32 = 42; + +// __ __ ___ ____ ____ +// | \/ |_ _| _ \/ ___| +// | |\/| || || |_) \___ \ +// | | | || || __/ ___) | +// |_| |_|___|_| |____/ +// +// MIPS-specific declarations +// See: https://www.linux-mips.org/pub/linux/mips/doc/ABI/elf64-2.4.pdf +// See: https://refspecs.linuxfoundation.org/elf/mipsabi.pdf + +/// At least one .noreorder assembly directive appeared in a source contributing to the object +pub const EF_MIPS_NOREORDER: u32 = 0x00000001; +/// This file contains position-independent code +pub const EF_MIPS_PIC: u32 = 0x00000002; +/// This file’s code follows standard conventions for calling position-independent code +pub const EF_MIPS_CPIC: u32 = 0x00000004; +pub const EF_MIPS_XGOT: u32 = 0x00000008; +/// This file contains UCODE (obsolete) +pub const EF_MIPS_64BIT_WHIRL: u32 = 0x00000010; +/// This file follows the MIPS III 32-bit ABI. (Its `EI_CLASS` will be `ELFCLASS32`.) +pub const EF_MIPS_ABI2: u32 = 0x00000020; +/// Obsolete +pub const EF_MIPS_ABI_ON32: u32 = 0x00000040; +/// This `.MIPS.options` section in this file contains one or more descriptors, currently types `ODK_GP_GROUP` and/or +/// `ODK_IDENT`, which should be processed first by `ld`. +pub const EF_MIPS_OPTIONS_FIRST: u32 = 0x00000080; +/// Code compiled for 64-bit machine with 32-bit registers +pub const EF_MIPS_32BITMODE: u32 = 0x00000100; +/// Uses FP64 (12 callee-saved) +pub const EF_MIPS_FP64: u32 = 0x00000200; +/// Uses IEEE 754-2008 NaN encoding +pub const EF_MIPS_NAN2008: u32 = 0x00000400; +/// Embedded ABI - MIPS32 with 32-bit address +pub const EF_MIPS_ABI_EABI32: u32 = 0x00003000; +/// Embedded ABI - MIPS64 with 32-bit address, similar to n32 +pub const EF_MIPS_ABI_EABI64: u32 = 0x00004000; +/// Machine variant bits, but not standard +pub const EF_MIPS_MACH: u32 = 0x00FF0000; +/// MicroMIPS +pub const EF_MIPS_ARCH_ASE_MICROMIPS: u32 = 0x02000000; +/// Application-specific architectural extensions used by this object file +pub const EF_MIPS_ARCH_ASE: u32 = 0x0f000000; +/// Uses MDMX multimedia extensions +pub const EF_MIPS_ARCH_ASE_MDMX: u32 = 0x08000000; +/// Uses MIPS-16 ISA extensions +pub const EF_MIPS_ARCH_ASE_M16: u32 = 0x04000000; +/// Architecture assumed by code in this file, given by the value of the 4-bit field selected by the mask +pub const EF_MIPS_ARCH: u32 = 0xf0000000; +/// MIPS I +pub const EF_MIPS_ARCH_1: u32 = 0x00000000; +/// MIPS II +pub const EF_MIPS_ARCH_2: u32 = 0x10000000; +/// MIPS III +pub const EF_MIPS_ARCH_3: u32 = 0x20000000; +/// MIPS IV +pub const EF_MIPS_ARCH_4: u32 = 0x30000000; +/// MIPS V (unused) +pub const EF_MIPS_ARCH_5: u32 = 0x40000000; +/// MIPS32 R1 +pub const EF_MIPS_ARCH_32: u32 = 0x50000000; +/// MIPS64 R1 +pub const EF_MIPS_ARCH_64: u32 = 0x60000000; +/// MIPS3 R2 +pub const EF_MIPS_ARCH_32R2: u32 = 0x70000000; +/// MIPS64 R2 +pub const EF_MIPS_ARCH_64R2: u32 = 0x80000000; +/// MIPS32 R6 +pub const EF_MIPS_ARCH_32R6: u32 = 0x90000000; +/// MIPS64 R6 +pub const EF_MIPS_ARCH_64R6: u32 = 0xa0000000; + +/// DSO library information used in link +pub const SHT_MIPS_LIBLIST: u32 = 0x70000000; +/// MIPS symbol table extension +pub const SHT_MIPS_MSYM: u32 = 0x70000001; +/// Symbols conflicting with DSO-defined symbols +pub const SHT_MIPS_CONFLICT: u32 = 0x70000002; +/// Global pointer table +pub const SHT_MIPS_GPTAB: u32 = 0x70000003; +/// Reserved +pub const SHT_MIPS_UCODE: u32 = 0x70000004; +/// Reserved (obsolete debug information) +pub const SHT_MIPS_DEBUG: u32 = 0x70000005; +/// Register usage information +pub const SHT_MIPS_REGINFO: u32 = 0x70000006; +/// OSF reserved +pub const SHT_MIPS_PACKAGE: u32 = 0x70000007; +/// OSF reserved +pub const SHT_MIPS_PACKSYM: u32 = 0x70000008; +/// Dynamic relocation? +pub const SHT_MIPS_RELD: u32 = 0x70000009; +/// Subprogram interface information +pub const SHT_MIPS_IFACE: u32 = 0x7000000b; +/// Section content classification +pub const SHT_MIPS_CONTENT: u32 = 0x7000000c; +/// General options +pub const SHT_MIPS_OPTIONS: u32 = 0x7000000d; +/// Delta C++: symbol table +pub const SHT_MIPS_DELTASYM: u32 = 0x7000001b; +/// Delta C++: instance table +pub const SHT_MIPS_DELTAINST: u32 = 0x7000001c; +/// Delta C++: class table +pub const SHT_MIPS_DELTACLASS: u32 = 0x7000001d; +/// DWARF debug information +pub const SHT_MIPS_DWARF: u32 = 0x7000001e; +/// Delta C++: declarations +pub const SHT_MIPS_DELTADECL: u32 = 0x7000001f; +/// Symbol-to-library mapping. +pub const SHT_MIPS_SYMBOL_LIB: u32 = 0x70000020; +/// Event locations +pub const SHT_MIPS_EVENTS: u32 = 0x70000021; +// this is listed in the 64-bit ELF Object File Specification as "???" and +// no other implementation has a comment about its purpose +pub const SHT_MIPS_TRANSLATE: u32 = 0x70000022; +/// Special pixie sections +pub const SHT_MIPS_PIXIE: u32 = 0x70000023; +/// Address translation table +pub const SHT_MIPS_XLATE: u32 = 0x70000024; +/// SGI internal address translation tablea +pub const SHT_MIPS_XLATE_DEBUG: u32 = 0x70000025; +/// Intermediate code +pub const SHT_MIPS_WHIRL: u32 = 0x70000026; +/// C++ exception handling region info +pub const SHT_MIPS_EH_REGION: u32 = 0x70000027; +/// Obsolete +pub const SHT_MIPS_XLATE_OLD: u32 = 0x70000028; +/// Runtime procedure descriptor table exception information (ucode) +pub const SHT_MIPS_PDR_EXCEPTION: u32 = 0x70000029; + +/// Section must be part of global data area. +pub const SHF_MIPS_GPREL: u32 = 0x10000000; +/// Section data should be merged to eliminate duplication +pub const SHF_MIPS_MERGE: u32 = 0x20000000; +/// Section data is addresses by default. Address size to be inferred from section entry size. +pub const SHF_MIPS_ADDR: u32 = 0x40000000; +/// Section data is string data by default +pub const SHF_MIPS_STRING: u32 = 0x80000000; +/// Section data may not be stripped +pub const SHF_MIPS_NOSTRIP: u32 = 0x08000000; +/// Section data local to process +pub const SHF_MIPS_LOCAL: u32 = 0x04000000; +/// Linker must generate implicit hidden weak names +pub const SHF_MIPS_NAMES: u32 = 0x02000000; +/// Section contains text/data which may be replicated in other sections. Linker must retain only one copy. +pub const SHF_MIPS_NODUPE: u32 = 0x01000000; + +/// DSO export class +pub const STO_EXPORT: u8 = 3; +/// Default: `STB_GLOBAL` or `STB_WEAK` are preemptible, `STB_LOCAL` are hidden. +pub const STO_DEFAULT: u8 = 0; +/// Not referenced outside executable/DSO +pub const STO_INTERNAL: u8 = 1; +/// Not visible outside executable/DSO +pub const STO_HIDDEN: u8 = 2; +/// Not preemptible +pub const STO_PROTECTED: u8 = 3; +/// Symbol is optional. If no definition is available at runtime, it is resolved to the symbol `_RLD_MISSING`. +pub const STO_OPTIONAL: u8 = 4; +pub const STO_MIPS_PLT: u8 = 8; +pub const STO_MIPS_SC_ALIGN_UNUSED: u8 = 0xff; + +/// Section indices between SHN_LORESERVE and SHN_HIRESERVE are reserved for special values — they do not refer to the +/// section header table. +pub const SHN_LORESERVE: u16 = 0xff00; +/// Section indices between SHN_LOPROC and SHN_HIPROC are reserved for processor-specific values. +pub const SHN_LOPROC: u16 = 0xff00; +/// Allocated common symbols in a DSO +pub const SHN_MIPS_ACOMMON: u16 = 0xff00; +/// Reserved (obsolete). +pub const SHN_MIPS_TEXT: u16 = 0xff01; +/// Reserved (obsolete). +pub const SHN_MIPS_DATA: u16 = 0xff02; +/// gp-addressable common symbolsb (relocatable objects only). +pub const SHN_MIPS_SCOMMON: u16 = 0xff03; +/// gp-addressable undefined symbolsc (relocatable objects only). +pub const SHN_MIPS_SUNDEFINED: u16 = 0xff04; +/// Local common, equivalent to SHN_COMMON, except that the common block will be allocated in a local section, i.e. one +/// replicated for each process in a multi-process program sharing memory. +pub const SHN_MIPS_LCOMMON: u16 = 0xff05; +/// Local undefined symbol, equivalent to SHN_UNDEFINED, except that the symbol must resolve to a local section, i.e. +/// one replicated for each process in a multi-process program sharing memory. +pub const SHN_MIPS_LUNDEFINED: u16 = 0xff06; +/// Section indices between SHN_LOPROC and SHN_HIPROC are reserved for processor-specific values. +pub const SHN_HIPROC: u16 = 0xff1f; +/// Section indices between SHN_LORESERVE and SHN_HIRESERVE are reserved for special values — they do not refer to the +/// section header table. +pub const SHN_HIRESERVE: u16 = 0xffff; + +// `kind` field in `ELF_Options` + +/// Undefined +pub const ODK_NULL: u8 = 0; +/// Register usage information +pub const ODK_REGINF: u8 = 1; +/// Exception processing options +pub const ODK_EXCEPTION: u8 = 2; +/// Section padding options +pub const ODK_PA: u8 = 3; +/// Hardware patches applied +pub const ODK_HWPATC: u8 = 4; +/// Linker fill value +pub const ODK_FIL: u8 = 5; +/// Space for tool identification +pub const ODK_TAG: u8 = 6; +/// Hardware AND patches applied +pub const ODK_HWAN: u8 = 7; +/// Hardware OR patches applied +pub const ODK_HWO: u8 = 8; +/// GP group to use for text/data sections +pub const ODK_GP_GROU: u8 = 9; +/// ID information +pub const ODK_IDEN: u8 = 10; +/// Page size information +pub const ODK_PAGESIZ: u8 = 11; + +// Values for `info' in Elf_Options for ODK_EXCEPTIONS entries. + +/// Min FPU exception enable +pub const OEX_FPU_MIN: u32 = 0x0000001f; +/// Max FPU exception enable +pub const OEX_FPU_MAX: u32 = 0x00001f00; +/// Page zero of the virtual address space must be mapped +pub const OEX_PAGE0: u32 = 0x00010000; +/// Run in sequential memory mode +pub const OEX_SMM: u32 = 0x00020000; +/// Run in precise FP exception mode +pub const OEX_PRECISEFP: u32 = 0x00040000; +/// Dismiss invalid address traps +pub const OEX_DISMISS: u32 = 0x00080000; +pub const OEX_FPU_INEX: u32 = 0x01; +pub const OEX_FPU_UFLO: u32 = 0x02; +pub const OEX_FPU_OFLO: u32 = 0x04; +pub const OEX_FPU_DIV0: u32 = 0x08; +pub const OEX_FPU_INVAL: u32 = 0x10; + +/// Patch for R4000 branch at end-of-page bug +pub const OHW_R4KEOP: u32 = 0x00000001; +/// Object contains prefetch instructions which may cause R8000 prefetch bug to occur +pub const OHW_R8KPFETCH: u32 = 0x00000002; +/// Patch for R5000 branch at end-of-page bug +pub const OHW_R5KEOP: u32 = 0x00000004; +/// R5000 cvt.[ds].l bug: clean=1 +pub const OHW_R5KCVTL: u32 = 0x00000008; +/// Requires patch for R10000 misaligned load. +pub const OHW_R10KLDL: u32 = 0x00000010; + +pub const OPAD_PREFIX: u32 = 0x1; +pub const OPAD_POSTFIX: u32 = 0x2; +pub const OPAD_SYMBOL: u32 = 0x4; + +// Hardware Patch AND/OR Options Descriptor Flags + +/// Object checked for R4K end-of-page bug. +pub const OHWA0_R4KEOP_CHECKED: u32 = 0x00000001; +/// Object verified clean of R4K end-of-page bug. +pub const OHWA0_R4KEOP_CLEAN: u32 = 0x00000002; +/// Object requires call to fixade +pub const OHWO0_FIXADE: u32 = 0x00000001; + +/// None — value is zero. +pub const RSS_UNDEF: u32 = 0; +/// Value of gp +pub const RSS_GP: u32 = 1; +/// Value of gp used to create object being relocated +pub const RSS_GP0: u32 = 2; +/// Address of location being relocated +pub const RSS_LOC: u32 = 3; + +// Relocation Types + +/// None +pub const R_MIPS_NONE: u8 = 0; +/// `S + sign_extend(A)` +pub const R_MIPS_16: u8 = 1; +/// `S + A` +pub const R_MIPS_32: u8 = 2; +/// Alias of `R_MIPS_32` +pub const R_MIPS_ADD: u8 = R_MIPS_32; +/// `S + A - EA`jA +pub const R_MIPS_REL32: u8 = 3; +/// Alias of `MIPS_REL32` +pub const R_MIPS_REL: u8 = R_MIPS_REL32; +/// local - `(((A << 2) | (P&0xf0000000)) + S) >> 2` +/// external - `(sign_extend(A<<2) + S) >> 2` +pub const R_MIPS_26: u8 = 4; +/// `%high (AHL + S)` +pub const R_MIPS_HI16: u8 = 5; +/// `%high (AHL + S)` +pub const R_MIPS_LO16: u8 = 6; +/// external `sign_extend(A) + S - GP` +pub const R_MIPS_GPREL16: u8 = 7; +/// local `sign_extend(A) + S + GP0 - GP` +pub const R_MIPS_GPREL: u8 = R_MIPS_GPREL16; +/// `sign_extend(A) + L` +pub const R_MIPS_LITERAL: u8 = 8; +/// external - `G` +pub const R_MIPS_GOT16: u8 = 9; +/// local - `f` +pub const R_MIPS_GOT: u8 = R_MIPS_GOT16; +/// `sign_extend(A) + S - P` +pub const R_MIPS_PC16: u8 = 10; +/// `G` +pub const R_MIPS_CALL16: u8 = 11; +/// Alias for `R_MIPS_CALL16` +pub const R_MIPS_CALL: u8 = R_MIPS_CALL16; +/// `A + S + GP0 - GP` +pub const R_MIPS_GPREL32: u8 = 12; +/// `S` +pub const R_MIPS_SHIFT5: u8 = 16; +/// `S` +pub const R_MIPS_SHIFT6: u8 = 17; +/// `S + A` +pub const R_MIPS_64: u8 = 18; +/// `G` +pub const R_MIPS_GOT_DISP: u8 = 19; +/// `h` +pub const R_MIPS_GOT_PAGE: u8 = 20; +/// `h` +pub const R_MIPS_GOT_OFST: u8 = 21; +/// `%high(G)` +pub const R_MIPS_GOT_HI16: u8 = 22; +/// `G` +pub const R_MIPS_GOT_LO16: u8 = 23; +/// `S - A` +pub const R_MIPS_SUB: u8 = 24; +/// Insert addend as instruction immediately prior to addressed location +pub const R_MIPS_INSERT_A: u8 = 25; +/// Insert addend as instruction immediately prior to addressed location +pub const R_MIPS_INSERT_B: u8 = 26; +/// Remove the addressed 32-bit object (normally an instruction). +pub const R_MIPS_DELETE: u8 = 27; +/// `%higher(A+S)` +pub const R_MIPS_HIGHER: u8 = 28; +/// `%highest(A+S)` +pub const R_MIPS_HIGHEST: u8 = 29; +/// `%high(G)` +pub const R_MIPS_CALL_HI16: u8 = 30; +/// `G` +pub const R_MIPS_CALL_LO16: u8 = 31; +/// `S+A-scn_addr` (section displacement) +pub const R_MIPS_SCN_DISP: u8 = 32; +/// `S + A` +pub const R_MIPS_REL16: u8 = 33; +/// V-half16 any oS + sign_extend(A) +pub const R_MIPS_ADD_IMMEDIATE: u8 = 34; +/// T-word32 any Deprecated (protected jump) +pub const R_MIPS_PJUMP: u8 = 35; +/// T-word32 any qS + A - EA +pub const R_MIPS_RELGOT: u8 = 36; +/// T-word32 any pProtected jump conversion +pub const R_MIPS_JALR: u8 = 37; + +/// Module number 32 bit +pub const R_MIPS_TLS_DTPMOD32: u8 = 38; +/// Module-relative offset 32-bit +pub const R_MIPS_TLS_DTPREL3: u8 = 39; +/// Module number 64 bit +pub const R_MIPS_TLS_DTPMOD6: u8 = 40; +/// Module-relative offset 64-bit +pub const R_MIPS_TLS_DTPREL64: u8 = 41; +/// 16-bit GOT offset for GD +pub const R_MIPS_TLS_GD: u8 = 42; +/// 16 bit GOT offset for LDM +pub const R_MIPS_TLS_LDM: u8 = 43; +/// Module-relative offset, high 16-bits +pub const R_MIPS_TLS_DTPREL_HI16: u8 = 44; +/// Module-relative offset, low 16-bits +pub const R_MIPS_TLS_DTPREL_LO16: u8 = 45; +/// 16-bit GOT offset for IE +pub const R_MIPS_TLS_GOTTPREL: u8 = 46; +/// TP-relative offset, 32-bit +pub const R_MIPS_TLS_TPREL32: u8 = 47; +/// TP-relative offset, 64-bit +pub const R_MIPS_TLS_TPREL64: u8 = 48; +/// TP-relative offset, high 16-bits +pub const R_MIPS_TLS_TPREL_HI16: u8 = 49; +/// TP-relative offset, low 16-bits +pub const R_MIPS_TLS_TPREL_LO16: u8 = 50; +pub const R_MIPS_GLOB_DAT: u8 = 51; +pub const R_MIPS_COPY: u8 = 126; +pub const R_MIPS_JUMP_SLOT: u8 = 127; + +/// Version ID for the Runtime Linker Interface +pub const DT_MIPS_RLD_VERSION: u32 = 0x70000001; +/// Timestamp +pub const DT_MIPS_TIME_STAMP: u32 = 0x70000002; +/// Checksum +pub const DT_MIPS_ICHECKSUM: u32 = 0x70000003; +/// String table index of a compatible version string +pub const DT_MIPS_IVERSION: u32 = 0x70000004; +/// MIPS-specific flags +pub const DT_MIPS_FLAG: u32 = 0x70000005; +/// The base address assumed for the executable/DSO at static link time +pub const DT_MIPS_BASE_ADDRESS: u32 = 0x70000006; +pub const DT_MIPS_MSYM: u32 = 0x70000007; +/// The address of the `.conflict` section +pub const DT_MIPS_CONFLICT: u32 = 0x70000008; +/// The address of the `.liblist` section +pub const DT_MIPS_LIBLIST: u32 = 0x70000009; +/// The number of local GOT entries. +pub const DT_MIPS_LOCAL_GOTNO: u32 = 0x7000000a; +/// The number of entries in the `.conflict` section +pub const DT_MIPS_CONFLICTNO: u32 = 0x7000000b; +/// The number of entries in the `.liblist` section +pub const DT_MIPS_LIBLISTNO: u32 = 0x70000010; +/// The number of entries in the `.dynsym` section +pub const DT_MIPS_SYMTABNO: u32 = 0x70000011; +/// the index into the dynamic symbol table of the first external symbol that is not referenced in the same object +pub const DT_MIPS_UNREFEXTNO: u32 = 0x70000012; +/// The index into the dynamic symbol table of the first entry that corresponds to an external symbol with an entry in +/// the GOT +pub const DT_MIPS_GOTSYM: u32 = 0x70000013; +/// The number of page table entries in the GOT +pub const DT_MIPS_HIPAGENO: u32 = 0x70000014; +/// Address of run time loader map +pub const DT_MIPS_RLD_MAP: u32 = 0x70000016; +/// Delta C++ class definition table +pub const DT_MIPS_DELTA_CLASS: u32 = 0x70000017; +/// The number of entries in Delta C++ class definition table +pub const DT_MIPS_DELTA_CLASS_NO: u32 = 0x70000018; +/// Delta C++ class instances table +pub const DT_MIPS_DELTA_INSTANCE: u32 = 0x70000019; +/// The number of entries in the Delta C++ class instances table +pub const DT_MIPS_DELTA_INSTANCE_NO: u32 = 0x7000001a; +/// Delta relocations table +pub const DT_MIPS_DELTA_RELOC: u32 = 0x7000001b; +/// The number of entries in the Delta relocations table +pub const DT_MIPS_DELTA_RELOC_NO: u32 = 0x7000001c; +/// Delta symbols table +pub const DT_MIPS_DELTA_SYM: u32 = 0x7000001d; +/// The number of entries in the Delta symbols table +pub const DT_MIPS_DELTA_SYM_NO: u32 = 0x7000001e; +/// Delta symbols which hold the class declaration table +pub const DT_MIPS_DELTA_CLASSSYM: u32 = 0x70000020; +/// The number of entries in the Delta symbols with class delcaration table +pub const DT_MIPS_DELTA_CLASSSYM_NO: u32 = 0x70000021; +/// Flags regarding the C++ flavor +pub const DT_MIPS_CXX_FLAGS: u32 = 0x70000022; +/// The address of an initialization routine created by pixie +pub const DT_MIPS_PIXIE_INIT: u32 = 0x70000023; +/// The address of the `.MIPS.symlib` section, describing a mapping from the `.dynsym` symbols to the DSOs where they +/// are defined +pub const DT_MIPS_SYMBOL_LIB: u32 = 0x70000024; +/// The index in the GOT of the first page table entry for a segment +pub const DT_MIPS_LOCALPAGE_GOTIDX: u32 = 0x70000025; +/// The index in the GOT of the first entry for a local symbol +pub const DT_MIPS_LOCAL_GOTIDX: u32 = 0x70000026; +/// The index in the GOT of the first entry for a hidden symbol +pub const DT_MIPS_HIDDEN_GOTIDX: u32 = 0x70000027; +/// The index in the GOT of the first entry for a protected symbol +pub const DT_MIPS_PROTECTED_GOTIDX: u32 = 0x70000028; +/// The address of the Options section, containing various execution options +pub const DT_MIPS_OPTIONS: u32 = 0x70000029; +/// The address of the `.MIPS.interface` section, describing subprogram interfaces +pub const DT_MIPS_INTERFACE: u32 = 0x7000002a; +pub const DT_MIPS_DYNSTR_ALIGN: u32 = 0x7000002b; +/// The size in bytes of the .MIPS.interface section +pub const DT_MIPS_INTERFACE_SIZE: u32 = 0x7000002c; +/// When present, contains the link-time address of `_rld_text_resolve` to place in GOT entry 0. When missing or +/// different from the address in `rld` of the `_rld_text_resolve` function, then `rld` will place the actual address of +/// `_rld_text_resolve` in entry 0 of GOT at run-time. +pub const DT_MIPS_RLD_TEXT_RESOLVE_ADDR: u32 = 0x7000002d; +/// an index to the string table. +pub const DT_MIPS_PERF_SUFFIX: u32 = 0x7000002e; +/// The size of a ucode compact relocation header record +pub const DT_MIPS_COMPACT_SIZE: u32 = 0x7000002f; +/// The GP value of a specific GP relative range +pub const DT_MIPS_GP_VALUE: u32 = 0x70000030; +/// The address of an auxiliary dynamic table in the case of multigot +pub const DT_MIPS_AUX_DYNAMIC: u32 = 0x70000031; +/// Informs the runtime linker (`rld`) that the `.symlib` section is fully filled out and preemption is not used +pub const DT_MIPS_DIRECT: u32 = 0x70000032; +/// The dynamic symbol entry of a callback function +pub const DT_MIPS_RLD_OBJ_UPDATE: u32 = 0x70000033; + +/// None +pub const RHF_NONE: u32 = 0x00000000; +/// Use runtime loading shortcuts if possible +pub const RHF_QUICKSTART: u32 = 0x00000001; +/// Hash size not a power of two +pub const RHF_NOTPOT: u32 = 0x00000002; +/// Ignore LD_LIBRARY_PATH +pub const RHF_NO_LIBRARY_REPLACEMENT: u32 = 0x00000004; +/// DSO addresses may not be relocated by rld +pub const RHF_NO_MOVE: u32 = 0x00000008; +/// Contains SGI-specific features +pub const RHF_SGI_ONLY: u32 = 0x00000010; +/// Guarantee that .init will finish executing before any non-.init code in the DSO is called +pub const RHF_GUARANTEE_INIT: u32 = 0x00000020; +/// Contains Delta C++ code +pub const RHF_DELTA_C_PLUS_PLUS: u32 = 0x00000040; +/// Guarantee that .init will begin executing before any non-.init code in the DSO is called +pub const RHF_GUARANTEE_START_INIT: u32 = 0x00000080; +/// Generated by pixie +pub const RHF_PIXIE: u32 = 0x00000100; +/// Delay-load DSO by default +pub const RHF_DEFAULT_DELAY_LOAD: u32 = 0x00000200; +/// Object may be requickstarted +pub const RHF_REQUICKSTART: u32 = 0x00000400; +/// Object has been requickstarted +pub const RHF_REQUICKSTARTED: u32 = 0x00000800; +/// Generated by cord +pub const RHF_CORD: u32 = 0x00001000; +/// Object contains no unresolved undef symbols +pub const RHF_NO_UNRES_UNDEF: u32 = 0x00002000; +/// Symbol table is in a safe order +pub const RHF_RLD_ORDER_SAFE: u32 = 0x00004000; diff --git a/src/third_party/rust-elf/src/compression.rs b/src/third_party/rust-elf/src/compression.rs new file mode 100644 index 0000000..1e11fdb --- /dev/null +++ b/src/third_party/rust-elf/src/compression.rs @@ -0,0 +1,153 @@ +//! Parsing [CompressionHeader] from compressed ELF sections +//! +//! Note: This library does not provide any decompression functionality, but +//! does expose parsed ELF compression headers alongside the raw compressed data. +//! +//! It is up to users of the library to choose the decompression library of +//! their choice when dealing with compressed section contents. +use crate::endian::EndianParse; +use crate::file::Class; +use crate::parse::{ParseAt, ParseError}; + +/// C-style 32-bit ELF Compression Header definition +/// +/// These C-style definitions are for users who want to implement their own ELF manipulation logic. +#[derive(Debug)] +#[repr(C)] +pub struct Elf32_Chdr { + pub ch_type: u32, + pub ch_size: u32, + pub ch_addralign: u32, +} + +/// C-style 64-bit ELF Compression Header definition +/// +/// These C-style definitions are for users who want to implement their own ELF manipulation logic. +#[derive(Debug)] +#[repr(C)] +pub struct Elf64_Chdr { + pub ch_type: u32, + pub ch_reserved: u32, + pub ch_size: u64, + pub ch_addralign: u64, +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct CompressionHeader { + pub ch_type: u32, + pub ch_size: u64, + pub ch_addralign: u64, +} + +impl ParseAt for CompressionHeader { + fn parse_at( + endian: E, + class: Class, + offset: &mut usize, + data: &[u8], + ) -> Result { + match class { + Class::ELF32 => Ok(CompressionHeader { + ch_type: endian.parse_u32_at(offset, data)?, + ch_size: endian.parse_u32_at(offset, data)? as u64, + ch_addralign: endian.parse_u32_at(offset, data)? as u64, + }), + Class::ELF64 => { + let ch_type = endian.parse_u32_at(offset, data)?; + let _ch_reserved = endian.parse_u32_at(offset, data)?; + Ok(CompressionHeader { + ch_type, + ch_size: endian.parse_u64_at(offset, data)?, + ch_addralign: endian.parse_u64_at(offset, data)?, + }) + } + } + } + + #[inline] + fn size_for(class: Class) -> usize { + match class { + Class::ELF32 => 12, + Class::ELF64 => 24, + } + } +} + +#[cfg(test)] +mod parse_tests { + use super::*; + use crate::endian::{BigEndian, LittleEndian}; + use crate::parse::{test_parse_for, test_parse_fuzz_too_short}; + + #[test] + fn parse_chdr32_lsb() { + test_parse_for( + LittleEndian, + Class::ELF32, + CompressionHeader { + ch_type: 0x03020100, + ch_size: 0x07060504, + ch_addralign: 0x0B0A0908, + }, + ); + } + + #[test] + fn parse_chdr32_msb() { + test_parse_for( + BigEndian, + Class::ELF32, + CompressionHeader { + ch_type: 0x00010203, + ch_size: 0x04050607, + ch_addralign: 0x08090A0B, + }, + ); + } + + #[test] + fn parse_chdr64_lsb() { + test_parse_for( + LittleEndian, + Class::ELF64, + CompressionHeader { + ch_type: 0x03020100, + ch_size: 0x0F0E0D0C0B0A0908, + ch_addralign: 0x1716151413121110, + }, + ); + } + + #[test] + fn parse_chdr64_msb() { + test_parse_for( + BigEndian, + Class::ELF64, + CompressionHeader { + ch_type: 0x00010203, + ch_size: 0x08090A0B0C0D0E0F, + ch_addralign: 0x1011121314151617, + }, + ); + } + + #[test] + fn parse_chdr32_lsb_fuzz_too_short() { + test_parse_fuzz_too_short::<_, CompressionHeader>(LittleEndian, Class::ELF32); + } + + #[test] + fn parse_chdr32_msb_fuzz_too_short() { + test_parse_fuzz_too_short::<_, CompressionHeader>(BigEndian, Class::ELF32); + } + + #[test] + fn parse_chdr64_lsb_fuzz_too_short() { + test_parse_fuzz_too_short::<_, CompressionHeader>(LittleEndian, Class::ELF64); + } + + #[test] + fn parse_chdr64_msb_fuzz_too_short() { + test_parse_fuzz_too_short::<_, CompressionHeader>(BigEndian, Class::ELF64); + } +} diff --git a/src/third_party/rust-elf/src/dynamic.rs b/src/third_party/rust-elf/src/dynamic.rs new file mode 100644 index 0000000..288b536 --- /dev/null +++ b/src/third_party/rust-elf/src/dynamic.rs @@ -0,0 +1,157 @@ +//! Parsing `.dynamic` section or [PT_DYNAMIC](crate::abi::PT_DYNAMIC) segment contents +use crate::endian::EndianParse; +use crate::file::Class; +use crate::parse::{ParseAt, ParseError, ParsingTable}; + +pub type DynamicTable<'data, E> = ParsingTable<'data, E, Dyn>; + +/// C-style 32-bit ELF Dynamic section entry definition +/// +/// These C-style definitions are for users who want to implement their own ELF manipulation logic. +#[derive(Debug)] +#[repr(C)] +pub struct Elf32_Dyn { + pub d_tag: i32, + // union of both {d_val, d_ptr} + pub d_un: u32, +} + +/// C-style 64-bit ELF Dynamic section entry definition +/// +/// These C-style definitions are for users who want to implement their own ELF manipulation logic. +#[derive(Debug)] +#[repr(C)] +pub struct Elf64_Dyn { + pub d_tag: i64, + // union of both {d_val, d_ptr} + pub d_un: u64, +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct Dyn { + pub d_tag: i64, + pub(super) d_un: u64, +} + +impl Dyn { + pub fn d_val(&self) -> u64 { + self.d_un + } + + pub fn d_ptr(&self) -> u64 { + self.d_un + } +} + +impl ParseAt for Dyn { + fn parse_at( + endian: E, + class: Class, + offset: &mut usize, + data: &[u8], + ) -> Result { + match class { + Class::ELF32 => Ok(Dyn { + d_tag: endian.parse_i32_at(offset, data)? as i64, + d_un: endian.parse_u32_at(offset, data)? as u64, + }), + Class::ELF64 => Ok(Dyn { + d_tag: endian.parse_i64_at(offset, data)?, + d_un: endian.parse_u64_at(offset, data)?, + }), + } + } + + #[inline] + fn size_for(class: Class) -> usize { + match class { + Class::ELF32 => 8, + Class::ELF64 => 16, + } + } +} + +#[cfg(test)] +mod parse_tests { + use super::*; + use crate::endian::{BigEndian, LittleEndian}; + use crate::parse::{test_parse_for, test_parse_fuzz_too_short}; + + #[test] + fn test_d_val_and_d_ptr() { + let val = Dyn { + d_tag: 0x01, + d_un: 0x0102030405060708, + }; + assert_eq!(val.d_ptr(), 0x0102030405060708); + assert_eq!(val.d_val(), 0x0102030405060708); + } + + #[test] + fn parse_dyn32_lsb() { + test_parse_for( + LittleEndian, + Class::ELF32, + Dyn { + d_tag: 0x03020100, + d_un: 0x07060504, + }, + ); + } + + #[test] + fn parse_dyn32_msb() { + test_parse_for( + BigEndian, + Class::ELF32, + Dyn { + d_tag: 0x00010203, + d_un: 0x04050607, + }, + ); + } + + #[test] + fn parse_dyn64_lsb() { + test_parse_for( + LittleEndian, + Class::ELF64, + Dyn { + d_tag: 0x0706050403020100, + d_un: 0x0F0E0D0C0B0A0908, + }, + ); + } + + #[test] + fn parse_dyn64_msb() { + test_parse_for( + BigEndian, + Class::ELF64, + Dyn { + d_tag: 0x0001020304050607, + d_un: 0x08090A0B0C0D0E0F, + }, + ); + } + + #[test] + fn parse_dyn32_lsb_fuzz_too_short() { + test_parse_fuzz_too_short::<_, Dyn>(LittleEndian, Class::ELF32); + } + + #[test] + fn parse_dyn32_msb_fuzz_too_short() { + test_parse_fuzz_too_short::<_, Dyn>(BigEndian, Class::ELF32); + } + + #[test] + fn parse_dyn64_lsb_fuzz_too_short() { + test_parse_fuzz_too_short::<_, Dyn>(LittleEndian, Class::ELF64); + } + + #[test] + fn parse_dyn64_msb_fuzz_too_short() { + test_parse_fuzz_too_short::<_, Dyn>(BigEndian, Class::ELF64); + } +} diff --git a/src/third_party/rust-elf/src/elf_bytes.rs b/src/third_party/rust-elf/src/elf_bytes.rs new file mode 100644 index 0000000..6444e76 --- /dev/null +++ b/src/third_party/rust-elf/src/elf_bytes.rs @@ -0,0 +1,1578 @@ +use crate::abi; +use crate::compression::CompressionHeader; +use crate::dynamic::{Dyn, DynamicTable}; +use crate::endian::EndianParse; +use crate::file::{parse_ident, Class, FileHeader}; +use crate::gnu_symver::{ + SymbolVersionTable, VerDefIterator, VerNeedIterator, VersionIndex, VersionIndexTable, +}; +use crate::hash::{GnuHashTable, SysVHashTable}; +use crate::note::NoteIterator; +use crate::parse::{ParseAt, ParseError, ReadBytesExt}; +use crate::relocation::{RelIterator, RelaIterator}; +use crate::section::{SectionHeader, SectionHeaderTable}; +use crate::segment::{ProgramHeader, SegmentTable}; +use crate::string_table::StringTable; +use crate::symbol::{Symbol, SymbolTable}; + +// _____ _ _____ ____ _ +// | ____| | | ___| __ ) _ _| |_ ___ ___ +// | _| | | | |_ | _ \| | | | __/ _ \/ __| +// | |___| |___| _| | |_) | |_| | || __/\__ \ +// |_____|_____|_| |____/ \__, |\__\___||___/ +// |___/ +// + +/// This type encapsulates the bytes-oriented interface for parsing ELF objects from `&[u8]`. +/// +/// This parser is no_std and zero-alloc, returning lazy-parsing interfaces wrapped around +/// subslices of the provided ELF bytes `&[u8]`. The various ELF structures are +/// parsed on-demand into a native Rust representation. +/// +/// Example usage: +/// ``` +/// use elf::abi::PT_LOAD; +/// use elf::endian::AnyEndian; +/// use elf::ElfBytes; +/// use elf::segment::ProgramHeader; +/// +/// let path = std::path::PathBuf::from("sample-objects/symver.x86_64.so"); +/// let file_data = std::fs::read(path).unwrap(); +/// +/// let slice = file_data.as_slice(); +/// let file = ElfBytes::::minimal_parse(slice).unwrap(); +/// +/// // Get all the common ELF sections (if any). We have a lot of ELF work to do! +/// let common_sections = file.find_common_data().unwrap(); +/// // ... do some stuff with the symtab, dynsyms etc +/// +/// // It can also yield iterators on which we can do normal iterator things, like filtering +/// // for all the segments of a specific type. Parsing is done on each iter.next() call, so +/// // if you end iteration early, it won't parse the rest of the table. +/// let first_load_phdr: Option = file.segments().unwrap() +/// .iter() +/// .find(|phdr|{phdr.p_type == PT_LOAD}); +/// println!("First load segment is at: {}", first_load_phdr.unwrap().p_vaddr); +/// +/// // Or if you do things like this to get a vec of only the PT_LOAD segments. +/// let all_load_phdrs: Vec = file.segments().unwrap() +/// .iter() +/// .filter(|phdr|{phdr.p_type == PT_LOAD}) +/// .collect(); +/// println!("There are {} PT_LOAD segments", all_load_phdrs.len()); +/// ``` +#[derive(Debug)] +pub struct ElfBytes<'data, E: EndianParse> { + pub ehdr: FileHeader, + data: &'data [u8], + shdrs: Option>, + phdrs: Option>, +} + +/// Find the location (if any) of the section headers in the given data buffer and take a +/// subslice of their data and wrap it in a lazy-parsing SectionHeaderTable. +/// If shnum > SHN_LORESERVE (0xff00), then this will additionally parse out shdr[0] to calculate +/// the full table size, but all other parsing of SectionHeaders is deferred. +fn find_shdrs<'data, E: EndianParse>( + ehdr: &FileHeader, + data: &'data [u8], +) -> Result>, ParseError> { + // It's Ok to have no section headers + if ehdr.e_shoff == 0 { + return Ok(None); + } + + // If the number of sections is greater than or equal to SHN_LORESERVE (0xff00), + // e_shnum is zero and the actual number of section header table entries + // is contained in the sh_size field of the section header at index 0. + let shoff: usize = ehdr.e_shoff.try_into()?; + let mut shnum = ehdr.e_shnum as usize; + if shnum == 0 { + let mut offset = shoff; + let shdr0 = SectionHeader::parse_at(ehdr.endianness, ehdr.class, &mut offset, data)?; + shnum = shdr0.sh_size.try_into()?; + } + + // Validate shentsize before trying to read the table so that we can error early for corrupted files + let entsize = SectionHeader::validate_entsize(ehdr.class, ehdr.e_shentsize as usize)?; + + let size = entsize + .checked_mul(shnum) + .ok_or(ParseError::IntegerOverflow)?; + let end = shoff.checked_add(size).ok_or(ParseError::IntegerOverflow)?; + let buf = data.get_bytes(shoff..end)?; + Ok(Some(SectionHeaderTable::new( + ehdr.endianness, + ehdr.class, + buf, + ))) +} + +/// Find the location (if any) of the program headers in the given data buffer and take a +/// subslice of their data and wrap it in a lazy-parsing SegmentTable. +fn find_phdrs<'data, E: EndianParse>( + ehdr: &FileHeader, + data: &'data [u8], +) -> Result>, ParseError> { + // It's Ok to have no program headers + if ehdr.e_phoff == 0 { + return Ok(None); + } + + // If the number of segments is greater than or equal to PN_XNUM (0xffff), + // e_phnum is set to PN_XNUM, and the actual number of program header table + // entries is contained in the sh_info field of the section header at index 0. + let mut phnum = ehdr.e_phnum as usize; + if phnum == abi::PN_XNUM as usize { + let shoff: usize = ehdr.e_shoff.try_into()?; + let mut offset = shoff; + let shdr0 = SectionHeader::parse_at(ehdr.endianness, ehdr.class, &mut offset, data)?; + phnum = shdr0.sh_info.try_into()?; + } + + // Validate phentsize before trying to read the table so that we can error early for corrupted files + let entsize = ProgramHeader::validate_entsize(ehdr.class, ehdr.e_phentsize as usize)?; + + let phoff: usize = ehdr.e_phoff.try_into()?; + let size = entsize + .checked_mul(phnum) + .ok_or(ParseError::IntegerOverflow)?; + let end = phoff.checked_add(size).ok_or(ParseError::IntegerOverflow)?; + let buf = data.get_bytes(phoff..end)?; + Ok(Some(SegmentTable::new(ehdr.endianness, ehdr.class, buf))) +} + +/// This struct collects the common sections found in ELF objects +#[derive(Debug, Default)] +pub struct CommonElfData<'data, E: EndianParse> { + /// .symtab section + pub symtab: Option>, + /// strtab for .symtab + pub symtab_strs: Option>, + + /// .dynsym section + pub dynsyms: Option>, + /// strtab for .dynsym + pub dynsyms_strs: Option>, + + /// .dynamic section or PT_DYNAMIC segment (both point to the same table) + pub dynamic: Option>, + + /// .hash section + pub sysv_hash: Option>, + + /// .gnu.hash section + pub gnu_hash: Option>, +} + +impl<'data, E: EndianParse> ElfBytes<'data, E> { + /// Do the minimal parsing work to get an [ElfBytes] handle from a byte slice containing an ELF object. + /// + /// This parses the ELF [FileHeader], and locates (but does not parse) the + /// Section Header Table and Segment Table. + /// + // N.B. I thought about calling this "sparse_parse", but it felt too silly for a serious lib like this + pub fn minimal_parse(data: &'data [u8]) -> Result { + let ident_buf = data.get_bytes(0..abi::EI_NIDENT)?; + let ident = parse_ident(ident_buf)?; + + let tail_start = abi::EI_NIDENT; + let tail_end = match ident.1 { + Class::ELF32 => tail_start + crate::file::ELF32_EHDR_TAILSIZE, + Class::ELF64 => tail_start + crate::file::ELF64_EHDR_TAILSIZE, + }; + let tail_buf = data.get_bytes(tail_start..tail_end)?; + + let ehdr = FileHeader::parse_tail(ident, tail_buf)?; + + let shdrs = find_shdrs(&ehdr, data)?; + let phdrs = find_phdrs(&ehdr, data)?; + Ok(ElfBytes { + ehdr, + data, + shdrs, + phdrs, + }) + } + + /// Get this Elf object's zero-alloc lazy-parsing [SegmentTable] (if any). + /// + /// This table parses [ProgramHeader]s on demand and does not make any internal heap allocations + /// when parsing. + pub fn segments(&self) -> Option> { + self.phdrs + } + + /// Get this Elf object's zero-alloc lazy-parsing [SectionHeaderTable] (if any). + /// + /// This table parses [SectionHeader]s on demand and does not make any internal heap allocations + /// when parsing. + pub fn section_headers(&self) -> Option> { + self.shdrs + } + + /// Get this ELF object's [SectionHeaderTable] alongside its corresponding [StringTable]. + /// + /// This is useful if you want to know the string name of sections. + /// + /// Example usage: + /// ``` + /// use std::collections::HashMap; + /// use elf::endian::AnyEndian; + /// use elf::ElfBytes; + /// use elf::note::Note; + /// use elf::note::NoteGnuBuildId; + /// use elf::section::SectionHeader; + /// + /// let path = std::path::PathBuf::from("sample-objects/symver.x86_64.so"); + /// let file_data = std::fs::read(path).unwrap(); + /// + /// let slice = file_data.as_slice(); + /// let file = ElfBytes::::minimal_parse(slice).unwrap(); + /// + /// // Get the section header table alongside its string table + /// let (shdrs_opt, strtab_opt) = file + /// .section_headers_with_strtab() + /// .expect("shdrs offsets should be valid"); + /// let (shdrs, strtab) = ( + /// shdrs_opt.expect("Should have shdrs"), + /// strtab_opt.expect("Should have strtab") + /// ); + /// + /// // Parse the shdrs and collect them into a map keyed on their zero-copied name + /// let with_names: HashMap<&str, SectionHeader> = shdrs + /// .iter() + /// .map(|shdr| { + /// ( + /// strtab.get(shdr.sh_name as usize).expect("Failed to get section name"), + /// shdr, + /// ) + /// }) + /// .collect(); + /// + /// // Get the zero-copy parsed type for the the build id note + /// let build_id_note_shdr: &SectionHeader = with_names + /// .get(".note.gnu.build-id") + /// .expect("Should have build id note section"); + /// let notes: Vec<_> = file + /// .section_data_as_notes(build_id_note_shdr) + /// .expect("Should be able to get note section data") + /// .collect(); + /// println!("{:?}", notes[0]); + /// ``` + pub fn section_headers_with_strtab( + &self, + ) -> Result< + ( + Option>, + Option>, + ), + ParseError, + > { + // It's Ok to have no section headers + let shdrs = match self.section_headers() { + Some(shdrs) => shdrs, + None => { + return Ok((None, None)); + } + }; + + // It's Ok to not have a string table + if self.ehdr.e_shstrndx == abi::SHN_UNDEF { + return Ok((Some(shdrs), None)); + } + + // If the section name string table section index is greater than or + // equal to SHN_LORESERVE (0xff00), e_shstrndx has the value SHN_XINDEX + // (0xffff) and the actual index of the section name string table section + // is contained in the sh_link field of the section header at index 0. + let mut shstrndx = self.ehdr.e_shstrndx as usize; + if self.ehdr.e_shstrndx == abi::SHN_XINDEX { + let shdr_0 = shdrs.get(0)?; + shstrndx = shdr_0.sh_link as usize; + } + + let strtab = shdrs.get(shstrndx)?; + let (strtab_start, strtab_end) = strtab.get_data_range()?; + let strtab_buf = self.data.get_bytes(strtab_start..strtab_end)?; + Ok((Some(shdrs), Some(StringTable::new(strtab_buf)))) + } + + /// Parse section headers until one is found with the given name + /// + /// Example to get the ELF file's ABI-tag note + /// ``` + /// use elf::ElfBytes; + /// use elf::endian::AnyEndian; + /// use elf::section::SectionHeader; + /// use elf::note::Note; + /// use elf::note::NoteGnuAbiTag; + /// + /// let path = std::path::PathBuf::from("sample-objects/basic.x86_64"); + /// let file_data = std::fs::read(path).unwrap(); + /// let slice = file_data.as_slice(); + /// let file = ElfBytes::::minimal_parse(slice).unwrap(); + /// + /// let shdr: SectionHeader = file + /// .section_header_by_name(".note.ABI-tag") + /// .expect("section table should be parseable") + /// .expect("file should have a .note.ABI-tag section"); + /// + /// let notes: Vec<_> = file + /// .section_data_as_notes(&shdr) + /// .expect("Should be able to get note section data") + /// .collect(); + /// assert_eq!( + /// notes[0], + /// Note::GnuAbiTag(NoteGnuAbiTag { + /// os: 0, + /// major: 2, + /// minor: 6, + /// subminor: 32 + /// })); + /// ``` + pub fn section_header_by_name(&self, name: &str) -> Result, ParseError> { + let (shdrs, strtab) = match self.section_headers_with_strtab()? { + (Some(shdrs), Some(strtab)) => (shdrs, strtab), + _ => { + // If we don't have shdrs, or don't have a strtab, we can't find a section by its name + return Ok(None); + } + }; + + Ok(shdrs.iter().find(|shdr| { + let sh_name = match strtab.get(shdr.sh_name as usize) { + Ok(name) => name, + _ => { + return false; + } + }; + name == sh_name + })) + } + + /// Efficiently locate the set of common sections found in ELF files by doing a single iteration + /// over the SectionHeaders table. + /// + /// This is useful for those who know they're going to be accessing multiple common sections, like + /// symbol tables, string tables. Many of these can also be accessed by the more targeted + /// helpers like [ElfBytes::symbol_table] or [ElfBytes::dynamic], though those each do their own + /// internal searches through the shdrs to find the section. + pub fn find_common_data(&self) -> Result, ParseError> { + let mut result: CommonElfData<'data, E> = CommonElfData::default(); + + // Iterate once over the shdrs to collect up any known sections + if let Some(shdrs) = self.shdrs { + for shdr in shdrs.iter() { + match shdr.sh_type { + abi::SHT_SYMTAB => { + let strtab_shdr = shdrs.get(shdr.sh_link as usize)?; + let (symtab, strtab) = + self.section_data_as_symbol_table(&shdr, &strtab_shdr)?; + + result.symtab = Some(symtab); + result.symtab_strs = Some(strtab); + } + abi::SHT_DYNSYM => { + let strtab_shdr = shdrs.get(shdr.sh_link as usize)?; + let (symtab, strtab) = + self.section_data_as_symbol_table(&shdr, &strtab_shdr)?; + + result.dynsyms = Some(symtab); + result.dynsyms_strs = Some(strtab); + } + abi::SHT_DYNAMIC => { + result.dynamic = Some(self.section_data_as_dynamic(&shdr)?); + } + abi::SHT_HASH => { + let (start, end) = shdr.get_data_range()?; + let buf = self.data.get_bytes(start..end)?; + result.sysv_hash = Some(SysVHashTable::new( + self.ehdr.endianness, + self.ehdr.class, + buf, + )?); + } + abi::SHT_GNU_HASH => { + let (start, end) = shdr.get_data_range()?; + let buf = self.data.get_bytes(start..end)?; + result.gnu_hash = Some(GnuHashTable::new( + self.ehdr.endianness, + self.ehdr.class, + buf, + )?); + } + _ => { + continue; + } + } + } + } + + // If we didn't find SHT_DYNAMIC from the section headers, try the program headers + if result.dynamic.is_none() { + if let Some(phdrs) = self.phdrs { + if let Some(dyn_phdr) = phdrs.iter().find(|phdr| phdr.p_type == abi::PT_DYNAMIC) { + let (start, end) = dyn_phdr.get_file_data_range()?; + let buf = self.data.get_bytes(start..end)?; + result.dynamic = Some(DynamicTable::new( + self.ehdr.endianness, + self.ehdr.class, + buf, + )); + } + } + } + + Ok(result) + } + + /// Get the section data for a given [SectionHeader], alongside an optional compression context. + /// + /// This library does not do any decompression for the user, but merely returns the raw compressed + /// section data if the section is compressed alongside its ELF compression structure describing the + /// compression algorithm used. + /// + /// Users who wish to work with compressed sections must pick their compression library of choice + /// and do the decompression themselves. The only two options supported by the ELF spec for section + /// compression are: [abi::ELFCOMPRESS_ZLIB] and [abi::ELFCOMPRESS_ZSTD]. + pub fn section_data( + &self, + shdr: &SectionHeader, + ) -> Result<(&'data [u8], Option), ParseError> { + if shdr.sh_type == abi::SHT_NOBITS { + return Ok((&[], None)); + } + + let (start, end) = shdr.get_data_range()?; + let buf = self.data.get_bytes(start..end)?; + + if shdr.sh_flags & abi::SHF_COMPRESSED as u64 == 0 { + Ok((buf, None)) + } else { + let mut offset = 0; + let chdr = CompressionHeader::parse_at( + self.ehdr.endianness, + self.ehdr.class, + &mut offset, + buf, + )?; + let compressed_buf = buf.get(offset..).ok_or(ParseError::SliceReadError(( + offset, + shdr.sh_size.try_into()?, + )))?; + Ok((compressed_buf, Some(chdr))) + } + } + + /// Get the section data for a given [SectionHeader], and interpret it as a [StringTable] + /// + /// Returns a ParseError if the section is not of type [abi::SHT_STRTAB] + pub fn section_data_as_strtab( + &self, + shdr: &SectionHeader, + ) -> Result, ParseError> { + if shdr.sh_type != abi::SHT_STRTAB { + return Err(ParseError::UnexpectedSectionType(( + shdr.sh_type, + abi::SHT_STRTAB, + ))); + } + + let (buf, _) = self.section_data(shdr)?; + Ok(StringTable::new(buf)) + } + + /// Get the section data for a given [SectionHeader], and interpret it as an + /// iterator over no-addend relocations [Rel](crate::relocation::Rel) + /// + /// Returns a ParseError if the section is not of type [abi::SHT_REL] + pub fn section_data_as_rels( + &self, + shdr: &SectionHeader, + ) -> Result, ParseError> { + if shdr.sh_type != abi::SHT_REL { + return Err(ParseError::UnexpectedSectionType(( + shdr.sh_type, + abi::SHT_REL, + ))); + } + + let (buf, _) = self.section_data(shdr)?; + Ok(RelIterator::new(self.ehdr.endianness, self.ehdr.class, buf)) + } + + /// Get the section data for a given [SectionHeader], and interpret it as an + /// iterator over relocations with addends [Rela](crate::relocation::Rela) + /// + /// Returns a ParseError if the section is not of type [abi::SHT_RELA] + pub fn section_data_as_relas( + &self, + shdr: &SectionHeader, + ) -> Result, ParseError> { + if shdr.sh_type != abi::SHT_RELA { + return Err(ParseError::UnexpectedSectionType(( + shdr.sh_type, + abi::SHT_RELA, + ))); + } + + let (buf, _) = self.section_data(shdr)?; + Ok(RelaIterator::new( + self.ehdr.endianness, + self.ehdr.class, + buf, + )) + } + + /// Get the section data for a given [SectionHeader], and interpret it as an + /// iterator over [Note](crate::note::Note)s + /// + /// Returns a ParseError if the section is not of type [abi::SHT_NOTE] + pub fn section_data_as_notes( + &self, + shdr: &SectionHeader, + ) -> Result, ParseError> { + if shdr.sh_type != abi::SHT_NOTE { + return Err(ParseError::UnexpectedSectionType(( + shdr.sh_type, + abi::SHT_NOTE, + ))); + } + + let (buf, _) = self.section_data(shdr)?; + Ok(NoteIterator::new( + self.ehdr.endianness, + self.ehdr.class, + shdr.sh_addralign as usize, + buf, + )) + } + + /// Internal helper to get the section data for an SHT_DYNAMIC section as a .dynamic section table. + /// See [ElfBytes::dynamic] or [ElfBytes::find_common_data] for the public interface + fn section_data_as_dynamic( + &self, + shdr: &SectionHeader, + ) -> Result, ParseError> { + if shdr.sh_type != abi::SHT_DYNAMIC { + return Err(ParseError::UnexpectedSectionType(( + shdr.sh_type, + abi::SHT_DYNAMIC, + ))); + } + + // Validate entsize before trying to read the table so that we can error early for corrupted files + Dyn::validate_entsize(self.ehdr.class, shdr.sh_entsize.try_into()?)?; + let (buf, _) = self.section_data(shdr)?; + Ok(DynamicTable::new( + self.ehdr.endianness, + self.ehdr.class, + buf, + )) + } + + /// Get the segment's file data for a given segment/[ProgramHeader]. + /// + /// This is the segment's data as found in the file. + pub fn segment_data(&self, phdr: &ProgramHeader) -> Result<&'data [u8], ParseError> { + let (start, end) = phdr.get_file_data_range()?; + self.data.get_bytes(start..end) + } + + /// Get the segment's file data for a given [ProgramHeader], and interpret it as an + /// iterator over [Note](crate::note::Note)s + /// + /// Returns a ParseError if the section is not of type [abi::PT_NOTE] + pub fn segment_data_as_notes( + &self, + phdr: &ProgramHeader, + ) -> Result, ParseError> { + if phdr.p_type != abi::PT_NOTE { + return Err(ParseError::UnexpectedSegmentType(( + phdr.p_type, + abi::PT_NOTE, + ))); + } + + let buf = self.segment_data(phdr)?; + Ok(NoteIterator::new( + self.ehdr.endianness, + self.ehdr.class, + phdr.p_align as usize, + buf, + )) + } + + /// Get the .dynamic section or [abi::PT_DYNAMIC] segment contents. + pub fn dynamic(&self) -> Result>, ParseError> { + // If we have section headers, look for the SHT_DYNAMIC section + if let Some(shdrs) = self.section_headers() { + if let Some(shdr) = shdrs.iter().find(|shdr| shdr.sh_type == abi::SHT_DYNAMIC) { + return Ok(Some(self.section_data_as_dynamic(&shdr)?)); + } + // Otherwise, look up the PT_DYNAMIC segment (if any) + } else if let Some(phdrs) = self.segments() { + if let Some(phdr) = phdrs.iter().find(|phdr| phdr.p_type == abi::PT_DYNAMIC) { + let (start, end) = phdr.get_file_data_range()?; + let buf = self.data.get_bytes(start..end)?; + return Ok(Some(DynamicTable::new( + self.ehdr.endianness, + self.ehdr.class, + buf, + ))); + } + } + + Ok(None) + } + + /// Helper method to get the section data for a given pair of [SectionHeader] for the symbol + /// table and its linked strtab, and interpret them as [SymbolTable] and [StringTable]. + fn section_data_as_symbol_table( + &self, + shdr: &SectionHeader, + strtab_shdr: &SectionHeader, + ) -> Result<(SymbolTable<'data, E>, StringTable<'data>), ParseError> { + // Validate entsize before trying to read the table so that we can error early for corrupted files + Symbol::validate_entsize(self.ehdr.class, shdr.sh_entsize.try_into()?)?; + + // Load the section bytes for the symtab + // (we want immutable references to both the symtab and its strtab concurrently) + let (symtab_start, symtab_end) = shdr.get_data_range()?; + let symtab_buf = self.data.get_bytes(symtab_start..symtab_end)?; + + // Load the section bytes for the strtab + // (we want immutable references to both the symtab and its strtab concurrently) + let (strtab_start, strtab_end) = strtab_shdr.get_data_range()?; + let strtab_buf = self.data.get_bytes(strtab_start..strtab_end)?; + + let symtab = SymbolTable::new(self.ehdr.endianness, self.ehdr.class, symtab_buf); + let strtab = StringTable::new(strtab_buf); + Ok((symtab, strtab)) + } + + /// Get the ELF file's `.symtab` and associated strtab (if any) + pub fn symbol_table( + &self, + ) -> Result, StringTable<'data>)>, ParseError> { + let shdrs = match self.section_headers() { + Some(shdrs) => shdrs, + None => { + return Ok(None); + } + }; + + // Get the symtab header for the symtab. The GABI states there can be zero or one per ELF file. + let symtab_shdr = match shdrs.iter().find(|shdr| shdr.sh_type == abi::SHT_SYMTAB) { + Some(shdr) => shdr, + None => { + return Ok(None); + } + }; + + let strtab_shdr = shdrs.get(symtab_shdr.sh_link as usize)?; + Ok(Some(self.section_data_as_symbol_table( + &symtab_shdr, + &strtab_shdr, + )?)) + } + + /// Get the ELF file's `.dynsym` and associated strtab (if any) + pub fn dynamic_symbol_table( + &self, + ) -> Result, StringTable<'data>)>, ParseError> { + let shdrs = match self.section_headers() { + Some(shdrs) => shdrs, + None => { + return Ok(None); + } + }; + + // Get the symtab header for the symtab. The GABI states there can be zero or one per ELF file. + let symtab_shdr = match shdrs.iter().find(|shdr| shdr.sh_type == abi::SHT_DYNSYM) { + Some(shdr) => shdr, + None => { + return Ok(None); + } + }; + + let strtab_shdr = shdrs.get(symtab_shdr.sh_link as usize)?; + Ok(Some(self.section_data_as_symbol_table( + &symtab_shdr, + &strtab_shdr, + )?)) + } + + /// Locate the section data for the various GNU Symbol Versioning sections (if any) + /// and return them in a [SymbolVersionTable] that which can interpret them in-place to + /// yield [SymbolRequirement](crate::gnu_symver::SymbolRequirement)s + /// and [SymbolDefinition](crate::gnu_symver::SymbolDefinition)s + /// + /// This is a GNU extension and not all objects use symbol versioning. + /// Returns an empty Option if the object does not use symbol versioning. + pub fn symbol_version_table(&self) -> Result>, ParseError> { + // No sections means no GNU symbol versioning sections, which is ok + let shdrs = match self.section_headers() { + Some(shdrs) => shdrs, + None => { + return Ok(None); + } + }; + + let mut versym_opt: Option = None; + let mut needs_opt: Option = None; + let mut defs_opt: Option = None; + // Find the GNU Symbol versioning sections (if any) + for shdr in shdrs.iter() { + if shdr.sh_type == abi::SHT_GNU_VERSYM { + versym_opt = Some(shdr); + } else if shdr.sh_type == abi::SHT_GNU_VERNEED { + needs_opt = Some(shdr); + } else if shdr.sh_type == abi::SHT_GNU_VERDEF { + defs_opt = Some(shdr); + } + + // If we've found all three sections, then we're done + if versym_opt.is_some() && needs_opt.is_some() && defs_opt.is_some() { + break; + } + } + + let versym_shdr = match versym_opt { + Some(shdr) => shdr, + // No VERSYM section means the object doesn't use symbol versioning, which is ok. + None => { + return Ok(None); + } + }; + + // Load the versym table + // Validate VERSYM entsize before trying to read the table so that we can error early for corrupted files + VersionIndex::validate_entsize(self.ehdr.class, versym_shdr.sh_entsize.try_into()?)?; + let (versym_start, versym_end) = versym_shdr.get_data_range()?; + let version_ids = VersionIndexTable::new( + self.ehdr.endianness, + self.ehdr.class, + self.data.get_bytes(versym_start..versym_end)?, + ); + + // Wrap the VERNEED section and strings data in an iterator and string table (if any) + let verneeds = match needs_opt { + Some(shdr) => { + let (start, end) = shdr.get_data_range()?; + let needs_buf = self.data.get_bytes(start..end)?; + + let strs_shdr = shdrs.get(shdr.sh_link as usize)?; + let (strs_start, strs_end) = strs_shdr.get_data_range()?; + let strs_buf = self.data.get_bytes(strs_start..strs_end)?; + + Some(( + VerNeedIterator::new( + self.ehdr.endianness, + self.ehdr.class, + shdr.sh_info as u64, + 0, + needs_buf, + ), + StringTable::new(strs_buf), + )) + } + // It's possible to have symbol versioning with no NEEDs if we're an object that only + // exports defined symbols. + None => None, + }; + + // Wrap the VERDEF section and strings data in an iterator and string table (if any) + let verdefs = match defs_opt { + Some(shdr) => { + let (start, end) = shdr.get_data_range()?; + let defs_buf = self.data.get_bytes(start..end)?; + + let strs_shdr = shdrs.get(shdr.sh_link as usize)?; + let (strs_start, strs_end) = strs_shdr.get_data_range()?; + let strs_buf = self.data.get_bytes(strs_start..strs_end)?; + + Some(( + VerDefIterator::new( + self.ehdr.endianness, + self.ehdr.class, + shdr.sh_info as u64, + 0, + defs_buf, + ), + StringTable::new(strs_buf), + )) + } + // It's possible to have symbol versioning with no NEEDs if we're an object that only + // exports defined symbols. + None => None, + }; + + // whew, we're done here! + Ok(Some(SymbolVersionTable::new( + version_ids, + verneeds, + verdefs, + ))) + } +} + +// _ _ +// | |_ ___ ___| |_ ___ +// | __/ _ \/ __| __/ __| +// | || __/\__ \ |_\__ \ +// \__\___||___/\__|___/ +// + +#[cfg(test)] +mod interface_tests { + use super::*; + use crate::abi::{SHT_GNU_HASH, SHT_NOBITS, SHT_NOTE, SHT_NULL, SHT_REL, SHT_RELA, SHT_STRTAB}; + use crate::endian::AnyEndian; + use crate::hash::sysv_hash; + use crate::note::{Note, NoteGnuAbiTag, NoteGnuBuildId}; + use crate::relocation::Rela; + + #[test] + fn simultaenous_segments_parsing() { + let path = std::path::PathBuf::from("sample-objects/basic.x86_64"); + let file_data = std::fs::read(path).expect("Could not read file."); + let slice = file_data.as_slice(); + let file = ElfBytes::::minimal_parse(slice).expect("Open test1"); + + // With the bytes interface, we should be able to get multiple lazy-parsing types concurrently, + // since the trait is implemented for shared references. + // + // Get the segment table + let iter = file.segments().expect("File should have a segment table"); + + // Concurrently get the segment table again as an iterator and collect the headers into a vec + let segments: Vec = file + .segments() + .expect("File should have a segment table") + .iter() + .collect(); + + let expected_phdr = ProgramHeader { + p_type: abi::PT_PHDR, + p_offset: 64, + p_vaddr: 4194368, + p_paddr: 4194368, + p_filesz: 448, + p_memsz: 448, + p_flags: 5, + p_align: 8, + }; + + // Assert we parsed the first header correctly + assert_eq!(segments[0], expected_phdr); + + // Now use the original lazy-parsing table to parse out the first entry + assert_eq!( + iter.get(0).expect("should be able to parse phdr"), + expected_phdr + ) + } + + #[test] + fn segments() { + let path = std::path::PathBuf::from("sample-objects/basic.x86_64"); + let file_data = std::fs::read(path).expect("Could not read file."); + let slice = file_data.as_slice(); + let file = ElfBytes::::minimal_parse(slice).expect("Open test1"); + + let segments: Vec = file + .segments() + .expect("File should have a segment table") + .iter() + .collect(); + assert_eq!( + segments[0], + ProgramHeader { + p_type: abi::PT_PHDR, + p_offset: 64, + p_vaddr: 4194368, + p_paddr: 4194368, + p_filesz: 448, + p_memsz: 448, + p_flags: 5, + p_align: 8, + } + ); + } + + #[test] + fn segments_phnum_in_shdr0() { + let path = std::path::PathBuf::from("sample-objects/phnum.m68k.so"); + let file_data = std::fs::read(path).expect("Could not read file."); + let slice = file_data.as_slice(); + let file = ElfBytes::::minimal_parse(slice).expect("Open test1"); + + let segments: Vec = file + .segments() + .expect("File should have a segment table") + .iter() + .collect(); + assert_eq!( + segments[0], + ProgramHeader { + p_type: abi::PT_PHDR, + p_offset: 92, + p_vaddr: 0, + p_paddr: 0, + p_filesz: 32, + p_memsz: 32, + p_flags: 0x20003, + p_align: 0x40000, + } + ); + } + + #[test] + fn section_headers() { + let path = std::path::PathBuf::from("sample-objects/basic.x86_64"); + let file_data = std::fs::read(path).expect("Could not read file."); + let slice = file_data.as_slice(); + let file = ElfBytes::::minimal_parse(slice).expect("Open test1"); + + let shdrs = file + .section_headers() + .expect("File should have a section table"); + + let shdrs_vec: Vec = shdrs.iter().collect(); + + assert_eq!(shdrs_vec[4].sh_type, SHT_GNU_HASH); + } + + #[test] + fn section_headers_with_strtab() { + let path = std::path::PathBuf::from("sample-objects/basic.x86_64"); + let file_data = std::fs::read(path).expect("Could not read file."); + let slice = file_data.as_slice(); + let file = ElfBytes::::minimal_parse(slice).expect("Open test1"); + + let (shdrs, strtab) = file + .section_headers_with_strtab() + .expect("shdrs should be parsable"); + let (shdrs, strtab) = (shdrs.unwrap(), strtab.unwrap()); + + let with_names: Vec<(&str, SectionHeader)> = shdrs + .iter() + .map(|shdr| { + ( + strtab + .get(shdr.sh_name as usize) + .expect("Failed to get section name"), + shdr, + ) + }) + .collect(); + + let (name, shdr) = with_names[4]; + assert_eq!(name, ".gnu.hash"); + assert_eq!(shdr.sh_type, abi::SHT_GNU_HASH); + } + + #[test] + fn shnum_and_shstrndx_in_shdr0() { + let path = std::path::PathBuf::from("sample-objects/shnum.x86_64"); + let file_data = std::fs::read(path).expect("Could not read file."); + let slice = file_data.as_slice(); + let file = ElfBytes::::minimal_parse(slice).unwrap(); + + let (shdrs, strtab) = file + .section_headers_with_strtab() + .expect("shdrs should be parsable"); + let (shdrs, strtab) = (shdrs.unwrap(), strtab.unwrap()); + + let shdrs_len = shdrs.len(); + assert_eq!(shdrs_len, 0xFF15); + + let shdr = shdrs.get(shdrs_len - 1).unwrap(); + let name = strtab + .get(shdr.sh_name as usize) + .expect("Failed to get section name"); + + assert_eq!(name, ".shstrtab"); + assert_eq!(shdr.sh_type, abi::SHT_STRTAB); + } + + #[test] + fn section_header_by_name() { + let path = std::path::PathBuf::from("sample-objects/basic.x86_64"); + let file_data = std::fs::read(path).expect("Could not read file."); + let slice = file_data.as_slice(); + let file = ElfBytes::::minimal_parse(slice).expect("Open test1"); + + let shdr = file + .section_header_by_name(".gnu.hash") + .expect("section table should be parseable") + .expect("file should have .gnu.hash section"); + + assert_eq!(shdr.sh_type, SHT_GNU_HASH); + + let shdr = file + .section_header_by_name(".not.found") + .expect("section table should be parseable"); + + assert_eq!(shdr, None); + } + + #[test] + fn find_common_data() { + let path = std::path::PathBuf::from("sample-objects/symver.x86_64.so"); + let file_data = std::fs::read(path).expect("Could not read file."); + let slice = file_data.as_slice(); + let file = ElfBytes::::minimal_parse(slice).expect("Open test1"); + + let elf_scns = file.find_common_data().expect("file should parse"); + + // hello.so should find everything + assert!(elf_scns.symtab.is_some()); + assert!(elf_scns.symtab_strs.is_some()); + assert!(elf_scns.dynsyms.is_some()); + assert!(elf_scns.dynsyms_strs.is_some()); + assert!(elf_scns.dynamic.is_some()); + assert!(elf_scns.sysv_hash.is_some()); + assert!(elf_scns.gnu_hash.is_some()); + } + + #[test] + fn section_data() { + let path = std::path::PathBuf::from("sample-objects/basic.x86_64"); + let file_data = std::fs::read(path).expect("Could not read file."); + let slice = file_data.as_slice(); + let file = ElfBytes::::minimal_parse(slice).expect("Open test1"); + + let shdr = file + .section_headers() + .expect("File should have section table") + .get(26) + .expect("shdr should be parsable"); + + assert_eq!(shdr.sh_type, SHT_NOBITS); + + let (data, chdr) = file + .section_data(&shdr) + .expect("Failed to get section data"); + + assert_eq!(chdr, None); + assert_eq!(data, &[]); + } + + // Test all the different section_data_as* with a section of the wrong type + #[test] + fn section_data_as_wrong_type() { + let path = std::path::PathBuf::from("sample-objects/basic.x86_64"); + let file_data = std::fs::read(path).expect("Could not read file."); + let slice = file_data.as_slice(); + let file = ElfBytes::::minimal_parse(slice).expect("Open test1"); + + // Section 0 is SHT_NULL, so all of the section_data_as* should error on it + let shdr = file + .section_headers() + .expect("File should have section table") + .get(0) + .expect("shdr should be parsable"); + + let err = file + .section_data_as_strtab(&shdr) + .expect_err("shdr0 should be the wrong type"); + assert!( + matches!( + err, + ParseError::UnexpectedSectionType((SHT_NULL, SHT_STRTAB)) + ), + "Unexpected Error type found: {err}" + ); + + let err = file + .section_data_as_rels(&shdr) + .expect_err("shdr0 should be the wrong type"); + assert!( + matches!(err, ParseError::UnexpectedSectionType((SHT_NULL, SHT_REL))), + "Unexpected Error type found: {err}" + ); + + let err = file + .section_data_as_relas(&shdr) + .expect_err("shdr0 should be the wrong type"); + assert!( + matches!(err, ParseError::UnexpectedSectionType((SHT_NULL, SHT_RELA))), + "Unexpected Error type found: {err}" + ); + + let err = file + .section_data_as_notes(&shdr) + .expect_err("shdr0 should be the wrong type"); + assert!( + matches!(err, ParseError::UnexpectedSectionType((SHT_NULL, SHT_NOTE))), + "Unexpected Error type found: {err}" + ); + } + + #[test] + fn section_data_as_strtab() { + let path = std::path::PathBuf::from("sample-objects/basic.x86_64"); + let file_data = std::fs::read(path).expect("Could not read file."); + let slice = file_data.as_slice(); + let file = ElfBytes::::minimal_parse(slice).expect("Open test1"); + + let shdr = file + .section_headers() + .expect("File should have section table") + .get(file.ehdr.e_shstrndx as usize) + .expect("shdr should be parsable"); + + let strtab = file + .section_data_as_strtab(&shdr) + .expect("Failed to read strtab"); + + assert_eq!( + strtab.get(1).expect("Failed to get strtab entry"), + ".symtab" + ); + } + + #[test] + fn section_data_as_relas() { + let path = std::path::PathBuf::from("sample-objects/basic.x86_64"); + let file_data = std::fs::read(path).expect("Could not read file."); + let slice = file_data.as_slice(); + let file = ElfBytes::::minimal_parse(slice).expect("Open test1"); + + let shdr = file + .section_headers() + .expect("File should have section table") + .get(10) + .expect("Failed to get rela shdr"); + + let mut relas = file + .section_data_as_relas(&shdr) + .expect("Failed to read relas section"); + assert_eq!( + relas.next().expect("Failed to get rela entry"), + Rela { + r_offset: 6293704, + r_sym: 1, + r_type: 7, + r_addend: 0, + } + ); + assert_eq!( + relas.next().expect("Failed to get rela entry"), + Rela { + r_offset: 6293712, + r_sym: 2, + r_type: 7, + r_addend: 0, + } + ); + assert!(relas.next().is_none()); + } + + #[test] + fn section_data_as_notes() { + let path = std::path::PathBuf::from("sample-objects/basic.x86_64"); + let file_data = std::fs::read(path).expect("Could not read file."); + let slice = file_data.as_slice(); + let file = ElfBytes::::minimal_parse(slice).expect("Open test1"); + + let shdr = file + .section_headers() + .expect("File should have section table") + .get(2) + .expect("Failed to get note shdr"); + + let mut notes = file + .section_data_as_notes(&shdr) + .expect("Failed to read note section"); + assert_eq!( + notes.next().expect("Failed to get first note"), + Note::GnuAbiTag(NoteGnuAbiTag { + os: 0, + major: 2, + minor: 6, + subminor: 32 + }) + ); + assert!(notes.next().is_none()); + } + + #[test] + fn segment_data_as_notes() { + let path = std::path::PathBuf::from("sample-objects/basic.x86_64"); + let file_data = std::fs::read(path).expect("Could not read file."); + let slice = file_data.as_slice(); + let file = ElfBytes::::minimal_parse(slice).expect("Open test1"); + + let phdr = file + .segments() + .expect("File should have segmetn table") + .get(5) + .expect("Failed to get notes phdr"); + + let mut notes = file + .segment_data_as_notes(&phdr) + .expect("Failed to read notes segment"); + assert_eq!( + notes.next().expect("Failed to get first note"), + Note::GnuAbiTag(NoteGnuAbiTag { + os: 0, + major: 2, + minor: 6, + subminor: 32 + }) + ); + assert_eq!( + notes.next().expect("Failed to get second note"), + Note::GnuBuildId(NoteGnuBuildId(&[ + 119, 65, 159, 13, 165, 16, 131, 12, 87, 167, 200, 204, 176, 238, 133, 95, 238, 211, + 118, 163 + ])) + ); + assert!(notes.next().is_none()); + } + + #[test] + fn dynamic() { + let path = std::path::PathBuf::from("sample-objects/basic.x86_64"); + let file_data = std::fs::read(path).expect("Could not read file."); + let slice = file_data.as_slice(); + let file = ElfBytes::::minimal_parse(slice).expect("Open test1"); + + let mut dynamic = file + .dynamic() + .expect("Failed to parse .dynamic") + .expect("Failed to find .dynamic") + .iter(); + assert_eq!( + dynamic.next().expect("Failed to get dyn entry"), + Dyn { + d_tag: abi::DT_NEEDED, + d_un: 1 + } + ); + assert_eq!( + dynamic.next().expect("Failed to get dyn entry"), + Dyn { + d_tag: abi::DT_INIT, + d_un: 4195216 + } + ); + } + + #[test] + fn symbol_table() { + let path = std::path::PathBuf::from("sample-objects/basic.x86_64"); + let file_data = std::fs::read(path).expect("Could not read file."); + let slice = file_data.as_slice(); + let file = ElfBytes::::minimal_parse(slice).expect("Open test1"); + + let (symtab, strtab) = file + .symbol_table() + .expect("Failed to read symbol table") + .expect("Failed to find symbol table"); + let symbol = symtab.get(30).expect("Failed to get symbol"); + assert_eq!( + symbol, + Symbol { + st_name: 19, + st_value: 6293200, + st_size: 0, + st_shndx: 21, + st_info: 1, + st_other: 0, + } + ); + assert_eq!( + strtab + .get(symbol.st_name as usize) + .expect("Failed to get name from strtab"), + "__JCR_LIST__" + ); + } + + #[test] + fn dynamic_symbol_table() { + let path = std::path::PathBuf::from("sample-objects/basic.x86_64"); + let file_data = std::fs::read(path).expect("Could not read file."); + let slice = file_data.as_slice(); + let file = ElfBytes::::minimal_parse(slice).expect("Open test1"); + + let (symtab, strtab) = file + .dynamic_symbol_table() + .expect("Failed to read symbol table") + .expect("Failed to find symbol table"); + let symbol = symtab.get(1).expect("Failed to get symbol"); + assert_eq!( + symbol, + Symbol { + st_name: 11, + st_value: 0, + st_size: 0, + st_shndx: 0, + st_info: 18, + st_other: 0, + } + ); + assert_eq!( + strtab + .get(symbol.st_name as usize) + .expect("Failed to get name from strtab"), + "memset" + ); + } + + #[test] + fn symbol_version_table() { + let path = std::path::PathBuf::from("sample-objects/symver.x86_64.so"); + let file_data = std::fs::read(path).expect("Could not read file."); + let slice = file_data.as_slice(); + let file = ElfBytes::::minimal_parse(slice).expect("Open test1"); + + let vst = file + .symbol_version_table() + .expect("Failed to parse GNU symbol versions") + .expect("Failed to find GNU symbol versions"); + + let req = vst + .get_requirement(2) + .expect("Failed to parse NEED") + .expect("Failed to find NEED"); + assert_eq!(req.file, "libc.so.6"); + assert_eq!(req.name, "GLIBC_2.2.5"); + assert_eq!(req.hash, 0x9691A75); + + let req = vst.get_requirement(3).expect("Failed to parse NEED"); + assert!(req.is_none()); + + let req = vst.get_requirement(4).expect("Failed to parse NEED"); + assert!(req.is_none()); + + let req = vst + .get_requirement(5) + .expect("Failed to parse NEED") + .expect("Failed to find NEED"); + assert_eq!(req.file, "libc.so.6"); + assert_eq!(req.name, "GLIBC_2.2.5"); + assert_eq!(req.hash, 0x9691A75); + + let def = vst + .get_definition(3) + .expect("Failed to parse DEF") + .expect("Failed to find DEF"); + assert_eq!(def.hash, 0xC33237F); + assert_eq!(def.flags, 1); + assert!(!def.hidden); + let def_names: Vec<&str> = def.names.map(|res| res.expect("should parse")).collect(); + assert_eq!(def_names, &["hello.so"]); + + let def = vst + .get_definition(7) + .expect("Failed to parse DEF") + .expect("Failed to find DEF"); + assert_eq!(def.hash, 0x1570B62); + assert_eq!(def.flags, 0); + assert!(def.hidden); + let def_names: Vec<&str> = def.names.map(|res| res.expect("should parse")).collect(); + assert_eq!(def_names, &["HELLO_1.42"]); + } + + #[test] + fn sysv_hash_table() { + let path = std::path::PathBuf::from("sample-objects/symver.x86_64.so"); + let file_data = std::fs::read(path).expect("Could not read file."); + let slice = file_data.as_slice(); + let file = ElfBytes::::minimal_parse(slice).expect("Open test1"); + + // Look up the SysV hash section header + let common = file.find_common_data().expect("should parse"); + let hash_table = common.sysv_hash.expect("should have .hash section"); + + // Get the dynamic symbol table. + let (symtab, strtab) = file + .dynamic_symbol_table() + .expect("Failed to read symbol table") + .expect("Failed to find symbol table"); + + // Verify that these three symbols all collide in the hash table's buckets + assert_eq!(sysv_hash(b"use_memset_v2"), 0x8080542); + assert_eq!(sysv_hash(b"__gmon_start__"), 0xF4D007F); + assert_eq!(sysv_hash(b"memset"), 0x73C49C4); + assert_eq!(sysv_hash(b"use_memset_v2") % 3, 0); + assert_eq!(sysv_hash(b"__gmon_start__") % 3, 0); + assert_eq!(sysv_hash(b"memset") % 3, 0); + + // Use the hash table to find a given symbol in it. + let (sym_idx, sym) = hash_table + .find(b"memset", &symtab, &strtab) + .expect("Failed to parse hash") + .expect("Failed to find hash"); + + // Verify that we got the same symbol from the hash table we expected + assert_eq!(sym_idx, 2); + assert_eq!(strtab.get(sym.st_name as usize).unwrap(), "memset"); + assert_eq!( + sym, + symtab.get(sym_idx).expect("Failed to get expected sym") + ); + } + + #[test] + fn gnu_hash_table() { + let path = std::path::PathBuf::from("sample-objects/symver.x86_64.so"); + let file_data = std::fs::read(path).expect("Could not read file."); + let slice = file_data.as_slice(); + let file = ElfBytes::::minimal_parse(slice).unwrap(); + + // Look up the SysV hash section header + let common = file.find_common_data().unwrap(); + let hash_table = common.gnu_hash.expect("should have .gnu.hash section"); + + // Get the dynamic symbol table. + let (symtab, strtab) = (common.dynsyms.unwrap(), common.dynsyms_strs.unwrap()); + + // manually look one up by explicit name to make sure the above loop is doing something + let (sym_idx, sym) = hash_table + .find(b"use_memset", &symtab, &strtab) + .expect("Failed to parse hash") + .expect("Failed to find hash"); + + // Verify that we got the same symbol from the hash table we expected + assert_eq!(sym_idx, 9); + assert_eq!(strtab.get(sym.st_name as usize).unwrap(), "use_memset"); + assert_eq!( + sym, + symtab.get(sym_idx).expect("Failed to get expected sym") + ); + } +} + +#[cfg(test)] +mod arch_tests { + use super::*; + use crate::endian::AnyEndian; + + // Basic smoke test which parses out symbols and headers for a given sample object of a given architecture + macro_rules! arch_test { + ( $arch:expr, $e_machine:expr, $endian:expr) => {{ + let path_str = format!("sample-objects/symver.{}.so", $arch); + let path = std::path::PathBuf::from(path_str); + let file_data = std::fs::read(path).expect("file should exist"); + let slice = file_data.as_slice(); + let file = ElfBytes::::minimal_parse(slice).expect("should parse"); + + assert_eq!(file.ehdr.e_machine, $e_machine); + assert_eq!(file.ehdr.endianness, $endian); + + let (shdrs, strtab) = file.section_headers_with_strtab().expect("should parse"); + let (shdrs, strtab) = (shdrs.unwrap(), strtab.unwrap()); + let _: Vec<_> = shdrs + .iter() + .map(|shdr| { + ( + strtab.get(shdr.sh_name as usize).expect("should parse"), + shdr, + ) + }) + .collect(); + + let common = file.find_common_data().expect("should parse"); + + // parse out all the normal symbol table symbols with their names + { + let symtab = common.symtab.unwrap(); + let strtab = common.symtab_strs.unwrap(); + let _: Vec<_> = symtab + .iter() + .map(|sym| (strtab.get(sym.st_name as usize).expect("should parse"), sym)) + .collect(); + } + + // parse out all the dynamic symbols and look them up in the gnu hash table + { + let symtab = common.dynsyms.unwrap(); + let strtab = common.dynsyms_strs.unwrap(); + let symbols_with_names: Vec<_> = symtab + .iter() + .map(|sym| (strtab.get_raw(sym.st_name as usize).expect("should parse"), sym)) + .collect(); + + let hash_table = common.gnu_hash.unwrap(); + + // look up each entry that should be in the hash table and make sure its there + let start_idx = hash_table.hdr.table_start_idx as usize; + for sym_idx in 0..symtab.len() { + let (symbol_name, symbol) = symbols_with_names.get(sym_idx).unwrap(); + + let result = hash_table + .find(symbol_name, &symtab, &strtab) + .expect("Failed to parse hash"); + + if sym_idx < start_idx { + assert_eq!(result, None); + } else { + let (hash_sym_idx, hash_symbol) = result.unwrap(); + + // Verify that we got the same symbol from the hash table we expected + assert_eq!(sym_idx, hash_sym_idx); + assert_eq!( + strtab.get_raw(hash_symbol.st_name as usize).unwrap(), + *symbol_name + ); + assert_eq!(*symbol, hash_symbol); + } + } + } + + let phdrs = file.segments().unwrap(); + let note_phdrs: Vec<_> = phdrs + .iter() + .filter(|phdr| phdr.p_type == abi::PT_NOTE) + .collect(); + for phdr in note_phdrs { + let _: Vec<_> = file + .segment_data_as_notes(&phdr) + .expect("should parse") + .collect(); + } + }}; + } + + #[test] + fn x86_64() { + arch_test!("x86_64", abi::EM_X86_64, AnyEndian::Little); + } + + #[test] + fn m68k() { + arch_test!("m68k", abi::EM_68K, AnyEndian::Big); + } + + #[test] + fn aarch64() { + arch_test!("aarch64", abi::EM_AARCH64, AnyEndian::Little); + } + + #[test] + fn armhf() { + arch_test!("armhf", abi::EM_ARM, AnyEndian::Little); + } + + #[test] + fn powerpc64() { + arch_test!("powerpc64", abi::EM_PPC64, AnyEndian::Big); + } + + #[test] + fn powerpc64le() { + arch_test!("powerpc64le", abi::EM_PPC64, AnyEndian::Little); + } + + #[test] + fn riscv64() { + arch_test!("riscv64", abi::EM_RISCV, AnyEndian::Little); + } +} diff --git a/src/third_party/rust-elf/src/elf_stream.rs b/src/third_party/rust-elf/src/elf_stream.rs new file mode 100644 index 0000000..026c885 --- /dev/null +++ b/src/third_party/rust-elf/src/elf_stream.rs @@ -0,0 +1,1311 @@ +use core::ops::Range; +use std::collections::HashMap; +use std::io::{Read, Seek, SeekFrom}; + +use crate::abi; +use crate::compression::CompressionHeader; +use crate::dynamic::DynamicTable; +use crate::endian::EndianParse; +use crate::file::{parse_ident, Class}; +use crate::gnu_symver::{ + SymbolVersionTable, VerDefIterator, VerNeedIterator, VersionIndex, VersionIndexTable, +}; +use crate::note::NoteIterator; +use crate::parse::{ParseAt, ParseError}; +use crate::relocation::{RelIterator, RelaIterator}; +use crate::section::{SectionHeader, SectionHeaderTable}; +use crate::segment::ProgramHeader; +use crate::segment::SegmentTable; +use crate::string_table::StringTable; +use crate::symbol::{Symbol, SymbolTable}; + +use crate::file::FileHeader; + +/// This type encapsulates the stream-oriented interface for parsing ELF objects from +/// a `Read + Seek`. +#[derive(Debug)] +pub struct ElfStream { + pub ehdr: FileHeader, + shdrs: Vec, + phdrs: Vec, + reader: CachingReader, +} + +/// Read the stream bytes backing the section headers table and parse them all into their Rust native type. +/// +/// Returns a [ParseError] if the data bytes for the section table cannot be read. +/// i.e. if the ELF [FileHeader]'s e_shnum, e_shoff, e_shentsize are invalid and point +/// to a range in the file data that does not actually exist, or if any of the headers failed to parse. +fn parse_section_headers( + ehdr: &FileHeader, + reader: &mut CachingReader, +) -> Result, ParseError> { + // It's Ok to have no section headers + if ehdr.e_shoff == 0 { + return Ok(Vec::default()); + } + + // Validate shentsize before trying to read the table so that we can error early for corrupted files + let entsize = SectionHeader::validate_entsize(ehdr.class, ehdr.e_shentsize as usize)?; + + // If the number of sections is greater than or equal to SHN_LORESERVE (0xff00), + // e_shnum is zero and the actual number of section header table entries + // is contained in the sh_size field of the section header at index 0. + let shoff: usize = ehdr.e_shoff.try_into()?; + let mut shnum = ehdr.e_shnum as usize; + if shnum == 0 { + let end = shoff + .checked_add(entsize) + .ok_or(ParseError::IntegerOverflow)?; + let mut offset = 0; + let data = reader.read_bytes(shoff, end)?; + let shdr0 = SectionHeader::parse_at(ehdr.endianness, ehdr.class, &mut offset, data)?; + shnum = shdr0.sh_size.try_into()?; + } + + let size = entsize + .checked_mul(shnum) + .ok_or(ParseError::IntegerOverflow)?; + let end = shoff.checked_add(size).ok_or(ParseError::IntegerOverflow)?; + let buf = reader.read_bytes(shoff, end)?; + let shdr_vec = SectionHeaderTable::new(ehdr.endianness, ehdr.class, buf) + .iter() + .collect(); + Ok(shdr_vec) +} + +fn parse_program_headers( + ehdr: &FileHeader, + reader: &mut CachingReader, +) -> Result, ParseError> { + // It's Ok to have no program headers + if ehdr.e_phoff == 0 { + return Ok(Vec::default()); + } + + // If the number of segments is greater than or equal to PN_XNUM (0xffff), + // e_phnum is set to PN_XNUM, and the actual number of program header table + // entries is contained in the sh_info field of the section header at index 0. + let mut phnum = ehdr.e_phnum as usize; + if phnum == abi::PN_XNUM as usize { + let shoff: usize = ehdr.e_shoff.try_into()?; + let end = shoff + .checked_add(SectionHeader::size_for(ehdr.class)) + .ok_or(ParseError::IntegerOverflow)?; + let data = reader.read_bytes(shoff, end)?; + let mut offset = 0; + let shdr0 = SectionHeader::parse_at(ehdr.endianness, ehdr.class, &mut offset, data)?; + phnum = shdr0.sh_info.try_into()?; + } + + // Validate phentsize before trying to read the table so that we can error early for corrupted files + let entsize = ProgramHeader::validate_entsize(ehdr.class, ehdr.e_phentsize as usize)?; + + let phoff: usize = ehdr.e_phoff.try_into()?; + let size = entsize + .checked_mul(phnum) + .ok_or(ParseError::IntegerOverflow)?; + let end = phoff.checked_add(size).ok_or(ParseError::IntegerOverflow)?; + let buf = reader.read_bytes(phoff, end)?; + let phdrs_vec = SegmentTable::new(ehdr.endianness, ehdr.class, buf) + .iter() + .collect(); + Ok(phdrs_vec) +} + +impl ElfStream { + /// Do a minimal amount of parsing work to open an [ElfStream] handle from a Read+Seek containing an ELF object. + /// + /// This parses the ELF [FileHeader], [SectionHeader] table, and [ProgramHeader] (segments) table. + /// All other file data (section data, segment data) is left unread and unparsed. + pub fn open_stream(reader: S) -> Result, ParseError> { + let mut cr = CachingReader::new(reader)?; + let ident_buf = cr.read_bytes(0, abi::EI_NIDENT)?; + let ident = parse_ident(ident_buf)?; + + let tail_start = abi::EI_NIDENT; + let tail_end = match ident.1 { + Class::ELF32 => tail_start + crate::file::ELF32_EHDR_TAILSIZE, + Class::ELF64 => tail_start + crate::file::ELF64_EHDR_TAILSIZE, + }; + let tail_buf = cr.read_bytes(tail_start, tail_end)?; + + let ehdr = FileHeader::parse_tail(ident, tail_buf)?; + + let shdrs = parse_section_headers(&ehdr, &mut cr)?; + let phdrs = parse_program_headers(&ehdr, &mut cr)?; + + // We parsed out the ehdr and shdrs into their own allocated containers, so there's no need to keep + // around their backing data anymore. + cr.clear_cache(); + + Ok(ElfStream { + ehdr, + shdrs, + phdrs, + reader: cr, + }) + } + + /// Get the parsed section headers table + pub fn segments(&self) -> &Vec { + &self.phdrs + } + + /// Get the parsed section headers table + pub fn section_headers(&self) -> &Vec { + &self.shdrs + } + + /// Get an lazy-parsing table for the Section Headers in the file and its associated StringTable. + /// + /// The underlying ELF bytes backing the section headers table and string + /// table are read all at once when the table is requested, but parsing is + /// deferred to be lazily parsed on demand on each table.get(), strtab.get(), or + /// table.iter().next() call. + /// + /// Returns a [ParseError] if the data bytes for these tables cannot be + /// read i.e. if the ELF [FileHeader]'s + /// [e_shnum](FileHeader#structfield.e_shnum), + /// [e_shoff](FileHeader#structfield.e_shoff), + /// [e_shentsize](FileHeader#structfield.e_shentsize), + /// [e_shstrndx](FileHeader#structfield.e_shstrndx) are invalid and point + /// to a ranges in the file data that does not actually exist. + pub fn section_headers_with_strtab( + &mut self, + ) -> Result<(&Vec, Option>), ParseError> { + // It's Ok to have no section headers + if self.shdrs.is_empty() { + return Ok((&self.shdrs, None)); + } + + // It's Ok to not have a string table + if self.ehdr.e_shstrndx == abi::SHN_UNDEF { + return Ok((&self.shdrs, None)); + } + + // If the section name string table section index is greater than or + // equal to SHN_LORESERVE (0xff00), e_shstrndx has the value SHN_XINDEX + // (0xffff) and the actual index of the section name string table section + // is contained in the sh_link field of the section header at index 0. + let mut shstrndx = self.ehdr.e_shstrndx as usize; + if self.ehdr.e_shstrndx == abi::SHN_XINDEX { + shstrndx = self.shdrs[0].sh_link as usize; + } + + // We have a strtab, so wrap it in a zero-copy StringTable + let strtab = self + .shdrs + .get(shstrndx) + .ok_or(ParseError::BadOffset(shstrndx as u64))?; + let (strtab_start, strtab_end) = strtab.get_data_range()?; + let strtab_buf = self.reader.read_bytes(strtab_start, strtab_end)?; + let strtab = StringTable::new(strtab_buf); + Ok((&self.shdrs, Some(strtab))) + } + + /// Find the parsed section header with the given name (if any). + /// + /// Returns a ParseError if the section headers string table can't be read + /// + /// Example to get the ELF file's ABI-tag note + /// ``` + /// use elf::ElfStream; + /// use elf::endian::AnyEndian; + /// use elf::section::SectionHeader; + /// use elf::note::Note; + /// use elf::note::NoteGnuAbiTag; + /// + /// let path = std::path::PathBuf::from("sample-objects/basic.x86_64"); + /// let io = std::fs::File::open(path).expect("Could not open file."); + /// let mut file = ElfStream::::open_stream(io).expect("Open test1"); + /// + /// let shdr: SectionHeader = *file + /// .section_header_by_name(".note.ABI-tag") + /// .expect("section table should be parseable") + /// .expect("file should have a .note.ABI-tag section"); + /// + /// let notes: Vec<_> = file + /// .section_data_as_notes(&shdr) + /// .expect("Should be able to get note section data") + /// .collect(); + /// assert_eq!( + /// notes[0], + /// Note::GnuAbiTag(NoteGnuAbiTag { + /// os: 0, + /// major: 2, + /// minor: 6, + /// subminor: 32 + /// })); + /// ``` + pub fn section_header_by_name( + &mut self, + name: &str, + ) -> Result, ParseError> { + let (shdrs, strtab) = match self.section_headers_with_strtab()? { + (shdr, Some(strtab)) => (shdr, strtab), + // We can't look up shdrs by name if there's no strtab. + // (hint: try looking it up by its sh_type). + _ => { + return Ok(None); + } + }; + + Ok(shdrs.iter().find(|shdr| { + let sh_name = match strtab.get(shdr.sh_name as usize) { + Ok(name) => name, + _ => { + return false; + } + }; + name == sh_name + })) + } + + /// Read the section data for the given [SectionHeader](SectionHeader). + /// Returns both the secion data and an optional CompressionHeader. + /// + /// No compression header signals that the section contents are uncompressed and can be used as-is. + /// + /// Some(chdr) signals that the section contents are compressed and need to be uncompressed via the + /// compression algorithm described in [ch_type](CompressionHeader#structfield.ch_type). + /// The returned buffer represents the compressed section bytes as found in the file, without the + /// CompressionHeader. + /// + /// It is up to the user to perform the decompression themselves with the compression library of + /// their choosing. + /// + /// SHT_NOBITS sections yield an empty slice. + pub fn section_data( + &mut self, + shdr: &SectionHeader, + ) -> Result<(&[u8], Option), ParseError> { + if shdr.sh_type == abi::SHT_NOBITS { + return Ok((&[], None)); + } + + let (start, end) = shdr.get_data_range()?; + let buf = self.reader.read_bytes(start, end)?; + + if shdr.sh_flags & abi::SHF_COMPRESSED as u64 == 0 { + Ok((buf, None)) + } else { + let mut offset = 0; + let chdr = CompressionHeader::parse_at( + self.ehdr.endianness, + self.ehdr.class, + &mut offset, + buf, + )?; + let compressed_buf = buf.get(offset..).ok_or(ParseError::SliceReadError(( + offset, + shdr.sh_size.try_into()?, + )))?; + Ok((compressed_buf, Some(chdr))) + } + } + + /// Read the section data for the given + /// [SectionHeader](SectionHeader) and interpret it in-place as a + /// [StringTable](StringTable). + /// + /// Returns a [ParseError] if the + /// [sh_type](SectionHeader#structfield.sh_type) is not + /// [SHT_STRTAB](abi::SHT_STRTAB). + pub fn section_data_as_strtab( + &mut self, + shdr: &SectionHeader, + ) -> Result, ParseError> { + if shdr.sh_type != abi::SHT_STRTAB { + return Err(ParseError::UnexpectedSectionType(( + shdr.sh_type, + abi::SHT_STRTAB, + ))); + } + + let (start, end) = shdr.get_data_range()?; + let buf = self.reader.read_bytes(start, end)?; + Ok(StringTable::new(buf)) + } + + fn get_symbol_table_of_type( + &mut self, + symtab_type: u32, + ) -> Result, StringTable<'_>)>, ParseError> { + if self.shdrs.is_empty() { + return Ok(None); + } + + // Get the symtab header for the symtab. The gABI states there can be zero or one per ELF file. + match self.shdrs.iter().find(|shdr| shdr.sh_type == symtab_type) { + Some(shdr) => { + // Load the section bytes for the symtab + // (we want immutable references to both the symtab and its strtab concurrently) + let (symtab_start, symtab_end) = shdr.get_data_range()?; + self.reader.load_bytes(symtab_start..symtab_end)?; + + // Load the section bytes for the strtab + // (we want immutable references to both the symtab and its strtab concurrently) + let strtab = self + .shdrs + .get(shdr.sh_link as usize) + .ok_or(ParseError::BadOffset(shdr.sh_link as u64))?; + let (strtab_start, strtab_end) = strtab.get_data_range()?; + self.reader.load_bytes(strtab_start..strtab_end)?; + + // Validate entsize before trying to read the table so that we can error early for corrupted files + Symbol::validate_entsize(self.ehdr.class, shdr.sh_entsize.try_into()?)?; + let symtab = SymbolTable::new( + self.ehdr.endianness, + self.ehdr.class, + self.reader.get_bytes(symtab_start..symtab_end), + ); + let strtab = StringTable::new(self.reader.get_bytes(strtab_start..strtab_end)); + Ok(Some((symtab, strtab))) + } + None => Ok(None), + } + } + + /// Get the symbol table (section of type SHT_SYMTAB) and its associated string table. + /// + /// The gABI specifies that ELF object files may have zero or one sections of type SHT_SYMTAB. + pub fn symbol_table( + &mut self, + ) -> Result, StringTable<'_>)>, ParseError> { + self.get_symbol_table_of_type(abi::SHT_SYMTAB) + } + + /// Get the dynamic symbol table (section of type SHT_DYNSYM) and its associated string table. + /// + /// The gABI specifies that ELF object files may have zero or one sections of type SHT_DYNSYM. + pub fn dynamic_symbol_table( + &mut self, + ) -> Result, StringTable<'_>)>, ParseError> { + self.get_symbol_table_of_type(abi::SHT_DYNSYM) + } + + /// Get the .dynamic section/segment contents. + pub fn dynamic(&mut self) -> Result>, ParseError> { + // If we have section headers, then look it up there + if !self.shdrs.is_empty() { + if let Some(shdr) = self + .shdrs + .iter() + .find(|shdr| shdr.sh_type == abi::SHT_DYNAMIC) + { + let (start, end) = shdr.get_data_range()?; + let buf = self.reader.read_bytes(start, end)?; + return Ok(Some(DynamicTable::new( + self.ehdr.endianness, + self.ehdr.class, + buf, + ))); + } + // Otherwise, look up the PT_DYNAMIC segment (if any) + } else if !self.phdrs.is_empty() { + if let Some(phdr) = self + .phdrs + .iter() + .find(|phdr| phdr.p_type == abi::PT_DYNAMIC) + { + let (start, end) = phdr.get_file_data_range()?; + let buf = self.reader.read_bytes(start, end)?; + return Ok(Some(DynamicTable::new( + self.ehdr.endianness, + self.ehdr.class, + buf, + ))); + } + } + Ok(None) + } + + /// Read the section data for the various GNU Symbol Versioning sections (if any) + /// and return them in a [SymbolVersionTable] that which can interpret them in-place to + /// yield [SymbolRequirement](crate::gnu_symver::SymbolRequirement)s + /// and [SymbolDefinition](crate::gnu_symver::SymbolDefinition)s + /// + /// This is a GNU extension and not all objects use symbol versioning. + /// Returns an empty Option if the object does not use symbol versioning. + pub fn symbol_version_table( + &mut self, + ) -> Result>, ParseError> { + // No sections means no GNU symbol versioning sections, which is ok + if self.shdrs.is_empty() { + return Ok(None); + } + + let mut versym_opt: Option = None; + let mut needs_opt: Option = None; + let mut defs_opt: Option = None; + // Find the GNU Symbol versioning sections (if any) + for shdr in self.shdrs.iter() { + if shdr.sh_type == abi::SHT_GNU_VERSYM { + versym_opt = Some(*shdr); + } else if shdr.sh_type == abi::SHT_GNU_VERNEED { + needs_opt = Some(*shdr); + } else if shdr.sh_type == abi::SHT_GNU_VERDEF { + defs_opt = Some(*shdr); + } + + // If we've found all three sections, then we're done + if versym_opt.is_some() && needs_opt.is_some() && defs_opt.is_some() { + break; + } + } + + // No VERSYM section means the object doesn't use symbol versioning, which is ok. + if versym_opt.is_none() { + return Ok(None); + } + + // Load the versym table + let versym_shdr = versym_opt.unwrap(); + // Validate VERSYM entsize before trying to read the table so that we can error early for corrupted files + VersionIndex::validate_entsize(self.ehdr.class, versym_shdr.sh_entsize.try_into()?)?; + let (versym_start, versym_end) = versym_shdr.get_data_range()?; + self.reader.load_bytes(versym_start..versym_end)?; + + // Get the VERNEED string shdr and load the VERNEED section data (if any) + let needs_shdrs = match needs_opt { + Some(shdr) => { + let (start, end) = shdr.get_data_range()?; + self.reader.load_bytes(start..end)?; + + let strs_shdr = self + .shdrs + .get(shdr.sh_link as usize) + .ok_or(ParseError::BadOffset(shdr.sh_link as u64))?; + let (strs_start, strs_end) = strs_shdr.get_data_range()?; + self.reader.load_bytes(strs_start..strs_end)?; + + Some((shdr, strs_shdr)) + } + // It's possible to have symbol versioning with no NEEDs if we're an object that only + // exports defined symbols. + None => None, + }; + + // Get the VERDEF string shdr and load the VERDEF section data (if any) + let defs_shdrs = match defs_opt { + Some(shdr) => { + let (start, end) = shdr.get_data_range()?; + self.reader.load_bytes(start..end)?; + + let strs_shdr = self + .shdrs + .get(shdr.sh_link as usize) + .ok_or(ParseError::BadOffset(shdr.sh_link as u64))?; + let (strs_start, strs_end) = strs_shdr.get_data_range()?; + self.reader.load_bytes(strs_start..strs_end)?; + + Some((shdr, strs_shdr)) + } + // It's possible to have symbol versioning with no DEFs if we're an object that doesn't + // export any symbols but does use dynamic symbols from other objects. + None => None, + }; + + // Wrap the VERNEED section and strings data in an iterator and string table + let verneeds = match needs_shdrs { + Some((shdr, strs_shdr)) => { + let (strs_start, strs_end) = strs_shdr.get_data_range()?; + let strs_buf = self.reader.get_bytes(strs_start..strs_end); + + let (start, end) = shdr.get_data_range()?; + let buf = self.reader.get_bytes(start..end); + Some(( + VerNeedIterator::new( + self.ehdr.endianness, + self.ehdr.class, + shdr.sh_info as u64, + 0, + buf, + ), + StringTable::new(strs_buf), + )) + } + // If there's no NEEDs, then construct empty wrappers for them + None => None, + }; + + // Wrap the VERDEF section and strings data in an iterator and string table + let verdefs = match defs_shdrs { + Some((shdr, strs_shdr)) => { + let (strs_start, strs_end) = strs_shdr.get_data_range()?; + let strs_buf = self.reader.get_bytes(strs_start..strs_end); + + let (start, end) = shdr.get_data_range()?; + let buf = self.reader.get_bytes(start..end); + Some(( + VerDefIterator::new( + self.ehdr.endianness, + self.ehdr.class, + shdr.sh_info as u64, + 0, + buf, + ), + StringTable::new(strs_buf), + )) + } + // If there's no DEFs, then construct empty wrappers for them + None => None, + }; + + // Wrap the versym section data in a parsing table + let version_ids = VersionIndexTable::new( + self.ehdr.endianness, + self.ehdr.class, + self.reader.get_bytes(versym_start..versym_end), + ); + + // whew, we're done here! + Ok(Some(SymbolVersionTable::new( + version_ids, + verneeds, + verdefs, + ))) + } + + /// Read the section data for the given + /// [SectionHeader](SectionHeader) and interpret it in-place as a + /// [RelIterator](RelIterator). + /// + /// Returns a [ParseError] if the + /// [sh_type](SectionHeader#structfield.sh_type) is not + /// [SHT_REL](abi::SHT_REL). + pub fn section_data_as_rels( + &mut self, + shdr: &SectionHeader, + ) -> Result, ParseError> { + if shdr.sh_type != abi::SHT_REL { + return Err(ParseError::UnexpectedSectionType(( + shdr.sh_type, + abi::SHT_REL, + ))); + } + + let (start, end) = shdr.get_data_range()?; + let buf = self.reader.read_bytes(start, end)?; + Ok(RelIterator::new(self.ehdr.endianness, self.ehdr.class, buf)) + } + + /// Read the section data for the given + /// [SectionHeader](SectionHeader) and interpret it in-place as a + /// [RelaIterator](RelaIterator). + /// + /// Returns a [ParseError] if the + /// [sh_type](SectionHeader#structfield.sh_type) is not + /// [SHT_RELA](abi::SHT_RELA). + pub fn section_data_as_relas( + &mut self, + shdr: &SectionHeader, + ) -> Result, ParseError> { + if shdr.sh_type != abi::SHT_RELA { + return Err(ParseError::UnexpectedSectionType(( + shdr.sh_type, + abi::SHT_RELA, + ))); + } + + let (start, end) = shdr.get_data_range()?; + let buf = self.reader.read_bytes(start, end)?; + Ok(RelaIterator::new( + self.ehdr.endianness, + self.ehdr.class, + buf, + )) + } + + /// Read the section data for the given + /// [SectionHeader](SectionHeader) and interpret it in-place as a + /// [NoteIterator](NoteIterator). + /// + /// Returns a [ParseError] if the + /// [sh_type](SectionHeader#structfield.sh_type) is not + /// [SHT_NOTE](abi::SHT_NOTE). + pub fn section_data_as_notes( + &mut self, + shdr: &SectionHeader, + ) -> Result, ParseError> { + if shdr.sh_type != abi::SHT_NOTE { + return Err(ParseError::UnexpectedSectionType(( + shdr.sh_type, + abi::SHT_NOTE, + ))); + } + + let (start, end) = shdr.get_data_range()?; + let buf = self.reader.read_bytes(start, end)?; + Ok(NoteIterator::new( + self.ehdr.endianness, + self.ehdr.class, + shdr.sh_addralign as usize, + buf, + )) + } + + /// Read the segment data for the given [Segment](ProgramHeader). + pub fn segment_data(&mut self, phdr: &ProgramHeader) -> Result<&[u8], ParseError> { + let (start, end) = phdr.get_file_data_range()?; + self.reader.read_bytes(start, end) + } + + /// Read the segment data for the given + /// [Segment](ProgramHeader) and interpret it in-place as a + /// [NoteIterator](NoteIterator). + /// + /// Returns a [ParseError] if the + /// [p_type](ProgramHeader#structfield.p_type) is not + /// [PT_NOTE](abi::PT_NOTE). + pub fn segment_data_as_notes( + &mut self, + phdr: &ProgramHeader, + ) -> Result, ParseError> { + if phdr.p_type != abi::PT_NOTE { + return Err(ParseError::UnexpectedSegmentType(( + phdr.p_type, + abi::PT_NOTE, + ))); + } + + let (start, end) = phdr.get_file_data_range()?; + let buf = self.reader.read_bytes(start, end)?; + Ok(NoteIterator::new( + self.ehdr.endianness, + self.ehdr.class, + phdr.p_align as usize, + buf, + )) + } +} + +#[derive(Debug)] +struct CachingReader { + reader: R, + stream_len: u64, + bufs: HashMap<(usize, usize), Box<[u8]>>, +} + +impl CachingReader { + fn new(mut reader: R) -> Result { + // Cache the size of the stream so that we can err (rather than OOM) on invalid + // huge read requests. + let stream_len = reader.seek(SeekFrom::End(0))?; + Ok(CachingReader { + reader, + stream_len, + bufs: HashMap::<(usize, usize), Box<[u8]>>::default(), + }) + } + + fn read_bytes(&mut self, start: usize, end: usize) -> Result<&[u8], ParseError> { + self.load_bytes(start..end)?; + Ok(self.get_bytes(start..end)) + } + + fn get_bytes(&self, range: Range) -> &[u8] { + // It's a programmer error to call get_bytes without first calling load_bytes, so + // we want to panic here. + self.bufs + .get(&(range.start, range.end)) + .expect("load_bytes must be called before get_bytes for every range") + } + + fn load_bytes(&mut self, range: Range) -> Result<(), ParseError> { + if self.bufs.contains_key(&(range.start, range.end)) { + return Ok(()); + } + + // Verify that the read range doesn't go past the end of the stream (corrupted files) + let end = range.end as u64; + if end > self.stream_len { + return Err(ParseError::BadOffset(end)); + } + + self.reader.seek(SeekFrom::Start(range.start as u64))?; + let mut bytes = vec![0; range.len()].into_boxed_slice(); + self.reader.read_exact(&mut bytes)?; + self.bufs.insert((range.start, range.end), bytes); + Ok(()) + } + + fn clear_cache(&mut self) { + self.bufs.clear() + } +} + +#[cfg(test)] +mod interface_tests { + use super::*; + use crate::dynamic::Dyn; + use crate::endian::AnyEndian; + use crate::hash::SysVHashTable; + use crate::note::{Note, NoteGnuAbiTag, NoteGnuBuildId}; + use crate::relocation::Rela; + + #[test] + fn test_open_stream() { + let path = std::path::PathBuf::from("sample-objects/basic.x86_64"); + let io = std::fs::File::open(path).expect("Could not open file."); + let file = ElfStream::::open_stream(io).expect("Open test1"); + assert_eq!(file.ehdr.e_type, abi::ET_EXEC); + } + + #[test] + fn section_headers_with_strtab() { + let path = std::path::PathBuf::from("sample-objects/basic.x86_64"); + let io = std::fs::File::open(path).expect("Could not open file."); + let mut file = ElfStream::::open_stream(io).expect("Open test1"); + + let (shdrs, strtab) = file + .section_headers_with_strtab() + .expect("Failed to get shdrs"); + let (shdrs, strtab) = (shdrs, strtab.unwrap()); + + let shdr_4 = &shdrs[4]; + let name = strtab + .get(shdr_4.sh_name as usize) + .expect("Failed to get section name"); + + assert_eq!(name, ".gnu.hash"); + assert_eq!(shdr_4.sh_type, abi::SHT_GNU_HASH); + } + + #[test] + fn shnum_and_shstrndx_in_shdr0() { + let path = std::path::PathBuf::from("sample-objects/shnum.x86_64"); + let io = std::fs::File::open(path).expect("Could not open file."); + let mut file = ElfStream::::open_stream(io).expect("Open test1"); + + let (shdrs, strtab) = file + .section_headers_with_strtab() + .expect("shdrs should be parsable"); + let (shdrs, strtab) = (shdrs, strtab.unwrap()); + + let shdrs_len = shdrs.len(); + assert_eq!(shdrs_len, 0xFF15); + + let shdr = shdrs.get(shdrs_len - 1).unwrap(); + let name = strtab + .get(shdr.sh_name as usize) + .expect("Failed to get section name"); + + assert_eq!(name, ".shstrtab"); + assert_eq!(shdr.sh_type, abi::SHT_STRTAB); + } + + #[test] + fn section_header_by_name() { + let path = std::path::PathBuf::from("sample-objects/basic.x86_64"); + let io = std::fs::File::open(path).expect("Could not open file."); + let mut file = ElfStream::::open_stream(io).expect("Open test1"); + + let shdr: SectionHeader = *file + .section_header_by_name(".gnu.hash") + .expect("section table should be parseable") + .expect("file should have .gnu.hash section"); + + assert_eq!(shdr.sh_type, abi::SHT_GNU_HASH); + + let shdr = file + .section_header_by_name(".not.found") + .expect("section table should be parseable"); + + assert_eq!(shdr, None); + } + + #[test] + fn section_data_for_nobits() { + let path = std::path::PathBuf::from("sample-objects/basic.x86_64"); + let io = std::fs::File::open(path).expect("Could not open file."); + let mut file = ElfStream::::open_stream(io).expect("Open test1"); + + let shdr = file.section_headers()[26]; + assert_eq!(shdr.sh_type, abi::SHT_NOBITS); + let (data, chdr) = file + .section_data(&shdr) + .expect("Failed to get section data"); + assert_eq!(chdr, None); + assert_eq!(data, &[]); + } + + #[test] + fn section_data() { + let path = std::path::PathBuf::from("sample-objects/basic.x86_64"); + let io = std::fs::File::open(path).expect("Could not open file."); + let mut file = ElfStream::::open_stream(io).expect("Open test1"); + + let shdr = file.section_headers()[7]; + assert_eq!(shdr.sh_type, abi::SHT_GNU_VERSYM); + let (data, chdr) = file + .section_data(&shdr) + .expect("Failed to get section data"); + assert_eq!(chdr, None); + assert_eq!(data, [0, 0, 2, 0, 2, 0, 0, 0]); + } + + #[test] + fn section_data_as_strtab() { + let path = std::path::PathBuf::from("sample-objects/basic.x86_64"); + let io = std::fs::File::open(path).expect("Could not open file."); + let mut file = ElfStream::::open_stream(io).expect("Open test1"); + + let shdr = file.section_headers()[file.ehdr.e_shstrndx as usize]; + let strtab = file + .section_data_as_strtab(&shdr) + .expect("Failed to read strtab"); + assert_eq!( + strtab.get(1).expect("Failed to get strtab entry"), + ".symtab" + ); + } + + #[test] + fn segments() { + let path = std::path::PathBuf::from("sample-objects/basic.x86_64"); + let io = std::fs::File::open(path).expect("Could not open file."); + let file = ElfStream::::open_stream(io).expect("Open test1"); + + let segments = file.segments(); + assert_eq!( + segments[0], + ProgramHeader { + p_type: abi::PT_PHDR, + p_offset: 64, + p_vaddr: 4194368, + p_paddr: 4194368, + p_filesz: 448, + p_memsz: 448, + p_flags: 5, + p_align: 8, + } + ) + } + + #[test] + fn segments_phnum_in_shdr0() { + let path = std::path::PathBuf::from("sample-objects/phnum.m68k.so"); + let io = std::fs::File::open(path).expect("Could not open file."); + let file = ElfStream::::open_stream(io).expect("Open test1"); + + assert_eq!( + file.segments()[0], + ProgramHeader { + p_type: abi::PT_PHDR, + p_offset: 92, + p_vaddr: 0, + p_paddr: 0, + p_filesz: 32, + p_memsz: 32, + p_flags: 0x20003, + p_align: 0x40000, + } + ); + } + + #[test] + fn symbol_table() { + let path = std::path::PathBuf::from("sample-objects/basic.x86_64"); + let io = std::fs::File::open(path).expect("Could not open file."); + let mut file = ElfStream::::open_stream(io).expect("Open test1"); + + let (symtab, strtab) = file + .symbol_table() + .expect("Failed to read symbol table") + .expect("Failed to find symbol table"); + let symbol = symtab.get(30).expect("Failed to get symbol"); + assert_eq!( + symbol, + Symbol { + st_name: 19, + st_value: 6293200, + st_size: 0, + st_shndx: 21, + st_info: 1, + st_other: 0, + } + ); + assert_eq!( + strtab + .get(symbol.st_name as usize) + .expect("Failed to get name from strtab"), + "__JCR_LIST__" + ); + } + + #[test] + fn dynamic_symbol_table() { + let path = std::path::PathBuf::from("sample-objects/basic.x86_64"); + let io = std::fs::File::open(path).expect("Could not open file."); + let mut file = ElfStream::::open_stream(io).expect("Open test1"); + + let (symtab, strtab) = file + .dynamic_symbol_table() + .expect("Failed to read symbol table") + .expect("Failed to find symbol table"); + let symbol = symtab.get(1).expect("Failed to get symbol"); + assert_eq!( + symbol, + Symbol { + st_name: 11, + st_value: 0, + st_size: 0, + st_shndx: 0, + st_info: 18, + st_other: 0, + } + ); + assert_eq!( + strtab + .get(symbol.st_name as usize) + .expect("Failed to get name from strtab"), + "memset" + ); + } + + #[test] + fn dynamic() { + let path = std::path::PathBuf::from("sample-objects/basic.x86_64"); + let io = std::fs::File::open(path).expect("Could not open file."); + let mut file = ElfStream::::open_stream(io).expect("Open test1"); + + let mut dynamic = file + .dynamic() + .expect("Failed to parse .dynamic") + .expect("Failed to find .dynamic") + .iter(); + assert_eq!( + dynamic.next().expect("Failed to get dyn entry"), + Dyn { + d_tag: abi::DT_NEEDED, + d_un: 1 + } + ); + assert_eq!( + dynamic.next().expect("Failed to get dyn entry"), + Dyn { + d_tag: abi::DT_INIT, + d_un: 4195216 + } + ); + } + + #[test] + fn section_data_as_rels() { + let path = std::path::PathBuf::from("sample-objects/basic.x86_64"); + let io = std::fs::File::open(path).expect("Could not open file."); + let mut file = ElfStream::::open_stream(io).expect("Open test1"); + + let shdr = file.section_headers()[10]; + file.section_data_as_rels(&shdr) + .expect_err("Expected error parsing non-REL scn as RELs"); + } + + #[test] + fn section_data_as_relas() { + let path = std::path::PathBuf::from("sample-objects/basic.x86_64"); + let io = std::fs::File::open(path).expect("Could not open file."); + let mut file = ElfStream::::open_stream(io).expect("Open test1"); + + let shdr = file.section_headers()[10]; + let mut relas = file + .section_data_as_relas(&shdr) + .expect("Failed to read relas section"); + assert_eq!( + relas.next().expect("Failed to get rela entry"), + Rela { + r_offset: 6293704, + r_sym: 1, + r_type: 7, + r_addend: 0, + } + ); + assert_eq!( + relas.next().expect("Failed to get rela entry"), + Rela { + r_offset: 6293712, + r_sym: 2, + r_type: 7, + r_addend: 0, + } + ); + assert!(relas.next().is_none()); + } + + #[test] + fn section_data_as_notes() { + let path = std::path::PathBuf::from("sample-objects/basic.x86_64"); + let io = std::fs::File::open(path).expect("Could not open file."); + let mut file = ElfStream::::open_stream(io).expect("Open test1"); + + let shdr = file.section_headers()[2]; + let mut notes = file + .section_data_as_notes(&shdr) + .expect("Failed to read relas section"); + assert_eq!( + notes.next().expect("Failed to get first note"), + Note::GnuAbiTag(NoteGnuAbiTag { + os: 0, + major: 2, + minor: 6, + subminor: 32 + }) + ); + assert!(notes.next().is_none()); + } + + #[test] + fn segment_data() { + let path = std::path::PathBuf::from("sample-objects/basic.x86_64"); + let io = std::fs::File::open(path).expect("Could not open file."); + let mut file = ElfStream::::open_stream(io).expect("Open test1"); + + let phdrs = file.segments(); + let note_phdr = phdrs[5]; + + let raw_notes = file + .segment_data(¬e_phdr) + .expect("Failed to read notes segment"); + + // Generated with: + // $ dd if=sample-objects/basic.x86_64 bs=1 skip=$((0x21c)) count=$((0x44)) status=none \ + // | xxd -i + let expected = [ + 0x04, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x47, 0x4e, + 0x55, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, + 0x20, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x03, 0x00, + 0x00, 0x00, 0x47, 0x4e, 0x55, 0x00, 0x77, 0x41, 0x9f, 0x0d, 0xa5, 0x10, 0x83, 0x0c, + 0x57, 0xa7, 0xc8, 0xcc, 0xb0, 0xee, 0x85, 0x5f, 0xee, 0xd3, 0x76, 0xa3, + ]; + assert_eq!(raw_notes, expected); + } + + #[test] + fn segment_data_as_notes() { + let path = std::path::PathBuf::from("sample-objects/basic.x86_64"); + let io = std::fs::File::open(path).expect("Could not open file."); + let mut file = ElfStream::::open_stream(io).expect("Open test1"); + + let phdrs = file.segments(); + let note_phdr = phdrs[5]; + let mut notes = file + .segment_data_as_notes(¬e_phdr) + .expect("Failed to read notes segment"); + assert_eq!( + notes.next().expect("Failed to get first note"), + Note::GnuAbiTag(NoteGnuAbiTag { + os: 0, + major: 2, + minor: 6, + subminor: 32 + }) + ); + assert_eq!( + notes.next().expect("Failed to get second note"), + Note::GnuBuildId(NoteGnuBuildId(&[ + 119, 65, 159, 13, 165, 16, 131, 12, 87, 167, 200, 204, 176, 238, 133, 95, 238, 211, + 118, 163 + ])) + ); + assert!(notes.next().is_none()); + } + + #[test] + fn symbol_version_table() { + let path = std::path::PathBuf::from("sample-objects/symver.x86_64.so"); + let io = std::fs::File::open(path).expect("Could not open file."); + let mut file = ElfStream::::open_stream(io).expect("Open test1"); + + let vst = file + .symbol_version_table() + .expect("Failed to parse GNU symbol versions") + .expect("Failed to find GNU symbol versions"); + + let req = vst + .get_requirement(2) + .expect("Failed to parse NEED") + .expect("Failed to find NEED"); + assert_eq!(req.file, "libc.so.6"); + assert_eq!(req.name, "GLIBC_2.2.5"); + assert_eq!(req.hash, 0x9691A75); + + let req = vst.get_requirement(3).expect("Failed to parse NEED"); + assert!(req.is_none()); + + let req = vst.get_requirement(4).expect("Failed to parse NEED"); + assert!(req.is_none()); + + let req = vst + .get_requirement(5) + .expect("Failed to parse NEED") + .expect("Failed to find NEED"); + assert_eq!(req.file, "libc.so.6"); + assert_eq!(req.name, "GLIBC_2.2.5"); + assert_eq!(req.hash, 0x9691A75); + + let def = vst + .get_definition(3) + .expect("Failed to parse DEF") + .expect("Failed to find DEF"); + assert_eq!(def.hash, 0xC33237F); + assert_eq!(def.flags, 1); + assert!(!def.hidden); + let def_names: Vec<&str> = def.names.map(|res| res.expect("should parse")).collect(); + assert_eq!(def_names, &["hello.so"]); + + let def = vst + .get_definition(7) + .expect("Failed to parse DEF") + .expect("Failed to find DEF"); + assert_eq!(def.hash, 0x1570B62); + assert_eq!(def.flags, 0); + assert!(def.hidden); + let def_names: Vec<&str> = def.names.map(|res| res.expect("should parse")).collect(); + assert_eq!(def_names, &["HELLO_1.42"]); + } + + #[test] + fn sysv_hash_table() { + let path = std::path::PathBuf::from("sample-objects/symver.x86_64.so"); + let io = std::fs::File::open(path).expect("Could not open file."); + let mut file = ElfStream::::open_stream(io).expect("Open test1"); + + // Look up the SysV hash section header + let hash_shdr = *file + .section_header_by_name(".hash") + .expect("Failed to find sysv hash section") + .expect("Failed to find sysv hash section"); + + // We don't have a file interface for getting the SysV hash section yet, so clone the section bytes + // So we can use them to back a SysVHashTable + let (data, _) = file + .section_data(&hash_shdr) + .expect("Failed to get hash section data"); + let data_copy: Vec = data.into(); + let hash_table = + SysVHashTable::new(file.ehdr.endianness, file.ehdr.class, data_copy.as_ref()) + .expect("Failed to parse hash table"); + + // Get the dynamic symbol table. + let (symtab, strtab) = file + .dynamic_symbol_table() + .expect("Failed to read symbol table") + .expect("Failed to find symbol table"); + + // Verify that these three symbols all collide in the hash table's buckets + assert_eq!(crate::hash::sysv_hash(b"use_memset_v2"), 0x8080542); + assert_eq!(crate::hash::sysv_hash(b"__gmon_start__"), 0xF4D007F); + assert_eq!(crate::hash::sysv_hash(b"memset"), 0x73C49C4); + assert_eq!(crate::hash::sysv_hash(b"use_memset_v2") % 3, 0); + assert_eq!(crate::hash::sysv_hash(b"__gmon_start__") % 3, 0); + assert_eq!(crate::hash::sysv_hash(b"memset") % 3, 0); + + // Use the hash table to find a given symbol in it. + let (sym_idx, sym) = hash_table + .find(b"memset", &symtab, &strtab) + .expect("Failed to parse hash") + .expect("Failed to find hash"); + + // Verify that we got the same symbol from the hash table we expected + assert_eq!(sym_idx, 2); + assert_eq!(strtab.get(sym.st_name as usize).unwrap(), "memset"); + assert_eq!( + sym, + symtab.get(sym_idx).expect("Failed to get expected sym") + ); + } +} + +#[cfg(test)] +mod arch_tests { + use super::*; + use crate::endian::AnyEndian; + + // Basic smoke test which parses out symbols and headers for a given sample object of a given architecture + macro_rules! arch_test { + ( $arch:expr, $e_machine:expr, $endian:expr) => {{ + let path_str = format!("sample-objects/symver.{}.so", $arch); + let path = std::path::PathBuf::from(path_str); + let io = std::fs::File::open(path).expect("file should exist"); + let mut file = ElfStream::::open_stream(io).expect("should parse"); + + assert_eq!(file.ehdr.e_machine, $e_machine); + assert_eq!(file.ehdr.endianness, $endian); + + let (shdrs, strtab) = file.section_headers_with_strtab().expect("should parse"); + let (shdrs, strtab) = (shdrs, strtab.unwrap()); + let _: Vec<_> = shdrs + .iter() + .map(|shdr| { + ( + strtab.get(shdr.sh_name as usize).expect("should parse"), + shdr, + ) + }) + .collect(); + + if let Some((symtab, strtab)) = file.symbol_table().expect("should parse") { + let _: Vec<_> = symtab + .iter() + .map(|sym| (strtab.get(sym.st_name as usize).expect("should parse"), sym)) + .collect(); + } + + if let Some((symtab, strtab)) = file.dynamic_symbol_table().expect("should parse") { + let _: Vec<_> = symtab + .iter() + .map(|sym| (strtab.get(sym.st_name as usize).expect("should parse"), sym)) + .collect(); + } + + let note_phdrs: Vec<_> = file.segments() + .iter() + .filter(|phdr| phdr.p_type == abi::PT_NOTE) + .map(|phdr| *phdr) + .collect(); + for phdr in note_phdrs { + let _: Vec<_> = file + .segment_data_as_notes(&phdr) + .expect("should parse") + .collect(); + } + }}; + } + + #[test] + fn x86_64() { + arch_test!("x86_64", abi::EM_X86_64, AnyEndian::Little); + } + + #[test] + fn m68k() { + arch_test!("m68k", abi::EM_68K, AnyEndian::Big); + } + + #[test] + fn aarch64() { + arch_test!("aarch64", abi::EM_AARCH64, AnyEndian::Little); + } + + #[test] + fn armhf() { + arch_test!("armhf", abi::EM_ARM, AnyEndian::Little); + } + + #[test] + fn powerpc64() { + arch_test!("powerpc64", abi::EM_PPC64, AnyEndian::Big); + } + + #[test] + fn powerpc64le() { + arch_test!("powerpc64le", abi::EM_PPC64, AnyEndian::Little); + } + + #[test] + fn riscv64() { + arch_test!("riscv64", abi::EM_RISCV, AnyEndian::Little); + } +} diff --git a/src/third_party/rust-elf/src/endian.rs b/src/third_party/rust-elf/src/endian.rs new file mode 100644 index 0000000..d07812d --- /dev/null +++ b/src/third_party/rust-elf/src/endian.rs @@ -0,0 +1,327 @@ +//! An all-safe-code endian-aware integer parsing implementation via the +//! [EndianParse] trait. +//! +//! This module provides four endian parsing implementations optimized to support the different +//! common use-cases for an ELF parsing library. Each trait impl represents a +//! specification that encapsulates an interface for parsing integers from some +//! set of allowed byte orderings. +//! +//! * [AnyEndian]: Dynamically parsing either byte order at runtime based on the type of ELF object being parsed. +//! * [BigEndian]/[LittleEndian]: For tools that know they only want to parse a single given byte order known at compile time. +//! * [type@NativeEndian]: For tools that know they want to parse the same byte order as the target's byte order. +// +// Note: +// I'd love to see this get replaced with safe transmutes, if that RFC ever gets formalized. +// Until then, this crate serves as an example implementation for what's possible with purely safe rust. +use crate::abi; +use crate::parse::ParseError; + +/// This macro writes out safe code to get a subslice from the the byte slice $data +/// at the given $off as a [u8; size_of<$typ>], then calls the corresponding safe +/// endian-aware conversion on it. +/// +/// This uses safe integer math and returns a ParseError on overflow or if $data did +/// not contain enough bytes at $off to perform the conversion. +macro_rules! safe_from { + ( $self:ident, $typ:ty, $off:ident, $data:ident) => {{ + const SIZE: usize = core::mem::size_of::<$typ>(); + + let end = (*$off) + .checked_add(SIZE) + .ok_or(ParseError::IntegerOverflow)?; + + let buf: [u8; SIZE] = $data + .get(*$off..end) + .ok_or(ParseError::SliceReadError((*$off, end)))? + .try_into()?; + + *$off = end; + + // Note: This check evaluates to a constant true/false for the "fixed" types + // so the compiler should optimize out the check (LittleEndian, BigEndian, NativeEndian) + if $self.is_little() { + Ok(<$typ>::from_le_bytes(buf)) + } else { + Ok(<$typ>::from_be_bytes(buf)) + } + }}; +} + +/// An all-safe-code endian-aware integer parsing trait. +/// +/// These methods use safe code to get a subslice from the the byte slice $data +/// at the given $off as a [u8; size_of<$typ>], then calls the corresponding safe +/// endian-aware conversion on it. +/// +/// These use checked integer math and returns a ParseError on overflow or if $data did +/// not contain enough bytes at $off to perform the conversion. +pub trait EndianParse: Clone + Copy + Default + PartialEq + Eq { + fn parse_u8_at(self, offset: &mut usize, data: &[u8]) -> Result { + safe_from!(self, u8, offset, data) + } + + fn parse_u16_at(self, offset: &mut usize, data: &[u8]) -> Result { + safe_from!(self, u16, offset, data) + } + + fn parse_u32_at(self, offset: &mut usize, data: &[u8]) -> Result { + safe_from!(self, u32, offset, data) + } + + fn parse_u64_at(self, offset: &mut usize, data: &[u8]) -> Result { + safe_from!(self, u64, offset, data) + } + + fn parse_i32_at(self, offset: &mut usize, data: &[u8]) -> Result { + safe_from!(self, i32, offset, data) + } + + fn parse_i64_at(self, offset: &mut usize, data: &[u8]) -> Result { + safe_from!(self, i64, offset, data) + } + + /// Get an endian-aware integer parsing spec for an ELF [FileHeader](crate::file::FileHeader)'s + /// `ident[EI_DATA]` byte. + /// + /// Returns an [UnsupportedElfEndianness](ParseError::UnsupportedElfEndianness) if this spec + /// doesn't support parsing the byte-order represented by ei_data. If you're + /// seeing this error, are you trying to read files of any endianness? i.e. + /// did you want to use AnyEndian? + fn from_ei_data(ei_data: u8) -> Result; + + fn is_little(self) -> bool; + + #[inline(always)] + fn is_big(self) -> bool { + !self.is_little() + } +} + +/// An endian parsing type that can choose at runtime which byte order to parse integers as. +/// This is useful for scenarios where a single compiled binary wants to dynamically +/// interpret ELF files of any byte order. +#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)] +pub enum AnyEndian { + /// Used for a little-endian ELF structures that have been parsed with AnyEndian + #[default] + Little, + /// Used for a big-endian ELF structures that have been parsed with AnyEndian + Big, +} + +/// A zero-sized type that always parses integers as if they're in little-endian order. +/// This is useful for scenarios where a combiled binary knows it only wants to interpret +/// little-endian ELF files and doesn't want the performance penalty of evaluating a match +/// each time it parses an integer. +#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)] +pub struct LittleEndian; + +/// A zero-sized type that always parses integers as if they're in big-endian order. +/// This is useful for scenarios where a combiled binary knows it only wants to interpret +/// big-endian ELF files and doesn't want the performance penalty of evaluating a match +/// each time it parses an integer. +#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)] +pub struct BigEndian; + +/// A zero-sized type that always parses integers as if they're in the compilation target's native-endian order. +/// This is useful for toolchain scenarios where a combiled binary knows it only wants to interpret +/// ELF files compiled for the same target and doesn't want the performance penalty of evaluating a match +/// each time it parses an integer. +#[cfg(target_endian = "little")] +pub type NativeEndian = LittleEndian; + +#[cfg(target_endian = "little")] +#[allow(non_upper_case_globals)] +#[doc(hidden)] +pub const NativeEndian: LittleEndian = LittleEndian; + +/// A zero-sized type that always parses integers as if they're in the compilation target's native-endian order. +/// This is useful for toolchain scenarios where a combiled binary knows it only wants to interpret +/// ELF files compiled for the same target and doesn't want the performance penalty of evaluating a match +/// each time it parses an integer. +#[cfg(target_endian = "big")] +pub type NativeEndian = BigEndian; + +#[cfg(target_endian = "big")] +#[allow(non_upper_case_globals)] +#[doc(hidden)] +pub const NativeEndian: BigEndian = BigEndian; + +impl EndianParse for LittleEndian { + fn from_ei_data(ei_data: u8) -> Result { + match ei_data { + abi::ELFDATA2LSB => Ok(LittleEndian), + _ => Err(ParseError::UnsupportedElfEndianness(ei_data)), + } + } + + #[inline(always)] + fn is_little(self) -> bool { + true + } +} + +impl EndianParse for BigEndian { + fn from_ei_data(ei_data: u8) -> Result { + match ei_data { + abi::ELFDATA2MSB => Ok(BigEndian), + _ => Err(ParseError::UnsupportedElfEndianness(ei_data)), + } + } + + #[inline(always)] + fn is_little(self) -> bool { + false + } +} + +impl EndianParse for AnyEndian { + fn from_ei_data(ei_data: u8) -> Result { + match ei_data { + abi::ELFDATA2LSB => Ok(AnyEndian::Little), + abi::ELFDATA2MSB => Ok(AnyEndian::Big), + _ => Err(ParseError::UnsupportedElfEndianness(ei_data)), + } + } + + #[inline(always)] + fn is_little(self) -> bool { + match self { + AnyEndian::Little => true, + AnyEndian::Big => false, + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + macro_rules! parse_test { + ( $endian:expr, $res_typ:ty, $method:ident, $expect:expr) => {{ + let bytes = [ + 0x01u8, 0x02u8, 0x03u8, 0x04u8, 0x05u8, 0x06u8, 0x07u8, 0x08u8, + ]; + let mut offset = 0; + let result = $endian.$method(&mut offset, &bytes).unwrap(); + assert_eq!(result, $expect); + assert_eq!(offset, core::mem::size_of::<$res_typ>()); + }}; + } + + macro_rules! fuzz_too_short_test { + ( $endian:expr, $res_typ:ty, $method:ident) => {{ + let bytes = [ + 0x01u8, 0x02u8, 0x03u8, 0x04u8, 0x05u8, 0x06u8, 0x07u8, 0x08u8, + ]; + let size = core::mem::size_of::<$res_typ>(); + for n in 0..size { + let buf = bytes.split_at(n).0.as_ref(); + let mut offset: usize = 0; + let error = $endian + .$method(&mut offset, buf) + .expect_err("Expected an error, but parsed: "); + assert!( + matches!(error, ParseError::SliceReadError(_)), + "Unexpected Error type found: {error}" + ); + } + }}; + } + + #[test] + fn parse_u8_at() { + parse_test!(LittleEndian, u8, parse_u8_at, 0x01u8); + parse_test!(BigEndian, u8, parse_u8_at, 0x01u8); + parse_test!(AnyEndian::Little, u8, parse_u8_at, 0x01u8); + parse_test!(AnyEndian::Big, u8, parse_u8_at, 0x01u8); + } + + #[test] + fn parse_u16_at() { + parse_test!(LittleEndian, u16, parse_u16_at, 0x0201u16); + parse_test!(BigEndian, u16, parse_u16_at, 0x0102u16); + parse_test!(AnyEndian::Little, u16, parse_u16_at, 0x0201u16); + parse_test!(AnyEndian::Big, u16, parse_u16_at, 0x0102u16); + } + + #[test] + fn parse_u32_at() { + parse_test!(LittleEndian, u32, parse_u32_at, 0x04030201u32); + parse_test!(BigEndian, u32, parse_u32_at, 0x01020304u32); + parse_test!(AnyEndian::Little, u32, parse_u32_at, 0x04030201u32); + parse_test!(AnyEndian::Big, u32, parse_u32_at, 0x01020304u32); + } + + #[test] + fn parse_u64_at() { + parse_test!(LittleEndian, u64, parse_u64_at, 0x0807060504030201u64); + parse_test!(BigEndian, u64, parse_u64_at, 0x0102030405060708u64); + parse_test!(AnyEndian::Little, u64, parse_u64_at, 0x0807060504030201u64); + parse_test!(AnyEndian::Big, u64, parse_u64_at, 0x0102030405060708u64); + } + + #[test] + fn parse_i32_at() { + parse_test!(LittleEndian, i32, parse_i32_at, 0x04030201i32); + parse_test!(BigEndian, i32, parse_i32_at, 0x01020304i32); + parse_test!(AnyEndian::Little, i32, parse_i32_at, 0x04030201i32); + parse_test!(AnyEndian::Big, i32, parse_i32_at, 0x01020304i32); + } + + #[test] + fn parse_i64_at() { + parse_test!(LittleEndian, i64, parse_i64_at, 0x0807060504030201i64); + parse_test!(BigEndian, i64, parse_i64_at, 0x0102030405060708i64); + parse_test!(AnyEndian::Little, i64, parse_i64_at, 0x0807060504030201i64); + parse_test!(AnyEndian::Big, i64, parse_i64_at, 0x0102030405060708i64); + } + + #[test] + fn fuzz_u8_too_short() { + fuzz_too_short_test!(LittleEndian, u8, parse_u8_at); + fuzz_too_short_test!(BigEndian, u8, parse_u8_at); + fuzz_too_short_test!(AnyEndian::Little, u8, parse_u8_at); + fuzz_too_short_test!(AnyEndian::Big, u8, parse_u8_at); + } + + #[test] + fn fuzz_u16_too_short() { + fuzz_too_short_test!(LittleEndian, u16, parse_u16_at); + fuzz_too_short_test!(BigEndian, u16, parse_u16_at); + fuzz_too_short_test!(AnyEndian::Little, u16, parse_u16_at); + fuzz_too_short_test!(AnyEndian::Big, u16, parse_u16_at); + } + + #[test] + fn fuzz_u32_too_short() { + fuzz_too_short_test!(LittleEndian, u32, parse_u32_at); + fuzz_too_short_test!(BigEndian, u32, parse_u32_at); + fuzz_too_short_test!(AnyEndian::Little, u32, parse_u32_at); + fuzz_too_short_test!(AnyEndian::Big, u32, parse_u32_at); + } + + #[test] + fn fuzz_i32_too_short() { + fuzz_too_short_test!(LittleEndian, i32, parse_i32_at); + fuzz_too_short_test!(BigEndian, i32, parse_i32_at); + fuzz_too_short_test!(AnyEndian::Little, i32, parse_i32_at); + fuzz_too_short_test!(AnyEndian::Big, i32, parse_i32_at); + } + + #[test] + fn fuzz_u64_too_short() { + fuzz_too_short_test!(LittleEndian, u64, parse_u64_at); + fuzz_too_short_test!(BigEndian, u64, parse_u64_at); + fuzz_too_short_test!(AnyEndian::Little, u64, parse_u64_at); + fuzz_too_short_test!(AnyEndian::Big, u64, parse_u64_at); + } + + #[test] + fn fuzz_i64_too_short() { + fuzz_too_short_test!(LittleEndian, i64, parse_i64_at); + fuzz_too_short_test!(BigEndian, i64, parse_i64_at); + fuzz_too_short_test!(AnyEndian::Little, i64, parse_i64_at); + fuzz_too_short_test!(AnyEndian::Big, i64, parse_i64_at); + } +} diff --git a/src/third_party/rust-elf/src/file.rs b/src/third_party/rust-elf/src/file.rs new file mode 100644 index 0000000..bf4137d --- /dev/null +++ b/src/third_party/rust-elf/src/file.rs @@ -0,0 +1,478 @@ +//! Parsing the ELF File Header +use crate::abi; +use crate::endian::EndianParse; +use crate::parse::ParseError; + +/// Represents the ELF file word size (32-bit vs 64-bit) +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub enum Class { + ELF32, + ELF64, +} + +/// C-style 32-bit ELF File Header definition +/// +/// These C-style definitions are for users who want to implement their own ELF manipulation logic. +#[derive(Debug)] +#[repr(C)] +pub struct Elf32_Ehdr { + pub e_ident: [u8; abi::EI_NIDENT], + pub e_type: u16, + pub e_machine: u16, + pub e_version: u32, + pub e_entry: u32, + pub e_phoff: u32, + pub e_shoff: u32, + pub e_flags: u32, + pub e_ehsize: u16, + pub e_phentsize: u16, + pub e_phnum: u16, + pub e_shentsize: u16, + pub e_shnum: u16, + pub e_shstrndx: u16, +} + +/// C-style 64-bit ELF File Header definition +/// +/// These C-style definitions are for users who want to implement their own ELF manipulation logic. +#[derive(Debug)] +#[repr(C)] +pub struct Elf64_Ehdr { + pub e_ident: [u8; abi::EI_NIDENT], + pub e_type: u16, + pub e_machine: u16, + pub e_version: u32, + pub e_entry: u64, + pub e_phoff: u64, + pub e_shoff: u64, + pub e_flags: u32, + pub e_ehsize: u16, + pub e_phentsize: u16, + pub e_phnum: u16, + pub e_shentsize: u16, + pub e_shnum: u16, + pub e_shstrndx: u16, +} + +/// Encapsulates the contents of the ELF File Header +/// +/// The ELF File Header starts off every ELF file and both identifies the +/// file contents and informs how to interpret said contents. This includes +/// the width of certain fields (32-bit vs 64-bit), the data endianness, the +/// file type, and more. +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub struct FileHeader { + /// 32-bit vs 64-bit + pub class: Class, + // file byte order + pub endianness: E, + /// elf version + pub version: u32, + /// OS ABI + pub osabi: u8, + /// Version of the OS ABI + pub abiversion: u8, + /// ELF file type + pub e_type: u16, + /// Target machine architecture + pub e_machine: u16, + /// Virtual address of program entry point + /// This member gives the virtual address to which the system first transfers control, + /// thus starting the process. If the file has no associated entry point, this member holds zero. + /// + /// Note: Type is Elf32_Addr or Elf64_Addr which are either 4 or 8 bytes. We aren't trying to zero-copy + /// parse the FileHeader since there's only one per file and its only ~45 bytes anyway, so we use + /// u64 for the three Elf*_Addr and Elf*_Off fields here. + pub e_entry: u64, + /// This member holds the program header table's file offset in bytes. If the file has no program header + /// table, this member holds zero. + pub e_phoff: u64, + /// This member holds the section header table's file offset in bytes. If the file has no section header + /// table, this member holds zero. + pub e_shoff: u64, + /// This member holds processor-specific flags associated with the file. Flag names take the form EF_machine_flag. + pub e_flags: u32, + /// This member holds the ELF header's size in bytes. + pub e_ehsize: u16, + /// This member holds the size in bytes of one entry in the file's program header table; all entries are the same size. + pub e_phentsize: u16, + /// This member holds the number of entries in the program header table. Thus the product of e_phentsize and e_phnum + /// gives the table's size in bytes. If a file has no program header table, e_phnum holds the value zero. + pub e_phnum: u16, + /// This member holds a section header's size in bytes. A section header is one entry in the section header table; + /// all entries are the same size. + pub e_shentsize: u16, + /// This member holds the number of entries in the section header table. Thus the product of e_shentsize and e_shnum + /// gives the section header table's size in bytes. If a file has no section header table, e_shnum holds the value zero. + /// + /// If the number of sections is greater than or equal to SHN_LORESERVE (0xff00), this member has the value zero and + /// the actual number of section header table entries is contained in the sh_size field of the section header at index 0. + /// (Otherwise, the sh_size member of the initial entry contains 0.) + pub e_shnum: u16, + /// This member holds the section header table index of the entry associated with the section name string table. If the + /// file has no section name string table, this member holds the value SHN_UNDEF. + /// + /// If the section name string table section index is greater than or equal to SHN_LORESERVE (0xff00), this member has + /// the value SHN_XINDEX (0xffff) and the actual index of the section name string table section is contained in the + /// sh_link field of the section header at index 0. (Otherwise, the sh_link member of the initial entry contains 0.) + pub e_shstrndx: u16, +} + +pub const ELF32_EHDR_TAILSIZE: usize = 36; +pub const ELF64_EHDR_TAILSIZE: usize = 48; + +fn verify_ident(buf: &[u8]) -> Result<(), ParseError> { + // Verify the magic number + let magic = buf.split_at(abi::EI_CLASS).0; + if magic != abi::ELFMAGIC { + return Err(ParseError::BadMagic([ + magic[0], magic[1], magic[2], magic[3], + ])); + } + + // Verify ELF Version + let version = buf[abi::EI_VERSION]; + if version != abi::EV_CURRENT { + return Err(ParseError::UnsupportedVersion(( + version as u64, + abi::EV_CURRENT as u64, + ))); + } + + Ok(()) +} + +pub fn parse_ident(data: &[u8]) -> Result<(E, Class, u8, u8), ParseError> { + verify_ident(data)?; + + let e_class = data[abi::EI_CLASS]; + let class = match e_class { + abi::ELFCLASS32 => Class::ELF32, + abi::ELFCLASS64 => Class::ELF64, + _ => { + return Err(ParseError::UnsupportedElfClass(e_class)); + } + }; + + // Verify endianness is something we know how to parse + let file_endian = E::from_ei_data(data[abi::EI_DATA])?; + + Ok(( + file_endian, + class, + data[abi::EI_OSABI], + data[abi::EI_ABIVERSION], + )) +} + +impl FileHeader { + pub fn parse_tail(ident: (E, Class, u8, u8), data: &[u8]) -> Result, ParseError> { + let (file_endian, class, osabi, abiversion) = ident; + + let mut offset = 0; + let e_type = file_endian.parse_u16_at(&mut offset, data)?; + let e_machine = file_endian.parse_u16_at(&mut offset, data)?; + let version = file_endian.parse_u32_at(&mut offset, data)?; + + let e_entry: u64; + let e_phoff: u64; + let e_shoff: u64; + + if class == Class::ELF32 { + e_entry = file_endian.parse_u32_at(&mut offset, data)? as u64; + e_phoff = file_endian.parse_u32_at(&mut offset, data)? as u64; + e_shoff = file_endian.parse_u32_at(&mut offset, data)? as u64; + } else { + e_entry = file_endian.parse_u64_at(&mut offset, data)?; + e_phoff = file_endian.parse_u64_at(&mut offset, data)?; + e_shoff = file_endian.parse_u64_at(&mut offset, data)?; + } + + let e_flags = file_endian.parse_u32_at(&mut offset, data)?; + let e_ehsize = file_endian.parse_u16_at(&mut offset, data)?; + let e_phentsize = file_endian.parse_u16_at(&mut offset, data)?; + let e_phnum = file_endian.parse_u16_at(&mut offset, data)?; + let e_shentsize = file_endian.parse_u16_at(&mut offset, data)?; + let e_shnum = file_endian.parse_u16_at(&mut offset, data)?; + let e_shstrndx = file_endian.parse_u16_at(&mut offset, data)?; + + Ok(FileHeader { + class, + endianness: file_endian, + version, + e_type, + e_machine, + osabi, + abiversion, + e_entry, + e_phoff, + e_shoff, + e_flags, + e_ehsize, + e_phentsize, + e_phnum, + e_shentsize, + e_shnum, + e_shstrndx, + }) + } +} + +#[cfg(test)] +mod parse_tests { + use super::*; + use crate::endian::AnyEndian; + + #[test] + fn test_verify_ident_valid() { + let data: [u8; abi::EI_NIDENT] = [ + abi::ELFMAG0, + abi::ELFMAG1, + abi::ELFMAG2, + abi::ELFMAG3, + abi::ELFCLASS32, + abi::ELFDATA2LSB, + abi::EV_CURRENT, + abi::ELFOSABI_LINUX, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + ]; + verify_ident(data.as_ref()).expect("Expected Ok result"); + } + + #[test] + fn test_verify_ident_invalid_mag0() { + let data: [u8; abi::EI_NIDENT] = [ + 0xFF, + abi::ELFMAG1, + abi::ELFMAG2, + abi::ELFMAG3, + abi::ELFCLASS32, + abi::ELFDATA2LSB, + abi::EV_CURRENT, + abi::ELFOSABI_LINUX, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + ]; + let result = verify_ident(data.as_ref()).expect_err("Expected an error"); + assert!( + matches!(result, ParseError::BadMagic(_)), + "Unexpected Error type found: {result}" + ); + } + + #[test] + fn test_verify_ident_invalid_mag1() { + let data: [u8; abi::EI_NIDENT] = [ + abi::ELFMAG0, + 0xFF, + abi::ELFMAG2, + abi::ELFMAG3, + abi::ELFCLASS32, + abi::ELFDATA2LSB, + abi::EV_CURRENT, + abi::ELFOSABI_LINUX, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + ]; + let result = verify_ident(data.as_ref()).expect_err("Expected an error"); + assert!( + matches!(result, ParseError::BadMagic(_)), + "Unexpected Error type found: {result}" + ); + } + + #[test] + fn test_verify_ident_invalid_mag2() { + let data: [u8; abi::EI_NIDENT] = [ + abi::ELFMAG0, + abi::ELFMAG1, + 0xFF, + abi::ELFMAG3, + abi::ELFCLASS32, + abi::ELFDATA2LSB, + abi::EV_CURRENT, + abi::ELFOSABI_LINUX, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + ]; + let result = verify_ident(data.as_ref()).expect_err("Expected an error"); + assert!( + matches!(result, ParseError::BadMagic(_)), + "Unexpected Error type found: {result}" + ); + } + + #[test] + fn test_verify_ident_invalid_mag3() { + let data: [u8; abi::EI_NIDENT] = [ + abi::ELFMAG0, + abi::ELFMAG1, + abi::ELFMAG2, + 0xFF, + abi::ELFCLASS32, + abi::ELFDATA2LSB, + abi::EV_CURRENT, + abi::ELFOSABI_LINUX, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + ]; + let result = verify_ident(data.as_ref()).expect_err("Expected an error"); + assert!( + matches!(result, ParseError::BadMagic(_)), + "Unexpected Error type found: {result}" + ); + } + + #[allow(deprecated)] + #[test] + fn test_verify_ident_invalid_version() { + let data: [u8; abi::EI_NIDENT] = [ + abi::ELFMAG0, + abi::ELFMAG1, + abi::ELFMAG2, + abi::ELFMAG3, + abi::ELFCLASS32, + abi::ELFDATA2LSB, + 42, + abi::ELFOSABI_LINUX, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + ]; + let result = verify_ident(data.as_ref()).expect_err("Expected an error"); + assert!( + matches!(result, ParseError::UnsupportedVersion((42, 1))), + "Unexpected Error type found: {result}" + ); + } + + #[test] + fn test_parse_ehdr32_works() { + let ident = (AnyEndian::Little, Class::ELF32, abi::ELFOSABI_LINUX, 7u8); + let mut tail = [0u8; ELF64_EHDR_TAILSIZE]; + for (n, elem) in tail.iter_mut().enumerate().take(ELF64_EHDR_TAILSIZE) { + *elem = n as u8; + } + + assert_eq!( + FileHeader::parse_tail(ident, &tail).unwrap(), + FileHeader { + class: Class::ELF32, + endianness: AnyEndian::Little, + version: 0x7060504, + osabi: abi::ELFOSABI_LINUX, + abiversion: 7, + e_type: 0x100, + e_machine: 0x302, + e_entry: 0x0B0A0908, + e_phoff: 0x0F0E0D0C, + e_shoff: 0x13121110, + e_flags: 0x17161514, + e_ehsize: 0x1918, + e_phentsize: 0x1B1A, + e_phnum: 0x1D1C, + e_shentsize: 0x1F1E, + e_shnum: 0x2120, + e_shstrndx: 0x2322, + } + ); + } + + #[test] + fn test_parse_ehdr32_fuzz_too_short() { + let ident = (AnyEndian::Little, Class::ELF32, abi::ELFOSABI_LINUX, 7u8); + let tail = [0u8; ELF32_EHDR_TAILSIZE]; + + for n in 0..ELF32_EHDR_TAILSIZE { + let buf = tail.split_at(n).0; + let result = FileHeader::parse_tail(ident, buf).expect_err("Expected an error"); + assert!( + matches!(result, ParseError::SliceReadError(_)), + "Unexpected Error type found: {result:?}" + ); + } + } + + #[test] + fn test_parse_ehdr64_works() { + let ident = (AnyEndian::Big, Class::ELF64, abi::ELFOSABI_LINUX, 7u8); + let mut tail = [0u8; ELF64_EHDR_TAILSIZE]; + for (n, elem) in tail.iter_mut().enumerate().take(ELF64_EHDR_TAILSIZE) { + *elem = n as u8; + } + + assert_eq!( + FileHeader::parse_tail(ident, &tail).unwrap(), + FileHeader { + class: Class::ELF64, + endianness: AnyEndian::Big, + version: 0x04050607, + osabi: abi::ELFOSABI_LINUX, + abiversion: 7, + e_type: 0x0001, + e_machine: 0x0203, + e_entry: 0x08090A0B0C0D0E0F, + e_phoff: 0x1011121314151617, + e_shoff: 0x18191A1B1C1D1E1F, + e_flags: 0x20212223, + e_ehsize: 0x2425, + e_phentsize: 0x2627, + e_phnum: 0x2829, + e_shentsize: 0x2A2B, + e_shnum: 0x2C2D, + e_shstrndx: 0x2E2F, + } + ); + } + + #[test] + fn test_parse_ehdr64_fuzz_too_short() { + let ident = (AnyEndian::Little, Class::ELF64, abi::ELFOSABI_LINUX, 7u8); + let tail = [0u8; ELF64_EHDR_TAILSIZE]; + + for n in 0..ELF64_EHDR_TAILSIZE { + let buf = tail.split_at(n).0; + let result = FileHeader::parse_tail(ident, buf).expect_err("Expected an error"); + assert!( + matches!(result, ParseError::SliceReadError(_)), + "Unexpected Error type found: {result:?}" + ); + } + } +} diff --git a/src/third_party/rust-elf/src/gnu_symver.rs b/src/third_party/rust-elf/src/gnu_symver.rs new file mode 100644 index 0000000..d444c33 --- /dev/null +++ b/src/third_party/rust-elf/src/gnu_symver.rs @@ -0,0 +1,1592 @@ +//! Parsing GNU extension sections for dynamic symbol versioning `.gnu.version.*` +use crate::abi; +use crate::endian::EndianParse; +use crate::file::Class; +use crate::parse::{ParseAt, ParseError, ParsingTable}; +use crate::string_table::StringTable; + +#[derive(Debug, PartialEq, Eq)] +pub struct SymbolRequirement<'data> { + pub file: &'data str, + pub name: &'data str, + pub hash: u32, + pub flags: u16, + pub hidden: bool, +} + +#[derive(Debug)] +pub struct SymbolDefinition<'data, E: EndianParse> { + pub hash: u32, + pub flags: u16, + pub names: SymbolNamesIterator<'data, E>, + pub hidden: bool, +} + +#[derive(Debug)] +pub struct SymbolNamesIterator<'data, E: EndianParse> { + vda_iter: VerDefAuxIterator<'data, E>, + strtab: &'data StringTable<'data>, +} + +impl<'data, E: EndianParse> SymbolNamesIterator<'data, E> { + pub fn new(vda_iter: VerDefAuxIterator<'data, E>, strtab: &'data StringTable<'data>) -> Self { + SymbolNamesIterator { vda_iter, strtab } + } +} + +impl<'data, E: EndianParse> Iterator for SymbolNamesIterator<'data, E> { + type Item = Result<&'data str, ParseError>; + fn next(&mut self) -> Option { + let vda = self.vda_iter.next(); + match vda { + Some(vda) => Some(self.strtab.get(vda.vda_name as usize)), + None => None, + } + } +} + +#[derive(Debug)] +pub struct SymbolVersionTable<'data, E: EndianParse> { + version_ids: VersionIndexTable<'data, E>, + + verneeds: Option<(VerNeedIterator<'data, E>, StringTable<'data>)>, + verdefs: Option<(VerDefIterator<'data, E>, StringTable<'data>)>, +} + +impl<'data, E: EndianParse> SymbolVersionTable<'data, E> { + pub fn new( + version_ids: VersionIndexTable<'data, E>, + verneeds: Option<(VerNeedIterator<'data, E>, StringTable<'data>)>, + verdefs: Option<(VerDefIterator<'data, E>, StringTable<'data>)>, + ) -> Self { + SymbolVersionTable { + version_ids, + verneeds, + verdefs, + } + } + + pub fn get_requirement( + &self, + sym_idx: usize, + ) -> Result>, ParseError> { + let (verneeds, verneed_strs) = match self.verneeds { + Some(verneeds) => verneeds, + None => { + return Ok(None); + } + }; + + let ver_ndx = self.version_ids.get(sym_idx)?; + let iter = verneeds; + for (vn, vna_iter) in iter { + for vna in vna_iter { + if vna.vna_other != ver_ndx.index() { + continue; + } + + let file = verneed_strs.get(vn.vn_file as usize)?; + let name = verneed_strs.get(vna.vna_name as usize)?; + let hash = vna.vna_hash; + let hidden = ver_ndx.is_hidden(); + return Ok(Some(SymbolRequirement { + file, + name, + hash, + flags: vna.vna_flags, + hidden, + })); + } + } + + // Maybe we should treat this as a ParseError instead of returning an + // empty Option? This can only happen if .gnu.versions[N] contains an + // index that doesn't exist, which is likely a file corruption or + // programmer error (i.e asking for a requirement for a defined symbol) + Ok(None) + } + + pub fn get_definition( + &self, + sym_idx: usize, + ) -> Result>, ParseError> { + let (ref verdefs, ref verdef_strs) = match self.verdefs { + Some(ref verdefs) => verdefs, + None => { + return Ok(None); + } + }; + + let ver_ndx = self.version_ids.get(sym_idx)?; + let iter = *verdefs; + for (vd, vda_iter) in iter { + if vd.vd_ndx != ver_ndx.index() { + continue; + } + + let flags = vd.vd_flags; + let hash = vd.vd_hash; + let hidden = ver_ndx.is_hidden(); + return Ok(Some(SymbolDefinition { + hash, + flags, + names: SymbolNamesIterator { + vda_iter, + strtab: verdef_strs, + }, + hidden, + })); + } + + // Maybe we should treat this as a ParseError instead of returning an + // empty Option? This can only happen if .gnu.versions[N] contains an + // index that doesn't exist, which is likely a file corruption or + // programmer error (i.e asking for a definition for an undefined symbol) + Ok(None) + } +} + +//////////////////////////////////////////////////////////////////// +// _ // +// __ _ _ __ _ _ __ _____ _ __ ___(_) ___ _ __ // +// / _` | '_ \| | | | \ \ / / _ \ '__/ __| |/ _ \| '_ \ // +// _ | (_| | | | | |_| | _ \ V / __/ | \__ \ | (_) | | | | // +// (_) \__, |_| |_|\__,_| (_) \_/ \___|_| |___/_|\___/|_| |_| // +// |___/ // +//////////////////////////////////////////////////////////////////// + +pub type VersionIndexTable<'data, E> = ParsingTable<'data, E, VersionIndex>; + +/// The special GNU extension section .gnu.version has a section type of SHT_GNU_VERSYM. +/// This section shall have the same number of entries as the Dynamic Symbol Table in +/// the .dynsym section. The .gnu.version section shall contain an array of +/// elements of type Elfxx_Half (both of which are 16-bit unsigned integers). +/// +/// The .gnu.version section and VersionIndex values act as a lookup table for specifying +/// the version defined for or required by the corresponding symbol in the Dynamic Symbol Table. +/// +/// For example, the symbol at index N in the .dynsym Symbol Table will have a VersionIndex +/// value located in the versym table at .gnu.version\[N\] which identifies +/// structures in the .gnu.version_d and .gnu.version_r sections. These values +/// are located in identifiers provided by the the vna_other member of the VerNeedAux +/// structure or the vd_ndx member of the VerDef structure. +#[derive(Debug, PartialEq, Eq)] +pub struct VersionIndex(pub u16); + +impl VersionIndex { + pub fn index(&self) -> u16 { + self.0 & abi::VER_NDX_VERSION + } + + pub fn is_local(&self) -> bool { + self.index() == abi::VER_NDX_LOCAL + } + + pub fn is_global(&self) -> bool { + self.index() == abi::VER_NDX_GLOBAL + } + + pub fn is_hidden(&self) -> bool { + (self.0 & abi::VER_NDX_HIDDEN) != 0 + } +} + +impl ParseAt for VersionIndex { + fn parse_at( + endian: E, + _class: Class, + offset: &mut usize, + data: &[u8], + ) -> Result { + Ok(VersionIndex(endian.parse_u16_at(offset, data)?)) + } + + #[inline] + fn size_for(_class: Class) -> usize { + core::mem::size_of::() + } +} + +/////////////////////////////////////////////////////////////////////////////// +// _ _ // +// __ _ _ __ _ _ __ _____ _ __ ___(_) ___ _ __ __| | // +// / _` | '_ \| | | | \ \ / / _ \ '__/ __| |/ _ \| '_ \ / _` | // +// _ | (_| | | | | |_| | _ \ V / __/ | \__ \ | (_) | | | | | (_| | // +// (_) \__, |_| |_|\__,_| (_) \_/ \___|_| |___/_|\___/|_| |_|_____\__,_| // +// |___/ |_____| // +/////////////////////////////////////////////////////////////////////////////// + +/// The special GNU extension section .gnu.version_d has a section type of SHT_GNU_VERDEF +/// This section shall contain symbol version definitions. The number of entries +/// in this section shall be contained in the DT_VERDEFNUM entry of the Dynamic +/// Section .dynamic, and also the sh_info member of the section header. +/// The sh_link member of the section header shall point to the section that +/// contains the strings referenced by this section. +/// +/// The .gnu.version_d section shall contain an array of VerDef structures +/// optionally followed by an array of VerDefAux structures. +#[derive(Debug, PartialEq, Eq)] +pub struct VerDef { + /// Version information flag bitmask. + pub vd_flags: u16, + /// VersionIndex value referencing the SHT_GNU_VERSYM section. + pub vd_ndx: u16, + /// Number of associated verdaux array entries. + pub vd_cnt: u16, + /// Version name hash value (ELF hash function). + pub vd_hash: u32, + /// Offset in bytes to a corresponding entry in an array of VerDefAux structures. + vd_aux: u32, + /// Offset to the next VerDef entry, in bytes. + vd_next: u32, +} + +impl ParseAt for VerDef { + fn parse_at( + endian: E, + _class: Class, + offset: &mut usize, + data: &[u8], + ) -> Result { + let vd_version = endian.parse_u16_at(offset, data)?; + if vd_version != abi::VER_DEF_CURRENT { + return Err(ParseError::UnsupportedVersion(( + vd_version as u64, + abi::VER_DEF_CURRENT as u64, + ))); + } + + Ok(VerDef { + vd_flags: endian.parse_u16_at(offset, data)?, + vd_ndx: endian.parse_u16_at(offset, data)?, + vd_cnt: endian.parse_u16_at(offset, data)?, + vd_hash: endian.parse_u32_at(offset, data)?, + vd_aux: endian.parse_u32_at(offset, data)?, + vd_next: endian.parse_u32_at(offset, data)?, + }) + } + + #[inline] + fn size_for(_class: Class) -> usize { + ELFVERDEFSIZE + } +} + +const ELFVERDEFSIZE: usize = 20; + +#[derive(Debug, Clone, Copy)] +pub struct VerDefIterator<'data, E: EndianParse> { + endian: E, + class: Class, + /// The number of entries in this iterator is given by the .dynamic DT_VERDEFNUM entry + /// and also in the .gnu.version_d section header's sh_info field. + count: u64, + data: &'data [u8], + offset: usize, +} + +impl<'data, E: EndianParse> VerDefIterator<'data, E> { + pub fn new( + endian: E, + class: Class, + count: u64, + starting_offset: usize, + data: &'data [u8], + ) -> Self { + VerDefIterator { + endian, + class, + count, + data, + offset: starting_offset, + } + } +} + +impl<'data, E: EndianParse> Iterator for VerDefIterator<'data, E> { + type Item = (VerDef, VerDefAuxIterator<'data, E>); + fn next(&mut self) -> Option { + if self.data.is_empty() || self.count == 0 { + return None; + } + + let mut start = self.offset; + let vd = VerDef::parse_at(self.endian, self.class, &mut start, self.data).ok()?; + let vda_iter = VerDefAuxIterator::new( + self.endian, + self.class, + vd.vd_cnt, + self.offset + vd.vd_aux as usize, + self.data, + ); + + // If offset overflows, silently end iteration + match self.offset.checked_add(vd.vd_next as usize) { + Some(new_off) => self.offset = new_off, + None => self.count = 0, + } + self.count -= 1; + + // Silently end iteration early if the next link stops pointing somewhere new + // TODO: Make this an error condition by allowing the iterator to yield a ParseError + if self.count > 0 && vd.vd_next == 0 { + self.count = 0 + } + Some((vd, vda_iter)) + } +} + +/// Version Definition Auxiliary Entries from the .gnu.version_d section +#[derive(Debug, PartialEq, Eq)] +pub struct VerDefAux { + /// Offset to the version or dependency name string in the linked string table, in bytes. + pub vda_name: u32, + /// Offset to the next VerDefAux entry, in bytes. + vda_next: u32, +} + +impl ParseAt for VerDefAux { + fn parse_at( + endian: E, + _class: Class, + offset: &mut usize, + data: &[u8], + ) -> Result { + Ok(VerDefAux { + vda_name: endian.parse_u32_at(offset, data)?, + vda_next: endian.parse_u32_at(offset, data)?, + }) + } + + #[inline] + fn size_for(_class: Class) -> usize { + 8 + } +} + +#[derive(Debug)] +pub struct VerDefAuxIterator<'data, E: EndianParse> { + endian: E, + class: Class, + count: u16, + data: &'data [u8], + offset: usize, +} + +impl<'data, E: EndianParse> VerDefAuxIterator<'data, E> { + pub fn new( + endian: E, + class: Class, + count: u16, + starting_offset: usize, + data: &'data [u8], + ) -> Self { + VerDefAuxIterator { + endian, + class, + count, + data, + offset: starting_offset, + } + } +} + +impl Iterator for VerDefAuxIterator<'_, E> { + type Item = VerDefAux; + fn next(&mut self) -> Option { + if self.data.is_empty() || self.count == 0 { + return None; + } + + // N.B. This offset handling is maybe unnecessary, but faithful to the + // spec. As far as I've observed, VerDefAux entries for a VerDef are all + // encoded sequentially after the VerDef, so we could likely just + // use the normal pattern here and pass in &mut self.offset here. + // + // The spec claims that "The section shall contain an array of + // Elfxx_Verdef structures, optionally followed by an array of + // Elfxx_Verdaux structures." This reads a bit ambiguously + // (is there one big array of Verdefs followed by one big array of + // Verdauxs?). If so, the vd_next and vda_next links seem unnecessary + // given the vd_cnt field. In practice, it appears that all the VerDefAux + // fields for a given VerDef are sequentially following the VerDef, meaning + // they're contiguous, but intersersed. The _next fields could theoretically + // give non-contiguous linked-list-like configurations, though (but only linking + // forward, not backward, since the link is a u32). + // + // The vd_next and vda_next fields are also not "pointers" i.e. offsets from + // the start of the section, but rather "increments" in telling how far to + // advance from where you just read the containing struct for where you should + // read the next. Given the sequentially-following nature described, these vd_next + // and vda_next fields end up being 0x14 and 0x8 (the size of the VerDef and + // VerDefAux structs). + // + // So observationally, we could likely get away with using self.offset and count here + // and ignoring the vda_next field, but that'd break things if they weren't contiguous. + let mut start = self.offset; + let vda = VerDefAux::parse_at(self.endian, self.class, &mut start, self.data).ok()?; + + // If offset overflows, silently end iteration + match self.offset.checked_add(vda.vda_next as usize) { + Some(new_off) => self.offset = new_off, + None => self.count = 0, + } + self.count -= 1; + + // Silently end iteration early if the next link stops pointing somewhere new + // TODO: Make this an error condition by allowing the iterator to yield a ParseError + if self.count > 0 && vda.vda_next == 0 { + self.count = 0 + } + Some(vda) + } +} + +/////////////////////////////////////////////////////////////////////////////// +// _ // +// __ _ _ __ _ _ __ _____ _ __ ___(_) ___ _ __ _ __ // +// / _` | '_ \| | | | \ \ / / _ \ '__/ __| |/ _ \| '_ \ | '__| // +// _ | (_| | | | | |_| | _ \ V / __/ | \__ \ | (_) | | | | | | // +// (_) \__, |_| |_|\__,_| (_) \_/ \___|_| |___/_|\___/|_| |_|_____|_| // +// |___/ |_____| // +/////////////////////////////////////////////////////////////////////////////// + +/// The GNU extension section .gnu.version_r has a section type of SHT_GNU_VERNEED. +/// This section contains required symbol version definitions. The number of +/// entries in this section shall be contained in the DT_VERNEEDNUM entry of the +/// Dynamic Section .dynamic and also the sh_info member of the section header. +/// The sh_link member of the section header shall point to the referenced +/// string table section. +/// +/// The section shall contain an array of VerNeed structures optionally +/// followed by an array of VerNeedAux structures. +#[derive(Debug, PartialEq, Eq)] +pub struct VerNeed { + /// Number of associated verneed array entries. + pub vn_cnt: u16, + /// Offset to the file name string in the linked string table, in bytes. + pub vn_file: u32, + /// Offset to a corresponding entry in the VerNeedAux array, in bytes. + vn_aux: u32, + /// Offset to the next VerNeed entry, in bytes. + vn_next: u32, +} + +impl ParseAt for VerNeed { + fn parse_at( + endian: E, + _class: Class, + offset: &mut usize, + data: &[u8], + ) -> Result { + let vd_version = endian.parse_u16_at(offset, data)?; + if vd_version != abi::VER_NEED_CURRENT { + return Err(ParseError::UnsupportedVersion(( + vd_version as u64, + abi::VER_DEF_CURRENT as u64, + ))); + } + Ok(VerNeed { + vn_cnt: endian.parse_u16_at(offset, data)?, + vn_file: endian.parse_u32_at(offset, data)?, + vn_aux: endian.parse_u32_at(offset, data)?, + vn_next: endian.parse_u32_at(offset, data)?, + }) + } + + #[inline] + fn size_for(_class: Class) -> usize { + ELFVERNEEDSIZE + } +} + +const ELFVERNEEDSIZE: usize = 16; + +#[derive(Debug, Copy, Clone)] +pub struct VerNeedIterator<'data, E: EndianParse> { + endian: E, + class: Class, + /// The number of entries in this iterator is given by the .dynamic DT_VERNEEDNUM entry + /// and also in the .gnu.version_r section header's sh_info field. + count: u64, + data: &'data [u8], + offset: usize, +} + +impl<'data, E: EndianParse> VerNeedIterator<'data, E> { + pub fn new( + endian: E, + class: Class, + count: u64, + starting_offset: usize, + data: &'data [u8], + ) -> Self { + VerNeedIterator { + endian, + class, + count, + data, + offset: starting_offset, + } + } +} + +impl<'data, E: EndianParse> Iterator for VerNeedIterator<'data, E> { + type Item = (VerNeed, VerNeedAuxIterator<'data, E>); + fn next(&mut self) -> Option { + if self.data.is_empty() || self.count == 0 { + return None; + } + + let mut start = self.offset; + let vn = VerNeed::parse_at(self.endian, self.class, &mut start, self.data).ok()?; + let vna_iter = VerNeedAuxIterator::new( + self.endian, + self.class, + vn.vn_cnt, + self.offset + vn.vn_aux as usize, + self.data, + ); + + // If offset overflows, silently end iteration + match self.offset.checked_add(vn.vn_next as usize) { + Some(new_off) => self.offset = new_off, + None => self.count = 0, + } + self.count -= 1; + + // Silently end iteration early if the next link stops pointing somewhere new + // TODO: Make this an error condition by allowing the iterator to yield a ParseError + if self.count > 0 && vn.vn_next == 0 { + self.count = 0 + } + Some((vn, vna_iter)) + } +} + +/// Version Need Auxiliary Entries from the .gnu.version_r section +#[derive(Debug, PartialEq, Eq)] +pub struct VerNeedAux { + /// Dependency name hash value (ELF hash function). + pub vna_hash: u32, + /// Dependency information flag bitmask. + pub vna_flags: u16, + /// VersionIndex value used in the .gnu.version symbol version array. + pub vna_other: u16, + /// Offset to the dependency name string in the linked string table, in bytes. + pub vna_name: u32, + /// Offset to the next vernaux entry, in bytes. + vna_next: u32, +} + +impl ParseAt for VerNeedAux { + fn parse_at( + endian: E, + _class: Class, + offset: &mut usize, + data: &[u8], + ) -> Result { + Ok(VerNeedAux { + vna_hash: endian.parse_u32_at(offset, data)?, + vna_flags: endian.parse_u16_at(offset, data)?, + vna_other: endian.parse_u16_at(offset, data)?, + vna_name: endian.parse_u32_at(offset, data)?, + vna_next: endian.parse_u32_at(offset, data)?, + }) + } + + #[inline] + fn size_for(_class: Class) -> usize { + 16 + } +} + +#[derive(Debug)] +pub struct VerNeedAuxIterator<'data, E: EndianParse> { + endian: E, + class: Class, + count: u16, + data: &'data [u8], + offset: usize, +} + +impl<'data, E: EndianParse> VerNeedAuxIterator<'data, E> { + pub fn new( + endian: E, + class: Class, + count: u16, + starting_offset: usize, + data: &'data [u8], + ) -> Self { + VerNeedAuxIterator { + endian, + class, + count, + data, + offset: starting_offset, + } + } +} + +impl Iterator for VerNeedAuxIterator<'_, E> { + type Item = VerNeedAux; + fn next(&mut self) -> Option { + if self.data.is_empty() || self.count == 0 { + return None; + } + + let mut start = self.offset; + let vna = VerNeedAux::parse_at(self.endian, self.class, &mut start, self.data).ok()?; + + // If offset overflows, silently end iteration + match self.offset.checked_add(vna.vna_next as usize) { + Some(new_off) => self.offset = new_off, + None => self.count = 0, + } + self.count -= 1; + + // Silently end iteration early if the next link stops pointing somewhere new + // TODO: Make this an error condition by allowing the iterator to yield a ParseError + if self.count > 0 && vna.vna_next == 0 { + self.count = 0 + } + Some(vna) + } +} + +////////////////////////////// +// _____ _ // +// |_ _|__ ___| |_ ___ // +// | |/ _ \/ __| __/ __| // +// | | __/\__ \ |_\__ \ // +// |_|\___||___/\__|___/ // +// // +////////////////////////////// + +#[cfg(test)] +mod iter_tests { + use super::*; + use crate::endian::LittleEndian; + + #[rustfmt::skip] + const GNU_VERNEED_STRINGS: [u8; 65] = [ + // ZLIB_1.2.0 (0x1) + 0x00, 0x5a, 0x4c, 0x49, 0x42, 0x5f, 0x31, 0x2e, 0x32, 0x2e, 0x30, 0x00, + // GLIBC_2.33 (0xC) + 0x47, 0x4c, 0x49, 0x42, 0x43, 0x5f, 0x32, 0x2e, 0x33, 0x33, 0x00, + // GLIBC_2.2.5 (0x17) + 0x47, 0x4c, 0x49, 0x42, 0x43, 0x5f, 0x32, 0x2e, 0x32, 0x2e, 0x35, 0x00, + // libz.so.1 (0x23) + 0x6c, 0x69, 0x62, 0x7a, 0x2e, 0x73, 0x6f, 0x2e, 0x31, 0x00, + // libc.so.6 (0x2D) + 0x6c, 0x69, 0x62, 0x63, 0x2e, 0x73, 0x6f, 0x2e, 0x36, 0x00, + // GLIBC_2.3 (0x37) + 0x47, 0x4c, 0x49, 0x42, 0x43, 0x5f, 0x32, 0x2e, 0x33, 0x00, + ]; + + #[rustfmt::skip] + const GNU_VERNEED_DATA: [u8; 96] = [ + // {vn_version, vn_cnt, vn_file, vn_aux, vn_next } + 0x01, 0x00, 0x01, 0x00, 0x23, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, + // {vn_hash, vn_flags, vn_other, vn_name, vn_next } + 0xc0, 0xe5, 0x27, 0x08, 0x00, 0x00, 0x0a, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + // {vn_version, vn_cnt, vn_file, vn_aux, vn_next } + 0x01, 0x00, 0x03, 0x00, 0x2D, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + // {vn_hash, vn_flags, vn_other, vn_name, vn_next } + 0x13, 0x69, 0x69, 0x0d, 0x00, 0x00, 0x0c, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, + // {vn_hash, vn_flags, vn_other, vn_name, vn_next } + 0xb3, 0x91, 0x96, 0x06, 0x00, 0x00, 0x0b, 0x00, 0x17, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, + // {vn_hash, vn_flags, vn_other, vn_name, vn_next } + 0x94, 0x91, 0x96, 0x06, 0x00, 0x00, 0x09, 0x00, 0x37, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + ]; + + #[test] + fn verneed_iter() { + let iter = VerNeedIterator::new(LittleEndian, Class::ELF64, 2, 0, &GNU_VERNEED_DATA); + let entries: Vec<(VerNeed, Vec)> = + iter.map(|(vn, iter)| (vn, iter.collect())).collect(); + + assert_eq!(entries.len(), 2); + } + + #[test] + fn verneed_iter_early_termination_on_broken_next_link() { + // set count = 3 even though there's only 2 entries + let iter = VerNeedIterator::new(LittleEndian, Class::ELF64, 3, 0, &GNU_VERNEED_DATA); + let entries: Vec<(VerNeed, Vec)> = + iter.map(|(vn, iter)| (vn, iter.collect())).collect(); + + // TODO: make this a ParseError condition instead of silently returning only the good data. + assert_eq!(entries.len(), 2); + } + + #[test] + fn verneedaux_iter_one_entry() { + let mut iter = + VerNeedAuxIterator::new(LittleEndian, Class::ELF64, 1, 0x10, &GNU_VERNEED_DATA); + let aux1 = iter.next().expect("Failed to parse"); + assert_eq!( + aux1, + VerNeedAux { + vna_hash: 0x0827e5c0, + vna_flags: 0, + vna_other: 0x0a, + vna_name: 0x01, + vna_next: 0 + } + ); + assert!(iter.next().is_none()); + } + + #[test] + fn verneedaux_iter_multiple_entries() { + let mut iter = + VerNeedAuxIterator::new(LittleEndian, Class::ELF64, 3, 0x30, &GNU_VERNEED_DATA); + let aux1 = iter.next().expect("Failed to parse"); + assert_eq!( + aux1, + VerNeedAux { + vna_hash: 0x0d696913, + vna_flags: 0, + vna_other: 0x0c, + vna_name: 0x0c, + vna_next: 0x10 + } + ); + let aux2 = iter.next().expect("Failed to parse"); + assert_eq!( + aux2, + VerNeedAux { + vna_hash: 0x069691b3, + vna_flags: 0, + vna_other: 0x0b, + vna_name: 0x17, + vna_next: 0x10 + } + ); + let aux3 = iter.next().expect("Failed to parse"); + assert_eq!( + aux3, + VerNeedAux { + vna_hash: 0x06969194, + vna_flags: 0, + vna_other: 0x09, + vna_name: 0x37, + vna_next: 0 + } + ); + assert!(iter.next().is_none()); + } + + // Hypothetical case where VerDefAux entries are non-contiguous + #[test] + fn verneedaux_iter_two_lists_interspersed() { + #[rustfmt::skip] + let data: [u8; 64] = [ + // {vn_hash, vn_flags, vn_other, vn_name, vn_next } + 0xc0, 0xe5, 0x27, 0x08, 0x00, 0x00, 0x0a, 0x00, 0xcc, 0x0c, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, + // {vn_hash, vn_flags, vn_other, vn_name, vn_next } + 0x13, 0x69, 0x69, 0x0d, 0x00, 0x00, 0x0c, 0x00, 0xd7, 0x0c, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, + // {vn_hash, vn_flags, vn_other, vn_name, vn_next } + 0xb3, 0x91, 0x96, 0x06, 0x00, 0x00, 0x0b, 0x00, 0xe1, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + // {vn_hash, vn_flags, vn_other, vn_name, vn_next } + 0x94, 0x91, 0x96, 0x06, 0x00, 0x00, 0x09, 0x00, 0xec, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + ]; + + let mut iter1 = VerNeedAuxIterator::new(LittleEndian, Class::ELF64, 2, 0, &data); + let mut iter2 = VerNeedAuxIterator::new(LittleEndian, Class::ELF64, 2, 0x10, &data); + + let aux1_1 = iter1.next().expect("Failed to parse"); + assert_eq!( + aux1_1, + VerNeedAux { + vna_hash: 0x0827e5c0, + vna_flags: 0, + vna_other: 0x0a, + vna_name: 0x0ccc, + vna_next: 0x20, + } + ); + let aux2_1 = iter2.next().expect("Failed to parse"); + assert_eq!( + aux2_1, + VerNeedAux { + vna_hash: 0x0d696913, + vna_flags: 0, + vna_other: 0x0c, + vna_name: 0x0cd7, + vna_next: 0x20 + } + ); + let aux1_2 = iter1.next().expect("Failed to parse"); + assert_eq!( + aux1_2, + VerNeedAux { + vna_hash: 0x069691b3, + vna_flags: 0, + vna_other: 0x0b, + vna_name: 0x0ce1, + vna_next: 0 + } + ); + let aux2_2 = iter2.next().expect("Failed to parse"); + assert_eq!( + aux2_2, + VerNeedAux { + vna_hash: 0x06969194, + vna_flags: 0, + vna_other: 0x09, + vna_name: 0x0cec, + vna_next: 0 + } + ); + assert!(iter1.next().is_none()); + assert!(iter2.next().is_none()); + } + + #[test] + fn verneedaux_iter_early_termination_on_broken_next_link() { + // set count = 7 even though there's only 1 entry + let iter = VerNeedAuxIterator::new(LittleEndian, Class::ELF64, 7, 0x10, &GNU_VERNEED_DATA); + let entries: Vec = iter.collect(); + + // TODO: make this a ParseError condition instead of silently returning only the good data. + assert_eq!(entries.len(), 1); + } + + #[rustfmt::skip] + const GNU_VERDEF_STRINGS: [u8; 34] = [ + // LIBCTF_1.0 (0x1) + 0x00, 0x4c, 0x49, 0x42, 0x43, 0x54, 0x46, 0x5f, 0x31, 0x2e, 0x30, 0x00, + // LIBCTF_1.1 (0xC) + 0x4c, 0x49, 0x42, 0x43, 0x54, 0x46, 0x5f, 0x31, 0x2e, 0x31, 0x00, + // LIBCTF_1.2 (0x17) + 0x4c, 0x49, 0x42, 0x43, 0x54, 0x46, 0x5f, 0x31, 0x2e, 0x32, 0x00, + ]; + + // Sample .gnu.version_d section contents + #[rustfmt::skip] + const GNU_VERDEF_DATA: [u8; 128] = [ + // {vd_version, vd_flags, vd_ndx, vd_cnt + 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, + // vd_hash, vd_aux, + 0xb0, 0x7a, 0x07, 0x0b, 0x14, 0x00, 0x00, 0x00, + // vd_next}, {vda_name, + 0x1c, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + // vda_next}, {vd_version, vd_flags, + 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + // vd_ndx, vd_cnt, vd_hash, + 0x02, 0x00, 0x01, 0x00, 0x70, 0x2f, 0x8f, 0x08, + // vd_aux, vd_next}, + 0x14, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x00, + // {vda_name, vda_next}, + 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + // {vd_version, vd_flags, vd_ndx, vd_cnt + 0x01, 0x00, 0x00, 0x00, 0x03, 0x00, 0x02, 0x00, + // vd_hash, vd_aux, + 0x71, 0x2f, 0x8f, 0x08, 0x14, 0x00, 0x00, 0x00, + // vd_next}, {vda_name, + 0x24, 0x00, 0x00, 0x00, 0x17, 0x00, 0x00, 0x00, + // vda_next}, {vda_name, + 0x08, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, + // vda_next}, {vd_version, vd_flags, + 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + // vd_ndx, vd_cnt, vd_hash, + 0x04, 0x00, 0x02, 0x00, 0x72, 0x2f, 0x8f, 0x08, + // vd_aux, vd_next}, + 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + // {vda_name, vda_next}, + 0x0c, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, + // {vda_name, vda_next}, + 0x17, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + ]; + + #[test] + fn verdef_iter() { + let iter = VerDefIterator::new(LittleEndian, Class::ELF64, 4, 0, &GNU_VERDEF_DATA); + let entries: Vec<(VerDef, Vec)> = + iter.map(|(vd, iter)| (vd, iter.collect())).collect(); + + assert_eq!(entries.len(), 4); + + assert_eq!( + entries, + vec![ + ( + VerDef { + vd_flags: 1, + vd_ndx: 1, + vd_cnt: 1, + vd_hash: 0x0B077AB0, + vd_aux: 20, + vd_next: 28, + }, + vec![VerDefAux { + vda_name: 0x1, + vda_next: 0 + }] + ), + ( + VerDef { + vd_flags: 0, + vd_ndx: 2, + vd_cnt: 1, + vd_hash: 0x088f2f70, + vd_aux: 20, + vd_next: 28, + }, + vec![VerDefAux { + vda_name: 0xC, + vda_next: 0 + }] + ), + ( + VerDef { + vd_flags: 0, + vd_ndx: 3, + vd_cnt: 2, + vd_hash: 0x088f2f71, + vd_aux: 20, + vd_next: 36, + }, + vec![ + VerDefAux { + vda_name: 0x17, + vda_next: 8 + }, + VerDefAux { + vda_name: 0xC, + vda_next: 0 + } + ] + ), + ( + VerDef { + vd_flags: 0, + vd_ndx: 4, + vd_cnt: 2, + vd_hash: 0x088f2f72, + vd_aux: 20, + vd_next: 0, + }, + vec![ + VerDefAux { + vda_name: 0xC, + vda_next: 8 + }, + VerDefAux { + vda_name: 0x17, + vda_next: 0 + } + ] + ), + ] + ); + } + + #[test] + fn verdef_iter_early_termination_on_broken_next_link() { + // set count = 7 even though there's only 4 entries + let iter = VerDefIterator::new(LittleEndian, Class::ELF64, 7, 0, &GNU_VERDEF_DATA); + let entries: Vec<(VerDef, Vec)> = + iter.map(|(vn, iter)| (vn, iter.collect())).collect(); + + // TODO: make this a ParseError condition instead of silently returning only the good data. + assert_eq!(entries.len(), 4); + } + + #[test] + fn verdefaux_iter_one_entry() { + let mut iter = + VerDefAuxIterator::new(LittleEndian, Class::ELF64, 1, 0x14, &GNU_VERDEF_DATA); + let aux1 = iter.next().expect("Failed to parse"); + assert_eq!( + aux1, + VerDefAux { + vda_name: 0x01, + vda_next: 0 + } + ); + assert!(iter.next().is_none()); + } + + #[test] + fn verdefaux_iter_multiple_entries() { + let mut iter = + VerDefAuxIterator::new(LittleEndian, Class::ELF64, 2, 0x4C, &GNU_VERDEF_DATA); + let aux1 = iter.next().expect("Failed to parse"); + assert_eq!( + aux1, + VerDefAux { + vda_name: 0x17, + vda_next: 8 + } + ); + let aux1 = iter.next().expect("Failed to parse"); + assert_eq!( + aux1, + VerDefAux { + vda_name: 0xC, + vda_next: 0 + } + ); + assert!(iter.next().is_none()); + } + + // Hypothetical case where VerDefAux entries are non-contiguous + #[test] + fn verdefaux_iter_two_lists_interspersed() { + #[rustfmt::skip] + let data: [u8; 32] = [ + 0x01, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, // list 1 entry 1 + 0xA1, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, // list 2 entry 1 + 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // list 1 entry 2 + 0xA2, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // list 2 entry 2 + ]; + + let mut iter1 = VerDefAuxIterator::new(LittleEndian, Class::ELF64, 2, 0, &data); + let mut iter2 = VerDefAuxIterator::new(LittleEndian, Class::ELF64, 2, 8, &data); + + let aux1_1 = iter1.next().expect("Failed to parse"); + assert_eq!( + aux1_1, + VerDefAux { + vda_name: 0x0001, + vda_next: 0x10, + } + ); + let aux2_1 = iter2.next().expect("Failed to parse"); + assert_eq!( + aux2_1, + VerDefAux { + vda_name: 0x00A1, + vda_next: 0x10, + } + ); + let aux1_2 = iter1.next().expect("Failed to parse"); + assert_eq!( + aux1_2, + VerDefAux { + vda_name: 0x0002, + vda_next: 0, + } + ); + let aux2_2 = iter2.next().expect("Failed to parse"); + assert_eq!( + aux2_2, + VerDefAux { + vda_name: 0x00A2, + vda_next: 0, + } + ); + assert!(iter1.next().is_none()); + assert!(iter2.next().is_none()); + } + + #[test] + fn verdefaux_iter_early_termination_on_broken_next_link() { + // set count = 7 even though there's only 1 entry + let iter = VerDefAuxIterator::new(LittleEndian, Class::ELF64, 7, 0x14, &GNU_VERDEF_DATA); + let entries: Vec = iter.collect(); + + // TODO: make this a ParseError condition instead of silently returning only the good data. + assert_eq!(entries.len(), 1); + } + + #[test] + fn version_table() { + let ver_idx_buf: [u8; 10] = [0x02, 0x00, 0x03, 0x00, 0x09, 0x00, 0x0A, 0x00, 0xff, 0xff]; + let version_ids = VersionIndexTable::new(LittleEndian, Class::ELF64, &ver_idx_buf); + let verdefs = VerDefIterator::new(LittleEndian, Class::ELF64, 4, 0, &GNU_VERDEF_DATA); + let verneed_strs = StringTable::new(&GNU_VERNEED_STRINGS); + let verneeds = VerNeedIterator::new(LittleEndian, Class::ELF64, 2, 0, &GNU_VERNEED_DATA); + let verdef_strs = StringTable::new(&GNU_VERDEF_STRINGS); + + let table = SymbolVersionTable::new( + version_ids, + Some((verneeds, verneed_strs)), + Some((verdefs, verdef_strs)), + ); + + let def1 = table + .get_definition(0) + .expect("Failed to parse definition") + .expect("Failed to find def"); + assert_eq!(def1.hash, 0x088f2f70); + assert_eq!(def1.flags, 0); + let def1_names: Vec<&str> = def1 + .names + .map(|res| res.expect("Failed to parse")) + .collect(); + assert_eq!(def1_names, ["LIBCTF_1.1"]); + assert!(!def1.hidden); + + let def2 = table + .get_definition(1) + .expect("Failed to parse definition") + .expect("Failed to find def"); + assert_eq!(def2.hash, 0x088f2f71); + assert_eq!(def2.flags, 0); + let def2_names: Vec<&str> = def2 + .names + .map(|res| res.expect("Failed to parse")) + .collect(); + assert_eq!(def2_names, ["LIBCTF_1.2", "LIBCTF_1.1"]); + assert!(!def2.hidden); + + let req1 = table + .get_requirement(2) + .expect("Failed to parse definition") + .expect("Failed to find req"); + assert_eq!( + req1, + SymbolRequirement { + file: "libc.so.6", + name: "GLIBC_2.3", + hash: 0x6969194, + flags: 0, + hidden: false + } + ); + + let req2 = table + .get_requirement(3) + .expect("Failed to parse definition") + .expect("Failed to find req"); + assert_eq!( + req2, + SymbolRequirement { + file: "libz.so.1", + name: "ZLIB_1.2.0", + hash: 0x827E5C0, + flags: 0, + hidden: false + } + ); + + // The last version_index points to non-existent definitions. Maybe we should treat + // this as a ParseError instead of returning an empty Option? This can only happen + // if .gnu.versions[N] contains an index that doesn't exist, which is likely a file corruption + // or programmer error (i.e asking for a definition for an undefined symbol) + assert!(table.get_definition(4).expect("Failed to parse").is_none()); + assert!(table.get_requirement(4).expect("Failed to parse").is_none()); + } +} + +#[cfg(test)] +mod parse_tests { + use super::*; + use crate::endian::{BigEndian, LittleEndian}; + use crate::parse::{test_parse_for, test_parse_fuzz_too_short}; + + #[test] + fn parse_verndx32_lsb() { + test_parse_for(LittleEndian, Class::ELF32, VersionIndex(0x0100)); + } + + #[test] + fn parse_verndx32_msb() { + test_parse_for(BigEndian, Class::ELF32, VersionIndex(0x0001)); + } + + #[test] + fn parse_verndx64_lsb() { + test_parse_for(LittleEndian, Class::ELF64, VersionIndex(0x0100)); + } + + #[test] + fn parse_verndx64_msb() { + test_parse_for(BigEndian, Class::ELF64, VersionIndex(0x0001)); + } + + #[test] + fn parse_verndx32_lsb_fuzz_too_short() { + test_parse_fuzz_too_short::<_, VersionIndex>(LittleEndian, Class::ELF32); + } + + #[test] + fn parse_verndx32_msb_fuzz_too_short() { + test_parse_fuzz_too_short::<_, VersionIndex>(BigEndian, Class::ELF32); + } + + #[test] + fn parse_verndx64_lsb_fuzz_too_short() { + test_parse_fuzz_too_short::<_, VersionIndex>(LittleEndian, Class::ELF64); + } + + #[test] + fn parse_verndx64_msb_fuzz_too_short() { + test_parse_fuzz_too_short::<_, VersionIndex>(BigEndian, Class::ELF64); + } + + // + // VerDef + // + #[test] + fn parse_verdef32_lsb() { + let mut data = [0u8; ELFVERDEFSIZE]; + for (n, elem) in data.iter_mut().enumerate().take(ELFVERDEFSIZE) { + *elem = n as u8; + } + data[0] = 1; + data[1] = 0; + + let mut offset = 0; + let entry = VerDef::parse_at(LittleEndian, Class::ELF32, &mut offset, data.as_ref()) + .expect("Failed to parse VerDef"); + + assert_eq!( + entry, + VerDef { + vd_flags: 0x0302, + vd_ndx: 0x0504, + vd_cnt: 0x0706, + vd_hash: 0x0B0A0908, + vd_aux: 0x0F0E0D0C, + vd_next: 0x13121110, + } + ); + assert_eq!(offset, ELFVERDEFSIZE); + } + + #[test] + fn parse_verdef32_fuzz_too_short() { + let mut data = [0u8; ELFVERDEFSIZE]; + data[1] = 1; + for n in 0..ELFVERDEFSIZE { + let buf = data.split_at(n).0; + let mut offset: usize = 0; + let error = VerDef::parse_at(BigEndian, Class::ELF32, &mut offset, buf) + .expect_err("Expected an error"); + assert!( + matches!(error, ParseError::SliceReadError(_)), + "Unexpected Error type found: {error}" + ); + } + } + + #[test] + fn parse_verdef64_msb() { + let mut data = [0u8; ELFVERDEFSIZE]; + for (n, elem) in data.iter_mut().enumerate().take(ELFVERDEFSIZE).skip(2) { + *elem = n as u8; + } + data[1] = 1; + + let mut offset = 0; + let entry = VerDef::parse_at(BigEndian, Class::ELF64, &mut offset, data.as_ref()) + .expect("Failed to parse VerDef"); + + assert_eq!( + entry, + VerDef { + vd_flags: 0x0203, + vd_ndx: 0x0405, + vd_cnt: 0x0607, + vd_hash: 0x08090A0B, + vd_aux: 0x0C0D0E0F, + vd_next: 0x10111213, + } + ); + assert_eq!(offset, ELFVERDEFSIZE); + } + + #[test] + fn parse_verdef64_fuzz_too_short() { + let mut data = [0u8; ELFVERDEFSIZE]; + data[1] = 1; + for n in 0..ELFVERDEFSIZE { + let buf = data.split_at(n).0; + let mut offset: usize = 0; + let error = VerDef::parse_at(BigEndian, Class::ELF64, &mut offset, buf) + .expect_err("Expected an error"); + assert!( + matches!(error, ParseError::SliceReadError(_)), + "Unexpected Error type found: {error}" + ); + } + } + + #[test] + fn parse_verdef_bad_version_errors() { + let data = [0u8; ELFVERDEFSIZE]; + // version is 0, which is not 1, which is bad :) + + let mut offset = 0; + let err = VerDef::parse_at(BigEndian, Class::ELF64, &mut offset, data.as_ref()) + .expect_err("Expected an error"); + assert!( + matches!(err, ParseError::UnsupportedVersion((0, 1))), + "Unexpected Error type found: {err}" + ); + } + + // + // VerDefAux + // + #[test] + fn parse_verdefaux32_lsb() { + test_parse_for( + LittleEndian, + Class::ELF32, + VerDefAux { + vda_name: 0x03020100, + vda_next: 0x07060504, + }, + ); + } + + #[test] + fn parse_verdefaux32_msb() { + test_parse_for( + BigEndian, + Class::ELF32, + VerDefAux { + vda_name: 0x00010203, + vda_next: 0x04050607, + }, + ); + } + + #[test] + fn parse_verdefaux64_lsb() { + test_parse_for( + LittleEndian, + Class::ELF64, + VerDefAux { + vda_name: 0x03020100, + vda_next: 0x07060504, + }, + ); + } + + #[test] + fn parse_verdefaux64_msb() { + test_parse_for( + BigEndian, + Class::ELF64, + VerDefAux { + vda_name: 0x00010203, + vda_next: 0x04050607, + }, + ); + } + + #[test] + fn parse_verdefaux32_lsb_fuzz_too_short() { + test_parse_fuzz_too_short::<_, VerDefAux>(LittleEndian, Class::ELF32); + } + + #[test] + fn parse_verdefaux32_msb_fuzz_too_short() { + test_parse_fuzz_too_short::<_, VerDefAux>(BigEndian, Class::ELF32); + } + + #[test] + fn parse_verdefaux64_lsb_fuzz_too_short() { + test_parse_fuzz_too_short::<_, VerDefAux>(LittleEndian, Class::ELF64); + } + + #[test] + fn parse_verdefaux64_msb_fuzz_too_short() { + test_parse_fuzz_too_short::<_, VerDefAux>(BigEndian, Class::ELF64); + } + + // + // VerNeed + // + #[test] + fn parse_verneed32_lsb() { + let mut data = [0u8; ELFVERNEEDSIZE]; + for (n, elem) in data.iter_mut().enumerate().take(ELFVERNEEDSIZE) { + *elem = n as u8; + } + data[0] = 1; + data[1] = 0; + + let mut offset = 0; + let entry = VerNeed::parse_at(LittleEndian, Class::ELF32, &mut offset, data.as_ref()) + .expect("Failed to parse VerNeed"); + + assert_eq!( + entry, + VerNeed { + vn_cnt: 0x0302, + vn_file: 0x07060504, + vn_aux: 0x0B0A0908, + vn_next: 0x0F0E0D0C, + } + ); + assert_eq!(offset, ELFVERNEEDSIZE); + } + + #[test] + fn parse_verneed32_fuzz_too_short() { + let mut data = [0u8; ELFVERNEEDSIZE]; + data[1] = 1; + for n in 0..ELFVERNEEDSIZE { + let buf = data.split_at(n).0; + let mut offset: usize = 0; + let error = VerNeed::parse_at(BigEndian, Class::ELF32, &mut offset, buf) + .expect_err("Expected an error"); + assert!( + matches!(error, ParseError::SliceReadError(_)), + "Unexpected Error type found: {error}" + ); + } + } + + #[test] + fn parse_verneed64_msb() { + let mut data = [0u8; ELFVERNEEDSIZE]; + for (n, elem) in data.iter_mut().enumerate().take(ELFVERNEEDSIZE) { + *elem = n as u8; + } + data[1] = 1; + + let mut offset = 0; + let entry = VerNeed::parse_at(BigEndian, Class::ELF64, &mut offset, data.as_ref()) + .expect("Failed to parse VerNeed"); + + assert_eq!( + entry, + VerNeed { + vn_cnt: 0x0203, + vn_file: 0x04050607, + vn_aux: 0x08090A0B, + vn_next: 0x0C0D0E0F, + } + ); + assert_eq!(offset, ELFVERNEEDSIZE); + } + + #[test] + fn parse_verneed64_fuzz_too_short() { + let mut data = [0u8; ELFVERNEEDSIZE]; + data[1] = 1; + for n in 0..ELFVERNEEDSIZE { + let buf = data.split_at(n).0; + let mut offset: usize = 0; + let error = VerNeed::parse_at(BigEndian, Class::ELF64, &mut offset, buf) + .expect_err("Expected an error"); + assert!( + matches!(error, ParseError::SliceReadError(_)), + "Unexpected Error type found: {error}" + ); + } + } + + // + // VerNeedAux + // + #[test] + fn parse_verneedaux32_lsb() { + test_parse_for( + LittleEndian, + Class::ELF32, + VerNeedAux { + vna_hash: 0x03020100, + vna_flags: 0x0504, + vna_other: 0x0706, + vna_name: 0x0B0A0908, + vna_next: 0x0F0E0D0C, + }, + ); + } + + #[test] + fn parse_verneedaux32_msb() { + test_parse_for( + BigEndian, + Class::ELF32, + VerNeedAux { + vna_hash: 0x00010203, + vna_flags: 0x0405, + vna_other: 0x0607, + vna_name: 0x08090A0B, + vna_next: 0x0C0D0E0F, + }, + ); + } + + #[test] + fn parse_verneedaux64_lsb() { + test_parse_for( + LittleEndian, + Class::ELF64, + VerNeedAux { + vna_hash: 0x03020100, + vna_flags: 0x0504, + vna_other: 0x0706, + vna_name: 0x0B0A0908, + vna_next: 0x0F0E0D0C, + }, + ); + } + + #[test] + fn parse_verneedaux64_msb() { + test_parse_for( + BigEndian, + Class::ELF64, + VerNeedAux { + vna_hash: 0x00010203, + vna_flags: 0x0405, + vna_other: 0x0607, + vna_name: 0x08090A0B, + vna_next: 0x0C0D0E0F, + }, + ); + } + + #[test] + fn parse_verneedaux32_lsb_fuzz_too_short() { + test_parse_fuzz_too_short::<_, VerNeedAux>(LittleEndian, Class::ELF32); + } + + #[test] + fn parse_verneedaux32_msb_fuzz_too_short() { + test_parse_fuzz_too_short::<_, VerNeedAux>(BigEndian, Class::ELF32); + } + + #[test] + fn parse_verneedaux64_lsb_fuzz_too_short() { + test_parse_fuzz_too_short::<_, VerNeedAux>(LittleEndian, Class::ELF64); + } + + #[test] + fn parse_verneedaux64_msb_fuzz_too_short() { + test_parse_fuzz_too_short::<_, VerNeedAux>(BigEndian, Class::ELF64); + } +} + +#[cfg(test)] +mod version_index_tests { + use super::*; + + #[test] + fn is_local() { + let idx = VersionIndex(0); + assert!(idx.is_local()); + } + + #[test] + fn is_global() { + let idx = VersionIndex(1); + assert!(idx.is_global()); + } + + #[test] + fn index_visible() { + let idx = VersionIndex(42); + assert_eq!(idx.index(), 42); + assert!(!idx.is_hidden()); + } + + #[test] + fn index_hidden() { + let idx = VersionIndex(42 | abi::VER_NDX_HIDDEN); + assert_eq!(idx.index(), 42); + assert!(idx.is_hidden()); + } +} diff --git a/src/third_party/rust-elf/src/hash.rs b/src/third_party/rust-elf/src/hash.rs new file mode 100644 index 0000000..eb461f4 --- /dev/null +++ b/src/third_party/rust-elf/src/hash.rs @@ -0,0 +1,495 @@ +//! Parsing hash table sections for symbol tables: `.hash`, and `.gnu.hash` +use core::mem::size_of; + +use crate::endian::EndianParse; +use crate::file::Class; +use crate::parse::{ParseAt, ParseError, ParsingTable, ReadBytesExt}; +use crate::string_table::StringTable; +use crate::symbol::{Symbol, SymbolTable}; + +impl ParseAt for u32 { + fn parse_at( + endian: E, + _class: Class, + offset: &mut usize, + data: &[u8], + ) -> Result { + endian.parse_u32_at(offset, data) + } + + #[inline] + fn size_for(_class: Class) -> usize { + core::mem::size_of::() + } +} + +type U32Table<'data, E> = ParsingTable<'data, E, u32>; + +/// Header at the start of SysV Hash Table sections of type [SHT_HASH](crate::abi::SHT_HASH). +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct SysVHashHeader { + pub nbucket: u32, + pub nchain: u32, +} + +impl ParseAt for SysVHashHeader { + fn parse_at( + endian: E, + _class: Class, + offset: &mut usize, + data: &[u8], + ) -> Result { + Ok(SysVHashHeader { + nbucket: endian.parse_u32_at(offset, data)?, + nchain: endian.parse_u32_at(offset, data)?, + }) + } + + #[inline] + fn size_for(_class: Class) -> usize { + size_of::() + size_of::() + } +} + +/// Calculate the SysV hash value for a given symbol name. +pub fn sysv_hash(name: &[u8]) -> u32 { + let mut hash = 0u32; + for byte in name { + hash = hash.wrapping_mul(16).wrapping_add(*byte as u32); + hash ^= (hash >> 24) & 0xf0; + } + hash & 0xfffffff +} + +#[derive(Debug)] +pub struct SysVHashTable<'data, E: EndianParse> { + buckets: U32Table<'data, E>, + chains: U32Table<'data, E>, +} + +/// This constructs a lazy-parsing type that keeps a reference to the provided data +/// bytes from which it lazily parses and interprets its contents. +impl<'data, E: EndianParse> SysVHashTable<'data, E> { + /// Construct a SysVHashTable from given bytes. Keeps a reference to the data for lazy parsing. + pub fn new(endian: E, class: Class, data: &'data [u8]) -> Result { + let mut offset = 0; + let hdr = SysVHashHeader::parse_at(endian, class, &mut offset, data)?; + + let buckets_size = size_of::() + .checked_mul(hdr.nbucket.try_into()?) + .ok_or(ParseError::IntegerOverflow)?; + let buckets_end = offset + .checked_add(buckets_size) + .ok_or(ParseError::IntegerOverflow)?; + let buckets_buf = data.get_bytes(offset..buckets_end)?; + let buckets = U32Table::new(endian, class, buckets_buf); + offset = buckets_end; + + let chains_size = size_of::() + .checked_mul(hdr.nchain.try_into()?) + .ok_or(ParseError::IntegerOverflow)?; + let chains_end = offset + .checked_add(chains_size) + .ok_or(ParseError::IntegerOverflow)?; + let chains_buf = data.get_bytes(offset..chains_end)?; + let chains = U32Table::new(endian, class, chains_buf); + + Ok(SysVHashTable { buckets, chains }) + } + + /// Use the hash table to find the symbol table entry with the given name and hash. + pub fn find( + &self, + name: &[u8], + symtab: &SymbolTable<'data, E>, + strtab: &StringTable<'data>, + ) -> Result, ParseError> { + // empty hash tables don't have any entries. This avoids a divde by zero in the modulus calculation + if self.buckets.is_empty() { + return Ok(None); + } + + let hash = sysv_hash(name); + + let start = (hash as usize) % self.buckets.len(); + let mut index = self.buckets.get(start)? as usize; + + // Bound the number of chain lookups by the chain size so we don't loop forever + let mut i = 0; + while index != 0 && i < self.chains.len() { + let symbol = symtab.get(index)?; + if strtab.get_raw(symbol.st_name as usize)? == name { + return Ok(Some((index, symbol))); + } + + index = self.chains.get(index)? as usize; + i += 1; + } + Ok(None) + } +} + +/// Calculate the GNU hash for a given symbol name. +pub fn gnu_hash(name: &[u8]) -> u32 { + let mut hash = 5381u32; + for byte in name { + hash = hash.wrapping_mul(33).wrapping_add(u32::from(*byte)); + } + hash +} + +/// Header at the start of a GNU extension Hash Table section of type [SHT_GNU_HASH](crate::abi::SHT_GNU_HASH). +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct GnuHashHeader { + pub nbucket: u32, + /// The symbol table index of the first symbol in the hash table. + /// (GNU hash sections omit symbols at the start of the table that wont be looked up) + pub table_start_idx: u32, + /// The number of words in the bloom filter. (must be a non-zero power of 2) + pub nbloom: u32, + /// The bit shift count for the bloom filter. + pub nshift: u32, +} + +impl ParseAt for GnuHashHeader { + fn parse_at( + endian: E, + _class: Class, + offset: &mut usize, + data: &[u8], + ) -> Result { + Ok(GnuHashHeader { + nbucket: endian.parse_u32_at(offset, data)?, + table_start_idx: endian.parse_u32_at(offset, data)?, + nbloom: endian.parse_u32_at(offset, data)?, + nshift: endian.parse_u32_at(offset, data)?, + }) + } + + #[inline] + fn size_for(_class: Class) -> usize { + size_of::() + size_of::() + size_of::() + size_of::() + } +} + +type U64Table<'data, E> = ParsingTable<'data, E, u64>; + +impl ParseAt for u64 { + fn parse_at( + endian: E, + _class: Class, + offset: &mut usize, + data: &[u8], + ) -> Result { + endian.parse_u64_at(offset, data) + } + + #[inline] + fn size_for(_class: Class) -> usize { + core::mem::size_of::() + } +} + +#[derive(Debug)] +pub struct GnuHashTable<'data, E: EndianParse> { + pub hdr: GnuHashHeader, + + endian: E, + class: Class, + bloom: &'data [u8], + buckets: U32Table<'data, E>, + chains: U32Table<'data, E>, +} + +impl<'data, E: EndianParse> GnuHashTable<'data, E> { + /// Construct a GnuHashTable from given bytes. Keeps a reference to the data for lazy parsing. + pub fn new(endian: E, class: Class, data: &'data [u8]) -> Result { + let mut offset = 0; + let hdr = GnuHashHeader::parse_at(endian, class, &mut offset, data)?; + + // length of the bloom filter in bytes. ELF32 is [u32; nbloom], ELF64 is [u64; nbloom]. + let nbloom: usize = hdr.nbloom as usize; + let bloom_size = match class { + Class::ELF32 => nbloom + .checked_mul(size_of::()) + .ok_or(ParseError::IntegerOverflow)?, + Class::ELF64 => nbloom + .checked_mul(size_of::()) + .ok_or(ParseError::IntegerOverflow)?, + }; + let bloom_end = offset + .checked_add(bloom_size) + .ok_or(ParseError::IntegerOverflow)?; + let bloom_buf = data.get_bytes(offset..bloom_end)?; + offset = bloom_end; + + let buckets_size = size_of::() + .checked_mul(hdr.nbucket.try_into()?) + .ok_or(ParseError::IntegerOverflow)?; + let buckets_end = offset + .checked_add(buckets_size) + .ok_or(ParseError::IntegerOverflow)?; + let buckets_buf = data.get_bytes(offset..buckets_end)?; + let buckets = U32Table::new(endian, class, buckets_buf); + offset = buckets_end; + + // the rest of the section is the chains + let chains_buf = data + .get(offset..) + .ok_or(ParseError::SliceReadError((offset, data.len())))?; + let chains = U32Table::new(endian, class, chains_buf); + + Ok(GnuHashTable { + hdr, + endian, + class, + bloom: bloom_buf, + buckets, + chains, + }) + } + + /// Use the hash table to find the symbol table entry with the given name. + pub fn find( + &self, + name: &[u8], + symtab: &SymbolTable<'data, E>, + strtab: &StringTable<'data>, + ) -> Result, ParseError> { + // empty hash tables don't have any entries. This avoids a divde by zero in the modulus calculation, + // and also avoids a potential division by zero panic in the bloom filter index calculation. + if self.buckets.is_empty() || self.hdr.nbloom == 0 { + return Ok(None); + } + + let hash = gnu_hash(name); + + // Test against bloom filter. + let (bloom_width, filter) = match self.class { + Class::ELF32 => { + let bloom_width: u32 = 8 * size_of::() as u32; // 32 + let bloom_idx = (hash / (bloom_width)) % self.hdr.nbloom; + let bloom_table = U32Table::new(self.endian, self.class, self.bloom); + (bloom_width, bloom_table.get(bloom_idx as usize)? as u64) + } + Class::ELF64 => { + let bloom_width: u32 = 8 * size_of::() as u32; // 64 + let bloom_idx = (hash / (bloom_width)) % self.hdr.nbloom; + let bloom_table = U64Table::new(self.endian, self.class, self.bloom); + (bloom_width, bloom_table.get(bloom_idx as usize)?) + } + }; + + // Check bloom filter for both hashes - symbol is present in the hash table IFF both bits are set. + if filter & (1 << (hash % bloom_width)) == 0 { + return Ok(None); + } + let hash2 = hash + .checked_shr(self.hdr.nshift) + .ok_or(ParseError::IntegerOverflow)?; + if filter & (1 << (hash2 % bloom_width)) == 0 { + return Ok(None); + } + + let table_start_idx = self.hdr.table_start_idx as usize; + let chain_start_idx = self.buckets.get((hash as usize) % self.buckets.len())? as usize; + if chain_start_idx < table_start_idx { + // All symbols before table_start_idx don't exist in the hash table + return Ok(None); + } + + let chain_len = self.chains.len(); + for chain_idx in (chain_start_idx - table_start_idx)..chain_len { + let chain_hash = self.chains.get(chain_idx)?; + + // compare the hashes by or'ing the 1's bit back on + if hash | 1 == chain_hash | 1 { + // we have a hash match! + // let's see if this symtab[sym_idx].name is what we're looking for + let sym_idx = chain_idx + .checked_add(table_start_idx) + .ok_or(ParseError::IntegerOverflow)?; + let symbol = symtab.get(sym_idx)?; + let r_sym_name = strtab.get_raw(symbol.st_name as usize)?; + + if r_sym_name == name { + return Ok(Some((sym_idx, symbol))); + } + } + + // the chain uses the 1's bit to signal chain comparison stoppage + if chain_hash & 1 != 0 { + break; + } + } + + Ok(None) + } +} + +#[cfg(test)] +mod sysv_parse_tests { + use super::*; + use crate::endian::{BigEndian, LittleEndian}; + use crate::parse::{test_parse_for, test_parse_fuzz_too_short}; + + #[test] + fn parse_sysvhdr32_lsb() { + test_parse_for( + LittleEndian, + Class::ELF32, + SysVHashHeader { + nbucket: 0x03020100, + nchain: 0x07060504, + }, + ); + } + + #[test] + fn parse_sysvhdr32_msb() { + test_parse_for( + BigEndian, + Class::ELF32, + SysVHashHeader { + nbucket: 0x00010203, + nchain: 0x04050607, + }, + ); + } + + #[test] + fn parse_sysvhdr64_lsb() { + test_parse_for( + LittleEndian, + Class::ELF64, + SysVHashHeader { + nbucket: 0x03020100, + nchain: 0x07060504, + }, + ); + } + + #[test] + fn parse_sysvhdr64_msb() { + test_parse_for( + BigEndian, + Class::ELF64, + SysVHashHeader { + nbucket: 0x00010203, + nchain: 0x04050607, + }, + ); + } + + #[test] + fn parse_sysvhdr32_lsb_fuzz_too_short() { + test_parse_fuzz_too_short::<_, SysVHashHeader>(LittleEndian, Class::ELF32); + } + + #[test] + fn parse_sysvhdr32_msb_fuzz_too_short() { + test_parse_fuzz_too_short::<_, SysVHashHeader>(BigEndian, Class::ELF32); + } + + #[test] + fn parse_sysvhdr64_lsb_fuzz_too_short() { + test_parse_fuzz_too_short::<_, SysVHashHeader>(LittleEndian, Class::ELF64); + } + + #[test] + fn parse_sysvhdr64_msb_fuzz_too_short() { + test_parse_fuzz_too_short::<_, SysVHashHeader>(BigEndian, Class::ELF64); + } +} + +#[cfg(test)] +mod gnu_parse_tests { + use super::*; + use crate::endian::{BigEndian, LittleEndian}; + use crate::parse::{test_parse_for, test_parse_fuzz_too_short}; + + #[test] + fn gnu_hash_tests() { + // some known example hash values + assert_eq!(gnu_hash(b""), 0x00001505); + assert_eq!(gnu_hash(b"printf"), 0x156b2bb8); + assert_eq!(gnu_hash(b"exit"), 0x7c967e3f); + assert_eq!(gnu_hash(b"syscall"), 0xbac212a0); + } + + #[test] + fn parse_gnuhdr32_lsb() { + test_parse_for( + LittleEndian, + Class::ELF32, + GnuHashHeader { + nbucket: 0x03020100, + table_start_idx: 0x07060504, + nbloom: 0x0B0A0908, + nshift: 0x0F0E0D0C, + }, + ); + } + + #[test] + fn parse_gnuhdr32_msb() { + test_parse_for( + BigEndian, + Class::ELF32, + GnuHashHeader { + nbucket: 0x00010203, + table_start_idx: 0x04050607, + nbloom: 0x008090A0B, + nshift: 0x0C0D0E0F, + }, + ); + } + + #[test] + fn parse_gnuhdr64_lsb() { + test_parse_for( + LittleEndian, + Class::ELF64, + GnuHashHeader { + nbucket: 0x03020100, + table_start_idx: 0x07060504, + nbloom: 0x0B0A0908, + nshift: 0x0F0E0D0C, + }, + ); + } + + #[test] + fn parse_gnuhdr64_msb() { + test_parse_for( + BigEndian, + Class::ELF64, + GnuHashHeader { + nbucket: 0x00010203, + table_start_idx: 0x04050607, + nbloom: 0x008090A0B, + nshift: 0x0C0D0E0F, + }, + ); + } + + #[test] + fn parse_gnuhdr32_lsb_fuzz_too_short() { + test_parse_fuzz_too_short::<_, GnuHashHeader>(LittleEndian, Class::ELF32); + } + + #[test] + fn parse_gnuhdr32_msb_fuzz_too_short() { + test_parse_fuzz_too_short::<_, GnuHashHeader>(BigEndian, Class::ELF32); + } + + #[test] + fn parse_gnuhdr64_lsb_fuzz_too_short() { + test_parse_fuzz_too_short::<_, GnuHashHeader>(LittleEndian, Class::ELF64); + } + + #[test] + fn parse_gnuhdr64_msb_fuzz_too_short() { + test_parse_fuzz_too_short::<_, GnuHashHeader>(BigEndian, Class::ELF64); + } +} diff --git a/src/third_party/rust-elf/src/lib.rs b/src/third_party/rust-elf/src/lib.rs new file mode 100644 index 0000000..75cfad8 --- /dev/null +++ b/src/third_party/rust-elf/src/lib.rs @@ -0,0 +1,165 @@ +//! The `elf` crate provides a pure-safe-rust interface for reading ELF object files. +//! +//! # Capabilities +//! +//! ### ✨ Works in `no_std` environments ✨ +//! This crate provides an elf parsing interface which does not allocate or use any std +//! features, so it can be used in `no_std` environments such as kernels and bootloaders. +//! The no_std variant merely disables the additional stream-oriented `std:: Read + Seek` interface. +//! All core parsing functionality is the same! +//! +//! ### ✨ Endian-aware ✨ +//! This crate handles translating between file and host endianness when +//! parsing the ELF contents and provides four endian parsing implementations +//! optimized to support the different common use-cases for an ELF parsing library. +//! Parsing is generic across the specifications and each trait impl represents a +//! specification that encapsulates an interface for parsing integers from some +//! set of allowed byte orderings. +//! +//! * [AnyEndian](endian::AnyEndian): Dynamically parsing either byte order at runtime based on the type of ELF object being parsed. +//! * [BigEndian](endian::BigEndian)/[LittleEndian](endian::LittleEndian): For tools that know they only want to parse a single given byte order known at compile time. +//! * [NativeEndian](type@endian::NativeEndian): For tools that know they want to parse the same byte order as the compilation target's byte order. +//! +//! When the limited specifications are used, errors are properly returned when asked to parse an ELF file +//! with an unexpected byte ordering. +//! +//! ### ✨ Zero-alloc parser ✨ +//! This crate implements parsing in a way that avoids heap allocations. ELF structures +//! are parsed and stored on the stack and provided by patterns such as lazily parsed iterators +//! that yield stack allocated rust types, or lazily parsing tables that only parse out a particular +//! entry on table.get(index). The structures are copy-converted as needed from the underlying file +//! data into Rust's native struct representation. +//! +//! ### ✨ Fuzz Tested ✨ +//! Various parts of the library are fuzz tested for panics and crashes (see `fuzz/`). +//! +//! Memory safety is a core goal, as is providing a safe interface that errors on bad data +//! over crashing or panicking. Checked integer math is used where appropriate, and ParseErrors are +//! returned when bad or corrupted ELF structures are encountered. +//! +//! ### ✨ Uses only safe interfaces ✨ +//! With memory safety a core goal, this crate contains zero unsafe code blocks +//! of its own and only uses safe interface methods from core and std, so you can +//! trust in rust's memory safety guarantees without also having to trust this +//! library developer as having truly been "right" in why some unsafe block was +//! safe. 💃 +//! +//! Note: I'd love to see this crate be enhanced further once rust provides safe transmutes. +//! +//! See: +//! +//! ### ✨ Some zero-copy interfaces ✨ +//! The StringTable, for instance, yields `&[u8]` and `&str` backed by the raw string table bytes. +//! +//! The [ElfBytes] parser type also does not make raw copies of the underlying file data to back +//! the parser lazy parser interfaces `ParsingIterator` and `ParsingTable`. They merely wrap byte slices +//! internally, and yield rust repr values on demand, which does entail copying of the bytes into the +//! parsed rust-native format. +//! +//! Depending on the use-case, it can be more efficient to restructure the raw ELF into different layouts +//! for more efficient interpretation, say, by re-indexing a flat table into a HashMap. `ParsingIterator`s +//! make that easy and rustily-intuitive. +//! +//! The `ParsingIterator`s are also nice in that you can easily zip/enumerate/filter/collect them +//! how you wish. Do you know that you want to do multiple passes over pairs from different tables? Just +//! zip/collect them into another type so you only parse/endian-flip each entry once! +//! +//! ### ✨ Stream-based lazy i/o interface ✨ +//! The [ElfStream] parser type takes a `std:: Read + Seek` (such as `std::fs::File`) where ranges of +//! file contents are read lazily on-demand based on what the user wants to parse. +//! +//! This, alongside the bytes-oriented interface, allow you to decide which tradeoffs +//! you want to make. If you're going to be working with the whole file contents, +//! then the byte slice approach is probably worthwhile to minimize i/o overhead by +//! streaming the whole file into memory at once. If you're only going to be +//! inspecting part of the file, then the [ElfStream] approach would help avoid the +//! overhead of reading a bunch of unused file data just to parse out a few things, (like +//! grabbing the `.gnu.note.build-id`) +//! +//! ### ✨ Tiny library with no dependencies and fast compilation times ✨ +//! Release-target compilation times on this developer's 2021 m1 macbook are sub-second. +//! +//! Example using [ElfBytes]: +//! ``` +//! use elf::ElfBytes; +//! use elf::endian::AnyEndian; +//! use elf::note::Note; +//! use elf::note::NoteGnuBuildId; +//! use elf::section::SectionHeader; +//! +//! let path = std::path::PathBuf::from("sample-objects/symver.x86_64.so"); +//! let file_data = std::fs::read(path).expect("Could not read file."); +//! let slice = file_data.as_slice(); +//! let file = ElfBytes::::minimal_parse(slice).expect("Open test1"); +//! +//! // Get the ELF file's build-id +//! let abi_shdr: SectionHeader = file +//! .section_header_by_name(".note.gnu.build-id") +//! .expect("section table should be parseable") +//! .expect("file should have a .note.ABI-tag section"); +//! +//! let notes: Vec = file +//! .section_data_as_notes(&abi_shdr) +//! .expect("Should be able to get note section data") +//! .collect(); +//! assert_eq!( +//! notes[0], +//! Note::GnuBuildId(NoteGnuBuildId( +//! &[140, 51, 19, 23, 221, 90, 215, 131, 169, 13, +//! 210, 183, 215, 77, 216, 175, 167, 110, 3, 209])) +//! ); +//! +//! // Find lazy-parsing types for the common ELF sections (we want .dynsym, .dynstr, .hash) +//! let common = file.find_common_data().expect("shdrs should parse"); +//! let (dynsyms, strtab) = (common.dynsyms.unwrap(), common.dynsyms_strs.unwrap()); +//! let hash_table = common.sysv_hash.unwrap(); +//! +//! // Use the hash table to find a given symbol in it. +//! let name = b"memset"; +//! let (sym_idx, sym) = hash_table.find(name, &dynsyms, &strtab) +//! .expect("hash table and symbols should parse").unwrap(); +//! +//! // Verify that we got the same symbol from the hash table we expected +//! assert_eq!(sym_idx, 2); +//! assert_eq!(strtab.get(sym.st_name as usize).unwrap(), "memset"); +//! assert_eq!(sym, dynsyms.get(sym_idx).unwrap()); +//! ``` + +#![cfg_attr(not(feature = "std"), no_std)] +#![warn(rust_2018_idioms)] +#![deny(missing_debug_implementations)] +#![forbid(unsafe_code)] + +#[cfg(all(feature = "alloc", not(feature = "std")))] +extern crate alloc; + +pub mod abi; + +pub mod compression; +pub mod dynamic; +pub mod file; +pub mod gnu_symver; +pub mod hash; +pub mod note; +pub mod relocation; +pub mod section; +pub mod segment; +pub mod string_table; +pub mod symbol; + +#[cfg(feature = "to_str")] +pub mod to_str; + +pub mod endian; +pub mod parse; + +mod elf_bytes; +pub use elf_bytes::CommonElfData; +pub use elf_bytes::ElfBytes; + +#[cfg(feature = "std")] +mod elf_stream; +#[cfg(feature = "std")] +pub use elf_stream::ElfStream; + +pub use parse::ParseError; diff --git a/src/third_party/rust-elf/src/note.rs b/src/third_party/rust-elf/src/note.rs new file mode 100644 index 0000000..fd9210f --- /dev/null +++ b/src/third_party/rust-elf/src/note.rs @@ -0,0 +1,646 @@ +//! Parsing ELF notes: `.note.*`, [SHT_NOTE](crate::abi::SHT_NOTE), [PT_NOTE](crate::abi::PT_NOTE) +//! +//! Example for getting the GNU ABI-tag note: +//! ``` +//! use elf::ElfBytes; +//! use elf::endian::AnyEndian; +//! use elf::note::Note; +//! use elf::note::NoteGnuAbiTag; +//! +//! let path = std::path::PathBuf::from("sample-objects/basic.x86_64"); +//! let file_data = std::fs::read(path).expect("Could not read file."); +//! let slice = file_data.as_slice(); +//! let file = ElfBytes::::minimal_parse(slice).expect("Open test1"); +//! +//! let shdr = file +//! .section_header_by_name(".note.ABI-tag") +//! .expect("section table should be parseable") +//! .expect("file should have a .note.ABI-tag section"); +//! +//! let notes: Vec<_> = file +//! .section_data_as_notes(&shdr) +//! .expect("Should be able to get note section data") +//! .collect(); +//! assert_eq!( +//! notes[0], +//! Note::GnuAbiTag(NoteGnuAbiTag { +//! os: 0, +//! major: 2, +//! minor: 6, +//! subminor: 32 +//! }) +//! ); +//! ``` +use crate::abi; +use crate::endian::EndianParse; +use crate::file::Class; +use crate::parse::{ParseAt, ParseError, ReadBytesExt}; +use core::mem::size_of; +use core::str::from_utf8; + +/// This enum contains parsed Note variants which can be matched on +#[derive(Debug, PartialEq, Eq)] +pub enum Note<'data> { + /// (name: [abi::ELF_NOTE_GNU], n_type: [abi::NT_GNU_ABI_TAG]) + GnuAbiTag(NoteGnuAbiTag), + /// (name: [abi::ELF_NOTE_GNU], n_type: [abi::NT_GNU_BUILD_ID]) + GnuBuildId(NoteGnuBuildId<'data>), + /// All other notes that we don't know how to parse + Unknown(NoteAny<'data>), +} + +impl<'data> Note<'data> { + fn parse_at( + endian: E, + _class: Class, + align: usize, + offset: &mut usize, + data: &'data [u8], + ) -> Result { + // We don't know what to do if the section or segment header specified a zero alignment, so error + // (this is likely a file corruption) + if align == 0 { + return Err(ParseError::UnexpectedAlignment(align)); + } + + // It looks like clang and gcc emit 32-bit notes for 64-bit files, so we + // currently always parse all note headers as 32-bit. + let nhdr = NoteHeader::parse_at(endian, Class::ELF32, offset, data)?; + + let name_start = *offset; + let name_size: usize = nhdr.n_namesz.try_into()?; + let name_end = name_start + .checked_add(name_size) + .ok_or(ParseError::IntegerOverflow)?; + let name = data.get_bytes(name_start..name_end)?; + *offset = name_end; + + // skip over padding if needed to get back to 4-byte alignment + if *offset % align > 0 { + *offset = (*offset) + .checked_add(align - *offset % align) + .ok_or(ParseError::IntegerOverflow)?; + } + + let desc_start = *offset; + let desc_size: usize = nhdr.n_descsz.try_into()?; + let desc_end = desc_start + .checked_add(desc_size) + .ok_or(ParseError::IntegerOverflow)?; + let raw_desc = data.get_bytes(desc_start..desc_end)?; + *offset = desc_end; + + // skip over padding if needed to get back to 4-byte alignment + if *offset % align > 0 { + *offset = (*offset) + .checked_add(align - *offset % align) + .ok_or(ParseError::IntegerOverflow)?; + } + + // Interpret the note contents to try to return a known note variant + match name { + abi::ELF_NOTE_GNU => match nhdr.n_type { + abi::NT_GNU_ABI_TAG => { + let mut offset = 0; + Ok(Note::GnuAbiTag(NoteGnuAbiTag::parse_at( + endian, + _class, + &mut offset, + raw_desc, + )?)) + } + abi::NT_GNU_BUILD_ID => Ok(Note::GnuBuildId(NoteGnuBuildId(raw_desc))), + _ => Ok(Note::Unknown(NoteAny { + n_type: nhdr.n_type, + name, + desc: raw_desc, + })), + }, + _ => Ok(Note::Unknown(NoteAny { + n_type: nhdr.n_type, + name, + desc: raw_desc, + })), + } + } +} + +/// Contains four 4-byte integers. +/// The first 4-byte integer specifies the os. The second, third, and fourth +/// 4-byte integers contain the earliest compatible kernel version. +/// For example, if the 3 integers are 6, 0, and 7, this signifies a 6.0.7 kernel. +/// +/// (see: ) +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct NoteGnuAbiTag { + pub os: u32, + pub major: u32, + pub minor: u32, + pub subminor: u32, +} + +impl ParseAt for NoteGnuAbiTag { + fn parse_at( + endian: E, + _class: Class, + offset: &mut usize, + data: &[u8], + ) -> Result { + Ok(NoteGnuAbiTag { + os: endian.parse_u32_at(offset, data)?, + major: endian.parse_u32_at(offset, data)?, + minor: endian.parse_u32_at(offset, data)?, + subminor: endian.parse_u32_at(offset, data)?, + }) + } + + fn size_for(_class: Class) -> usize { + size_of::() * 4 + } +} + +/// Contains a build ID note which is unique among the set of meaningful contents +/// for ELF files and identical when the output file would otherwise have been identical. +/// This is a zero-copy type which merely contains a slice of the note data from which it was parsed. +/// +/// (see: ) +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct NoteGnuBuildId<'data>(pub &'data [u8]); + +/// Contains the raw fields found in any ELF note. Used for notes that we don't know +/// how to parse into more specific types. +#[derive(Debug, PartialEq, Eq)] +pub struct NoteAny<'data> { + pub n_type: u64, + pub name: &'data [u8], + pub desc: &'data [u8], +} + +impl NoteAny<'_> { + /// Parses the note's name bytes as a utf8 sequence, with any trailing NUL bytes removed + pub fn name_str(&self) -> Result<&str, ParseError> { + let name = from_utf8(self.name)?; + Ok(name.trim_end_matches('\0')) + } +} + +#[derive(Debug)] +pub struct NoteIterator<'data, E: EndianParse> { + endian: E, + class: Class, + align: usize, + data: &'data [u8], + offset: usize, +} + +impl<'data, E: EndianParse> NoteIterator<'data, E> { + pub fn new(endian: E, class: Class, align: usize, data: &'data [u8]) -> Self { + NoteIterator { + endian, + class, + align, + data, + offset: 0, + } + } +} + +impl<'data, E: EndianParse> Iterator for NoteIterator<'data, E> { + type Item = Note<'data>; + fn next(&mut self) -> Option { + if self.data.is_empty() { + return None; + } + + Note::parse_at( + self.endian, + self.class, + self.align, + &mut self.offset, + self.data, + ) + .ok() + } +} + +#[derive(Debug, Clone, PartialEq, Eq)] +struct NoteHeader { + pub n_namesz: u64, + pub n_descsz: u64, + pub n_type: u64, +} + +impl ParseAt for NoteHeader { + fn parse_at( + endian: E, + class: Class, + offset: &mut usize, + data: &[u8], + ) -> Result { + match class { + Class::ELF32 => Ok(NoteHeader { + n_namesz: endian.parse_u32_at(offset, data)? as u64, + n_descsz: endian.parse_u32_at(offset, data)? as u64, + n_type: endian.parse_u32_at(offset, data)? as u64, + }), + Class::ELF64 => Ok(NoteHeader { + n_namesz: endian.parse_u64_at(offset, data)?, + n_descsz: endian.parse_u64_at(offset, data)?, + n_type: endian.parse_u64_at(offset, data)?, + }), + } + } + + #[inline] + fn size_for(class: Class) -> usize { + match class { + Class::ELF32 => 12, + Class::ELF64 => 24, + } + } +} + +#[cfg(test)] +mod parse_tests { + use super::*; + use crate::endian::{BigEndian, LittleEndian}; + + #[test] + fn parse_nt_gnu_abi_tag() { + #[rustfmt::skip] + let data = [ + 0x04, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x47, 0x4e, 0x55, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, + 0x06, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, + ]; + + let mut offset = 0; + let note = Note::parse_at(LittleEndian, Class::ELF32, 4, &mut offset, &data) + .expect("Failed to parse"); + + assert_eq!( + note, + Note::GnuAbiTag(NoteGnuAbiTag { + os: abi::ELF_NOTE_GNU_ABI_TAG_OS_LINUX, + major: 2, + minor: 6, + subminor: 32 + }) + ); + } + + #[test] + fn parse_desc_gnu_build_id() { + let data = [ + 0x04, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x47, 0x4e, + 0x55, 0x00, 0x77, 0x41, 0x9f, 0x0d, 0xa5, 0x10, 0x83, 0x0c, 0x57, 0xa7, 0xc8, 0xcc, + 0xb0, 0xee, 0x85, 0x5f, 0xee, 0xd3, 0x76, 0xa3, + ]; + + let mut offset = 0; + let note = Note::parse_at(LittleEndian, Class::ELF32, 4, &mut offset, &data) + .expect("Failed to parse"); + + assert_eq!( + note, + Note::GnuBuildId(NoteGnuBuildId(&[ + 0x77, 0x41, 0x9f, 0x0d, 0xa5, 0x10, 0x83, 0x0c, 0x57, 0xa7, 0xc8, 0xcc, 0xb0, 0xee, + 0x85, 0x5f, 0xee, 0xd3, 0x76, 0xa3, + ])) + ); + } + + #[test] + fn parse_note_errors_with_zero_alignment() { + // This is a .note.gnu.property section + #[rustfmt::skip] + let data = [ + 0x04, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, + 0x05, 0x00, 0x00, 0x00, 0x47, 0x4e, 0x55, 0x00, + 0x02, 0x00, 0x00, 0xc0, 0x04, 0x00, 0x00, 0x00, + 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + ]; + + let mut offset = 0; + Note::parse_at(LittleEndian, Class::ELF64, 0, &mut offset, &data) + .expect_err("Should have gotten an alignment error"); + } + + #[test] + fn parse_note_with_8_byte_alignment() { + // This is a .note.gnu.property section, which has been seen generated with 8-byte alignment + #[rustfmt::skip] + let data = [ + 0x04, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, + 0x05, 0x00, 0x00, 0x00, 0x47, 0x4e, 0x55, 0x00, + 0x02, 0x00, 0x00, 0xc0, 0x04, 0x00, 0x00, 0x00, + 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + ]; + + // Even though the file class is ELF64, we parse it as a 32-bit struct. gcc/clang seem to output 32-bit notes + // even though the gABI states that ELF64 files should contain 64-bit notes. Sometimes those notes are generated + // in sections with 4-byte alignment, and other times with 8-byte alignment, as specified by shdr.sh_addralign. + // + // See https://raw.githubusercontent.com/wiki/hjl-tools/linux-abi/linux-abi-draft.pdf + // Excerpt: + // All entries in a PT_NOTE segment have the same alignment which equals to the + // p_align field in program header. + // According to gABI, each note entry should be aligned to 4 bytes in 32-bit + // objects or 8 bytes in 64-bit objects. But .note.ABI-tag section (see Sec- + // tion 2.1.6) and .note.gnu.build-id section (see Section 2.1.4) are aligned + // to 4 bytes in both 32-bit and 64-bit objects. Note parser should use p_align for + // note alignment, instead of assuming alignment based on ELF file class. + let mut offset = 0; + let note = Note::parse_at(LittleEndian, Class::ELF64, 8, &mut offset, &data) + .expect("Failed to parse"); + assert_eq!( + note, + Note::Unknown(NoteAny { + n_type: 5, + name: abi::ELF_NOTE_GNU, + desc: &[ + 0x2, 0x0, 0x0, 0xc0, 0x4, 0x0, 0x0, 0x0, 0x3, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0 + ] + }) + ); + } + + #[test] + fn parse_note_with_8_byte_alignment_unaligned_namesz() { + let data = [ + 0x05, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, // namesz 5, descsz 2 + 0x42, 0x00, 0x00, 0x00, 0x47, 0x4e, 0x55, 0x55, // type 42 (unknown), name GNUU + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // NUL + 7 pad for 8 alignment + 0x01, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // desc 0102 + 6 pad for alignment + ]; + + let mut offset = 0; + let note = Note::parse_at(LittleEndian, Class::ELF32, 8, &mut offset, &data) + .expect("Failed to parse"); + assert_eq!( + note, + Note::Unknown(NoteAny { + n_type: 0x42, + name: b"GNUU\0", + desc: &[0x01, 0x02], + }) + ); + assert_eq!(offset, 32); + } + + #[test] + fn parse_note_for_elf64_expects_nhdr32() { + let data = [ + 0x04, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x47, 0x4e, + 0x55, 0x00, 0x77, 0x41, 0x9f, 0x0d, 0xa5, 0x10, 0x83, 0x0c, 0x57, 0xa7, 0xc8, 0xcc, + 0xb0, 0xee, 0x85, 0x5f, 0xee, 0xd3, 0x76, 0xa3, + ]; + + let mut offset = 0; + // Even though the file class is ELF64, we parse it as a 32-bit struct. gcc/clang seem to output 32-bit notes + // even though the gABI states that ELF64 files should contain 64-bit notes. + let note = Note::parse_at(LittleEndian, Class::ELF64, 4, &mut offset, &data) + .expect("Failed to parse"); + assert_eq!( + note, + Note::GnuBuildId(NoteGnuBuildId(&[ + 0x77, 0x41, 0x9f, 0x0d, 0xa5, 0x10, 0x83, 0x0c, 0x57, 0xa7, 0xc8, 0xcc, 0xb0, 0xee, + 0x85, 0x5f, 0xee, 0xd3, 0x76, 0xa3, + ])) + ); + } + + #[test] + fn parse_note_32_lsb() { + let data = [ + 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x20, 0x00, + 0x00, 0x00, + ]; + + let mut offset = 0; + let note = Note::parse_at(LittleEndian, Class::ELF32, 4, &mut offset, &data) + .expect("Failed to parse"); + assert_eq!( + note, + Note::Unknown(NoteAny { + n_type: 6, + name: &[], + desc: &[0x20, 0x0], + }) + ); + assert_eq!(offset, 16); + } + + #[test] + fn parse_note_32_lsb_with_name_padding() { + let data = [ + 0x03, 0x00, 0x00, 0x00, // namesz 3 + 0x04, 0x00, 0x00, 0x00, // descsz 4 + 0x01, 0x00, 0x00, 0x00, // type 1 + 0x47, 0x4e, 0x00, 0x00, // name GN\0 + 1 pad byte + 0x01, 0x02, 0x03, 0x04, + ]; // desc 01020304 + + let mut offset = 0; + let note = Note::parse_at(LittleEndian, Class::ELF32, 4, &mut offset, &data) + .expect("Failed to parse"); + assert_eq!( + note, + Note::Unknown(NoteAny { + n_type: 1, + name: b"GN\0", + desc: &[0x01, 0x02, 0x03, 0x04], + }) + ); + assert_eq!(offset, 20); + } + + #[test] + fn parse_note_32_lsb_with_desc_padding() { + let data = [ + 0x04, 0x00, 0x00, 0x00, // namesz 4 + 0x02, 0x00, 0x00, 0x00, // descsz 2 + 0x42, 0x00, 0x00, 0x00, // type 42 (unknown) + 0x47, 0x4e, 0x55, 0x00, // name GNU\0 + 0x01, 0x02, 0x00, 0x00, // desc 0102 + 2 pad bytes + ]; + + let mut offset = 0; + let note = Note::parse_at(LittleEndian, Class::ELF32, 4, &mut offset, &data) + .expect("Failed to parse"); + assert_eq!( + note, + Note::Unknown(NoteAny { + n_type: 0x42, + name: abi::ELF_NOTE_GNU, + desc: &[0x01, 0x02], + }) + ); + assert_eq!(offset, 20); + } + + #[test] + fn parse_note_32_lsb_with_no_name() { + let data = [ + 0x00, 0x00, 0x00, 0x00, // namesz 0 + 0x02, 0x00, 0x00, 0x00, // descsz 2 + 0x42, 0x00, 0x00, 0x00, // type 42 (unknown) + 0x20, 0x00, 0x00, 0x00, // desc 20, 00 + 2 pad bytes + ]; + + let mut offset = 0; + let note = Note::parse_at(LittleEndian, Class::ELF32, 4, &mut offset, &data) + .expect("Failed to parse"); + assert_eq!( + note, + Note::Unknown(NoteAny { + n_type: 0x42, + name: &[], + desc: &[0x20, 0x0], + }) + ); + assert_eq!(offset, 16); + } + + #[test] + fn parse_note_32_lsb_with_no_desc() { + let data = [ + 0x04, 0x00, 0x00, 0x00, // namesz 4 + 0x00, 0x00, 0x00, 0x00, // descsz 0 + 0x42, 0x00, 0x00, 0x00, // type 42 (unknown) + 0x47, 0x4e, 0x55, 0x00, // name GNU\0 + ]; + + let mut offset = 0; + let note = Note::parse_at(LittleEndian, Class::ELF32, 4, &mut offset, &data) + .expect("Failed to parse"); + assert_eq!( + note, + Note::Unknown(NoteAny { + n_type: 0x42, + name: abi::ELF_NOTE_GNU, + desc: &[], + }) + ); + assert_eq!(offset, 16); + } + + #[test] + fn parse_note_any_with_invalid_utf8_name() { + let data = [ + 0x04, 0x00, 0x00, 0x00, // namesz 4 + 0x00, 0x00, 0x00, 0x00, // descsz 0 + 0x42, 0x00, 0x00, 0x00, // type 42 (unknown) + 0x47, 0xc3, 0x28, 0x00, // name G..\0 (dots are an invalid utf8 sequence) + ]; + + let mut offset = 0; + let note = Note::parse_at(LittleEndian, Class::ELF32, 4, &mut offset, &data) + .expect("Failed to parse"); + assert_eq!( + note, + Note::Unknown(NoteAny { + n_type: 0x42, + name: &[0x47, 0xc3, 0x28, 0x0], + desc: &[], + }) + ); + assert_eq!(offset, 16); + } + + #[test] + fn name_str_works_for_note_any_with_valid_utf8_name() { + let note = NoteAny { + n_type: 1, + name: &[0x47, 0x4e, 0x55, 0x00], + desc: &[], + }; + let name = note.name_str().expect("Failed to parse utf8"); + assert_eq!(name, "GNU"); + } + + #[test] + fn name_str_errors_for_note_any_with_invalid_utf8_name() { + let note = NoteAny { + n_type: 1, + name: &[0x47, 0xc3, 0x28, 0x00], + desc: &[], + }; + assert!(matches!(note.name_str(), Err(ParseError::Utf8Error(_)))); + } + + use crate::parse::{test_parse_for, test_parse_fuzz_too_short}; + + #[test] + fn parse_nhdr32_lsb() { + test_parse_for( + LittleEndian, + Class::ELF32, + NoteHeader { + n_namesz: 0x03020100, + n_descsz: 0x07060504, + n_type: 0x0B0A0908, + }, + ); + } + + #[test] + fn parse_nhdr32_msb() { + test_parse_for( + BigEndian, + Class::ELF32, + NoteHeader { + n_namesz: 0x00010203, + n_descsz: 0x04050607, + n_type: 0x08090A0B, + }, + ); + } + + #[test] + fn parse_nhdr64_lsb() { + test_parse_for( + LittleEndian, + Class::ELF64, + NoteHeader { + n_namesz: 0x0706050403020100, + n_descsz: 0x0F0E0D0C0B0A0908, + n_type: 0x1716151413121110, + }, + ); + } + + #[test] + fn parse_nhdr64_msb() { + test_parse_for( + BigEndian, + Class::ELF64, + NoteHeader { + n_namesz: 0x0001020304050607, + n_descsz: 0x08090A0B0C0D0E0F, + n_type: 0x1011121314151617, + }, + ); + } + + #[test] + fn parse_nhdr32_lsb_fuzz_too_short() { + test_parse_fuzz_too_short::<_, NoteHeader>(LittleEndian, Class::ELF32); + } + + #[test] + fn parse_nhdr32_msb_fuzz_too_short() { + test_parse_fuzz_too_short::<_, NoteHeader>(BigEndian, Class::ELF32); + } + + #[test] + fn parse_nhdr64_lsb_fuzz_too_short() { + test_parse_fuzz_too_short::<_, NoteHeader>(LittleEndian, Class::ELF64); + } + + #[test] + fn parse_nhdr64_msb_fuzz_too_short() { + test_parse_fuzz_too_short::<_, NoteHeader>(BigEndian, Class::ELF64); + } +} diff --git a/src/third_party/rust-elf/src/parse.rs b/src/third_party/rust-elf/src/parse.rs new file mode 100644 index 0000000..cad50ad --- /dev/null +++ b/src/third_party/rust-elf/src/parse.rs @@ -0,0 +1,502 @@ +//! Utilities to drive safe and lazy parsing of ELF structures. +use core::{marker::PhantomData, ops::Range}; + +use crate::endian::EndianParse; +use crate::file::Class; + +#[derive(Debug)] +pub enum ParseError { + /// Returned when the ELF File Header's magic bytes weren't ELF's defined + /// magic bytes + BadMagic([u8; 4]), + /// Returned when the ELF File Header's `e_ident[EI_CLASS]` wasn't one of the + /// defined `ELFCLASS*` constants + UnsupportedElfClass(u8), + /// Returned when the ELF File Header's `e_ident[EI_DATA]` wasn't one of the + /// defined `ELFDATA*` constants + UnsupportedElfEndianness(u8), + /// Returned when parsing an ELF struct with a version field whose value wasn't + /// something we support and know how to parse. + UnsupportedVersion((u64, u64)), + /// Returned when parsing an ELF structure resulted in an offset which fell + /// out of bounds of the requested structure + BadOffset(u64), + /// Returned when parsing a string out of a StringTable failed to find the + /// terminating NUL byte + StringTableMissingNul(u64), + /// Returned when parsing a table of ELF structures and the file specified + /// an entry size for that table that was different than what we had + /// expected + BadEntsize((u64, u64)), + /// Returned when trying to interpret a section's data as the wrong type. + /// For example, trying to treat an SHT_PROGBIGS section as a SHT_STRTAB. + UnexpectedSectionType((u32, u32)), + /// Returned when trying to interpret a segment's data as the wrong type. + /// For example, trying to treat an PT_LOAD section as a PT_NOTE. + UnexpectedSegmentType((u32, u32)), + /// Returned when a section has a sh_addralign value that was different + /// than we expected. + UnexpectedAlignment(usize), + /// Returned when parsing an ELF structure out of an in-memory `&[u8]` + /// resulted in a request for a section of file bytes outside the range of + /// the slice. Commonly caused by truncated file contents. + SliceReadError((usize, usize)), + /// Returned when doing math with parsed elf fields that resulted in integer overflow. + IntegerOverflow, + /// Returned when parsing a string out of a StringTable that contained + /// invalid Utf8 + Utf8Error(core::str::Utf8Error), + /// Returned when parsing an ELF structure and the underlying structure data + /// was truncated and thus the full structure contents could not be parsed. + TryFromSliceError(core::array::TryFromSliceError), + /// Returned when parsing an ELF structure whose on-disk fields were too big + /// to represent in the native machine's usize type for in-memory processing. + /// This could be the case when processessing large 64-bit files on a 32-bit machine. + TryFromIntError(core::num::TryFromIntError), + #[cfg(feature = "std")] + /// Returned when parsing an ELF structure out of an io stream encountered + /// an io error. + IOError(std::io::Error), +} + +#[cfg(feature = "std")] +impl std::error::Error for ParseError { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + match *self { + ParseError::BadMagic(_) => None, + ParseError::UnsupportedElfClass(_) => None, + ParseError::UnsupportedElfEndianness(_) => None, + ParseError::UnsupportedVersion(_) => None, + ParseError::BadOffset(_) => None, + ParseError::StringTableMissingNul(_) => None, + ParseError::BadEntsize(_) => None, + ParseError::UnexpectedSectionType(_) => None, + ParseError::UnexpectedSegmentType(_) => None, + ParseError::UnexpectedAlignment(_) => None, + ParseError::SliceReadError(_) => None, + ParseError::IntegerOverflow => None, + ParseError::Utf8Error(ref err) => Some(err), + ParseError::TryFromSliceError(ref err) => Some(err), + ParseError::TryFromIntError(ref err) => Some(err), + ParseError::IOError(ref err) => Some(err), + } + } +} + +#[cfg(not(feature = "std"))] +impl core::error::Error for ParseError { + fn source(&self) -> Option<&(dyn core::error::Error + 'static)> { + match *self { + ParseError::BadMagic(_) => None, + ParseError::UnsupportedElfClass(_) => None, + ParseError::UnsupportedElfEndianness(_) => None, + ParseError::UnsupportedVersion(_) => None, + ParseError::BadOffset(_) => None, + ParseError::StringTableMissingNul(_) => None, + ParseError::BadEntsize(_) => None, + ParseError::UnexpectedSectionType(_) => None, + ParseError::UnexpectedSegmentType(_) => None, + ParseError::UnexpectedAlignment(_) => None, + ParseError::SliceReadError(_) => None, + ParseError::IntegerOverflow => None, + ParseError::Utf8Error(ref err) => Some(err), + ParseError::TryFromSliceError(ref err) => Some(err), + ParseError::TryFromIntError(ref err) => Some(err), + } + } +} + +impl core::fmt::Display for ParseError { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + match *self { + ParseError::BadMagic(ref magic) => { + write!(f, "Invalid Magic Bytes: {magic:X?}") + } + ParseError::UnsupportedElfClass(class) => { + write!(f, "Unsupported ELF Class: {class}") + } + ParseError::UnsupportedElfEndianness(endianness) => { + write!(f, "Unsupported ELF Endianness: {endianness}") + } + ParseError::UnsupportedVersion((found, expected)) => { + write!( + f, + "Unsupported ELF Version field found: {found} expected: {expected}" + ) + } + ParseError::BadOffset(offset) => { + write!(f, "Bad offset: {offset:#X}") + } + ParseError::StringTableMissingNul(offset) => { + write!( + f, + "Could not find terminating NUL byte starting at offset: {offset:#X}" + ) + } + ParseError::BadEntsize((found, expected)) => { + write!( + f, + "Invalid entsize. Expected: {expected:#X}, Found: {found:#X}" + ) + } + ParseError::UnexpectedSectionType((found, expected)) => { + write!( + f, + "Could not interpret section of type {found} as type {expected}" + ) + } + ParseError::UnexpectedSegmentType((found, expected)) => { + write!( + f, + "Could not interpret section of type {found} as type {expected}" + ) + } + ParseError::UnexpectedAlignment(align) => { + write!( + f, + "Could not interpret section with unexpected alignment of {align}" + ) + } + ParseError::SliceReadError((start, end)) => { + write!(f, "Could not read bytes in range [{start:#X}, {end:#X})") + } + ParseError::IntegerOverflow => { + write!(f, "Integer overflow detected") + } + ParseError::Utf8Error(ref err) => err.fmt(f), + ParseError::TryFromSliceError(ref err) => err.fmt(f), + ParseError::TryFromIntError(ref err) => err.fmt(f), + #[cfg(feature = "std")] + ParseError::IOError(ref err) => err.fmt(f), + } + } +} + +impl From for ParseError { + fn from(err: core::str::Utf8Error) -> Self { + ParseError::Utf8Error(err) + } +} + +impl From for ParseError { + fn from(err: core::array::TryFromSliceError) -> Self { + ParseError::TryFromSliceError(err) + } +} + +impl From for ParseError { + fn from(err: core::num::TryFromIntError) -> Self { + ParseError::TryFromIntError(err) + } +} + +#[cfg(feature = "std")] +impl From for ParseError { + fn from(err: std::io::Error) -> ParseError { + ParseError::IOError(err) + } +} + +/// Trait for safely parsing an ELF structure of a given class (32/64 bit) with +/// an given endian-awareness at the given offset into the data buffer. +/// +/// This is the trait that drives our elf parser, where the various ELF +/// structures implement ParseAt in order to parse their Rust-native representation +/// from a buffer, all using safe code. +pub trait ParseAt: Sized { + /// Parse this type by using the given endian-awareness and ELF class layout. + /// This is generic on EndianParse in order to allow users to optimize for + /// their expectations of data layout. See EndianParse for more details. + fn parse_at( + endian: E, + class: Class, + offset: &mut usize, + data: &[u8], + ) -> Result; + + /// Returns the expected size of the type being parsed for the given ELF class + fn size_for(class: Class) -> usize; + + /// Checks whether the given entsize matches what we need to parse this type + /// + /// Returns a ParseError for bad/unexpected entsizes that don't match what this type parses. + fn validate_entsize(class: Class, entsize: usize) -> Result { + let expected = Self::size_for(class); + match entsize == expected { + true => Ok(entsize), + false => Err(ParseError::BadEntsize((entsize as u64, expected as u64))), + } + } +} + +/// Lazy-parsing iterator which wraps bytes and parses out a `P: ParseAt` on each `next()` +#[derive(Debug)] +pub struct ParsingIterator<'data, E: EndianParse, P: ParseAt> { + endian: E, + class: Class, + data: &'data [u8], + offset: usize, + // This struct doesn't technically own a P, but it yields them + // as it iterates + pd: PhantomData<&'data P>, +} + +impl<'data, E: EndianParse, P: ParseAt> ParsingIterator<'data, E, P> { + pub fn new(endian: E, class: Class, data: &'data [u8]) -> Self { + ParsingIterator { + endian, + class, + data, + offset: 0, + pd: PhantomData, + } + } +} + +impl Iterator for ParsingIterator<'_, E, P> { + type Item = P; + fn next(&mut self) -> Option { + if self.data.is_empty() { + return None; + } + + Self::Item::parse_at(self.endian, self.class, &mut self.offset, self.data).ok() + } +} + +/// Lazy-parsing table which wraps bytes and parses out a `P: ParseAt` at a given index into +/// the table on each `get()`. +#[derive(Debug, Clone, Copy)] +pub struct ParsingTable<'data, E: EndianParse, P: ParseAt> { + endian: E, + class: Class, + data: &'data [u8], + // This struct doesn't technically own a P, but it yields them + pd: PhantomData<&'data P>, +} + +impl<'data, E: EndianParse, P: ParseAt> ParsingTable<'data, E, P> { + pub fn new(endian: E, class: Class, data: &'data [u8]) -> Self { + ParsingTable { + endian, + class, + data, + pd: PhantomData, + } + } + + /// Get a lazy-parsing iterator for the table's bytes + pub fn iter(&self) -> ParsingIterator<'data, E, P> { + ParsingIterator::new(self.endian, self.class, self.data) + } + + /// Returns the number of elements of type P in the table. + pub fn len(&self) -> usize { + self.data.len() / P::size_for(self.class) + } + + /// Returns whether the table is empty (contains zero elements). + pub fn is_empty(&self) -> bool { + self.len() == 0 + } + + /// Parse the element at `index` in the table. + pub fn get(&self, index: usize) -> Result { + if self.data.is_empty() { + return Err(ParseError::BadOffset(index as u64)); + } + + let entsize = P::size_for(self.class); + let mut start = index + .checked_mul(entsize) + .ok_or(ParseError::IntegerOverflow)?; + if start > self.data.len() { + return Err(ParseError::BadOffset(index as u64)); + } + + P::parse_at(self.endian, self.class, &mut start, self.data) + } +} + +impl<'data, E: EndianParse, P: ParseAt> IntoIterator for ParsingTable<'data, E, P> { + type IntoIter = ParsingIterator<'data, E, P>; + type Item = P; + + fn into_iter(self) -> Self::IntoIter { + ParsingIterator::new(self.endian, self.class, self.data) + } +} + +// Simple convenience extension trait to wrap get() with .ok_or(SliceReadError) +pub(crate) trait ReadBytesExt<'data> { + fn get_bytes(self, range: Range) -> Result<&'data [u8], ParseError>; +} + +impl<'data> ReadBytesExt<'data> for &'data [u8] { + fn get_bytes(self, range: Range) -> Result<&'data [u8], ParseError> { + let start = range.start; + let end = range.end; + self.get(range) + .ok_or(ParseError::SliceReadError((start, end))) + } +} + +#[cfg(test)] +pub(crate) fn test_parse_for( + endian: E, + class: Class, + expected: P, +) { + let size = P::size_for(class); + let mut data = vec![0u8; size]; + for (n, elem) in data.iter_mut().enumerate().take(size) { + *elem = n as u8; + } + + let mut offset = 0; + let entry = P::parse_at(endian, class, &mut offset, data.as_ref()).expect("Failed to parse"); + + assert_eq!(entry, expected); + assert_eq!(offset, size); +} + +#[cfg(test)] +pub(crate) fn test_parse_fuzz_too_short( + endian: E, + class: Class, +) { + let size = P::size_for(class); + let data = vec![0u8; size]; + for n in 0..size { + let buf = data.split_at(n).0; + let mut offset: usize = 0; + let error = P::parse_at(endian, class, &mut offset, buf).expect_err("Expected an error"); + assert!( + matches!(error, ParseError::SliceReadError(_)), + "Unexpected Error type found: {error}" + ); + } +} + +#[cfg(test)] +mod read_bytes_tests { + use super::ParseError; + use super::ReadBytesExt; + + #[test] + fn get_bytes_works() { + let data = &[0u8, 1, 2, 3]; + let subslice = data.get_bytes(1..3).expect("should be within range"); + assert_eq!(subslice, [1, 2]); + } + + #[test] + fn get_bytes_out_of_range_errors() { + let data = &[0u8, 1, 2, 3]; + let err = data.get_bytes(3..9).expect_err("should be out of range"); + assert!( + matches!(err, ParseError::SliceReadError((3, 9))), + "Unexpected Error type found: {err}" + ); + } +} + +#[cfg(test)] +mod parsing_table_tests { + use crate::endian::{AnyEndian, BigEndian, LittleEndian}; + + use super::*; + + type U32Table<'data, E> = ParsingTable<'data, E, u32>; + + #[test] + fn test_u32_validate_entsize() { + assert!(matches!(u32::validate_entsize(Class::ELF32, 4), Ok(4))); + assert!(matches!( + u32::validate_entsize(Class::ELF32, 8), + Err(ParseError::BadEntsize((8, 4))) + )); + } + + #[test] + fn test_u32_parse_at() { + let data = vec![0u8, 1, 2, 3, 4, 5, 6, 7]; + let mut offset = 2; + let result = u32::parse_at(LittleEndian, Class::ELF32, &mut offset, data.as_ref()) + .expect("Expected to parse but:"); + assert_eq!(result, 0x05040302); + } + + #[test] + fn test_u32_table_len() { + let data = vec![0u8, 1, 2, 3, 4, 5, 6, 7]; + let table = U32Table::new(LittleEndian, Class::ELF32, data.as_ref()); + assert_eq!(table.len(), 2); + } + + #[test] + fn test_u32_table_is_empty() { + let data = vec![0u8, 1, 2, 3, 4, 5, 6, 7]; + let table = U32Table::new(LittleEndian, Class::ELF32, data.as_ref()); + assert!(!table.is_empty()); + + let table = U32Table::new(LittleEndian, Class::ELF32, &[]); + assert!(table.is_empty()); + + let table = U32Table::new(LittleEndian, Class::ELF32, data.get(0..1).unwrap()); + assert!(table.is_empty()); + } + + #[test] + fn test_u32_table_get_parse_failure() { + let data = vec![0u8, 1]; + let table = U32Table::new(LittleEndian, Class::ELF32, data.as_ref()); + assert!(matches!( + table.get(0), + Err(ParseError::SliceReadError((0, 4))) + )); + } + + #[test] + fn test_lsb_u32_table_get() { + let data = vec![0u8, 1, 2, 3, 4, 5, 6, 7]; + let table = U32Table::new(LittleEndian, Class::ELF32, data.as_ref()); + assert!(matches!(table.get(0), Ok(0x03020100))); + assert!(matches!(table.get(1), Ok(0x07060504))); + assert!(matches!(table.get(7), Err(ParseError::BadOffset(7)))); + } + + #[test] + fn test_any_lsb_u32_table_get() { + let data = vec![0u8, 1, 2, 3, 4, 5, 6, 7]; + let table = U32Table::new(AnyEndian::Little, Class::ELF32, data.as_ref()); + assert!(matches!(table.get(0), Ok(0x03020100))); + assert!(matches!(table.get(1), Ok(0x07060504))); + assert!(matches!(table.get(7), Err(ParseError::BadOffset(7)))); + } + + #[test] + fn test_msb_u32_table_get() { + let data = vec![0u8, 1, 2, 3, 4, 5, 6, 7]; + let table = U32Table::new(BigEndian, Class::ELF32, data.as_ref()); + assert!(matches!(table.get(0), Ok(0x00010203))); + assert!(matches!(table.get(1), Ok(0x04050607))); + assert!(matches!(table.get(7), Err(ParseError::BadOffset(7)))); + } + + #[test] + fn test_any_msb_u32_table_get() { + let data = vec![0u8, 1, 2, 3, 4, 5, 6, 7]; + let table = U32Table::new(AnyEndian::Big, Class::ELF32, data.as_ref()); + assert!(matches!(table.get(0), Ok(0x00010203))); + assert!(matches!(table.get(1), Ok(0x04050607))); + assert!(matches!(table.get(7), Err(ParseError::BadOffset(7)))); + } + + #[test] + fn test_u32_table_get_unaligned() { + let data = [0u8, 1, 2, 3, 4, 5, 6, 7]; + let table = U32Table::new(LittleEndian, Class::ELF32, data.get(1..).unwrap()); + assert!(matches!(table.get(0), Ok(0x04030201))); + } +} diff --git a/src/third_party/rust-elf/src/relocation.rs b/src/third_party/rust-elf/src/relocation.rs new file mode 100644 index 0000000..bbec4a0 --- /dev/null +++ b/src/third_party/rust-elf/src/relocation.rs @@ -0,0 +1,299 @@ +//! Parsing relocation sections: `.rel.*`, `.rela.*`, [SHT_REL](crate::abi::SHT_REL), [SHT_RELA](crate::abi::SHT_RELA) +use crate::endian::EndianParse; +use crate::file::Class; +use crate::parse::{ParseAt, ParseError, ParsingIterator}; + +pub type RelIterator<'data, E> = ParsingIterator<'data, E, Rel>; +pub type RelaIterator<'data, E> = ParsingIterator<'data, E, Rela>; + +/// C-style 32-bit ELF Relocation definition +/// +/// These C-style definitions are for users who want to implement their own ELF manipulation logic. +#[derive(Debug)] +#[repr(C)] +pub struct Elf32_Rel { + pub r_offset: u32, + pub r_info: u32, +} + +/// C-style 64-bit ELF Relocation definition +/// +/// These C-style definitions are for users who want to implement their own ELF manipulation logic. +#[derive(Debug)] +#[repr(C)] +pub struct Elf64_Rel { + pub r_offset: u64, + pub r_info: u64, +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct Rel { + pub r_offset: u64, + pub r_sym: u32, + pub r_type: u32, +} + +impl ParseAt for Rel { + fn parse_at( + endian: E, + class: Class, + offset: &mut usize, + data: &[u8], + ) -> Result { + match class { + Class::ELF32 => { + let r_offset = endian.parse_u32_at(offset, data)? as u64; + let r_info = endian.parse_u32_at(offset, data)?; + Ok(Rel { + r_offset, + r_sym: r_info >> 8, + r_type: r_info & 0xFF, + }) + } + Class::ELF64 => { + let r_offset = endian.parse_u64_at(offset, data)?; + let r_info = endian.parse_u64_at(offset, data)?; + Ok(Rel { + r_offset, + r_sym: (r_info >> 32) as u32, + r_type: (r_info & 0xFFFFFFFF) as u32, + }) + } + } + } + + #[inline] + fn size_for(class: Class) -> usize { + match class { + Class::ELF32 => 8, + Class::ELF64 => 16, + } + } +} + +/// C-style 32-bit ELF Relocation (with addend) definition +/// +/// These C-style definitions are for users who want to implement their own ELF manipulation logic. +#[derive(Debug)] +#[repr(C)] +pub struct Elf32_Rela { + pub r_offset: u32, + pub r_info: u32, + pub r_addend: i32, +} + +/// C-style 64-bit ELF Relocation (with addend) definition +/// +/// These C-style definitions are for users who want to implement their own ELF manipulation logic. +#[derive(Debug)] +#[repr(C)] +pub struct Elf64_Rela { + pub r_offset: u64, + pub r_info: u64, + pub r_addend: i64, +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct Rela { + pub r_offset: u64, + pub r_sym: u32, + pub r_type: u32, + pub r_addend: i64, +} + +impl ParseAt for Rela { + fn parse_at( + endian: E, + class: Class, + offset: &mut usize, + data: &[u8], + ) -> Result { + match class { + Class::ELF32 => { + let r_offset = endian.parse_u32_at(offset, data)? as u64; + let r_info = endian.parse_u32_at(offset, data)?; + let r_addend = endian.parse_i32_at(offset, data)? as i64; + Ok(Rela { + r_offset, + r_sym: r_info >> 8, + r_type: r_info & 0xFF, + r_addend, + }) + } + Class::ELF64 => { + let r_offset = endian.parse_u64_at(offset, data)?; + let r_info = endian.parse_u64_at(offset, data)?; + let r_addend = endian.parse_i64_at(offset, data)?; + Ok(Rela { + r_offset, + r_sym: (r_info >> 32) as u32, + r_type: (r_info & 0xFFFFFFFF) as u32, + r_addend, + }) + } + } + } + + #[inline] + fn size_for(class: Class) -> usize { + match class { + Class::ELF32 => 12, + Class::ELF64 => 24, + } + } +} + +#[cfg(test)] +mod parse_tests { + use super::*; + use crate::endian::{BigEndian, LittleEndian}; + use crate::parse::{test_parse_for, test_parse_fuzz_too_short}; + + #[test] + fn parse_rel32_lsb() { + test_parse_for( + LittleEndian, + Class::ELF32, + Rel { + r_offset: 0x03020100, + r_sym: 0x00070605, + r_type: 0x00000004, + }, + ); + } + + #[test] + fn parse_rel32_msb() { + test_parse_for( + BigEndian, + Class::ELF32, + Rel { + r_offset: 0x00010203, + r_sym: 0x00040506, + r_type: 0x00000007, + }, + ); + } + + #[test] + fn parse_rel64_lsb() { + test_parse_for( + LittleEndian, + Class::ELF64, + Rel { + r_offset: 0x0706050403020100, + r_sym: 0x0F0E0D0C, + r_type: 0x0B0A0908, + }, + ); + } + + #[test] + fn parse_rel64_msb() { + test_parse_for( + BigEndian, + Class::ELF64, + Rel { + r_offset: 0x0001020304050607, + r_sym: 0x08090A0B, + r_type: 0x0C0D0E0F, + }, + ); + } + + #[test] + fn parse_rel32_lsb_fuzz_too_short() { + test_parse_fuzz_too_short::<_, Rel>(LittleEndian, Class::ELF32); + } + + #[test] + fn parse_rel32_msb_fuzz_too_short() { + test_parse_fuzz_too_short::<_, Rel>(BigEndian, Class::ELF32); + } + + #[test] + fn parse_rel64_lsb_fuzz_too_short() { + test_parse_fuzz_too_short::<_, Rel>(LittleEndian, Class::ELF64); + } + + #[test] + fn parse_rel64_msb_fuzz_too_short() { + test_parse_fuzz_too_short::<_, Rel>(BigEndian, Class::ELF64); + } + + #[test] + fn parse_rela32_lsb() { + test_parse_for( + LittleEndian, + Class::ELF32, + Rela { + r_offset: 0x03020100, + r_sym: 0x00070605, + r_type: 0x00000004, + r_addend: 0x0B0A0908, + }, + ); + } + + #[test] + fn parse_rela32_msb() { + test_parse_for( + BigEndian, + Class::ELF32, + Rela { + r_offset: 0x00010203, + r_sym: 0x00040506, + r_type: 0x00000007, + r_addend: 0x08090A0B, + }, + ); + } + + #[test] + fn parse_rela64_lsb() { + test_parse_for( + LittleEndian, + Class::ELF64, + Rela { + r_offset: 0x0706050403020100, + r_sym: 0x0F0E0D0C, + r_type: 0x0B0A0908, + r_addend: 0x1716151413121110, + }, + ); + } + + #[test] + fn parse_rela64_msb() { + test_parse_for( + BigEndian, + Class::ELF64, + Rela { + r_offset: 0x0001020304050607, + r_sym: 0x08090A0B, + r_type: 0x0C0D0E0F, + r_addend: 0x1011121314151617, + }, + ); + } + + #[test] + fn parse_rela32_lsb_fuzz_too_short() { + test_parse_fuzz_too_short::<_, Rela>(LittleEndian, Class::ELF32); + } + + #[test] + fn parse_rela32_msb_fuzz_too_short() { + test_parse_fuzz_too_short::<_, Rela>(BigEndian, Class::ELF32); + } + + #[test] + fn parse_rela64_lsb_fuzz_too_short() { + test_parse_fuzz_too_short::<_, Rela>(LittleEndian, Class::ELF64); + } + + #[test] + fn parse_rela64_msb_fuzz_too_short() { + test_parse_fuzz_too_short::<_, Rela>(BigEndian, Class::ELF64); + } +} diff --git a/src/third_party/rust-elf/src/section.rs b/src/third_party/rust-elf/src/section.rs new file mode 100644 index 0000000..a973051 --- /dev/null +++ b/src/third_party/rust-elf/src/section.rs @@ -0,0 +1,231 @@ +//! Parsing the Section Header table +use crate::endian::EndianParse; +use crate::file::Class; +use crate::parse::{ParseAt, ParseError, ParsingTable}; + +pub type SectionHeaderTable<'data, E> = ParsingTable<'data, E, SectionHeader>; + +/// C-style 32-bit ELF Section Header definition +/// +/// These C-style definitions are for users who want to implement their own ELF manipulation logic. +#[derive(Debug)] +#[repr(C)] +pub struct Elf32_Shdr { + pub sh_name: u32, + pub sh_type: u32, + pub sh_flags: u32, + pub sh_addr: u32, + pub sh_offset: u32, + pub sh_size: u32, + pub sh_link: u32, + pub sh_info: u32, + pub sh_addralign: u32, + pub sh_entsize: u32, +} + +/// C-style 64-bit ELF Section Header definition +/// +/// These C-style definitions are for users who want to implement their own ELF manipulation logic. +#[derive(Debug)] +#[repr(C)] +pub struct Elf64_Shdr { + pub sh_name: u32, + pub sh_type: u32, + pub sh_flags: u64, + pub sh_addr: u64, + pub sh_offset: u64, + pub sh_size: u64, + pub sh_link: u32, + pub sh_info: u32, + pub sh_addralign: u64, + pub sh_entsize: u64, +} + +/// Encapsulates the contents of an ELF Section Header +/// +/// This is a Rust-native type that represents a Section Header that is bit-width-agnostic. +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub struct SectionHeader { + /// Section Name + pub sh_name: u32, + /// Section Type + pub sh_type: u32, + /// Section Flags + pub sh_flags: u64, + /// in-memory address where this section is loaded + pub sh_addr: u64, + /// Byte-offset into the file where this section starts + pub sh_offset: u64, + /// Section size in bytes + pub sh_size: u64, + /// Defined by section type + pub sh_link: u32, + /// Defined by section type + pub sh_info: u32, + /// address alignment + pub sh_addralign: u64, + /// size of an entry if section data is an array of entries + pub sh_entsize: u64, +} + +impl ParseAt for SectionHeader { + fn parse_at( + endian: E, + class: Class, + offset: &mut usize, + data: &[u8], + ) -> Result { + match class { + Class::ELF32 => Ok(SectionHeader { + sh_name: endian.parse_u32_at(offset, data)?, + sh_type: endian.parse_u32_at(offset, data)?, + sh_flags: endian.parse_u32_at(offset, data)? as u64, + sh_addr: endian.parse_u32_at(offset, data)? as u64, + sh_offset: endian.parse_u32_at(offset, data)? as u64, + sh_size: endian.parse_u32_at(offset, data)? as u64, + sh_link: endian.parse_u32_at(offset, data)?, + sh_info: endian.parse_u32_at(offset, data)?, + sh_addralign: endian.parse_u32_at(offset, data)? as u64, + sh_entsize: endian.parse_u32_at(offset, data)? as u64, + }), + Class::ELF64 => Ok(SectionHeader { + sh_name: endian.parse_u32_at(offset, data)?, + sh_type: endian.parse_u32_at(offset, data)?, + sh_flags: endian.parse_u64_at(offset, data)?, + sh_addr: endian.parse_u64_at(offset, data)?, + sh_offset: endian.parse_u64_at(offset, data)?, + sh_size: endian.parse_u64_at(offset, data)?, + sh_link: endian.parse_u32_at(offset, data)?, + sh_info: endian.parse_u32_at(offset, data)?, + sh_addralign: endian.parse_u64_at(offset, data)?, + sh_entsize: endian.parse_u64_at(offset, data)?, + }), + } + } + + #[inline] + fn size_for(class: Class) -> usize { + match class { + Class::ELF32 => 40, + Class::ELF64 => 64, + } + } +} + +impl SectionHeader { + /// Helper method which uses checked integer math to get a tuple of (start,end) for + /// this SectionHeader's (sh_offset, sh_offset + sh_size) + pub(crate) fn get_data_range(&self) -> Result<(usize, usize), ParseError> { + let start: usize = self.sh_offset.try_into()?; + let size: usize = self.sh_size.try_into()?; + let end = start.checked_add(size).ok_or(ParseError::IntegerOverflow)?; + Ok((start, end)) + } +} + +#[cfg(test)] +mod parse_tests { + use super::*; + use crate::endian::{BigEndian, LittleEndian}; + use crate::parse::{test_parse_for, test_parse_fuzz_too_short}; + + #[test] + fn parse_shdr32_lsb() { + test_parse_for( + LittleEndian, + Class::ELF32, + SectionHeader { + sh_name: 0x03020100, + sh_type: 0x07060504, + sh_flags: 0xB0A0908, + sh_addr: 0x0F0E0D0C, + sh_offset: 0x13121110, + sh_size: 0x17161514, + sh_link: 0x1B1A1918, + sh_info: 0x1F1E1D1C, + sh_addralign: 0x23222120, + sh_entsize: 0x27262524, + }, + ); + } + + #[test] + fn parse_shdr32_msb() { + test_parse_for( + BigEndian, + Class::ELF32, + SectionHeader { + sh_name: 0x00010203, + sh_type: 0x04050607, + sh_flags: 0x08090A0B, + sh_addr: 0x0C0D0E0F, + sh_offset: 0x10111213, + sh_size: 0x14151617, + sh_link: 0x18191A1B, + sh_info: 0x1C1D1E1F, + sh_addralign: 0x20212223, + sh_entsize: 0x24252627, + }, + ); + } + + #[test] + fn parse_shdr64_lsb() { + test_parse_for( + LittleEndian, + Class::ELF64, + SectionHeader { + sh_name: 0x03020100, + sh_type: 0x07060504, + sh_flags: 0x0F0E0D0C0B0A0908, + sh_addr: 0x1716151413121110, + sh_offset: 0x1F1E1D1C1B1A1918, + sh_size: 0x2726252423222120, + sh_link: 0x2B2A2928, + sh_info: 0x2F2E2D2C, + sh_addralign: 0x3736353433323130, + sh_entsize: 0x3F3E3D3C3B3A3938, + }, + ); + } + + #[test] + fn parse_shdr64_msb() { + test_parse_for( + BigEndian, + Class::ELF64, + SectionHeader { + sh_name: 0x00010203, + sh_type: 0x04050607, + sh_flags: 0x08090A0B0C0D0E0F, + sh_addr: 0x1011121314151617, + sh_offset: 0x18191A1B1C1D1E1F, + sh_size: 0x2021222324252627, + sh_link: 0x28292A2B, + sh_info: 0x2C2D2E2F, + sh_addralign: 0x3031323334353637, + sh_entsize: 0x38393A3B3C3D3E3F, + }, + ); + } + + #[test] + fn parse_shdr32_lsb_fuzz_too_short() { + test_parse_fuzz_too_short::<_, SectionHeader>(LittleEndian, Class::ELF32); + } + + #[test] + fn parse_shdr32_msb_fuzz_too_short() { + test_parse_fuzz_too_short::<_, SectionHeader>(BigEndian, Class::ELF32); + } + + #[test] + fn parse_shdr64_lsb_fuzz_too_short() { + test_parse_fuzz_too_short::<_, SectionHeader>(LittleEndian, Class::ELF64); + } + + #[test] + fn parse_shdr64_msb_fuzz_too_short() { + test_parse_fuzz_too_short::<_, SectionHeader>(BigEndian, Class::ELF64); + } +} diff --git a/src/third_party/rust-elf/src/segment.rs b/src/third_party/rust-elf/src/segment.rs new file mode 100644 index 0000000..5f148f0 --- /dev/null +++ b/src/third_party/rust-elf/src/segment.rs @@ -0,0 +1,223 @@ +//! Parsing the Program Header table aka Segment table aka `Elf_Phdr` +use crate::endian::EndianParse; +use crate::file::Class; +use crate::parse::{ParseAt, ParseError, ParsingTable}; + +pub type SegmentTable<'data, E> = ParsingTable<'data, E, ProgramHeader>; + +/// C-style 32-bit ELF Program Segment Header definition +/// +/// These C-style definitions are for users who want to implement their own ELF manipulation logic. +#[derive(Debug)] +#[repr(C)] +pub struct Elf32_Phdr { + pub p_type: u32, + pub p_offset: u32, + pub p_vaddr: u32, + pub p_paddr: u32, + pub p_filesz: u32, + pub p_memsz: u32, + pub p_flags: u32, + pub p_align: u32, +} + +/// C-style 64-bit ELF Program Segment Header definition +/// +/// These C-style definitions are for users who want to implement their own ELF manipulation logic. +#[derive(Debug)] +#[repr(C)] +pub struct Elf64_Phdr { + pub p_type: u32, + pub p_flags: u32, + pub p_offset: u64, + pub p_vaddr: u64, + pub p_paddr: u64, + pub p_filesz: u64, + pub p_memsz: u64, + pub p_align: u64, +} + +/// Encapsulates the contents of an ELF Program Header +/// +/// The program header table is an array of program header structures describing +/// the various segments for program execution. +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub struct ProgramHeader { + /// Program segment type + pub p_type: u32, + /// Offset into the ELF file where this segment begins + pub p_offset: u64, + /// Virtual adress where this segment should be loaded + pub p_vaddr: u64, + /// Physical address where this segment should be loaded + pub p_paddr: u64, + /// Size of this segment in the file + pub p_filesz: u64, + /// Size of this segment in memory + pub p_memsz: u64, + /// Flags for this segment + pub p_flags: u32, + /// file and memory alignment + pub p_align: u64, +} + +impl ParseAt for ProgramHeader { + fn parse_at( + endian: E, + class: Class, + offset: &mut usize, + data: &[u8], + ) -> Result { + if class == Class::ELF32 { + return Ok(ProgramHeader { + p_type: endian.parse_u32_at(offset, data)?, + p_offset: endian.parse_u32_at(offset, data)? as u64, + p_vaddr: endian.parse_u32_at(offset, data)? as u64, + p_paddr: endian.parse_u32_at(offset, data)? as u64, + p_filesz: endian.parse_u32_at(offset, data)? as u64, + p_memsz: endian.parse_u32_at(offset, data)? as u64, + p_flags: endian.parse_u32_at(offset, data)?, + p_align: endian.parse_u32_at(offset, data)? as u64, + }); + } + + // Note: 64-bit fields are in a different order + let p_type = endian.parse_u32_at(offset, data)?; + let p_flags = endian.parse_u32_at(offset, data)?; + let p_offset = endian.parse_u64_at(offset, data)?; + let p_vaddr = endian.parse_u64_at(offset, data)?; + let p_paddr = endian.parse_u64_at(offset, data)?; + let p_filesz = endian.parse_u64_at(offset, data)?; + let p_memsz = endian.parse_u64_at(offset, data)?; + let p_align = endian.parse_u64_at(offset, data)?; + Ok(ProgramHeader { + p_type, + p_offset, + p_vaddr, + p_paddr, + p_filesz, + p_memsz, + p_flags, + p_align, + }) + } + + #[inline] + fn size_for(class: Class) -> usize { + match class { + Class::ELF32 => 32, + Class::ELF64 => 56, + } + } +} + +impl ProgramHeader { + /// Helper method which uses checked integer math to get a tuple of (start, end) for + /// the location in bytes for this ProgramHeader's data in the file. + /// i.e. (p_offset, p_offset + p_filesz) + pub(crate) fn get_file_data_range(&self) -> Result<(usize, usize), ParseError> { + let start: usize = self.p_offset.try_into()?; + let size: usize = self.p_filesz.try_into()?; + let end = start.checked_add(size).ok_or(ParseError::IntegerOverflow)?; + Ok((start, end)) + } +} + +#[cfg(test)] +mod parse_tests { + use super::*; + use crate::endian::{BigEndian, LittleEndian}; + use crate::parse::{test_parse_for, test_parse_fuzz_too_short}; + + #[test] + fn parse_phdr32_lsb() { + test_parse_for( + LittleEndian, + Class::ELF32, + ProgramHeader { + p_type: 0x03020100, + p_offset: 0x07060504, + p_vaddr: 0xB0A0908, + p_paddr: 0x0F0E0D0C, + p_filesz: 0x13121110, + p_memsz: 0x17161514, + p_flags: 0x1B1A1918, + p_align: 0x1F1E1D1C, + }, + ); + } + + #[test] + fn parse_phdr32_msb() { + test_parse_for( + BigEndian, + Class::ELF32, + ProgramHeader { + p_type: 0x00010203, + p_offset: 0x04050607, + p_vaddr: 0x08090A0B, + p_paddr: 0x0C0D0E0F, + p_filesz: 0x10111213, + p_memsz: 0x14151617, + p_flags: 0x18191A1B, + p_align: 0x1C1D1E1F, + }, + ); + } + + #[test] + fn parse_phdr64_lsb() { + test_parse_for( + LittleEndian, + Class::ELF64, + ProgramHeader { + p_type: 0x03020100, + p_offset: 0x0F0E0D0C0B0A0908, + p_vaddr: 0x1716151413121110, + p_paddr: 0x1F1E1D1C1B1A1918, + p_filesz: 0x2726252423222120, + p_memsz: 0x2F2E2D2C2B2A2928, + p_flags: 0x07060504, + p_align: 0x3736353433323130, + }, + ); + } + + #[test] + fn parse_phdr64_msb() { + test_parse_for( + BigEndian, + Class::ELF64, + ProgramHeader { + p_type: 0x00010203, + p_offset: 0x08090A0B0C0D0E0F, + p_vaddr: 0x1011121314151617, + p_paddr: 0x18191A1B1C1D1E1F, + p_filesz: 0x2021222324252627, + p_memsz: 0x28292A2B2C2D2E2F, + p_flags: 0x04050607, + p_align: 0x3031323334353637, + }, + ); + } + + #[test] + fn parse_phdr32_lsb_fuzz_too_short() { + test_parse_fuzz_too_short::<_, ProgramHeader>(LittleEndian, Class::ELF32); + } + + #[test] + fn parse_phdr32_msb_fuzz_too_short() { + test_parse_fuzz_too_short::<_, ProgramHeader>(BigEndian, Class::ELF32); + } + + #[test] + fn parse_phdr64_lsb_fuzz_too_short() { + test_parse_fuzz_too_short::<_, ProgramHeader>(LittleEndian, Class::ELF64); + } + + #[test] + fn parse_phdr64_msb_fuzz_too_short() { + test_parse_fuzz_too_short::<_, ProgramHeader>(BigEndian, Class::ELF64); + } +} diff --git a/src/third_party/rust-elf/src/string_table.rs b/src/third_party/rust-elf/src/string_table.rs new file mode 100644 index 0000000..0311cb0 --- /dev/null +++ b/src/third_party/rust-elf/src/string_table.rs @@ -0,0 +1,116 @@ +//! Interpreting string table sections: `.strtab`, [SHT_STRTAB][crate::abi::SHT_STRTAB] +use crate::parse::ParseError; +use core::str::from_utf8; + +#[derive(Debug, Default, Clone, Copy)] +pub struct StringTable<'data> { + data: &'data [u8], +} + +impl<'data> StringTable<'data> { + pub fn new(data: &'data [u8]) -> Self { + StringTable { data } + } + + pub fn get_raw(&self, offset: usize) -> Result<&'data [u8], ParseError> { + if self.data.is_empty() { + return Err(ParseError::BadOffset(offset as u64)); + }; + + let start = self + .data + .get(offset..) + .ok_or(ParseError::BadOffset(offset as u64))?; + let end = start + .iter() + .position(|&b| b == 0u8) + .ok_or(ParseError::StringTableMissingNul(offset as u64))?; + + Ok(start.split_at(end).0) + } + + pub fn get(&self, offset: usize) -> Result<&'data str, ParseError> { + let raw_data = self.get_raw(offset)?; + Ok(from_utf8(raw_data)?) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_empty_table_errors() { + let st = StringTable::default(); + assert!(matches!(st.get(0), Err(ParseError::BadOffset(0)))); + assert!(matches!(st.get(1), Err(ParseError::BadOffset(1)))); + } + + /// Note: ELF string tables are defined to always start with a NUL and use + /// index 0 to give an empty string, so getting a string starting at a NUL + /// should properly give an empty string. + #[test] + fn test_get_index_0_gives_empty_string() { + let data = [0u8, 42u8, 0u8]; + let st = StringTable::new(&data); + assert_eq!(st.get(0).unwrap(), ""); + } + + #[test] + fn test_get_raw_works() { + let data = [0u8, 0x45, 0x4C, 0x46, 0u8]; + let st = StringTable::new(&data); + assert_eq!(st.get_raw(1).unwrap(), [0x45, 0x4c, 0x46]); + } + + #[test] + fn test_get_string_works() { + let data = [0u8, 0x45, 0x4C, 0x46, 0u8]; + let st = StringTable::new(&data); + assert_eq!(st.get(1).unwrap(), "ELF"); + } + + #[test] + fn test_get_raw_index_out_of_bounds_errors() { + let data = [0u8, 0x45, 0x4C, 0x46, 0u8]; + let st = StringTable::new(&data); + let result = st.get_raw(7); + assert!( + matches!(result, Err(ParseError::BadOffset(7))), + "Unexpected Error type found: {result:?}" + ); + } + + #[test] + fn test_get_index_out_of_bounds_errors() { + let data = [0u8, 0x45, 0x4C, 0x46, 0u8]; + let st = StringTable::new(&data); + let result = st.get(7); + assert!( + matches!(result, Err(ParseError::BadOffset(7))), + "Unexpected Error type found: {result:?}" + ); + } + + #[test] + fn test_get_raw_with_malformed_table_no_trailing_nul() { + let data = [0u8, 0x45, 0x4C, 0x46]; + let st = StringTable::new(&data); + let result = st.get_raw(1); + assert!( + matches!(result, Err(ParseError::StringTableMissingNul(1))), + "Unexpected Error type found: {result:?}" + ); + } + + #[test] + fn test_get_with_malformed_table_no_trailing_nul() { + let data = [0u8, 0x45, 0x4C, 0x46]; + let st = StringTable::new(&data); + let result = st.get(1); + assert!( + matches!(result, Err(ParseError::StringTableMissingNul(1))), + "Unexpected Error type found: {result:?}" + ); + } +} diff --git a/src/third_party/rust-elf/src/symbol.rs b/src/third_party/rust-elf/src/symbol.rs new file mode 100644 index 0000000..4be8855 --- /dev/null +++ b/src/third_party/rust-elf/src/symbol.rs @@ -0,0 +1,271 @@ +//! Parsing symbol table sections: `.symtab`, `.dynsym` +use crate::abi; +use crate::endian::EndianParse; +use crate::file::Class; +use crate::parse::{ParseAt, ParseError, ParsingTable}; + +pub type SymbolTable<'data, E> = ParsingTable<'data, E, Symbol>; + +/// C-style 32-bit ELF Symbol definition +/// +/// These C-style definitions are for users who want to implement their own ELF manipulation logic. +#[derive(Debug)] +#[repr(C)] +pub struct Elf32_Sym { + pub st_name: u32, + pub st_value: u32, + pub st_size: u32, + pub st_info: u8, + pub st_other: u8, + pub st_shndx: u32, +} + +/// C-style 64-bit ELF Symbol definition +/// +/// These C-style definitions are for users who want to implement their own ELF manipulation logic. +#[derive(Debug)] +#[repr(C)] +pub struct Elf64_Sym { + pub st_name: u32, + pub st_info: u8, + pub st_other: u8, + pub st_shndx: u16, + pub st_value: u64, + pub st_size: u64, +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct Symbol { + /// This member holds an index into the symbol table's string table, + /// which holds the character representations of the symbol names. If the + /// value is non-zero, it represents a string table index that gives the + /// symbol name. Otherwise, the symbol table entry has no name. + pub st_name: u32, + + /// Every symbol table entry is defined in relation to some section. This + /// member holds the relevant section header table index. As the sh_link and + /// sh_info interpretation table and the related text describe, some section + /// indexes indicate special meanings. + /// + /// If this member contains SHN_XINDEX, then the actual section header index + /// is too large to fit in this field. The actual value is contained in the + /// associated section of type SHT_SYMTAB_SHNDX. + pub st_shndx: u16, + + /// This member specifies the symbol's type and binding attributes. + pub st_info: u8, + + /// This member currently specifies a symbol's visibility. + pub st_other: u8, + + /// This member gives the value of the associated symbol. Depending on the + /// context, this may be an absolute value, an address, and so on. + /// + /// * In relocatable files, st_value holds alignment constraints for a + /// symbol whose section index is SHN_COMMON. + /// * In relocatable files, st_value holds a section offset for a defined + /// symbol. st_value is an offset from the beginning of the section that + /// st_shndx identifies. + /// * In executable and shared object files, st_value holds a virtual + /// address. To make these files' symbols more useful for the dynamic + /// linker, the section offset (file interpretation) gives way to a + /// virtual address (memory interpretation) for which the section number + /// is irrelevant. + pub st_value: u64, + + /// This member gives the symbol's size. + /// For example, a data object's size is the number of bytes contained in + /// the object. This member holds 0 if the symbol has no size or an unknown + /// size. + pub st_size: u64, +} + +impl Symbol { + /// Returns true if a symbol is undefined in this ELF object. + /// + /// When linking and loading, undefined symbols in this object get linked to + /// a defined symbol in another object. + pub fn is_undefined(&self) -> bool { + self.st_shndx == abi::SHN_UNDEF + } + + pub fn st_symtype(&self) -> u8 { + self.st_info & 0xf + } + + pub fn st_bind(&self) -> u8 { + self.st_info >> 4 + } + + pub fn st_vis(&self) -> u8 { + self.st_other & 0x3 + } +} + +impl ParseAt for Symbol { + fn parse_at( + endian: E, + class: Class, + offset: &mut usize, + data: &[u8], + ) -> Result { + let st_name: u32; + let st_value: u64; + let st_size: u64; + let st_shndx: u16; + let st_info: u8; + let st_other: u8; + + if class == Class::ELF32 { + st_name = endian.parse_u32_at(offset, data)?; + st_value = endian.parse_u32_at(offset, data)? as u64; + st_size = endian.parse_u32_at(offset, data)? as u64; + st_info = endian.parse_u8_at(offset, data)?; + st_other = endian.parse_u8_at(offset, data)?; + st_shndx = endian.parse_u16_at(offset, data)?; + } else { + st_name = endian.parse_u32_at(offset, data)?; + st_info = endian.parse_u8_at(offset, data)?; + st_other = endian.parse_u8_at(offset, data)?; + st_shndx = endian.parse_u16_at(offset, data)?; + st_value = endian.parse_u64_at(offset, data)?; + st_size = endian.parse_u64_at(offset, data)?; + } + + Ok(Symbol { + st_name, + st_value, + st_size, + st_shndx, + st_info, + st_other, + }) + } + + #[inline] + fn size_for(class: Class) -> usize { + match class { + Class::ELF32 => 16, + Class::ELF64 => 24, + } + } +} + +#[cfg(test)] +mod symbol_tests { + use super::*; + + #[test] + fn symbol_undefined() { + let undef_sym = Symbol { + st_name: 0, + st_value: 0, + st_size: 0, + st_shndx: 0, + st_info: 0, + st_other: 0, + }; + assert!(undef_sym.is_undefined()); + + let def_sym = Symbol { + st_name: 0, + st_value: 0, + st_size: 0, + st_shndx: 42, + st_info: 0, + st_other: 0, + }; + assert!(!def_sym.is_undefined()); + } +} + +#[cfg(test)] +mod parse_tests { + use super::*; + use crate::endian::{BigEndian, LittleEndian}; + use crate::parse::{test_parse_for, test_parse_fuzz_too_short}; + + #[test] + fn parse_sym32_lsb() { + test_parse_for( + LittleEndian, + Class::ELF32, + Symbol { + st_name: 0x03020100, + st_value: 0x07060504, + st_size: 0x0B0A0908, + st_shndx: 0x0F0E, + st_info: 0x0C, + st_other: 0x0D, + }, + ); + } + + #[test] + fn parse_sym32_msb() { + test_parse_for( + BigEndian, + Class::ELF32, + Symbol { + st_name: 0x00010203, + st_value: 0x04050607, + st_size: 0x08090A0B, + st_shndx: 0x0E0F, + st_info: 0x0C, + st_other: 0x0D, + }, + ); + } + + #[test] + fn parse_sym64_lsb() { + test_parse_for( + LittleEndian, + Class::ELF64, + Symbol { + st_name: 0x03020100, + st_value: 0x0F0E0D0C0B0A0908, + st_size: 0x1716151413121110, + st_shndx: 0x0706, + st_info: 0x04, + st_other: 0x05, + }, + ); + } + + #[test] + fn parse_sym64_msb() { + test_parse_for( + BigEndian, + Class::ELF64, + Symbol { + st_name: 0x00010203, + st_value: 0x08090A0B0C0D0E0F, + st_size: 0x1011121314151617, + st_shndx: 0x0607, + st_info: 0x04, + st_other: 0x05, + }, + ); + } + + #[test] + fn parse_sym32_lsb_fuzz_too_short() { + test_parse_fuzz_too_short::<_, Symbol>(LittleEndian, Class::ELF32); + } + + #[test] + fn parse_sym32_msb_fuzz_too_short() { + test_parse_fuzz_too_short::<_, Symbol>(BigEndian, Class::ELF32); + } + + #[test] + fn parse_sym64_lsb_fuzz_too_short() { + test_parse_fuzz_too_short::<_, Symbol>(LittleEndian, Class::ELF64); + } + + #[test] + fn parse_sym64_msb_fuzz_too_short() { + test_parse_fuzz_too_short::<_, Symbol>(BigEndian, Class::ELF64); + } +} diff --git a/src/third_party/rust-elf/src/to_str.rs b/src/third_party/rust-elf/src/to_str.rs new file mode 100644 index 0000000..0fcb27a --- /dev/null +++ b/src/third_party/rust-elf/src/to_str.rs @@ -0,0 +1,692 @@ +//! Optional module for getting string representations of ELF constants +use crate::abi; + +#[cfg(all(feature = "alloc", not(feature = "std")))] +use alloc::{ + format, + string::{String, ToString}, +}; + +pub fn e_osabi_to_str(e_osabi: u8) -> Option<&'static str> { + match e_osabi { + abi::ELFOSABI_SYSV => Some("ELFOSABI_SYSV"), + abi::ELFOSABI_HPUX => Some("ELFOSABI_HPUX"), + abi::ELFOSABI_NETBSD => Some("ELFOSABI_NETBSD"), + abi::ELFOSABI_LINUX => Some("ELFOSABI_LINUX"), + abi::ELFOSABI_SOLARIS => Some("ELFOSABI_SOLARIS"), + abi::ELFOSABI_AIX => Some("ELFOSABI_AIX"), + abi::ELFOSABI_IRIX => Some("ELFOSABI_IRIX"), + abi::ELFOSABI_FREEBSD => Some("ELFOSABI_FREEBSD"), + abi::ELFOSABI_TRU64 => Some("ELFOSABI_TRU64"), + abi::ELFOSABI_MODESTO => Some("ELFOSABI_MODESTO"), + abi::ELFOSABI_OPENBSD => Some("ELFOSABI_OPENBSD"), + abi::ELFOSABI_OPENVMS => Some("ELFOSABI_OPENVMS"), + abi::ELFOSABI_NSK => Some("ELFOSABI_NSK"), + abi::ELFOSABI_AROS => Some("ELFOSABI_AROS"), + abi::ELFOSABI_FENIXOS => Some("ELFOSABI_FENIXOS"), + abi::ELFOSABI_CLOUDABI => Some("ELFOSABI_CLOUDABI"), + abi::ELFOSABI_OPENVOS => Some("ELFOSABI_OPENVOS"), + _ => None, + } +} + +#[cfg(feature = "alloc")] +pub fn e_osabi_to_string(e_osabi: u8) -> String { + match e_osabi_to_str(e_osabi) { + Some(s) => s.to_string(), + None => format!("e_osabi({e_osabi:#x})"), + } +} + +pub fn e_type_to_human_str(e_type: u16) -> Option<&'static str> { + match e_type { + abi::ET_NONE => Some("No file type"), + abi::ET_REL => Some("Relocatable file"), + abi::ET_EXEC => Some("Executable file"), + abi::ET_DYN => Some("Shared object file"), + abi::ET_CORE => Some("Core file"), + _ => None, + } +} + +pub fn e_type_to_str(e_type: u16) -> Option<&'static str> { + match e_type { + abi::ET_NONE => Some("ET_NONE"), + abi::ET_REL => Some("ET_REL"), + abi::ET_EXEC => Some("ET_EXEC"), + abi::ET_DYN => Some("ET_DYN"), + abi::ET_CORE => Some("ET_CORE"), + _ => None, + } +} + +#[cfg(feature = "alloc")] +pub fn e_type_to_string(e_type: u16) -> String { + match e_type_to_str(e_type) { + Some(s) => s.to_string(), + None => format!("e_type({e_type:#x})"), + } +} + +pub fn e_machine_to_human_str(e_machine: u16) -> Option<&'static str> { + match e_machine { + abi::EM_NONE => Some("No machine"), + abi::EM_M32 => Some("AT&T WE 32100"), + abi::EM_SPARC => Some("SPARC"), + abi::EM_386 => Some("Intel 80386"), + abi::EM_68K => Some("Motorola 68000"), + abi::EM_88K => Some("Motorola 88000"), + abi::EM_IAMCU => Some("Intel MCU"), + abi::EM_860 => Some("Intel 80860"), + abi::EM_MIPS => Some("MIPS I Architecture"), + abi::EM_S370 => Some("IBM System/370 Processor"), + abi::EM_MIPS_RS3_LE => Some("MIPS RS3000 Little-endian"), + abi::EM_PARISC => Some("Hewlett-Packard PA-RISC"), + abi::EM_VPP500 => Some("Fujitsu VPP500"), + abi::EM_SPARC32PLUS => Some("Enhanced instruction set SPARC"), + abi::EM_960 => Some("Intel 80960"), + abi::EM_PPC => Some("PowerPC"), + abi::EM_PPC64 => Some("64-bit PowerPC"), + abi::EM_S390 => Some("IBM System/390 Processor"), + abi::EM_SPU => Some("IBM SPU/SPC"), + abi::EM_V800 => Some("NEC V800"), + abi::EM_FR20 => Some("Fujitsu FR20"), + abi::EM_RH32 => Some("TRW RH-32"), + abi::EM_RCE => Some("Motorola RCE"), + abi::EM_ARM => Some("ARM 32-bit architecture (AARCH32)"), + abi::EM_ALPHA => Some("Digital Alpha"), + abi::EM_SH => Some("Hitachi SH"), + abi::EM_SPARCV9 => Some("SPARC Version 9"), + abi::EM_TRICORE => Some("Siemens TriCore embedded processor"), + abi::EM_ARC => Some("Argonaut RISC Core, Argonaut Technologies Inc."), + abi::EM_H8_300 => Some("Hitachi H8/300"), + abi::EM_H8_300H => Some("Hitachi H8/300H"), + abi::EM_H8S => Some("Hitachi H8S"), + abi::EM_H8_500 => Some("Hitachi H8/500"), + abi::EM_IA_64 => Some("Intel IA-64 processor architecture"), + abi::EM_MIPS_X => Some("Stanford MIPS-X"), + abi::EM_COLDFIRE => Some("Motorola ColdFire"), + abi::EM_68HC12 => Some("Motorola M68HC12"), + abi::EM_MMA => Some("Fujitsu MMA Multimedia Accelerator"), + abi::EM_PCP => Some("Siemens PCP"), + abi::EM_NCPU => Some("Sony nCPU embedded RISC processor"), + abi::EM_NDR1 => Some("Denso NDR1 microprocessor"), + abi::EM_STARCORE => Some("Motorola Star*Core processor"), + abi::EM_ME16 => Some("Toyota ME16 processor"), + abi::EM_ST100 => Some("STMicroelectronics ST100 processor"), + abi::EM_TINYJ => Some("Advanced Logic Corp. TinyJ embedded processor family"), + abi::EM_X86_64 => Some("AMD x86-64 architecture"), + abi::EM_PDSP => Some("Sony DSP Processor"), + abi::EM_PDP10 => Some("Digital Equipment Corp. PDP-10"), + abi::EM_PDP11 => Some("Digital Equipment Corp. PDP-11"), + abi::EM_FX66 => Some("Siemens FX66 microcontroller"), + abi::EM_ST9PLUS => Some("STMicroelectronics ST9+ 8/16 bit microcontroller"), + abi::EM_ST7 => Some("STMicroelectronics ST7 8-bit microcontroller"), + abi::EM_68HC16 => Some("Motorola MC68HC16 Microcontroller"), + abi::EM_68HC11 => Some("Motorola MC68HC11 Microcontroller"), + abi::EM_68HC08 => Some("Motorola MC68HC08 Microcontroller"), + abi::EM_68HC05 => Some("Motorola MC68HC05 Microcontroller"), + abi::EM_SVX => Some("Silicon Graphics SVx"), + abi::EM_ST19 => Some("STMicroelectronics ST19 8-bit microcontroller"), + abi::EM_VAX => Some("Digital VAX"), + abi::EM_CRIS => Some("Axis Communications 32-bit embedded processor"), + abi::EM_JAVELIN => Some("Infineon Technologies 32-bit embedded processor"), + abi::EM_FIREPATH => Some("Element 14 64-bit DSP Processor"), + abi::EM_ZSP => Some("LSI Logic 16-bit DSP Processor"), + abi::EM_MMIX => Some("Donald Knuth's educational 64-bit processor"), + abi::EM_HUANY => Some("Harvard University machine-independent object files"), + abi::EM_PRISM => Some("SiTera Prism"), + abi::EM_AVR => Some("Atmel AVR 8-bit microcontroller"), + abi::EM_FR30 => Some("Fujitsu FR30"), + abi::EM_D10V => Some("Mitsubishi D10V"), + abi::EM_D30V => Some("Mitsubishi D30V"), + abi::EM_V850 => Some("NEC v850"), + abi::EM_M32R => Some("Mitsubishi M32R"), + abi::EM_MN10300 => Some("Matsushita MN10300"), + abi::EM_MN10200 => Some("Matsushita MN10200"), + abi::EM_PJ => Some("picoJava"), + abi::EM_OPENRISC => Some("OpenRISC 32-bit embedded processor"), + abi::EM_ARC_COMPACT => Some("ARC International ARCompact processor"), + abi::EM_XTENSA => Some("Tensilica Xtensa Architecture"), + abi::EM_VIDEOCORE => Some("Alphamosaic VideoCore processor"), + abi::EM_TMM_GPP => Some("Thompson Multimedia General Purpose Processor"), + abi::EM_NS32K => Some("National Semiconductor 32000 series"), + abi::EM_TPC => Some("Tenor Network TPC processor"), + abi::EM_SNP1K => Some("Trebia SNP 1000 processor"), + abi::EM_ST200 => Some("STMicroelectronics (www.st.com) ST200 microcontroller"), + abi::EM_IP2K => Some("Ubicom IP2xxx microcontroller family"), + abi::EM_MAX => Some("MAX Processor"), + abi::EM_CR => Some("National Semiconductor CompactRISC microprocessor"), + abi::EM_F2MC16 => Some("Fujitsu F2MC16"), + abi::EM_MSP430 => Some("Texas Instruments embedded microcontroller msp430"), + abi::EM_BLACKFIN => Some("Analog Devices Blackfin (DSP) processor"), + abi::EM_SE_C33 => Some("S1C33 Family of Seiko Epson processors"), + abi::EM_SEP => Some("Sharp embedded microprocessor"), + abi::EM_ARCA => Some("Arca RISC Microprocessor"), + abi::EM_UNICORE => { + Some("Microprocessor series from PKU-Unity Ltd. and MPRC of Peking University") + } + abi::EM_EXCESS => Some("eXcess: 16/32/64-bit configurable embedded CPU"), + abi::EM_DXP => Some("Icera Semiconductor Inc. Deep Execution Processor"), + abi::EM_ALTERA_NIOS2 => Some("Altera Nios II soft-core processor"), + abi::EM_CRX => Some("National Semiconductor CompactRISC CRX microprocessor"), + abi::EM_XGATE => Some("Motorola XGATE embedded processor"), + abi::EM_C166 => Some("Infineon C16x/XC16x processor"), + abi::EM_M16C => Some("Renesas M16C series microprocessors"), + abi::EM_DSPIC30F => Some("Microchip Technology dsPIC30F Digital Signal Controller"), + abi::EM_CE => Some("Freescale Communication Engine RISC core"), + abi::EM_M32C => Some("Renesas M32C series microprocessors"), + abi::EM_TSK3000 => Some("Altium TSK3000 core"), + abi::EM_RS08 => Some("Freescale RS08 embedded processor"), + abi::EM_SHARC => Some("Analog Devices SHARC family of 32-bit DSP processors"), + abi::EM_ECOG2 => Some("Cyan Technology eCOG2 microprocessor"), + abi::EM_SCORE7 => Some("Sunplus S+core7 RISC processor"), + abi::EM_DSP24 => Some("New Japan Radio (NJR) 24-bit DSP Processor"), + abi::EM_VIDEOCORE3 => Some("Broadcom VideoCore III processor"), + abi::EM_LATTICEMICO32 => Some("RISC processor for Lattice FPGA architecture"), + abi::EM_SE_C17 => Some("Seiko Epson C17 family"), + abi::EM_TI_C6000 => Some("The Texas Instruments TMS320C6000 DSP family"), + abi::EM_TI_C2000 => Some("The Texas Instruments TMS320C2000 DSP family"), + abi::EM_TI_C5500 => Some("The Texas Instruments TMS320C55x DSP family"), + abi::EM_TI_ARP32 => { + Some("Texas Instruments Application Specific RISC Processor, 32bit fetch") + } + abi::EM_TI_PRU => Some("Texas Instruments Programmable Realtime Unit"), + abi::EM_MMDSP_PLUS => Some("STMicroelectronics 64bit VLIW Data Signal Processor"), + abi::EM_CYPRESS_M8C => Some("Cypress M8C microprocessor"), + abi::EM_R32C => Some("Renesas R32C series microprocessors"), + abi::EM_TRIMEDIA => Some("NXP Semiconductors TriMedia architecture family"), + abi::EM_QDSP6 => Some("QUALCOMM DSP6 Processor"), + abi::EM_8051 => Some("Intel 8051 and variants"), + abi::EM_STXP7X => { + Some("STMicroelectronics STxP7x family of configurable and extensible RISC processors") + } + abi::EM_NDS32 => Some("Andes Technology compact code size embedded RISC processor family"), + abi::EM_ECOG1X => Some("Cyan Technology eCOG1X family"), + abi::EM_MAXQ30 => Some("Dallas Semiconductor MAXQ30 Core Micro-controllers"), + abi::EM_XIMO16 => Some("New Japan Radio (NJR) 16-bit DSP Processor"), + abi::EM_MANIK => Some("M2000 Reconfigurable RISC Microprocessor"), + abi::EM_CRAYNV2 => Some("Cray Inc. NV2 vector architecture"), + abi::EM_RX => Some("Renesas RX family"), + abi::EM_METAG => Some("Imagination Technologies META processor architecture"), + abi::EM_MCST_ELBRUS => Some("MCST Elbrus general purpose hardware architecture"), + abi::EM_ECOG16 => Some("Cyan Technology eCOG16 family"), + abi::EM_CR16 => Some("National Semiconductor CompactRISC CR16 16-bit microprocessor"), + abi::EM_ETPU => Some("Freescale Extended Time Processing Unit"), + abi::EM_SLE9X => Some("Infineon Technologies SLE9X core"), + abi::EM_L10M => Some("Intel L10M"), + abi::EM_K10M => Some("Intel K10M"), + abi::EM_AARCH64 => Some("ARM 64-bit architecture (AARCH64)"), + abi::EM_AVR32 => Some("Atmel Corporation 32-bit microprocessor family"), + abi::EM_STM8 => Some("STMicroeletronics STM8 8-bit microcontroller"), + abi::EM_TILE64 => Some("Tilera TILE64 multicore architecture family"), + abi::EM_TILEPRO => Some("Tilera TILEPro multicore architecture family"), + abi::EM_MICROBLAZE => Some("Xilinx MicroBlaze 32-bit RISC soft processor core"), + abi::EM_CUDA => Some("NVIDIA CUDA architecture"), + abi::EM_TILEGX => Some("Tilera TILE-Gx multicore architecture family"), + abi::EM_CLOUDSHIELD => Some("CloudShield architecture family"), + abi::EM_COREA_1ST => Some("KIPO-KAIST Core-A 1st generation processor family"), + abi::EM_COREA_2ND => Some("KIPO-KAIST Core-A 2nd generation processor family"), + abi::EM_ARC_COMPACT2 => Some("Synopsys ARCompact V2"), + abi::EM_OPEN8 => Some("Open8 8-bit RISC soft processor core"), + abi::EM_RL78 => Some("Renesas RL78 family"), + abi::EM_VIDEOCORE5 => Some("Broadcom VideoCore V processor"), + abi::EM_78KOR => Some("Renesas 78KOR family"), + abi::EM_56800EX => Some("Freescale 56800EX Digital Signal Controller (DSC)"), + abi::EM_BA1 => Some("Beyond BA1 CPU architecture"), + abi::EM_BA2 => Some("Beyond BA2 CPU architecture"), + abi::EM_XCORE => Some("XMOS xCORE processor family"), + abi::EM_MCHP_PIC => Some("Microchip 8-bit PIC(r) family"), + abi::EM_INTEL205 => Some("Reserved by Intel"), + abi::EM_INTEL206 => Some("Reserved by Intel"), + abi::EM_INTEL207 => Some("Reserved by Intel"), + abi::EM_INTEL208 => Some("Reserved by Intel"), + abi::EM_INTEL209 => Some("Reserved by Intel"), + abi::EM_KM32 => Some("KM211 KM32 32-bit processor"), + abi::EM_KMX32 => Some("KM211 KMX32 32-bit processor"), + abi::EM_KMX16 => Some("KM211 KMX16 16-bit processor"), + abi::EM_KMX8 => Some("KM211 KMX8 8-bit processor"), + abi::EM_KVARC => Some("KM211 KVARC processor"), + abi::EM_CDP => Some("Paneve CDP architecture family"), + abi::EM_COGE => Some("Cognitive Smart Memory Processor"), + abi::EM_COOL => Some("Bluechip Systems CoolEngine"), + abi::EM_NORC => Some("Nanoradio Optimized RISC"), + abi::EM_CSR_KALIMBA => Some("CSR Kalimba architecture family"), + abi::EM_Z80 => Some("Zilog Z80"), + abi::EM_VISIUM => Some("Controls and Data Services VISIUMcore processor"), + abi::EM_FT32 => Some("FTDI Chip FT32 high performance 32-bit RISC architecture"), + abi::EM_MOXIE => Some("Moxie processor family"), + abi::EM_AMDGPU => Some("AMD GPU architecture"), + abi::EM_RISCV => Some("RISC-V"), + abi::EM_BPF => Some("Linux BPF"), + _ => None, + } +} + +pub fn e_machine_to_str(e_machine: u16) -> Option<&'static str> { + match e_machine { + abi::EM_NONE => Some("EM_NONE"), + abi::EM_M32 => Some("EM_M32"), + abi::EM_SPARC => Some("EM_SPARC"), + abi::EM_386 => Some("EM_386"), + abi::EM_68K => Some("EM_68K"), + abi::EM_88K => Some("EM_88K"), + abi::EM_IAMCU => Some("EM_IAMCU"), + abi::EM_860 => Some("EM_860"), + abi::EM_MIPS => Some("EM_MIPS"), + abi::EM_S370 => Some("EM_S370"), + abi::EM_MIPS_RS3_LE => Some("EM_MIPS_RS3_LE"), + abi::EM_PARISC => Some("EM_PARISC"), + abi::EM_VPP500 => Some("EM_VPP500"), + abi::EM_SPARC32PLUS => Some("EM_SPARC32PLUS"), + abi::EM_960 => Some("EM_960"), + abi::EM_PPC => Some("EM_PPC"), + abi::EM_PPC64 => Some("EM_PPC64"), + abi::EM_S390 => Some("EM_S390"), + abi::EM_SPU => Some("EM_SPU"), + abi::EM_V800 => Some("EM_V800"), + abi::EM_FR20 => Some("EM_FR20"), + abi::EM_RH32 => Some("EM_RH32"), + abi::EM_RCE => Some("EM_RCE"), + abi::EM_ARM => Some("EM_ARM"), + abi::EM_ALPHA => Some("EM_ALPHA"), + abi::EM_SH => Some("EM_SH"), + abi::EM_SPARCV9 => Some("EM_SPARCV9"), + abi::EM_TRICORE => Some("EM_TRICORE"), + abi::EM_ARC => Some("EM_ARC"), + abi::EM_H8_300 => Some("EM_H8_300"), + abi::EM_H8_300H => Some("EM_H8_300H"), + abi::EM_H8S => Some("EM_H8S"), + abi::EM_H8_500 => Some("EM_H8_500"), + abi::EM_IA_64 => Some("EM_IA_64"), + abi::EM_MIPS_X => Some("EM_MIPS_X"), + abi::EM_COLDFIRE => Some("EM_COLDFIRE"), + abi::EM_68HC12 => Some("EM_68HC12"), + abi::EM_MMA => Some("EM_MMA"), + abi::EM_PCP => Some("EM_PCP"), + abi::EM_NCPU => Some("EM_NCPU"), + abi::EM_NDR1 => Some("EM_NDR1"), + abi::EM_STARCORE => Some("EM_STARCORE"), + abi::EM_ME16 => Some("EM_ME16"), + abi::EM_ST100 => Some("EM_ST100"), + abi::EM_TINYJ => Some("EM_TINYJ"), + abi::EM_X86_64 => Some("EM_X86_64"), + abi::EM_PDSP => Some("EM_PDSP"), + abi::EM_PDP10 => Some("EM_PDP10"), + abi::EM_PDP11 => Some("EM_PDP11"), + abi::EM_FX66 => Some("EM_FX66"), + abi::EM_ST9PLUS => Some("EM_ST9PLUS"), + abi::EM_ST7 => Some("EM_ST7"), + abi::EM_68HC16 => Some("EM_68HC16"), + abi::EM_68HC11 => Some("EM_68HC11"), + abi::EM_68HC08 => Some("EM_68HC08"), + abi::EM_68HC05 => Some("EM_68HC05"), + abi::EM_SVX => Some("EM_SVX"), + abi::EM_ST19 => Some("EM_ST19"), + abi::EM_VAX => Some("EM_VAX"), + abi::EM_CRIS => Some("EM_CRIS"), + abi::EM_JAVELIN => Some("EM_JAVELIN"), + abi::EM_FIREPATH => Some("EM_FIREPATH"), + abi::EM_ZSP => Some("EM_ZSP"), + abi::EM_MMIX => Some("EM_MMIX"), + abi::EM_HUANY => Some("EM_HUANY"), + abi::EM_PRISM => Some("EM_PRISM"), + abi::EM_AVR => Some("EM_AVR"), + abi::EM_FR30 => Some("EM_FR30"), + abi::EM_D10V => Some("EM_D10V"), + abi::EM_D30V => Some("EM_D30V"), + abi::EM_V850 => Some("EM_V850"), + abi::EM_M32R => Some("EM_M32R"), + abi::EM_MN10300 => Some("EM_MN10300"), + abi::EM_MN10200 => Some("EM_MN10200"), + abi::EM_PJ => Some("EM_PJ"), + abi::EM_OPENRISC => Some("EM_OPENRISC"), + abi::EM_ARC_COMPACT => Some("EM_ARC_COMPACT"), + abi::EM_XTENSA => Some("EM_XTENSA"), + abi::EM_VIDEOCORE => Some("EM_VIDEOCORE"), + abi::EM_TMM_GPP => Some("EM_TMM_GPP"), + abi::EM_NS32K => Some("EM_NS32K"), + abi::EM_TPC => Some("EM_TPC"), + abi::EM_SNP1K => Some("EM_SNP1K"), + abi::EM_ST200 => Some("EM_ST200"), + abi::EM_IP2K => Some("EM_IP2K"), + abi::EM_MAX => Some("EM_MAX"), + abi::EM_CR => Some("EM_CR"), + abi::EM_F2MC16 => Some("EM_F2MC16"), + abi::EM_MSP430 => Some("EM_MSP430"), + abi::EM_BLACKFIN => Some("EM_BLACKFIN"), + abi::EM_SE_C33 => Some("EM_SE_C33"), + abi::EM_SEP => Some("EM_SEP"), + abi::EM_ARCA => Some("EM_ARCA"), + abi::EM_UNICORE => Some("EM_UNICORE"), + abi::EM_EXCESS => Some("EM_EXCESS"), + abi::EM_DXP => Some("EM_DXP"), + abi::EM_ALTERA_NIOS2 => Some("EM_ALTERA_NIOS2"), + abi::EM_CRX => Some("EM_CRX"), + abi::EM_XGATE => Some("EM_XGATE"), + abi::EM_C166 => Some("EM_C166"), + abi::EM_M16C => Some("EM_M16C"), + abi::EM_DSPIC30F => Some("EM_DSPIC30F"), + abi::EM_CE => Some("EM_CE"), + abi::EM_M32C => Some("EM_M32C"), + abi::EM_TSK3000 => Some("EM_TSK3000"), + abi::EM_RS08 => Some("EM_RS08"), + abi::EM_SHARC => Some("EM_SHARC"), + abi::EM_ECOG2 => Some("EM_ECOG2"), + abi::EM_SCORE7 => Some("EM_SCORE7"), + abi::EM_DSP24 => Some("EM_DSP24"), + abi::EM_VIDEOCORE3 => Some("EM_VIDEOCORE3"), + abi::EM_LATTICEMICO32 => Some("EM_LATTICEMICO32"), + abi::EM_SE_C17 => Some("EM_SE_C17"), + abi::EM_TI_C6000 => Some("EM_TI_C6000"), + abi::EM_TI_C2000 => Some("EM_TI_C2000"), + abi::EM_TI_C5500 => Some("EM_TI_C5500"), + abi::EM_TI_ARP32 => Some("EM_TI_ARP32"), + abi::EM_TI_PRU => Some("EM_TI_PRU"), + abi::EM_MMDSP_PLUS => Some("EM_MMDSP_PLUS"), + abi::EM_CYPRESS_M8C => Some("EM_CYPRESS_M8C"), + abi::EM_R32C => Some("EM_R32C"), + abi::EM_TRIMEDIA => Some("EM_TRIMEDIA"), + abi::EM_QDSP6 => Some("EM_QDSP6"), + abi::EM_8051 => Some("EM_8051"), + abi::EM_STXP7X => Some("EM_STXP7X"), + abi::EM_NDS32 => Some("EM_NDS32"), + abi::EM_ECOG1X => Some("EM_ECOG1X"), + abi::EM_MAXQ30 => Some("EM_MAXQ30"), + abi::EM_XIMO16 => Some("EM_XIMO16"), + abi::EM_MANIK => Some("EM_MANIK"), + abi::EM_CRAYNV2 => Some("EM_CRAYNV2"), + abi::EM_RX => Some("EM_RX"), + abi::EM_METAG => Some("EM_METAG"), + abi::EM_MCST_ELBRUS => Some("EM_MCST_ELBRUS"), + abi::EM_ECOG16 => Some("EM_ECOG16"), + abi::EM_CR16 => Some("EM_CR16"), + abi::EM_ETPU => Some("EM_ETPU"), + abi::EM_SLE9X => Some("EM_SLE9X"), + abi::EM_L10M => Some("EM_L10M"), + abi::EM_K10M => Some("EM_K10M"), + abi::EM_AARCH64 => Some("EM_AARCH64"), + abi::EM_AVR32 => Some("EM_AVR32"), + abi::EM_STM8 => Some("EM_STM8"), + abi::EM_TILE64 => Some("EM_TILE64"), + abi::EM_TILEPRO => Some("EM_TILEPRO"), + abi::EM_MICROBLAZE => Some("EM_MICROBLAZE"), + abi::EM_CUDA => Some("EM_CUDA"), + abi::EM_TILEGX => Some("EM_TILEGX"), + abi::EM_CLOUDSHIELD => Some("EM_CLOUDSHIELD"), + abi::EM_COREA_1ST => Some("EM_COREA_1ST"), + abi::EM_COREA_2ND => Some("EM_COREA_2ND"), + abi::EM_ARC_COMPACT2 => Some("EM_ARC_COMPACT2"), + abi::EM_OPEN8 => Some("EM_OPEN8"), + abi::EM_RL78 => Some("EM_RL78"), + abi::EM_VIDEOCORE5 => Some("EM_VIDEOCORE5"), + abi::EM_78KOR => Some("EM_78KOR"), + abi::EM_56800EX => Some("EM_56800EX"), + abi::EM_BA1 => Some("EM_BA1"), + abi::EM_BA2 => Some("EM_BA2"), + abi::EM_XCORE => Some("EM_XCORE"), + abi::EM_MCHP_PIC => Some("EM_MCHP_PIC"), + abi::EM_INTEL205 => Some("EM_INTEL205"), + abi::EM_INTEL206 => Some("EM_INTEL206"), + abi::EM_INTEL207 => Some("EM_INTEL207"), + abi::EM_INTEL208 => Some("EM_INTEL208"), + abi::EM_INTEL209 => Some("EM_INTEL209"), + abi::EM_KM32 => Some("EM_KM32"), + abi::EM_KMX32 => Some("EM_KMX32"), + abi::EM_KMX16 => Some("EM_KMX16"), + abi::EM_KMX8 => Some("EM_KMX8"), + abi::EM_KVARC => Some("EM_KVARC"), + abi::EM_CDP => Some("EM_CDP"), + abi::EM_COGE => Some("EM_COGE"), + abi::EM_COOL => Some("EM_COOL"), + abi::EM_NORC => Some("EM_NORC"), + abi::EM_CSR_KALIMBA => Some("EM_CSR_KALIMBA"), + abi::EM_Z80 => Some("EM_Z80"), + abi::EM_VISIUM => Some("EM_VISIUM"), + abi::EM_FT32 => Some("EM_FT32"), + abi::EM_MOXIE => Some("EM_MOXIE"), + abi::EM_AMDGPU => Some("EM_AMDGPU"), + abi::EM_RISCV => Some("EM_RISCV"), + abi::EM_BPF => Some("EM_BPF"), + _ => None, + } +} + +#[cfg(feature = "alloc")] +pub fn e_machine_to_string(e_machine: u16) -> String { + match e_machine_to_str(e_machine) { + Some(s) => s.to_string(), + None => format!("e_machine({e_machine:#x})"), + } +} + +pub fn sh_type_to_str(sh_type: u32) -> Option<&'static str> { + match sh_type { + abi::SHT_NULL => Some("SHT_NULL"), + abi::SHT_PROGBITS => Some("SHT_PROGBITS"), + abi::SHT_SYMTAB => Some("SHT_SYMTAB"), + abi::SHT_STRTAB => Some("SHT_STRTAB"), + abi::SHT_RELA => Some("SHT_RELA"), + abi::SHT_HASH => Some("SHT_HASH"), + abi::SHT_DYNAMIC => Some("SHT_DYNAMIC"), + abi::SHT_NOTE => Some("SHT_NOTE"), + abi::SHT_NOBITS => Some("SHT_NOBITS"), + abi::SHT_REL => Some("SHT_REL"), + abi::SHT_SHLIB => Some("SHT_SHLIB"), + abi::SHT_DYNSYM => Some("SHT_DYNSYM"), + abi::SHT_INIT_ARRAY => Some("SHT_INIT_ARRAY"), + abi::SHT_FINI_ARRAY => Some("SHT_FINI_ARRAY"), + abi::SHT_PREINIT_ARRAY => Some("SHT_PREINIT_ARRAY"), + abi::SHT_GROUP => Some("SHT_GROUP"), + abi::SHT_SYMTAB_SHNDX => Some("SHT_SYMTAB_SHNDX"), + abi::SHT_GNU_ATTRIBUTES => Some("SHT_GNU_ATTRIBUTES"), + abi::SHT_GNU_HASH => Some("SHT_GNU_HASH"), + abi::SHT_GNU_LIBLIST => Some("SHT_GNU_LIBLIST"), + abi::SHT_GNU_VERDEF => Some("SHT_GNU_VERDEF"), + abi::SHT_GNU_VERNEED => Some("SHT_GNU_VERNEED"), + abi::SHT_GNU_VERSYM => Some("SHT_GNU_VERSYM"), + _ => None, + } +} + +#[cfg(feature = "alloc")] +pub fn sh_type_to_string(sh_type: u32) -> String { + match sh_type_to_str(sh_type) { + Some(s) => s.to_string(), + None => format!("sh_type({sh_type:#x})"), + } +} + +#[cfg(feature = "alloc")] +pub fn p_flags_to_string(p_flags: u32) -> String { + match p_flags < 8 { + true => { + let r = if p_flags & abi::PF_R != 0 { "R" } else { " " }; + let w = if p_flags & abi::PF_W != 0 { "W" } else { " " }; + let x = if p_flags & abi::PF_X != 0 { "E" } else { " " }; + format!("{r}{w}{x}") + } + false => format!("p_flags({p_flags:#x})"), + } +} + +pub fn p_type_to_str(p_type: u32) -> Option<&'static str> { + match p_type { + abi::PT_NULL => Some("PT_NULL"), + abi::PT_LOAD => Some("PT_LOAD"), + abi::PT_DYNAMIC => Some("PT_DYNAMIC"), + abi::PT_INTERP => Some("PT_INTERP"), + abi::PT_NOTE => Some("PT_NOTE"), + abi::PT_SHLIB => Some("PT_SHLIB"), + abi::PT_PHDR => Some("PT_PHDR"), + abi::PT_TLS => Some("PT_TLS"), + abi::PT_GNU_EH_FRAME => Some("PT_GNU_EH_FRAME"), + abi::PT_GNU_STACK => Some("PT_GNU_STACK"), + abi::PT_GNU_RELRO => Some("PT_GNU_RELRO"), + abi::PT_GNU_PROPERTY => Some("PT_GNU_PROPERTY"), + _ => None, + } +} + +#[cfg(feature = "alloc")] +pub fn p_type_to_string(p_type: u32) -> String { + match p_type_to_str(p_type) { + Some(s) => s.to_string(), + None => format!("p_type({p_type:#x})"), + } +} + +pub fn st_symtype_to_str(st_symtype: u8) -> Option<&'static str> { + match st_symtype { + abi::STT_NOTYPE => Some("STT_NOTYPE"), + abi::STT_OBJECT => Some("STT_OBJECT"), + abi::STT_FUNC => Some("STT_FUNC"), + abi::STT_SECTION => Some("STT_SECTION"), + abi::STT_FILE => Some("STT_FILE"), + abi::STT_COMMON => Some("STT_COMMON"), + abi::STT_TLS => Some("STT_TLS"), + abi::STT_GNU_IFUNC => Some("STT_GNU_IFUNC"), + _ => None, + } +} + +#[cfg(feature = "alloc")] +pub fn st_symtype_to_string(st_symtype: u8) -> String { + match st_symtype_to_str(st_symtype) { + Some(s) => s.to_string(), + None => format!("st_symtype({st_symtype:#x})"), + } +} + +pub fn st_bind_to_str(st_bind: u8) -> Option<&'static str> { + match st_bind { + abi::STB_LOCAL => Some("STB_LOCAL"), + abi::STB_GLOBAL => Some("STB_GLOBAL"), + abi::STB_WEAK => Some("STB_WEAK"), + abi::STB_GNU_UNIQUE => Some("STB_GNU_UNIQUE"), + _ => None, + } +} + +#[cfg(feature = "alloc")] +pub fn st_bind_to_string(st_bind: u8) -> String { + match st_bind_to_str(st_bind) { + Some(s) => s.to_string(), + None => format!("st_bind({st_bind:#x})"), + } +} + +pub fn st_vis_to_str(st_vis: u8) -> Option<&'static str> { + match st_vis { + abi::STV_DEFAULT => Some("STV_DEFAULT"), + abi::STV_INTERNAL => Some("STV_INTERNAL"), + abi::STV_HIDDEN => Some("STV_HIDDEN"), + abi::STV_PROTECTED => Some("STV_PROTECTED"), + _ => None, + } +} + +#[cfg(feature = "alloc")] +pub fn st_vis_to_string(st_vis: u8) -> String { + match st_vis_to_str(st_vis) { + Some(s) => s.to_string(), + None => format!("st_vis({st_vis:#x})"), + } +} + +pub fn ch_type_to_str(ch_type: u32) -> Option<&'static str> { + match ch_type { + abi::ELFCOMPRESS_ZLIB => Some("ELFCOMPRESS_ZLIB"), + abi::ELFCOMPRESS_ZSTD => Some("ELFCOMPRESS_ZSTD "), + _ => None, + } +} + +pub fn note_abi_tag_os_to_str(os: u32) -> Option<&'static str> { + match os { + abi::ELF_NOTE_GNU_ABI_TAG_OS_LINUX => Some("Linux"), + abi::ELF_NOTE_GNU_ABI_TAG_OS_GNU => Some("GNU"), + abi::ELF_NOTE_GNU_ABI_TAG_OS_SOLARIS2 => Some("Solaris"), + abi::ELF_NOTE_GNU_ABI_TAG_OS_FREEBSD => Some("FreeBSD"), + _ => None, + } +} + +pub fn d_tag_to_str(d_tag: i64) -> Option<&'static str> { + match d_tag { + abi::DT_NULL => Some("DT_NULL"), + abi::DT_NEEDED => Some("DT_NEEDED"), + abi::DT_PLTRELSZ => Some("DT_PLTRELSZ"), + abi::DT_PLTGOT => Some("DT_PLTGOT"), + abi::DT_HASH => Some("DT_HASH"), + abi::DT_STRTAB => Some("DT_STRTAB"), + abi::DT_SYMTAB => Some("DT_SYMTAB"), + abi::DT_RELA => Some("DT_RELA"), + abi::DT_RELASZ => Some("DT_RELASZ"), + abi::DT_RELAENT => Some("DT_RELAENT"), + abi::DT_STRSZ => Some("DT_STRSZ"), + abi::DT_SYMENT => Some("DT_SYMENT"), + abi::DT_INIT => Some("DT_INIT"), + abi::DT_FINI => Some("DT_FINI"), + abi::DT_SONAME => Some("DT_SONAME"), + abi::DT_RPATH => Some("DT_RPATH"), + abi::DT_SYMBOLIC => Some("DT_SYMBOLIC"), + abi::DT_REL => Some("DT_REL"), + abi::DT_RELSZ => Some("DT_RELSZ"), + abi::DT_RELENT => Some("DT_RELENT"), + abi::DT_PLTREL => Some("DT_PLTREL"), + abi::DT_DEBUG => Some("DT_DEBUG"), + abi::DT_TEXTREL => Some("DT_TEXTREL"), + abi::DT_JMPREL => Some("DT_JMPREL"), + abi::DT_BIND_NOW => Some("DT_BIND_NOW"), + abi::DT_INIT_ARRAY => Some("DT_INIT_ARRAY"), + abi::DT_FINI_ARRAY => Some("DT_FINI_ARRAY"), + abi::DT_INIT_ARRAYSZ => Some("DT_INIT_ARRAYSZ"), + abi::DT_FINI_ARRAYSZ => Some("DT_FINI_ARRAYSZ"), + abi::DT_RUNPATH => Some("DT_RUNPATH"), + abi::DT_FLAGS => Some("DT_FLAGS"), + abi::DT_PREINIT_ARRAY => Some("DT_PREINIT_ARRAY"), + abi::DT_PREINIT_ARRAYSZ => Some("DT_PREINIT_ARRAYSZ"), + abi::DT_SYMTAB_SHNDX => Some("DT_SYMTAB_SHNDX"), + abi::DT_GUILE_GC_ROOT => Some("DT_GUILE_GC_ROOT"), + abi::DT_GUILE_GC_ROOT_SZ => Some("DT_GUILE_GC_ROOT_SZ"), + abi::DT_GUILE_ENTRY => Some("DT_GUILE_ENTRY"), + abi::DT_GUILE_VM_VERSION => Some("DT_GUILE_VM_VERSION"), + abi::DT_GUILE_FRAME_MAPS => Some("DT_GUILE_FRAME_MAPS"), + abi::DT_LOOS => Some("DT_LOOS"), + abi::DT_GNU_PRELINKED => Some("DT_GNU_PRELINKED"), + abi::DT_GNU_CONFLICTSZ => Some("DT_GNU_CONFLICTSZ"), + abi::DT_GNU_LIBLISTSZ => Some("DT_GNU_LIBLISTSZ"), + abi::DT_CHECKSUM => Some("DT_CHECKSUM"), + abi::DT_PLTPADSZ => Some("DT_PLTPADSZ"), + abi::DT_MOVEENT => Some("DT_MOVEENT"), + abi::DT_MOVESZ => Some("DT_MOVESZ"), + abi::DT_FEATURE_1 => Some("DT_FEATURE_1"), + abi::DT_POSFLAG_1 => Some("DT_POSFLAG_1"), + abi::DT_SYMINSZ => Some("DT_SYMINSZ"), + abi::DT_SYMINENT => Some("DT_SYMINENT"), + abi::DT_GNU_HASH => Some("DT_GNU_HASH"), + abi::DT_TLSDESC_PLT => Some("DT_TLSDESC_PLT"), + abi::DT_TLSDESC_GOT => Some("DT_TLSDESC_GOT"), + abi::DT_GNU_CONFLICT => Some("DT_GNU_CONFLICT"), + abi::DT_GNU_LIBLIST => Some("DT_GNU_LIBLIST"), + abi::DT_CONFIG => Some("DT_CONFIG"), + abi::DT_DEPAUDIT => Some("DT_DEPAUDIT"), + abi::DT_AUDIT => Some("DT_AUDIT"), + abi::DT_PLTPAD => Some("DT_PLTPAD"), + abi::DT_MOVETAB => Some("DT_MOVETAB"), + abi::DT_SYMINFO => Some("DT_SYMINFO"), + abi::DT_VERSYM => Some("DT_VERSYM"), + abi::DT_RELACOUNT => Some("DT_RELACOUNT"), + abi::DT_RELCOUNT => Some("DT_RELCOUNT"), + abi::DT_FLAGS_1 => Some("DT_FLAGS_1"), + abi::DT_VERDEF => Some("DT_VERDEF"), + abi::DT_VERDEFNUM => Some("DT_VERDEFNUM"), + abi::DT_VERNEED => Some("DT_VERNEED"), + abi::DT_VERNEEDNUM => Some("DT_VERNEEDNUM"), + abi::DT_HIOS => Some("DT_HIOS"), + abi::DT_LOPROC => Some("DT_LOPROC"), + abi::DT_HIPROC => Some("DT_HIPROC"), + _ => None, + } +} diff --git a/tools/parse_depmod.awk b/tools/parse_depmod.awk new file mode 100755 index 0000000..5907e11 --- /dev/null +++ b/tools/parse_depmod.awk @@ -0,0 +1,60 @@ +#!/bin/awk -f +# SPDX-License-Identifier: (LGPL-2.1 OR LGPL-3.0) +# Copyright (C) SUSE LLC 2025, all rights reserved. +BEGIN { + if (ARGC < 2) { + printf("usage: parse_depmod.awk -- ...\n") > "/dev/stderr" + exit 1 + } + for (i = 1; i < ARGC; i++) { + #printf("searching for %s\n", ARGV[i]) + search[i] = ARGV[i] + delete ARGV[i] + } + search_len = ARGC - 1 + #printf("searching for %d module(s)\n", search_len) +} + +# parse kmod deps into an associative array with module name as key e.g. +# "kernel/fs/btrfs/btrfs.ko: kernel/crypto/xor.ko ..." +# all_mods[btrfs] = "kernel/fs/btrfs/btrfs.ko: kernel/crypto/xor.ko ..." +# TODO: support xz, gz, etc. Currently only supports zst or uncompressed kmods +$1 ~ /^kernel.*:$/ { + line = $0 + # grab the last component of the key + num = split($1, a ,"/") + key_mod = a[num] # btrfs.ko or btrfs.zst: + gsub(/.ko:$|.ko.zst:$/, "", key_mod) # btrfs + all_mods[key_mod] = line # faster if we only split() for search items +} + +END { + for (i = 1; i <= search_len; i++) { + key = search[i] + if (!(key in all_mods)) { + printf("%s missing from depmod\n", key) > "/dev/stderr" + exit 1 + } + #printf("got %s\n", all_mods[key]) + num_deps = split(all_mods[key], this_deps, " ") + found_dep = this_deps[1] + gsub(/:$/, "", found_dep) + if (found_dep in deps_paths) { + #printf("%s already found\n", found_dep) + continue + } + deps_paths[found_dep] = key + for (newdep = 2; newdep <= num_deps; newdep++) { + search_len++ + num=split(this_deps[newdep], a ,"/") + key_mod = a[num] + gsub(/.ko$|.ko.zst$/, "", key_mod) + #printf("new dep %s\n", key_mod); + search[search_len] = key_mod + } + } + + for (found in deps_paths) { + print(found) + } +} diff --git a/vm.sh b/vm.sh index 2ced3b1..3d6785d 100755 --- a/vm.sh +++ b/vm.sh @@ -21,7 +21,7 @@ _vm_start() { local vm_pid_file="${QEMU_PID_DIR}/rapido_vm${vm_num}.pid" local netd_flag netd_mach_id i vm_tap tap_mac n f local vm_resources=() - local vm_num_kparam="rapido.vm_num=${vm_num}" + local vm_num_kparam="rdinit=/rdinit rapido.vm_num=${vm_num}" local net_conf="${VM_NET_CONF}/vm${vm_num}" local qemu_netdev=() local kcmdline=(rd.systemd.unit=dracut-cmdline.service \ @@ -33,7 +33,7 @@ _vm_start() { # XXX could use systemd.hostname=, but it requires systemd-hostnamed n=$(head -n1 "${net_conf}/hostname" 2>/dev/null) \ - && kcmdline+=("rapido.hostname=\"${n}\"") + && kcmdline+=("rapido.hostname=${n}") _rt_qemu_resources_get "${DRACUT_OUT}" vm_resources netd_flag \ || _fail "failed to get qemu resource parameters" diff --git a/vm_autorun.env b/vm_autorun.env index bd6b8ab..5f17d02 100644 --- a/vm_autorun.env +++ b/vm_autorun.env @@ -275,22 +275,27 @@ _vm_ar_load_kmods() { ((${#kmods[*]} > 0)) && modprobe -a "${kmods[@]}" } -_vm_ar_load_kmods -_vm_kcli_param_get "rapido.vm_num" -[ -z "$kcli_rapido_vm_num" ] && _fatal "rapido.vm_num missing in kcli" +# The boot sequence is either: +# rapido-init -> rapido.rc (here with $RAPIDO_INIT) -> /rapido_autorun/* +# or... +# dracut -> 00-rapido-init.sh -> rapido.rc (here) -> /rapido_autorun/* +if [[ ! -n $RAPIDO_INIT ]]; then + # Dracut based init + _vm_ar_load_kmods + _vm_kcli_param_get "rapido.vm_num" + [ -z "$kcli_rapido_vm_num" ] && _fatal "rapido.vm_num missing in kcli" -# Set hostname manually, DHCP may override it -_vm_ar_hostname_set "$kcli_rapido_vm_num" + # Set hostname manually, DHCP may override it + _vm_ar_hostname_set "$kcli_rapido_vm_num" -_vm_ar_network_setup "$kcli_rapido_vm_num" + _vm_ar_network_setup "$kcli_rapido_vm_num" -export TERM="linux" -export PS1="$(cat /proc/sys/kernel/hostname):\${PWD}# " -resize &> /dev/null -_vm_ar_virtfs_mount + export TERM="linux" + export PS1="$(cat /proc/sys/kernel/hostname):\${PWD}# " + _vm_ar_virtfs_mount +fi -# The boot sequence is: -# dracut -> 00-rapido-init.sh -> rapido.rc (here) -> /rapido_autorun/* +resize &> /dev/null for _f in /rapido_autorun/*; do echo "Rapido: starting $_f" [ -f "$_f" ] && . "$_f"