//
// Syd: rock-solid application kernel
// src/elf.rs: ELF parser
//
// Copyright (c) 2024, 2025 Ali Polatel <alip@chesswob.org>
// Based in part upon Paludis' paludis/util/elf.{cc,hh} which is:
//   Copyright (c) 2007 Tiziano Müller
//   Copyright (c) 2007 David Leverton
//   SPDX-License-Identifier: GPL-2.0
// Based in part upon binutils' readelf.c which is:
//   Originally developed by Eric Youngdale <eric@andante.jic.com>
//   Modifications by Nick Clifton <nickc@redhat.com>
//   Copyright (C) 1998-2024 Free Software Foundation, Inc.
//   SPDX-License-Identifier: GPL-3.0-or-later
//
// SPDX-License-Identifier: GPL-3.0

//! Set of functions to manage parsing ELF files

use std::{
    convert::TryInto,
    fmt,
    fmt::Write as FmtWrite,
    fs::File,
    io::{self, BufWriter, ErrorKind, Read, Seek, SeekFrom, Write},
    os::fd::{AsRawFd, FromRawFd},
    process::{Command, Stdio},
};

use iced_x86::{Decoder, DecoderOptions, FastFormatter, Formatter, IntelFormatter};
use nix::{
    errno::Errno,
    unistd::{mkstemp, unlink},
};
use raki::{Decode, Isa};
use serde::{ser::SerializeSeq, Serialize, Serializer};
use yaxpeax_arch::{Arch, Decoder as YDecoder, Reader, U8Reader};
use yaxpeax_arm::{armv7::ARMv7, armv8::a64::ARMv8};

use crate::{err::err2no, fs::safe_open_file, libseccomp::ScmpArch, scmp_arch, XPath};

/// ELF magic number used to identify ELF files.
const ELF_MAGIC: &[u8] = b"\x7FELF";
/// Index of the ELF class byte in the ELF header.
const EI_CLASS: usize = 4;
/// Index of the endianness byte in the ELF header.
const EI_DATA: usize = 5;
/// Index of the version byte in the ELF header.
const EI_VERSION: usize = 6;
/// Value representing a 32-bit ELF file.
const ELFCLASS32: u8 = 1;
/// Value representing a 64-bit ELF file.
const ELFCLASS64: u8 = 2;
/// ELF type value representing no file type.
const ET_NONE: u16 = 0;
/// ELF type value representing an executable file.
const ET_EXEC: u16 = 2;
/// ELF type value representing a shared library or position-independent executable (PIE).
const ET_DYN: u16 = 3;
/// ELF type value representing a relocatable file.
const ET_REL: u16 = 1;
/// ELF type value representing a core file.
const ET_CORE: u16 = 4;
/// ELF type value for processor-specific semantics (low).
const ET_LOPROC: u16 = 0xff00;
/// ELF type value for processor-specific semantics (high).
const ET_HIPROC: u16 = 0xffff;
// Program header table entry unused.
//const PT_NULL: u32 = 0;
// Loadable program segment.
// const PT_LOAD: u32 = 1;
/// Dynamic linking information.
const PT_DYNAMIC: u32 = 2;
/// Program header type value for the interpreter segment (used for dynamic linking).
const PT_INTERP: u32 = 3;
// Auxiliary information.
// const PT_NOTE: u32 = 4;
// Reserved, unspecified semantics.
// const PT_SHLIB: u32 = 5;
// Entry for header table itself.
// const PT_PHDR: u32 = 6;
// Thread local storage segment.
// const PT_TLS: u32 = 7;
/// Hold permissions for the stack on GNU/Linux.
const PT_GNU_STACK: u32 = 0x6474e551;
/// Segment is executable.
const PF_X: u32 = 0x1;
/// Value representing little-endian data encoding.
const ELFDATA2LSB: u8 = 1;
/// Value representing big-endian data encoding.
const ELFDATA2MSB: u8 = 2;
/// Expected ELF version value.
const EV_CURRENT: u8 = 1;

/// Maximum number of program headers to prevent DoS attacks.
const MAX_PROGRAM_HEADERS: usize = 0x0001_0000;
/// Maximum size of program header entry to prevent DoS attacks.
const MAX_PHENT_SIZE: usize = 1024;
/// Maximum allowed size for the dynamic section to prevent DoS attacks.
const MAX_DYNAMIC_SECTION_SIZE: u64 = 16 * 1024 * 1024; // 16 MB

// DT_* constants
// const DT_NULL: u64 = 0;
const DT_FLAGS_1: u64 = 0x6fff_fffb;
const DF_1_PIE: u64 = 0x0800_0000;

/// Enum representing the executable file information.
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
pub enum ExecutableFile {
    /// Represents an ELF file with its class type, file type, linking type, and PIE status.
    Elf {
        /// The class type of the ELF file (32-bit or 64-bit).
        elf_type: ElfType,
        /// The file type of the ELF file (Executable, Library, etc.).
        file_type: ElfFileType,
        /// The linking type of the ELF file (Static or Dynamic), only for executables.
        linking_type: Option<LinkingType>,
        /// Indicates whether the ELF file is a PIE (Position-Independent Executable).
        pie: bool,
        /// Indicates whether the binary has executable stack.
        xs: bool,
    },
    /// Represents a script file with a shebang.
    Script,
}

/// Enum representing the ELF class type (32-bit or 64-bit).
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
pub enum ElfType {
    /// 32-bit ELF file.
    Elf32,
    /// 64-bit ELF file.
    Elf64,
}

/// The native ELF type for the current target architecture.
#[cfg(target_pointer_width = "32")]
pub const ELFTYPE_NATIVE: ElfType = ElfType::Elf32;

/// The native ELF type for the current target architecture.
#[cfg(target_pointer_width = "64")]
pub const ELFTYPE_NATIVE: ElfType = ElfType::Elf64;

/// Enum representing the ELF file type (Executable, Library, Relocatable, Core).
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
pub enum ElfFileType {
    /// No file type.
    None,
    /// Executable file.
    Executable,
    /// Shared object file (library).
    Library,
    /// Relocatable file.
    Relocatable,
    /// Core file.
    Core,
    /// Processor-specific file type.
    ProcessorSpecific,
    /// Unknown file type (reserved for new object file types in the future).
    Unknown,
}

/// Enum representing the ELF linking type (Static or Dynamic).
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
pub enum LinkingType {
    /// Statically linked ELF file.
    Static,
    /// Dynamically linked ELF file.
    Dynamic,
}

/// Enum representing possible errors during ELF parsing.
#[derive(Debug)]
pub enum ElfError {
    /// Error indicating the file does not have a valid ELF magic number.
    BadMagic,
    /// Error indicating the ELF header is malformed.
    Malformed,
    /// Error indicating an I/O error occurred.
    IoError(io::Error),
}

