1
0
mirror of https://github.com/bitdefender/bddisasm.git synced 2024-11-18 05:28:09 +00:00

Initial Rust bindings implementation

This commit is contained in:
Ionel-Cristinel ANICHITEI 2021-10-19 17:46:46 +03:00
parent 4a485853b6
commit 51dbf5fb0a
24 changed files with 7319 additions and 1 deletions

4
.gitignore vendored
View File

@ -64,6 +64,8 @@ bdshemu_fuzz/out-32
bdshemu_fuzz/out-64
docs/build
libbddisasm.pc
build*
build/
.vscode
disasmtool_lix/_build
rsbddisasm/target
rsbddisasm/Cargo.lock

6
rsbddisasm/Cargo.toml Normal file
View File

@ -0,0 +1,6 @@
[workspace]
members = [
"bddisasm-sys",
"bddisasm",
]

26
rsbddisasm/README.md Normal file
View File

@ -0,0 +1,26 @@
# bddisasm Rust bindings
## Build
Run `cargo build` or `cargo build --release`.
## Directory structure
### bddisasm-sys
This crate uses the [*-sys crate convention](https://doc.rust-lang.org/cargo/reference/build-scripts.html#-sys-packages) and links with `libbddisasm`, exposing a raw, low-level, interface.
### bddisasm
This crate aims to offer a higher-level interface over [bddisasm-sys](#bddisasm-sys). It is currently held back by the fact that I don't really know Rust.
Parts of it are auto-generated, with slight manual changes (for example, the `Mnemonic` enum and related functions).
#### TODO
- [ ] encode registers, not just the register index (for example, instead of `Gpr` we should have `Gpr::Rax`, `Gpr::Eax`, etc)
- [ ] more documentation for the `operand` module, with examples on how to work wit h each operand type
- [ ] more examples for `cpuid`
- [ ] more examples for `cpu_modes`
- [ ] an API to check if an instruction is supported with certain CPU modes on or off (currently a user has to manually check the `CpuModes` structure)
- [ ] implement `Display` for more types (especially those in `operand`)

View File

@ -0,0 +1,25 @@
[package]
name = "bddisasm-sys"
version = "0.1.0"
authors = ["Cristi Anichitei <ianichitei@bitdefender.com>"]
edition = "2018"
links = "bddisasm"
build = "build.rs"
license = "Apache-2.0"
repository = "https://github.com/bitdefender/bddisasm"
documentation = "https://docs.rs/bddisasm-sys"
description = """
Bindings to bddisasm instruction decoder library
"""
categories = ["external-ffi-bindings", "hardware-support"]
keywords = ["disassembler", "decoder", "x86", "amd64", "x86_64", "bindings"]
[lib]
name = "bddisasm_sys"
path = "src/lib.rs"
[dependencies]
[build-dependencies]
bindgen = "0.59.1"
cc = "1.0.70"

View File

@ -0,0 +1 @@
../../LICENSE

View File

@ -0,0 +1,39 @@
/*
* Copyright (c) 2021 Bitdefender
* SPDX-License-Identifier: Apache-2.0
*/
use std::env;
use std::path::PathBuf;
fn main() {
println!("cargo:rerun-if-changed=csrc");
cc::Build::new()
.file("csrc/bddisasm/bddisasm.c")
.file("csrc/bddisasm/bdformat.c")
.file("csrc/bddisasm/crt.c")
.include("csrc/bddisasm/include")
.include("csrc/inc")
.define("BDDISASM_HAS_VSNPRINTF", Some("1"))
.define("BDDISASM_HAS_MEMSET", Some("1"))
.compile("bddisasm");
let bindings = bindgen::Builder::default()
.header("csrc/bddisasm_wrapper.h")
.allowlist_function("Nd.*")
.allowlist_type("ND.*")
.allowlist_var("ND.*")
.rustified_enum(".*")
.impl_debug(true)
.generate_comments(false)
.parse_callbacks(Box::new(bindgen::CargoCallbacks))
.generate()
.expect("Unable to generate bindings");
let out_path = PathBuf::from(env::var("OUT_DIR").unwrap());
bindings
.write_to_file(out_path.join("bindings.rs"))
.expect("Couldn't write bindings!");
println!("cargo:rustc-link-lib=static=bddisasm");
}

View File

@ -0,0 +1 @@
../../../bddisasm

View File

@ -0,0 +1,10 @@
/*
* Copyright (c) 2021 Bitdefender
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdbool.h>
#include <stdint.h>
#include <stdlib.h>
#include "inc/bddisasm.h"

View File

@ -0,0 +1 @@
../../../inc

View File

@ -0,0 +1,93 @@
/*
* Copyright (c) 2021 Bitdefender
* SPDX-License-Identifier: Apache-2.0
*/
#![allow(non_camel_case_types)]
#![allow(non_snake_case)]
#![allow(non_upper_case_globals)]
#![allow(deref_nullptr)] // See https://github.com/rust-lang/rust-bindgen/issues/1651
#![allow(clippy::all)]
include!(concat!(env!("OUT_DIR"), "/bindings.rs"));
#[cfg(test)]
mod tests {
use super::*;
use ::std::os::raw::c_char;
use std::mem;
#[test]
fn get_version() {
let mut major: u32 = 0;
let mut minor: u32 = 0;
let mut revision: u32 = 0;
let mut build_date: *mut c_char = std::ptr::null_mut();
let mut build_time: *mut c_char = std::ptr::null_mut();
unsafe {
NdGetVersion(
&mut major,
&mut minor,
&mut revision,
&mut build_date,
&mut build_time,
);
}
println!("major: {} minor: {} rev: {}", major, minor, revision);
let build_date = unsafe { std::ffi::CStr::from_ptr(build_date) };
let build_date = build_date.to_str().unwrap().to_string();
let build_time = unsafe { std::ffi::CStr::from_ptr(build_time) };
let build_time = build_time.to_str().unwrap().to_string();
println!("Build date: {} build time: {}", build_date, build_time);
// There are no other asserts in this test. Enforcing a known minor version is not worth it, we mainly want to
// see that `NdGetVersion` works.
assert_eq!(major, 1);
}
fn do_decode(code: &[u8]) -> (INSTRUX, NDSTATUS) {
let mut instrux: mem::MaybeUninit<INSTRUX> = mem::MaybeUninit::uninit();
let instrux = instrux.as_mut_ptr();
let status = unsafe {
NdDecodeEx(
instrux,
code.as_ptr(),
code.len() as u64,
ND_CODE_32 as u8,
ND_DATA_32 as u8,
)
};
(unsafe { *instrux }, status)
}
#[test]
fn decode() {
let code = vec![0x90];
let (instrux, status) = do_decode(&code);
assert_eq!(status, 0, "Failed to decode instruction {:#x?}", code);
assert_eq!(
unsafe { instrux.__bindgen_anon_2.Instruction },
_ND_INS_CLASS::ND_INS_NOP
);
}
#[test]
fn format() {
let code = vec![0x89, 0x29];
let (instrux, status) = do_decode(&code);
assert_eq!(status, 0, "Failed to decode instruction {:#x?}", code);
let mut buffer: [i8; ND_MIN_BUF_SIZE as usize] = [0; ND_MIN_BUF_SIZE as usize];
let status = unsafe { NdToText(&instrux, 0, ND_MIN_BUF_SIZE, buffer.as_mut_ptr()) };
assert_eq!(status, 0, "Failed to decode format {:#x?}", code);
let text = String::from_utf8(buffer.iter().map(|&c| c as u8).collect()).unwrap();
let text = text.trim_matches(char::from(0));
assert_eq!(text, "MOV dword ptr [ecx], ebp");
}
}

View File

@ -0,0 +1,17 @@
[package]
name = "bddisasm"
version = "0.1.0"
authors = ["Cristi Anichitei <ianichitei@bitdefender.com>"]
edition = "2018"
license = "Apache-2.0"
readme = "README.md"
repository = "https://github.com/bitdefender/bddisasm"
documentation = "https://docs.rs/bddisasm"
description = """
Bindings to bddisasm instruction decoder library
"""
categories = ["api-bindings", "hardware-support"]
keywords = ["disassembler", "decoder", "x86", "amd64", "x86_64", "bindings"]
[dependencies]
bddisasm-sys = { version = "0.1.0", path = "../bddisasm-sys" }

View File

@ -0,0 +1,147 @@
/*
* Copyright (c) 2021 Bitdefender
* SPDX-License-Identifier: Apache-2.0
*/
//! Offers information about the CPU modes in which an instruction is supported.
//!
//! # Examples
//!
//! ```
//! # use std::error::Error;
//! #
//! # fn main() -> Result<(), Box<dyn Error>> {
//! use bddisasm::decoded_instruction::{DecodedInstruction, DecodeMode, Mnemonic, OperandSize};
//!
//! // `VMXON qword ptr [rax]`
//! let ins = DecodedInstruction::decode(&[0xf3, 0x0f, 0xc7, 0x30], DecodeMode::Bits64)?;
//! let modes = ins.valid_cpu_modes();
//!
//! // Check if the instruction is available from user mode
//! if modes.privilege_level.ring3 {
//! println!("Available in user mode");
//! } else {
//! println!("Not available in user mode");
//! }
//!
//! # Ok(())
//! # }
extern crate bddisasm_sys as ffi;
// TODO: maybe use something like the `bitflags` crate and have all these as flags?
/// Privilege levels (rings) in which an instruction is supported.
#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
pub struct PrivilegeLevel {
pub ring0: bool,
pub ring1: bool,
pub ring2: bool,
pub ring3: bool,
}
/// Operating modes in which an instruction is supported.
#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
pub struct OperatingMode {
/// The instruction is valid in real mode.
pub real: bool,
/// The instruction is valid in Virtual 8086 mode.
pub v8086: bool,
/// The instruction is valid in protected mode (32 bit).
pub protected: bool,
/// The instruction is valid in compatibility mode (32 bit in 64 bit).
pub compat: bool,
/// The instruction is valid in long mode.
pub long: bool,
}
/// Special modes - these may be active inside other modes (example: `TSX` in `Long mode`).
#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
pub struct SpecialModes {
/// The instruction is valid in System Management Mode.
pub smm: bool,
/// The instruction is valid outside System Management Mode.
pub smm_off: bool,
/// The instruction is valid in SGX mode.
pub sgx: bool,
/// The instruction is valid outside SGX mode.
pub sgx_off: bool,
/// The instruction is valid in transactional regions.
pub tsx: bool,
/// The instruction is valid in transactional regions.
pub tsx_off: bool,
}
/// VMX mode - they engulf all the other modes.
#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
pub struct VmxMode {
/// The instruction is valid in VMX root mode.
pub root: bool,
/// The instruction is valid in VMX non root mode.
pub non_root: bool,
/// The instruction is valid in VMX root SEAM.
pub root_seam: bool,
/// The instruction is valid in VMX non-root SEAM.
pub non_root_seam: bool,
/// The instruction is valid outside VMX operation.
pub off: bool,
}
/// Indicates in which modes the instruction is valid in
#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
pub struct CpuModes {
pub privilege_level: PrivilegeLevel,
pub operating_mode: OperatingMode,
pub special_modes: SpecialModes,
pub vmx: VmxMode,
}
#[doc(hidden)]
impl From<ffi::ND_VALID_MODES> for CpuModes {
fn from(raw: ffi::ND_VALID_MODES) -> CpuModes {
let raw = unsafe { raw.__bindgen_anon_1 };
CpuModes {
privilege_level: PrivilegeLevel {
ring0: raw.Ring0() != 0,
ring1: raw.Ring1() != 0,
ring2: raw.Ring2() != 0,
ring3: raw.Ring3() != 0,
},
operating_mode: OperatingMode {
real: raw.Real() != 0,
v8086: raw.V8086() != 0,
protected: raw.Protected() != 0,
compat: raw.Compat() != 0,
long: raw.Long() != 0,
},
special_modes: SpecialModes {
smm: raw.Smm() != 0,
smm_off: raw.SmmOff() != 0,
sgx: raw.Sgx() != 0,
sgx_off: raw.SgxOff() != 0,
tsx: raw.Tsx() != 0,
tsx_off: raw.TsxOff() != 0,
},
vmx: VmxMode {
root: raw.VmxRoot() != 0,
non_root: raw.VmxNonRoot() != 0,
root_seam: raw.VmxRootSeam() != 0,
non_root_seam: raw.VmxNonRootSeam() != 0,
off: raw.VmxOff() != 0,
},
}
}
}

View File

@ -0,0 +1,63 @@
/*
* Copyright (c) 2021 Bitdefender
* SPDX-License-Identifier: Apache-2.0
*/
//! CPUID information used to check if an instruction is supported on a CPU or not.
//!
//! # Examples
//!
//! ```
//! # use std::error::Error;
//! #
//! # fn test() -> Option<()> {
//! use bddisasm::decoded_instruction::{DecodedInstruction, DecodeMode};
//!
//! // `ENCLS`
//! let ins = DecodedInstruction::decode(&[0x0f, 0x01, 0xcf], DecodeMode::Bits64).ok()?;
//! let cpuid = ins.cpuid()?;
//! println!("Leaf: {}", cpuid.leaf);
//! match cpuid.sub_leaf {
//! Some(sub_leaf) => println!("Sub-leaf: {}", sub_leaf),
//! None => println!("Sub-leaf: None"),
//! }
//! println!("Register to check: {} Bit: {}", cpuid.register, cpuid.bit);
//!
//! # Some(())
//! # }
extern crate bddisasm_sys as ffi;
use std::fmt;
/// Describes the CPUID leaf, sub-leaf, register & bit that indicate whether an instruction is supported or not.
#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
pub struct Cpuid {
/// CPUID leaf.
pub leaf: u32,
/// CPUID sub-leaf.
pub sub_leaf: Option<u32>,
/// The register that contains information regarding the instruction.
pub register: u8,
/// Bit inside the register that indicates whether the instruction is present.
pub bit: u64,
}
impl fmt::Display for Cpuid {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self.sub_leaf {
Some(sub_leaf) => write!(
f,
"leaf: {:#x} sub-leaf: {:#x} register: {} bit to test: {:#x}",
self.leaf, sub_leaf, self.register, self.bit
),
None => write!(
f,
"leaf: {:#x} sub-leaf: - register: {} bit to test: {:#x}",
self.leaf, self.register, self.bit
),
}
}
}

View File

@ -0,0 +1,256 @@
/*
* Copyright (c) 2021 Bitdefender
* SPDX-License-Identifier: Apache-2.0
*/
//! Errors that can be encountered when decoding an instruction or when trying to get details about a decoded
//! instruction.
//!
//! # Notes
//!
//! All error codes that can be returned by `bddisasm-sys` are encapsulated in the [DecodeError](DecodeError) enum.
//! However, some of these are unlikely to be encountered when using this crate (for example,
//! [BufferOverflow](DecodeError::BufferOverflow)) which indicates that a buffer passed to the `bddisasm` C library is
//! not large enough.
//!
//! Other errors, such as [UnknownStatus](DecodeError::UnknownStatus) or
//! [UnknownInstruction](DecodeError::UnknownInstruction) are used to indicate that this crate is out of sync with
//! `bddisasm-sys`, which also should never happen.
extern crate bddisasm_sys as ffi;
use std::error::Error;
use std::fmt;
/// Holds all the possible errors that can be encountered by the decoder.
#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
pub enum DecodeError {
/// The underlying bddisasm status is not known. The inner value holds the status as it was returned by bddisasm.
///
/// This usually means that this library is out of sync with bddisasm-sys and is not aware that a new error status
/// was added.
UnknownStatus(u32),
/// The instruction class returned by bddisasm is not known. The inner value holds the instruction class as it was
/// returned by bddisasm.
///
/// This usually means that this library is out of sync with bddisasm-sys and is not aware that a new instruction
/// class was added.
UnknownInstruction(u32),
/// The provided input buffer is too small and does not contain a valid instruction.
BufferTooSmall,
/// Invalid encoding/instruction.
InvalidEncoding,
/// Instruction exceeds the maximum 15 bytes.
InstructionTooLong,
/// Invalid prefix sequence is present.
InvalidPrefixSequence,
/// The instruction uses an invalid register.
InvalidRegisterInInstruction,
/// XOP is present, but also a legacy prefix.
XopWithPrefix,
/// VEX is present, but also a legacy prefix.
VexWithPrefix,
/// EVEX is present, but also a legacy prefix.
EvexWithPrefix,
/// Invalid encoding/instruction.
InvalidEncodingInMode,
/// Invalid usage of LOCK.
BadLockPrefix,
/// An attempt to load the CS register.
CsLoad,
/// 0x66 prefix is not accepted.
Prefix66NotAccepted,
/// 16 bit addressing mode not supported.
AddressingNotSupported16Bit,
/// RIP-relative addressing not supported.
RipRelAddressingNotSupported,
/// Instruction uses VSIB, but SIB is not present.
VsibWithoutSib,
/// VSIB addressing, same vector reg used more than once.
InvalidVsibRegs,
/// VEX.VVVV field must be zero.
VexVvvvMustBeZero,
/// Masking is not supported.
MaskNotSupported,
/// Masking is mandatory.
MaskRequired,
/// Embedded rounding/SAE not supported.
ErSaeNotSupported,
/// Zeroing not supported.
ZeroingNotSupported,
/// Zeroing on memory.
ZeroingOnMemory,
/// Zeroing without masking.
ZeroingNoMask,
/// Broadcast not supported.
BroadcastNotSupported,
/// EVEX.V' field must be one (negated 0).
BadEvexVPrime,
/// EVEX.L'L field is invalid for the instruction.
BadEvexLl,
/// Instruction uses SIBMEM, but SIB is not present.
SibmemWithoutSib,
/// Tile registers are not unique.
InvalidTileRegs,
/// Destination register is not unique (used as src).
InvalidDestRegs,
/// An invalid parameter was provided.
InvalidParameter,
/// The INSTRUX contains unexpected values.
InvalidInstrux,
/// Not enough space is available.
BufferOverflow,
/// Internal library error.
InternalError,
}
impl fmt::Display for DecodeError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
DecodeError::UnknownStatus(value) => write!(f, "unknown status: {:#x}", value),
DecodeError::UnknownInstruction(value) => {
write!(f, "unknown instruction: {:#x}", value)
}
DecodeError::BufferTooSmall => write!(f, "the provided input buffer is too small"),
DecodeError::InvalidEncoding => write!(f, "invalid encoding/instruction"),
DecodeError::InstructionTooLong => {
write!(f, "instruction exceeds the maximum 15 bytes")
}
DecodeError::InvalidPrefixSequence => write!(f, "invalid prefix sequence is present"),
DecodeError::InvalidRegisterInInstruction => {
write!(f, "the instruction uses an invalid register")
}
DecodeError::XopWithPrefix => write!(f, "XOP is present, but also a legacy prefix"),
DecodeError::VexWithPrefix => write!(f, "VEX is present, but also a legacy prefix"),
DecodeError::EvexWithPrefix => write!(f, "EVEX is present, but also a legacy prefix"),
DecodeError::InvalidEncodingInMode => write!(f, "invalid encoding/instruction"),
DecodeError::BadLockPrefix => write!(f, "invalid usage of LOCK"),
DecodeError::CsLoad => write!(f, "an attempt to load the CS register"),
DecodeError::Prefix66NotAccepted => write!(f, "0x66 prefix is not accepted"),
DecodeError::AddressingNotSupported16Bit => {
write!(f, "16 bit addressing mode not supported")
}
DecodeError::RipRelAddressingNotSupported => {
write!(f, "RIP-relative addressing not supported")
}
DecodeError::VsibWithoutSib => {
write!(f, "instruction uses VSIB, but SIB is not present")
}
DecodeError::InvalidVsibRegs => {
write!(
f,
"VSIB addressing with the same vector register used more than once"
)
}
DecodeError::VexVvvvMustBeZero => write!(f, "VEX.VVVV field must be zero"),
DecodeError::MaskNotSupported => write!(f, "masking is not supported"),
DecodeError::MaskRequired => write!(f, "masking is mandatory"),
DecodeError::ErSaeNotSupported => write!(f, "embedded rounding/SAE not supported"),
DecodeError::ZeroingNotSupported => write!(f, "zeroing not supported"),
DecodeError::ZeroingOnMemory => write!(f, "zeroing on memory"),
DecodeError::ZeroingNoMask => write!(f, "zeroing without masking"),
DecodeError::BroadcastNotSupported => write!(f, "broadcast not supported"),
DecodeError::BadEvexVPrime => write!(f, "EVEX.V' field must be one (negated 0)"),
DecodeError::BadEvexLl => write!(f, "EVEX.L'L field is invalid for the instruction"),
DecodeError::SibmemWithoutSib => {
write!(f, "instruction uses SIBMEM, but SIB is not present")
}
DecodeError::InvalidTileRegs => write!(f, "tile registers are not unique"),
DecodeError::InvalidDestRegs => {
write!(f, "destination register is not unique (used as src)")
}
DecodeError::InvalidParameter => write!(f, "an invalid parameter was provided"),
DecodeError::InvalidInstrux => {
write!(f, "the INSTRUX structure contains unexpected values")
}
DecodeError::BufferOverflow => {
write!(f, "not enough space is available to format instruction")
}
DecodeError::InternalError => write!(f, "internal error"),
}
}
}
impl Error for DecodeError {}
pub(crate) fn status_to_error(status: ffi::NDSTATUS) -> Result<(), DecodeError> {
if status == ffi::ND_STATUS_SUCCESS || status == ffi::ND_STATUS_HINT_OPERAND_NOT_USED {
Ok(())
} else {
match status {
ffi::ND_STATUS_BUFFER_TOO_SMALL => Err(DecodeError::BufferTooSmall),
ffi::ND_STATUS_INVALID_ENCODING => Err(DecodeError::InvalidEncoding),
ffi::ND_STATUS_INSTRUCTION_TOO_LONG => Err(DecodeError::InstructionTooLong),
ffi::ND_STATUS_INVALID_PREFIX_SEQUENCE => Err(DecodeError::InvalidPrefixSequence),
ffi::ND_STATUS_INVALID_REGISTER_IN_INSTRUCTION => {
Err(DecodeError::InvalidRegisterInInstruction)
}
ffi::ND_STATUS_XOP_WITH_PREFIX => Err(DecodeError::XopWithPrefix),
ffi::ND_STATUS_VEX_WITH_PREFIX => Err(DecodeError::VexWithPrefix),
ffi::ND_STATUS_EVEX_WITH_PREFIX => Err(DecodeError::EvexWithPrefix),
ffi::ND_STATUS_INVALID_ENCODING_IN_MODE => Err(DecodeError::InvalidEncodingInMode),
ffi::ND_STATUS_BAD_LOCK_PREFIX => Err(DecodeError::BadLockPrefix),
ffi::ND_STATUS_CS_LOAD => Err(DecodeError::CsLoad),
ffi::ND_STATUS_66_NOT_ACCEPTED => Err(DecodeError::Prefix66NotAccepted),
ffi::ND_STATUS_16_BIT_ADDRESSING_NOT_SUPPORTED => {
Err(DecodeError::AddressingNotSupported16Bit)
}
ffi::ND_STATUS_RIP_REL_ADDRESSING_NOT_SUPPORTED => {
Err(DecodeError::RipRelAddressingNotSupported)
}
ffi::ND_STATUS_VSIB_WITHOUT_SIB => Err(DecodeError::VsibWithoutSib),
ffi::ND_STATUS_INVALID_VSIB_REGS => Err(DecodeError::InvalidVsibRegs),
ffi::ND_STATUS_VEX_VVVV_MUST_BE_ZERO => Err(DecodeError::VexVvvvMustBeZero),
ffi::ND_STATUS_MASK_NOT_SUPPORTED => Err(DecodeError::MaskNotSupported),
ffi::ND_STATUS_MASK_REQUIRED => Err(DecodeError::MaskRequired),
ffi::ND_STATUS_ER_SAE_NOT_SUPPORTED => Err(DecodeError::ErSaeNotSupported),
ffi::ND_STATUS_ZEROING_NOT_SUPPORTED => Err(DecodeError::ZeroingNotSupported),
ffi::ND_STATUS_ZEROING_ON_MEMORY => Err(DecodeError::ZeroingOnMemory),
ffi::ND_STATUS_ZEROING_NO_MASK => Err(DecodeError::ZeroingNoMask),
ffi::ND_STATUS_BROADCAST_NOT_SUPPORTED => Err(DecodeError::BroadcastNotSupported),
ffi::ND_STATUS_BAD_EVEX_V_PRIME => Err(DecodeError::BadEvexVPrime),
ffi::ND_STATUS_BAD_EVEX_LL => Err(DecodeError::BadEvexLl),
ffi::ND_STATUS_SIBMEM_WITHOUT_SIB => Err(DecodeError::SibmemWithoutSib),
ffi::ND_STATUS_INVALID_TILE_REGS => Err(DecodeError::InvalidTileRegs),
ffi::ND_STATUS_INVALID_DEST_REGS => Err(DecodeError::InvalidDestRegs),
ffi::ND_STATUS_INVALID_PARAMETER => Err(DecodeError::InvalidParameter),
ffi::ND_STATUS_INVALID_INSTRUX => Err(DecodeError::InvalidInstrux),
ffi::ND_STATUS_BUFFER_OVERFLOW => Err(DecodeError::BufferOverflow),
ffi::ND_STATUS_INTERNAL_ERROR => Err(DecodeError::InternalError),
_ => Err(DecodeError::UnknownStatus(status)),
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,215 @@
/*
* Copyright (c) 2021 Bitdefender
* SPDX-License-Identifier: Apache-2.0
*/
//! Decodes instructions.
extern crate bddisasm_sys as ffi;
pub use crate::decode_error::DecodeError;
pub use crate::decoded_instruction::{DecodeMode, DecodeResult, DecodedInstruction};
pub use crate::mnemonic::Mnemonic;
/// Decodes instructions.
#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
pub struct Decoder<'a> {
code: &'a [u8],
ip: u64,
mode: DecodeMode,
offset: usize,
}
impl<'a> Decoder<'a> {
/// Creates a new decoder.
///
/// # Arguments
///
/// * `code` - An [u8](u8) slice that holds the code to be decoded.
/// * `mode` - The mode in which to decode the instruction.
/// * `ip` - The instruction pointer value to use when formatting the decoded instruction. Does not affect the
/// decoding process in any way.
pub fn new(code: &'a [u8], mode: DecodeMode, ip: u64) -> Self {
Self {
code,
mode,
ip,
offset: 0,
}
}
/// Attempts to decode the next instruction from the given code chunk.
///
/// # Returns
///
/// * `Some(DecodeResult)` - if there are still undecoded bytes in the given code chunk. The decoding may have
/// still failed. See `Remarks`.
/// * `None` - if all the bytes in the given code chunk were decoded.
///
/// # Remarks
///
/// This function decodes one instruction from the given code chunk at a time. After each call, the offset inside
/// the code chunk is advanced by:
///
/// - the size of the decoded instruction, if decoding was succesfull
/// - 1, if decoding was not succesfull
///
/// The `ip` value specified when the decoder was created is automatically updated:
///
/// - if the decoding was succesfull, it is incremented with the instruction size
/// - if the decoding was not succesfull, it is incremented with 1
///
/// # Examples
///
/// ```
/// # use std::error::Error;
/// #
/// # fn main() -> Result<(), Box<dyn Error>> {
/// use bddisasm::decoder::{Decoder, DecodeMode};
///
/// let mut decoder = Decoder::new(&[0x51, 0x53], DecodeMode::Bits32, 0);
///
/// // As long as we have something to decode
/// while let Some(result) = decoder.decode_next() {
/// // Check if the decoding succeeded
/// match result {
/// Ok(instruction) => println!("{}", instruction),
/// Err(e) => println!("Unable to decode: {}", e),
/// }
/// }
///
/// # Ok(())
/// # }
/// ```
pub fn decode_next(&mut self) -> Option<DecodeResult> {
if self.offset >= self.code.len() {
None
} else {
let result =
DecodedInstruction::decode_with_ip(&self.code[self.offset..], self.mode, self.ip);
match result {
Ok(ins) => {
self.offset += ins.length() as usize;
self.ip += ins.length() as u64;
}
Err(_) => {
self.offset += 1;
self.ip += 1;
}
};
Some(result)
}
}
/// Attempts to decode the next instruction from the given code chunk.
///
/// Behaves like [decode_next](Decoder::decode_next), but in addition to the [DecodeResult](DecodeResult) it will
/// also return the offset from which decoding was attempted, as well as the corresponding instruction pointer.
///
/// # Examples
///
/// ```
/// # use std::error::Error;
/// #
/// # fn main() -> Result<(), Box<dyn Error>> {
/// use bddisasm::decoder::{Decoder, DecodeMode};
///
/// let mut decoder = Decoder::new(&[0x51, 0x53], DecodeMode::Bits32, 0x1000);
///
/// // As long as we have something to decode
/// while let Some((result, offset, ip)) = decoder.decode_next_with_info() {
/// // Check if the decoding succeeded
/// match result {
/// Ok(instruction) => println!("IP: {:#x} {}", ip, instruction),
/// Err(e) => println!("Unable to decode at offset {:#x}: {}", offset, e),
/// }
/// }
///
/// # Ok(())
/// # }
/// ```
pub fn decode_next_with_info(&mut self) -> Option<(DecodeResult, usize, u64)> {
let offset = self.offset;
let ip = self.ip;
self.decode_next().map(|res| (res, offset, ip))
}
}
impl Iterator for Decoder<'_> {
type Item = DecodeResult;
#[inline]
fn next(&mut self) -> Option<Self::Item> {
self.decode_next()
}
}
impl std::iter::FusedIterator for Decoder<'_> {}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn decode_next() {
let code = vec![0xb8, 0x00, 0x00, 0x00, 0x00, 0x48, 0x8b, 0xf9, 0xff, 0xff];
let mut decoder = Decoder::new(&code, DecodeMode::Bits64, 0x1000);
let expected: Vec<Result<(Mnemonic, &str, &[u8]), DecodeError>> = vec![
Ok((
Mnemonic::Mov,
"MOV eax, 0x00000000",
&[0xb8, 0x00, 0x00, 0x00, 0x00],
)),
Ok((Mnemonic::Mov, "MOV rdi, rcx", &[0x48, 0x8b, 0xf9])),
Err(DecodeError::InvalidEncoding),
Err(DecodeError::BufferTooSmall),
];
let mut exected_index = 0usize;
while let Some(ins) = decoder.decode_next() {
match expected[exected_index] {
Ok((i, s, b)) => {
let ins = ins.expect("Unable to decode");
assert_eq!(i, ins.mnemonic());
assert_eq!(b, ins.bytes());
assert_eq!(s, format!("{}", ins));
}
Err(e) => {
assert_eq!(e, ins.expect_err("Expected error"));
}
};
exected_index += 1;
}
}
#[test]
fn decoder_iter() {
let code = vec![0xb8, 0x00, 0x00, 0x00, 0x00, 0x48, 0x8b, 0xf9, 0xff, 0xff];
let decoder = Decoder::new(&code, DecodeMode::Bits64, 0x1000);
let expected: Vec<Result<(Mnemonic, &str, &[u8]), DecodeError>> = vec![
Ok((
Mnemonic::Mov,
"MOV eax, 0x00000000",
&[0xb8, 0x00, 0x00, 0x00, 0x00],
)),
Ok((Mnemonic::Mov, "MOV rdi, rcx", &[0x48, 0x8b, 0xf9])),
Err(DecodeError::InvalidEncoding),
Err(DecodeError::BufferTooSmall),
];
for (index, ins) in decoder.enumerate() {
match expected[index] {
Ok((i, s, b)) => {
let ins = ins.expect("Unable to decode");
assert_eq!(i, ins.mnemonic());
assert_eq!(b, ins.bytes());
assert_eq!(s, format!("{}", ins));
}
Err(e) => {
assert_eq!(e, ins.expect_err("Expected error"));
}
};
}
}
}

