From 70db095765ab2066dd88dfb7bbcc42259ed167c5 Mon Sep 17 00:00:00 2001 From: "BITDEFENDER\\vlutas" Date: Wed, 5 Jan 2022 14:17:13 +0200 Subject: [PATCH] Updates Rust binding to the latest version. Fixed build in disasmtool_lix. --- bindings/rsbddisasm/bddisasm-sys/CHANGELOG.md | 7 + bindings/rsbddisasm/bddisasm-sys/Cargo.toml | 5 +- bindings/rsbddisasm/bddisasm-sys/README.md | 6 +- bindings/rsbddisasm/bddisasm-sys/build.rs | 3 + bindings/rsbddisasm/bddisasm-sys/src/lib.rs | 1 + bindings/rsbddisasm/bddisasm/CHANGELOG.md | 17 + bindings/rsbddisasm/bddisasm/Cargo.toml | 12 +- bindings/rsbddisasm/bddisasm/README.md | 86 +- .../rsbddisasm/bddisasm/examples/emulator.rs | 159 +++ bindings/rsbddisasm/bddisasm/src/cpu_modes.rs | 21 +- bindings/rsbddisasm/bddisasm/src/cpuid.rs | 8 +- .../rsbddisasm/bddisasm/src/decode_error.rs | 49 +- .../bddisasm/src/decoded_instruction.rs | 828 ++++++++------- bindings/rsbddisasm/bddisasm/src/decoder.rs | 96 +- bindings/rsbddisasm/bddisasm/src/fpu_flags.rs | 35 +- .../bddisasm/src/instruction_category.rs | 12 +- bindings/rsbddisasm/bddisasm/src/isa_set.rs | 12 +- bindings/rsbddisasm/bddisasm/src/lib.rs | 52 +- bindings/rsbddisasm/bddisasm/src/mnemonic.rs | 12 +- bindings/rsbddisasm/bddisasm/src/operand.rs | 959 ++++++++++++++---- bindings/rsbddisasm/bddisasm/src/rflags.rs | 2 - bindings/rsbddisasm/bddisasm/src/tuple.rs | 101 +- bindings/rsbddisasm/fix_symlinks.sh | 12 + disasmtool/disasmtool.vcxproj | 96 ++ disasmtool_lix/CMakeLists.txt | 12 +- disasmtool_lix/disasm.hpp | 5 - disasmtool_lix/disasmtool.cpp | 4 +- 27 files changed, 1888 insertions(+), 724 deletions(-) create mode 100644 bindings/rsbddisasm/bddisasm-sys/CHANGELOG.md create mode 100644 bindings/rsbddisasm/bddisasm/CHANGELOG.md create mode 100644 bindings/rsbddisasm/bddisasm/examples/emulator.rs create mode 100644 bindings/rsbddisasm/fix_symlinks.sh diff --git a/bindings/rsbddisasm/bddisasm-sys/CHANGELOG.md b/bindings/rsbddisasm/bddisasm-sys/CHANGELOG.md new file mode 100644 index 0000000..b4001ab --- /dev/null +++ b/bindings/rsbddisasm/bddisasm-sys/CHANGELOG.md @@ -0,0 +1,7 @@ +# bddisasm-sys changelog + +## Unreleased + +### Changed + +- the crate is now a `no_std` crate (this adds a dependency on [cty](https://crates.io/crates/cty)) diff --git a/bindings/rsbddisasm/bddisasm-sys/Cargo.toml b/bindings/rsbddisasm/bddisasm-sys/Cargo.toml index c8516d8..d61887d 100644 --- a/bindings/rsbddisasm/bddisasm-sys/Cargo.toml +++ b/bindings/rsbddisasm/bddisasm-sys/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "bddisasm-sys" -version = "0.1.0" +version = "0.1.1" authors = ["Cristi Anichitei "] edition = "2018" links = "bddisasm" @@ -12,7 +12,7 @@ documentation = "https://docs.rs/bddisasm-sys" description = """ Bindings to bddisasm instruction decoder library """ -categories = ["external-ffi-bindings", "hardware-support"] +categories = ["external-ffi-bindings", "hardware-support", "no_std"] keywords = ["disassembler", "decoder", "x86", "amd64", "x86_64"] [lib] @@ -20,6 +20,7 @@ name = "bddisasm_sys" path = "src/lib.rs" [dependencies] +cty = "0.2.2" [build-dependencies] bindgen = "0.59.1" diff --git a/bindings/rsbddisasm/bddisasm-sys/README.md b/bindings/rsbddisasm/bddisasm-sys/README.md index 48c8fb5..2b82214 100644 --- a/bindings/rsbddisasm/bddisasm-sys/README.md +++ b/bindings/rsbddisasm/bddisasm-sys/README.md @@ -1,9 +1,13 @@ # bddisasm-sys -Rust bindings for [bddisasm](https://github.com/bitdefender/bddisasm). +`no_std` Rust bindings for [bddisasm](https://github.com/bitdefender/bddisasm). See [bddisasm](https://crates.io/crates/bddisasm) if you're looking for a Rust wrapper for these bindings. ## Requirements [bindgen](https://crates.io/crates/bindgen) is used to generate the bindings at build time. Because of this, users need to have `clang` installed. Check the [bindgen documentation](https://rust-lang.github.io/rust-bindgen/requirements.html) for more information. + +## Notes + +While this crate is `no_std`, the `bddisasm` library it links against depends on a C library because it needs `vsnprintf` and `memset`. It is possible to [work around this limitation](https://github.com/bitdefender/bddisasm#nd_vsnprintf_s-and-nd_memset), but this is not currently done for these bindings. diff --git a/bindings/rsbddisasm/bddisasm-sys/build.rs b/bindings/rsbddisasm/bddisasm-sys/build.rs index 9be3215..cbf0b13 100644 --- a/bindings/rsbddisasm/bddisasm-sys/build.rs +++ b/bindings/rsbddisasm/bddisasm-sys/build.rs @@ -11,6 +11,7 @@ fn main() { cc::Build::new() .file("csrc/bddisasm/bddisasm.c") .file("csrc/bddisasm/bdformat.c") + .file("csrc/bddisasm/bdhelpers.c") .file("csrc/bddisasm/crt.c") .include("csrc/bddisasm/include") .include("csrc/inc") @@ -24,6 +25,8 @@ fn main() { .allowlist_type("ND.*") .allowlist_var("ND.*") .rustified_enum(".*") + .ctypes_prefix("cty") + .use_core() .impl_debug(true) .generate_comments(false) .parse_callbacks(Box::new(bindgen::CargoCallbacks)) diff --git a/bindings/rsbddisasm/bddisasm-sys/src/lib.rs b/bindings/rsbddisasm/bddisasm-sys/src/lib.rs index 24750f5..b988f80 100644 --- a/bindings/rsbddisasm/bddisasm-sys/src/lib.rs +++ b/bindings/rsbddisasm/bddisasm-sys/src/lib.rs @@ -11,6 +11,7 @@ //! [bindgen](https://crates.io/crates/bindgen) is used to generate the bindings at build time. Because of this, users //! need to have `clang` installed. //! Check the [bindgen documentation](https://rust-lang.github.io/rust-bindgen/requirements.html) for more information. +#![cfg_attr(not(test), no_std)] #![allow(non_camel_case_types)] #![allow(non_snake_case)] #![allow(non_upper_case_globals)] diff --git a/bindings/rsbddisasm/bddisasm/CHANGELOG.md b/bindings/rsbddisasm/bddisasm/CHANGELOG.md new file mode 100644 index 0000000..3952155 --- /dev/null +++ b/bindings/rsbddisasm/bddisasm/CHANGELOG.md @@ -0,0 +1,17 @@ +# bddisasm changelog + +## Unreleased + +### Added + +- implement `Decoder::decode_next_with_offset` +- implement ``Decoder::decode_next_with_ip` +- `OperandLookup` struct which makes working with operands easier +- re-export `bddisasm_sys` as `ffi` +- re-export commonly used items +- Implement `as_*` and `is_*` accessors for the `OpInfo` enum + +### Changed + +- the crate is now a `no_std` crate +- public types no longer implement `From` and no longer `panic!` when an unexpected value is encountered diff --git a/bindings/rsbddisasm/bddisasm/Cargo.toml b/bindings/rsbddisasm/bddisasm/Cargo.toml index 72d43af..fbb6912 100644 --- a/bindings/rsbddisasm/bddisasm/Cargo.toml +++ b/bindings/rsbddisasm/bddisasm/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "bddisasm" -version = "0.1.0" +version = "0.1.2" authors = ["Cristi Anichitei "] edition = "2018" license = "Apache-2.0" @@ -15,3 +15,13 @@ keywords = ["disassembler", "decoder", "x86", "amd64", "x86_64"] [dependencies] bddisasm-sys = { version = "0.1.0", path = "../bddisasm-sys" } + +[features] +std = [] + +[package.metadata."docs.rs"] +all-features = true + +[dev-dependencies] +anyhow = "1.0" +clap = "2.34.0" diff --git a/bindings/rsbddisasm/bddisasm/README.md b/bindings/rsbddisasm/bddisasm/README.md index d4d258b..b3c5ac7 100644 --- a/bindings/rsbddisasm/bddisasm/README.md +++ b/bindings/rsbddisasm/bddisasm/README.md @@ -1,11 +1,13 @@ -# bddisasm +# bddisasm x86/x64 instruction decoder -Rust bindings for the [bddisasm](https://github.com/bitdefender/bddisasm) x86/x64 decoder library, built on top -of [bddisasm-sys](https://crates.io/crates/bddisasm-sys). +`no_std` Rust bindings for the [bddisasm](https://github.com/bitdefender/bddisasm) x86/x64 decoder library, built +on top of [bddisasm-sys](https://crates.io/crates/bddisasm-sys). -It supports all existing x86 instruction, offering a wide range of information about each one, including: +It supports all existing 16-bit, 32-bit and 64-bit instructions, offering a wide range of information about each one, +including: -- operands (implicit and explicit) +- implicit operands +- explicit operands - access mode for each operand - CPUID feature flags - CPU modes in which an instruction is valid @@ -23,8 +25,11 @@ bddisasm = "0.1.0" ### Decoding one instruction -```rust -use bddisasm::decoded_instruction::{DecodedInstruction, DecodeMode, Mnemonic}; +Use [`DecodedInstruction::decode`](https://docs.rs/bddisasm/latest/bddisasm/decoded_instruction/struct.DecodedInstruction.html#method.decode) +to decode an instruction from a chunk of code. + +```Rust +use bddisasm::{DecodedInstruction, DecodeMode, Mnemonic}; let code = vec![0x31, 0xc0]; match DecodedInstruction::decode(&code, DecodeMode::Bits32) { @@ -38,8 +43,11 @@ match DecodedInstruction::decode(&code, DecodeMode::Bits32) { ### Decoding multiple instructions -```rust -use bddisasm::decoder::{Decoder, DecodeMode}; +Use [`Decoder`](https://docs.rs/bddisasm/latest/bddisasm/decoder/struct.Decoder.html) to decode multiple instructions +from a chunk of code. + +```Rust +use bddisasm::{Decoder, DecodeMode}; let code = [ // ENCLS @@ -70,21 +78,58 @@ the provided input buffer is too small WRMSR ``` +Use [`Decoder::decode_next_with_info`](https://docs.rs/bddisasm/latest/bddisasm/decoder/struct.Decoder.html#method.decode_next_with_info) +to get information about the offset inside the code chunk at which an instruction was decoded from. + +```Rust +use bddisasm::{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 -Rich informaion is offered for each type of operand. Bellow is a minimal example that looks at a memory operand. +Instruction operands can be analyzed using the [operand](https://docs.rs/bddisasm/latest/bddisasm/operand/index.html) +module. Rich informaion is offered for each type of operand. Bellow is a minimal example that looks at a memory operand. -```rust -# use bddisasm::decode_error::DecodeError; -# fn test() -> Result<(), DecodeError> { -use bddisasm::decoded_instruction::{DecodedInstruction, DecodeMode}; -use bddisasm::operand::OpInfo; +```Rust +use bddisasm::{DecodedInstruction, DecodeMode, 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]; @@ -117,8 +162,6 @@ match src.info { }, _ => unreachable!(), } -# Ok(()) -# } ``` Will print: @@ -131,6 +174,11 @@ Scale: 2 No displacement ``` -## Requirements +## Accessing the raw bindings + +The raw `bddisasm_sys` bindings are available via the `ffi` re-export. + +## Feature Flags -Because [bddisasm-sys](https://crates.io/crates/bddisasm-sys) uses [bindgen](https://crates.io/crates/bindgen) to generate the bindings at build time, users need to have `clang` installed. Check the [bindgen documentation](https://rust-lang.github.io/rust-bindgen/requirements.html) for more information. +- `std` - adds a `std` dependency - the only visible difference when doing this is that [`DecodeError`] implements +the `Error` trait diff --git a/bindings/rsbddisasm/bddisasm/examples/emulator.rs b/bindings/rsbddisasm/bddisasm/examples/emulator.rs new file mode 100644 index 0000000..9e71227 --- /dev/null +++ b/bindings/rsbddisasm/bddisasm/examples/emulator.rs @@ -0,0 +1,159 @@ +extern crate bddisasm; + +use std::fmt; + +use bddisasm::{DecodeMode, DecodedInstruction, Decoder, Mnemonic, OpRegType, Operand}; + +use anyhow::Result; +use clap::{App, Arg}; + +static ABOUT: &str = " +Simple emulator example. +Supports only a few 64-bit instructions (MOV, INC, DEC) and operand types (register, and immediate). +All registers are initially set to 0. +"; + +/// Holds the reigster state. +#[derive(Debug, Default)] +struct Context { + /// Registers are in the Intel documented order (RAX, RCX, RDX, etc). + regs: [u64; 16], +} + +impl Context { + /// Emulates a 64-bit instruction. + pub fn emulate(&mut self, ins: &DecodedInstruction) -> Result<()> { + let operands = ins.operands(); + + match ins.mnemonic() { + Mnemonic::Mov => { + self.set_operand_value(&operands[0], self.get_operand_value(&operands[1])?)? + } + Mnemonic::Inc => self.set_operand_value( + &operands[0], + self.get_operand_value(&operands[0])?.wrapping_add(1), + )?, + Mnemonic::Dec => self.set_operand_value( + &operands[0], + self.get_operand_value(&operands[0])?.wrapping_sub(1), + )?, + _ => anyhow::bail!("Unsupported instruction: {}", ins), + } + + Ok(()) + } + + fn get_operand_value(&self, op: &Operand) -> Result { + if let Some(reg) = op.info.as_reg() { + if reg.kind != OpRegType::Gpr { + anyhow::bail!("Unsupported register type: {}", reg.kind) + } else { + match reg.size { + 1 => { + if reg.is_high8 { + // For `AH`, `BH`, etc. + Ok((self.regs[reg.index] & 0xFF00) >> 8) + } else { + Ok(self.regs[reg.index] & 0xFF) + } + } + 2 => Ok(self.regs[reg.index] & 0xFFFF), + 4 => Ok(self.regs[reg.index] & 0xFFFFFFFF), + 8 => Ok(self.regs[reg.index]), + _ => unreachable!("Unexpected GPR size: {}", reg.size), + } + } + } else if let Some(imm) = op.info.as_imm() { + Ok(imm) + } else { + anyhow::bail!("Unsupported operand type: {}", op.info) + } + } + + fn set_operand_value(&mut self, op: &Operand, value: u64) -> Result<()> { + if let Some(reg) = op.info.as_reg() { + if reg.kind != OpRegType::Gpr { + anyhow::bail!("Unsupported register type: {}", reg.kind) + } else { + let orig_value = self.regs[reg.index]; + + let value = match reg.size { + 1 => { + if reg.is_high8 { + let value = value << 8; + value | (orig_value & 0xFFFFFFFFFFFF00FF) + } else { + value | (orig_value & 0xFFFFFFFFFFFFFF00) + } + } + 2 => value | (orig_value & 0xFFFFFFFFFFFF0000), + 4 => { + // The upper 32-bits are always set to 0. + value & 0xFFFFFFFF + } + 8 => value, + _ => unreachable!("Unexpected GPR size: {}", reg.size), + }; + + self.regs[reg.index] = value; + Ok(()) + } + } else { + anyhow::bail!("Unsupported operand type: {}", op.info) + } + } +} + +impl fmt::Display for Context { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let names = [ + "RAX", "RCX", "RDX", "RBX", "RSP", "RBP", "RSI", "RDI", " R8", " R9", "R10", "R11", + "R12", "R13", "R14", "R15", + ]; + + for (i, name) in names.iter().enumerate() { + if let Err(e) = write!(f, "{}: {:#018x} ", name, self.regs[i]) { + return Err(e); + } + + if (i + 1) % 4 == 0 { + if let Err(e) = writeln!(f) { + return Err(e); + } + } + } + + Ok(()) + } +} + +fn main() -> Result<()> { + let matches = App::new("emulator") + .about(ABOUT) + .arg( + Arg::with_name("INPUT") + .short("f") + .long("file") + .value_name("PATH") + .help("Path to the binary file from which to load code") + .takes_value(true), + ) + .get_matches(); + + let code = std::fs::read(matches.value_of("INPUT").unwrap())?; + + let decoder = Decoder::new(&code, DecodeMode::Bits64, 0); + let mut ctx = Context::default(); + + for ins in decoder { + match ins { + Ok(ins) => { + ctx.emulate(&ins)?; + println!("{}\n{}", ins, ctx); + } + Err(e) => anyhow::bail!("Failed to decode: {}", e), + } + } + + Ok(()) +} diff --git a/bindings/rsbddisasm/bddisasm/src/cpu_modes.rs b/bindings/rsbddisasm/bddisasm/src/cpu_modes.rs index 3b8a09b..23bf710 100644 --- a/bindings/rsbddisasm/bddisasm/src/cpu_modes.rs +++ b/bindings/rsbddisasm/bddisasm/src/cpu_modes.rs @@ -7,10 +7,10 @@ //! # Examples //! //! ``` -//! # use std::error::Error; +//! # use bddisasm::DecodeError; //! # -//! # fn main() -> Result<(), Box> { -//! use bddisasm::decoded_instruction::{DecodedInstruction, DecodeMode, Mnemonic, OperandSize}; +//! # fn main() -> Result<(), DecodeError> { +//! use bddisasm::{DecodedInstruction, DecodeMode, Mnemonic}; //! //! // `VMXON qword ptr [rax]` //! let ins = DecodedInstruction::decode(&[0xf3, 0x0f, 0xc7, 0x30], DecodeMode::Bits64)?; @@ -26,16 +26,21 @@ //! # 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 { + /// Instruction is valid in ring 0. pub ring0: bool, + + /// Instruction is valid in ring 1. pub ring1: bool, + + /// Instruction is valid in ring 2. pub ring2: bool, + + /// Instruction is valid in ring 3. pub ring3: bool, } @@ -109,11 +114,11 @@ pub struct CpuModes { } #[doc(hidden)] -impl From for CpuModes { - fn from(raw: ffi::ND_VALID_MODES) -> CpuModes { +impl CpuModes { + pub(crate) fn from_raw(raw: ffi::ND_VALID_MODES) -> Self { let raw = unsafe { raw.__bindgen_anon_1 }; - CpuModes { + Self { privilege_level: PrivilegeLevel { ring0: raw.Ring0() != 0, ring1: raw.Ring1() != 0, diff --git a/bindings/rsbddisasm/bddisasm/src/cpuid.rs b/bindings/rsbddisasm/bddisasm/src/cpuid.rs index cacce5f..593d893 100644 --- a/bindings/rsbddisasm/bddisasm/src/cpuid.rs +++ b/bindings/rsbddisasm/bddisasm/src/cpuid.rs @@ -7,10 +7,10 @@ //! # Examples //! //! ``` -//! # use std::error::Error; +//! # use bddisasm::DecodeError; //! # //! # fn test() -> Option<()> { -//! use bddisasm::decoded_instruction::{DecodedInstruction, DecodeMode}; +//! use bddisasm::{DecodedInstruction, DecodeMode}; //! //! // `ENCLS` //! let ins = DecodedInstruction::decode(&[0x0f, 0x01, 0xcf], DecodeMode::Bits64).ok()?; @@ -25,9 +25,7 @@ //! # Some(()) //! # } -extern crate bddisasm_sys as ffi; - -use std::fmt; +use core::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)] diff --git a/bindings/rsbddisasm/bddisasm/src/decode_error.rs b/bindings/rsbddisasm/bddisasm/src/decode_error.rs index 713de56..a40f416 100644 --- a/bindings/rsbddisasm/bddisasm/src/decode_error.rs +++ b/bindings/rsbddisasm/bddisasm/src/decode_error.rs @@ -7,36 +7,20 @@ //! //! # Notes //! -//! All error codes that can be returned by `bddisasm-sys` are encapsulated in the [DecodeError](DecodeError) enum. +//! 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 +//! [`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; +use core::fmt; /// Holds all the possible errors that can be encountered by the decoder. +/// +/// # Notes +/// +/// If the `std` feature is disabled, [`DecodeError`] does not implement the `Error` trait. #[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, @@ -134,16 +118,12 @@ pub enum DecodeError { BufferOverflow, /// Internal library error. - InternalError, + InternalError(u64), } 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 => { @@ -156,7 +136,9 @@ impl fmt::Display for DecodeError { 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::InvalidEncodingInMode => { + write!(f, "invalid encoding/instruction in the given mode") + } 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"), @@ -199,12 +181,13 @@ impl fmt::Display for DecodeError { DecodeError::BufferOverflow => { write!(f, "not enough space is available to format instruction") } - DecodeError::InternalError => write!(f, "internal error"), + DecodeError::InternalError(e) => write!(f, "internal error: {}", e), } } } -impl Error for DecodeError {} +#[cfg(feature = "std")] +impl std::error::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 { @@ -249,8 +232,8 @@ pub(crate) fn status_to_error(status: ffi::NDSTATUS) -> Result<(), DecodeError> 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)), + ffi::ND_STATUS_INTERNAL_ERROR => Err(DecodeError::InternalError(0)), + _ => panic!("Unexpected status: {:#x}", status), } } } diff --git a/bindings/rsbddisasm/bddisasm/src/decoded_instruction.rs b/bindings/rsbddisasm/bddisasm/src/decoded_instruction.rs index 6f2541d..0be97ec 100644 --- a/bindings/rsbddisasm/bddisasm/src/decoded_instruction.rs +++ b/bindings/rsbddisasm/bddisasm/src/decoded_instruction.rs @@ -4,28 +4,21 @@ */ //! Decodes instructions. -extern crate bddisasm_sys as ffi; - -pub use crate::cpu_modes::CpuModes; -pub use crate::cpuid::Cpuid; -pub use crate::decode_error::DecodeError; -pub use crate::fpu_flags::FpuFlags; -pub use crate::instruction_category::Category; -pub use crate::isa_set::IsaSet; -pub use crate::mnemonic::Mnemonic; -pub use crate::operand; -pub use crate::operand::{OpAccess, OpAddr}; -pub use crate::rflags; -pub use crate::tuple::Tuple; - +use crate::cpu_modes::CpuModes; +use crate::cpuid::Cpuid; +use crate::decode_error::{status_to_error, DecodeError}; +use crate::fpu_flags::FpuFlags; +use crate::instruction_category::Category; +use crate::isa_set::IsaSet; +use crate::mnemonic::Mnemonic; +use crate::operand; +use crate::operand::{OpAccess, OpAddr, Operands, OperandsLookup}; use crate::rflags::flags_raw; +use crate::tuple::Tuple; -use crate::decode_error; - -use std::fmt; -use std::mem; - -use std::convert::TryFrom; +use core::convert::TryFrom; +use core::fmt; +use core::mem; /// Represents a succesfull instruction decoding, or failure. pub type DecodeResult = Result; @@ -41,6 +34,7 @@ pub enum DecodeMode { Bits64, } +#[doc(hidden)] impl From for (u8, u8) { fn from(mode: DecodeMode) -> Self { match mode { @@ -67,14 +61,15 @@ pub enum EncodingMode { Evex, } -impl From for EncodingMode { - fn from(value: u32) -> EncodingMode { +#[doc(hidden)] +impl EncodingMode { + pub(crate) fn from_raw(value: u32) -> Result { match value { - ffi::ND_ENCM_LEGACY => EncodingMode::Legacy, - ffi::ND_ENCM_XOP => EncodingMode::Xop, - ffi::ND_ENCM_VEX => EncodingMode::Vex, - ffi::ND_ENCM_EVEX => EncodingMode::Evex, - _ => panic!("Unexpected encoding mode mode {}", value), + ffi::ND_ENCM_LEGACY => Ok(EncodingMode::Legacy), + ffi::ND_ENCM_XOP => Ok(EncodingMode::Xop), + ffi::ND_ENCM_VEX => Ok(EncodingMode::Vex), + ffi::ND_ENCM_EVEX => Ok(EncodingMode::Evex), + _ => Err(DecodeError::InternalError(value.into())), } } } @@ -89,12 +84,13 @@ pub enum VexMode { Vex3b, } -impl From for VexMode { - fn from(value: u32) -> VexMode { +#[doc(hidden)] +impl VexMode { + pub(crate) fn from_raw(value: u32) -> Result { match value { - ffi::ND_VEXM_2B => VexMode::Vex2b, - ffi::ND_VEXM_3B => VexMode::Vex3b, - _ => panic!("Unexpected VEX mode mode {}", value), + ffi::ND_VEXM_2B => Ok(VexMode::Vex2b), + ffi::ND_VEXM_3B => Ok(VexMode::Vex3b), + _ => Err(DecodeError::InternalError(value.into())), } } } @@ -112,13 +108,14 @@ pub enum AddressingMode { Addr64, } -impl From for AddressingMode { - fn from(value: u32) -> AddressingMode { +#[doc(hidden)] +impl AddressingMode { + pub(crate) fn from_raw(value: u32) -> Result { match value { - ffi::ND_ADDR_16 => AddressingMode::Addr16, - ffi::ND_ADDR_32 => AddressingMode::Addr32, - ffi::ND_ADDR_64 => AddressingMode::Addr64, - _ => panic!("Unexpected addressing mode {}", value), + ffi::ND_ADDR_16 => Ok(AddressingMode::Addr16), + ffi::ND_ADDR_32 => Ok(AddressingMode::Addr32), + ffi::ND_ADDR_64 => Ok(AddressingMode::Addr64), + _ => Err(DecodeError::InternalError(value.into())), } } } @@ -136,13 +133,14 @@ pub enum OperandSize { OpSize64, } -impl From for OperandSize { - fn from(value: u32) -> OperandSize { +#[doc(hidden)] +impl OperandSize { + pub(crate) fn from_raw(value: u32) -> Result { match value { - ffi::ND_OPSZ_16 => OperandSize::OpSize16, - ffi::ND_OPSZ_32 => OperandSize::OpSize32, - ffi::ND_OPSZ_64 => OperandSize::OpSize64, - _ => panic!("Unexpected operand size {}", value), + ffi::ND_OPSZ_16 => Ok(OperandSize::OpSize16), + ffi::ND_OPSZ_32 => Ok(OperandSize::OpSize32), + ffi::ND_OPSZ_64 => Ok(OperandSize::OpSize64), + _ => Err(DecodeError::InternalError(value.into())), } } } @@ -158,13 +156,14 @@ pub enum VectorSize { VecSize512, } -impl From for VectorSize { - fn from(value: u32) -> VectorSize { +#[doc(hidden)] +impl VectorSize { + pub(crate) fn from_raw(value: u32) -> Result { match value { - ffi::ND_VECM_128 => VectorSize::VecSize128, - ffi::ND_VECM_256 => VectorSize::VecSize256, - ffi::ND_VECM_512 => VectorSize::VecSize512, - _ => panic!("Unexpected vector size {}", value), + ffi::ND_VECM_128 => Ok(VectorSize::VecSize128), + ffi::ND_VECM_256 => Ok(VectorSize::VecSize256), + ffi::ND_VECM_512 => Ok(VectorSize::VecSize512), + _ => Err(DecodeError::InternalError(value.into())), } } } @@ -190,41 +189,27 @@ pub enum ExceptionClass { } #[doc(hidden)] -impl From for ExceptionClass { - fn from(value: ffi::ND_EX_CLASS) -> ExceptionClass { - match value { - ffi::_ND_EX_CLASS::ND_EXC_None => ExceptionClass::None, - ffi::_ND_EX_CLASS::ND_EXC_SSE_AVX => ExceptionClass::SseAvx, - ffi::_ND_EX_CLASS::ND_EXC_EVEX => ExceptionClass::Evex, - ffi::_ND_EX_CLASS::ND_EXC_OPMASK => ExceptionClass::Opmask, - ffi::_ND_EX_CLASS::ND_EXC_AMX => ExceptionClass::Amx, - // NOTE: when updating this take care to also update the `From` implementation! - // TODO: any way of keeping these in sync automagically? - } - } -} - -impl From for ExceptionClass { - fn from(value: u8) -> ExceptionClass { +impl ExceptionClass { + pub(crate) fn from_raw(value: u8) -> Result { if value == ffi::_ND_EX_CLASS::ND_EXC_None as u8 { - ExceptionClass::None + Ok(ExceptionClass::None) } else if value == ffi::_ND_EX_CLASS::ND_EXC_SSE_AVX as u8 { - ExceptionClass::SseAvx + Ok(ExceptionClass::SseAvx) } else if value == ffi::_ND_EX_CLASS::ND_EXC_EVEX as u8 { - ExceptionClass::Evex + Ok(ExceptionClass::Evex) } else if value == ffi::_ND_EX_CLASS::ND_EXC_OPMASK as u8 { - ExceptionClass::Opmask + Ok(ExceptionClass::Opmask) } else if value == ffi::_ND_EX_CLASS::ND_EXC_AMX as u8 { - ExceptionClass::Amx + Ok(ExceptionClass::Amx) } else { - panic!("Unknown exception class {}", value) + Err(DecodeError::InternalError(value.into())) } } } /// Describes the way an instruction accesses the flags register. /// -/// Individual bits can be checked using the [rflags](rflags) module. +/// Individual bits can be checked using the [rflags](crate::rflags) module. #[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)] pub struct FlagsAccess { /// RFLAGS access mode, as per the entire register. @@ -260,31 +245,18 @@ pub enum EvexRounding { } #[doc(hidden)] -impl From for EvexRounding { - fn from(value: ffi::ND_ROUNDING) -> EvexRounding { - match value { - ffi::_ND_ROUNDING::ND_RND_RNE => EvexRounding::NearestEqual, - ffi::_ND_ROUNDING::ND_RND_RD => EvexRounding::Down, - ffi::_ND_ROUNDING::ND_RND_RU => EvexRounding::Up, - ffi::_ND_ROUNDING::ND_RND_RZ => EvexRounding::Zero, - // NOTE: when updating this take care to also update the `From` implementation! - // TODO: any way of keeping these in sync automagically? - } - } -} - -impl From for EvexRounding { - fn from(value: u8) -> EvexRounding { +impl EvexRounding { + pub(crate) fn from_raw(value: u8) -> Result { if value == ffi::_ND_ROUNDING::ND_RND_RNE as u8 { - EvexRounding::NearestEqual + Ok(EvexRounding::NearestEqual) } else if value == ffi::_ND_ROUNDING::ND_RND_RD as u8 { - EvexRounding::Down + Ok(EvexRounding::Down) } else if value == ffi::_ND_ROUNDING::ND_RND_RU as u8 { - EvexRounding::Up + Ok(EvexRounding::Up) } else if value == ffi::_ND_ROUNDING::ND_RND_RZ as u8 { - EvexRounding::Zero + Ok(EvexRounding::Zero) } else { - panic!("Unknown EVEX rounding: {}", value) + Err(DecodeError::InternalError(value.into())) } } } @@ -315,10 +287,10 @@ pub struct ValidPrefixes { } #[doc(hidden)] -impl From for ValidPrefixes { - fn from(value: ffi::ND_VALID_PREFIXES) -> ValidPrefixes { - let raw = unsafe { value.__bindgen_anon_1 }; - ValidPrefixes { +impl ValidPrefixes { + pub(crate) fn from_raw(raw: ffi::ND_VALID_PREFIXES) -> Self { + let raw = unsafe { raw.__bindgen_anon_1 }; + Self { rep: raw.Rep() != 0, rep_cond: raw.RepCond() != 0, lock: raw.Lock() != 0, @@ -349,10 +321,10 @@ pub struct ValidDecorators { } #[doc(hidden)] -impl From for ValidDecorators { - fn from(value: ffi::ND_VALID_DECORATORS) -> ValidDecorators { - let raw = unsafe { value.__bindgen_anon_1 }; - ValidDecorators { +impl ValidDecorators { + pub(crate) fn from_raw(raw: ffi::ND_VALID_DECORATORS) -> Self { + let raw = unsafe { raw.__bindgen_anon_1 }; + Self { er: raw.Er() != 0, sae: raw.Sae() != 0, zero: raw.Zero() != 0, @@ -384,32 +356,31 @@ impl DecodedInstruction { /// /// # Errors /// - /// This function reports back errors returned by the bddisasm library, as well as internal errors that usually - /// signal a bug in this implementation. - /// - /// Note that [`DecodeError::UnknownStatus`] and [`DecodeError::UnknownInstruction`] should never be encountered, - /// unless this crate has fallen out if sync with `bddisasm-sys`, thus they signal a bug in this crate. + /// Wil return `Err` if the given bytes do not encode a valid instruction in the given mode. /// /// # Examples /// /// ``` - /// # use std::error::Error; + /// # use bddisasm::DecodeError; /// # - /// # fn main() -> Result<(), Box> { - /// use bddisasm::decoder::{DecodedInstruction, DecodeMode, Mnemonic}; + /// # fn main() -> Result<(), DecodeError> { + /// use bddisasm::{DecodedInstruction, DecodeMode, Mnemonic}; /// /// let code = vec![0x48, 0x8b, 0x05, 0xf9, 0xff, 0xff, 0xff]; /// /// let instruction = DecodedInstruction::decode_with_ip(&code, DecodeMode::Bits64, 0)?; - /// // Will print `MOV rax, qword ptr [rel 0x0]` - /// println!("{}", instruction); + /// assert_eq!(format!("{}", instruction), "MOV rax, qword ptr [rel 0x0]"); /// /// let instruction = DecodedInstruction::decode_with_ip(&code, DecodeMode::Bits64, 0x100)?; - /// // Will print `MOV rax, qword ptr [rel 0x100]` - /// println!("{}", instruction); + /// assert_eq!(format!("{}", instruction), "MOV rax, qword ptr [rel 0x100]"); /// # Ok(()) /// # } /// ``` + /// + /// # Panics + /// + /// This function will panic if the result of the C library is unrecognized. This can not happen under normal + /// circumstances. pub fn decode_with_ip(code: &[u8], mode: DecodeMode, ip: u64) -> DecodeResult { let mut instrux: mem::MaybeUninit = mem::MaybeUninit::uninit(); let instrux = instrux.as_mut_ptr(); @@ -426,28 +397,38 @@ impl DecodedInstruction { ) }; - decode_error::status_to_error(status)?; + status_to_error(status)?; let instrux = unsafe { *instrux }; Ok(DecodedInstruction { inner: instrux, ip, - instruction: Mnemonic::try_from(unsafe { instrux.__bindgen_anon_2.Instruction })?, + instruction: Mnemonic::try_from(unsafe { instrux.__bindgen_anon_2.Instruction }) + .unwrap(), length: instrux.Length as usize, }) } /// Decodes an array of bytes. /// - /// This function is a thin wrapper over [decode_with_ip](DecodedInstruction::decode_with_ip) and behaves exactly + /// This function is a thin wrapper over [`decode_with_ip`](DecodedInstruction::decode_with_ip) and behaves exactly /// the same. It sets `ip` to 0. + /// + /// # Errors + /// + /// Wil return `Err` if the given bytes do not encode a valid instruction in the given mode. + /// + /// # Panics + /// + /// This function will panic if the result of the C library is unrecognized. This can not happen under normal + /// circumstances. pub fn decode(code: &[u8], mode: DecodeMode) -> DecodeResult { Self::decode_with_ip(code, mode, 0) } /// Get the mnemonic of the instruction. #[inline] - pub fn mnemonic(self) -> Mnemonic { + pub fn mnemonic(&self) -> Mnemonic { self.instruction } @@ -455,12 +436,15 @@ impl DecodedInstruction { /// /// It is guaranteed that no instruction will exceed a length of 15 bytes. #[inline] - pub fn length(self) -> usize { + pub fn length(&self) -> usize { self.length } /// Get the instruction operands. /// + /// For working with specific operands (like the source or destination), + /// [`operand_lookup()`](DecodedInstruction::operand_lookup) might be a better choice. + /// /// The number of elements in the returned vector will always be equal to /// [`operands_count`](DecodedInstruction::operands_count). /// @@ -469,10 +453,10 @@ impl DecodedInstruction { /// See [`operand`] for more in-depth examples on how to work with instruction operands. /// /// ``` - /// # use std::error::Error; + /// # use bddisasm::DecodeError; /// # - /// # fn main() -> Result<(), Box> { - /// use bddisasm::decoder::{DecodedInstruction, DecodeMode, Mnemonic}; + /// # fn main() -> Result<(), DecodeError> { + /// use bddisasm::{DecodedInstruction, DecodeMode, Mnemonic}; /// /// // `MOV ebx, dword ptr [ecx+edi]` /// let instruction = DecodedInstruction::decode(b"\x8b\x1c\x39", DecodeMode::Bits32)?; /// @@ -480,23 +464,29 @@ impl DecodedInstruction { /// // The first operand is the destination /// let dst = operands[0]; /// // The second operand is the source - /// let src = operands[0]; + /// let src = operands[1]; /// /// // Print information about the operands /// println!("{:#?} {:#?}", dst.info, src.info); /// # Ok(()) /// # } /// ``` - pub fn operands(self) -> Vec { - let mut ops = Vec::new(); + /// + /// # Panics + /// + /// This function will panic if the operand returned by the C library is invalid. This can not happen under normal + /// circumstances. + pub fn operands(&self) -> Operands { + let mut operands = Operands::default(); for op_index in 0..self.inner.OperandsCount { - ops.push(operand::Operand::from( - self.inner.Operands[op_index as usize], - )); + operands.operands[op_index as usize] = + operand::Operand::from_raw(self.inner.Operands[op_index as usize]).unwrap(); } - ops + operands.actual_count = self.inner.OperandsCount as usize; + + operands } /// Returns the CPUID support flag. @@ -508,23 +498,26 @@ impl DecodedInstruction { /// See [cpuid](crate::cpuid) for more in-depth examples on how to use the CPUID information. /// /// ``` - /// # use std::error::Error; + /// # use bddisasm::DecodeError; /// # - /// # fn main() -> Result<(), Box> { - /// use bddisasm::decoder::{DecodedInstruction, DecodeMode, Mnemonic}; + /// # fn main() -> Result<(), DecodeError> { + /// use bddisasm::{DecodedInstruction, DecodeMode, Mnemonic}; /// /// // `RDMSR` /// let instruction = DecodedInstruction::decode(b"\x0f\x32", DecodeMode::Bits64)?; /// let cpuid = instruction.cpuid(); - /// // Will print `leaf: 0x1 sub-leaf: - register: 2 bit to test: 0x5` - /// if let Some(cpuid) = cpuid { - /// println!("{}", cpuid); - /// } + /// assert!(cpuid.is_some()); + /// + /// let cpuid = cpuid.unwrap(); + /// assert_eq!(cpuid.leaf, 1); + /// assert_eq!(cpuid.sub_leaf, None); + /// assert_eq!(cpuid.register, 2); + /// assert_eq!(cpuid.bit, 5); /// /// # Ok(()) /// # } /// ``` - pub fn cpuid(self) -> Option { + pub fn cpuid(&self) -> Option { let cpuid = unsafe { self.inner.CpuidFlag.__bindgen_anon_1 }; let leaf = cpuid.Leaf; if leaf == ffi::ND_CFF_NO_LEAF { @@ -538,7 +531,7 @@ impl DecodedInstruction { }; let register = cpuid.Reg() as u8; - let bit = cpuid.Bit() as u64; + let bit = u64::from(cpuid.Bit()); Some(Cpuid { leaf, @@ -550,25 +543,37 @@ impl DecodedInstruction { } /// Get the encoding mode used. + /// + /// # Panics + /// + /// This function will panic if the encoding mode is unrecognized. This can not happen under normal circumstances. #[inline] - pub fn encoding_mode(self) -> EncodingMode { - EncodingMode::from(self.inner.EncMode() as u32) + pub fn encoding_mode(&self) -> EncodingMode { + EncodingMode::from_raw(u32::from(self.inner.EncMode())).unwrap() } /// Get the VEX mode, if any. + /// + /// # Panics + /// + /// This function will panic if the VEX mode is unrecognized. This can not happen under normal circumstances. #[inline] - pub fn vex_mode(self) -> Option { + pub fn vex_mode(&self) -> Option { if self.has_vex() { - Some(VexMode::from(self.inner.VexMode() as u32)) + Some(VexMode::from_raw(u32::from(self.inner.VexMode())).unwrap()) } else { None } } /// Get the addressing mode. + /// + /// # Panics + /// + /// This function will panic if the addressing mode is unrecognized. This can not happen under normal circumstances. #[inline] - pub fn addr_mode(self) -> AddressingMode { - AddressingMode::from(self.inner.AddrMode() as u32) + pub fn addr_mode(&self) -> AddressingMode { + AddressingMode::from_raw(u32::from(self.inner.AddrMode())).unwrap() } /// Get the operand mode/size. @@ -577,7 +582,7 @@ impl DecodedInstruction { /// /// # Remarks /// - /// The effective mode can be different (see [effective_op_mode](DecodedInstruction::effective_op_mode)). + /// The effective mode can be different (see [`effective_op_mode`](DecodedInstruction::effective_op_mode)). /// /// # Examples /// @@ -586,10 +591,10 @@ impl DecodedInstruction { /// operates on 64 bits. /// /// ``` - /// # use std::error::Error; + /// # use bddisasm::DecodeError; /// # - /// # fn main() -> Result<(), Box> { - /// use bddisasm::decoded_instruction::{DecodedInstruction, DecodeMode, Mnemonic, OperandSize}; + /// # fn main() -> Result<(), DecodeError> { + /// use bddisasm::{DecodedInstruction, DecodeMode, Mnemonic, OperandSize}; /// /// let ins = /// DecodedInstruction::decode(&[0x50], DecodeMode::Bits64)?; @@ -604,17 +609,26 @@ impl DecodedInstruction { /// assert_eq!(ins.effective_op_mode(), OperandSize::OpSize64); /// # Ok(()) /// # } + /// ``` + /// + /// # Panics + /// + /// This function will panic if the operand size is unrecognized. This can not happen under normal circumstances. #[inline] - pub fn op_mode(self) -> OperandSize { - OperandSize::from(self.inner.OpMode() as u32) + pub fn op_mode(&self) -> OperandSize { + OperandSize::from_raw(u32::from(self.inner.OpMode())).unwrap() } /// Get the effective operand mode/size. /// - /// Unlike [op_mode](DecodedInstruction::op_mode), this takes into account the actual instruction. + /// Unlike [`op_mode`](DecodedInstruction::op_mode), this takes into account the actual instruction. + /// + /// # Panics + /// + /// This function will panic if the operand size is unrecognized. This can not happen under normal circumstances. #[inline] - pub fn effective_op_mode(self) -> OperandSize { - OperandSize::from(self.inner.EfOpMode() as u32) + pub fn effective_op_mode(&self) -> OperandSize { + OperandSize::from_raw(u32::from(self.inner.EfOpMode())).unwrap() } /// Get the Vector mode/size, if any. @@ -623,11 +637,15 @@ impl DecodedInstruction { /// /// # Remarks /// - /// The effective mode can be different (see [effective_vec_mode](DecodedInstruction::effective_vec_mode)). + /// The effective mode can be different (see [`effective_vec_mode`](DecodedInstruction::effective_vec_mode)). + /// + /// # Panics + /// + /// This function will panic if the vector mode is unrecognized. This can not happen under normal circumstances. #[inline] - pub fn vec_mode(self) -> Option { + pub fn vec_mode(&self) -> Option { if self.has_vector() { - Some(VectorSize::from(self.inner.VecMode() as u32)) + Some(VectorSize::from_raw(u32::from(self.inner.VecMode())).unwrap()) } else { None } @@ -635,11 +653,15 @@ impl DecodedInstruction { /// Get the Vector mode/size, if any. /// - /// Unlike [vec_mode](DecodedInstruction::vec_mode), this takes into account the actual instruction. + /// Unlike [`vec_mode`](DecodedInstruction::vec_mode), this takes into account the actual instruction. + /// + /// # Panics + /// + /// This function will panic if the vector mode is unrecognized. This can not happen under normal circumstances. #[inline] - pub fn effective_vec_mode(self) -> Option { + pub fn effective_vec_mode(&self) -> Option { if self.has_vector() { - Some(VectorSize::from(self.inner.EfVecMode() as u32)) + Some(VectorSize::from_raw(u32::from(self.inner.EfVecMode())).unwrap()) } else { None } @@ -647,235 +669,235 @@ impl DecodedInstruction { /// `true` if REX is present. #[inline] - pub fn has_rex(self) -> bool { - self.inner.HasRex() + pub fn has_rex(&self) -> bool { + self.inner.HasRex() != 0 } /// `true` if VEX is present. #[inline] - pub fn has_vex(self) -> bool { - self.inner.HasVex() + pub fn has_vex(&self) -> bool { + self.inner.HasVex() != 0 } /// `true` if XOP is present. #[inline] - pub fn has_xop(self) -> bool { - self.inner.HasXop() + pub fn has_xop(&self) -> bool { + self.inner.HasXop() != 0 } /// `true` if EVEX is present. #[inline] - pub fn has_evex(self) -> bool { - self.inner.HasEvex() + pub fn has_evex(&self) -> bool { + self.inner.HasEvex() != 0 } /// `true` if MVEX is present. #[inline] - pub fn has_mvex(self) -> bool { - self.inner.HasMvex() + pub fn has_mvex(&self) -> bool { + self.inner.HasMvex() != 0 } /// `true` if 0x66 is present. #[inline] - pub fn has_op_size(self) -> bool { - self.inner.HasOpSize() + pub fn has_op_size(&self) -> bool { + self.inner.HasOpSize() != 0 } /// `true` if 0x67 is present. #[inline] - pub fn has_addr_size(self) -> bool { - self.inner.HasAddrSize() + pub fn has_addr_size(&self) -> bool { + self.inner.HasAddrSize() != 0 } /// `true` if 0xF0 is present. #[inline] - pub fn has_lock(self) -> bool { - self.inner.HasLock() + pub fn has_lock(&self) -> bool { + self.inner.HasLock() != 0 } /// `true` if 0xF2 is present. #[inline] - pub fn has_repnz_xacquire_bnd(self) -> bool { - self.inner.HasRepnzXacquireBnd() + pub fn has_repnz_xacquire_bnd(&self) -> bool { + self.inner.HasRepnzXacquireBnd() != 0 } /// `true` if 0xF3 is present. #[inline] - pub fn has_rep_repz_xrelease(self) -> bool { - self.inner.HasRepRepzXrelease() + pub fn has_rep_repz_xrelease(&self) -> bool { + self.inner.HasRepRepzXrelease() != 0 } /// `true` if segment override is present. #[inline] - pub fn has_seg(self) -> bool { - self.inner.HasSeg() + pub fn has_seg(&self) -> bool { + self.inner.HasSeg() != 0 } - /// `true` if the instruction is REPed up to RCX times. + /// `true` if the instruction is repeated up to `RCX` times. #[inline] - pub fn is_repeated(self) -> bool { - self.inner.IsRepeated() + pub fn is_repeated(&self) -> bool { + self.inner.IsRepeated() != 0 } /// `true` if the instruction is XACQUIRE enabled. #[inline] - pub fn is_xacquire_enabled(self) -> bool { - self.inner.IsXacquireEnabled() + pub fn is_xacquire_enabled(&self) -> bool { + self.inner.IsXacquireEnabled() != 0 } /// `true` if the instruction is XRELEASE enabled. #[inline] - pub fn is_xrelease_enabled(self) -> bool { - self.inner.IsXreleaseEnabled() + pub fn is_xrelease_enabled(&self) -> bool { + self.inner.IsXreleaseEnabled() != 0 } /// `true` if the instruction uses RIP relative addressing. #[inline] - pub fn is_rip_relative(self) -> bool { - self.inner.IsRipRelative() + pub fn is_rip_relative(&self) -> bool { + self.inner.IsRipRelative() != 0 } /// `true` if this is an indirect CALL/JMP that is CET tracked. #[inline] - pub fn is_cet_tracked(self) -> bool { - self.inner.IsCetTracked() + pub fn is_cet_tracked(&self) -> bool { + self.inner.IsCetTracked() != 0 } /// `true` if we have valid MODRM. #[inline] - pub fn has_mod_rm(self) -> bool { - self.inner.HasModRm() + pub fn has_mod_rm(&self) -> bool { + self.inner.HasModRm() != 0 } /// `true` if we have valid SIB. #[inline] - pub fn has_sib(self) -> bool { - self.inner.HasSib() + pub fn has_sib(&self) -> bool { + self.inner.HasSib() != 0 } /// `true` if we have valid DREX. #[inline] - pub fn has_drex(self) -> bool { - self.inner.HasDrex() + pub fn has_drex(&self) -> bool { + self.inner.HasDrex() != 0 } /// `true` if the instruction has displacement. #[inline] - pub fn has_disp(self) -> bool { - self.inner.HasDisp() + pub fn has_disp(&self) -> bool { + self.inner.HasDisp() != 0 } /// `true` if the instruction contains a direct address (ie, `CALL far 0x9A`). #[inline] - pub fn has_addr(self) -> bool { - self.inner.HasAddr() + pub fn has_addr(&self) -> bool { + self.inner.HasAddr() != 0 } /// `true` if the instruction contains a moffset (ie, `MOV al, [mem], 0xA0`). #[inline] - pub fn has_moffset(self) -> bool { - self.inner.HasMoffset() + pub fn has_moffset(&self) -> bool { + self.inner.HasMoffset() != 0 } /// `true` if immediate is present. #[inline] - pub fn has_imm1(self) -> bool { - self.inner.HasImm1() + pub fn has_imm1(&self) -> bool { + self.inner.HasImm1() != 0 } /// `true` if second immediate is present. #[inline] - pub fn has_imm2(self) -> bool { - self.inner.HasImm2() + pub fn has_imm2(&self) -> bool { + self.inner.HasImm2() != 0 } /// `true` if third immediate is present. #[inline] - pub fn has_imm3(self) -> bool { - self.inner.HasImm3() + pub fn has_imm3(&self) -> bool { + self.inner.HasImm3() != 0 } /// `true` if the instruction contains a relative offset (ie, `Jcc 0x7x`). #[inline] - pub fn has_rel_offs(self) -> bool { - self.inner.HasRelOffs() + pub fn has_rel_offs(&self) -> bool { + self.inner.HasRelOffs() != 0 } /// `true` if SSE immediate that encodes additional registers is present. #[inline] - pub fn has_sse_imm(self) -> bool { - self.inner.HasSseImm() + pub fn has_sse_imm(&self) -> bool { + self.inner.HasSseImm() != 0 } /// `true` if the instruction uses compressed displacement. #[inline] - pub fn has_comp_disp(self) -> bool { - self.inner.HasCompDisp() + pub fn has_comp_disp(&self) -> bool { + self.inner.HasCompDisp() != 0 } /// `true` if the instruction uses broadcast addressing. #[inline] - pub fn has_broadcast(self) -> bool { - self.inner.HasBroadcast() + pub fn has_broadcast(&self) -> bool { + self.inner.HasBroadcast() != 0 } /// `true` if the instruction has mask. #[inline] - pub fn has_mask(self) -> bool { - self.inner.HasMask() + pub fn has_mask(&self) -> bool { + self.inner.HasMask() != 0 } /// `true` if the instruction uses zeroing. #[inline] - pub fn has_zero(self) -> bool { - self.inner.HasZero() + pub fn has_zero(&self) -> bool { + self.inner.HasZero() != 0 } /// `true` if the instruction has embedded rounding. #[inline] - pub fn has_er(self) -> bool { - self.inner.HasEr() + pub fn has_er(&self) -> bool { + self.inner.HasEr() != 0 } /// `true` if the instruction has SAE. #[inline] - pub fn has_sae(self) -> bool { - self.inner.HasSae() + pub fn has_sae(&self) -> bool { + self.inner.HasSae() != 0 } /// `true` if the instruction ignores embedded rounding. #[inline] - pub fn has_ign_er(self) -> bool { - self.inner.HasIgnEr() + pub fn has_ign_er(&self) -> bool { + self.inner.HasIgnEr() != 0 } /// Displacement sign. `false` if positive, `true` if negative. #[inline] - pub fn sign_disp(self) -> bool { - self.inner.SignDisp() + pub fn sign_disp(&self) -> bool { + self.inner.SignDisp() != 0 } /// `true` if changing prefix. #[inline] - pub fn has_mandatory_66(self) -> bool { - self.inner.HasMandatory66() + pub fn has_mandatory_66(&self) -> bool { + self.inner.HasMandatory66() != 0 } /// 0x66 is mandatory prefix. Does not behave as REP prefix. #[inline] - pub fn has_mandatory_f2(self) -> bool { - self.inner.HasMandatoryF2() + pub fn has_mandatory_f2(&self) -> bool { + self.inner.HasMandatoryF2() != 0 } /// 0x66 is mandatory prefix. Does not behave as REP prefix. #[inline] - pub fn has_mandatory_f3(self) -> bool { - self.inner.HasMandatoryF3() + pub fn has_mandatory_f3(&self) -> bool { + self.inner.HasMandatoryF3() != 0 } /// The length of the instruction word. 2, 4 or 8. #[inline] - pub fn word_length(self) -> usize { + pub fn word_length(&self) -> usize { self.inner.WordLength() as usize } @@ -883,73 +905,73 @@ impl DecodedInstruction { /// /// This will also be the offset to the first opcode. The primary opcode will always be the last one. #[inline] - pub fn pref_length(self) -> usize { + pub fn pref_length(&self) -> usize { self.inner.PrefLength() as usize } /// Number of opcode bytes. Max 3. #[inline] - pub fn op_length(self) -> usize { + pub fn op_length(&self) -> usize { self.inner.OpLength() as usize } /// Displacement length, in bytes. Maximum 4. #[inline] - pub fn disp_length(self) -> usize { + pub fn disp_length(&self) -> usize { self.inner.DispLength() as usize } /// Absolute address length, in bytes. Maximum 8 bytes. #[inline] - pub fn addr_length(self) -> usize { + pub fn addr_length(&self) -> usize { self.inner.AddrLength() as usize } /// Memory offset length, in bytes. Maximum 8 bytes. #[inline] - pub fn moffset_length(self) -> usize { + pub fn moffset_length(&self) -> usize { self.inner.MoffsetLength() as usize } /// First immediate length, in bytes. Maximum 8 bytes. #[inline] - pub fn imm1_length(self) -> usize { + pub fn imm1_length(&self) -> usize { self.inner.Imm1Length() as usize } /// Second immediate length, in bytes. Maximum 8 bytes. #[inline] - pub fn imm2_length(self) -> usize { + pub fn imm2_length(&self) -> usize { self.inner.Imm2Length() as usize } /// Third immediate length, in bytes. Maximum 8 bytes. #[inline] - pub fn imm3_length(self) -> usize { + pub fn imm3_length(&self) -> usize { self.inner.Imm3Length() as usize } /// Relative offset length, in bytes. Maximum 4 bytes. #[inline] - pub fn rel_offs_length(self) -> usize { + pub fn rel_offs_length(&self) -> usize { self.inner.RelOffsLength() as usize } /// The offset of the first opcode, inside the instruction. #[inline] - pub fn op_offset(self) -> usize { + pub fn op_offset(&self) -> usize { self.inner.OpOffset() as usize } /// The offset of the nominal opcode, inside the instruction. #[inline] - pub fn main_op_offset(self) -> usize { + pub fn main_op_offset(&self) -> usize { self.inner.MainOpOffset() as usize } #[inline] /// The offset of the displacement, inside the instruction. - pub fn disp_offset(self) -> Option { + pub fn disp_offset(&self) -> Option { let value = self.inner.DispOffset() as usize; if value == 0 { None @@ -960,7 +982,7 @@ impl DecodedInstruction { #[inline] /// The offset of the hard-coded address. - pub fn addr_offset(self) -> Option { + pub fn addr_offset(&self) -> Option { let value = self.inner.AddrOffset() as usize; if value == 0 { None @@ -971,7 +993,7 @@ impl DecodedInstruction { /// The offset of the absolute address, inside the instruction. #[inline] - pub fn moffset_offset(self) -> Option { + pub fn moffset_offset(&self) -> Option { let value = self.inner.MoffsetOffset() as usize; if value == 0 { None @@ -982,7 +1004,7 @@ impl DecodedInstruction { /// The offset of the immediate, inside the instruction. #[inline] - pub fn imm1_offset(self) -> Option { + pub fn imm1_offset(&self) -> Option { let value = self.inner.Imm1Offset() as usize; if value == 0 { None @@ -993,7 +1015,7 @@ impl DecodedInstruction { /// The offset of the second immediate, if any, inside the instruction. #[inline] - pub fn imm2_offset(self) -> Option { + pub fn imm2_offset(&self) -> Option { let value = self.inner.Imm2Offset() as usize; if value == 0 { None @@ -1004,7 +1026,7 @@ impl DecodedInstruction { /// The offset of the third immediate, if any, inside the instruction. #[inline] - pub fn imm3_offset(self) -> Option { + pub fn imm3_offset(&self) -> Option { let value = self.inner.Imm3Offset() as usize; if value == 0 { None @@ -1015,7 +1037,7 @@ impl DecodedInstruction { /// The offset of the relative offset used in instruction. #[inline] - pub fn rel_offs_offset(self) -> Option { + pub fn rel_offs_offset(&self) -> Option { let value = self.inner.RelOffsOffset() as usize; if value == 0 { None @@ -1026,7 +1048,7 @@ impl DecodedInstruction { /// The offset of the SSE immediate, if any, inside the instruction. #[inline] - pub fn sse_imm_offset(self) -> Option { + pub fn sse_imm_offset(&self) -> Option { let value = self.inner.SseImmOffset() as usize; if value == 0 { None @@ -1037,7 +1059,7 @@ impl DecodedInstruction { /// The offset of the mod rm byte inside the instruction, if any. #[inline] - pub fn mod_rm_offset(self) -> Option { + pub fn mod_rm_offset(&self) -> Option { let value = self.inner.ModRmOffset() as usize; if value == 0 { None @@ -1048,13 +1070,13 @@ impl DecodedInstruction { /// Number of words accessed on/from the stack. #[inline] - pub fn stack_words(self) -> usize { + pub fn stack_words(&self) -> usize { self.inner.StackWords as usize } /// The last rep/repz/repnz prefix. if any. #[inline] - pub fn rep(self) -> Option { + pub fn rep(&self) -> Option { let value = self.inner.Rep; if value == 0 { None @@ -1065,7 +1087,7 @@ impl DecodedInstruction { /// The last segment override prefix. if none. `FS`/`GS` if 64 bit. #[inline] - pub fn seg(self) -> Option { + pub fn seg(&self) -> Option { let value = self.inner.Seg; if value == 0 { None @@ -1076,13 +1098,13 @@ impl DecodedInstruction { /// The last segment override indicating a branch hint. #[inline] - pub fn bhint(self) -> u8 { + pub fn bhint(&self) -> u8 { self.inner.Bhint } /// Get the REX prefix. #[inline] - pub fn rex(self) -> Option { + pub fn rex(&self) -> Option { if self.has_rex() { Some(unsafe { self.inner.Rex.Rex }) } else { @@ -1090,9 +1112,9 @@ impl DecodedInstruction { } } - /// Get the ModRM byte. + /// Get the `ModRM` byte. #[inline] - pub fn mod_rm(self) -> Option { + pub fn mod_rm(&self) -> Option { if self.has_mod_rm() { Some(unsafe { self.inner.ModRm.ModRm }) } else { @@ -1100,9 +1122,9 @@ impl DecodedInstruction { } } - /// Get the SIB byte. + /// Get the `SIB` byte. #[inline] - pub fn sib(self) -> Option { + pub fn sib(&self) -> Option { if self.has_sib() { Some(unsafe { self.inner.Sib.Sib }) } else { @@ -1110,9 +1132,9 @@ impl DecodedInstruction { } } - /// Get the 2-bytes VEX. + /// Get the 2-bytes `VEX` prefix. #[inline] - pub fn vex2(self) -> Option<(u8, u8)> { + pub fn vex2(&self) -> Option<(u8, u8)> { if matches!(self.vex_mode(), Some(VexMode::Vex2b)) { let vex2 = self.inner.__bindgen_anon_1; let vex2 = unsafe { vex2.Vex2.Vex }; @@ -1123,9 +1145,9 @@ impl DecodedInstruction { } } - /// Get the 3-bytes VEX. + /// Get the 3-bytes `VEX` prefix. #[inline] - pub fn vex3(self) -> Option<(u8, u8, u8)> { + pub fn vex3(&self) -> Option<(u8, u8, u8)> { if matches!(self.vex_mode(), Some(VexMode::Vex3b)) { let vex3 = self.inner.__bindgen_anon_1; let vex3 = unsafe { vex3.Vex3.Vex }; @@ -1136,9 +1158,9 @@ impl DecodedInstruction { } } - /// Get the XOP bytes. + /// Get the `XOP` bytes. #[inline] - pub fn xop(self) -> Option<(u8, u8, u8)> { + pub fn xop(&self) -> Option<(u8, u8, u8)> { if self.has_xop() { let xop = self.inner.__bindgen_anon_1; let xop = unsafe { xop.Xop.Xop }; @@ -1149,9 +1171,9 @@ impl DecodedInstruction { } } - /// Get the EVEX bytes. + /// Get the `EVEX` bytes. #[inline] - pub fn evex(self) -> Option<(u8, u8, u8, u8)> { + pub fn evex(&self) -> Option<(u8, u8, u8, u8)> { if self.has_evex() { let evex = self.inner.__bindgen_anon_1; let evex = unsafe { evex.Evex.Evex }; @@ -1164,12 +1186,12 @@ impl DecodedInstruction { /// Get the `segment:offset` address accessed by the instruction, if any. #[inline] - pub fn address(self) -> Option { + pub fn address(&self) -> Option { if self.has_addr() { let raw = self.inner.Address; let raw = unsafe { raw.__bindgen_anon_1 }; - Some(OpAddr::new(raw.Cs, raw.Ip as u64)) + Some(OpAddr::new(raw.Cs, u64::from(raw.Ip))) } else { None } @@ -1177,7 +1199,7 @@ impl DecodedInstruction { /// Get the absolute offset, if any. #[inline] - pub fn moffset(self) -> Option { + pub fn moffset(&self) -> Option { if self.has_moffset() { Some(self.inner.Moffset) } else { @@ -1185,9 +1207,9 @@ impl DecodedInstruction { } } - /// Get the displacement. Max 4 bytes. Used in ModRM instructions. + /// Get the displacement. Max 4 bytes. Used in `ModRM` instructions. #[inline] - pub fn disp(self) -> Option { + pub fn disp(&self) -> Option { if self.has_disp() { Some(self.inner.Displacement) } else { @@ -1197,7 +1219,7 @@ impl DecodedInstruction { /// Get the relative offset, used for branches. Max 4 bytes. #[inline] - pub fn rel_offset(self) -> Option { + pub fn rel_offset(&self) -> Option { if self.has_rel_offs() { Some(self.inner.RelativeOffset) } else { @@ -1207,7 +1229,7 @@ impl DecodedInstruction { /// Get the first immediate. #[inline] - pub fn immediate1(self) -> Option { + pub fn immediate1(&self) -> Option { if self.has_imm1() { Some(self.inner.Immediate1) } else { @@ -1215,9 +1237,9 @@ impl DecodedInstruction { } } - /// Get the second immediate. Used mainly for [Mnemonic::Enter](Mnemonic::Enter). + /// Get the second immediate. Used mainly for [`Mnemonic::Enter`](Mnemonic::Enter). #[inline] - pub fn immediate2(self) -> Option { + pub fn immediate2(&self) -> Option { if self.has_imm2() { Some(self.inner.Immediate2) } else { @@ -1227,7 +1249,7 @@ impl DecodedInstruction { /// Get the third additional immediate. #[inline] - pub fn immediate3(self) -> Option { + pub fn immediate3(&self) -> Option { if self.has_imm3() { Some(self.inner.Immediate3) } else { @@ -1237,7 +1259,7 @@ impl DecodedInstruction { /// Get the SSE immediate. It is used to select a register. #[inline] - pub fn sse_immediate(self) -> Option { + pub fn sse_immediate(&self) -> Option { if self.has_sse_imm() { Some(self.inner.SseImmediate) } else { @@ -1245,9 +1267,9 @@ impl DecodedInstruction { } } - /// Get the SSE condition byte. + /// Get the `SSE` condition byte. #[inline] - pub fn sse_cond(self) -> Option { + pub fn sse_cond(&self) -> Option { if (self.inner.Attributes & ffi::ND_FLAG_SSE_CONDB) != 0 { Some(self.inner.SseCondition) } else { @@ -1257,7 +1279,7 @@ impl DecodedInstruction { /// Get the condition byte. #[inline] - pub fn cond(self) -> Option { + pub fn cond(&self) -> Option { if (self.inner.Attributes & ffi::ND_FLAG_COND) != 0 { Some(self.inner.Condition) } else { @@ -1265,9 +1287,17 @@ impl DecodedInstruction { } } + /// `true` if the instruction is 3DNow!. + /// + /// The opcode is the last byte. + #[inline] + pub fn is_3d_now(&self) -> bool { + (self.inner.Attributes & ffi::ND_FLAG_3DNOW) != 0 + } + /// Get the number of operands. #[inline] - pub fn operands_count(self) -> usize { + pub fn operands_count(&self) -> usize { self.inner.OperandsCount as usize } @@ -1275,22 +1305,30 @@ impl DecodedInstruction { /// /// Use this if you want to ignore implicit operands such as stack, flags, etc. #[inline] - pub fn exp_operands_count(self) -> usize { + pub fn exp_operands_count(&self) -> usize { self.inner.ExpOperandsCount as usize } - /// Get the RIP access mode. + /// Get the `CS` access mode. #[inline] - pub fn rip_access(self) -> OpAccess { - OpAccess::from(ffi::ND_OPERAND_ACCESS { + pub fn cs_access(&self) -> OpAccess { + OpAccess::from_raw(ffi::ND_OPERAND_ACCESS { + Access: self.inner.CsAccess, + }) + } + + /// Get the `RIP` access mode. + #[inline] + pub fn rip_access(&self) -> OpAccess { + OpAccess::from_raw(ffi::ND_OPERAND_ACCESS { Access: self.inner.RipAccess, }) } /// Get the stack access mode. #[inline] - pub fn stack_access(self) -> OpAccess { - OpAccess::from(ffi::ND_OPERAND_ACCESS { + pub fn stack_access(&self) -> OpAccess { + OpAccess::from_raw(ffi::ND_OPERAND_ACCESS { Access: self.inner.StackAccess, }) } @@ -1299,17 +1337,41 @@ impl DecodedInstruction { /// /// This includes the stack or shadow stack access. #[inline] - pub fn memory_access(self) -> OpAccess { - OpAccess::from(ffi::ND_OPERAND_ACCESS { + pub fn memory_access(&self) -> OpAccess { + OpAccess::from_raw(ffi::ND_OPERAND_ACCESS { Access: self.inner.MemoryAccess, }) } + /// `true` if the instruction is a branch. + #[inline] + pub fn is_branch(&self) -> bool { + self.inner.BranchInfo.IsBranch() != 0 + } + + /// `true` if the instruction is a conditional branch. + #[inline] + pub fn is_conditional_branch(&self) -> bool { + self.inner.BranchInfo.IsConditional() != 0 + } + + /// `true` if the instruction is a indirect branch. + #[inline] + pub fn is_indirect_branch(&self) -> bool { + self.inner.BranchInfo.IsIndirect() != 0 + } + + /// `true` if the instruction is a far branch. + #[inline] + pub fn is_far_branch(&self) -> bool { + self.inner.BranchInfo.IsFar() != 0 + } + /// Get the rflags access. - pub fn flags_access(self) -> FlagsAccess { + pub fn flags_access(&self) -> FlagsAccess { let facc = self.inner.FlagsAccess; - let mode = OpAccess::from(ffi::ND_OPERAND_ACCESS { + let mode = OpAccess::from_raw(ffi::ND_OPERAND_ACCESS { Access: facc.RegAccess, }); @@ -1323,33 +1385,50 @@ impl DecodedInstruction { } } - /// FPU status word C0-C3 bits access. + /// `FPU` status word C0-C3 bits access. + /// + /// # Panics + /// + /// This function will panic if the access mode is unrecognized. This can not happen under normal circumstances. #[inline] - pub fn fpu_flags_access(self) -> FpuFlags { - FpuFlags::from(self.inner.FpuFlagsAccess) + pub fn fpu_flags_access(&self) -> FpuFlags { + FpuFlags::from_raw(self.inner.FpuFlagsAccess).unwrap() } - // The exception class. + /// The exception class. + /// + /// # Panics + /// + /// This function will panic if the exception class is unrecognized. This can not happen under normal circumstances. #[inline] - pub fn exception_class(self) -> ExceptionClass { - ExceptionClass::from(self.inner.ExceptionClass) + pub fn exception_class(&self) -> ExceptionClass { + ExceptionClass::from_raw(self.inner.ExceptionClass).unwrap() } - /// EVEX tuple type. + /// `EVEX` tuple type. + /// + /// # Panics + /// + /// This function will panic if the EVEX tuple type is unrecognized. This can not happen under normal circumstances. #[inline] - pub fn evex_tuple(self) -> Option { + pub fn evex_tuple(&self) -> Option { if self.has_evex() { - Some(Tuple::from(self.inner.TupleType as u32)) + Some(Tuple::from_raw(u32::from(self.inner.TupleType)).unwrap()) } else { None } } - /// EVEX rounding mode. + /// `EVEX` rounding mode. + /// + /// # Panics + /// + /// This function will panic if the EVEX rounding mode is unrecognized. This can not happen under normal + /// circumstances. #[inline] - pub fn evex_rounding(self) -> Option { + pub fn evex_rounding(&self) -> Option { if self.has_er() { - Some(EvexRounding::from(self.inner.RoundingMode)) + Some(EvexRounding::from_raw(self.inner.RoundingMode).unwrap()) } else { None } @@ -1357,55 +1436,55 @@ impl DecodedInstruction { /// Get the instruction category. /// - /// # Errors + /// # Panics /// - /// This function can return an error if the category of the instruction is not known. This is most likely a bug - /// in the current implementation. + /// This function will panic if the cateogory not recognized. This can not happen under normal circumstances. #[inline] - pub fn category(self) -> Result { - Category::try_from(self.inner.Category) + pub fn category(&self) -> Category { + Category::try_from(self.inner.Category).unwrap() } /// Get the ISA set. /// - /// # Errors + /// # Panics /// - /// This function can return an error if the ISA set of the instruction is not known. This is most likely a bug - /// in the current implementation. + /// This function will panic if the ISA set not recognized. This can not happen under normal circumstances. #[inline] - pub fn isa_set(self) -> Result { - IsaSet::try_from(self.inner.IsaSet) + pub fn isa_set(&self) -> IsaSet { + IsaSet::try_from(self.inner.IsaSet).unwrap() } /// Get the CPU modes in which the instruction is valid. /// - /// See [cpu_modes](crate::cpu_modes) for examples. + /// # Examples + /// + /// See [`cpu_modes`](crate::cpu_modes) for examples. #[inline] - pub fn valid_cpu_modes(self) -> CpuModes { - CpuModes::from(self.inner.ValidModes) + pub fn valid_cpu_modes(&self) -> CpuModes { + CpuModes::from_raw(self.inner.ValidModes) } /// Get the valid prefixes for this instruction. #[inline] - pub fn valid_prefixes(self) -> ValidPrefixes { - ValidPrefixes::from(self.inner.ValidPrefixes) + pub fn valid_prefixes(&self) -> ValidPrefixes { + ValidPrefixes::from_raw(self.inner.ValidPrefixes) } /// Get the decorators accepted by the instruction. #[inline] - pub fn valid_decorators(self) -> ValidDecorators { - ValidDecorators::from(self.inner.ValidDecorators) + pub fn valid_decorators(&self) -> ValidDecorators { + ValidDecorators::from_raw(self.inner.ValidDecorators) } /// Get the main/nominal opcode. #[inline] - pub fn primary_op_code(self) -> u8 { + pub fn primary_op_code(&self) -> u8 { self.inner.PrimaryOpCode } /// `true` if the instruction is a SIMD instruction that operates on vector regs. #[inline] - pub fn has_vector(self) -> bool { + pub fn has_vector(&self) -> bool { self.inner.Attributes & ffi::ND_FLAG_VECTOR != 0 } } @@ -1422,22 +1501,40 @@ impl<'a> DecodedInstruction { pub fn op_code_bytes(&'a self) -> &'a [u8] { &self.inner.OpCodeBytes[..self.op_length()] } + + /// Get an operand lookup table. + /// + /// This can be useful when needing to work with a specific operand without needing to iterate over the operands + /// returned by [operands()](DecodedInstruction::operands), and without needing to rely on the order of the + /// operands. + /// + /// # Examples + /// + /// See [`OperandsLookup`](OperandsLookup) for examples. + /// + /// # Panics + /// + /// This function will panic if the result of the C library is unrecognized. This can not happen under normal + /// circumstances. + pub fn operand_lookup(&'a self) -> OperandsLookup { + OperandsLookup::from_raw(&self.inner) + } } impl fmt::Display for DecodedInstruction { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - let mut buffer: [i8; ffi::ND_MIN_BUF_SIZE as usize] = [0; ffi::ND_MIN_BUF_SIZE as usize]; + let mut buffer: [u8; ffi::ND_MIN_BUF_SIZE as usize] = [0; ffi::ND_MIN_BUF_SIZE as usize]; let status = unsafe { ffi::NdToText( &self.inner, self.ip, buffer.len() as u32, - buffer.as_mut_ptr(), + buffer.as_mut_ptr().cast::(), ) }; - match decode_error::status_to_error(status) { + match status_to_error(status) { Ok(_) => { - let text = String::from_utf8(buffer.iter().map(|&c| c as u8).collect()).unwrap(); + let text = core::str::from_utf8(&buffer).unwrap(); write!(f, "{}", text.trim_matches(char::from(0))) } Err(_) => Err(fmt::Error), @@ -1495,44 +1592,87 @@ mod tests { let mut evex_rounding: u8 = 0; for line in bindings.lines() { if line.starts_with("#define ND_ENCM_") { - EncodingMode::from(get_tokens(line, 2)); + assert!(EncodingMode::from_raw(get_tokens(line, 2)).is_ok()); } else if line.starts_with("#define ND_VEXM_") { - VexMode::from(get_tokens(line, 2)); + assert!(VexMode::from_raw(get_tokens(line, 2)).is_ok()); } else if line.starts_with("#define ND_ADDR_") { - AddressingMode::from(get_tokens(line, 2)); + assert!(AddressingMode::from_raw(get_tokens(line, 2)).is_ok()); } else if line.starts_with("#define ND_OPSZ_") { - OperandSize::from(get_tokens(line, 2)); + assert!(OperandSize::from_raw(get_tokens(line, 2)).is_ok()); } else if line.starts_with("#define ND_VECM_") { - VectorSize::from(get_tokens(line, 2)); + assert!(VectorSize::from_raw(get_tokens(line, 2)).is_ok()); } else if line.starts_with(" ND_EXC_") { - ExceptionClass::from(exc_count); + assert!(ExceptionClass::from_raw(exc_count).is_ok()); exc_count += 1; } else if line.starts_with("#define ND_SIZE_") && !line.starts_with("#define ND_SIZE_TO_MASK(sz)") { // TODO: this test should be in `operand.rs`, but since we are already parsing `bddisasm.h` here it // felt wastefull to also parse it there. - operand::OpSize::from(get_tokens(line, 2)); + assert!(operand::OpSize::from_raw(get_tokens(line, 2)).is_ok()); } else if line.starts_with(" ND_SHSTK_") { // TODO: this test should be in `operand.rs`, but since we are already parsing `bddisasm.h` here it // felt wastefull to also parse it there. - operand::ShadowStackAccess::from(shadow_stack_count); + assert!(operand::ShadowStackAccess::from_raw(shadow_stack_count).is_ok()); shadow_stack_count += 1; } else if line.starts_with("#define ND_FPU_FLAG_") { // TODO: this test should be in `fpu_flags.rs`, but since we are already parsing `bddisasm.h` here it // felt wastefull to also parse it there. - crate::fpu_flags::FpuFlagsAccess::from(get_tokens(line, 2) as u8); + assert!( + crate::fpu_flags::FpuFlagsAccess::from_raw(get_tokens(line, 2) as u8).is_ok() + ); } else if line.starts_with(" ND_TUPLE_") { // TODO: this test should be in `operand.rs`, but since we are already parsing `bddisasm.h` here it // felt wastefull to also parse it there. - Tuple::from(tuple_count); + assert!(Tuple::from_raw(tuple_count).is_ok()); tuple_count += 1; } else if line.starts_with(" ND_RND_") { // TODO: this test should be in `operand.rs`, but since we are already parsing `bddisasm.h` here it // felt wastefull to also parse it there. - EvexRounding::from(evex_rounding); + assert!(EvexRounding::from_raw(evex_rounding).is_ok()); evex_rounding += 1; } } } + + #[test] + fn status() { + let status = include_str!("../../bddisasm-sys/csrc/inc/disasmstatus.h"); + for line in status.lines() { + if line.starts_with("#define ND_STATUS_SUCCESS") + || line.starts_with("#define ND_STATUS_HINT_OPERAND_NOT_USED") + { + assert!(status_to_error(get_tokens(line, 2)).is_ok()); + } else if line.starts_with("#define ND_STATUS_") { + assert!(status_to_error(get_tokens(line, 2)).is_err()); + } + } + } + + #[test] + fn check_all_exception_classes() { + // This is a really contrieved way of making sure that we check all variants of `ffi::_ND_EX_CLASS`. If a new + // one is added, this will fail to build. We do this because `ExceptionClass::from_raw` takes an `u8`. + // NOTE: When a new variant is added, `ExceptionClass::from_raw` must be updated. + match ffi::_ND_EX_CLASS::ND_EXC_None { + ffi::_ND_EX_CLASS::ND_EXC_None => {} + ffi::_ND_EX_CLASS::ND_EXC_SSE_AVX => {} + ffi::_ND_EX_CLASS::ND_EXC_EVEX => {} + ffi::_ND_EX_CLASS::ND_EXC_OPMASK => {} + ffi::_ND_EX_CLASS::ND_EXC_AMX => {} + } + } + + #[test] + fn check_all_evex_roundings() { + // This is a really contrieved way of making sure that we check all variants of `ffi::_ND_ROUNDING`. If a new + // one is added, this will fail to build. We do this vecause `EvexRounding::from_raw` takes an `u8`. + // NOTE: When a new variant is added, `EvexRounding::from_raw` must be updated. + match ffi::_ND_ROUNDING::ND_RND_RNE { + ffi::_ND_ROUNDING::ND_RND_RNE => {} + ffi::_ND_ROUNDING::ND_RND_RD => {} + ffi::_ND_ROUNDING::ND_RND_RU => {} + ffi::_ND_ROUNDING::ND_RND_RZ => {} + } + } } diff --git a/bindings/rsbddisasm/bddisasm/src/decoder.rs b/bindings/rsbddisasm/bddisasm/src/decoder.rs index 7f9a588..b6184a2 100644 --- a/bindings/rsbddisasm/bddisasm/src/decoder.rs +++ b/bindings/rsbddisasm/bddisasm/src/decoder.rs @@ -4,14 +4,10 @@ */ //! 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; +use crate::decoded_instruction::{DecodeMode, DecodeResult, DecodedInstruction}; /// Decodes instructions. -#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)] +#[derive(Clone, Eq, PartialEq, Hash, Debug)] pub struct Decoder<'a> { code: &'a [u8], ip: u64, @@ -61,10 +57,10 @@ impl<'a> Decoder<'a> { /// # Examples /// /// ``` - /// # use std::error::Error; + /// # use bddisasm::DecodeError; /// # - /// # fn main() -> Result<(), Box> { - /// use bddisasm::decoder::{Decoder, DecodeMode}; + /// # fn main() -> Result<(), DecodeError> { + /// use bddisasm::{Decoder, DecodeMode}; /// /// let mut decoder = Decoder::new(&[0x51, 0x53], DecodeMode::Bits32, 0); /// @@ -103,16 +99,16 @@ impl<'a> Decoder<'a> { /// 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. + /// 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; + /// # use bddisasm::DecodeError; /// # - /// # fn main() -> Result<(), Box> { - /// use bddisasm::decoder::{Decoder, DecodeMode}; + /// # fn main() -> Result<(), DecodeError> { + /// use bddisasm::{Decoder, DecodeMode}; /// /// let mut decoder = Decoder::new(&[0x51, 0x53], DecodeMode::Bits32, 0x1000); /// @@ -128,12 +124,81 @@ impl<'a> Decoder<'a> { /// # Ok(()) /// # } /// ``` + #[inline] 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)) } + + /// 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. + /// + /// # Examples + /// + /// ``` + /// # use bddisasm::DecodeError; + /// # + /// # fn main() -> Result<(), DecodeError> { + /// use bddisasm::{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)) = decoder.decode_next_with_offset() { + /// // Check if the decoding succeeded + /// match result { + /// Ok(instruction) => println!("{} at offset {:#x}", instruction, offset), + /// Err(e) => println!("Unable to decode at offset {:#x}: {}", offset, e), + /// } + /// } + /// + /// # Ok(()) + /// # } + /// ``` + #[inline] + pub fn decode_next_with_offset(&mut self) -> Option<(DecodeResult, usize)> { + let offset = self.offset; + + self.decode_next().map(|res| (res, offset)) + } + + /// 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 corresponding instruction pointer. + /// + /// # Examples + /// + /// ``` + /// # use bddisasm::DecodeError; + /// # + /// # fn main() -> Result<(), DecodeError> { + /// use bddisasm::{Decoder, DecodeMode}; + /// + /// let mut decoder = Decoder::new(&[0x51, 0x53], DecodeMode::Bits32, 0x1000); + /// + /// // As long as we have something to decode + /// while let Some((result, ip)) = decoder.decode_next_with_ip() { + /// // Check if the decoding succeeded + /// match result { + /// Ok(instruction) => println!("{:#x} {}", ip, instruction), + /// Err(e) => println!("Unable to decode: {}", e), + /// } + /// } + /// + /// # Ok(()) + /// # } + /// ``` + #[inline] + pub fn decode_next_with_ip(&mut self) -> Option<(DecodeResult, u64)> { + let ip = self.ip; + + self.decode_next().map(|res| (res, ip)) + } } impl Iterator for Decoder<'_> { @@ -145,11 +210,12 @@ impl Iterator for Decoder<'_> { } } -impl std::iter::FusedIterator for Decoder<'_> {} +impl core::iter::FusedIterator for Decoder<'_> {} #[cfg(test)] mod tests { use super::*; + use crate::*; #[test] fn decode_next() { diff --git a/bindings/rsbddisasm/bddisasm/src/fpu_flags.rs b/bindings/rsbddisasm/bddisasm/src/fpu_flags.rs index aa16cae..564b25d 100644 --- a/bindings/rsbddisasm/bddisasm/src/fpu_flags.rs +++ b/bindings/rsbddisasm/bddisasm/src/fpu_flags.rs @@ -4,7 +4,7 @@ */ //! Offers information about how an instructions accesses the FPU status registers. -extern crate bddisasm_sys as ffi; +use super::decode_error::DecodeError; /// The mode in which a FPU status flag is accessed. #[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)] @@ -19,15 +19,16 @@ pub enum FpuFlagsAccess { Undefined, } -impl From for FpuFlagsAccess { - fn from(value: u8) -> FpuFlagsAccess { - let value = value as u32; +#[doc(hidden)] +impl FpuFlagsAccess { + pub(crate) fn from_raw(value: u8) -> Result { + let value = u32::from(value); 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), + ffi::ND_FPU_FLAG_SET_0 => Ok(FpuFlagsAccess::Cleared), + ffi::ND_FPU_FLAG_SET_1 => Ok(FpuFlagsAccess::Set), + ffi::ND_FPU_FLAG_MODIFIED => Ok(FpuFlagsAccess::Modified), + ffi::ND_FPU_FLAG_UNDEFINED => Ok(FpuFlagsAccess::Undefined), + _ => Err(DecodeError::InternalError(value.into())), } } } @@ -46,13 +47,13 @@ pub struct FpuFlags { } #[doc(hidden)] -impl From 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()), - } +impl FpuFlags { + pub(crate) fn from_raw(flags: ffi::ND_FPU_FLAGS) -> Result { + Ok(Self { + c0: FpuFlagsAccess::from_raw(flags.C0())?, + c1: FpuFlagsAccess::from_raw(flags.C1())?, + c2: FpuFlagsAccess::from_raw(flags.C2())?, + c3: FpuFlagsAccess::from_raw(flags.C3())?, + }) } } diff --git a/bindings/rsbddisasm/bddisasm/src/instruction_category.rs b/bindings/rsbddisasm/bddisasm/src/instruction_category.rs index fef223c..6121941 100644 --- a/bindings/rsbddisasm/bddisasm/src/instruction_category.rs +++ b/bindings/rsbddisasm/bddisasm/src/instruction_category.rs @@ -4,10 +4,8 @@ */ //! Instruction categories. -extern crate bddisasm_sys as ffi; - -use super::decode_error; -use std::convert::TryFrom; +use super::decode_error::DecodeError; +use core::convert::TryFrom; /// Instruction category. #[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)] @@ -120,13 +118,11 @@ pub enum Category { #[doc(hidden)] impl TryFrom for Category { - type Error = decode_error::DecodeError; + type Error = DecodeError; fn try_from(value: ffi::ND_INS_CATEGORY) -> Result { match value { - ffi::_ND_INS_TYPE::ND_CAT_INVALID => { - Err(decode_error::DecodeError::UnknownInstruction(value as u32)) - } + ffi::_ND_INS_TYPE::ND_CAT_INVALID => Err(DecodeError::InternalError(value as u64)), 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), diff --git a/bindings/rsbddisasm/bddisasm/src/isa_set.rs b/bindings/rsbddisasm/bddisasm/src/isa_set.rs index 1861284..2fc031d 100644 --- a/bindings/rsbddisasm/bddisasm/src/isa_set.rs +++ b/bindings/rsbddisasm/bddisasm/src/isa_set.rs @@ -4,10 +4,8 @@ */ //! Instruction sets. -extern crate bddisasm_sys as ffi; - -use super::decode_error; -use std::convert::TryFrom; +use super::decode_error::DecodeError; +use core::convert::TryFrom; /// ISA set. #[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)] @@ -133,13 +131,11 @@ pub enum IsaSet { #[doc(hidden)] impl TryFrom for IsaSet { - type Error = decode_error::DecodeError; + type Error = DecodeError; fn try_from(value: ffi::ND_INS_SET) -> Result { match value { - ffi::_ND_INS_SET::ND_SET_INVALID => { - Err(decode_error::DecodeError::UnknownInstruction(value as u32)) - } + ffi::_ND_INS_SET::ND_SET_INVALID => Err(DecodeError::InternalError(value as u64)), 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), diff --git a/bindings/rsbddisasm/bddisasm/src/lib.rs b/bindings/rsbddisasm/bddisasm/src/lib.rs index a5cb822..e37e88a 100644 --- a/bindings/rsbddisasm/bddisasm/src/lib.rs +++ b/bindings/rsbddisasm/bddisasm/src/lib.rs @@ -4,12 +4,14 @@ */ //! bddisasm x86/x64 instruction decoder //! -//! Rust bindings for the [bddisasm](https://github.com/bitdefender/bddisasm) x86/x64 decoder library, built on top -//! of [bddisasm-sys](https://crates.io/crates/bddisasm-sys). +//! `no_std` Rust bindings for the [bddisasm](https://github.com/bitdefender/bddisasm) x86/x64 decoder library, built +//! on top of [bddisasm-sys](https://crates.io/crates/bddisasm-sys). //! -//! It supports all existing x86 instruction, offering a wide range of information about each one, including: +//! It supports all existing 16-bit, 32-bit and 64-bit instruction, offering a wide range of information about each one, +//! including: //! -//! - operands (implicit and explicit) +//! - implicit operands +//! - explicit operands //! - access mode for each operand //! - CPUID feature flags //! - CPU modes in which an instruction is valid @@ -27,11 +29,11 @@ //! //! ## Decoding one instruction //! -//! Use [DecodedInstruction::decode](crate::decoded_instruction::DecodedInstruction::decode) to decode an 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}; +//! use bddisasm::{DecodedInstruction, DecodeMode, Mnemonic}; //! //! let code = vec![0x31, 0xc0]; //! match DecodedInstruction::decode(&code, DecodeMode::Bits32) { @@ -45,10 +47,10 @@ //! //! ## Decoding multiple instructions //! -//! Use [Decoder](crate::decoder::Decoder) to decode multiple instructions from a chunk of code. +//! Use [`Decoder`](crate::decoder::Decoder) to decode multiple instructions from a chunk of code. //! //! ``` -//! use bddisasm::decoder::{Decoder, DecodeMode}; +//! use bddisasm::{Decoder, DecodeMode}; //! //! let code = [ //! // ENCLS @@ -79,11 +81,11 @@ //! WRMSR //! ``` //! -//! Use [Decoder::decode_next_with_info](crate::decoder::Decoder::decode_next_with_info) to get information about the +//! 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}; +//! use bddisasm::{Decoder, DecodeMode}; //! //! let code = [ //! // ENCLS @@ -122,10 +124,9 @@ //! each type of operand. Bellow is a minimal example that looks at a memory operand. //! //! ``` -//! # use bddisasm::decode_error::DecodeError; +//! # use bddisasm::DecodeError; //! # fn test() -> Result<(), DecodeError> { -//! use bddisasm::decoded_instruction::{DecodedInstruction, DecodeMode}; -//! use bddisasm::operand::OpInfo; +//! use bddisasm::{DecodedInstruction, DecodeMode, OpInfo}; //! //! // ` MOV rax, qword ptr [rcx+r15*2]` //! let code = b"\x4a\x8b\x04\x79"; @@ -177,6 +178,21 @@ //! Scale: 2 //! No displacement //! ``` +//! +//! ## Accessing the raw bindings +//! +//! The raw `bddisasm_sys` bindings are available via the `ffi` re-export. +//! +//! # Feature Flags +//! +//! - `std` - adds a `std` dependency - the only visible difference when doing this is that [`DecodeError`] implements +//! the `Error` trait +//! + +#![cfg_attr(all(not(test), not(feature = "std")), no_std)] + +pub extern crate bddisasm_sys as ffi; + pub mod cpu_modes; pub mod cpuid; pub mod decode_error; @@ -189,3 +205,13 @@ pub mod mnemonic; pub mod operand; pub mod rflags; pub mod tuple; + +pub use crate::decode_error::DecodeError; +pub use crate::decoded_instruction::{DecodeMode, DecodeResult, DecodedInstruction, OperandSize}; +pub use crate::decoder::Decoder; +pub use crate::instruction_category::Category; +pub use crate::isa_set::IsaSet; +pub use crate::mnemonic::Mnemonic; +pub use crate::operand::{ + OpAddr, OpInfo, OpMem, OpReg, OpRegType, OpSize, Operand, OperandsLookup, +}; diff --git a/bindings/rsbddisasm/bddisasm/src/mnemonic.rs b/bindings/rsbddisasm/bddisasm/src/mnemonic.rs index 15836a3..448043c 100644 --- a/bindings/rsbddisasm/bddisasm/src/mnemonic.rs +++ b/bindings/rsbddisasm/bddisasm/src/mnemonic.rs @@ -4,10 +4,8 @@ */ //! Mnemonics. -extern crate bddisasm_sys as ffi; - -use super::decode_error; -use std::convert::TryFrom; +use super::decode_error::DecodeError; +use core::convert::TryFrom; /// Uniquely identifies an instruction. #[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)] @@ -1601,13 +1599,11 @@ pub enum Mnemonic { #[doc(hidden)] impl TryFrom for Mnemonic { - type Error = decode_error::DecodeError; + type Error = DecodeError; fn try_from(value: ffi::ND_INS_CLASS) -> Result { match value { - ffi::_ND_INS_CLASS::ND_INS_INVALID => { - Err(decode_error::DecodeError::UnknownInstruction(value as u32)) - } + ffi::_ND_INS_CLASS::ND_INS_INVALID => Err(DecodeError::InternalError(value as u64)), ffi::_ND_INS_CLASS::ND_INS_AAA => Ok(Mnemonic::Aaa), ffi::_ND_INS_CLASS::ND_INS_AAD => Ok(Mnemonic::Aad), ffi::_ND_INS_CLASS::ND_INS_AAM => Ok(Mnemonic::Aam), diff --git a/bindings/rsbddisasm/bddisasm/src/operand.rs b/bindings/rsbddisasm/bddisasm/src/operand.rs index 524f903..b84d135 100644 --- a/bindings/rsbddisasm/bddisasm/src/operand.rs +++ b/bindings/rsbddisasm/bddisasm/src/operand.rs @@ -4,9 +4,10 @@ */ //! Operand types and details. -extern crate bddisasm_sys as ffi; - -use std::fmt; +use super::decode_error::{status_to_error, DecodeError}; +use core::fmt; +use core::marker::PhantomData; +use core::mem::MaybeUninit; /// Describes an address operand. #[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)] @@ -21,17 +22,14 @@ pub struct OpAddr { } #[doc(hidden)] -impl From for OpAddr { - fn from(op: ffi::ND_OPDESC_ADDRESS) -> OpAddr { - OpAddr { - base_seg: op.BaseSeg, - offset: op.Offset, +impl OpAddr { + pub(crate) fn from_raw(raw: ffi::ND_OPDESC_ADDRESS) -> Self { + Self { + base_seg: raw.BaseSeg, + offset: raw.Offset, } } -} -#[doc(hidden)] -impl OpAddr { pub(crate) fn new(base_seg: u16, offset: u64) -> Self { Self { base_seg, offset } } @@ -105,31 +103,33 @@ pub enum OpRegType { } #[doc(hidden)] -impl From 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 OpRegType { + pub(crate) fn from_raw(op_type: ffi::ND_REG_TYPE) -> Result { + match op_type { + ffi::_ND_REG_TYPE::ND_REG_NOT_PRESENT => { + Err(DecodeError::InternalError(op_type as u64)) + } + ffi::_ND_REG_TYPE::ND_REG_GPR => Ok(OpRegType::Gpr), + ffi::_ND_REG_TYPE::ND_REG_SEG => Ok(OpRegType::Seg), + ffi::_ND_REG_TYPE::ND_REG_FPU => Ok(OpRegType::Fpu), + ffi::_ND_REG_TYPE::ND_REG_MMX => Ok(OpRegType::Mmx), + ffi::_ND_REG_TYPE::ND_REG_SSE => Ok(OpRegType::Sse), + ffi::_ND_REG_TYPE::ND_REG_CR => Ok(OpRegType::Cr), + ffi::_ND_REG_TYPE::ND_REG_DR => Ok(OpRegType::Dr), + ffi::_ND_REG_TYPE::ND_REG_TR => Ok(OpRegType::Tr), + ffi::_ND_REG_TYPE::ND_REG_BND => Ok(OpRegType::Bnd), + ffi::_ND_REG_TYPE::ND_REG_MSK => Ok(OpRegType::Msk), + ffi::_ND_REG_TYPE::ND_REG_TILE => Ok(OpRegType::Tile), + ffi::_ND_REG_TYPE::ND_REG_MSR => Ok(OpRegType::Msr), + ffi::_ND_REG_TYPE::ND_REG_XCR => Ok(OpRegType::Xcr), + ffi::_ND_REG_TYPE::ND_REG_SYS => Ok(OpRegType::Sys), + ffi::_ND_REG_TYPE::ND_REG_X87 => Ok(OpRegType::X87), + ffi::_ND_REG_TYPE::ND_REG_MXCSR => Ok(OpRegType::Mxcsr), + ffi::_ND_REG_TYPE::ND_REG_PKRU => Ok(OpRegType::Pkru), + ffi::_ND_REG_TYPE::ND_REG_SSP => Ok(OpRegType::Ssp), + ffi::_ND_REG_TYPE::ND_REG_FLG => Ok(OpRegType::Flg), + ffi::_ND_REG_TYPE::ND_REG_RIP => Ok(OpRegType::Rip), + ffi::_ND_REG_TYPE::ND_REG_UIF => Ok(OpRegType::Uif), } } } @@ -203,30 +203,30 @@ pub struct OpReg { } #[doc(hidden)] -impl From for OpReg { - fn from(op: ffi::ND_OPDESC_REGISTER) -> OpReg { - let kind = OpRegType::from(op); - let is_high8 = op.IsHigh8(); +impl OpReg { + pub(crate) fn from_raw(raw: ffi::ND_OPDESC_REGISTER) -> Result { + let kind = OpRegType::from_raw(raw.Type)?; + let is_high8 = raw.IsHigh8() != 0; let index = match kind { OpRegType::Gpr => { if is_high8 { // See `ShemuGetGprValue` in `bdshemu.c`. - op.Reg - 4 + raw.Reg - 4 } else { - op.Reg + raw.Reg } } - _ => op.Reg, + _ => raw.Reg, } as usize; - OpReg { + Ok(Self { kind, - size: op.Size, + size: raw.Size, index, - count: op.Count, + count: raw.Count, is_high8, - is_block: op.IsBlock(), - } + is_block: raw.IsBlock() != 0, + }) } } @@ -259,34 +259,21 @@ pub enum ShadowStackAccess { } #[doc(hidden)] -impl From 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` implementation! - // TODO: any way of keeping these in sync automagically? - } - } -} - -impl From 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 +impl ShadowStackAccess { + pub(crate) fn from_raw(value: u8) -> Result { + let acc = u32::from(value); + if acc == ffi::_ND_SHSTK_ACCESS::ND_SHSTK_NONE as u32 { + Ok(ShadowStackAccess::None) + } else if acc == ffi::_ND_SHSTK_ACCESS::ND_SHSTK_EXPLICIT as u32 { + Ok(ShadowStackAccess::Explicit) + } else if acc == ffi::_ND_SHSTK_ACCESS::ND_SHSTK_SSP_LD_ST as u32 { + Ok(ShadowStackAccess::SspLdSt) + } else if acc == ffi::_ND_SHSTK_ACCESS::ND_SHSTK_SSP_PUSH_POP as u32 { + Ok(ShadowStackAccess::SspPushPop) + } else if acc == ffi::_ND_SHSTK_ACCESS::ND_SHSTK_PL0_SSP as u32 { + Ok(ShadowStackAccess::Pl0Ssp) } else { - panic!("Unexpected shadow stack access type: {}", acc) + Err(DecodeError::InternalError(value.into())) } } } @@ -363,62 +350,66 @@ pub struct OpMem { } #[doc(hidden)] -impl From for OpMem { - fn from(op: ffi::ND_OPDESC_MEMORY) -> OpMem { - let seg = if op.HasSeg() { Some(op.Seg) } else { None }; +impl OpMem { + pub(crate) fn from_raw(raw: ffi::ND_OPDESC_MEMORY) -> Result { + let seg = if raw.HasSeg() != 0 { + Some(raw.Seg) + } else { + None + }; - let (base, base_size) = if op.HasBase() { - (Some(op.Base), Some(op.BaseSize)) + let (base, base_size) = if raw.HasBase() != 0 { + (Some(raw.Base), Some(raw.BaseSize)) } else { (None, None) }; - let (index, index_size, scale) = if op.HasIndex() { - (Some(op.Index), Some(op.IndexSize), Some(op.Scale)) + let (index, index_size, scale) = if raw.HasIndex() != 0 { + (Some(raw.Index), Some(raw.IndexSize), Some(raw.Scale)) } else { (None, None, None) }; - let (disp, disp_size) = if op.HasDisp() { - (Some(op.Disp), Some(op.DispSize)) + let (disp, disp_size) = if raw.HasDisp() != 0 { + (Some(raw.Disp), Some(raw.DispSize)) } else { (None, None) }; - let comp_disp_size = if op.HasCompDisp() { - Some(op.CompDispSize) + let comp_disp_size = if raw.HasCompDisp() != 0 { + Some(raw.CompDispSize) } else { None }; - let (vsib, index_size) = if op.IsVsib() { + let (vsib, index_size) = if raw.IsVsib() != 0 { ( Some(Vsib { - vsib_element_size: op.Vsib.ElemSize, - vsib_element_count: op.Vsib.ElemCount, + vsib_element_size: raw.Vsib.ElemSize, + vsib_element_count: raw.Vsib.ElemCount, }), - Some(op.Vsib.IndexSize as u32), + Some(raw.Vsib.IndexSize.into()), ) } else { (None, index_size) }; - let shadow_stack_access = if op.IsShadowStack() { - Some(ShadowStackAccess::from(op.ShStkType)) + let shadow_stack_access = if raw.IsShadowStack() != 0 { + Some(ShadowStackAccess::from_raw(raw.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(), + Ok(Self { + has_broadcast: raw.HasBroadcast() != 0, + is_rip_rel: raw.IsRipRel() != 0, + is_stack: raw.IsStack() != 0, + is_string: raw.IsString() != 0, + is_direct: raw.IsDirect() != 0, + is_bitbase: raw.IsBitbase() != 0, + is_ag: raw.IsAG() != 0, + is_mib: raw.IsMib() != 0, + is_sib_mem: raw.IsSibMem() != 0, seg, base, base_size, @@ -430,7 +421,7 @@ impl From for OpMem { disp_size, comp_disp_size, shadow_stack_access, - } + }) } } @@ -462,23 +453,120 @@ pub enum OpInfo { } #[doc(hidden)] -impl From for OpInfo { - fn from(op: ffi::ND_OPERAND) -> OpInfo { - match op.Type { - ffi::_ND_OPERAND_TYPE::ND_OP_NOT_PRESENT => OpInfo::None, +impl Default for OpInfo { + fn default() -> Self { + Self::None + } +} + +impl OpInfo { + /// Returns the associated [OpReg](OpReg) for register operands. Returns [`None`] otherwise. + pub fn as_reg(&self) -> Option<&OpReg> { + if let OpInfo::Reg(o) = self { + Some(o) + } else { + None + } + } + + /// Returns the associated [OpMem](OpMem) for memory operands. Returns [`None`] otherwise. + pub fn as_mem(&self) -> Option<&OpMem> { + if let OpInfo::Mem(o) = self { + Some(o) + } else { + None + } + } + + /// Returns the associated immediate value for immediate operands. Returns [`None`] otherwise. + pub fn as_imm(&self) -> Option { + if let OpInfo::Imm(o) = self { + Some(*o) + } else { + None + } + } + + /// Returns the associated [OpAddr](OpAddr) for absolute address operands. Returns [`None`] otherwise. + pub fn as_addr(&self) -> Option<&OpAddr> { + if let OpInfo::Addr(o) = self { + Some(o) + } else { + None + } + } + + /// Returns the associated constant value for constant operands. Returns [`None`] otherwise. + pub fn as_const(&self) -> Option { + if let OpInfo::Const(o) = self { + Some(*o) + } else { + None + } + } + + /// Returns `Some` for bank operands. Returns [`None`] otherwise. + pub fn as_bank(&self) -> Option<()> { + if let OpInfo::Bank = self { + Some(()) + } else { + None + } + } + + /// Returns `true` for register operands. Returns `false` otherwise. + pub fn is_reg(&self) -> bool { + self.as_reg().is_some() + } + + /// Returns `true` for memory operands. Returns `false` otherwise. + pub fn is_mem(&self) -> bool { + self.as_mem().is_some() + } + + /// Returns `true` for immediate operands. Returns `false` otherwise. + pub fn is_imm(&self) -> bool { + self.as_imm().is_some() + } + + /// Returns `true` for absolute address operands. Returns `false` otherwise. + pub fn is_addr(&self) -> bool { + self.as_addr().is_some() + } + + /// Returns `true` for constant operands. Returns `false` otherwise. + pub fn is_const(&self) -> bool { + self.as_const().is_some() + } + + /// Returns `true` for bank operands. Returns `false` otherwise. + pub fn is_bank(&self) -> bool { + self.as_bank().is_some() + } +} + +#[doc(hidden)] +impl OpInfo { + pub(crate) fn from_raw(raw: ffi::ND_OPERAND) -> Result { + match raw.Type { + ffi::_ND_OPERAND_TYPE::ND_OP_NOT_PRESENT => Ok(OpInfo::None), ffi::_ND_OPERAND_TYPE::ND_OP_REG => { - OpInfo::Reg(OpReg::from(unsafe { op.Info.Register })) + Ok(OpInfo::Reg(OpReg::from_raw(unsafe { raw.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_MEM => { + Ok(OpInfo::Mem(OpMem::from_raw(unsafe { raw.Info.Memory })?)) + } + ffi::_ND_OPERAND_TYPE::ND_OP_IMM => Ok(OpInfo::Imm(unsafe { raw.Info.Immediate }.Imm)), ffi::_ND_OPERAND_TYPE::ND_OP_OFFS => { - OpInfo::Offs(unsafe { op.Info.RelativeOffset }.Rel) + Ok(OpInfo::Offs(unsafe { raw.Info.RelativeOffset }.Rel)) } ffi::_ND_OPERAND_TYPE::ND_OP_ADDR => { - OpInfo::Addr(OpAddr::from(unsafe { op.Info.Address })) + Ok(OpInfo::Addr(OpAddr::from_raw(unsafe { raw.Info.Address }))) + } + ffi::_ND_OPERAND_TYPE::ND_OP_CONST => { + Ok(OpInfo::Const(unsafe { raw.Info.Constant }.Const)) } - ffi::_ND_OPERAND_TYPE::ND_OP_CONST => OpInfo::Const(unsafe { op.Info.Constant }.Const), - ffi::_ND_OPERAND_TYPE::ND_OP_BANK => OpInfo::Bank, + ffi::_ND_OPERAND_TYPE::ND_OP_BANK => Ok(OpInfo::Bank), } } } @@ -511,6 +599,13 @@ pub enum OpSize { Unknown, } +#[doc(hidden)] +impl Default for OpSize { + fn default() -> Self { + Self::Unknown + } +} + impl fmt::Display for OpSize { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { @@ -522,34 +617,34 @@ impl fmt::Display for OpSize { } #[doc(hidden)] -impl From 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), +impl OpSize { + pub(crate) fn from_raw(value: ffi::ND_OPERAND_SIZE) -> Result { + match value { + ffi::ND_SIZE_8BIT => Ok(OpSize::Bytes(1)), + ffi::ND_SIZE_16BIT => Ok(OpSize::Bytes(2)), + ffi::ND_SIZE_32BIT => Ok(OpSize::Bytes(4)), + ffi::ND_SIZE_48BIT => Ok(OpSize::Bytes(6)), + ffi::ND_SIZE_64BIT => Ok(OpSize::Bytes(8)), + ffi::ND_SIZE_80BIT => Ok(OpSize::Bytes(10)), + ffi::ND_SIZE_112BIT => Ok(OpSize::Bytes(14)), + ffi::ND_SIZE_128BIT => Ok(OpSize::Bytes(16)), + ffi::ND_SIZE_224BIT => Ok(OpSize::Bytes(28)), + ffi::ND_SIZE_256BIT => Ok(OpSize::Bytes(32)), + ffi::ND_SIZE_384BIT => Ok(OpSize::Bytes(48)), + ffi::ND_SIZE_512BIT => Ok(OpSize::Bytes(64)), + ffi::ND_SIZE_752BIT => Ok(OpSize::Bytes(94)), + ffi::ND_SIZE_864BIT => Ok(OpSize::Bytes(108)), + ffi::ND_SIZE_4096BIT => Ok(OpSize::Bytes(512)), + ffi::ND_SIZE_1KB => Ok(OpSize::Bytes(1024)), + ffi::ND_SIZE_CACHE_LINE => Ok(OpSize::CacheLine), + ffi::ND_SIZE_UNKNOWN => Ok(OpSize::Unknown), + _ => Err(DecodeError::InternalError(value.into())), } } } /// Operand access mode. -#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)] +#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug, Default)] pub struct OpAccess { /// The operand is read. pub read: bool, @@ -568,11 +663,11 @@ pub struct OpAccess { } #[doc(hidden)] -impl From for OpAccess { - fn from(acc: ffi::ND_OPERAND_ACCESS) -> OpAccess { - let acc = unsafe { acc.__bindgen_anon_1 }; +impl OpAccess { + pub(crate) fn from_raw(raw: ffi::ND_OPERAND_ACCESS) -> Self { + let acc = unsafe { raw.__bindgen_anon_1 }; - OpAccess { + Self { read: acc.Read() != 0, write: acc.Write() != 0, cond_read: acc.CondRead() != 0, @@ -593,7 +688,7 @@ pub struct Broadcast { } /// Decorator information. -#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)] +#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug, Default)] pub struct Decorator { /// If mask is present, holds the ID of the mask register (`K0` - `K7`) used. pub mask_register: Option, @@ -606,26 +701,26 @@ pub struct Decorator { } #[doc(hidden)] -impl From for Decorator { - fn from(decorator: ffi::ND_OPERAND_DECORATOR) -> Decorator { - let mask_register = if decorator.HasMask() { - Some(decorator.Mask.Msk) +impl Decorator { + pub(crate) fn from_raw(raw: ffi::ND_OPERAND_DECORATOR) -> Decorator { + let mask_register = if raw.HasMask() != 0 { + Some(raw.Mask.Msk) } else { None }; - let broadcast = if decorator.HasBroadcast() { + let broadcast = if raw.HasBroadcast() != 0 { Some(Broadcast { - count: decorator.Broadcast.Count, - size: decorator.Broadcast.Size, + count: raw.Broadcast.Count, + size: raw.Broadcast.Size, }) } else { None }; - Decorator { + Self { mask_register, - has_zero: decorator.HasZero(), + has_zero: raw.HasZero() != 0, broadcast, } } @@ -638,11 +733,10 @@ impl From for Decorator { /// # Examples /// /// ``` -/// # use std::error::Error; +/// # use bddisasm::DecodeError; /// # -/// # fn main() -> Result<(), Box> { -/// use bddisasm::decoder::{DecodedInstruction, DecodeMode}; -/// use bddisasm::operand::*; +/// # fn main() -> Result<(), DecodeError> { +/// use bddisasm::{DecodedInstruction, DecodeMode, OpRegType, OpSize}; /// /// // `MOV ah, byte ptr [rcx+rdx*2+0x8]` /// let code = vec![0x8a, 0x64, 0x51, 0x08]; @@ -652,23 +746,19 @@ impl From for Decorator { /// let dst = operands[0]; /// let src = operands[1]; /// -/// // Get the size of each operand -/// println!("Destination size: {}", dst.size); -/// println!("Source size: {}", src.size); +/// // Check the size of each operand +/// assert_eq!(dst.size, OpSize::Bytes(1)); +/// assert_eq!(src.size, OpSize::Bytes(1)); /// -/// // 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"), -/// } +/// // Check the type of the destination operand +/// assert!(dst.info.is_reg()); +/// let dst_reg = dst.info.as_reg().unwrap(); +/// assert_eq!(dst_reg.kind, OpRegType::Gpr); /// /// # Ok(()) /// # } /// ``` -#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)] +#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug, Default)] pub struct Operand { /// Extended operand information. pub info: OpInfo, @@ -704,17 +794,460 @@ pub struct Operand { } #[doc(hidden)] -impl From 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), +impl Operand { + pub(crate) fn from_raw(raw: ffi::ND_OPERAND) -> Result { + Ok(Self { + info: OpInfo::from_raw(raw)?, + size: OpSize::from_raw(raw.Size)?, + raw_size: OpSize::from_raw(raw.RawSize)?, + access: OpAccess::from_raw(raw.Access), + is_default: unsafe { raw.Flags.__bindgen_anon_1 }.IsDefault() != 0, + decorator: Decorator::from_raw(raw.Decorator), + }) + } +} + +/// Operands lookup table. +/// +/// This can be useful when needing to work with a specific operand without needing to iterate over the operands +/// returned by [operands()](crate::decoded_instruction::DecodedInstruction::operands), and without needing to rely on +/// the order of the operands. +/// +/// # Examples +/// +/// ```rust +/// # use bddisasm::DecodeError; +/// # +/// # fn main() -> Result<(), DecodeError> { +/// use bddisasm::{DecodedInstruction, DecodeMode, OperandsLookup, OpRegType}; +/// +/// // `PUSH rbx` +/// let ins = DecodedInstruction::decode(b"\x53", DecodeMode::Bits64).unwrap(); +/// let operands = ins.operand_lookup(); +/// +/// // The first destination is the stack. +/// let first_destination = operands.dest(0); +/// assert!(first_destination.is_some()); +/// let first_destination = first_destination.unwrap(); +/// +/// // And it is the same as the first memory operand. +/// let first_mem = operands.mem(0); +/// assert!(first_mem.is_some()); +/// let first_mem = first_mem.unwrap(); +/// assert_eq!(first_destination, first_mem); +/// +/// // And the same as the stack operand. +/// let stack = operands.stack(); +/// assert!(stack.is_some()); +/// let stack = stack.unwrap(); +/// assert_eq!(first_destination, stack); +/// +/// assert!(first_destination.is_default); +/// assert!(first_destination.info.is_mem()); +/// assert!(first_destination.info.as_mem().unwrap().is_stack); +/// +/// // Although the source operand is the RBX register, it is not one of the default operands. +/// let rbx = operands.rbx(); +/// assert!(rbx.is_none()); +/// +/// // There is only one destination operand. +/// let second_destination = operands.dest(1); +/// assert!(second_destination.is_none()); +/// +/// // The first source is RBX. +/// let first_source = operands.src(0); +/// assert!(first_source.is_some()); +/// let first_source = first_source.unwrap(); +/// +/// assert_eq!(first_source.is_default, false); +/// assert!(first_source.info.is_reg()); +/// +/// let first_source = first_source.info.as_reg().unwrap(); +/// assert_eq!(first_source.kind, OpRegType::Gpr); +/// assert_eq!(first_source.index, 3); +/// +/// // There is only one source operand. +/// let second_source = operands.src(1); +/// assert!(second_source.is_none()); +/// +/// // There is no other memory operand. +/// let second_mem = operands.mem(1); +/// assert!(second_mem.is_none()); +/// +/// // The FLAGS register is not accessed. +/// let flags = operands.flags(); +/// assert!(flags.is_none()); +/// # Ok(()) +/// # } +/// ``` +#[derive(Clone, Debug)] +pub struct OperandsLookup<'a> { + // The C library fills a `ND_OPERAND_RLUT` structure with pointers to `ND_OPERAND` structures taken from the + // `INSTRUX` structure. As such, the `INSTRUX` structure must live at least as long as the `ND_OPERAND_RLUT` + // structure. This expresses that life-time dependency. + _instruction: PhantomData<&'a ffi::INSTRUX>, + op_rlut: ffi::ND_OPERAND_RLUT, +} + +impl<'a> OperandsLookup<'a> { + #[doc(hidden)] + pub(crate) fn from_raw(instruction: &'a ffi::INSTRUX) -> Self { + let mut op_rlut: MaybeUninit = MaybeUninit::uninit(); + let op_rlut = op_rlut.as_mut_ptr(); + + let status = unsafe { + ffi::NdGetOperandRlut( + instruction as *const ffi::INSTRUX as *mut ffi::INSTRUX, + op_rlut, + ) + }; + + // It is ok to unwrap here because `NdGetOperandRlut` fails only if the pointers passed to it are `NULL`. + status_to_error(status).unwrap(); + let op_rlut = unsafe { *op_rlut }; + + Self { + _instruction: PhantomData, + op_rlut, } } + + /// Get one of the destination operands. + /// + /// Most instructions have just one destination operand, but some will have two. + /// + /// # Arguments + /// + /// * `index` - The index of the destination operand. First destination operand has index 0, the second one has + /// index 1, etc. + /// + /// # Returns + /// + /// * [Some(Operand)](Operand) if the requested destination operand exists. + /// * [`None`] if the requested destination operand does not exist. + /// + /// # Panics + /// + /// This function will panic if the result of the C library is unrecognized. This can not happen under normal + /// circumstances. + #[inline] + pub fn dest(&self, index: usize) -> Option { + let op = match index { + 0 => unsafe { self.op_rlut.Dst1.as_ref() }, + 1 => unsafe { self.op_rlut.Dst2.as_ref() }, + _ => None, + }; + + op.map(|op| Operand::from_raw(*op).unwrap()) + } + + /// Get one of the source operands. + /// + /// Most instructions have just one souce operand, but some can have up to 4. + /// + /// # Arguments + /// + /// * `index` - The index of the source operand. First source operand has index 0, the second one has + /// index 1, etc. + /// + /// # Returns + /// + /// * [Some(Operand)](Operand) if the requested source operand exists. + /// * [`None`] if the requested source operand does not exist. + /// + /// # Panics + /// + /// This function will panic if the result of the C library is unrecognized. This can not happen under normal + /// circumstances. + #[inline] + pub fn src(&self, index: usize) -> Option { + let op = match index { + 0 => unsafe { self.op_rlut.Src1.as_ref() }, + 1 => unsafe { self.op_rlut.Src2.as_ref() }, + 2 => unsafe { self.op_rlut.Src3.as_ref() }, + 3 => unsafe { self.op_rlut.Src4.as_ref() }, + _ => None, + }; + + op.map(|op| Operand::from_raw(*op).unwrap()) + } + + /// Get one of the memory operands. + /// + /// Most instructions have just one memory operand, but some will have two. + /// + /// # Arguments + /// + /// * `index` - The index of the memory operand. First memory operand has index 0, the second one has index 1, etc. + /// + /// # Returns + /// + /// * [Some(Operand)](Operand) if the requested memory operand exists. + /// * [`None`] if the requested memory operand does not exist. + /// + /// # Panics + /// + /// This function will panic if the result of the C library is unrecognized. This can not happen under normal + /// circumstances. + #[inline] + pub fn mem(&self, index: usize) -> Option { + let op = match index { + 0 => unsafe { self.op_rlut.Mem1.as_ref() }, + 1 => unsafe { self.op_rlut.Mem2.as_ref() }, + _ => None, + }; + + op.map(|op| Operand::from_raw(*op).unwrap()) + } + + /// Get the stack operand. + /// + /// # Returns + /// + /// * [Some(Operand)](Operand) if a stack operand exists. + /// * [`None`] if a stack operand does not exist. + /// + /// # Panics + /// + /// This function will panic if the result of the C library is unrecognized. This can not happen under normal + /// circumstances. + #[inline] + pub fn stack(&self) -> Option { + let op = unsafe { self.op_rlut.Stack.as_ref() }; + + op.map(|op| Operand::from_raw(*op).unwrap()) + } + + /// Get the default flags register operand. + /// + /// # Returns + /// + /// * [Some(Operand)](Operand) if a flags register operand exists. + /// * [`None`] if a flags register operand does not exist. + /// + /// # Panics + /// + /// This function will panic if the result of the C library is unrecognized. This can not happen under normal + /// circumstances. + #[inline] + pub fn flags(&self) -> Option { + let op = unsafe { self.op_rlut.Flags.as_ref() }; + + op.map(|op| Operand::from_raw(*op).unwrap()) + } + + /// Get the default RIP operand. + /// + /// # Returns + /// + /// * [Some(Operand)](Operand) if a RIP operand exists. + /// * [`None`] if a RIP operand does not exist. + /// + /// # Panics + /// + /// This function will panic if the result of the C library is unrecognized. This can not happen under normal + /// circumstances. + #[inline] + pub fn rip(&self) -> Option { + let op = unsafe { self.op_rlut.Rip.as_ref() }; + + op.map(|op| Operand::from_raw(*op).unwrap()) + } + + /// Get the implicit CS operand. + /// + /// # Returns + /// + /// * [Some(Operand)](Operand) if a implicit CS operand exists. + /// * [`None`] if a implicit CS operand does not exist. + /// + /// # Panics + /// + /// This function will panic if the result of the C library is unrecognized. This can not happen under normal + /// circumstances. + #[inline] + pub fn cs(&self) -> Option { + let op = unsafe { self.op_rlut.Cs.as_ref() }; + + op.map(|op| Operand::from_raw(*op).unwrap()) + } + + /// Get the implicit SS operand. + /// + /// # Returns + /// + /// * [Some(Operand)](Operand) if a implicit SS operand exists. + /// * [`None`] if a implicit SS operand does not exist. + /// + /// # Panics + /// + /// This function will panic if the result of the C library is unrecognized. This can not happen under normal + /// circumstances. + #[inline] + pub fn ss(&self) -> Option { + let op = unsafe { self.op_rlut.Ss.as_ref() }; + + op.map(|op| Operand::from_raw(*op).unwrap()) + } + + /// Get the implicit RAX operand. + /// + /// # Returns + /// + /// * [Some(Operand)](Operand) if a implicit RAX operand exists. + /// * [`None`] if a implicit RAX operand does not exist. + /// + /// # Panics + /// + /// This function will panic if the result of the C library is unrecognized. This can not happen under normal + /// circumstances. + #[inline] + pub fn rax(&self) -> Option { + let op = unsafe { self.op_rlut.Rax.as_ref() }; + + op.map(|op| Operand::from_raw(*op).unwrap()) + } + + /// Get the implicit RCX operand. + /// + /// # Returns + /// + /// * [Some(Operand)](Operand) if a implicit RCX operand exists. + /// * [`None`] if a implicit RCX operand does not exist. + /// + /// # Panics + /// + /// This function will panic if the result of the C library is unrecognized. This can not happen under normal + /// circumstances. + #[inline] + pub fn rcx(&self) -> Option { + let op = unsafe { self.op_rlut.Rcx.as_ref() }; + + op.map(|op| Operand::from_raw(*op).unwrap()) + } + + /// Get the implicit RDX operand. + /// + /// # Returns + /// + /// * [Some(Operand)](Operand) if a implicit RDX operand exists. + /// * [`None`] if a implicit RDX operand does not exist. + /// + /// # Panics + /// + /// This function will panic if the result of the C library is unrecognized. This can not happen under normal + /// circumstances. + #[inline] + pub fn rdx(&self) -> Option { + let op = unsafe { self.op_rlut.Rdx.as_ref() }; + + op.map(|op| Operand::from_raw(*op).unwrap()) + } + + /// Get the implicit RBX operand. + /// + /// # Returns + /// + /// * [Some(Operand)](Operand) if a implicit RBX operand exists. + /// * [`None`] if a implicit RBX operand does not exist. + /// + /// # Panics + /// + /// This function will panic if the result of the C library is unrecognized. This can not happen under normal + /// circumstances. + #[inline] + pub fn rbx(&self) -> Option { + let op = unsafe { self.op_rlut.Rbx.as_ref() }; + + op.map(|op| Operand::from_raw(*op).unwrap()) + } + + /// Get the implicit RSP operand. + /// + /// # Returns + /// + /// * [Some(Operand)](Operand) if a implicit RSP operand exists. + /// * [`None`] if a implicit RSP operand does not exist. + /// + /// # Panics + /// + /// This function will panic if the result of the C library is unrecognized. This can not happen under normal + /// circumstances. + #[inline] + pub fn rsp(&self) -> Option { + let op = unsafe { self.op_rlut.Rsp.as_ref() }; + + op.map(|op| Operand::from_raw(*op).unwrap()) + } + + /// Get the implicit RBP operand. + /// + /// # Returns + /// + /// * [Some(Operand)](Operand) if a implicit RBP operand exists. + /// * [`None`] if a implicit RBP operand does not exist. + /// + /// # Panics + /// + /// This function will panic if the result of the C library is unrecognized. This can not happen under normal + /// circumstances. + #[inline] + pub fn rbp(&self) -> Option { + let op = unsafe { self.op_rlut.Rbp.as_ref() }; + + op.map(|op| Operand::from_raw(*op).unwrap()) + } + + /// Get the implicit RSI operand. + /// + /// # Returns + /// + /// * [Some(Operand)](Operand) if a implicit RSI operand exists. + /// * [`None`] if a implicit RSI operand does not exist. + /// + /// # Panics + /// + /// This function will panic if the result of the C library is unrecognized. This can not happen under normal + /// circumstances. + #[inline] + pub fn rsi(&self) -> Option { + let op = unsafe { self.op_rlut.Rsi.as_ref() }; + + op.map(|op| Operand::from_raw(*op).unwrap()) + } + + /// Get the implicit RDI operand. + /// + /// # Returns + /// + /// * [Some(Operand)](Operand) if a implicit RDI operand exists. + /// * [`None`] if a implicit RDI operand does not exist. + /// + /// # Panics + /// + /// This function will panic if the result of the C library is unrecognized. This can not happen under normal + /// circumstances. + #[inline] + pub fn rdi(&self) -> Option { + let op = unsafe { self.op_rlut.Rdi.as_ref() }; + + op.map(|op| Operand::from_raw(*op).unwrap()) + } +} + +/// A collection of [Operand](Operand)s. +#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug, Default)] +pub struct Operands { + pub(crate) operands: [Operand; 10], + pub(crate) actual_count: usize, +} + +impl core::ops::Deref for Operands { + type Target = [Operand]; + + fn deref(&self) -> &[Operand] { + &self.operands[..self.actual_count] + } } #[cfg(test)] @@ -764,4 +1297,78 @@ mod tests { unreachable!(); } } + + #[test] + fn op_lut() { + // `PUSH rbx` + let ins = DecodedInstruction::decode(b"\x53", DecodeMode::Bits64).unwrap(); + let operands = ins.operand_lookup(); + + // The first destination is the stack. + let first_destination = operands.dest(0); + assert!(first_destination.is_some()); + let first_destination = first_destination.unwrap(); + + // And it is the same as the first memory operand. + let first_mem = operands.mem(0); + assert!(first_mem.is_some()); + let first_mem = first_mem.unwrap(); + assert_eq!(first_destination, first_mem); + + // And the same as the stack operand. + let stack = operands.stack(); + assert!(stack.is_some()); + let stack = stack.unwrap(); + assert_eq!(first_destination, stack); + + assert!(first_destination.is_default); + assert!(first_destination.info.is_mem()); + assert!(first_destination.info.as_mem().unwrap().is_stack); + + // Although the source operand is the RBX register, it is not one of the default operands. + let rbx = operands.rbx(); + assert!(rbx.is_none()); + + // There is only one destination operand. + let second_destination = operands.dest(1); + assert!(second_destination.is_none()); + + // The first source is RBX. + let first_source = operands.src(0); + assert!(first_source.is_some()); + let first_source = first_source.unwrap(); + + assert_eq!(first_source.is_default, false); + assert!(first_source.info.is_reg()); + + let first_source = first_source.info.as_reg().unwrap(); + assert_eq!(first_source.kind, OpRegType::Gpr); + assert_eq!(first_source.index, 3); + + // There is only one source operand. + let second_source = operands.src(1); + assert!(second_source.is_none()); + + // There is no other memory operand. + let second_mem = operands.mem(1); + assert!(second_mem.is_none()); + + // The FLAGS register is not accessed. + let flags = operands.flags(); + assert!(flags.is_none()); + } + + #[test] + fn check_all_shstk_access_values() { + // This is a really contrieved way of making sure that we check all variants of `ffi::_ND_SHSTK_ACCESS`. If a + // new one is added, this will fail to build. We do this because `ShadowStackAccess::from_raw` takes an `u8`. + // NOTE: When a new variant is added, `ShadowStackAccess::from_raw` must be updated. + match ffi::_ND_SHSTK_ACCESS::ND_SHSTK_NONE { + ffi::_ND_SHSTK_ACCESS::ND_SHSTK_NONE => {} + ffi::_ND_SHSTK_ACCESS::ND_SHSTK_EXPLICIT => {} + ffi::_ND_SHSTK_ACCESS::ND_SHSTK_SSP_LD_ST => {} + ffi::_ND_SHSTK_ACCESS::ND_SHSTK_SSP_PUSH_POP => {} + ffi::_ND_SHSTK_ACCESS::ND_SHSTK_PL0_SSP => {} + } + } } diff --git a/bindings/rsbddisasm/bddisasm/src/rflags.rs b/bindings/rsbddisasm/bddisasm/src/rflags.rs index 5ab896d..da6a576 100644 --- a/bindings/rsbddisasm/bddisasm/src/rflags.rs +++ b/bindings/rsbddisasm/bddisasm/src/rflags.rs @@ -4,8 +4,6 @@ */ //! 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. diff --git a/bindings/rsbddisasm/bddisasm/src/tuple.rs b/bindings/rsbddisasm/bddisasm/src/tuple.rs index 9991b6e..5326de1 100644 --- a/bindings/rsbddisasm/bddisasm/src/tuple.rs +++ b/bindings/rsbddisasm/bddisasm/src/tuple.rs @@ -4,7 +4,7 @@ */ //! Instruction tuple type. -extern crate bddisasm_sys as ffi; +use super::decode_error::DecodeError; /// Instruction tuple type. /// @@ -50,73 +50,76 @@ pub enum Tuple { } #[doc(hidden)] -impl From 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` implementation! - // TODO: any way of keeping these in sync automagically? - } - } -} - -impl From for Tuple { - fn from(value: u32) -> Tuple { +impl Tuple { + pub(crate) fn from_raw(value: u32) -> Result { if value == ffi::_ND_TUPLE::ND_TUPLE_None as u32 { - Tuple::None + Ok(Tuple::None) } else if value == ffi::_ND_TUPLE::ND_TUPLE_FV as u32 { - Tuple::Fv + Ok(Tuple::Fv) } else if value == ffi::_ND_TUPLE::ND_TUPLE_HV as u32 { - Tuple::Hv + Ok(Tuple::Hv) } else if value == ffi::_ND_TUPLE::ND_TUPLE_QV as u32 { - Tuple::Qv + Ok(Tuple::Qv) } else if value == ffi::_ND_TUPLE::ND_TUPLE_T1S8 as u32 { - Tuple::T1S8 + Ok(Tuple::T1S8) } else if value == ffi::_ND_TUPLE::ND_TUPLE_T1S16 as u32 { - Tuple::T1S16 + Ok(Tuple::T1S16) } else if value == ffi::_ND_TUPLE::ND_TUPLE_T1S as u32 { - Tuple::T1S + Ok(Tuple::T1S) } else if value == ffi::_ND_TUPLE::ND_TUPLE_T1F as u32 { - Tuple::T1F + Ok(Tuple::T1F) } else if value == ffi::_ND_TUPLE::ND_TUPLE_T2 as u32 { - Tuple::T2 + Ok(Tuple::T2) } else if value == ffi::_ND_TUPLE::ND_TUPLE_T4 as u32 { - Tuple::T4 + Ok(Tuple::T4) } else if value == ffi::_ND_TUPLE::ND_TUPLE_T8 as u32 { - Tuple::T8 + Ok(Tuple::T8) } else if value == ffi::_ND_TUPLE::ND_TUPLE_FVM as u32 { - Tuple::Fvm + Ok(Tuple::Fvm) } else if value == ffi::_ND_TUPLE::ND_TUPLE_HVM as u32 { - Tuple::Hvm + Ok(Tuple::Hvm) } else if value == ffi::_ND_TUPLE::ND_TUPLE_QVM as u32 { - Tuple::Qvm + Ok(Tuple::Qvm) } else if value == ffi::_ND_TUPLE::ND_TUPLE_OVM as u32 { - Tuple::OVm + Ok(Tuple::OVm) } else if value == ffi::_ND_TUPLE::ND_TUPLE_M128 as u32 { - Tuple::M128 + Ok(Tuple::M128) } else if value == ffi::_ND_TUPLE::ND_TUPLE_DUP as u32 { - Tuple::Dup + Ok(Tuple::Dup) } else if value == ffi::_ND_TUPLE::ND_TUPLE_T1_4X as u32 { - Tuple::T14X + Ok(Tuple::T14X) } else { - panic!("Unknown tuple: {}", value) + Err(DecodeError::InternalError(value.into())) + } + } +} + +#[cfg(test)] +mod tests { + #[test] + fn check_all_tuples() { + // This is a really contrieved way of making sure that we check all variants of `ffi::_ND_TUPLE`. If a new + // one is added, this will fail to build. We do this because `Tuple::from_raw` takes an `u32`. + // NOTE: When a new variant is added, `Tuple::from_raw` must be updated. + match ffi::_ND_TUPLE::ND_TUPLE_None { + ffi::_ND_TUPLE::ND_TUPLE_None => {} + ffi::_ND_TUPLE::ND_TUPLE_FV => {} + ffi::_ND_TUPLE::ND_TUPLE_HV => {} + ffi::_ND_TUPLE::ND_TUPLE_QV => {} + ffi::_ND_TUPLE::ND_TUPLE_T1S8 => {} + ffi::_ND_TUPLE::ND_TUPLE_T1S16 => {} + ffi::_ND_TUPLE::ND_TUPLE_T1S => {} + ffi::_ND_TUPLE::ND_TUPLE_T1F => {} + ffi::_ND_TUPLE::ND_TUPLE_T2 => {} + ffi::_ND_TUPLE::ND_TUPLE_T4 => {} + ffi::_ND_TUPLE::ND_TUPLE_T8 => {} + ffi::_ND_TUPLE::ND_TUPLE_FVM => {} + ffi::_ND_TUPLE::ND_TUPLE_HVM => {} + ffi::_ND_TUPLE::ND_TUPLE_QVM => {} + ffi::_ND_TUPLE::ND_TUPLE_OVM => {} + ffi::_ND_TUPLE::ND_TUPLE_M128 => {} + ffi::_ND_TUPLE::ND_TUPLE_DUP => {} + ffi::_ND_TUPLE::ND_TUPLE_T1_4X => {} } } } diff --git a/bindings/rsbddisasm/fix_symlinks.sh b/bindings/rsbddisasm/fix_symlinks.sh new file mode 100644 index 0000000..288b32c --- /dev/null +++ b/bindings/rsbddisasm/fix_symlinks.sh @@ -0,0 +1,12 @@ +#!/bin/bash +set -e + +cd bddisasm-sys +rm -rf LICENSE +ln -s ../../../LICENSE . + +cd csrc +rm -rf bddisasm +ln -s ../../../../bddisasm/ . +rm -rf inc +ln -s ../../../../inc/ . diff --git a/disasmtool/disasmtool.vcxproj b/disasmtool/disasmtool.vcxproj index fb6627f..5c6269b 100644 --- a/disasmtool/disasmtool.vcxproj +++ b/disasmtool/disasmtool.vcxproj @@ -1,6 +1,10 @@  + + Debug + ARM64 + Debug Win32 @@ -9,6 +13,10 @@ Debug x64 + + Release + ARM64 + Release Win32 @@ -43,11 +51,22 @@ Unicode true + + Application + v142 + Unicode + true + Application v142 Unicode + + Application + v142 + Unicode + @@ -60,9 +79,15 @@ + + + + + + <_ProjectFileVersion>14.0.23107.0 @@ -77,6 +102,11 @@ $(SolutionDir)_intdir\$(ProjectName)\$(Platform)\$(Configuration)\ true + + $(SolutionDir)bin\$(Platform)\$(Configuration)\ + $(SolutionDir)_intdir\$(ProjectName)\$(Platform)\$(Configuration)\ + true + $(SolutionDir)bin\$(Platform)\$(Configuration)\ $(SolutionDir)_intdir\$(ProjectName)\$(Platform)\$(Configuration)\ @@ -89,6 +119,13 @@ false $(LibraryPath) + + $(SolutionDir)bin\$(Platform)\$(Configuration)\ + $(SolutionDir)_intdir\$(ProjectName)\$(Platform)\$(Configuration)\ + false + false + $(LibraryPath) + Disabled @@ -141,6 +178,33 @@ false + + + + /D "AMD64" %(AdditionalOptions) + Disabled + ..\inc;..\bdshemu;%(AdditionalIncludeDirectories) + WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + EnableFastChecks + MultiThreadedDebugDLL + Level4 + true + ProgramDatabase + $(SolutionDir)bin\$(Platform)\$(Configuration)\$(ProjectName).pdb + + + bddisasm.lib;bdshemu.lib;%(AdditionalDependencies) + $(OutDir)disasmtool.exe + $(SolutionDir)bin\$(Platform)\$(Configuration)\;%(AdditionalLibraryDirectories) + LIBCMTD;%(IgnoreSpecificDefaultLibraries) + true + Console + + + false + + MaxSpeed @@ -200,6 +264,38 @@ MachineX64 + + + + /D "AMD64" %(AdditionalOptions) + MaxSpeed + AnySuitable + true + Speed + ..\inc;..\bdshemu;%(AdditionalIncludeDirectories) + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + MultiThreadedDLL + false + Level4 + true + ProgramDatabase + $(SolutionDir)bin\$(Platform)\$(Configuration)\$(ProjectName).pdb + + + true + + + bddisasm.lib;bdshemu.lib;%(AdditionalDependencies) + $(OutDir)disasmtool.exe + $(SolutionDir)bin\$(Platform)\$(Configuration)\;%(AdditionalLibraryDirectories) + false + LIBCMT;%(IgnoreSpecificDefaultLibraries) + true + Console + true + true + + diff --git a/disasmtool_lix/CMakeLists.txt b/disasmtool_lix/CMakeLists.txt index 39c7c66..2c2b059 100644 --- a/disasmtool_lix/CMakeLists.txt +++ b/disasmtool_lix/CMakeLists.txt @@ -12,7 +12,7 @@ if (NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS "Debug" "Release") endif () -add_executable(disasmtool disasmtool.cpp dumpers.cpp) +add_executable(disasmtool disasmtool.cpp dumpers.cpp rapidjson.cpp) target_compile_options( disasmtool @@ -43,15 +43,11 @@ if (NOT TARGET bddisasm) find_package(bddisasm REQUIRED) endif () -find_package(RapidJSON QUIET) +find_package(RapidJSON QUIET REQUIRED) target_link_libraries(disasmtool PRIVATE bddisasm::bddisasm bddisasm::bdshemu) -if (RapidJSON_FOUND) - # :( https://github.com/satishbabariya/modern-cmake#good-boys-export-their-targets - target_include_directories(disasmtool PRIVATE ${RapidJSON_INCLUDE_DIRS}) - target_sources(disasmtool PRIVATE rapidjson.cpp) - target_compile_definitions(disasmtool PRIVATE HAS_RAPIDJSON) -endif () +# :( https://github.com/satishbabariya/modern-cmake#good-boys-export-their-targets +target_include_directories(disasmtool PRIVATE ${RapidJSON_INCLUDE_DIRS}) if ("${CMAKE_BUILD_TYPE}" STREQUAL "Release") include(CheckIPOSupported) diff --git a/disasmtool_lix/disasm.hpp b/disasmtool_lix/disasm.hpp index d8d5a77..96e8c8d 100644 --- a/disasmtool_lix/disasm.hpp +++ b/disasmtool_lix/disasm.hpp @@ -7,13 +7,8 @@ #include #include -#ifdef HAS_RAPIDJSON #include using StringBuffer = rapidjson::StringBuffer; -#else -#include "external/json.hpp" -using json = nlohmann::json; -#endif extern "C" diff --git a/disasmtool_lix/disasmtool.cpp b/disasmtool_lix/disasmtool.cpp index 0c05271..2359dcf 100644 --- a/disasmtool_lix/disasmtool.cpp +++ b/disasmtool_lix/disasmtool.cpp @@ -496,14 +496,14 @@ void shemu_log(PCHAR msg) printf("%s", msg); } -bool shemu_access_mem(void * /* Ctx */, uint64_t /* Gla */, size_t Size, uint8_t *Buffer, bool Store) +ND_BOOL shemu_access_mem(void * /* Ctx */, uint64_t /* Gla */, size_t Size, uint8_t *Buffer, ND_BOOL Store) { if (!Store) { memset(Buffer, 0, Size); } - return true; + return ND_TRUE; } void shemu(options &opts)