impl fmt::Display for ElfError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            ElfError::BadMagic => write!(f, "Invalid ELF magic number"),
            ElfError::Malformed => write!(f, "Malformed ELF header"),
            ElfError::IoError(e) => write!(f, "I/O error: {}", e),
        }
    }
}

impl From<io::Error> for ElfError {
    fn from(err: io::Error) -> Self {
        ElfError::IoError(err)
    }
}

impl From<ElfError> for Errno {
    fn from(err: ElfError) -> Self {
        match err {
            ElfError::BadMagic => Errno::EINVAL,
            ElfError::Malformed => Errno::EACCES,
            ElfError::IoError(e) => Errno::from_raw(e.raw_os_error().unwrap_or(Errno::EIO as i32)),
        }
    }
}

impl From<Errno> for ElfError {
    fn from(errno: Errno) -> Self {
        ElfError::IoError(io::Error::from_raw_os_error(errno as i32))
    }
}

impl fmt::Display for ExecutableFile {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            ExecutableFile::Elf {
                elf_type,
                file_type,
                linking_type,
                pie,
                xs,
            } => {
                let pie = if *pie { "-pie" } else { "" };
                let xs = if *xs { "-xs" } else { "" };
                if let Some(linking_type) = linking_type {
                    write!(f, "ELF:{file_type}{elf_type}-{linking_type}{pie}{xs}")
                } else {
                    write!(f, "ELF:{file_type}{elf_type}{pie}{xs}")
                }
            }
            ExecutableFile::Script => write!(f, "SCRIPT"),
        }
    }
}

impl fmt::Display for ElfType {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            ElfType::Elf32 => write!(f, "32"),
            ElfType::Elf64 => write!(f, "64"),
        }
    }
}

impl fmt::Display for ElfFileType {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            ElfFileType::None => write!(f, "none"),
            ElfFileType::Executable => write!(f, "exe"),
            ElfFileType::Library => write!(f, "lib"),
            ElfFileType::Relocatable => write!(f, "rel"),
            ElfFileType::Core => write!(f, "core"),
            ElfFileType::ProcessorSpecific => write!(f, "proc"),
            ElfFileType::Unknown => write!(f, "reserved"),
        }
    }
}

impl fmt::Display for LinkingType {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            LinkingType::Static => write!(f, "static"),
            LinkingType::Dynamic => write!(f, "dynamic"),
        }
    }
}

/// Structure representing a disassembled instruction.
#[derive(Clone, Debug)]
pub struct Instruction {
    /// Operation as a string, if available (e.g. may be zero padding).
    pub op: Option<String>,
    /// Hexadecimal encoded instruction bytes
    pub hex: String,
}

impl Serialize for Instruction {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        let mut seq = serializer.serialize_seq(Some(2))?;

        seq.serialize_element(&self.op)?;
        seq.serialize_element(&(self.hex.len() / 2))?;
        seq.serialize_element(&self.hex)?;

        seq.end()
    }
}

// A small enum to hold either a Intel or a Fast formatter for iced-x86.
// This lets us switch between them at runtime without using trait
// objects, since `FastFormatter` doesn't implement `Formatter`.
#[allow(clippy::large_enum_variant)]
enum X86Formatter {
    /// Uses the Intel formatter from iced-x86.
    Intel(IntelFormatter),
    /// Uses the specialized Fast formatter (faster, but fewer options).
    Fast(FastFormatter),
}

impl X86Formatter {
    /// Format a single instruction into the given output string.
    ///
    /// `instr` is the iced_x86 instruction to format,
    /// `output` is the `String` to which the formatted text is appended.
    fn format(&mut self, instr: &iced_x86::Instruction, output: &mut String) {
        match self {
            Self::Intel(fmt) => fmt.format(instr, output),
            Self::Fast(fmt) => fmt.format(instr, output),
        }
    }
}

impl ExecutableFile {
    /// Checks if the file at the given path is a script file by looking
    /// for the `#!` shebang.
    ///
    /// # Arguments
    ///
    /// * `path` - A reference to a `Path` representing the file to be
    ///   checked.
    ///
    /// # Returns
    ///
    /// A `Result` containing `true` if the file is a script, `false`
    /// otherwise, or an `ElfError` if an error occurs.
    ///
    /// # Errors
    ///
    /// This function returns `ElfError::IoError` if an I/O error
    /// occurs.
    ///
    /// # Safety
    ///
    /// This function ensures safety by handling all potential I/O
    /// errors gracefully.
    pub fn is_script_file<F: AsRawFd>(fd: Option<&F>, path: &XPath) -> Result<bool, ElfError> {
        let (mut file, _) = safe_open_file(fd, path)?;
        let mut shebang = [0u8; 2];
        file.read_exact(&mut shebang)?;
        Ok(&shebang == b"#!")
    }

    /// Checks if the file at the given path is an ELF file.
    pub fn is_elf_file<R: Read>(mut reader: R) -> Result<bool, ElfError> {
        let mut magic = [0u8; 4];
        let mut nread = 0;
        while nread < 4 {
            #[allow(clippy::arithmetic_side_effects)]
            match reader.read(&mut magic[nread..]) {
                Ok(0) => break,
                Ok(n) => nread += n,
                Err(ref e) if e.kind() == io::ErrorKind::Interrupted => continue,
                Err(e) => return Err(e.into()),
            }
        }

        if nread < 4 {
            // Short read, not a valid ELF file.
            Ok(false)
        } else {
            // Check the magic \x7FELF bytes.
            Ok(magic == ELF_MAGIC)
        }
    }

    /// Checks if the file at the given path is a valid ELF file.
    ///
    /// # Safety
    ///
    /// This function ensures safety by:
    /// - Validating ELF magic number.
    /// - Checking ELF version.
    /// - Validating endianness.
    /// - Handling all potential I/O errors gracefully.
    pub fn is_valid_elf_file<R: Read>(mut reader: R) -> Result<bool, ElfError> {
        let mut ident = [0u8; 16]; // EI_NIDENT is 16 bytes.
        let mut nread = 0;
        while nread < 16 {
            #[allow(clippy::arithmetic_side_effects)]
            match reader.read(&mut ident[nread..]) {
                Ok(0) => break,
                Ok(n) => nread += n,
                Err(ref e) if e.kind() == io::ErrorKind::Interrupted => continue,
                Err(e) => return Err(e.into()),
            }
        }

        if nread < 16 {
            // Short read, not a valid ELF file.
            return Ok(false);
        }

        // Check the magic \x7FELF bytes.
        if &ident[0..4] != ELF_MAGIC {
            return Ok(false);
        }

        // Check the ELF file version.
        if ident[EI_VERSION] != EV_CURRENT {
            return Ok(false);
        }

        // Check whether the endianness is valid.
        if ident[EI_DATA] != ELFDATA2LSB && ident[EI_DATA] != ELFDATA2MSB {
            return Ok(false);
        }

        // Check the ELF class.
        if ident[EI_CLASS] != ELFCLASS32 && ident[EI_CLASS] != ELFCLASS64 {
            return Ok(false);
        }

        Ok(true)
    }