View File

@ -0,0 +1,58 @@
/*
* Copyright (c) 2021 Bitdefender
* SPDX-License-Identifier: Apache-2.0
*/
//! Offers information about how an instructions accesses the FPU status registers.
extern crate bddisasm_sys as ffi;
/// The mode in which a FPU status flag is accessed.
#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
pub enum FpuFlagsAccess {
/// The FPU status flag is cleared to 0.
Cleared,
/// The FPU status flag is set to 1.
Set,
/// The FPU status flag is modified according to a result.
Modified,
/// The FPU status flag is undefined or unaffected.
Undefined,
}
impl From<u8> for FpuFlagsAccess {
fn from(value: u8) -> FpuFlagsAccess {
let value = value as u32;
match value {
ffi::ND_FPU_FLAG_SET_0 => FpuFlagsAccess::Cleared,
ffi::ND_FPU_FLAG_SET_1 => FpuFlagsAccess::Set,
ffi::ND_FPU_FLAG_MODIFIED => FpuFlagsAccess::Modified,
ffi::ND_FPU_FLAG_UNDEFINED => FpuFlagsAccess::Undefined,
_ => panic!("Unexpected FPU flag access: {}", value),
}
}
}
/// Describes the way an instruction accesses the FPU flags registers.
#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
pub struct FpuFlags {
/// C0 flag access mode.
pub c0: FpuFlagsAccess,
/// C1 flag access mode.
pub c1: FpuFlagsAccess,
/// C2 flag access mode.
pub c2: FpuFlagsAccess,
/// C3 flag access mode.
pub c3: FpuFlagsAccess,
}
#[doc(hidden)]
impl From<ffi::ND_FPU_FLAGS> for FpuFlags {
fn from(flags: ffi::ND_FPU_FLAGS) -> FpuFlags {
FpuFlags {
c0: FpuFlagsAccess::from(flags.C0()),
c1: FpuFlagsAccess::from(flags.C1()),
c2: FpuFlagsAccess::from(flags.C2()),
c3: FpuFlagsAccess::from(flags.C3()),
}
}
}

View File

@ -0,0 +1,236 @@
/*
* Copyright (c) 2021 Bitdefender
* SPDX-License-Identifier: Apache-2.0
*/
//! Instruction categories.
extern crate bddisasm_sys as ffi;
use super::decode_error;
use std::convert::TryFrom;
/// Instruction category.
#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
pub enum Category {
I3dnow,
Aes,
Aeskl,
Amx,
Arith,
Avx,
Avx2,
Avx2gather,
Avx512,
Avx512bf16,
Avx512fp16,
Avx512vbmi,
Avx512vp2intersect,
Avxvnni,
Bitbyte,
Blend,
Bmi1,
Bmi2,
Broadcast,
Call,
Cet,
Cldemote,
Cmov,
Compress,
CondBr,
Conflict,
Convert,
Dataxfer,
Decimal,
Enqcmd,
Expand,
Flagop,
Fma4,
Fred,
Gather,
Gfni,
Hreset,
I386,
Ifma,
Interrupt,
Io,
Iostringop,
Kl,
Kmask,
Knl,
Lkgs,
Logic,
Logical,
LogicalFp,
Lwp,
Lzcnt,
Misc,
Mmx,
Movdir64b,
Movdiri,
Mpx,
Nop,
Padlock,
Pclmulqdq,
Pconfig,
Pop,
Prefetch,
Ptwrite,
Push,
Rdpid,
Rdrand,
Rdseed,
Rdwrfsgs,
Ret,
Rotate,
Scatter,
Segop,
Semaphore,
Sgx,
Sha,
Shift,
Smap,
Sse,
Sse2,
Stringop,
Sttni,
Syscall,
Sysret,
System,
Tdx,
Ud,
Uintr,
UncondBr,
Unknown,
Vaes,
Vfma,
Vfmaps,
Vnni,
Vnniw,
Vpclmulqdq,
Vpopcnt,
Vtx,
Waitpkg,
Wbnoinvd,
Widenop,
WideKl,
X87Alu,
Xop,
Xsave,
}
#[doc(hidden)]
impl TryFrom<ffi::ND_INS_CATEGORY> for Category {
type Error = decode_error::DecodeError;
fn try_from(value: ffi::ND_INS_CATEGORY) -> Result<Self, Self::Error> {
match value {
ffi::_ND_INS_TYPE::ND_CAT_INVALID => {
Err(decode_error::DecodeError::UnknownInstruction(value as u32))
}
ffi::_ND_INS_TYPE::ND_CAT_3DNOW => Ok(Category::I3dnow),
ffi::_ND_INS_TYPE::ND_CAT_AES => Ok(Category::Aes),
ffi::_ND_INS_TYPE::ND_CAT_AESKL => Ok(Category::Aeskl),
ffi::_ND_INS_TYPE::ND_CAT_AMX => Ok(Category::Amx),
ffi::_ND_INS_TYPE::ND_CAT_ARITH => Ok(Category::Arith),
ffi::_ND_INS_TYPE::ND_CAT_AVX => Ok(Category::Avx),
ffi::_ND_INS_TYPE::ND_CAT_AVX2 => Ok(Category::Avx2),
ffi::_ND_INS_TYPE::ND_CAT_AVX2GATHER => Ok(Category::Avx2gather),
ffi::_ND_INS_TYPE::ND_CAT_AVX512 => Ok(Category::Avx512),
ffi::_ND_INS_TYPE::ND_CAT_AVX512BF16 => Ok(Category::Avx512bf16),
ffi::_ND_INS_TYPE::ND_CAT_AVX512FP16 => Ok(Category::Avx512fp16),
ffi::_ND_INS_TYPE::ND_CAT_AVX512VBMI => Ok(Category::Avx512vbmi),
ffi::_ND_INS_TYPE::ND_CAT_AVX512VP2INTERSECT => Ok(Category::Avx512vp2intersect),
ffi::_ND_INS_TYPE::ND_CAT_AVXVNNI => Ok(Category::Avxvnni),
ffi::_ND_INS_TYPE::ND_CAT_BITBYTE => Ok(Category::Bitbyte),
ffi::_ND_INS_TYPE::ND_CAT_BLEND => Ok(Category::Blend),
ffi::_ND_INS_TYPE::ND_CAT_BMI1 => Ok(Category::Bmi1),
ffi::_ND_INS_TYPE::ND_CAT_BMI2 => Ok(Category::Bmi2),
ffi::_ND_INS_TYPE::ND_CAT_BROADCAST => Ok(Category::Broadcast),
ffi::_ND_INS_TYPE::ND_CAT_CALL => Ok(Category::Call),
ffi::_ND_INS_TYPE::ND_CAT_CET => Ok(Category::Cet),
ffi::_ND_INS_TYPE::ND_CAT_CLDEMOTE => Ok(Category::Cldemote),
ffi::_ND_INS_TYPE::ND_CAT_CMOV => Ok(Category::Cmov),
ffi::_ND_INS_TYPE::ND_CAT_COMPRESS => Ok(Category::Compress),
ffi::_ND_INS_TYPE::ND_CAT_COND_BR => Ok(Category::CondBr),
ffi::_ND_INS_TYPE::ND_CAT_CONFLICT => Ok(Category::Conflict),
ffi::_ND_INS_TYPE::ND_CAT_CONVERT => Ok(Category::Convert),
ffi::_ND_INS_TYPE::ND_CAT_DATAXFER => Ok(Category::Dataxfer),
ffi::_ND_INS_TYPE::ND_CAT_DECIMAL => Ok(Category::Decimal),
ffi::_ND_INS_TYPE::ND_CAT_ENQCMD => Ok(Category::Enqcmd),
ffi::_ND_INS_TYPE::ND_CAT_EXPAND => Ok(Category::Expand),
ffi::_ND_INS_TYPE::ND_CAT_FLAGOP => Ok(Category::Flagop),
ffi::_ND_INS_TYPE::ND_CAT_FMA4 => Ok(Category::Fma4),
ffi::_ND_INS_TYPE::ND_CAT_FRED => Ok(Category::Fred),
ffi::_ND_INS_TYPE::ND_CAT_GATHER => Ok(Category::Gather),
ffi::_ND_INS_TYPE::ND_CAT_GFNI => Ok(Category::Gfni),
ffi::_ND_INS_TYPE::ND_CAT_HRESET => Ok(Category::Hreset),
ffi::_ND_INS_TYPE::ND_CAT_I386 => Ok(Category::I386),
ffi::_ND_INS_TYPE::ND_CAT_IFMA => Ok(Category::Ifma),
ffi::_ND_INS_TYPE::ND_CAT_INTERRUPT => Ok(Category::Interrupt),
ffi::_ND_INS_TYPE::ND_CAT_IO => Ok(Category::Io),
ffi::_ND_INS_TYPE::ND_CAT_IOSTRINGOP => Ok(Category::Iostringop),
ffi::_ND_INS_TYPE::ND_CAT_KL => Ok(Category::Kl),
ffi::_ND_INS_TYPE::ND_CAT_KMASK => Ok(Category::Kmask),
ffi::_ND_INS_TYPE::ND_CAT_KNL => Ok(Category::Knl),
ffi::_ND_INS_TYPE::ND_CAT_LKGS => Ok(Category::Lkgs),
ffi::_ND_INS_TYPE::ND_CAT_LOGIC => Ok(Category::Logic),
ffi::_ND_INS_TYPE::ND_CAT_LOGICAL => Ok(Category::Logical),
ffi::_ND_INS_TYPE::ND_CAT_LOGICAL_FP => Ok(Category::LogicalFp),
ffi::_ND_INS_TYPE::ND_CAT_LWP => Ok(Category::Lwp),
ffi::_ND_INS_TYPE::ND_CAT_LZCNT => Ok(Category::Lzcnt),
ffi::_ND_INS_TYPE::ND_CAT_MISC => Ok(Category::Misc),
ffi::_ND_INS_TYPE::ND_CAT_MMX => Ok(Category::Mmx),
ffi::_ND_INS_TYPE::ND_CAT_MOVDIR64B => Ok(Category::Movdir64b),
ffi::_ND_INS_TYPE::ND_CAT_MOVDIRI => Ok(Category::Movdiri),
ffi::_ND_INS_TYPE::ND_CAT_MPX => Ok(Category::Mpx),
ffi::_ND_INS_TYPE::ND_CAT_NOP => Ok(Category::Nop),
ffi::_ND_INS_TYPE::ND_CAT_PADLOCK => Ok(Category::Padlock),
ffi::_ND_INS_TYPE::ND_CAT_PCLMULQDQ => Ok(Category::Pclmulqdq),
ffi::_ND_INS_TYPE::ND_CAT_PCONFIG => Ok(Category::Pconfig),
ffi::_ND_INS_TYPE::ND_CAT_POP => Ok(Category::Pop),
ffi::_ND_INS_TYPE::ND_CAT_PREFETCH => Ok(Category::Prefetch),
ffi::_ND_INS_TYPE::ND_CAT_PTWRITE => Ok(Category::Ptwrite),
ffi::_ND_INS_TYPE::ND_CAT_PUSH => Ok(Category::Push),
ffi::_ND_INS_TYPE::ND_CAT_RDPID => Ok(Category::Rdpid),
ffi::_ND_INS_TYPE::ND_CAT_RDRAND => Ok(Category::Rdrand),
ffi::_ND_INS_TYPE::ND_CAT_RDSEED => Ok(Category::Rdseed),
ffi::_ND_INS_TYPE::ND_CAT_RDWRFSGS => Ok(Category::Rdwrfsgs),
ffi::_ND_INS_TYPE::ND_CAT_RET => Ok(Category::Ret),
ffi::_ND_INS_TYPE::ND_CAT_ROTATE => Ok(Category::Rotate),
ffi::_ND_INS_TYPE::ND_CAT_SCATTER => Ok(Category::Scatter),
ffi::_ND_INS_TYPE::ND_CAT_SEGOP => Ok(Category::Segop),
ffi::_ND_INS_TYPE::ND_CAT_SEMAPHORE => Ok(Category::Semaphore),
ffi::_ND_INS_TYPE::ND_CAT_SGX => Ok(Category::Sgx),
ffi::_ND_INS_TYPE::ND_CAT_SHA => Ok(Category::Sha),
ffi::_ND_INS_TYPE::ND_CAT_SHIFT => Ok(Category::Shift),
ffi::_ND_INS_TYPE::ND_CAT_SMAP => Ok(Category::Smap),
ffi::_ND_INS_TYPE::ND_CAT_SSE => Ok(Category::Sse),
ffi::_ND_INS_TYPE::ND_CAT_SSE2 => Ok(Category::Sse2),
ffi::_ND_INS_TYPE::ND_CAT_STRINGOP => Ok(Category::Stringop),
ffi::_ND_INS_TYPE::ND_CAT_STTNI => Ok(Category::Sttni),
ffi::_ND_INS_TYPE::ND_CAT_SYSCALL => Ok(Category::Syscall),
ffi::_ND_INS_TYPE::ND_CAT_SYSRET => Ok(Category::Sysret),
ffi::_ND_INS_TYPE::ND_CAT_SYSTEM => Ok(Category::System),
ffi::_ND_INS_TYPE::ND_CAT_TDX => Ok(Category::Tdx),
ffi::_ND_INS_TYPE::ND_CAT_UD => Ok(Category::Ud),
ffi::_ND_INS_TYPE::ND_CAT_UINTR => Ok(Category::Uintr),
ffi::_ND_INS_TYPE::ND_CAT_UNCOND_BR => Ok(Category::UncondBr),
ffi::_ND_INS_TYPE::ND_CAT_UNKNOWN => Ok(Category::Unknown),
ffi::_ND_INS_TYPE::ND_CAT_VAES => Ok(Category::Vaes),
ffi::_ND_INS_TYPE::ND_CAT_VFMA => Ok(Category::Vfma),
ffi::_ND_INS_TYPE::ND_CAT_VFMAPS => Ok(Category::Vfmaps),
ffi::_ND_INS_TYPE::ND_CAT_VNNI => Ok(Category::Vnni),
ffi::_ND_INS_TYPE::ND_CAT_VNNIW => Ok(Category::Vnniw),
ffi::_ND_INS_TYPE::ND_CAT_VPCLMULQDQ => Ok(Category::Vpclmulqdq),
ffi::_ND_INS_TYPE::ND_CAT_VPOPCNT => Ok(Category::Vpopcnt),
ffi::_ND_INS_TYPE::ND_CAT_VTX => Ok(Category::Vtx),
ffi::_ND_INS_TYPE::ND_CAT_WAITPKG => Ok(Category::Waitpkg),
ffi::_ND_INS_TYPE::ND_CAT_WBNOINVD => Ok(Category::Wbnoinvd),
ffi::_ND_INS_TYPE::ND_CAT_WIDENOP => Ok(Category::Widenop),
ffi::_ND_INS_TYPE::ND_CAT_WIDE_KL => Ok(Category::WideKl),
ffi::_ND_INS_TYPE::ND_CAT_X87_ALU => Ok(Category::X87Alu),
ffi::_ND_INS_TYPE::ND_CAT_XOP => Ok(Category::Xop),
ffi::_ND_INS_TYPE::ND_CAT_XSAVE => Ok(Category::Xsave),
}
}
}