    /// Parses an executable file from a reader and returns information
    /// about its type, file type, linking type, and PIE status.
    ///
    /// # Arguments
    ///
    /// * `reader` - A reader that implements the `Read` and `Seek`
    ///   traits.
    /// * `check_linking` - A boolean indicating whether to check for
    ///   static or dynamic linking.
    ///
    /// # Returns
    ///
    /// A `Result` containing an `ExecutableFile` enum if successful, or
    /// an `ElfError` if an error occurs.
    ///
    /// # Errors
    ///
    /// This function returns `ElfError::BadMagic` if the file does not
    /// have a valid ELF magic number, `ElfError::Malformed` if the ELF
    /// header is malformed, and `ElfError::IoError` if an I/O error
    /// occurs.
    ///
    /// # Safety
    ///
    /// This function ensures safety by:
    /// - Checking file accessibility by attempting to open the file directly.
    /// - Validating ELF magic number and class.
    /// - Handling all potential I/O errors gracefully.
    /// - Handling endianness properly for different machine architectures.
    /// - Limiting the number of program headers to prevent DoS attacks.
    /// - Checking for script files with the `#!` shebang.
    #[allow(clippy::cognitive_complexity)]
    pub fn parse<R: Read + Seek>(
        mut reader: R,
        check_linking: bool,
    ) -> Result<ExecutableFile, ElfError> {
        let mut header = [0u8; 64];
        let mut bytes_read = 0;
        while bytes_read < header.len() {
            #[allow(clippy::arithmetic_side_effects)]
            match reader.read(&mut header[bytes_read..]) {
                Ok(0) => break,
                Ok(n) => bytes_read += n,
                Err(ref e) if e.kind() == ErrorKind::Interrupted => continue,
                Err(e) => return Err(ElfError::IoError(e)),
            }
        }

        // Check for script files
        match bytes_read {
            0 => return Err(ElfError::BadMagic),
            1 => {
                if header[0] == b'#' {
                    return Ok(ExecutableFile::Script);
                } else {
                    return Err(ElfError::BadMagic);
                }
            }
            2..=3 => {
                if header.starts_with(b"#!") {
                    return Ok(ExecutableFile::Script);
                } else {
                    return Err(ElfError::BadMagic);
                }
            }
            4..=63 => {
                if header.starts_with(b"#!") {
                    return Ok(ExecutableFile::Script);
                } else if bytes_read >= 4 && &header[0..4] != ELF_MAGIC {
                    return Err(ElfError::BadMagic);
                } else {
                    return Err(ElfError::Malformed);
                }
            }
            _ => {
                if header.starts_with(b"#!") {
                    return Ok(ExecutableFile::Script);
                } else if &header[0..4] != ELF_MAGIC {
                    return Err(ElfError::BadMagic);
                }
            }
        }

        // Determine endianness
        let is_big_endian = match header.get(EI_DATA) {
            Some(&ELFDATA2LSB) => false,
            Some(&ELFDATA2MSB) => true,
            _ => return Err(ElfError::Malformed),
        };

        // Determine ELF type
        let elf_type = match header.get(EI_CLASS) {
            Some(&ELFCLASS32) => ElfType::Elf32,
            Some(&ELFCLASS64) => ElfType::Elf64,
            _ => return Err(ElfError::Malformed),
        };

        // Determine ELF file type (Executable, Library, Relocatable, Core, Processor-specific, Unknown)
        let e_type = if is_big_endian {
            read_u16_be(header.get(16..18).ok_or(ElfError::Malformed)?)?
        } else {
            read_u16_le(header.get(16..18).ok_or(ElfError::Malformed)?)?
        };
        let mut file_type = match e_type {
            ET_NONE => ElfFileType::None,
            ET_EXEC => ElfFileType::Executable,
            ET_DYN => ElfFileType::Library,
            ET_REL => ElfFileType::Relocatable,
            ET_CORE => ElfFileType::Core,
            ET_LOPROC..=ET_HIPROC => ElfFileType::ProcessorSpecific,
            _ => ElfFileType::Unknown,
        };

        let mut dynamic = false; // Static,Dynamic linking
        let mut pie = false; // Position Independent Executable
        let mut xs = false; // Executable Stack

        if check_linking && matches!(file_type, ElfFileType::Executable | ElfFileType::Library) {
            // Read program headers
            let (phoff_offset, phnum_offset, phentsize_offset) = if elf_type == ElfType::Elf64 {
                (32, 56, 54)
            } else {
                (28, 44, 42)
            };
            #[allow(clippy::arithmetic_side_effects)]
            let phoff = if elf_type == ElfType::Elf64 {
                if is_big_endian {
                    read_u64_be(
                        header
                            .get(phoff_offset..phoff_offset + 8)
                            .ok_or(ElfError::Malformed)?,
                    )?
                } else {
                    read_u64_le(
                        header
                            .get(phoff_offset..phoff_offset + 8)
                            .ok_or(ElfError::Malformed)?,
                    )?
                }
            } else {
                if is_big_endian {
                    read_u32_be(
                        header
                            .get(phoff_offset..phoff_offset + 4)
                            .ok_or(ElfError::Malformed)?,
                    )?
                } else {
                    read_u32_le(
                        header
                            .get(phoff_offset..phoff_offset + 4)
                            .ok_or(ElfError::Malformed)?,
                    )?
                }
                .into()
            };
            #[allow(clippy::arithmetic_side_effects)]
            let phnum = if is_big_endian {
                read_u16_be(
                    header
                        .get(phnum_offset..phnum_offset + 2)
                        .ok_or(ElfError::Malformed)?,
                )?
            } else {
                read_u16_le(
                    header
                        .get(phnum_offset..phnum_offset + 2)
                        .ok_or(ElfError::Malformed)?,
                )?
            } as usize;
            #[allow(clippy::arithmetic_side_effects)]
            let phentsize = if is_big_endian {
                read_u16_be(
                    header
                        .get(phentsize_offset..phentsize_offset + 2)
                        .ok_or(ElfError::Malformed)?,
                )?
            } else {
                read_u16_le(
                    header
                        .get(phentsize_offset..phentsize_offset + 2)
                        .ok_or(ElfError::Malformed)?,
                )?
            } as usize;

            // Ensure the number of program headers and entry size is
            // within a reasonable limit.
            if phnum == 0
                || phnum > MAX_PROGRAM_HEADERS
                || phentsize == 0
                || phentsize > MAX_PHENT_SIZE
            {
                return Err(ElfError::Malformed);
            }

            // Allocate memory for program headers safely.
            let total_size = phnum.checked_mul(phentsize).ok_or(ElfError::Malformed)?;
            let mut phdrs = Vec::new();
            phdrs.try_reserve(total_size).or(Err(ElfError::Malformed))?;
            phdrs.resize(total_size, 0);
            reader.seek(SeekFrom::Start(phoff))?;
            reader.read_exact(&mut phdrs)?;

            // Set to true when we find PT_GNU_STACK.
            let mut seen_gstack = false;
            // Set to true when we find PT_INTERP.
            let mut seen_interp = false;
            for i in 0..phnum {
                let offset = i.checked_mul(phentsize).ok_or(ElfError::Malformed)?;
                let end = offset.checked_add(4).ok_or(ElfError::Malformed)?;
                if end > phdrs.len() || offset >= phdrs.len() {
                    // If the offset plus the size of the field (4
                    // bytes) exceeds the length of the program header
                    // table, it indicates that the program header entry
                    // cannot be read completely. This scenario can
                    // occur if the ELF file is valid but the headers do
                    // not cover the expected size. We break out of the
                    // loop to avoid further processing of incomplete
                    // data. This approach ensures we do not enter an
                    // infinite loop and handle the ELF file gracefully.
                    break;
                }
                let p_type = if is_big_endian {
                    read_u32_be(&phdrs[offset..end])?
                } else {
                    read_u32_le(&phdrs[offset..end])?
                };
                match p_type {
                    PT_INTERP => {
                        file_type = ElfFileType::Executable;
                        dynamic = true;
                        seen_interp = true;
                    }
                    PT_GNU_STACK => {
                        // Determine the offset to the p_flags field
                        // based on ELF type. In 64-bit ELF, p_flags is
                        // at offset 4 from the start of the program
                        // header. In 32-bit ELF, p_flags is at offset
                        // 24 from the start of the program header.
                        let flags_offset = if elf_type == ElfType::Elf64 {
                            offset.checked_add(4).ok_or(ElfError::Malformed)?
                        } else {
                            offset.checked_add(24).ok_or(ElfError::Malformed)?
                        };

                        // `p_flags` is always 4 bytes in both 32-bit
                        // and 64-bit ELF headers.
                        let flags_end = flags_offset.checked_add(4).ok_or(ElfError::Malformed)?;

                        // Check sanity of offsets.
                        if flags_end > phdrs.len() || flags_offset >= phdrs.len() {
                            break;
                        }

                        let p_flags = if is_big_endian {
                            read_u32_be(&phdrs[flags_offset..flags_end])?
                        } else {
                            read_u32_le(&phdrs[flags_offset..flags_end])?
                        };

                        if p_flags & PF_X != 0 {
                            xs = true;
                        }
                        seen_gstack = true;
                    }
                    _ => continue,
                }

                // We're only here for headers PT_{GNU_STACK,INTERP}.
                // If we've seen both, there's nothing else to do.
                if seen_gstack && seen_interp {
                    break;
                }
            }

            // In case of a missing GNU_STACK ELF header,
            // the loader defaults to an executable stack.
            if !seen_gstack {
                xs = true;
            }

            if let Some((dynamic_section, dynamic_size)) = read_dynamic_section(
                &mut reader,
                &phdrs,
                elf_type,
                is_big_endian,
                phnum,
                phentsize,
            )? {
                pie = is_pie(&dynamic_section, dynamic_size, elf_type, is_big_endian)?;
                if pie {
                    file_type = ElfFileType::Executable;
                }
            }
        }

        // Linking type should be None for libraries
        let linking_type = if file_type == ElfFileType::Library {
            None
        } else if file_type == ElfFileType::Executable {
            if dynamic {
                Some(LinkingType::Dynamic)
            } else {
                Some(LinkingType::Static)
            }
        } else {
            None
        };

        Ok(ExecutableFile::Elf {
            elf_type,
            file_type,
            linking_type,
            pie,
            xs,
        })
    }
}