View File

@ -0,0 +1,262 @@
/*
* Copyright (c) 2021 Bitdefender
* SPDX-License-Identifier: Apache-2.0
*/
//! Instruction sets.
extern crate bddisasm_sys as ffi;
use super::decode_error;
use std::convert::TryFrom;
/// ISA set.
#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
pub enum IsaSet {
I3dnow,
Adx,
Aes,
Amd,
Amxbf16,
Amxint8,
Amxtile,
Avx,
Avx2,
Avx2gather,
Avx5124fmaps,
Avx5124vnniw,
Avx512bf16,
Avx512bitalg,
Avx512bw,
Avx512cd,
Avx512dq,
Avx512er,
Avx512f,
Avx512fp16,
Avx512ifma,
Avx512pf,
Avx512vbmi,
Avx512vbmi2,
Avx512vnni,
Avx512vp2intersect,
Avx512vpopcntdq,
Avxvnni,
Bmi1,
Bmi2,
CetIbt,
CetSs,
Cldemote,
Clfsh,
Clfshopt,
Clwb,
Clzero,
Cmpxchg16b,
Cyrix,
CyrixSmm,
Enqcmd,
F16c,
Fma,
Fma4,
Fred,
Fxsave,
Gfni,
Hreset,
I186,
I286prot,
I286real,
I386,
I486,
I486real,
I64,
I86,
Invlpgb,
Invpcid,
Kl,
Lkgs,
Longmode,
Lwp,
Lzcnt,
Mcommit,
Mmx,
Movbe,
Movdir64b,
Movdiri,
Mpx,
Mwaitt,
Pause,
Pclmulqdq,
Pconfig,
Pentiumreal,
Pku,
Popcnt,
Ppro,
PrefetchNop,
Ptwrite,
Rdpid,
Rdpmc,
Rdpru,
Rdrand,
Rdseed,
Rdtscp,
Rdwrfsgs,
Serialize,
Sgx,
Sha,
Smap,
Smx,
Snp,
Sse,
Sse2,
Sse3,
Sse4,
Sse42,
Sse4a,
Ssse3,
Svm,
Tbm,
Tdx,
Tsx,
Tsxldtrk,
Ud,
Uintr,
Unknown,
Vaes,
Vpclmulqdq,
Vtx,
Waitpkg,
Wbnoinvd,
X87,
Xop,
Xsave,
Xsavec,
Xsaves,
}
#[doc(hidden)]
impl TryFrom<ffi::ND_INS_SET> for IsaSet {
type Error = decode_error::DecodeError;
fn try_from(value: ffi::ND_INS_SET) -> Result<Self, Self::Error> {
match value {
ffi::_ND_INS_SET::ND_SET_INVALID => {
Err(decode_error::DecodeError::UnknownInstruction(value as u32))
}
ffi::_ND_INS_SET::ND_SET_3DNOW => Ok(IsaSet::I3dnow),
ffi::_ND_INS_SET::ND_SET_ADX => Ok(IsaSet::Adx),
ffi::_ND_INS_SET::ND_SET_AES => Ok(IsaSet::Aes),
ffi::_ND_INS_SET::ND_SET_AMD => Ok(IsaSet::Amd),
ffi::_ND_INS_SET::ND_SET_AMXBF16 => Ok(IsaSet::Amxbf16),
ffi::_ND_INS_SET::ND_SET_AMXINT8 => Ok(IsaSet::Amxint8),
ffi::_ND_INS_SET::ND_SET_AMXTILE => Ok(IsaSet::Amxtile),
ffi::_ND_INS_SET::ND_SET_AVX => Ok(IsaSet::Avx),
ffi::_ND_INS_SET::ND_SET_AVX2 => Ok(IsaSet::Avx2),
ffi::_ND_INS_SET::ND_SET_AVX2GATHER => Ok(IsaSet::Avx2gather),
ffi::_ND_INS_SET::ND_SET_AVX5124FMAPS => Ok(IsaSet::Avx5124fmaps),
ffi::_ND_INS_SET::ND_SET_AVX5124VNNIW => Ok(IsaSet::Avx5124vnniw),
ffi::_ND_INS_SET::ND_SET_AVX512BF16 => Ok(IsaSet::Avx512bf16),
ffi::_ND_INS_SET::ND_SET_AVX512BITALG => Ok(IsaSet::Avx512bitalg),
ffi::_ND_INS_SET::ND_SET_AVX512BW => Ok(IsaSet::Avx512bw),
ffi::_ND_INS_SET::ND_SET_AVX512CD => Ok(IsaSet::Avx512cd),
ffi::_ND_INS_SET::ND_SET_AVX512DQ => Ok(IsaSet::Avx512dq),
ffi::_ND_INS_SET::ND_SET_AVX512ER => Ok(IsaSet::Avx512er),
ffi::_ND_INS_SET::ND_SET_AVX512F => Ok(IsaSet::Avx512f),
ffi::_ND_INS_SET::ND_SET_AVX512FP16 => Ok(IsaSet::Avx512fp16),
ffi::_ND_INS_SET::ND_SET_AVX512IFMA => Ok(IsaSet::Avx512ifma),
ffi::_ND_INS_SET::ND_SET_AVX512PF => Ok(IsaSet::Avx512pf),
ffi::_ND_INS_SET::ND_SET_AVX512VBMI => Ok(IsaSet::Avx512vbmi),
ffi::_ND_INS_SET::ND_SET_AVX512VBMI2 => Ok(IsaSet::Avx512vbmi2),
ffi::_ND_INS_SET::ND_SET_AVX512VNNI => Ok(IsaSet::Avx512vnni),
ffi::_ND_INS_SET::ND_SET_AVX512VP2INTERSECT => Ok(IsaSet::Avx512vp2intersect),
ffi::_ND_INS_SET::ND_SET_AVX512VPOPCNTDQ => Ok(IsaSet::Avx512vpopcntdq),
ffi::_ND_INS_SET::ND_SET_AVXVNNI => Ok(IsaSet::Avxvnni),
ffi::_ND_INS_SET::ND_SET_BMI1 => Ok(IsaSet::Bmi1),
ffi::_ND_INS_SET::ND_SET_BMI2 => Ok(IsaSet::Bmi2),
ffi::_ND_INS_SET::ND_SET_CET_IBT => Ok(IsaSet::CetIbt),
ffi::_ND_INS_SET::ND_SET_CET_SS => Ok(IsaSet::CetSs),
ffi::_ND_INS_SET::ND_SET_CLDEMOTE => Ok(IsaSet::Cldemote),
ffi::_ND_INS_SET::ND_SET_CLFSH => Ok(IsaSet::Clfsh),
ffi::_ND_INS_SET::ND_SET_CLFSHOPT => Ok(IsaSet::Clfshopt),
ffi::_ND_INS_SET::ND_SET_CLWB => Ok(IsaSet::Clwb),
ffi::_ND_INS_SET::ND_SET_CLZERO => Ok(IsaSet::Clzero),
ffi::_ND_INS_SET::ND_SET_CMPXCHG16B => Ok(IsaSet::Cmpxchg16b),
ffi::_ND_INS_SET::ND_SET_CYRIX => Ok(IsaSet::Cyrix),
ffi::_ND_INS_SET::ND_SET_CYRIX_SMM => Ok(IsaSet::CyrixSmm),
ffi::_ND_INS_SET::ND_SET_ENQCMD => Ok(IsaSet::Enqcmd),
ffi::_ND_INS_SET::ND_SET_F16C => Ok(IsaSet::F16c),
ffi::_ND_INS_SET::ND_SET_FMA => Ok(IsaSet::Fma),
ffi::_ND_INS_SET::ND_SET_FMA4 => Ok(IsaSet::Fma4),
ffi::_ND_INS_SET::ND_SET_FRED => Ok(IsaSet::Fred),
ffi::_ND_INS_SET::ND_SET_FXSAVE => Ok(IsaSet::Fxsave),
ffi::_ND_INS_SET::ND_SET_GFNI => Ok(IsaSet::Gfni),
ffi::_ND_INS_SET::ND_SET_HRESET => Ok(IsaSet::Hreset),
ffi::_ND_INS_SET::ND_SET_I186 => Ok(IsaSet::I186),
ffi::_ND_INS_SET::ND_SET_I286PROT => Ok(IsaSet::I286prot),
ffi::_ND_INS_SET::ND_SET_I286REAL => Ok(IsaSet::I286real),
ffi::_ND_INS_SET::ND_SET_I386 => Ok(IsaSet::I386),
ffi::_ND_INS_SET::ND_SET_I486 => Ok(IsaSet::I486),
ffi::_ND_INS_SET::ND_SET_I486REAL => Ok(IsaSet::I486real),
ffi::_ND_INS_SET::ND_SET_I64 => Ok(IsaSet::I64),
ffi::_ND_INS_SET::ND_SET_I86 => Ok(IsaSet::I86),
ffi::_ND_INS_SET::ND_SET_INVLPGB => Ok(IsaSet::Invlpgb),
ffi::_ND_INS_SET::ND_SET_INVPCID => Ok(IsaSet::Invpcid),
ffi::_ND_INS_SET::ND_SET_KL => Ok(IsaSet::Kl),
ffi::_ND_INS_SET::ND_SET_LKGS => Ok(IsaSet::Lkgs),
ffi::_ND_INS_SET::ND_SET_LONGMODE => Ok(IsaSet::Longmode),
ffi::_ND_INS_SET::ND_SET_LWP => Ok(IsaSet::Lwp),
ffi::_ND_INS_SET::ND_SET_LZCNT => Ok(IsaSet::Lzcnt),
ffi::_ND_INS_SET::ND_SET_MCOMMIT => Ok(IsaSet::Mcommit),
ffi::_ND_INS_SET::ND_SET_MMX => Ok(IsaSet::Mmx),
ffi::_ND_INS_SET::ND_SET_MOVBE => Ok(IsaSet::Movbe),
ffi::_ND_INS_SET::ND_SET_MOVDIR64B => Ok(IsaSet::Movdir64b),
ffi::_ND_INS_SET::ND_SET_MOVDIRI => Ok(IsaSet::Movdiri),
ffi::_ND_INS_SET::ND_SET_MPX => Ok(IsaSet::Mpx),
ffi::_ND_INS_SET::ND_SET_MWAITT => Ok(IsaSet::Mwaitt),
ffi::_ND_INS_SET::ND_SET_PAUSE => Ok(IsaSet::Pause),
ffi::_ND_INS_SET::ND_SET_PCLMULQDQ => Ok(IsaSet::Pclmulqdq),
ffi::_ND_INS_SET::ND_SET_PCONFIG => Ok(IsaSet::Pconfig),
ffi::_ND_INS_SET::ND_SET_PENTIUMREAL => Ok(IsaSet::Pentiumreal),
ffi::_ND_INS_SET::ND_SET_PKU => Ok(IsaSet::Pku),
ffi::_ND_INS_SET::ND_SET_POPCNT => Ok(IsaSet::Popcnt),
ffi::_ND_INS_SET::ND_SET_PPRO => Ok(IsaSet::Ppro),
ffi::_ND_INS_SET::ND_SET_PREFETCH_NOP => Ok(IsaSet::PrefetchNop),
ffi::_ND_INS_SET::ND_SET_PTWRITE => Ok(IsaSet::Ptwrite),
ffi::_ND_INS_SET::ND_SET_RDPID => Ok(IsaSet::Rdpid),
ffi::_ND_INS_SET::ND_SET_RDPMC => Ok(IsaSet::Rdpmc),
ffi::_ND_INS_SET::ND_SET_RDPRU => Ok(IsaSet::Rdpru),
ffi::_ND_INS_SET::ND_SET_RDRAND => Ok(IsaSet::Rdrand),
ffi::_ND_INS_SET::ND_SET_RDSEED => Ok(IsaSet::Rdseed),
ffi::_ND_INS_SET::ND_SET_RDTSCP => Ok(IsaSet::Rdtscp),
ffi::_ND_INS_SET::ND_SET_RDWRFSGS => Ok(IsaSet::Rdwrfsgs),
ffi::_ND_INS_SET::ND_SET_SERIALIZE => Ok(IsaSet::Serialize),
ffi::_ND_INS_SET::ND_SET_SGX => Ok(IsaSet::Sgx),
ffi::_ND_INS_SET::ND_SET_SHA => Ok(IsaSet::Sha),
ffi::_ND_INS_SET::ND_SET_SMAP => Ok(IsaSet::Smap),
ffi::_ND_INS_SET::ND_SET_SMX => Ok(IsaSet::Smx),
ffi::_ND_INS_SET::ND_SET_SNP => Ok(IsaSet::Snp),
ffi::_ND_INS_SET::ND_SET_SSE => Ok(IsaSet::Sse),
ffi::_ND_INS_SET::ND_SET_SSE2 => Ok(IsaSet::Sse2),
ffi::_ND_INS_SET::ND_SET_SSE3 => Ok(IsaSet::Sse3),
ffi::_ND_INS_SET::ND_SET_SSE4 => Ok(IsaSet::Sse4),
ffi::_ND_INS_SET::ND_SET_SSE42 => Ok(IsaSet::Sse42),
ffi::_ND_INS_SET::ND_SET_SSE4A => Ok(IsaSet::Sse4a),
ffi::_ND_INS_SET::ND_SET_SSSE3 => Ok(IsaSet::Ssse3),
ffi::_ND_INS_SET::ND_SET_SVM => Ok(IsaSet::Svm),
ffi::_ND_INS_SET::ND_SET_TBM => Ok(IsaSet::Tbm),
ffi::_ND_INS_SET::ND_SET_TDX => Ok(IsaSet::Tdx),
ffi::_ND_INS_SET::ND_SET_TSX => Ok(IsaSet::Tsx),
ffi::_ND_INS_SET::ND_SET_TSXLDTRK => Ok(IsaSet::Tsxldtrk),
ffi::_ND_INS_SET::ND_SET_UD => Ok(IsaSet::Ud),
ffi::_ND_INS_SET::ND_SET_UINTR => Ok(IsaSet::Uintr),
ffi::_ND_INS_SET::ND_SET_UNKNOWN => Ok(IsaSet::Unknown),
ffi::_ND_INS_SET::ND_SET_VAES => Ok(IsaSet::Vaes),
ffi::_ND_INS_SET::ND_SET_VPCLMULQDQ => Ok(IsaSet::Vpclmulqdq),
ffi::_ND_INS_SET::ND_SET_VTX => Ok(IsaSet::Vtx),
ffi::_ND_INS_SET::ND_SET_WAITPKG => Ok(IsaSet::Waitpkg),
ffi::_ND_INS_SET::ND_SET_WBNOINVD => Ok(IsaSet::Wbnoinvd),
ffi::_ND_INS_SET::ND_SET_X87 => Ok(IsaSet::X87),
ffi::_ND_INS_SET::ND_SET_XOP => Ok(IsaSet::Xop),
ffi::_ND_INS_SET::ND_SET_XSAVE => Ok(IsaSet::Xsave),
ffi::_ND_INS_SET::ND_SET_XSAVEC => Ok(IsaSet::Xsavec),
ffi::_ND_INS_SET::ND_SET_XSAVES => Ok(IsaSet::Xsaves),
}
}
}