// Function to determine if the file is PIE (Position Independent Executable)
fn is_pie(
    dynamic_section: &[u8],
    dynamic_size: usize,
    elf_type: ElfType,
    is_big_endian: bool,
) -> Result<bool, ElfError> {
    let entry_size = match elf_type {
        ElfType::Elf32 => 8,
        ElfType::Elf64 => 16,
    };

    for i in (0..dynamic_size).step_by(entry_size) {
        let j = i.checked_add(entry_size / 2).ok_or(ElfError::Malformed)?;
        if j > dynamic_size || i >= dynamic_size {
            // See the comment in parse().
            break;
        }
        #[allow(clippy::collapsible_else_if)]
        let d_tag = if is_big_endian {
            if elf_type == ElfType::Elf64 {
                read_u64_be(&dynamic_section[i..j])?
            } else {
                read_u32_be(&dynamic_section[i..j])?.into()
            }
        } else {
            if elf_type == ElfType::Elf64 {
                read_u64_le(&dynamic_section[i..j])?
            } else {
                read_u32_le(&dynamic_section[i..j])?.into()
            }
        };
        if d_tag == DT_FLAGS_1 {
            let k = i.checked_add(entry_size).ok_or(ElfError::Malformed)?;
            if k > dynamic_size || j >= dynamic_size {
                // See the comment in parse().
                break;
            }
            #[allow(clippy::collapsible_else_if)]
            let d_val = if is_big_endian {
                if elf_type == ElfType::Elf64 {
                    read_u64_be(&dynamic_section[j..k])?
                } else {
                    read_u32_be(&dynamic_section[j..k])?.into()
                }
            } else {
                if elf_type == ElfType::Elf64 {
                    read_u64_le(&dynamic_section[j..k])?
                } else {
                    read_u32_le(&dynamic_section[j..k])?.into()
                }
            };
            return Ok(d_val & DF_1_PIE != 0);
        }
    }
    Ok(false)
}

// Function to read the dynamic section from the ELF file
#[allow(clippy::cognitive_complexity)]
#[allow(clippy::type_complexity)]
fn read_dynamic_section<R: Read + Seek>(
    reader: &mut R,
    phdrs: &[u8],
    elf_type: ElfType,
    is_big_endian: bool,
    phnum: usize,
    phentsize: usize,
) -> Result<Option<(Vec<u8>, usize)>, ElfError> {
    for i in 0..phnum {
        let offset = i.checked_mul(phentsize).ok_or(ElfError::Malformed)?;
        let end = offset.checked_add(4).ok_or(ElfError::Malformed)?;
        if end > phdrs.len() || offset >= phdrs.len() {
            // See the comment in parse().
            break;
        }
        let p_type = if is_big_endian {
            read_u32_be(&phdrs[offset..end])?
        } else {
            read_u32_le(&phdrs[offset..end])?
        };
        if p_type == PT_DYNAMIC {
            let p_offset = if elf_type == ElfType::Elf64 {
                let offset_dyn_min = offset.checked_add(8).ok_or(ElfError::Malformed)?;
                let offset_dyn_max = offset.checked_add(16).ok_or(ElfError::Malformed)?;
                if offset_dyn_max > phdrs.len() || offset_dyn_min >= phdrs.len() {
                    // See the comment in parse().
                    break;
                }
                if is_big_endian {
                    read_u64_be(&phdrs[offset_dyn_min..offset_dyn_max])?
                } else {
                    read_u64_le(&phdrs[offset_dyn_min..offset_dyn_max])?
                }
            } else {
                let offset_dyn_min = offset.checked_add(4).ok_or(ElfError::Malformed)?;
                let offset_dyn_max = offset.checked_add(8).ok_or(ElfError::Malformed)?;
                if offset_dyn_max > phdrs.len() || offset_dyn_min >= phdrs.len() {
                    // See the comment in parse().
                    break;
                }
                if is_big_endian {
                    read_u32_be(&phdrs[offset_dyn_min..offset_dyn_max])?.into()
                } else {
                    read_u32_le(&phdrs[offset_dyn_min..offset_dyn_max])?.into()
                }
            };
            let p_filesz = if elf_type == ElfType::Elf64 {
                let offset_filesz_min = offset.checked_add(32).ok_or(ElfError::Malformed)?;
                let offset_filesz_max = offset.checked_add(40).ok_or(ElfError::Malformed)?;
                if offset_filesz_max > phdrs.len() || offset_filesz_min >= phdrs.len() {
                    // See the comment in parse().
                    break;
                }
                if is_big_endian {
                    read_u64_be(&phdrs[offset_filesz_min..offset_filesz_max])?
                } else {
                    read_u64_le(&phdrs[offset_filesz_min..offset_filesz_max])?
                }
            } else {
                let offset_filesz_min = offset.checked_add(16).ok_or(ElfError::Malformed)?;
                let offset_filesz_max = offset.checked_add(20).ok_or(ElfError::Malformed)?;
                if offset_filesz_max > phdrs.len() || offset_filesz_min >= phdrs.len() {
                    // See the comment in parse().
                    break;
                }
                if is_big_endian {
                    read_u32_be(&phdrs[offset_filesz_min..offset_filesz_max])?.into()
                } else {
                    read_u32_le(&phdrs[offset_filesz_min..offset_filesz_max])?.into()
                }
            };

            // Validate the size to avoid DoS attacks
            if p_filesz > MAX_DYNAMIC_SECTION_SIZE {
                return Err(ElfError::Malformed);
            }

            let file_size = reader.seek(SeekFrom::End(0))?;
            if p_offset > file_size || p_offset.saturating_add(p_filesz) > file_size {
                return Err(ElfError::Malformed);
            }

            reader.seek(SeekFrom::Start(p_offset))?;
            let mut dynamic_section = Vec::new();
            let p_filesz = usize::try_from(p_filesz).or(Err(ElfError::Malformed))?;
            dynamic_section
                .try_reserve(p_filesz)
                .or(Err(ElfError::Malformed))?;
            dynamic_section.resize(p_filesz, 0);
            reader.read_exact(&mut dynamic_section)?;

            return Ok(Some((dynamic_section, p_filesz)));
        }
    }
    Ok(None)
}

// Function to convert bytes to u16 in big-endian order.
fn read_u16_be(bytes: &[u8]) -> Result<u16, ElfError> {
    let arr: [u8; 2] = bytes.try_into().or(Err(ElfError::Malformed))?;
    Ok(u16::from_be_bytes(arr))
}

// Function to convert bytes to u16 in little-endian order.
fn read_u16_le(bytes: &[u8]) -> Result<u16, ElfError> {
    let arr: [u8; 2] = bytes.try_into().or(Err(ElfError::Malformed))?;
    Ok(u16::from_le_bytes(arr))
}

// Function to convert bytes to u32 in big-endian order.
fn read_u32_be(bytes: &[u8]) -> Result<u32, ElfError> {
    let arr: [u8; 4] = bytes.try_into().or(Err(ElfError::Malformed))?;
    Ok(u32::from_be_bytes(arr))
}

// Function to convert bytes to u32 in little-endian order.
fn read_u32_le(bytes: &[u8]) -> Result<u32, ElfError> {
    let arr: [u8; 4] = bytes.try_into().or(Err(ElfError::Malformed))?;
    Ok(u32::from_le_bytes(arr))
}

// Function to convert bytes to u64 in big-endian order.
fn read_u64_be(bytes: &[u8]) -> Result<u64, ElfError> {
    let arr: [u8; 8] = bytes.try_into().or(Err(ElfError::Malformed))?;
    Ok(u64::from_be_bytes(arr))
}

// Function to convert bytes to u64 in little-endian order.
fn read_u64_le(bytes: &[u8]) -> Result<u64, ElfError> {
    let arr: [u8; 8] = bytes.try_into().or(Err(ElfError::Malformed))?;
    Ok(u64::from_le_bytes(arr))
}

/// Disassemble raw machine code into a vector of instructions.
#[allow(clippy::arithmetic_side_effects)]
pub fn disasm(
    machine_code: &[u8],
    arch: ScmpArch,
    ip: u64,
    fast_fmt: bool,
    verbose: bool,
) -> Result<Vec<Instruction>, Errno> {
    match arch {
        ScmpArch::X8664 | ScmpArch::X86 | ScmpArch::X32 => {
            // Use native X86 decoder.
            return disasm_x86(machine_code, arch, ip, fast_fmt, DecoderOptions::NONE);
        }
        ScmpArch::Aarch64 | ScmpArch::Arm => {
            // Use native ARM decoder.
            return disasm_arm(machine_code, arch);
        }
        ScmpArch::Riscv64 => {
            // Use native Riscv64 decoder.
            return disasm_riscv64(machine_code);
        }
        // or else fallback to objdump.
        // TODO: Add llvm-objdump support!
        _ => {}
    }

    // Map architecture to objdump-compatible string
    let arch = scmp_arch2objdump(&arch);

    // Create a temporary file using nix.
    let (fd, path) = mkstemp("/tmp/syd_objdumpXXXXXX")?;

    // SAFETY: mkstemp returns a valid FD.
    let mut file = BufWriter::new(unsafe { File::from_raw_fd(fd) });

    // Write the machine code to the temporary file.
    file.write_all(machine_code).map_err(|err| err2no(&err))?;

    // Close the file, ensure writes persist.
    drop(file);

    // Call objdump with the appropriate arguments.
    let mut command = Command::new("objdump");
    if verbose {
        command.stderr(Stdio::inherit());
    }
    let output = command
        .env("LC_ALL", "C")
        .env("LANG", "C")
        .arg("-D")
        .arg("-b")
        .arg("binary")
        .arg("-m")
        .arg(arch)
        .arg(&path)
        .output()
        .map_err(|err| err2no(&err))?;

    // Clean up the temporary file.
    unlink(&path)?;

    let output = std::str::from_utf8(&output.stdout).or(Err(Errno::EINVAL))?;

    // Parse objdump output
    let mut instructions = Vec::new();
    for line in output.lines() {
        // Check if the line starts with a valid address-like pattern (hexadecimal followed by ':')
        let trimmed = line.trim();
        if let Some(colon_pos) = trimmed.find(':') {
            // Validate the address part (before ':')
            let address_part = &trimmed[..colon_pos];
            if !address_part.chars().all(|c| c.is_ascii_hexdigit()) {
                continue;
            }

            // The part after the colon contains the rest of the disassembled instruction.
            let rest = &trimmed[colon_pos + 1..];
            let parts: Vec<_> = rest.split_whitespace().collect();

            if !parts.is_empty() {
                // Calculate instruction size from hex.
                let hex_end = parts
                    .iter()
                    .position(|&s| s.chars().any(|c| !c.is_ascii_hexdigit()))
                    .unwrap_or(parts.len());
                let hex = parts[..hex_end].join("");

                // Extract operation (mnemonic + operands).
                let op = parts[hex_end..].join(" ");

                // Add the instruction to the list.
                instructions.push(Instruction {
                    hex,
                    op: if op.is_empty() { None } else { Some(op) },
                });
            }
        }
    }

    Ok(instructions)
}