View File

@ -0,0 +1,189 @@
/*
* Copyright (c) 2021 Bitdefender
* SPDX-License-Identifier: Apache-2.0
*/
//! bddisasm x86/x64 instruction decoder
//!
//! This crate contains bindings for the [bddisasm](https://github.com/bitdefender/bddisasm) x86/x64 decoder library.
//! It supports all existing x86 instruction, offering a wide range of information about each one, including:
//!
//! - operands (implicit and explicit)
//! - access mode for each operand
//! - CPUID feature flags
//! - CPU modes in which an instruction is valid
//!
//! # Usage
//!
//! Add `bddisasm` to your `Cargo.toml`:
//!
//! ```toml
//! [dependencies]
//! bddisasm = "0.1.0"
//! ```
//!
//! # Examples
//!
//! ## Decoding one instruction
//!
//! Use [DecodedInstruction::decode](crate::decoded_instruction::DecodedInstruction::decode) to decode an instruction
//! from a chunk of code.
//!
//! ```
//! use bddisasm::decoded_instruction::{DecodedInstruction, DecodeMode, Mnemonic};
//!
//! let code = vec![0x31, 0xc0];
//! match DecodedInstruction::decode(&code, DecodeMode::Bits32) {
//! Ok(ins) => {
//! assert_eq!(ins.mnemonic(), Mnemonic::Xor);
//! println!("{}", ins);
//! },
//! Err(err) => println!("Unable to decode: {}", err),
//! }
//! ```
//!
//! ## Decoding multiple instructions
//!
//! Use [Decoder](crate::decoder::Decoder) to decode multiple instructions from a chunk of code.
//!
//! ```
//! use bddisasm::decoder::{Decoder, DecodeMode};
//!
//! let code = [
//! // ENCLS
//! 0x0f, 0x01, 0xcf,
//! // MOV rax, qword ptr [rbx+rcx*4+0x1234]
//! 0x48, 0x8b, 0x84, 0x8b, 0x34, 0x12, 0x00, 0x00,
//! // Not a valid instruction
//! 0x0f,
//! // WRMSR
//! 0x0f, 0x30,
//! ];
//! let decoder = Decoder::new(&code, DecodeMode::Bits64, 0x1234);
//!
//! for ins in decoder {
//! match ins {
//! Ok(ins) => println!("{}", ins),
//! Err(e) => println!("{}", e),
//! }
//! }
//! ```
//!
//! This will print:
//!
//! ```text
//! ENCLS
//! MOV rax, qword ptr [rbx+rcx*4+0x1234]
//! the provided input buffer is too small
//! WRMSR
//! ```
//!
//! Use [Decoder::decode_next_with_info](crate::decoder::Decoder::decode_next_with_info) to get information about the
//! offset inside the code chunk at which an instruction was decoded from.
//!
//! ```
//! use bddisasm::decoder::{Decoder, DecodeMode};
//!
//! let code = [
//! // ENCLS
//! 0x0f, 0x01, 0xcf,
//! // MOV rax, qword ptr [rbx+rcx*4+0x1234]
//! 0x48, 0x8b, 0x84, 0x8b, 0x34, 0x12, 0x00, 0x00,
//! // Not a valid instruction
//! 0x0f,
//! // WRMSR
//! 0x0f, 0x30,
//! ];
//! let mut decoder = Decoder::new(&code, DecodeMode::Bits64, 0x1234);
//!
//!
//! // Keep decoding until there's nothing left to decode
//! while let Some((result, offset, _)) = decoder.decode_next_with_info() {
//! match result {
//! Ok(ins) => println!("{:#x} {}", offset, ins),
//! Err(e) => println!("Error: `{}` at offset {:#x}", e, offset),
//! }
//! }
//! ```
//!
//! This will print:
//!
//! ```text
//! 0x0 ENCLS
//! 0x3 MOV rax, qword ptr [rbx+rcx*4+0x1234]
//! Error `the provided input buffer is too small` at offset 0xb
//! 0xc WRMSR
//! ```
//!
//! ## Working with instruction operands
//!
//! Instruction operands can be analyzed using the [operand](crate::operand) module. Rich informaion is offered for
//! each type of operand. Bellow is a minimal example that looks at a memory operand.
//!
//! ```
//! # use bddisasm::decode_error::DecodeError;
//! # fn test() -> Result<(), DecodeError> {
//! use bddisasm::decoded_instruction::{DecodedInstruction, DecodeMode};
//! use bddisasm::operand::OpInfo;
//!
//! // ` MOV rax, qword ptr [rcx+r15*2]`
//! let code = b"\x4a\x8b\x04\x79";
//! let ins = DecodedInstruction::decode(code, DecodeMode::Bits64).unwrap();
//! // Get the operands
//! let operands = ins.operands();
//! // Get the second operand which is the source (`[rcx+r15*2]`)
//! let src = operands[1];
//!
//! println!("Source operand type: {}", src.info);
//! match src.info {
//! OpInfo::Mem(mem) => {
//! if let Some(base) = mem.base {
//! println!("Base register: {}", base);
//! } else {
//! println!("No base register");
//! }
//!
//! if let Some(index) = mem.index {
//! println!("Index register: {}", index);
//! } else {
//! println!("No index register");
//! }
//!
//! if let Some(scale) = mem.scale {
//! println!("Scale: {}", scale);
//! } else {
//! println!("No scale");
//! }
//!
//! if let Some(displacement) = mem.disp {
//! println!("Displacement: {}", displacement);
//! } else {
//! println!("No displacement");
//! }
//! },
//! _ => unreachable!(),
//! }
//! # Ok(())
//! # }
//! ```
//!
//! Will print:
//!
//! ```text
//! Source operand type: memory
//! Base register: 1
//! Index register: 15
//! Scale: 2
//! No displacement
//! ```
pub mod cpu_modes;
pub mod cpuid;
pub mod decode_error;
pub mod decoded_instruction;
pub mod decoder;
pub mod fpu_flags;
pub mod instruction_category;
pub mod isa_set;
pub mod mnemonic;
pub mod operand;
pub mod rflags;
pub mod tuple;

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,767 @@
/*
* Copyright (c) 2021 Bitdefender
* SPDX-License-Identifier: Apache-2.0
*/
//! Operand types and details.
extern crate bddisasm_sys as ffi;
use std::fmt;
/// Describes an address operand.
#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
pub struct OpAddr {
/// The ID of the base segment selector.
///
/// `ES` = 0, `CS` = 1, `SS` = 2, `DS` = 3, `FS` = 4, `GS` = 5.
pub base_seg: u16,
/// Offset inside the segment.
pub offset: u64,
}
#[doc(hidden)]
impl From<ffi::ND_OPDESC_ADDRESS> for OpAddr {
fn from(op: ffi::ND_OPDESC_ADDRESS) -> OpAddr {
OpAddr {
base_seg: op.BaseSeg,
offset: op.Offset,
}
}
}
#[doc(hidden)]
impl OpAddr {
pub(crate) fn new(base_seg: u16, offset: u64) -> Self {
Self { base_seg, offset }
}
}
/// The type of a register.
#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
pub enum OpRegType {
/// The register is a 8/16/32/64 bit general purpose register.
Gpr,
/// The register is a segment register.
Seg,
/// The register is a 80-bit FPU register.
Fpu,
/// The register is a 64-bit MMX register.
Mmx,
/// The register is a 128/256/512 bit SSE vector register.
Sse,
/// The register is a control register.
Cr,
/// The register is a debug register.
Dr,
/// The register is a test register.
Tr,
/// The register is a bound register.
Bnd,
/// The register is a mask register.
Msk,
/// The register is a tile register.
Tile,
/// The register is a model specific register.
Msr,
/// The register is a extended control register.
Xcr,
/// The register is a system register.
Sys,
/// The register is a x87 status/control register.
X87,
/// The register is the MXCSR register.
Mxcsr,
/// The register is the PKRU register.
Pkru,
/// The register is the SSP (Shadow Stack Pointer) register.
Ssp,
/// The register is the FLAGS register.
Flg,
/// The register is the instruction pointer register.
Rip,
/// The register is the User Interrupt Flag.
Uif,
}
#[doc(hidden)]
impl From<ffi::ND_OPDESC_REGISTER> for OpRegType {
fn from(op: ffi::ND_OPDESC_REGISTER) -> OpRegType {
match op.Type {
ffi::_ND_REG_TYPE::ND_REG_NOT_PRESENT => panic!("Unexpected ND_REG_NOT_PRESENT"),
ffi::_ND_REG_TYPE::ND_REG_GPR => OpRegType::Gpr,
ffi::_ND_REG_TYPE::ND_REG_SEG => OpRegType::Seg,
ffi::_ND_REG_TYPE::ND_REG_FPU => OpRegType::Fpu,
ffi::_ND_REG_TYPE::ND_REG_MMX => OpRegType::Mmx,
ffi::_ND_REG_TYPE::ND_REG_SSE => OpRegType::Sse,
ffi::_ND_REG_TYPE::ND_REG_CR => OpRegType::Cr,
ffi::_ND_REG_TYPE::ND_REG_DR => OpRegType::Dr,
ffi::_ND_REG_TYPE::ND_REG_TR => OpRegType::Tr,
ffi::_ND_REG_TYPE::ND_REG_BND => OpRegType::Bnd,
ffi::_ND_REG_TYPE::ND_REG_MSK => OpRegType::Msk,
ffi::_ND_REG_TYPE::ND_REG_TILE => OpRegType::Tile,
ffi::_ND_REG_TYPE::ND_REG_MSR => OpRegType::Msr,
ffi::_ND_REG_TYPE::ND_REG_XCR => OpRegType::Xcr,
ffi::_ND_REG_TYPE::ND_REG_SYS => OpRegType::Sys,
ffi::_ND_REG_TYPE::ND_REG_X87 => OpRegType::X87,
ffi::_ND_REG_TYPE::ND_REG_MXCSR => OpRegType::Mxcsr,
ffi::_ND_REG_TYPE::ND_REG_PKRU => OpRegType::Pkru,
ffi::_ND_REG_TYPE::ND_REG_SSP => OpRegType::Ssp,
ffi::_ND_REG_TYPE::ND_REG_FLG => OpRegType::Flg,
ffi::_ND_REG_TYPE::ND_REG_RIP => OpRegType::Rip,
ffi::_ND_REG_TYPE::ND_REG_UIF => OpRegType::Uif,
}
}
}
impl fmt::Display for OpRegType {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Self::Gpr => write!(f, "GPR"),
Self::Seg => write!(f, "Segment"),
Self::Fpu => write!(f, "FPU"),
Self::Mmx => write!(f, "MMX"),
Self::Sse => write!(f, "SSE"),
Self::Cr => write!(f, "CR"),
Self::Dr => write!(f, "DR"),
Self::Tr => write!(f, "TR"),
Self::Bnd => write!(f, "BND"),
Self::Msk => write!(f, "MSK"),
Self::Tile => write!(f, "Tile"),
Self::Msr => write!(f, "MSR"),
Self::Xcr => write!(f, "XCR"),
Self::Sys => write!(f, "Sys"),
Self::X87 => write!(f, "X87"),
Self::Mxcsr => write!(f, "MXCSR"),
Self::Pkru => write!(f, "PKRU"),
Self::Ssp => write!(f, "SSP"),
Self::Flg => write!(f, "Flg"),
Self::Rip => write!(f, "RIP"),
Self::Uif => write!(f, "UIF"),
}
}
}
/// Describes a register operand.
#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
pub struct OpReg {
/// The type of the register.
pub kind: OpRegType,
/// Indicates the register size, in bytes.
///
/// This may not be equal to the [`Operand::size`] field, as a smaller amount of data may be processed from a
/// register (especially if we have a SSE register or a mask register).
pub size: u32,
// TODO: create an enum for each register, instead of using an ID.
/// The register index/ID.
///
/// Registers are numbered as they are in the Intel documentation. Examples:
///
/// - `AL` = `AX` = `EAX` = `RAX` = 0
/// - `R15B` = `R15W` = `R15D` = `R15` = 15
/// - `XMM0` = 0
/// - `ZMM31` = 31
/// - `ES` = 0
/// - `CS` = 1
///
/// # Remarks
///
/// If [kind](OpReg::kind) is [OpRegType::Gpr](OpRegType::Gpr), the high and low part of 16-bit registers will have
/// the same index (for example, `AH` and `AL`). To differentiate between them use [is_high8](OpReg::is_high8).
pub index: usize,
/// The number of registers accessed, starting with [id](OpReg::index).
pub count: u32,
/// `true` if this is the high part of a 16-bit GPR: `AH`, `CH`, `DH`, or `BH`.
pub is_high8: bool,
/// `true` if this is a block register addressing.
pub is_block: bool,
}
#[doc(hidden)]
impl From<ffi::ND_OPDESC_REGISTER> for OpReg {
fn from(op: ffi::ND_OPDESC_REGISTER) -> OpReg {
let kind = OpRegType::from(op);
let is_high8 = op.IsHigh8();
let index = match kind {
OpRegType::Gpr => {
if is_high8 {
// See `ShemuGetGprValue` in `bdshemu.c`.
op.Reg - 4
} else {
op.Reg
}
}
_ => op.Reg,
} as usize;
OpReg {
kind,
size: op.Size,
index,
count: op.Count,
is_high8,
is_block: op.IsBlock(),
}
}
}
/// Holds extra information for instruction that use VSIB addressing.
#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
pub struct Vsib {
/// VSIB element size.
pub vsib_element_size: u8,
/// Number of elements scattered/gathered/prefetched.
pub vsib_element_count: u8,
}
/// Shadow stack access types.
#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
pub enum ShadowStackAccess {
None,
/// Explicit memory operand accessed as shadow stack.
Explicit,
/// Shadow Stack Pointer (`SSP`) used as base for addressing using conventional load/store.
SspLdSt,
/// Shadow Stack Pointer (`SSP`) used as base for addressing using `push`/`pop`.
SspPushPop,
/// Privilege 0 `SSP` (`IA32_PL0_SSP`) used (`SETSSBSY`).
Pl0Ssp,
}
#[doc(hidden)]
impl From<ffi::ND_SHSTK_ACCESS> for ShadowStackAccess {
fn from(acc: ffi::ND_SHSTK_ACCESS) -> ShadowStackAccess {
match acc {
ffi::_ND_SHSTK_ACCESS::ND_SHSTK_NONE => ShadowStackAccess::None,
ffi::_ND_SHSTK_ACCESS::ND_SHSTK_EXPLICIT => ShadowStackAccess::Explicit,
ffi::_ND_SHSTK_ACCESS::ND_SHSTK_SSP_LD_ST => ShadowStackAccess::SspLdSt,
ffi::_ND_SHSTK_ACCESS::ND_SHSTK_SSP_PUSH_POP => ShadowStackAccess::SspPushPop,
ffi::_ND_SHSTK_ACCESS::ND_SHSTK_PL0_SSP => ShadowStackAccess::Pl0Ssp,
// NOTE: when updating this take care to also update the `From<u8>` implementation!
// TODO: any way of keeping these in sync automagically?
}
}
}
impl From<u8> for ShadowStackAccess {
fn from(acc: u8) -> ShadowStackAccess {
if acc == ffi::_ND_SHSTK_ACCESS::ND_SHSTK_NONE as u8 {
ShadowStackAccess::None
} else if acc == ffi::_ND_SHSTK_ACCESS::ND_SHSTK_EXPLICIT as u8 {
ShadowStackAccess::Explicit
} else if acc == ffi::_ND_SHSTK_ACCESS::ND_SHSTK_SSP_LD_ST as u8 {
ShadowStackAccess::SspLdSt
} else if acc == ffi::_ND_SHSTK_ACCESS::ND_SHSTK_SSP_PUSH_POP as u8 {
ShadowStackAccess::SspPushPop
} else if acc == ffi::_ND_SHSTK_ACCESS::ND_SHSTK_PL0_SSP as u8 {
ShadowStackAccess::Pl0Ssp
} else {
panic!("Unexpected shadow stack access type: {}", acc)
}
}
}
/// Describes a memory operand.
#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
pub struct OpMem {
/// `true` if the memory operand is a broadcast operand.
pub has_broadcast: bool,
/// `true` if this is a rip-relative addressing. Base, Index, Scale are all ignored.
pub is_rip_rel: bool,
/// `true` if this is a stack op.
///
/// # Note
///
/// Explicit stack accesses are not included (eg: `mov eax, [rsp]`).
pub is_stack: bool,
/// `true` for `[RSI]` and `[RDI]` operands inside string operations.
pub is_string: bool,
/// `true` if direct addressing (`MOV [...], EAX, 0xA3`).
pub is_direct: bool,
/// `true` if this is a bit base. Used for `BT*` instructions.
///
/// The bitbase stored in the second operand must be added to the linear address.
pub is_bitbase: bool,
/// `true` if the memory operand is address generation and no memory access is made.
pub is_ag: bool,
/// `true` MIB addressing is used (MPX instructions).
pub is_mib: bool,
/// `true` the addressing uses sibmem (AMX instructions).
pub is_sib_mem: bool,
/// Base segment used to address the memory.
pub seg: Option<u8>,
/// Base register.
pub base: Option<u8>,
/// Base register size, in bytes. Max 8 bytes.
pub base_size: Option<u32>,
/// Index register. Can be a vector reg (ZMM0-ZMM31).
pub index: Option<u8>,
/// Index register size, in bytes. Max 8 bytes.
pub index_size: Option<u32>,
/// The `index` register selects a vector register.
pub vsib: Option<Vsib>,
/// Scale: 1, 2, 4 or 8. Always present if `index` is present.
pub scale: Option<u8>,
/// Sign extended displacement.
pub disp: Option<u64>,
/// Displacement size. Max 4 bytes.
pub disp_size: Option<u8>,
/// Compressed displacement size - 1, 2, 4, 8, 16, 32, 64.
pub comp_disp_size: Option<u8>,
/// Shadow stack access type.
///
/// Will be `None` if the shadow stack is not accessed.
pub shadow_stack_access: Option<ShadowStackAccess>,
}
#[doc(hidden)]
impl From<ffi::ND_OPDESC_MEMORY> for OpMem {
fn from(op: ffi::ND_OPDESC_MEMORY) -> OpMem {
let seg = if op.HasSeg() { Some(op.Seg) } else { None };
let (base, base_size) = if op.HasBase() {
(Some(op.Base), Some(op.BaseSize))
} else {
(None, None)
};
let (index, index_size, scale) = if op.HasIndex() {
(Some(op.Index), Some(op.IndexSize), Some(op.Scale))
} else {
(None, None, None)
};
let (disp, disp_size) = if op.HasDisp() {
(Some(op.Disp), Some(op.DispSize))
} else {
(None, None)
};
let comp_disp_size = if op.HasCompDisp() {
Some(op.CompDispSize)
} else {
None
};
let (vsib, index_size) = if op.IsVsib() {
(
Some(Vsib {
vsib_element_size: op.Vsib.ElemSize,
vsib_element_count: op.Vsib.ElemCount,
}),
Some(op.Vsib.IndexSize as u32),
)
} else {
(None, index_size)
};
let shadow_stack_access = if op.IsShadowStack() {
Some(ShadowStackAccess::from(op.ShStkType))
} else {
None
};
OpMem {
has_broadcast: op.HasBroadcast(),
is_rip_rel: op.IsRipRel(),
is_stack: op.IsStack(),
is_string: op.IsString(),
is_direct: op.IsDirect(),
is_bitbase: op.IsBitbase(),
is_ag: op.IsAG(),
is_mib: op.IsMib(),
is_sib_mem: op.IsSibMem(),
seg,
base,
base_size,
index,
index_size,
vsib,
scale,
disp,
disp_size,
comp_disp_size,
shadow_stack_access,
}
}
}
/// Extended operand information, based on the actual type of the operand.
#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
pub enum OpInfo {
None,
/// The operand is a register.
Reg(OpReg),
/// The operand is located in memory.
Mem(OpMem),
/// The operand is an immediate.
Imm(u64),
/// The operand is a relative offset.
Offs(u64),
/// The operand is an absolute address, in the form `seg:offset`.
Addr(OpAddr),
/// The operand is an implicit constant.
Const(u64),
/// An entire bank/set of registers are being accessed. Used in `PUSHA`/`POPA`/`XSAVE`/`LOADALL`.
Bank,
}
#[doc(hidden)]
impl From<ffi::ND_OPERAND> for OpInfo {
fn from(op: ffi::ND_OPERAND) -> OpInfo {
match op.Type {
ffi::_ND_OPERAND_TYPE::ND_OP_NOT_PRESENT => OpInfo::None,
ffi::_ND_OPERAND_TYPE::ND_OP_REG => {
OpInfo::Reg(OpReg::from(unsafe { op.Info.Register }))
}
ffi::_ND_OPERAND_TYPE::ND_OP_MEM => OpInfo::Mem(OpMem::from(unsafe { op.Info.Memory })),
ffi::_ND_OPERAND_TYPE::ND_OP_IMM => OpInfo::Imm(unsafe { op.Info.Immediate }.Imm),
ffi::_ND_OPERAND_TYPE::ND_OP_OFFS => {
OpInfo::Offs(unsafe { op.Info.RelativeOffset }.Rel)
}
ffi::_ND_OPERAND_TYPE::ND_OP_ADDR => {
OpInfo::Addr(OpAddr::from(unsafe { op.Info.Address }))
}
ffi::_ND_OPERAND_TYPE::ND_OP_CONST => OpInfo::Const(unsafe { op.Info.Constant }.Const),
ffi::_ND_OPERAND_TYPE::ND_OP_BANK => OpInfo::Bank,
}
}
}
impl fmt::Display for OpInfo {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Self::None => write!(f, "None"),
Self::Reg(_) => write!(f, "register"),
Self::Mem(_) => write!(f, "memory"),
Self::Imm(_) => write!(f, "immediate"),
Self::Offs(_) => write!(f, "relative offset"),
Self::Addr(_) => write!(f, "absolute address"),
Self::Const(_) => write!(f, "implicit constant"),
Self::Bank => write!(f, "register bank"),
}
}
}
/// Operand size.
#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
pub enum OpSize {
/// The inner value contains the operand size in bytes.
Bytes(usize),
/// The size of a cache line.
CacheLine,
/// Unknown size.
Unknown,
}
impl fmt::Display for OpSize {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Self::Bytes(size) => write!(f, "{} bytes", size),
Self::CacheLine => write!(f, "cache line"),
Self::Unknown => write!(f, "unknown"),
}
}
}
#[doc(hidden)]
impl From<ffi::ND_OPERAND_SIZE> for OpSize {
fn from(sz: ffi::ND_OPERAND_SIZE) -> OpSize {
match sz {
ffi::ND_SIZE_8BIT => OpSize::Bytes(1),
ffi::ND_SIZE_16BIT => OpSize::Bytes(2),
ffi::ND_SIZE_32BIT => OpSize::Bytes(4),
ffi::ND_SIZE_48BIT => OpSize::Bytes(6),
ffi::ND_SIZE_64BIT => OpSize::Bytes(8),
ffi::ND_SIZE_80BIT => OpSize::Bytes(10),
ffi::ND_SIZE_112BIT => OpSize::Bytes(14),
ffi::ND_SIZE_128BIT => OpSize::Bytes(16),
ffi::ND_SIZE_224BIT => OpSize::Bytes(28),
ffi::ND_SIZE_256BIT => OpSize::Bytes(32),
ffi::ND_SIZE_384BIT => OpSize::Bytes(48),
ffi::ND_SIZE_512BIT => OpSize::Bytes(64),
ffi::ND_SIZE_752BIT => OpSize::Bytes(94),
ffi::ND_SIZE_864BIT => OpSize::Bytes(108),
ffi::ND_SIZE_4096BIT => OpSize::Bytes(512),
ffi::ND_SIZE_1KB => OpSize::Bytes(1024),
ffi::ND_SIZE_CACHE_LINE => OpSize::CacheLine,
ffi::ND_SIZE_UNKNOWN => OpSize::Unknown,
_ => panic!("Unespected operand size: {}", sz),
}
}
}
/// Operand access mode.
#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
pub struct OpAccess {
/// The operand is read.
pub read: bool,
/// The operand is written.
pub write: bool,
/// The operand is read only under some conditions.
pub cond_read: bool,
/// The operand is written only under some conditions.
pub cond_write: bool,
/// The operand is prefetched.
pub prefetch: bool,
}
#[doc(hidden)]
impl From<ffi::ND_OPERAND_ACCESS> for OpAccess {
fn from(acc: ffi::ND_OPERAND_ACCESS) -> OpAccess {
let acc = unsafe { acc.__bindgen_anon_1 };
OpAccess {
read: acc.Read() != 0,
write: acc.Write() != 0,
cond_read: acc.CondRead() != 0,
cond_write: acc.CondWrite() != 0,
prefetch: acc.Prefetch() != 0,
}
}
}
/// Broadcast specifier.
#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
pub struct Broadcast {
/// Number of times to broadcast the element.
pub count: u8,
/// Size of one element.
pub size: u8,
}
/// Decorator information.
#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
pub struct Decorator {
/// If mask is present, holds the ID of the mask register (`K0` - `K7`) used.
pub mask_register: Option<u8>,
/// `true` if zeroing will be made, `false` if merging will be made.
pub has_zero: bool,
/// If broadcasting is being made, holds the broadcast specifier.
pub broadcast: Option<Broadcast>,
}
#[doc(hidden)]
impl From<ffi::ND_OPERAND_DECORATOR> for Decorator {
fn from(decorator: ffi::ND_OPERAND_DECORATOR) -> Decorator {
let mask_register = if decorator.HasMask() {
Some(decorator.Mask.Msk)
} else {
None
};
let broadcast = if decorator.HasBroadcast() {
Some(Broadcast {
count: decorator.Broadcast.Count,
size: decorator.Broadcast.Size,
})
} else {
None
};
Decorator {
mask_register,
has_zero: decorator.HasZero(),
broadcast,
}
}
}
/// Describes an instruction operand.
///
/// Each operand type encodes different information. See [`OpInfo`] for details.
///
/// # Examples
///
/// ```
/// # use std::error::Error;
/// #
/// # fn main() -> Result<(), Box<dyn Error>> {
/// use bddisasm::decoder::{DecodedInstruction, DecodeMode};
/// use bddisasm::operand::*;
///
/// // `MOV ah, byte ptr [rcx+rdx*2+0x8]`
/// let code = vec![0x8a, 0x64, 0x51, 0x08];
/// let ins = DecodedInstruction::decode(&code, DecodeMode::Bits64)?;
///
/// let operands = ins.operands();
/// let dst = operands[0];
/// let src = operands[1];
///
/// // Get the size of each operand
/// println!("Destination size: {}", dst.size);
/// println!("Source size: {}", src.size);
///
/// // Get the type of the destination operand
/// match dst.info {
/// OpInfo::Reg(reg) => {
/// println!("Register kind: {} Size: {} Index: {}", reg.kind, reg.size, reg.index)
/// },
/// // In this case we know that the destination operand is a register
/// _ => println!("Unexpected operand info type"),
/// }
///
/// # Ok(())
/// # }
/// ```
#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
pub struct Operand {
/// Extended operand information.
pub info: OpInfo,
// Operand size in bytes.
//
// This should be used when operating with the operand. It includes sign-extension or zero-extensions.
//
// # Note
///
/// It indicates the actual amount of data used for processing. If the operand is a [register](OpInfo::Reg), it
/// MAY NOT indicate the register size. Use the [info](Operand::info) field to get the actual register size in these
/// cases.
pub size: OpSize,
/// Raw size inside the instruction.
///
/// This will usually be identical to [size](Operand::size), however, some instructions force the actual size of
/// their operands to 64 bit (`PUSH`/`POP` or branches are good examples).
///
/// Although the raw size of the relative offset or the immediate will be [raw_size](Operand::raw_size), internally,
/// the CPU will use [size](Operand::size) (usually sign-extended).
pub raw_size: OpSize,
/// Access mode.
pub access: OpAccess,
/// `true` if the operand is default. This also applies to implicit operands.
pub is_default: bool,
/// Decorator information.
pub decorator: Decorator,
}
#[doc(hidden)]
impl From<ffi::ND_OPERAND> for Operand {
fn from(op: ffi::ND_OPERAND) -> Operand {
Operand {
info: OpInfo::from(op),
size: OpSize::from(op.Size),
raw_size: OpSize::from(op.RawSize),
access: OpAccess::from(op.Access),
is_default: unsafe { op.Flags.__bindgen_anon_1 }.IsDefault() != 0,
decorator: Decorator::from(op.Decorator),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::decoded_instruction::{DecodeMode, DecodedInstruction};
#[test]
fn ops() {
let code = vec![0x8a, 0x64, 0x51, 0x08];
let ins = DecodedInstruction::decode(&code, DecodeMode::Bits64).unwrap();
let operands = ins.operands();
assert_eq!(operands.len(), 2);
let dest = operands[0];
assert_eq!(dest.size, OpSize::Bytes(1));
assert_eq!(dest.raw_size, OpSize::Bytes(1));
assert_eq!(dest.is_default, false);
assert!(dest.access.write);
if let OpInfo::Reg(dst_reg) = dest.info {
assert_eq!(dst_reg.kind, OpRegType::Gpr);
assert_eq!(dst_reg.size, 1);
assert_eq!(dst_reg.index, 0);
assert_eq!(dst_reg.is_high8, true);
} else {
unreachable!();
}
let src = operands[1];
assert_eq!(src.size, OpSize::Bytes(1));
assert_eq!(src.raw_size, OpSize::Bytes(1));
assert_eq!(src.is_default, false);
assert!(src.access.read);
if let OpInfo::Mem(src_mem) = src.info {
assert_eq!(src_mem.seg, Some(3));
assert_eq!(src_mem.base, Some(1));
assert_eq!(src_mem.base_size, Some(8));
assert_eq!(src_mem.index, Some(2));
assert_eq!(src_mem.index_size, Some(8));
assert_eq!(src_mem.scale, Some(2));
assert_eq!(src_mem.disp, Some(8));
assert_eq!(src_mem.disp_size, Some(1));
} else {
unreachable!();
}
}
}

View File

@ -0,0 +1,46 @@
/*
* Copyright (c) 2021 Bitdefender
* SPDX-License-Identifier: Apache-2.0
*/
//! Describes the bits in the FLAGS register.
extern crate bddisasm_sys as ffi;
/// Carry flag.
pub const CF: u32 = ffi::NDR_RFLAG_CF;
/// Parity flag.
pub const PF: u32 = ffi::NDR_RFLAG_PF;
/// Auxiliary flag.
pub const AF: u32 = ffi::NDR_RFLAG_AF;
/// Zero flag.
pub const ZF: u32 = ffi::NDR_RFLAG_ZF;
/// Sign flag.
pub const SF: u32 = ffi::NDR_RFLAG_SF;
/// Trap flag.
pub const TF: u32 = ffi::NDR_RFLAG_TF;
/// Interrupt flag.
pub const IF: u32 = ffi::NDR_RFLAG_IF;
/// Direction flag.
pub const DF: u32 = ffi::NDR_RFLAG_DF;
/// Overflow flag.
pub const OF: u32 = ffi::NDR_RFLAG_OF;
/// I/O privilege level flag.
pub const IOPL: u32 = ffi::NDR_RFLAG_IOPL;
/// Nested task flag.
pub const NT: u32 = ffi::NDR_RFLAG_NT;
/// Resume flag.
pub const RF: u32 = ffi::NDR_RFLAG_RF;
/// Virtual mode flag.
pub const VM: u32 = ffi::NDR_RFLAG_VM;
/// Alignment check flag.
pub const AC: u32 = ffi::NDR_RFLAG_AC;
/// Virtual interrupts flag.
pub const VIF: u32 = ffi::NDR_RFLAG_VIF;
/// Virtual interrupt pending flag.
pub const VIP: u32 = ffi::NDR_RFLAG_VIP;
/// CPUID identification flag.
pub const ID: u32 = ffi::NDR_RFLAG_ID;
pub(crate) fn flags_raw(flags: ffi::ND_RFLAGS) -> u32 {
unsafe { flags.Raw }
}

View File

@ -0,0 +1,122 @@
/*
* Copyright (c) 2021 Bitdefender
* SPDX-License-Identifier: Apache-2.0
*/
//! Instruction tuple type.
extern crate bddisasm_sys as ffi;
/// Instruction tuple type.
///
/// Used to determine compressed displacement size for `disp8 EVEX` instructions. Note that most of the `EVEX` encoded
/// instructions use the compressed displacement addressing scheme.
#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
pub enum Tuple {
None,
/// Full Vector.
Fv,
/// Half Vector.
Hv,
/// Quarter Vector.
Qv,
/// Tuple1 scalar, size 8 bit.
T1S8,
/// Tuple1 scalar, size 16 bit.
T1S16,
/// Tuple1 scalar, size 32/64 bit.
T1S,
/// Tuple1 float, size 32/64 bit.
T1F,
/// Tuple2, 64/128 bit.
T2,
/// Tuple4, 128/256 bit.
T4,
/// Tuple8, 256 bit.
T8,
/// Full Vector Memory.
Fvm,
/// Half Vector Memory.
Hvm,
/// Quarter Vector Memory.
Qvm,
/// Oct Vector Memory.
OVm,
/// M128, 128 bit.
M128,
/// DUP (VMOVDDUP).
Dup,
/// 4 x 32 bit Memory Elements are referenced.
T14X,
}
#[doc(hidden)]
impl From<ffi::ND_TUPLE> for Tuple {
fn from(value: ffi::ND_TUPLE) -> Tuple {
match value {
ffi::_ND_TUPLE::ND_TUPLE_None => Tuple::None,
ffi::_ND_TUPLE::ND_TUPLE_FV => Tuple::Fv,
ffi::_ND_TUPLE::ND_TUPLE_HV => Tuple::Hv,
ffi::_ND_TUPLE::ND_TUPLE_QV => Tuple::Qv,
ffi::_ND_TUPLE::ND_TUPLE_T1S8 => Tuple::T1S8,
ffi::_ND_TUPLE::ND_TUPLE_T1S16 => Tuple::T1S16,
ffi::_ND_TUPLE::ND_TUPLE_T1S => Tuple::T1S,
ffi::_ND_TUPLE::ND_TUPLE_T1F => Tuple::T1F,
ffi::_ND_TUPLE::ND_TUPLE_T2 => Tuple::T2,
ffi::_ND_TUPLE::ND_TUPLE_T4 => Tuple::T4,
ffi::_ND_TUPLE::ND_TUPLE_T8 => Tuple::T8,
ffi::_ND_TUPLE::ND_TUPLE_FVM => Tuple::Fvm,
ffi::_ND_TUPLE::ND_TUPLE_HVM => Tuple::Hvm,
ffi::_ND_TUPLE::ND_TUPLE_QVM => Tuple::Qvm,
ffi::_ND_TUPLE::ND_TUPLE_OVM => Tuple::OVm,
ffi::_ND_TUPLE::ND_TUPLE_M128 => Tuple::M128,
ffi::_ND_TUPLE::ND_TUPLE_DUP => Tuple::Dup,
ffi::_ND_TUPLE::ND_TUPLE_T1_4X => Tuple::T14X,
// NOTE: when updating this take care to also update the `From<u32>` implementation!
// TODO: any way of keeping these in sync automagically?
}
}
}
impl From<u32> for Tuple {
fn from(value: u32) -> Tuple {
if value == ffi::_ND_TUPLE::ND_TUPLE_None as u32 {
Tuple::None
} else if value == ffi::_ND_TUPLE::ND_TUPLE_FV as u32 {
Tuple::Fv
} else if value == ffi::_ND_TUPLE::ND_TUPLE_HV as u32 {
Tuple::Hv
} else if value == ffi::_ND_TUPLE::ND_TUPLE_QV as u32 {
Tuple::Qv
} else if value == ffi::_ND_TUPLE::ND_TUPLE_T1S8 as u32 {
Tuple::T1S8
} else if value == ffi::_ND_TUPLE::ND_TUPLE_T1S16 as u32 {
Tuple::T1S16
} else if value == ffi::_ND_TUPLE::ND_TUPLE_T1S as u32 {
Tuple::T1S
} else if value == ffi::_ND_TUPLE::ND_TUPLE_T1F as u32 {
Tuple::T1F
} else if value == ffi::_ND_TUPLE::ND_TUPLE_T2 as u32 {
Tuple::T2
} else if value == ffi::_ND_TUPLE::ND_TUPLE_T4 as u32 {
Tuple::T4
} else if value == ffi::_ND_TUPLE::ND_TUPLE_T8 as u32 {
Tuple::T8
} else if value == ffi::_ND_TUPLE::ND_TUPLE_FVM as u32 {
Tuple::Fvm
} else if value == ffi::_ND_TUPLE::ND_TUPLE_HVM as u32 {
Tuple::Hvm
} else if value == ffi::_ND_TUPLE::ND_TUPLE_QVM as u32 {
Tuple::Qvm
} else if value == ffi::_ND_TUPLE::ND_TUPLE_OVM as u32 {
Tuple::OVm
} else if value == ffi::_ND_TUPLE::ND_TUPLE_M128 as u32 {
Tuple::M128
} else if value == ffi::_ND_TUPLE::ND_TUPLE_DUP as u32 {
Tuple::Dup
} else if value == ffi::_ND_TUPLE::ND_TUPLE_T1_4X as u32 {
Tuple::T14X
} else {
panic!("Unknown tuple: {}", value)
}
}
}