/// Disassemble raw machine code into a vector of instructions.
///
/// `arch` _must_ be one of `ScmpArch::X8664`, `ScmpArch::X86`, or `ScmpArch::X32`,
/// or else this function will return `Err(Errno::ENOSYS)`.
pub fn disasm_x86(
    machine_code: &[u8],
    arch: ScmpArch,
    ip: u64,
    fast_fmt: bool,
    opts: u32,
) -> Result<Vec<Instruction>, Errno> {
    // Determine bitness.
    let bitness = match arch {
        ScmpArch::X8664 => 64,
        ScmpArch::X86 => 32,
        ScmpArch::X32 => 32,
        _ => return Err(Errno::ENOSYS),
    };

    // Create an iced-x86 decoder with the given IP.
    let mut decoder = Decoder::with_ip(bitness, machine_code, ip, opts);

    // Select our runtime formatter, storing it in the enum.
    let mut formatter = if fast_fmt {
        X86Formatter::Fast(FastFormatter::new())
    } else {
        X86Formatter::Intel(IntelFormatter::new())
    };

    // We'll store the final instructions in this vector.
    let mut instructions = Vec::new();

    // Reusable iced_x86 Instruction to avoid extra allocations.
    let mut iced_instr = iced_x86::Instruction::default();

    // Decode until no bytes remain or we hit invalid data.
    while decoder.can_decode() {
        // Decode into `iced_instr`.
        // If it's invalid, we push a “null” instruction.
        decoder.decode_out(&mut iced_instr);

        if iced_instr.is_invalid() {
            // We attempt to extract the failing byte, if any
            let fault_pos = decoder.position().saturating_sub(1);
            let null_hex = if fault_pos < machine_code.len() {
                format!("{:02x}", machine_code[fault_pos])
            } else {
                String::new()
            };

            instructions.push(Instruction {
                hex: null_hex,
                op: Some("null".to_string()),
            });
            continue;
        }

        // Format the instruction.
        let mut text = String::new();
        formatter.format(&iced_instr, &mut text);

        // Instruction size in bytes
        let instr_len = iced_instr.len();
        let end_pos = decoder.position();
        let start_pos = end_pos.saturating_sub(instr_len);

        // Extract the raw bytes,
        // and convert to a hex string (e.g. "0f1f8400000000")
        let raw_bytes = &machine_code[start_pos..end_pos];
        let hex_str = raw_bytes
            .iter()
            .map(|b| format!("{:02x}", b))
            .collect::<Vec<_>>()
            .join("");

        // Push our final instruction struct.
        instructions.push(Instruction {
            hex: hex_str,
            // e.g. "syscall", "nopl 0x0(%rax,%rax,1)", etc.
            op: Some(text),
        });
    }

    Ok(instructions)
}

/// Disassemble raw ARM machine code into a vector of instructions.
///
/// - `arch` must be either `ScmpArch::Arm` (ARMv7) or `ScmpArch::Aarch64` (ARMv8),
///   or this returns `Err(Errno::ENOSYS)`.
///
/// If a decode error occurs, we push a pseudo "null" instruction for the single
/// offending byte and skip it.
fn disasm_arm(machine_code: &[u8], arch: ScmpArch) -> Result<Vec<Instruction>, Errno> {
    match arch {
        ScmpArch::Arm => disasm_armv7(machine_code),
        ScmpArch::Aarch64 => disasm_armv8(machine_code),
        _ => Err(Errno::ENOSYS),
    }
}

/// Helper to decode ARMv7 instructions from `machine_code` using `yaxpeax_arm::armv7::ARMv7`.
/// Returns a vector of `Instruction` with `.hex` and `.op` fields.
fn disasm_armv7(machine_code: &[u8]) -> Result<Vec<Instruction>, Errno> {
    let mut instructions = Vec::new();

    let decoder = <ARMv7 as Arch>::Decoder::default();
    let mut reader = U8Reader::new(machine_code);

    // We track how many bytes we've consumed so far with `old_offset`.
    let mut old_offset = <U8Reader<'_> as yaxpeax_arch::Reader<u32, u8>>::total_offset(&mut reader);

    loop {
        let decode_res = decoder.decode(&mut reader);
        match decode_res {
            Ok(inst) => {
                // Successfully decoded an instruction.
                let new_offset: u32 = <U8Reader<'_> as Reader<u32, u8>>::total_offset(&mut reader);

                // Grab the actual bytes from the input slice.
                let raw_bytes = &machine_code[old_offset as usize..new_offset as usize];
                let mut hex_str = String::new();
                for b in raw_bytes {
                    write!(&mut hex_str, "{b:02x}").or(Err(Errno::ENOMEM))?;
                }

                // Convert instruction to a display string.
                let op_str = inst.to_string();

                instructions.push(Instruction {
                    hex: hex_str,
                    op: Some(op_str),
                });

                // Update offset for the next iteration.
                old_offset = new_offset;
            }
            Err(_decode_err) => {
                // On decode error, we push a "null" for one offending byte if any remain.
                #[allow(clippy::arithmetic_side_effects)]
                if (old_offset as usize) < machine_code.len() {
                    let b = machine_code[old_offset as usize];
                    instructions.push(Instruction {
                        hex: format!("{:02x}", b),
                        op: Some("null".to_string()),
                    });
                    // Manually consume one byte from the reader to move on.
                    // ignoring the actual result
                    let _ = <U8Reader<'_> as Reader<u32, u8>>::next(&mut reader);
                    old_offset += 1;
                } else {
                    // No more data left to consume, so break out.
                    break;
                }
            }
        }

        // If we've consumed everything, break out.
        if (old_offset as usize) >= machine_code.len() {
            break;
        }
    }

    Ok(instructions)
}

/// Helper to decode ARMv8 (AArch64) instructions from `machine_code`
/// using `yaxpeax_arm::armv8::a64::ARMv8`.
fn disasm_armv8(machine_code: &[u8]) -> Result<Vec<Instruction>, Errno> {
    let mut instructions = Vec::new();

    let decoder = <ARMv8 as Arch>::Decoder::default();
    let mut reader = U8Reader::new(machine_code);

    // We track how many bytes we've consumed so far with `old_offset`.
    let mut old_offset = <U8Reader<'_> as yaxpeax_arch::Reader<u64, u8>>::total_offset(&mut reader);

    #[allow(clippy::arithmetic_side_effects)]
    #[allow(clippy::cast_possible_truncation)]
    loop {
        let decode_res = decoder.decode(&mut reader);
        match decode_res {
            Ok(inst) => {
                // Successfully decoded an instruction.
                let new_offset: u64 = <U8Reader<'_> as Reader<u64, u8>>::total_offset(&mut reader);

                // Grab the actual bytes from the input slice.
                let raw_bytes = &machine_code[old_offset as usize..new_offset as usize];
                let mut hex_str = String::new();
                for b in raw_bytes {
                    write!(&mut hex_str, "{b:02x}").or(Err(Errno::ENOMEM))?;
                }

                // Convert instruction to a display string.
                let op_str = inst.to_string();

                instructions.push(Instruction {
                    hex: hex_str,
                    op: Some(op_str),
                });

                // Update offset for the next iteration.
                old_offset = new_offset;
            }
            Err(_decode_err) => {
                // On decode error, we push a "null" for one offending byte if any remain.
                if (old_offset as usize) < machine_code.len() {
                    let b = machine_code[old_offset as usize];
                    instructions.push(Instruction {
                        hex: format!("{:02x}", b),
                        op: Some("null".to_string()),
                    });
                    // Manually consume one byte from the reader to move on.
                    // ignoring the actual result
                    let _ = <U8Reader<'_> as Reader<u64, u8>>::next(&mut reader);
                    old_offset += 1;
                } else {
                    break;
                }
            }
        }

        // If we've consumed everything, break out.
        if (old_offset as usize) >= machine_code.len() {
            break;
        }
    }

    Ok(instructions)
}

/// Disassemble raw RISC-V (RV64) machine code into a vector of instructions.
///
/// Decoding uses the `raki` crate (`raki::Decode`) in `Isa::Rv64` mode.
pub fn disasm_riscv64(machine_code: &[u8]) -> Result<Vec<Instruction>, Errno> {
    let mut instructions = Vec::new();
    let mut offset = 0usize;

    // Loop until we’ve consumed all bytes.
    #[allow(clippy::arithmetic_side_effects)]
    while offset < machine_code.len() {
        let remaining = machine_code.len() - offset;

        // 1) If we have at least 2 bytes, try decode as 16-bit (compressed).
        if remaining >= 2 {
            let half_word_bytes = &machine_code[offset..offset + 2];
            let half_word = u16::from_le_bytes([half_word_bytes[0], half_word_bytes[1]]);

            match half_word.decode(Isa::Rv64) {
                Ok(inst) => {
                    // Decoded a valid 16-bit instruction.
                    let mut hex_str = String::new();
                    for b in half_word_bytes {
                        write!(&mut hex_str, "{b:02x}").or(Err(Errno::ENOMEM))?;
                    }

                    instructions.push(Instruction {
                        hex: hex_str,
                        op: Some(inst.to_string()),
                    });

                    offset += 2;
                    continue; // next iteration
                }
                Err(_) => {
                    // 2) If 16-bit failed and we have at least 4 bytes, try 32-bit.
                    if remaining >= 4 {
                        let word_bytes = &machine_code[offset..offset + 4];
                        let word = u32::from_le_bytes([
                            word_bytes[0],
                            word_bytes[1],
                            word_bytes[2],
                            word_bytes[3],
                        ]);

                        match word.decode(Isa::Rv64) {
                            Ok(inst) => {
                                // Valid 32-bit instruction.
                                let mut hex_str = String::new();
                                for b in word_bytes {
                                    write!(&mut hex_str, "{b:02x}").or(Err(Errno::ENOMEM))?;
                                }

                                instructions.push(Instruction {
                                    hex: hex_str,
                                    op: Some(inst.to_string()),
                                });

                                offset += 4;
                                continue;
                            }
                            Err(_) => {
                                // Both 16-bit and 32-bit decode failed.
                                // => “null” for just the first byte, skip 1.
                                let b = machine_code[offset];
                                instructions.push(Instruction {
                                    hex: format!("{:02x}", b),
                                    op: Some("null".to_string()),
                                });
                                offset += 1;
                                continue;
                            }
                        }
                    } else {
                        // Not enough bytes to try 32-bit => “null” for first byte.
                        let b = machine_code[offset];
                        instructions.push(Instruction {
                            hex: format!("{:02x}", b),
                            op: Some("null".to_string()),
                        });
                        offset += 1;
                        continue;
                    }
                }
            }
        } else {
            // 3) If fewer than 2 bytes remain, we can’t decode 16-bit => “null” each leftover byte.
            let b = machine_code[offset];
            instructions.push(Instruction {
                hex: format!("{:02x}", b),
                op: Some("null".to_string()),
            });
            offset += 1;
        }
    }

    Ok(instructions)
}

/// Convert ScmpArch to objdump architecture name.
/// Map ScmpArch to objdump architecture strings.
pub const fn scmp_arch2objdump(arch: &ScmpArch) -> &'static str {
    match arch {
        ScmpArch::X8664 => "i386:x86-64",
        ScmpArch::X86 => "i386",
        ScmpArch::Arm => "arm",
        ScmpArch::Aarch64 => "aarch64",
        ScmpArch::Loongarch64 => "loongarch64",
        ScmpArch::M68k => "m68k",
        ScmpArch::Mips => "mips",
        ScmpArch::Mips64 => "mips64",
        ScmpArch::Riscv64 => "riscv:rv64",
        ScmpArch::Ppc64 => "powerpc:common64",
        ScmpArch::Ppc64Le => "powerpc:common64",
        ScmpArch::S390X => "s390:64",
        ScmpArch::Sheb => "sheb",
        ScmpArch::Sh => "sh",
        _ => "unknown",
    }
}

/// Return "ret;" instruction based on the given architecture.
pub const fn scmp_ret_instruction(arch: u32) -> &'static [u8] {
    match scmp_arch(arch) {
        Ok(ScmpArch::X8664 | ScmpArch::X32 | ScmpArch::X86) => {
            // x86_64, x32, and i386 use "ret" (c3)
            b"\xc3"
        }
        Ok(ScmpArch::Aarch64) => {
            // aarch64 uses "ret": 0xd65f03c0 (little-endian: \xc0\x03\x5f\xd6)
            b"\xc0\x03\x5f\xd6"
        }
        Ok(ScmpArch::Arm) => {
            // arm uses "bx lr": 0xe12fff1e (little-endian: \x1e\xff\x2f\xe1)
            b"\x1e\xff\x2f\xe1"
        }
        Ok(ScmpArch::Loongarch64) => {
            // loongarch64 uses "ret" (jirl zero, ra, 0): 0x4C000020 (little-endian: \x20\x00\x00\x4c)
            b"\x20\x00\x00\x4c"
        }
        Ok(ScmpArch::Riscv64) => {
            // riscv64 uses "ret" (jalr x0, x1, 0): 0x00008067 (little-endian: \x67\x80\x00\x00)
            b"\x67\x80\x00\x00"
        }
        Ok(ScmpArch::Ppc64 | ScmpArch::Ppc64Le) => {
            // powerpc uses "blr" = \x4e\x80\x00\x20 (big-endian)
            b"\x4e\x80\x00\x20"
        }
        Ok(ScmpArch::S390X) => {
            // s390x uses "br %r14" as a return: \x07\xf0
            b"\x07\xf0"
        }
        _ => {
            // Not supported.
            b""
        }
    }
}

/// Return "syscall;" instruction based on the given architecture.
pub const fn scmp_syscall_instruction(arch: u32) -> &'static [u8] {
    match scmp_arch(arch) {
        Ok(ScmpArch::X8664 | ScmpArch::X32) => {
            // x86_64 and x32 use the "syscall" instruction (0f 05)
            b"\x0f\x05"
        }
        Ok(ScmpArch::X86) => {
            // i386 uses int 0x80 (cd 80) for syscalls
            b"\xcd\x80"
        }
        Ok(ScmpArch::Aarch64) => {
            // aarch64 uses "svc #0" for syscalls: 0xD4 0x00 0x00 0x01 (little-endian: \x01\x00\x00\xd4)
            b"\x01\x00\x00\xd4"
        }
        Ok(ScmpArch::Arm) => {
            // arm uses "svc #0": 0xef000000 (little-endian: \x00\x00\x00\xef)
            b"\x00\x00\x00\xef"
        }
        Ok(ScmpArch::Riscv64) => {
            // riscv64 uses "ecall": 0x00000073 (little-endian: \x73\x00\x00\x00)
            b"\x73\x00\x00\x00"
        }
        Ok(ScmpArch::Ppc64 | ScmpArch::Ppc64Le) => {
            // powerpc uses "sc": 0x44000002 (big-endian), in memory (little-endian machine would store in big-endian?),
            // ppc instructions are traditionally big-endian
            b"\x44\x00\x00\x02"
        }
        Ok(ScmpArch::S390X) => {
            // s390x uses "svc 0": typically \x0a\x00
            b"\x0a\x00"
        }
        _ => {
            // Not supported.
            b""
        }
    }
}

/// Return "syscall; ret;" instruction based on the given architecture.
///
/// Safety: Do NOT use this function to detect SROP, attacker can include
/// noop instructions in between. Use it for logging purposes only.
pub const fn scmp_sysret_instruction(arch: u32) -> &'static [u8] {
    match scmp_arch(arch) {
        Ok(ScmpArch::X8664 | ScmpArch::X32) => {
            // x86_64 and x32 use the "syscall" instruction (0f 05) followed by "ret" (c3)
            b"\x0f\x05\xc3"
        }
        Ok(ScmpArch::X86) => {
            // i386 uses int 0x80 (cd 80) for syscalls followed by ret (c3)
            b"\xcd\x80\xc3"
        }
        Ok(ScmpArch::Aarch64) => {
            // aarch64 uses "svc #0" for syscalls: 0xD4 0x00 0x00 0x01 (little-endian: \x01\x00\x00\xd4)
            // followed by "ret": 0xd65f03c0 (little-endian: \xc0\x03\x5f\xd6)
            b"\x01\x00\x00\xd4\xc0\x03\x5f\xd6"
        }
        Ok(ScmpArch::Arm) => {
            // arm uses "svc #0": 0xef000000 (little-endian: \x00\x00\x00\xef)
            // followed by "bx lr": 0xe12fff1e (little-endian: \x1e\xff\x2f\xe1)
            b"\x00\x00\x00\xef\x1e\xff\x2f\xe1"
        }
        Ok(ScmpArch::Riscv64) => {
            // riscv64 uses "ecall": 0x00000073 (little-endian: \x73\x00\x00\x00)
            // followed by "ret" (jalr x0, x1, 0): 0x00008067 (little-endian: \x67\x80\x00\x00)
            b"\x73\x00\x00\x00\x67\x80\x00\x00"
        }
        Ok(ScmpArch::Ppc64 | ScmpArch::Ppc64Le) => {
            // powerpc uses "sc": 0x44000002 (big-endian), in memory (little-endian machine would store in big-endian?),
            // ppc instructions are traditionally big-endian, but in memory on a little-endian kernel?
            // We'll assume big-endian encoding as per ISA:
            // "sc" = \x44\x00\x00\x02
            // "blr" = \x4e\x80\x00\x20
            b"\x44\x00\x00\x02\x4e\x80\x00\x20"
        }
        Ok(ScmpArch::S390X) => {
            // s390x uses "svc 0": typically \x0a\x00
            // followed by "br %r14" as a return: \x07\xf0
            b"\x0a\x00\x07\xf0"
        }
        _ => {
            // Not supported.
            b""
        }
    }
}

/// Determine the size of the syscall instruction for the given
/// architecture.
#[inline]
pub fn syscall_instruction_size(arch: ScmpArch) -> u64 {
    match arch {
        ScmpArch::X8664 => 2,       // x86-64: 0x0f 0x05
        ScmpArch::X86 => 2,         // x86: 0xcd 0x80 (int 0x80)
        ScmpArch::X32 => 2,         // x32: 0xcd 0x80 (int 0x80)
        ScmpArch::Arm => 4,         // ARM: svc 0x000000
        ScmpArch::Aarch64 => 4,     // AArch64: svc 0x000000
        ScmpArch::Mips => 4,        // MIPS: syscall
        ScmpArch::Mips64 => 4,      // MIPS64: syscall
        ScmpArch::Mips64N32 => 4,   // MIPS64N32: syscall
        ScmpArch::Mipsel => 4,      // MIPSel: syscall
        ScmpArch::Mipsel64 => 4,    // MIPSel64: syscall
        ScmpArch::Mipsel64N32 => 4, // MIPSel64N32: syscall
        ScmpArch::Ppc => 4,         // PowerPC: sc
        ScmpArch::Ppc64 => 4,       // PowerPC64: sc
        ScmpArch::Ppc64Le => 4,     // PowerPC64Le: sc
        ScmpArch::Parisc => 4,      // PA-RISC: syscall
        ScmpArch::Parisc64 => 4,    // PA-RISC64: syscall
        ScmpArch::Riscv64 => 4,     // RISC-V: ecall
        ScmpArch::S390 => 4,        // S/390: svc
        ScmpArch::S390X => 4,       // S/390X: svc
        _ => 4,                     // Default size for any other non-exhaustive enum variants.
    }
}
