aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CHANGELOG5
-rw-r--r--Cargo.toml25
-rw-r--r--fuzz/Cargo.toml6
-rw-r--r--fuzz/fuzz_targets/behavior_does_not_panic.rs138
-rw-r--r--goodfile14
-rw-r--r--src/behavior.rs294
-rw-r--r--src/lib.rs5
-rw-r--r--src/long_mode/behavior.rs5856
-rw-r--r--src/long_mode/mod.rs15
-rw-r--r--src/long_mode/uarch.rs12
-rw-r--r--test/long_mode/behavior.rs2416
-rw-r--r--test/long_mode/mod.rs2
-rw-r--r--test/long_mode/reuse_test.rs8
13 files changed, 8787 insertions, 9 deletions
diff --git a/CHANGELOG b/CHANGELOG
index b36e7f6..732b850 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,5 +1,8 @@
## 2.1.0
+* x86-64, x86-32, and x86-16 instructions now have associated behavior information
+ (operand read/write, exceptions, privilege levels) accessible via `Instruction::behavior()`.
+ the new `behavior` modules include examples and further discussion of the new APIs.
* `InstDecoder` for all execution modes now has many more feature flags.
* `InstDecoder::default` still constructs a decoder that decodes all supported instructions.
* more ISA extensions can be individually selected for decoding support, though note
@@ -9,6 +12,8 @@
* removed 3DNow support from AMD uarch-specific decoders after K10
* RegSpec register helpers to construct RegSpec from register numbers are now const fn
(RegSpec::xmm, RegSpec::q, RegSpec::d, RegSpec::st, etc)
+* new crate feature flag, `unstable`, for library features that may see
+ breaking changes across semver-compatible releases.
* for uarch-specific decoding, there is now a feature bit for Intel Key Locker. this corrects an
issue where Key Locker instructions would decode under AMD-specific decoders.
* push-immediate, pushf, popf, pusha, popa, enter, leave, and xlat now all report a correct memory
diff --git a/Cargo.toml b/Cargo.toml
index 0fa7449..481cc0e 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -17,8 +17,15 @@ yaxpeax-arch = { version = "0.3.1", default-features = false, features = [] }
"serde_derive" = { version = "1.0", optional = true }
"cfg-if" = "1.0.0"
+# if yaxpeax-x86 ever pulls this into your dep tree: sorry! please report that!
+# this is only used for verifying library structures are kept in sync. it
+# should only be pulled in with the _debug_internal_asserts feature which you
+# should not be using!
+strum = { version = "0.28.0", features = ["derive"], optional = true }
+
[dev-dependencies]
-rand = "0.8.4"
+rand = { version = "0.10.0", features = ["thread_rng"] }
+asmlinator = { version = "2.0.0" }
[[test]]
name = "test"
@@ -37,7 +44,7 @@ opt-level = 3
lto = true
[features]
-default = ["std", "colors", "use-serde", "fmt"]
+default = ["std", "colors", "use-serde", "fmt", "behavior"]
# opt-in for some apis that are really much nicer with String
std = ["alloc", "yaxpeax-arch/std"]
@@ -51,10 +58,21 @@ alloc = ["yaxpeax-arch/alloc"]
# feature for formatting instructions and their components
fmt = []
+# feature for additional instruction behavior information
+#
+# as of writing this includes an additional ~6kb of combined code and data
+# per-decoder. much like `fmt`, this is mostly useful to ensure that unused
+# data truly is not included in a build.
+behavior = []
+
use-serde = ["yaxpeax-arch/use-serde", "serde", "serde_derive"]
colors = ["yaxpeax-arch/colors"]
+# feature for library surface areas that are *NOT STABLE*. these will change in
+# semver-incompatible ways across any releases.
+unstable = []
+
# This enables some capstone benchmarks over the same
# instruction bytes used to bench this code.
capstone_bench = []
@@ -65,3 +83,6 @@ profiling = []
# do not use this. it is for development and library debugging only.
_debug_internal_disasm_stats = []
+
+# do not use this. it is for development and library testing only.
+_debug_internal_asserts = ["strum"]
diff --git a/fuzz/Cargo.toml b/fuzz/Cargo.toml
index a1f871e..23e8a88 100644
--- a/fuzz/Cargo.toml
+++ b/fuzz/Cargo.toml
@@ -58,3 +58,9 @@ name = "small_reg_is_always_old_bank_if_possible"
path = "fuzz_targets/small_reg_is_always_old_bank_if_possible.rs"
test = false
doc = false
+
+[[bin]]
+name = "behavior_does_not_panic"
+path = "fuzz_targets/behavior_does_not_panic.rs"
+test = false
+doc = false
diff --git a/fuzz/fuzz_targets/behavior_does_not_panic.rs b/fuzz/fuzz_targets/behavior_does_not_panic.rs
new file mode 100644
index 0000000..1b6ac96
--- /dev/null
+++ b/fuzz/fuzz_targets/behavior_does_not_panic.rs
@@ -0,0 +1,138 @@
+#![no_main]
+#[macro_use] extern crate libfuzzer_sys;
+extern crate yaxpeax_x86;
+
+fuzz_target!(|data: &[u8]| {
+ if data.len() > 15 {
+ return;
+ }
+ let x86_64_decoder = yaxpeax_x86::long_mode::InstDecoder::default();
+ let x86_32_decoder = yaxpeax_x86::protected_mode::InstDecoder::default();
+ let x86_16_decoder = yaxpeax_x86::real_mode::InstDecoder::default();
+ /*
+ let inst_32b = x86_32_decoder.decode_slice(data).expect("is ok");
+ let inst_16b = x86_16_decoder.decode_slice(data).expect("is ok");
+ */
+
+ if let Ok(inst_64b) = x86_64_decoder.decode_slice(data) {
+ /*
+ for b in data {
+ eprint!("{:02x}", b);
+ }
+ eprintln!(": {}", inst_64b);
+ */
+ let behavior_64b = inst_64b.behavior();
+ let _ = behavior_64b.privilege_level();
+ let _ = behavior_64b.exceptions();
+ let _ = behavior_64b.implicit_oplist();
+ for i in 0..5 {
+ let _ = behavior_64b.operand_access(i);
+ }
+
+ let mut opcount_64b = 0;
+ use yaxpeax_x86::long_mode::{Opcode as b64Opcode, Operand as b64Operand};
+
+ if let Ok(ops) = behavior_64b.all_operands() {
+ // eprintln!("checking instr {}", inst_64b);
+ for (op, acc) in ops.iter() {
+ match op {
+ b64Operand::ImmediateI8 { .. } |
+ b64Operand::ImmediateU8 { .. } |
+ b64Operand::ImmediateI16 { .. } |
+ b64Operand::ImmediateU16 { .. } |
+ b64Operand::ImmediateI32 { .. } |
+ b64Operand::ImmediateU32 { .. } |
+ b64Operand::ImmediateI64 { .. } => {
+ // immediates are not reported as "accessed" below, as they are not really
+ // architectural state to be "accessed".. so skip them here to make the
+ // counters line up.
+ continue;
+ },
+ _ => {}
+ }
+// eprintln!("saw {:?}, {:?}", op, acc);
+ let mut accs = 0;
+ if inst_64b.opcode() == b64Opcode::LEA && op.is_memory() {
+ // the access-visiting interface below does not report a memory access for lea
+ // because lea does not access memory. skip it to make the counters line up.
+ continue;
+ }
+ match op {
+ b64Operand::AbsoluteU32 { .. } |
+ b64Operand::AbsoluteU64 { .. } |
+ b64Operand::Register { .. } |
+ b64Operand::MemDeref { .. } |
+ b64Operand::Disp { .. } |
+ b64Operand::MemIndexScale { .. } |
+ b64Operand::MemIndexScaleDisp { .. } |
+ b64Operand::MemBaseIndexScale { .. } |
+ b64Operand::MemBaseIndexScaleDisp { .. } => {
+ accs = 1;
+ }
+ b64Operand::RegisterMaskMerge { mask, .. } |
+ b64Operand::RegisterMaskMergeSae { mask, .. } |
+ b64Operand::RegisterMaskMergeSaeNoround { mask, .. } |
+ b64Operand::MemDerefMasked { mask, .. } |
+ b64Operand::DispMasked { mask, .. } |
+ b64Operand::MemIndexScaleMasked { mask, .. } |
+ b64Operand::MemIndexScaleDispMasked { mask, .. } |
+ b64Operand::MemBaseIndexScaleMasked { mask, .. } |
+ b64Operand::MemBaseIndexScaleDispMasked { mask, .. } => {
+ accs = 1;
+ if mask.num() != 0 {
+ // the variants producing RegisterMaskMerge* are not sufficiently
+ // careful..
+ accs += 1;
+ }
+ }
+ _ => {
+ // immediates don't produce a register/memory read/write
+ accs = 0;
+ }
+ }
+ // read-write accesses are reported as two accesses in the visitor interface below.
+ // count such cases twice here to make the counters line up.
+ if acc.is_read() {
+ opcount_64b += accs;
+ }
+ if acc.is_write() {
+ opcount_64b += accs;
+ }
+ }
+ }
+
+ struct AccessCounter<'ctr> {
+ counter: &'ctr mut usize,
+ }
+
+ impl<'ctr> yaxpeax_x86::long_mode::behavior::AccessVisitor for AccessCounter<'ctr> {
+ fn register_read(&mut self, _reg: yaxpeax_x86::long_mode::RegSpec) {
+// eprintln!("saw read {:?}", _reg);
+ *self.counter += 1;
+ }
+ fn register_write(&mut self, _reg: yaxpeax_x86::long_mode::RegSpec) {
+// eprintln!("saw write {:?}", _reg);
+ *self.counter += 1;
+ }
+ fn get_register(&mut self, _reg: yaxpeax_x86::long_mode::RegSpec) -> Option<u64> { None }
+ fn memory_read(&mut self, _address: Option<u64>, _size: u32) {
+// eprintln!("saw read {:?}", _size);
+ *self.counter += 1;
+ }
+ fn memory_write(&mut self, _address: Option<u64>, _size: u32) {
+// eprintln!("saw write {:?}", _size);
+ *self.counter += 1;
+ }
+ }
+
+ let mut acc_seen_64b = 0;
+ let mut visitor = AccessCounter {
+ counter: &mut acc_seen_64b,
+ };
+
+ let visit_res = behavior_64b.visit_accesses(&mut visitor);
+ if visit_res.is_ok() {
+ assert_eq!(opcount_64b, acc_seen_64b);
+ }
+ }
+});
diff --git a/goodfile b/goodfile
index a90a885..15fbc4e 100644
--- a/goodfile
+++ b/goodfile
@@ -9,11 +9,23 @@ Step.start("crate")
Step.push("build")
Build.run({"cargo", "build"}) -- `run` automatically records stdout and stderr to log files named after the command
Step.advance("test")
-Build.run({"cargo", "test"}, {name="test stdlib/fmt"}) -- artifacts are stored under `name` if that's present
+-- default features include "behavior" which gets the relatively slow (and KVM-requiring) behavior validation tests
+Build.run({"cargo", "test", "--features", "_debug_internal_asserts", "--", "--skip", "kvm"}, {name="test stdlib/fmt"}) -- artifacts are stored under `name` if that's present
+-- but no default features means these aren't built in!
Build.run({"cargo", "test", "--no-default-features"}, {name="test nostdlib/nofmt"})
Build.run({"cargo", "test", "--no-default-features", "--features", "fmt"}, {name="test nostdlib/fmt"})
Build.run({"cargo", "test", "--no-default-features", "--features", "std"}, {name="test nostdlib/std"})
+Build.run({"cargo", "test", "--no-default-features", "--features", "unstable"}, {name="test nostdlib/unstable"})
Build.run({"cargo", "test", "--no-default-features", "--features", "std,fmt"}, {name="test nostdlib/std+fmt"})
+Build.run({"cargo", "test", "--no-default-features", "--features", "std,fmt,unstable"}, {name="test nostdlib/std+fmt+unstable"})
+
+Step.advance("test-kvm")
+Build.run({"cargo", "test", "--release", "--no-default-features", "--features", "behavior"}, {name="test nostdlib/behavior"})
+Build.run({"cargo", "test", "--release", "--no-default-features", "--features", "behavior,fmt"}, {name="test nostdlib/behavior+fmt"})
+Build.run({"cargo", "test", "--release", "--no-default-features", "--features", "behavior,std"}, {name="test nostdlib/behavior+std"})
+Build.run({"cargo", "test", "--release", "--no-default-features", "--features", "behavior,fmt,std,_debug_internal_asserts"}, {name="test nostdlib/behavior+fmt+std"})
+Build.run({"cargo", "test", "--release", "--no-default-features", "--features", "behavior,fmt,std,unstable,_debug_internal_asserts"}, {name="test nostdlib/behavior+fmt+std+unstable"})
+
-- the interesting benchmarking happens through disas-bench, but the in-tree capstone bench isn't gone *quite* yet.
Step.start("bench")
diff --git a/src/behavior.rs b/src/behavior.rs
new file mode 100644
index 0000000..38525dd
--- /dev/null
+++ b/src/behavior.rs
@@ -0,0 +1,294 @@
+//! common types used for instruction behavior analysis across `yaxpeax-x86`
+//!
+//! these types are, in particular, used in [`yaxpeax_x86::long_mode::behavior`],
+//! [`yaxpeax_x86::protected_mode::behavior`], and [`yaxpeax_x86::real_mode::behavior`]. specifics
+//! like `Operand` are still mode-dependent, in part because `RegSpec` is different across modes.
+//! likewise, there is no generic method to go from an `Instruction` to these kinds of accessors
+//! yet. that said, `Instruction::behavior()` returns an `InstBehavior` that works effectively the
+//! same way in all modes.
+
+// a lot of people have told me that we don't need to read code anymore, just like we "never look at
+// machine code anymore". sit with me and have a sad laugh for a moment. if we weren't here, reading
+// and thinking and talking about how to model the computer, where would the training data come
+// from? "you're not being left behind, it's a personal choice!", i've heard. a year later this has
+// evolved into "it is an abdication of your responsibility as an engineer to not pay Anthropic".
+// it is our ~moral responsibility~ to build the highest quality software in service of furthering
+// the rot? it is an abdication of ethics to claim all works are good.
+
+/// a collection of possible exceptions an instruction can raise. this covers the handful of
+/// well-defined exception vectors with bits matching to the exception vectors listed in SDM
+/// chapter 6.5.1 "Call and Return Operation for Interrupt or Exception Handling Procedures"
+/// specifically "Table 6-1. Exceptions and Interrupts".
+pub struct ExceptionInfo {
+ possible_vectors: u32,
+}
+
+/// an individual exception vector. these are just a tiny wrapper around `u8` to have some
+/// associated constant definitions.
+///
+/// the associated constants on this type are named according to the Intel SDM chapter 7.3 "SOURCES
+/// OF INTERRUPTS" table 7-1 "Protected-Mode Exceptions and Interrupts". similar descriptions can
+/// be found in the AMD APM.
+#[derive(Copy, Clone, PartialEq, Eq)]
+pub struct Exception {
+ vector: u8,
+}
+
+impl Exception {
+ /// Divide Error
+ pub const DE: Exception = Exception::vector(0);
+ /// Debug
+ pub const DB: Exception = Exception::vector(1);
+ /// Non-Maskable Interrupt
+ pub const NMI: Exception = Exception::vector(2);
+ /// Breakpoint
+ pub const BP: Exception = Exception::vector(3);
+ /// Overflow
+ pub const OF: Exception = Exception::vector(4);
+ /// BOUND Range Exceeded
+ pub const BR: Exception = Exception::vector(5);
+ /// Invalid Opcode (Undefined Opcode)
+ pub const UD: Exception = Exception::vector(6);
+ /// Device Not Available (No Math Coprocessor)
+ pub const NM: Exception = Exception::vector(7);
+ /// Double Fault
+ pub const DF: Exception = Exception::vector(8);
+ // CoProcessor Segment Overrun (reserved)
+ // from the SDM:
+ // > IA-32 processors after the Intel386 processor do not generate this exception.
+ //
+ // and as the mnemonic has since been reused for exception vector 16,
+ // `Floating-Point Error (Math Fault)`, we won't bother giving vector 9 a nice symbolic name.
+ // const MF: Exception = Exception::vector(9);
+ /// Invalid TSS
+ pub const TS: Exception = Exception::vector(10);
+ /// Segment Not Present
+ pub const NP: Exception = Exception::vector(11);
+ /// Stack Segment Fault
+ pub const SS: Exception = Exception::vector(12);
+ /// General Protection
+ pub const GP: Exception = Exception::vector(13);
+ /// Page Fault
+ pub const PF: Exception = Exception::vector(14);
+ // vector 15 is reserved
+ /// Floating-Point Error (Math Fault)
+ pub const MF: Exception = Exception::vector(16);
+ /// Alignment Check
+ pub const AC: Exception = Exception::vector(17);
+ /// Machine Check
+ pub const MC: Exception = Exception::vector(18);
+ /// SIMD Floating-Point Exception
+ pub const XM: Exception = Exception::vector(19);
+ /// Virtualization Exception
+ pub const VE: Exception = Exception::vector(20);
+ /// Control Protection Exception
+ pub const CP: Exception = Exception::vector(21);
+
+ /// construct an `Exception` for the provided exception vector number.
+ ///
+ /// this is provided for convenience when converting (for example) the number in an x86
+ /// exception handler to the kinds of `Exception` in this library.
+ pub const fn vector(vector: u8) -> Self {
+ Self { vector }
+ }
+
+ /// convert this `Exception` to an index into an x86 IDT.
+ pub const fn to_u8(&self) -> u8 {
+ self.vector
+ }
+
+ #[cfg(any(doc, feature = "fmt"))]
+ /// get the typical mnemonic for this `Exception`, if one is documented.
+ ///
+ /// the names returned by helper do not include a leading `#`. they come from the Intel SDM
+ /// chapter 7.3 "SOURCES OF INTERRUPTS" table 7-1 "Protected-Mode Exceptions and Interrupts".
+ /// similar descriptions can be found in the AMD APM.
+ pub fn name(&self) -> Option<&'static str> {
+ static NAMES: [Option<&'static str>; 22] = [
+ Some("DE"), Some("DB"), Some("NMI"), Some("BP"),
+ Some("OF"), Some("BR"), Some("UD"), Some("NM"),
+ Some("DF"), None, Some("TS"), Some("NP"),
+ Some("SS"), Some("GP"), Some("PF"), None,
+ Some("MF"), Some("AC"), Some("MC"), Some("XM"),
+ Some("VE"), Some("CP")
+ ];
+
+ if let Some(maybe_name) = NAMES.get(self.vector as usize) {
+ *maybe_name
+ } else {
+ None
+ }
+ }
+}
+
+#[cfg(feature = "fmt")]
+use core::fmt;
+#[cfg(feature = "fmt")]
+impl fmt::Debug for Exception {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ if let Some(name) = self.name() {
+ write!(f, "#{}", name)
+ } else {
+ write!(f, "#Int{}", self.to_u8())
+ }
+ }
+}
+
+impl ExceptionInfo {
+ /// construct an empty set of possible exception vectors.
+ pub fn empty() -> Self {
+ Self {
+ possible_vectors: 0,
+ }
+ }
+
+ /// test if this `ExceptionInfo` has any possible vector set.
+ pub fn any(&self) -> bool {
+ self.possible_vectors != 0
+ }
+
+ /// test if this `ExceptionInfo` has no vector set.
+ pub fn none(&self) -> bool {
+ !self.any()
+ }
+
+ /// test if this `ExceptionInfo` indicates that exception `e` may be raised.
+ pub fn may(&self, e: Exception) -> bool {
+ (self.possible_vectors & (1 << e.vector)) != 0
+ }
+
+ /// record that exception `e` is or is not (`b`) possible in this `ExceptionInfo` record.
+ pub const fn set(&mut self, e: Exception, b: bool) {
+ let offset = e.vector;
+ assert!(offset < 32);
+ let mask = !(1 << offset);
+ let bit = (b as u32) << offset;
+
+ self.possible_vectors &= mask;
+ self.possible_vectors |= bit;
+ }
+
+ /// record that exception `e` is or is not (`b`) possible in this `ExceptionInfo` record, but
+ /// in a more chaining-friendly way.
+ pub const fn with(mut self, e: Exception, b: bool) -> Self {
+ self.set(e, b);
+ self
+ }
+}
+
+#[test]
+fn test_exception_info() {
+ let mut info = ExceptionInfo::empty();
+ info.set(Exception::MF, true);
+ assert_eq!(info.possible_vectors, 0x10000);
+
+ info.set(Exception::MF, true);
+ assert_eq!(info.possible_vectors, 0x10000);
+
+ info.set(Exception::MF, false);
+ assert_eq!(info.possible_vectors, 0x00000);
+
+ info.set(Exception::GP, false);
+ assert_eq!(info.possible_vectors, 0x00000);
+
+ info.set(Exception::GP, true);
+ assert_eq!(info.possible_vectors, 0x02000);
+
+ info.set(Exception::MF, true);
+ assert_eq!(info.possible_vectors, 0x12000);
+}
+
+/// a description of the privilege level (that is, value of `CPL` in the current code selector)
+/// that allows executing the corresponding instruction.
+#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
+pub enum PrivilegeLevel {
+ /// the corresponding instruction can run at any privilege level.
+ Any = 0b00,
+ /// the corresponding instruction can only run when `CPL=0` (aka "in ring 0").
+ PL0 = 0b01,
+ /// the corresponding instruction has more complex rule for when it is allowed.
+ ///
+ /// this may mean the instruction is either "Any" or "PL0" depending on other processor state
+ /// (such as `rdtsc`), or it may mean the instruction simply does not relate directly to
+ /// `CPL=3`/`CPL=0` (such as for `iret`).
+ Special = 0b10,
+}
+
+/// a description of how an operand is used.
+///
+/// `Access::ReadWrite` can be processed in the same manner as that operand listed as
+/// `Access::Read` followed by that same operand listed as `Access::Write`.
+///
+/// **important**: the meaning of `Access` is different for `flags`/`eflags`/`rflags` than other
+/// operands! these differences are documented on enum variants below.
+#[derive(Copy, Clone, Debug, PartialEq)]
+pub enum Access {
+ /// the corresponding operand is read.
+ ///
+ /// for memory operands, this describes the referenced memory; implicitly the registers used in
+ /// the operand's address calculation are also read.
+ Read = 0b01,
+ /// the corresponding operand is written.
+ ///
+ /// for memory operands, this describes the referenced memory; implicitly the registers used in
+ /// the operand's address calculation are also read.
+ ///
+ /// for flags/eflags/rflags, "write" refers to some subset of flag bits as appropriate for the
+ /// instruction, and implies that the instruction does not depend on the initial state of those
+ /// bits. this is in contrast to `Write` for other operands, where it implies a full write of
+ /// the corresponding operand. as a concrete example, `add` reports the flags register as a
+ /// `Write` since the resulting flag bits are purely a function of the `add` register/memory
+ /// operands.
+ Write = 0b10,
+ /// the corresponding operand is read and written.
+ ///
+ /// in some cases `Access::ReadWrite` is chosen in particular to represent a parital-write;
+ /// this is especially true with SIMD instructions as `yaxpeax-x86` does not currently have the
+ /// ability to express individual SIMD lane read/write operations. the `vmov{h,l}{ps,pd}`
+ /// instructions are more common examples of this access form. this kind of partial-write
+ /// access is reported as `Access::Write` for flags/eflags/rflags.
+ ///
+ /// for flags/eflags/rflags, "read-write" refers to some subset of flag bits as appropriate for the
+ /// instruction, and implies that the instruction does depends on the initial state of those
+ /// bits as well as modifying some (possibly different) bits in flags as a result.
+ /// as a concrete example, `adc` reports the flags register as a `ReadWrite` because the
+ /// initial state of `cf` is an input to the addition, and the normal arithmetic flags are
+ /// written based on the result.
+ ///
+ /// for memory operands, this describes the referenced memory; implicitly the registers used in
+ /// the operand's address calculation are also read.
+ ReadWrite = 0b11,
+ /// the corresponding operand is not actually accessed for reading or writing.
+ ///
+ /// this is only used to describe the operand of `nop` or `ud1` instructions.
+ None = 0b00,
+}
+
+impl Access {
+ // translate two bits to an `Access`. panics if the bit pattern has anything other than the low
+ // two bits set. don't do that.
+ pub(crate) fn from_bits(bits: u8) -> Option<Access> {
+ const LUT: [Option<Access>; 4] = [
+ Some(Access::None), Some(Access::Read),
+ Some(Access::Write), Some(Access::ReadWrite),
+ ];
+
+ assert!(bits <= 0b11);
+
+ LUT[bits as usize]
+ }
+
+ /// is this access a read?
+ ///
+ /// if it is `ReadWrite`, this will be `true` as will `is_write`.
+ pub fn is_read(&self) -> bool {
+ *self as u8 & 0b01 != 0
+ }
+
+ /// is this access a write?
+ ///
+ /// if it is `ReadWrite`, this will be `true` as will `is_read`.
+ pub fn is_write(&self) -> bool {
+ *self as u8 & 0b10 != 0
+ }
+}
diff --git a/src/lib.rs b/src/lib.rs
index aea4c91..6850234 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -128,6 +128,11 @@ extern crate serde;
#[cfg(feature="std")]
extern crate alloc;
+#[cfg(feature="behavior")]
+mod behavior;
+#[cfg(feature="behavior")]
+pub use behavior::{Access, Exception, ExceptionInfo, PrivilegeLevel};
+
#[macro_use]
mod isa_settings;
diff --git a/src/long_mode/behavior.rs b/src/long_mode/behavior.rs
new file mode 100644
index 0000000..dbadd29
--- /dev/null
+++ b/src/long_mode/behavior.rs
@@ -0,0 +1,5856 @@
+//! behavior information for x86-64 instructions.
+//!
+//! this module allows users of yaxpeax-x86 to collect operand read/write information about
+//! instructions ([`InstBehavior::operand_access()`]), as well as allowed execution level
+//! ([`InstBehavior::privilege_level()`]), potential exceptions ([`InstBehavior::exceptions()`]),
+//! and iterating all explicit and implicit operands ([`InstBehavior::all_operands()`]).
+//!
+//! a concise example of the information provided by this model:
+//! ```
+//! extern crate alloc;
+//!
+//! use yaxpeax_x86::Access;
+//! use yaxpeax_x86::long_mode::{InstDecoder, Operand, RegSpec};
+//!
+//! // `3300` is "xor eax, dword [rax]"
+//! let bytes = &[0x33, 0x00];
+//! let inst = InstDecoder::default().decode_slice(bytes).expect("can decode trivial instructions");
+//!
+//! #[cfg(feature="fmt")]
+//! assert_eq!(inst.to_string(), "xor eax, dword [rax]");
+//!
+//! let behavior = inst.behavior();
+//! let operands = behavior.all_operands().expect("xor eax, eax is not complex");
+//!
+//! let collected: alloc::vec::Vec<(Operand, Access)> = operands.iter().collect();
+//! let expected = alloc::vec![
+//! (Operand::Register { reg: RegSpec::rflags() }, Access::Write),
+//! (Operand::Register { reg: RegSpec::eax() }, Access::ReadWrite),
+//! (Operand::MemDeref { base: RegSpec::rax() }, Access::Read),
+//! ];
+//! assert_eq!(collected, expected);
+//!
+//! #[cfg(feature="unstable")]
+//! {
+//! use yaxpeax_x86::{Exception, PrivilegeLevel};
+//!
+//! assert_eq!(behavior.privilege_level(), Some(PrivilegeLevel::Any));
+//! let exceptions = behavior.exceptions();
+//! assert!(!exceptions.none());
+//! assert!(exceptions.may(Exception::PF));
+//! }
+//! ```
+//!
+//! [`InstBehavior::all_operands()`] and [`InstBehavior::visit_accesses()`] may error with a
+//! [`ComplexOp`]; this enum enumerates instructions either involving architectural state not
+//! expressed in yaxpeax-x86' API, or otherwise related to processor state in a way that simply
+//! considering the operands as-presented would be inaccurate. where possible, documentation on
+//! `ComplexOp` tries to guide users towards how to handle such instructions.
+//!
+//! some behavior information in this module is "unstable", meaning it must be opted into with
+//! `feature = ["unstable"]` on yaxpeax-x86; information from "unstable" interfaces may be
+//! less-tested and change across semver-compatible releases! if you want to use unstable
+//! interfaces here, first: thank you!! please report any issues, and second: consider pinning to a
+//! specific minor version while setting `feature = ["unstable"]` if instruction behavior becoming
+//! more correct might present an issue in your application.
+
+use super::{Instruction, Opcode, Operand, OperandSpec};
+use super::RegSpec;
+
+use crate::behavior::Access;
+#[cfg(any(doc, feature = "unstable"))]
+use crate::behavior::{Exception, ExceptionInfo, PrivilegeLevel};
+
+/// an accessor for run-time characteristics of instructions.
+///
+/// generally, behavior accessors across architectures are expected to have a `behavior()`
+/// entrypoint on a decoded instruction. it is not clear which properties of `behavior()`
+/// generalize across architectures (yet!) but presumably something like `all_operands()` and
+/// `Access` do.
+///
+/// additionally, of note for x86:
+///
+/// * x86 has privilege levels, where some instructions raise an exception when executed in
+/// inappropriate privilege levels. this is expressed by [`InstBehavior::privilege_level()`] and
+/// [`InstBehavior::exceptions()`].
+/// * x86 instructions have the familiar operands from textual disassembly, but many instructions
+/// have other implied operands. in some cases the implied operand is a second memory-accessing
+/// operation (consider `call qword [rcx]`; `qword [rcx]` is one memory access, but the implied
+/// push of a return address is a second memory operation).
+/// * `{,e,r}flags` is often written and sometimes read, but almost never as an explicit source or
+/// destination operand. this can be queried with [`InstBehavior::flags_access()`].
+///
+/// it's also useful to know if implicit and explicit operands are reads, writes, or both, such as
+/// when diagnosing a run-time fault. to iterate over this information, `all_operands().iter()`. or
+/// `visit_accesses(&mut ..)` to collect all operand/access information for this instruction.
+#[derive(Copy, Clone)]
+pub struct InstBehavior<'inst> {
+ inst: &'inst Instruction,
+ behavior: BehaviorDigest,
+}
+
+impl Instruction {
+ /// get a struct to query behaviors of an instruction.
+ ///
+ /// "behaviors" is broad! as of writing, "behavior" covers "implicit and explicit operand
+ /// reads/writes", "possible exceptions", "allowed privilege levels", and "instruction has
+ /// additional semantics not easily expressed by this library".
+ ///
+ /// see the documentation for [`InstBehavior`] as well as the
+ /// [`behavior`][crate::long_mode::behavior] module for more information.
+ pub fn behavior<'inst>(&'inst self) -> InstBehavior<'inst> {
+ let mut behavior = opcode2behavior(&self.opcode);
+
+ if behavior.is_nontrivial() {
+ // mul and imul are incredibly frustrating, with multiple behaviors corresponding to
+ // different encodings with different opcode counts. fix up behaviors here..
+ if self.opcode == Opcode::MUL || (self.opcode == Opcode::IMUL && self.operand_count == 1) {
+ let op_width = if self.operands[0] == OperandSpec::RegMMM {
+ self.regs[1].width()
+ } else {
+ self.mem_size
+ };
+ let ops_idx = match op_width {
+ 1 => MUL_IDX_1OP_BYTE,
+ 2 => MUL_IDX_1OP_WORD,
+ 4 => MUL_IDX_1OP_DWORD,
+ _ /* 8 */ => MUL_IDX_1OP_QWORD,
+ };
+ behavior = behavior
+ .set_implicit_ops(ops_idx);
+ } else if self.opcode == Opcode::IMUL {
+ if self.operand_count == 2 {
+ behavior = behavior
+ .set_operand(0, Access::ReadWrite)
+ .set_operand(1, Access::Read);
+ } else if self.operand_count == 3 {
+ behavior = behavior
+ .set_operand(0, Access::ReadWrite)
+ .set_operand(1, Access::Read)
+ .set_operand(2, Access::Read);
+ }
+ } else if self.opcode == Opcode::DIV || self.opcode == Opcode::IDIV {
+ let op_width = if self.operands[0] == OperandSpec::RegMMM {
+ self.regs[1].width()
+ } else {
+ self.mem_size
+ };
+ let ops_idx = match op_width {
+ 1 => DIV_IDX_1OP_BYTE,
+ 2 => DIV_IDX_1OP_WORD,
+ 4 => DIV_IDX_1OP_DWORD,
+ _ /* 8 */ => DIV_IDX_1OP_QWORD,
+ };
+ behavior = behavior
+ .set_implicit_ops(ops_idx);
+ } else if self.opcode == Opcode::NOP {
+ if self.operand_count == 1 {
+ behavior = behavior
+ .set_operand(0, Access::None);
+ }
+ } else if self.opcode == Opcode::CMPXCHG {
+ let op_width = if self.operands[0] == OperandSpec::RegMMM {
+ self.regs[1].width()
+ } else {
+ self.mem_size
+ };
+ let ops_idx = match op_width {
+ 1 => CMPXCHG_IDX_BYTE,
+ 2 => CMPXCHG_IDX_WORD,
+ 4 => CMPXCHG_IDX_DWORD,
+ _ /* 8 */ => CMPXCHG_IDX_QWORD,
+ };
+ behavior = behavior
+ .set_implicit_ops(ops_idx);
+ } else if self.opcode == Opcode::VMOVLPS || self.opcode == Opcode::VMOVHPS ||
+ self.opcode == Opcode::VMOVLPD || self.opcode == Opcode::VMOVHPD {
+ if self.operand_count == 2 {
+ behavior = behavior
+ .set_operand(0, Access::ReadWrite);
+ } else {
+ behavior = behavior
+ .set_operand(0, Access::Write)
+ .set_operand(2, Access::Read);
+ }
+ } else if self.opcode() == Opcode::EXTRQ {
+ if self.operand_count > 2 {
+ behavior = behavior
+ .set_operand(2, Access::Read);
+ }
+ } else if self.opcode() == Opcode::INSERTQ {
+ if self.operand_count > 2 {
+ behavior = behavior
+ .set_operand(2, Access::Read)
+ .set_operand(3, Access::Read);
+ }
+ } else if self.opcode() == Opcode::RETURN {
+ if self.operand_count != 0 {
+ behavior = behavior
+ .set_operand(0, Access::Read);
+ }
+ } else if self.opcode() == Opcode::RETF {
+ if self.operand_count != 0 {
+ behavior = behavior
+ .set_operand(0, Access::Read);
+ }
+ } else if self.opcode() == Opcode::MULX {
+ // `mulx` is always vex-encoded.
+ if self.prefixes.vex_unchecked().w() {
+ behavior = behavior
+ .set_implicit_ops(MULX_64B_IDX);
+ } else {
+ behavior = behavior
+ .set_implicit_ops(MULX_32B_IDX);
+ }
+ } else if self.opcode() == Opcode::VMASKMOVDQU {
+ // in 64-bit mode, 67 overrides 64-bit addressing down to 32-bit.
+ if self.prefixes.address_size() {
+ behavior = behavior
+ .set_implicit_ops(EDI_MEMWRITE_IDX);
+ } else {
+ behavior = behavior
+ .set_implicit_ops(RDI_MEMWRITE_IDX);
+ }
+ } else if self.opcode() == Opcode::PCMPESTRI || self.opcode() == Opcode::VPCMPESTRI {
+ if self.prefixes.vex_unchecked().w() {
+ behavior = behavior
+ .set_implicit_ops(PCMPESTRI_64B_IDX);
+ } else {
+ behavior = behavior
+ .set_implicit_ops(PCMPESTRI_32B_IDX);
+ }
+ } else if self.opcode() == Opcode::PCMPESTRM || self.opcode() == Opcode::VPCMPESTRM {
+ if self.prefixes.vex_unchecked().w() {
+ behavior = behavior
+ .set_implicit_ops(PCMPESTRM_64B_IDX);
+ } else {
+ behavior = behavior
+ .set_implicit_ops(PCMPESTRM_32B_IDX);
+ }
+ } else if self.opcode() == Opcode::LOOPNZ
+ || self.opcode() == Opcode::LOOPZ
+ || self.opcode() == Opcode::LOOP
+ || self.opcode() == Opcode::JRCXZ {
+ if self.prefixes.rex_unchecked().w() {
+ behavior = behavior
+ .set_implicit_ops(RW_RCX_IDX);
+ } else if self.prefixes.operand_size() {
+ behavior = behavior
+ .set_implicit_ops(RW_CX_IDX);
+ } else {
+ behavior = behavior
+ .set_implicit_ops(RW_ECX_IDX);
+ }
+ } else {
+ // there should never be an unhandled nontrivial opcode, but leave this in so
+ // fuzzing and testing can make sure. this way in normal builds the branch is empty
+ // and compilers can forget all about it for us.
+ #[cfg(feature = "_debug_internal_asserts")]
+ unreachable!();
+ }
+ }
+
+ InstBehavior {
+ inst: self,
+ behavior
+ }
+ }
+}
+
+/// a handle for an instruction, its behavior, and any related implicit operands.
+///
+/// this is only useful for [`InstOperands::iter()`].
+#[derive(Copy, Clone)]
+pub struct InstOperands<'inst> {
+ inst: InstBehavior<'inst>,
+ implicit_ops: &'static [ImplicitOperand],
+}
+
+impl<'inst> InstOperands<'inst> {
+ /// establish an iterator over the operands described in this `InstOperands`.
+ pub fn iter(self) -> AccessIter<'inst> {
+ AccessIter::new(self)
+ }
+}
+
+/// this struct implements [`Iterator`] to allow library users to walk all explicit and implicit
+/// operands for the corresponding instruction, along with if they are used for reading or for
+/// writing.
+///
+/// implicit operands are always walked first, explicit operands are walked last.
+pub struct AccessIter<'inst> {
+ operands: InstOperands<'inst>,
+ explicit: bool,
+ next: u8,
+}
+
+impl<'inst> AccessIter<'inst> {
+ fn new(operands: InstOperands<'inst>) -> Self {
+ Self {
+ operands,
+ explicit: false,
+ next: 0,
+ }
+ }
+
+ /// weaken this iterator to only returning the operands corresponding to this instruction,
+ /// without specific access information.
+ pub fn operands(self) -> OperandIter<'inst> {
+ OperandIter { inner: self }
+ }
+}
+
+impl<'inst> Iterator for AccessIter<'inst> {
+ type Item = (Operand, Access);
+
+ fn next(&mut self) -> Option<Self::Item> {
+ // iteration order is:
+ // * if the instruction accesses flags, report that
+ // * then, walk the implicit operand list
+ // * finally, walk explicit operands
+ // * fin
+
+ // the implicit operand list might be empty, there may be no flags, etc. so we need to
+ // consider the implicit operand iterator states up to two times before falling through to
+ // the first is this the first reported case of buttelement of the explicit operand list (if there is one). using a loop here
+ // seems like the least-gross way to go...
+ while !self.explicit {
+ if self.next == 0 {
+ // we only consider flags at most once; either we're returning a value here or
+ // we're going to keep searching through the loop.
+ self.next += 1;
+ if let Some(acc) = self.operands.inst.flags_access() {
+ if acc != Access::None {
+ return Some((Operand::Register { reg: RegSpec::rflags() }, acc));
+ }
+ }
+ } else {
+ let implicit_idx = self.next - 1;
+ self.next += 1;
+
+ if let Some(entry) = self.operands.implicit_ops.get(implicit_idx as usize) {
+ let access = if entry.write {
+ Access::Write
+ } else {
+ Access::Read
+ };
+ return Some((entry.into_operand(), access));
+ } else {
+ // we've gotten to the end of implicit operands. flip to explicit operands,
+ // reset `next`, and continue searching.
+ self.explicit = true;
+ self.next = 0;
+ }
+ }
+ }
+
+
+ if self.next < self.operands.inst.inst.operand_count() {
+ let op = self.operands.inst.inst.operand(self.next);
+ let access = self.operands.inst.operand_access(self.next)
+ .expect("defined operand has defined access");
+ debug_assert!(
+ access != Access::None || (
+ self.operands.inst.inst.opcode == Opcode::NOP ||
+ self.operands.inst.inst.opcode == Opcode::UD1 ||
+ self.operands.inst.inst.opcode == Opcode::UD0
+ )
+ );
+ let res = Some((op, access));
+ self.next += 1;
+ res
+ } else {
+ None
+ }
+ }
+}
+
+/// a reduced-strength iterator of an instruction's implicit and explicit operands.
+///
+/// unlike `AccessIter`, this iterator does not provide read/write information, simply that
+/// operands are or are not present. this is more likely useful for some kinds of instruction
+/// printing than automated instruction analysis.
+pub struct OperandIter<'inst> {
+ inner: AccessIter<'inst>,
+}
+
+/// enough structure to describe any implicitly-present operand in an x86_64 instruction.
+///
+/// this is (maybe surprisingly, compared to the rest of the isa) relatively tiny: the only
+/// implicit operands to date are register reads/writes, and simple dereference of a register (such
+/// as `[rsp - 8] = ...` in a push).
+// TODO: this needs accessors for the elements or something.
+#[derive(Copy, Clone)]
+pub struct ImplicitOperand {
+ // TODO: not suitable for public API!
+ spec: OperandSpec,
+ reg: RegSpec,
+ disp: i32,
+ write: bool,
+}
+
+impl ImplicitOperand {
+ fn into_operand(self) -> Operand {
+ match self.spec {
+ OperandSpec::RegRRR => {
+ Operand::Register { reg: self.reg }
+ },
+ OperandSpec::Deref => {
+ Operand::MemDeref { base: self.reg }
+ }
+ OperandSpec::Disp => {
+ Operand::Disp { base: self.reg, disp: self.disp }
+ }
+ OperandSpec::Deref_rdi => {
+ Operand::MemDeref { base: RegSpec::rdi() }
+ }
+ // from `xlat` specifically... `base` specifies rbx, infer ax as the index here.
+ OperandSpec::MemIndexScale => {
+ Operand::MemBaseIndexScale {
+ base: self.reg,
+ index: RegSpec::al(),
+ scale: 1
+ }
+ }
+ spec => {
+ panic!("unexpected implicit op: {:?}", spec);
+ }
+ }
+ }
+}
+
+impl<'inst> Iterator for OperandIter<'inst> {
+ type Item = Operand;
+
+ fn next(&mut self) -> Option<Self::Item> {
+ self.inner.next().map(|(op, _acc)| op)
+ }
+}
+
+impl<'inst> InstBehavior<'inst> {
+ #[cfg(any(doc, feature = "unstable"))]
+ /// get the [`PrivilegeLevel`] for this instruction.
+ ///
+ /// returns `None` if no privilege level information is recorded for the instruction. such
+ /// cases are a bug, please report if you see them.
+ pub fn privilege_level(&self) -> Option<PrivilegeLevel> {
+ let pl_bits = self.behavior.behavior & 0b11;
+ const LUT: [Option<PrivilegeLevel>; 4] = [
+ Some(PrivilegeLevel::Any), Some(PrivilegeLevel::PL0),
+ Some(PrivilegeLevel::Special), None,
+ ];
+
+ LUT[pl_bits as usize]
+ }
+
+ #[cfg(any(doc, feature = "unstable"))]
+ /// get the [`ExceptionInfo`] for this instruction.
+ ///
+ /// this is very much best-effort and poorly tested. it is behind the `unstable` feature for a
+ /// reason!
+ pub fn exceptions(&self) -> ExceptionInfo {
+ let mut exceptions = ExceptionInfo::empty();
+ if self.privilege_level() != Some(PrivilegeLevel::Any) {
+ // TODO: is it correct that executing with incorrect CPL always yields #GP?
+ exceptions.set(Exception::GP, true);
+ }
+
+ match self.all_operands() {
+ Ok(op_info) => {
+ exceptions.set(Exception::PF, op_info.iter().operands().any(|x| x.is_memory()));
+ }
+ Err(_complex) => {
+ // TODO: is it correct that all complex ops can page fault? no: wrmsr/rdmsr do not
+ // #PF.
+ exceptions.set(Exception::PF, true);
+ }
+ }
+
+ exceptions
+ }
+
+ /// transform this instruction's [`Opcode`] into a [`ComplexOp`], if the instruction is
+ /// "complex".
+ ///
+ /// documentation on [`ComplexOp`] covers what instructions are considered "complex" by
+ /// yaxpeax-x86 and why in more detail. correct analysis of a function (or program!) in the
+ /// presence of complex instructions may require consulting the Intel Software Developer's
+ /// Manual or AMD Architecture Programmer's Manual.
+ pub fn as_complex_op(&self) -> Option<ComplexOp> {
+ // if the behavior is not complex, it is *definitely* not a complex op. if the behavior is
+ // complex, it's really a "depending on the specific instruction and operands it might
+ // be"...
+ if !self.behavior.is_complex() {
+ return None;
+ }
+
+ if self.inst.opcode == Opcode::BT {
+ if self.inst.operands[0] != OperandSpec::RegMMM {
+ Some(ComplexOp::BT)
+ } else {
+ None
+ }
+ } else if self.inst.opcode == Opcode::BTS {
+ if self.inst.operands[0] != OperandSpec::RegMMM {
+ Some(ComplexOp::BTS)
+ } else {
+ None
+ }
+ } else if self.inst.opcode == Opcode::BTR {
+ if self.inst.operands[0] != OperandSpec::RegMMM {
+ Some(ComplexOp::BTR)
+ } else {
+ None
+ }
+ } else if self.inst.opcode == Opcode::BTC {
+ if self.inst.operands[0] != OperandSpec::RegMMM {
+ Some(ComplexOp::BTC)
+ } else {
+ None
+ }
+ } else {
+ // Safety: every `Opcode` with a `BehaviorDigest` that is `set_complex(true)` has a
+ // corresponding `ComplexOp` variant set to the same integer value, and the two types
+ // agree on repr.
+ let comp: ComplexOp = unsafe { core::mem::transmute::<Opcode, ComplexOp>(self.inst.opcode) };
+ Some(comp)
+ }
+ }
+
+ /// produce an `InstOperands` describing the explicit and implicit operands of this
+ /// instruction.
+ ///
+ /// "explicit" operands are ones that are displayed as part of the instruction's textual
+ /// assembly/disassembly, while "implicit" operands are operands describing the reset of the
+ /// instruction's behavior.
+ ///
+ /// this notion of "implicit operands" does not map precisely to terminology from either the
+ /// Intel SDM or AMD APM. instead, it is provided by `yaxpeax-x86` to try providing an answer
+ /// to some common queries about instructions .
+ pub fn all_operands(&self) -> Result<InstOperands<'inst>, ComplexOp> {
+ if let Some(op) = self.as_complex_op() {
+ return Err(op);
+ }
+
+ let implicit_ops = if let Some(ops) = self.implicit_oplist() {
+ ops
+ } else {
+ &[]
+ };
+
+ Ok(InstOperands {
+ inst: *self,
+ implicit_ops,
+ })
+ }
+
+ /// get the `Access` behavior this instruction has for `rflags`.
+ ///
+ /// note that as the documentation for [`Access`] describes, "read" and "write" have slightly
+ /// different meanings for the flags register than other locations.
+ // this implies that `rflags` must never appear in an implicit operand list.
+ pub fn flags_access(&self) -> Option<Access> {
+ let flag_acc = (self.behavior.behavior >> 2) & 0b11;
+ Access::from_bits(flag_acc)
+ }
+
+ fn implicit_oplist(&self) -> Option<&'static [ImplicitOperand]> {
+ let ops_idx = self.behavior.extra;
+ if ops_idx == 0 {
+ return None;
+ }
+
+ // TODO: ops_idx cannot be out of bounds, so maybe kinda-unchecked here..?
+ Some(&IMPLICIT_OPS_LIST[ops_idx as usize])
+ }
+
+ /// get the `Access` behavor for an explicit operand of this instruction.
+ ///
+ /// `None` means that there is no operand at the given index, while `Some(Access::None)` means
+ /// there is an operand, and the instruction does not actually access it (as for `nop`, `ud0`,
+ /// and `ud1`)
+ pub fn operand_access(&self, idx: u8) -> Option<Access> {
+ if idx >= self.inst.operand_count {
+ return None;
+ }
+
+ let op_acc = (self.behavior.operand_access >> (2 * idx)) & 0b11;
+ Access::from_bits(op_acc)
+ }
+
+ /// iterate all operands in the instruction and report them to the provided `AccessVisitor`.
+ ///
+ /// this is a more informative, but somewhat more specialized, interface than simply iterating
+ /// [`InstBehavior::all_operands()`]. for memory operands, address calculations are reported to
+ /// the access visitor as reads of the relevant registers. if all dependent values are
+ /// available, the resulting effective address is computed and reported as part of the memory
+ /// access.
+ ///
+ /// `visit_accesses()` is slightly more efficient in this than iterating `all_operands()` as
+ /// well, as it uses unstable internal representations directly, rather than converting to API
+ /// types and back for every operand.
+ pub fn visit_accesses<T: AccessVisitor>(&self, v: &mut T) -> Result<(), ComplexOp> {
+ if let Some(op) = self.as_complex_op() {
+ return Err(op);
+ }
+
+ fn compute_addr<T: AccessVisitor>(v: &mut T, inst: &Instruction, op_spec: OperandSpec) -> Option<u64> {
+ #[cfg(feature = "_debug_internal_asserts")]
+ if !op_spec.is_memory() {
+ panic!("expected memory operand but got {:?}", op_spec);
+ }
+
+ match op_spec {
+ OperandSpec::Deref |
+ OperandSpec::Deref_mask => {
+ v.get_register(inst.regs[1])
+ }
+ OperandSpec::Deref_rdi => {
+ v.get_register(RegSpec::rdi())
+ }
+ OperandSpec::Deref_rsi => {
+ v.get_register(RegSpec::rsi())
+ }
+ OperandSpec::Deref_edi => {
+ v.get_register(RegSpec::edi())
+ }
+ OperandSpec::Deref_esi => {
+ v.get_register(RegSpec::esi())
+ }
+ OperandSpec::Disp => {
+ let base = v.get_register(inst.regs[1]);
+ base.map(|addr| addr.wrapping_add(inst.disp as i32 as i64 as u64))
+ }
+ OperandSpec::Disp_mask => {
+ let base = v.get_register(inst.regs[1]);
+ base.map(|addr| addr.wrapping_add(inst.disp as i32 as i64 as u64))
+ }
+ OperandSpec::MemIndexScale => {
+ let index = v.get_register(inst.regs[2]);
+ index.map(|addr| {
+ addr
+ .wrapping_mul(inst.scale as u64)
+ })
+ }
+ OperandSpec::MemIndexScale_mask => {
+ let index = v.get_register(inst.regs[2]);
+ index.map(|addr| {
+ addr
+ .wrapping_mul(inst.scale as u64)
+ })
+ }
+ OperandSpec::MemIndexScaleDisp => {
+ let index = v.get_register(inst.regs[2]);
+ index.map(|addr| {
+ addr
+ .wrapping_mul(inst.scale as u64)
+ .wrapping_add(inst.disp as i32 as i64 as u64)
+ })
+ }
+ OperandSpec::MemIndexScaleDisp_mask => {
+ let index = v.get_register(inst.regs[2]);
+ index.map(|addr| {
+ addr
+ .wrapping_mul(inst.scale as u64)
+ .wrapping_add(inst.disp as i32 as i64 as u64)
+ })
+ }
+ OperandSpec::MemBaseIndexScale => {
+ let base = v.get_register(inst.regs[1]);
+ let index = v.get_register(inst.regs[2]);
+ base.and_then(|base| {
+ index.map(|index| {
+ base
+ .wrapping_add(index.wrapping_mul(inst.scale as u64))
+ })
+ })
+ }
+ OperandSpec::MemBaseIndexScale_mask => {
+ let base = v.get_register(inst.regs[1]);
+ let index = v.get_register(inst.regs[2]);
+ base.and_then(|base| {
+ index.map(|index| {
+ base
+ .wrapping_add(index.wrapping_mul(inst.scale as u64))
+ })
+ })
+ }
+ OperandSpec::MemBaseIndexScaleDisp => {
+ let base = v.get_register(inst.regs[1]);
+ let index = v.get_register(inst.regs[2]);
+ base.and_then(|base| {
+ index.map(|index| {
+ base
+ .wrapping_add(index.wrapping_mul(inst.scale as u64))
+ .wrapping_add(inst.disp as i32 as i64 as u64)
+ })
+ })
+ }
+ OperandSpec::MemBaseIndexScaleDisp_mask => {
+ let base = v.get_register(inst.regs[1]);
+ let index = v.get_register(inst.regs[2]);
+ base.and_then(|base| {
+ index.map(|index| {
+ base
+ .wrapping_add(index.wrapping_mul(inst.scale as u64))
+ .wrapping_add(inst.disp as i32 as i64 as u64)
+ })
+ })
+ }
+ OperandSpec::DispU64 => {
+ Some(inst.disp)
+ }
+ OperandSpec::DispU32 => {
+ Some(inst.disp as u32 as u64)
+ }
+ other => {
+ // this could be `_debug_internal_assertions`-gated, but i'm not quite that
+ // confident yet..
+ panic!("not-yet-handled memory operand: {:?}", other);
+ }
+ }
+ }
+
+ if let Some(implicit_oplist) = self.implicit_oplist() {
+ for op in implicit_oplist.iter() {
+ if op.spec == OperandSpec::RegRRR {
+ if op.write {
+ v.register_write(op.reg);
+ } else {
+ v.register_read(op.reg);
+ }
+ } else if op.spec == OperandSpec::Deref_rdi {
+ // Deref_rdi is used for string instructions; operand-size overrides apply here
+ // and so the register that is incremented (or decremented!) depends on the
+ // operand-size prefix. the register is correct for the operands, so we'll
+ let reg = match self.inst.operands[op.disp as usize] {
+ OperandSpec::Deref_rdi => RegSpec::rdi(),
+ OperandSpec::Deref_rsi => RegSpec::rsi(),
+ OperandSpec::Deref_edi => RegSpec::edi(),
+ OperandSpec::Deref_esi => RegSpec::esi(),
+ OperandSpec::Deref => self.inst.regs[1],
+ other => {
+ // this could be `_debug_internal_assertions`-gated, but i'm not quite
+ // that confident yet..
+ panic!("TODO: unreachable {:?}", other);
+ }
+ };
+ if op.write {
+ v.register_write(reg);
+ } else {
+ v.register_read(reg);
+ }
+ } else {
+ let addr = match op.spec {
+ OperandSpec::Deref => {
+ v.get_register(op.reg)
+ },
+ OperandSpec::Disp => {
+ if let Some(base) = v.get_register(op.reg) {
+ Some(base.wrapping_add(op.disp as i64 as u64))
+ } else {
+ None
+ }
+ }
+ OperandSpec::MemIndexScale => {
+ // HACK HACK HACK this is just how i've decided to interpret
+ // `MemIndexScale` as an operand spec; it's only for xlat. adding
+ // another field to implicit operands just for this is a little silly..
+ let base = v.get_register(op.reg);
+ let index = v.get_register(RegSpec::al());
+ if let (Some(base), Some(index)) = (base, index) {
+ Some(base.wrapping_add(index as u64))
+ } else {
+ None
+ }
+ }
+ other => {
+ // this could be `_debug_internal_assertions`-gated, but i'm not quite
+ // that confident yet..
+ panic!("impossible operand spec {:?}", other);
+ }
+ };
+
+ let size = self.inst.mem_size().expect("memory operand implies memory access size")
+ .bytes_size().expect("non-complex instructions have well-defined bytes_size()");
+
+ if op.write {
+ v.memory_write(addr, size as u32);
+ } else {
+ v.memory_read(addr, size as u32);
+ }
+ }
+ }
+ }
+
+ if let Some(acc) = self.flags_access() {
+ if acc.is_read() {
+ v.register_read(RegSpec::rflags());
+ }
+ if acc.is_write() {
+ v.register_write(RegSpec::rflags());
+ }
+ }
+
+ let operand_access = self.behavior.operand_access;
+
+ for i in 0..self.inst.operand_count {
+ let access = Access::from_bits((operand_access >> (i * 2)) & 0b11)
+ .expect("selected only low two bits");
+ let op_spec = self.inst.operands[i as usize];
+
+ if access.is_read() {
+ match op_spec {
+ OperandSpec::RegRRR => {
+ v.register_read(self.inst.regs[0]);
+ }
+ OperandSpec::RegMMM => {
+ v.register_read(self.inst.regs[1]);
+ }
+ OperandSpec::RegVex => {
+ v.register_read(self.inst.regs[3]);
+ }
+ OperandSpec::Reg4 => {
+ let spec = RegSpec {
+ num: self.inst.imm as u8,
+ bank: self.inst.regs[3].bank,
+ };
+ v.register_read(spec);
+ }
+ OperandSpec::RegRRR_maskmerge |
+ OperandSpec::RegRRR_maskmerge_sae |
+ OperandSpec::RegRRR_maskmerge_sae_noround => {
+ v.register_read(self.inst.regs[0]);
+ if self.inst.prefixes.evex_unchecked().mask_reg() != 0 {
+ v.register_read(RegSpec::mask(self.inst.prefixes.evex_unchecked().mask_reg()));
+ }
+ }
+ OperandSpec::RegMMM_maskmerge |
+ OperandSpec::RegMMM_maskmerge_sae_noround => {
+ v.register_read(self.inst.regs[1]);
+ if self.inst.prefixes.evex_unchecked().mask_reg() != 0 {
+ v.register_read(RegSpec::mask(self.inst.prefixes.evex_unchecked().mask_reg()));
+ }
+ }
+ OperandSpec::RegVex_maskmerge => {
+ v.register_read(self.inst.regs[3]);
+ if self.inst.prefixes.evex_unchecked().mask_reg() != 0 {
+ v.register_read(RegSpec::mask(self.inst.prefixes.evex_unchecked().mask_reg()));
+ }
+ }
+ OperandSpec::ImmI8 |
+ OperandSpec::ImmU8 |
+ OperandSpec::ImmI16 |
+ OperandSpec::ImmU16 |
+ OperandSpec::ImmI32 |
+ OperandSpec::ImmI64 |
+ OperandSpec::ImmInDispField => {
+ // no register/memory access to report.
+ }
+ other => {
+ // compute effective address...
+ let addr = compute_addr(v, &self.inst, op_spec);
+ let size = self.inst.mem_size().expect("memory operand implies memory access size")
+ .bytes_size().expect("non-complex instructions have well-defined bytes_size()");
+ if other.is_masked() && self.inst.prefixes.evex_unchecked().mask_reg() != 0 {
+ v.register_read(RegSpec::mask(self.inst.prefixes.evex_unchecked().mask_reg()));
+ }
+ // `lea` *just* computes the effective address, which we've done above.
+ // othrwise, the instruction will actually read this memory operand.
+ if self.inst.opcode != Opcode::LEA {
+ v.memory_read(addr, size as u32);
+ }
+ }
+ }
+ }
+
+ if access.is_write() {
+ // given a register `reg` that an instruction writes, expand it for the purposes of
+ // tracking register writes. x86 zero-extends writes to 32-bit GPRs into 64-bit GPR
+ // writes, so replicate that here.
+ fn apply_x86_zext(mut reg: RegSpec) -> RegSpec {
+ use super::RegisterBank;
+ if reg.bank == RegisterBank::D {
+ reg.bank = RegisterBank::Q;
+ }
+ reg
+ }
+ match op_spec {
+ OperandSpec::RegRRR => {
+ v.register_write(apply_x86_zext(self.inst.regs[0]));
+ }
+ OperandSpec::RegMMM => {
+ v.register_write(apply_x86_zext(self.inst.regs[1]));
+ }
+ OperandSpec::RegVex => {
+ v.register_write(apply_x86_zext(self.inst.regs[3]));
+ }
+ OperandSpec::Reg4 => {
+ let spec = RegSpec {
+ num: self.inst.imm as u8,
+ bank: self.inst.regs[3].bank,
+ };
+ v.register_write(apply_x86_zext(spec));
+ }
+ OperandSpec::RegRRR_maskmerge |
+ OperandSpec::RegRRR_maskmerge_sae |
+ OperandSpec::RegRRR_maskmerge_sae_noround => {
+ if self.inst.prefixes.evex_unchecked().mask_reg() != 0 {
+ v.register_read(RegSpec::mask(self.inst.prefixes.evex_unchecked().mask_reg()));
+ }
+ v.register_write(self.inst.regs[0]);
+ }
+ OperandSpec::RegMMM_maskmerge |
+ OperandSpec::RegMMM_maskmerge_sae_noround => {
+ if self.inst.prefixes.evex_unchecked().mask_reg() != 0 {
+ v.register_read(RegSpec::mask(self.inst.prefixes.evex_unchecked().mask_reg()));
+ }
+ v.register_write(self.inst.regs[1]);
+ }
+ OperandSpec::RegVex_maskmerge => {
+ if self.inst.prefixes.evex_unchecked().mask_reg() != 0 {
+ v.register_read(RegSpec::mask(self.inst.prefixes.evex_unchecked().mask_reg()));
+ }
+ v.register_write(self.inst.regs[3]);
+ }
+ OperandSpec::ImmI8 |
+ OperandSpec::ImmU8 |
+ OperandSpec::ImmI16 |
+ OperandSpec::ImmU16 |
+ OperandSpec::ImmI32 |
+ OperandSpec::ImmI64 |
+ OperandSpec::ImmInDispField => {
+ // no register/memory access to report.
+ }
+ other => {
+ // compute effective address...
+ let addr = compute_addr(v, &self.inst, op_spec);
+ let size = self.inst.mem_size().expect("memory operand implies memory access size")
+ .bytes_size().expect("non-complex instructions have well-defined bytes_size()");
+ if other.is_masked() && self.inst.prefixes.evex_unchecked().mask_reg() != 0 {
+ v.register_read(RegSpec::mask(self.inst.prefixes.evex_unchecked().mask_reg()));
+ }
+ // no lea check necessary: its memory access is coded as a read and no
+ // instruction has a similar "fake" memory write.
+ v.memory_write(addr, size as u32);
+ }
+ }
+ }
+ }
+
+ Ok(())
+ }
+}
+
+#[derive(Copy, Clone, PartialEq, Debug)]
+struct BehaviorDigest {
+ // laid out like:
+ //
+ // |7 6|5 4|3 2|1 0|
+ // |_ _ C N|FL |PL |
+ //
+ // C: complex (this instruction *may* N: non-trivial (implementation detail of constructing
+ // the instruction's behavior digest)
+ // N: non-trivial (implementation detail of constructing the instruction's behavior digest)
+ // FL: access bits for {,e,r}flags
+ // PL: privilege level this instruction can be executed.
+ // 00 -> all levels
+ // 01 -> CPL=0 only
+ // 10 -> something more complicated (instruction-specific)
+ // 11 -> reserved
+ behavior: u8,
+ // for the up-to four explicit operands in an x86 instruction.
+ //
+ // bits are pairs interpreted as described in `enum Access`. operand count on the instruction
+ // describes validity of these bits: fields left `00` must not have a corresponding operand at
+ // that offset. fields with no corresponding operand may have bits set.
+ operand_access: u8,
+ // selector for a `&'static [Operand]` of additional "implicit" operands for the instruction.
+ extra: u16,
+}
+
+impl BehaviorDigest {
+ const fn empty() -> BehaviorDigest {
+ BehaviorDigest {
+ behavior: 0,
+ operand_access: 0,
+ extra: 0
+ }
+ }
+
+ const fn set_pl0(mut self) -> Self {
+ self.behavior &= 0b11_11_11_00;
+ self.behavior |= 0b00_00_00_01;
+ self
+ }
+
+ const fn set_pl_any(mut self) -> Self {
+ self.behavior &= 0b11_11_11_00;
+ self.behavior |= 0b00_00_00_00;
+ self
+ }
+
+ const fn set_pl_special(mut self) -> Self {
+ self.behavior &= 0b11_11_11_00;
+ self.behavior |= 0b00_00_00_10;
+ self
+ }
+
+ const fn set_flags_access(mut self, access: Access) -> Self {
+ self.behavior &= 0b11_11_00_11;
+ self.behavior |= (access as u8) << 2;
+ self
+ }
+
+ const fn set_operand(mut self, idx: u8, access: Access) -> Self {
+ assert!(idx < 4);
+ let offset = idx * 2;
+ self.operand_access &= !(0b11 << offset);
+ self.operand_access |= (access as u8) << offset;
+ self
+ }
+
+ const fn set_implicit_ops(mut self, ops_idx: u16) -> Self {
+ // TODO: this needs much less than a full u16 (much less than |Opcode| even)
+ self.extra = ops_idx;
+ self
+ }
+
+ const fn set_nontrivial(mut self, state: bool) -> Self {
+ self.behavior &= 0b11_10_11_11;
+ self.behavior |= (state as u8) << 4;
+ self
+ }
+
+ const fn is_nontrivial(&self) -> bool {
+ (self.behavior & (1 << 4)) != 0
+ }
+
+ const fn set_complex(mut self, state: bool) -> Self {
+ self.behavior &= 0b11_01_11_11;
+ self.behavior |= (state as u8) << 5;
+ self
+ }
+
+ const fn is_complex(&self) -> bool {
+ (self.behavior & (1 << 5)) != 0
+ }
+}
+
+/// a subset of [`Opcode`] where access patterns cannot be expressed as a simple stream of reads or
+/// writes. in many cases these instructions are only executable when `CPL=0` but notable
+/// exceptions are `rep`-prefixed string instructions (`rep movs`, `rep stos`, etc).
+///
+/// complex instructions and appropriate handling are documented on a best-effort basis below.
+///
+/// ### `rdmsr`, `wrmsr`, and all other instructions that directly read or write MSRs
+///
+/// the library API has no way to express MSRs as read or written locations, and must defer to user
+/// code to track reads or writes of this state.
+///
+/// "other instructions" include `wrfsbase`, `wrgsbase`, `rdfsbase`, `rdgsbase`, `syscall`,
+/// `sysenter`, `rdpru`, `rdtsc`, and others.
+///
+/// ### the `xsave` family
+///
+/// this section applies for all of `xsave`, `xsaveopt`, `xsavec`, `xsavec64`, `xsaves`,
+/// `xsaves64`, `xrstor`, `xrstors`, `xrstors64`, as well as many other instructions operating on
+/// bulk processor state and `mxcsr`.
+///
+/// these instructions are considered "complex" because the actual amount of data read or written
+/// depends on dynamic processor state, specifically, bits in `xcr0`. further, the upper bound of
+/// data read or written by these instructions is *also* processor-dependent - each architecture
+/// extension that adds processor state tends to have a corresponding bit opting it in or out of
+/// xsave state with these instructions.
+///
+/// gaps between enabled feature bits are possible and it would be legal for processors to change
+/// xsave layout based on enabled feature bits as well. no processor *does* this, but i'm not
+/// assuming this layout is fixed only to leave you holding the bag!
+///
+/// see the Intel SDM chapter `13.1 XSAVE-Supported Features And State-Component Bitmaps` for more
+/// details.
+///
+/// other related instructions, like `fnstenv`, `frstor`, and others, simply save and restore
+/// architectural state that is not expressed in the library API and cannot be included in implicit
+/// operand lists (such as `mxcsr`).
+///
+/// ### `in`, `out`, including rep-prefixed forms
+///
+/// port I/O instructions use a register or immedate to select an I/O port, meaning the literal
+/// operand and architectural operation are totally distinct. the library API does not currently
+/// have an operand form for I/O ports, so these instructions are "complex".
+///
+/// ### rep-prefixed string instructions
+///
+/// this section applies for all of `rep movs`, `rep stos`, `rep lods`, `rep scas`, and `rep cmps`.
+///
+/// these instructions are considered "complex" for two reasons. first, while these are single
+/// instructions, they do not execute atomically. then even if they were executed in a single
+/// architectural state update, the size of the memory access is a function of `rcx`.
+///
+/// worse, different processors can execute these instructions somewhat differently. from the Intel
+/// SDM:
+///
+/// > 7.3.9.3 Fast-String Operation
+/// >
+/// > [...] Instructions using fast-string operation effectively operate on the string in groups
+/// > that may include multiple elements of the native data size (byte, word, doubleword, or
+/// > quadword). With fast-string operation, the processor recognizes interrupts and data
+/// > breakpoints only on boundaries between these groups. [...]
+///
+/// the number of multiple elements is not defined, nor is the unit size of a fast string operation
+/// group, and that interrupts and breakpoints are only recognized on boundaries between these
+/// groups of implementation-defined size. even considering `rep`-prefixed string instructions as a
+/// series of the instruction data size is incorrect if the instruction's initial conditions are
+/// eligible for `Fast-String Operation` acceleration.
+///
+/// correctly interpreting the access pattern of these instructions depends heavily on the
+/// application needing this information.
+///
+/// ### AVX512 scatter/gather instructions
+///
+/// this section applies for all of `vpscatter{dd,dq,qd,qq}` and `vpgather{dd,dq,qd,qq}`.
+/// additionally, the vector scatter/gather prefetch instructions
+/// `v{gather,scatter}pf0{dps,dpd,qps,qpd}` are complex in part for these reasons.
+///
+/// these instructions are considered "complex" because their memory access characteristics are
+/// actually to many memory addresses using the lanes of the vector register used as an index in
+/// the memory operand. consider `vpscatterdd [r15 + ymm25], k6, ymm10`; this instruction has
+/// accesses four memory locations, one for each dword lane in `ymm25` as an offset to `r15`.
+///
+/// `yaxpeax-x86` does not have a way to name individual lanes of vector registers and may not ever
+/// add one if this is the only use. therefore there is no way to express these memory accesses and
+/// the instructions are considered complex.
+///
+/// the vector scatter/gather instructions additionally are complex for the same reasons as
+/// prefetch instructions described below.
+///
+/// ### `monitor`, `monitorx`, `mwait`, `mwaitx`
+///
+/// these instructions reference memory but neither read nor write it. instead, `monitor` primes
+/// hardware to watch for accesses to the specified address, while `mwait` waits for an access to
+/// some earlier `monitor`-primed adddress. this address-monitoring hardware is not expressed in
+/// the library API and makes this family of instructions "complex" due to reading or writing
+/// unrepresented state.
+///
+/// arguably `monitor` could be described as a load; it sets the A-bit in page tables, is ordered
+/// as a load, and is subject to the permission checking associated with a byte load.
+///
+/// ### `syscall/sysret`, `sysenter/sysexit`
+///
+/// these instructions are considered "complex" because they include implicit reads and possibly
+/// writes to various MSRs. further, depending on dynamic processor state (i.e. "is FRED enabled")
+/// these instructions may behave quite differently than a "normal" shuffling of
+/// `rip`/`rflags`/`cs`.
+///
+/// ### `vmread`, `vmwrite`, `vmrun`, `vmsave`, `vmload`, and SVM/VMX generally
+///
+/// for instructions that *have* an operand, their operand's semantics differs substantially from a
+/// "normal" understanding of the literal operand.
+///
+/// for `vmread` and `vmwrite`, the memory operand may be `[rax]`, but it is implicitly an access
+/// to the current VMCS - and, indeed, not even an access to "memory".
+///
+/// for `vmrun`, `vmsave`, and `vmload`, the operand is "`rax`", but expects `rax` to carry a
+/// physical address to a VMCB which is then loaded from or stored into.
+///
+/// generally, SVM/VMX instructions operate on a hidden VMCB/VMCS structure and are "complex" for
+/// interacting with architectural state that is not expressed in library APIs.
+///
+/// ### `vzeroupper`, `vzeroall`
+///
+/// these instructions are considered "complex" because their actual effect varies by processor
+/// implementation. when AVX512 is supported, these operate on the `zmm*` registers, otherwise they
+/// operate on `ymm*`.
+///
+/// ### `prefetchnta`, `prefetcht2`, `prefetcht1`, `prefetcht0`
+///
+/// these instructions are considered "complex" because they are hints, but have effects on
+/// microarchitectural state. the memory operand is documented as a 1-byte access, reported as a
+/// 32-byte access, but practically is implementation-defined and "a minimum of 32 bytes". the
+/// memory operand needs not be a valid address, either, and if it is not a mapped address then the
+/// `prefetch*` instructions do not raise a #PF.
+///
+/// in architectural terms, these instructions could have an operand access form of `Access::None`,
+/// but due to the microarchitectural effects this would be misleading. so, these are "complex" and
+/// should be handled by user code as a no-op, or read, or access hint, etc.
+///
+/// ### `clzero`, `clflush`, `clflushopt`, `clwb`
+///
+/// these instructions are "complex" because the amount of memory that is operated on is
+/// processor-dependent and the accessed address is *not* simply the effective address of the
+/// memory operand.
+///
+/// the size of an x86 cache line is _typically_ 64 bytes, but is reported per-processor in CPUID
+/// information (leaf `eax=1`: `clflush line size`, AMD leaf `eax=8000_0005`: `cache line size`).
+///
+/// some x86 processors have had 32-byte cache lines.
+///
+/// `clflush`, `clflushopt`, and `clwb` are closer to a no-op in terms of architectural state. they
+/// are included as "complex" for the reasons above and in support of library uses which want to
+/// precisely model memory, such as in modeling the execution of multi-processor systems.
+///
+/// ### `bts`, `btc`, `bt`
+///
+/// these instructions are *conditionally* "complex". when the destination is a memory operand they
+/// are complex because the effective address of the modified word/dword/qword is a function of
+/// both operands of the instruction.
+///
+/// in particular, the accessed location is the word/dword/qword at the first operand's effective
+/// address *plus* the second operand divided by the access size. as a worked example with a dword
+/// access:
+/// ```text
+/// rax := 0x1_0000_0100
+/// rcx := 0x203
+///
+/// // bts dword [rax], ecx
+/// ptr = rax + (rcx / 32) ; 0x1_0000_0303
+/// bit = rcx % 32 ; 3
+/// cf := (*ptr >> bit) & 1
+/// *ptr |= (1 << bit)
+///
+/// this was very dismaying to learn! the library API has no hope of expressing this! but the fact
+/// that the test harness detected this is strong evidence it works...
+/// ```
+///
+/// ### `enqcmd`, `enqcmds`
+///
+/// these instructions use "enqueue stores" to write to what are expected to be "enqueue registers"
+/// via MMIO. additionally, i do not have hardware to test these against "normal" memory, so these
+/// are "complex" out of caution.
+///
+/// ### CET-related instructions (`wrss`, `incssp`, `clrssbsy`, etc)
+///
+/// CET-related instructions manipulate shadow stack state, which is a kind-of-hidden architectural
+/// state that is not expressed in the library API. these instructions are considered "complex" due
+/// to reading or writing that state.
+///
+/// ### `ltr`, `str`, `lldt`, `sldt`, `lidt`, `sidt`, `lgdt`, `sgdt`, (AMD: `clgi`, `stgi`)
+///
+/// these instructions all directly manage architectural state which is not expressed in the
+/// library API.
+///
+/// ### `xgetbv`/`xsetbv`
+///
+/// these instructions operate on `xcr*` registers (namely, `xcr0`), which is not currently
+/// expressible in the library API, so these are considered "complex".
+///
+/// ### `v4f*madd`
+///
+/// `v4f*` family multiply-add instructions operate on ranges of registers that are not (currently)
+/// expressed precisely in the library API; the {x,y,z}mm register set these operate on is obtained by
+/// "mask the low two bits of the SIMD register, the result is the base of and the next three are
+/// the rest of the bank". this *could* be expressed in the library API but seems like it would be
+/// awkward. the instructions seem uncommon, so they are "complex" for expediency.
+///
+/// ### `movdir64b`
+///
+/// movdir64b is considered complex primarily because it has two memory operands, but the
+/// destination operand (first, in Intel syntax) is expressly *not* a memory operand so far as
+/// syntax is concerned.
+///
+/// ### `hreset`
+///
+/// `hreset` manages microarchitectural processor history, but is considered "complex" somewhat
+/// arbitrarily as its sole responsibility is to operate on state that is not expressed in the
+/// library API.
+///
+/// ### `psmash`, `pvalidate`, `rmpadjust`, `rmpupdate`
+///
+/// `psmash`-related instructions depend on architectural state which is described in more depth
+/// above, but not currently expressed in the library API, so they are "complex".
+///
+/// ### `ptwrite`
+///
+/// `ptwrite` modifies processor state that is not expressed in the library API currently, so it
+/// is "complex".
+///
+/// ### Restricted Transactional Memory (RTM)
+///
+/// `xbegin`, `xend`, `xtest`, and `xabort` are all "complex" because the RTM instructions relate
+/// to architectural state for memory transactions which are not expressed in the library API.
+/// additionally, these instructions have consequences for control flow that are not easily
+/// expressed in the library API.
+///
+/// ### `pconfig`
+///
+/// `pconfig` is "complex" because it alters architectural state and has complex semantics. the
+/// instruction is similar to `getsec` or `cpuid` in intended breadth and like `cpuid` could
+/// perhaps be made non-complex on the expectation that library users interested in *this
+/// instruction* would look for the opcode instead.
+///
+/// out of caution, and because this is a CPL=0-only relatively-rare instruction, this is still
+/// "complex".
+///
+/// ### `bndldx`, `bndstx`
+///
+/// these MPX instructions are "complex" because the interpretation of their operands differs
+/// substantially from the typical meaning, and they interact with architectural state (bounds
+/// table entries, "BTEs") that is not expressed in the library API.
+///
+/// ### `iret`, `iretd`, `iretq`
+///
+/// interrupt return instructions are considered "complex" purely for their semantics being, well,
+/// complex. they interact with the current execution mode, privilege level, requested privilege
+/// level of returned-to segments, and shadow stacks.
+///
+/// most architectural state they interact with is expressed in the library API. these are
+/// difficult to consider "complex" by the general guidelines above. in truth, they are complex
+/// mostly because they are uncommon, typically executed at CPL=0, and more difficult to
+/// comprehensively test. these may stop being considered complex in a future release.
+///
+/// ### OSPKE
+///
+/// `rdpkru` and `wrpkru` are considered complex because these instructions operate on the `pkru`
+/// register, which is not expressed in the library API today. these may stop being considered
+/// complex in a future release, at which point `pkru` would be an implicit operand as appropriate.
+///
+/// ### `rsm`
+///
+/// this instruction is considered complex for a few related reasons:
+///
+/// * yours truly does not really know much about SMM at all, so it's not clear if there are
+/// architectural state gotchas involved in transitioning to/from SMM
+/// * yours truly is not sure how much state is covered by the processor state save/restore on SMM
+/// transition, and has no way to validate if any implicit operand list describing the
+/// reads/writes is correct.
+///
+/// you know how to test SMM transitions and returns, please write me!
+///
+/// ### WAITPKG
+///
+/// `tpause`, `umonitor`, and `umwait` are considered complex for different reasons:
+///
+/// * `umonitor` and `umwait` are complex in similar ways to `monitor` and `mwait`.
+/// * `tpause` is considered "complex" because the implicit operands are compared with the TSC; one
+/// might imagine the library would report an implicit read of the TSC MSR, but there is no
+/// library API to describe MSR accesses yet.
+///
+/// ### UINTR
+///
+/// UINTR-related instructions are considered complex for varied reasons:
+///
+/// * `stui`, `clui`, `testui`: these instructions manipulate a bit in `rflags` and probably do not
+/// need to be complex (similar to `sti`, `cli`). these may lose their "complex" status in a future
+/// release.
+/// * `senduipi`: this instruction is "complex" because the user-IPI mechanism involves the
+/// user-interrupt target table (UITT) and referenced user posted-interrupt descriptor (UPID).
+/// * `uiret`: this instruction is only "complex" because it is considered uncommon (for now?),
+/// this author has no hardware to test it on, and it's not immediately clear how this relates to
+/// a corresponding UPID (if i've even read the documentation correctly!)
+///
+/// ### TDX
+///
+/// TDX-related instructions are considered complex because they are not more precisely tested and
+/// are assumed as-complex-as-VMX in the first place.
+// TODO: this could be declared through a macro that does something like:
+// "declare_opcode_subset! { }" which gets a list of identifiers and generates the
+// `Opcode::<ident> as u32` rhs. but a vim macro will do for now.
+#[non_exhaustive]
+#[repr(u32)] // same repr as `Opcode`
+#[derive(Copy, Clone, Debug)]
+#[allow(missing_docs)]
+pub enum ComplexOp {
+ /// rdmsr/wrmsr are considered "complex" for reasons in the enum doc comment.
+ RDMSR = (Opcode::RDMSR as u32),
+ WRMSR = (Opcode::WRMSR as u32),
+
+ /// `rdtsc` and `rdtscp` read MSRs and can be modeled as a special form of `rdmsr`; they are
+ /// "complex" in the same way.
+ RDTSC = (Opcode::RDTSC as u32),
+ RDTSCP = (Opcode::RDTSCP as u32),
+
+ /// `rdpru` reads MSRs and can be modeled as a special form of `rdmsr`; it is "complex" in the
+ /// same way.
+ RDPRU = (Opcode::RDPRU as u32),
+
+ /// instructions interacting with MSRs, such as these (`IA32_FS_BASE`, `IA32_GS_BASE`,
+ /// `IA32_KERNEL_GS_BASE`) are complex for the moment.
+ SWAPGS = (Opcode::SWAPGS as u32),
+ RDFSBASE = (Opcode::RDFSBASE as u32),
+ WRFSBASE = (Opcode::WRFSBASE as u32),
+ RDGSBASE = (Opcode::RDGSBASE as u32),
+ WRGSBASE = (Opcode::WRGSBASE as u32),
+
+ /// the bulk processor state save/restore instructions, as well as `mxcsr`-related
+ /// instructions, are considered complex for reasons described under `fxsave` in the enum doc
+ /// comment above.
+ FRSTOR = (Opcode::FRSTOR as u32),
+ FLDENV = (Opcode::FLDENV as u32),
+ FNSTENV = (Opcode::FNSTENV as u32),
+ FNSAVE = (Opcode::FNSAVE as u32),
+ FNSTCW = (Opcode::FNSTCW as u32),
+ FNSTSW = (Opcode::FNSTSW as u32),
+ FXSAVE = (Opcode::FXSAVE as u32),
+ FXRSTOR = (Opcode::FXRSTOR as u32),
+ LDMXCSR = (Opcode::LDMXCSR as u32),
+ VLDMXCSR = (Opcode::VLDMXCSR as u32),
+ STMXCSR = (Opcode::STMXCSR as u32),
+ VSTMXCSR = (Opcode::VSTMXCSR as u32),
+ XSAVE = (Opcode::XSAVE as u32),
+ XSAVEC = (Opcode::XSAVEC as u32),
+ XSAVES = (Opcode::XSAVES as u32),
+ XSAVEC64 = (Opcode::XSAVEC64 as u32),
+ XSAVES64 = (Opcode::XSAVES64 as u32),
+ XRSTOR = (Opcode::XRSTOR as u32),
+ XRSTORS = (Opcode::XRSTORS as u32),
+ XRSTORS64 = (Opcode::XRSTORS64 as u32),
+ XSAVEOPT = (Opcode::XSAVEOPT as u32),
+
+ /// in/out are considered "complex" for reasons in the enum doc comment.
+ IN = (Opcode::IN as u32),
+ OUT = (Opcode::OUT as u32),
+
+ /// string instructions are considered "complex" for reasons in the enum doc comment.
+ MOVS = (Opcode::MOVS as u32),
+ STOS = (Opcode::STOS as u32),
+ LODS = (Opcode::LODS as u32),
+ SCAS = (Opcode::SCAS as u32),
+ CMPS = (Opcode::CMPS as u32),
+
+ /// scatter/gather instructions are considered "complex" for reasons in the enum doc comment.
+ VPGATHERDD = (Opcode::VPGATHERDD as u32),
+ VPGATHERDQ = (Opcode::VPGATHERDQ as u32),
+ VPGATHERQD = (Opcode::VPGATHERQD as u32),
+ VPGATHERQQ = (Opcode::VPGATHERQQ as u32),
+ VGATHERDPD = (Opcode::VGATHERDPD as u32),
+ VGATHERDPS = (Opcode::VGATHERDPS as u32),
+ VGATHERQPD = (Opcode::VGATHERQPD as u32),
+ VGATHERQPS = (Opcode::VGATHERQPS as u32),
+
+ VPSCATTERDD = (Opcode::VPSCATTERDD as u32),
+ VPSCATTERDQ = (Opcode::VPSCATTERDQ as u32),
+ VPSCATTERQD = (Opcode::VPSCATTERQD as u32),
+ VPSCATTERQQ = (Opcode::VPSCATTERQQ as u32),
+
+ /// monitor/mwait instructions are considered "complex" for reasons in the enum doc comment.
+ MONITOR = (Opcode::MONITOR as u32),
+ MONITORX = (Opcode::MONITORX as u32),
+ MWAIT = (Opcode::MWAIT as u32),
+ MWAITX = (Opcode::MWAITX as u32),
+
+ /// the syscall/systenter and sysexit/sysret instructions are considered complex because of
+ /// their interaction with architectural state that is not expressible purely as register or
+ /// memory accesses.
+ SYSCALL = (Opcode::SYSCALL as u32),
+ SYSRET = (Opcode::SYSRET as u32),
+ SYSENTER = (Opcode::SYSENTER as u32),
+ SYSEXIT = (Opcode::SYSEXIT as u32),
+
+ /// SVM instructions generally are considered "complex" for reasons in the doc comment above.
+ SKINIT = (Opcode::SKINIT as u32),
+ VMLOAD = (Opcode::VMLOAD as u32),
+ VMMCALL = (Opcode::VMMCALL as u32),
+ VMSAVE = (Opcode::VMSAVE as u32),
+ VMRUN = (Opcode::VMRUN as u32),
+ VMPTRLD = (Opcode::VMPTRLD as u32),
+ VMPTRST = (Opcode::VMPTRST as u32),
+
+ /// VMX instructions, too, are considered "complex" for similar reasons as SVM.
+ VMXON = (Opcode::VMXON as u32),
+ VMXOFF = (Opcode::VMXOFF as u32),
+ VMREAD = (Opcode::VMREAD as u32),
+ VMWRITE = (Opcode::VMWRITE as u32),
+ VMCLEAR = (Opcode::VMCLEAR as u32),
+ VMCALL = (Opcode::VMCALL as u32),
+ VMLAUNCH = (Opcode::VMLAUNCH as u32),
+ VMRESUME = (Opcode::VMRESUME as u32),
+ VMFUNC = (Opcode::VMFUNC as u32),
+
+ /// vzeroupper/vzeroall are considered "complex" for reasons in the doc comment above.
+ VZEROUPPER = (Opcode::VZEROUPPER as u32),
+ VZEROALL = (Opcode::VZEROALL as u32),
+
+ /// clzero, clflush, clflushopt, and clwb are considered "complex" for reasons in the enum doc
+ /// comment.
+ CLZERO = (Opcode::CLZERO as u32),
+ CLFLUSH = (Opcode::CLFLUSH as u32),
+ CLFLUSHOPT = (Opcode::CLFLUSHOPT as u32),
+ CLWB = (Opcode::CLWB as u32),
+
+ /// prefetch instructions are considered "complex" for reasons in the enum doc comment.
+ PREFETCHNTA = (Opcode::PREFETCHNTA as u32),
+ PREFETCHT2 = (Opcode::PREFETCH2 as u32),
+ PREFETCHT1 = (Opcode::PREFETCH1 as u32),
+ PREFETCHT0 = (Opcode::PREFETCH0 as u32),
+
+ /// bit test/set/reset/complement instructions are conditionally complex depending on their
+ /// destination operand form, as described in the enum doc comment.
+ BT = (Opcode::BT as u32),
+ BTC = (Opcode::BTC as u32),
+ BTR = (Opcode::BTR as u32),
+ BTS = (Opcode::BTS as u32),
+
+ /// enqueue stores in an archtecturally interesting way, and write to
+ /// architecturally-interesting non-memory locations, so they are "complex".
+ ENQCMD = (Opcode::ENQCMD as u32),
+ ENQCMDS = (Opcode::ENQCMDS as u32),
+
+ /// shadow stacks and other CET machinery involve modifies processor state that cannot be
+ /// expressed by `yaxpeax-x86` as any particular location currently, so it is "complex".
+ WRUSS = (Opcode::WRUSS as u32),
+ WRSS = (Opcode::WRSS as u32),
+ INCSSP = (Opcode::INCSSP as u32),
+ SAVEPREVSSP = (Opcode::SAVEPREVSSP as u32),
+ SETSSBSY = (Opcode::SETSSBSY as u32),
+ CLRSSBSY = (Opcode::CLRSSBSY as u32),
+ RSTORSSP = (Opcode::RSTORSSP as u32),
+ ENDBR64 = (Opcode::ENDBR64 as u32),
+ ENDBR32 = (Opcode::ENDBR32 as u32),
+
+ /// str/ldr and sldt/lldt are considered complex because of their interaction with
+ /// architectural state that is not expressible purely as register or memory accesses.
+ STR = (Opcode::STR as u32),
+ LTR = (Opcode::LTR as u32),
+ SLDT = (Opcode::SLDT as u32),
+ LLDT = (Opcode::LLDT as u32),
+
+ /// likewise, the AMD global interrupt flag (GIF) is not expressible as an architectural
+ /// location by `yaxpeax-x86`, and so instructions operating on it are "complex".
+ CLGI = (Opcode::CLGI as u32),
+ STGI = (Opcode::STGI as u32),
+
+ /// `xgetbv`/`xsetbv` are "complex" because the library API does not have a way to express
+ /// extended control registers (xcr0 and the like).
+ XGETBV = (Opcode::XGETBV as u32),
+ XSETBV = (Opcode::XSETBV as u32),
+
+ /// `v4f*` family multiply-add instructions operate on ranges of registers that are not
+ /// (currently) expressed precisely in the library API
+ V4FNMADDSS = (Opcode::V4FNMADDSS as u32),
+ V4FNMADDPS = (Opcode::V4FNMADDPS as u32),
+ V4FMADDSS = (Opcode::V4FMADDSS as u32),
+ V4FMADDPS = (Opcode::V4FMADDPS as u32),
+
+ /// movdir64b is considered complex primarily because it has two memory operands, but the
+ /// destination operand (first, in Intel syntax) is expressly *not* a memory operand so far as
+ /// syntax is concerned.
+ MOVDIR64B = (Opcode::MOVDIR64B as u32),
+
+ /// `hreset` manages microarchitectural processor history, but is considered "complex" somewhat
+ /// arbitrarily as its sole responsibility is to operate on state that is not expressed in the
+ /// library API.
+ HRESET = (Opcode::HRESET as u32),
+
+ /// `psmash`-related instructions depend on architectural state which is described in more depth
+ /// above, but not currently expressed in the library API, so they are "complex".
+ PSMASH = (Opcode::PSMASH as u32),
+ PVALIDATE = (Opcode::PVALIDATE as u32),
+ RMPADJUST = (Opcode::RMPADJUST as u32),
+ RMPUPDATE = (Opcode::RMPUPDATE as u32),
+
+ /// `ptwrite` modifies processor state that is not expressed in the library API currently, so it
+ /// is "complex".
+ PTWRITE = (Opcode::PTWRITE as u32),
+
+ /// these instructions are all documented as complex for the reasons under `Restricted
+ /// Transactional Memory` (RTM) above.
+ XABORT = (Opcode::XABORT as u32),
+ XBEGIN = (Opcode::XBEGIN as u32),
+ XEND = (Opcode::XEND as u32),
+ XTEST = (Opcode::XTEST as u32),
+
+ /// `pconfig` is "complex" because it alters architectural state and has complex semantics.
+ PCONFIG = (Opcode::PCONFIG as u32),
+
+ /// some MPX-related instructions are considered complex for the reasons described in the enum
+ /// doc comment above.
+ BNDLDX = (Opcode::BNDLDX as u32),
+ BNDSTX = (Opcode::BNDSTX as u32),
+
+ /// `iret*` instructions are considered complex for the reasons described in the enum doc
+ /// comment above.
+ IRET = (Opcode::IRET as u32),
+ IRETD = (Opcode::IRETD as u32),
+ IRETQ = (Opcode::IRETQ as u32),
+
+ /// enclave-related instructions are considered complex for the reasons described in the enum
+ /// doc comment above.
+ ENCLS = (Opcode::ENCLS as u32),
+ ENCLV = (Opcode::ENCLV as u32),
+ ENCLU = (Opcode::ENCLU as u32),
+
+ /// OSPKE-related instructions are considered complex for the reasons described in the enum doc
+ /// comment above.
+ RDPKRU = (Opcode::RDPKRU as u32),
+ WRPKRU = (Opcode::WRPKRU as u32),
+
+ /// `rsm` is considered complex for the reasons related to SMM described in the enum doc
+ /// comment above.
+ RSM = (Opcode::RSM as u32),
+
+ /// WAITPKG-related instructions are considered complex for the reasons described in the enum
+ /// doc comment above.
+ TPAUSE = (Opcode::TPAUSE as u32),
+ UMONITOR = (Opcode::UMONITOR as u32),
+ UMWAIT = (Opcode::UMWAIT as u32),
+
+ /// UINTR-related instructions are considered complex for the reasons described in the enum
+ /// doc comment above.
+ UIRET = (Opcode::UIRET as u32),
+ TESTUI = (Opcode::TESTUI as u32),
+ CLUI = (Opcode::CLUI as u32),
+ STUI = (Opcode::STUI as u32),
+ SENDUIPI = (Opcode::SENDUIPI as u32),
+
+ /// TDX-related instructions are considered complex for the reasons described in the enum
+ /// doc comment above.
+ TDCALL = (Opcode::TDCALL as u32),
+ SEAMRET = (Opcode::SEAMRET as u32),
+ SEAMOPS = (Opcode::SEAMOPS as u32),
+ SEAMCALL = (Opcode::SEAMCALL as u32),
+
+ /// vector scatter/gather prefetch instructions are considered complex for the reasons "normal"
+ /// scatter/gather are complex, as well as the reasons "normal" prefetch instructions are
+ /// complex.
+ VGATHERPF0DPD = (Opcode::VGATHERPF0DPD as u32),
+ VGATHERPF0DPS = (Opcode::VGATHERPF0DPS as u32),
+ VGATHERPF0QPD = (Opcode::VGATHERPF0QPD as u32),
+ VGATHERPF0QPS = (Opcode::VGATHERPF0QPS as u32),
+ VGATHERPF1DPD = (Opcode::VGATHERPF1DPD as u32),
+ VGATHERPF1DPS = (Opcode::VGATHERPF1DPS as u32),
+ VGATHERPF1QPD = (Opcode::VGATHERPF1QPD as u32),
+ VGATHERPF1QPS = (Opcode::VGATHERPF1QPS as u32),
+ VSCATTERPF0DPD = (Opcode::VSCATTERPF0DPD as u32),
+ VSCATTERPF0DPS = (Opcode::VSCATTERPF0DPS as u32),
+ VSCATTERPF0QPD = (Opcode::VSCATTERPF0QPD as u32),
+ VSCATTERPF0QPS = (Opcode::VSCATTERPF0QPS as u32),
+ VSCATTERPF1DPD = (Opcode::VSCATTERPF1DPD as u32),
+ VSCATTERPF1DPS = (Opcode::VSCATTERPF1DPS as u32),
+ VSCATTERPF1QPD = (Opcode::VSCATTERPF1QPD as u32),
+ VSCATTERPF1QPS = (Opcode::VSCATTERPF1QPS as u32),
+}
+
+/// a visitor for collecting architectural accesses for an `Instruction`. used with
+/// [`InstBehavior::visit_accesses`].
+///
+/// ## address calculation
+///
+/// [`memory_read()`][AccessVisitor::memory_read] and
+/// [`memory_write()`][AccessVisitor::memory_write] take an optional parameter for an effective
+/// address that is either read or written. by default, the address provided is typically `None`,
+/// but with appropriate implementations of this trait, `yaxpeax-x86` will calculate and report the
+/// effective addresses of memory acceses. when visiting a memory operand, the library will call
+/// [`get_register()`][AccessVisitor::get_register] on each register used in an operand's address
+/// calculation; if all calls return a value, then the library will compute an address and provide
+/// it in the corresponding `memory_read()` or `memory_write()`.
+///
+/// the default `get_register()` implementation does not return register values, but does call
+/// `register_read()`. this means that `register_read()` is called for each register that may be
+/// used by the instruction in question. if this is desirable and you are providing a custom
+/// implementation of `get_register()`, be sure to include a `register_read()`! alternatively, if
+/// `get_register()` is made to not call `register_read()`, then the other functions in this trait
+/// (`{register,memory}_{read,write}`()) are called one-to-one for implicit or explicit operands of
+/// this instruction.
+pub trait AccessVisitor {
+ /// record that the instruction reads a register. note that the default implementation of
+ /// [`AccessVisitor::get_register`] also calls `register_read`; registers used as part of an
+ /// address calculation for memory accesses are recorded via `register_read()` by default!
+ fn register_read(&mut self, reg: RegSpec);
+ /// record that the instruction writes a register.
+ fn register_write(&mut self, reg: RegSpec);
+ /// get a numeric value for `reg`, if possible. this is called as part of computing effective
+ /// addresses used in [`AccessVisitor::memory_read`] and [`AccessVisitor::memory_write`], for
+ /// each register involved in an address calculation.
+ ///
+ /// if any `get_register()` returns `None` in an address calculation, the subsequent
+ /// `memory_read()` or `memory_write()` for that operand will be given an `address` of `None`.
+ ///
+ /// `get_register()` may be implemented withhout calling `register_read()`, in which case when
+ /// used with `visit_accesses` the register/memory read/writes will all correspond directly to
+ /// implicit and explicit operands.
+ fn get_register(&mut self, reg: RegSpec) -> Option<u64> {
+ self.register_read(reg);
+ None
+ }
+ /// record that the instruction reads a memory location.
+ ///
+ /// when used with `visit_accesses`, an address is only provided when yaxpeax-x86 can calculate
+ /// an effective address (i.e. `get_register()` calls for all dependent registers return a
+ /// value). all non-`ComplexOp` instructions have a known memory access size, so this is always
+ /// reported regardless of if *where* is not known.
+ ///
+ /// some instructions can both read and write memory (consider `call [addr]`).
+ fn memory_read(&mut self, address: Option<u64>, size: u32);
+ /// record that the instruction writes a memory location.
+ ///
+ /// when used with `visit_accesses`, an address is only provided when yaxpeax-x86 can calculate
+ /// an effective address (i.e. `get_register()` calls for all dependent registers return a
+ /// value). all non-`ComplexOp` instructions have a known memory access size, so this is always
+ /// reported regardless of if *where* is not known.
+ ///
+ /// some instructions can both read and write memory (consider `call [addr]`).
+ fn memory_write(&mut self, address: Option<u64>, size: u32);
+}
+
+#[cfg(all(test, feature = "std"))]
+mod test {
+ use super::*;
+ use crate::long_mode::InstDecoder;
+
+ use alloc::vec;
+ use alloc::vec::Vec;
+
+ #[test]
+ fn access_visitor_works() {
+ // xor eax, dword [rcx]
+ let bytes = &[0x33, 0x01];
+ let inst = InstDecoder::default().decode_slice(bytes).expect("can decode trivial instructions");
+
+ struct AccessCtx {
+ rcx: u64,
+
+ accesses: Vec<(RegSpec, Access)>,
+ mem_accesses: Vec<((Option<u64>, u32), Access)>,
+ }
+
+ let mut ctx = AccessCtx {
+ rcx: 0x10000,
+ accesses: Vec::new(),
+ mem_accesses: Vec::new(),
+ };
+
+ impl AccessVisitor for AccessCtx {
+ fn register_read(&mut self, reg: RegSpec) {
+ self.accesses.push((reg, Access::Read));
+ }
+
+ fn register_write(&mut self, reg: RegSpec) {
+ self.accesses.push((reg, Access::Write));
+ }
+
+ fn get_register(&mut self, reg: RegSpec) -> Option<u64> {
+ self.register_read(reg);
+
+ if reg == RegSpec::rcx() {
+ Some(self.rcx)
+ } else {
+ None
+ }
+ }
+
+ fn memory_read(&mut self, address: Option<u64>, size: u32) {
+ self.mem_accesses.push(((address, size), Access::Read));
+ }
+
+ fn memory_write(&mut self, address: Option<u64>, size: u32) {
+ self.mem_accesses.push(((address, size), Access::Write));
+ }
+ }
+
+ let behavior = inst.behavior();
+ behavior.visit_accesses(&mut ctx).expect("xor eax, [rcx] is not complex");
+
+ assert_eq!(ctx.accesses, vec![
+ (RegSpec::rflags(), Access::Write),
+ (RegSpec::eax(), Access::Read),
+ (RegSpec::rax(), Access::Write),
+ (RegSpec::rcx(), Access::Read)
+ ]);
+ assert_eq!(ctx.mem_accesses, vec![((Some(0x10000), 4), Access::Read)]);
+ }
+
+ #[test]
+ fn operand_iter_basically_works() {
+ // xor eax, eax
+ let bytes = &[0x33, 0xc0];
+ let inst = InstDecoder::default().decode_slice(bytes).expect("can decode trivial instructions");
+
+ // uwu whats this...
+ let behavior = inst.behavior();
+
+ // owo hewwo there
+ let operands = behavior.all_operands().expect("xor eax, eax is not complex");
+
+ // OwO waowwww
+ let collected: alloc::vec::Vec<(Operand, Access)> = operands.iter().collect();
+ let expected = alloc::vec![
+ (Operand::Register { reg: RegSpec::rflags() }, Access::Write),
+ (Operand::Register { reg: RegSpec::eax() }, Access::ReadWrite),
+ (Operand::Register { reg: RegSpec::eax() }, Access::Read),
+ ];
+ assert_eq!(collected, expected);
+
+ #[cfg(feature = "unstable")]
+ {
+ assert_eq!(behavior.privilege_level(), Some(PrivilegeLevel::Any));
+ let exceptions = behavior.exceptions();
+ assert!(exceptions.none());
+ }
+
+ // but if an operand does a memory access, that can fault:
+ // xor eax, [rax]
+ let bytes = &[0x33, 0x00];
+ let inst = InstDecoder::default().decode_slice(bytes).expect("can decode trivial instructions");
+ let behavior = inst.behavior();
+ let operands = behavior.all_operands().expect("xor eax, eax is not complex");
+
+ let collected: alloc::vec::Vec<(Operand, Access)> = operands.iter().collect();
+ let expected = alloc::vec![
+ (Operand::Register { reg: RegSpec::rflags() }, Access::Write),
+ (Operand::Register { reg: RegSpec::eax() }, Access::ReadWrite),
+ (Operand::MemDeref { base: RegSpec::rax() }, Access::Read),
+ ];
+ assert_eq!(collected, expected);
+
+ #[cfg(feature = "unstable")]
+ {
+ assert_eq!(behavior.privilege_level(), Some(PrivilegeLevel::Any));
+ let exceptions = behavior.exceptions();
+ assert!(!exceptions.none());
+ assert!(exceptions.may(Exception::PF));
+ }
+ }
+}
+
+/// no operations, but you can run it anywhere.
+const GENERAL: BehaviorDigest = BehaviorDigest::empty()
+ .set_pl_any();
+
+/// instructions that can execute at all privilege levels, have two operands, read/write the first,
+/// and read the second.
+const GENERAL_RW_R: BehaviorDigest = BehaviorDigest::empty()
+ .set_pl_any()
+ .set_operand(0, Access::ReadWrite)
+ .set_operand(1, Access::Read);
+
+/// same as above, but writes flags. this is most arithmetic instructions.
+const GENERAL_RW_R_FLAGWRITE: BehaviorDigest = GENERAL_RW_R
+ .set_flags_access(Access::Write);
+
+/// popcnt and maybe others?
+const GENERAL_W_R_FLAGWRITE: BehaviorDigest = GENERAL_RW_R
+ .set_operand(0, Access::Write)
+ .set_flags_access(Access::Write);
+
+/// test, cmp, with no write but to flags.
+const GENERAL_R_R_FLAGWRITE: BehaviorDigest = GENERAL_RW_R
+ .set_operand(0, Access::Read)
+ .set_flags_access(Access::Write);
+
+/// `sbb`, `adc`, etc both read flags and write them.
+const GENERAL_RW_R_FLAGRW: BehaviorDigest = GENERAL_RW_R
+ .set_flags_access(Access::ReadWrite);
+
+/// `xadd` reads everything and writes everything, even flags!
+const GENERAL_RW_RW_FLAGRW: BehaviorDigest = GENERAL_RW_R_FLAGRW
+ .set_operand(1, Access::ReadWrite);
+
+/// setcc and friends read flags to maybe write their operand.
+const GENERAL_RW_FLAGREAD: BehaviorDigest = BehaviorDigest::empty()
+ .set_pl_any()
+ .set_operand(0, Access::ReadWrite)
+ .set_flags_access(Access::Read);
+
+/// cmov reads from a second operand and (may) writes to the first.
+const GENERAL_W_R_FLAGREAD: BehaviorDigest = GENERAL_RW_FLAGREAD
+ .set_operand(0, Access::Write)
+ .set_operand(1, Access::Read);
+
+/// cmc, clc, sti, cli, etc that toggle individual bits in flags
+const GENERAL_FLAGRW: BehaviorDigest = BehaviorDigest::empty()
+ .set_pl_any()
+ .set_flags_access(Access::ReadWrite);
+
+/// `inc`, `dec`, and `neg` have one operand and modify flags.
+const GENERAL_RW_FLAGWRITE: BehaviorDigest = BehaviorDigest::empty()
+ .set_pl_any()
+ .set_operand(0, Access::ReadWrite)
+ .set_flags_access(Access::Write);
+
+/// `inc`, `dec`, and `neg` have one operand and modify flags.
+const GENERAL_RW: BehaviorDigest = BehaviorDigest::empty()
+ .set_pl_any()
+ .set_operand(0, Access::ReadWrite);
+
+const GENERAL_R_R: BehaviorDigest = GENERAL_RW_R
+ .set_operand(0, Access::Read);
+
+/// `ins` writes to the memory operand, reads from `rdx` (second operand)
+const GENERAL_W_R: BehaviorDigest = GENERAL_RW_R
+ .set_operand(0, Access::Write);
+
+/// many vex/evex-encoded instructions
+const GENERAL_W_R_R: BehaviorDigest = GENERAL_W_R
+ .set_operand(2, Access::Read);
+
+/// and for vex/evex-encoded instructions with an imm8 suffix
+///
+/// this is not distinct from a `GENERAL_W_R_R_R`, but is named distinctly in case yaxpeax-x86
+/// should report imm8 operands differently from "read" or "write".
+const GENERAL_W_R_R_IMM8: BehaviorDigest = GENERAL_W_R_R
+ .set_operand(3, Access::Read);
+
+/// shld
+const GENERAL_RW_R_R: BehaviorDigest = GENERAL_W_R_R
+ .set_operand(0, Access::ReadWrite);
+
+const GENERAL_RW_RW: BehaviorDigest = GENERAL_RW_R
+ .set_operand(1, Access::ReadWrite);
+
+const GENERAL_RW_RW_FLAGWRITE: BehaviorDigest = GENERAL_RW_RW
+ .set_flags_access(Access::Write);
+
+const GENERAL_R: BehaviorDigest = BehaviorDigest::empty()
+ .set_pl_any()
+ .set_operand(0, Access::Read);
+
+const GENERAL_W: BehaviorDigest = BehaviorDigest::empty()
+ .set_pl_any()
+ .set_operand(0, Access::Write);
+
+const GENERAL_R_FLAGREAD: BehaviorDigest = GENERAL_R
+ .set_flags_access(Access::Read);
+
+const GENERAL_R_FLAGWRITE: BehaviorDigest = GENERAL_R
+ .set_flags_access(Access::Write);
+
+// TODO: seems incredibly funky that jcc's operand is an immediate, when written like this..
+const JCC: BehaviorDigest = BehaviorDigest::empty()
+ .set_implicit_ops(JCC_OPS_IDX)
+ .set_pl_any()
+ .set_operand(0, Access::Read);
+
+const CMOVCC: BehaviorDigest = BehaviorDigest::empty()
+ .set_pl_any()
+ .set_flags_access(Access::Read)
+ .set_operand(0, Access::Write)
+ .set_operand(1, Access::Read);
+
+const SETCC: BehaviorDigest = BehaviorDigest::empty()
+ .set_pl_any()
+ .set_flags_access(Access::Read)
+ .set_operand(0, Access::Write);
+
+static PUSH_OPS: &'static [ImplicitOperand] = &[
+ ImplicitOperand {
+ spec: OperandSpec::Disp,
+ reg: RegSpec::rsp(),
+ disp: -8i32,
+ write: true,
+ },
+ // push.. pushes the value (above), then does a RMW on rsp.
+ ImplicitOperand {
+ spec: OperandSpec::RegRRR,
+ reg: RegSpec::rsp(),
+ disp: 0,
+ write: false,
+ },
+ ImplicitOperand {
+ spec: OperandSpec::RegRRR,
+ reg: RegSpec::rsp(),
+ disp: 0,
+ write: true,
+ }
+];
+
+static POP_OPS: &'static [ImplicitOperand] = &[
+ ImplicitOperand {
+ spec: OperandSpec::Deref,
+ reg: RegSpec::rsp(),
+ disp: 0i32,
+ write: false,
+ },
+ ImplicitOperand {
+ spec: OperandSpec::RegRRR,
+ reg: RegSpec::rsp(),
+ disp: 0,
+ write: false,
+ },
+ ImplicitOperand {
+ spec: OperandSpec::RegRRR,
+ reg: RegSpec::rsp(),
+ disp: 0,
+ write: true,
+ }
+];
+
+static JCC_OPS: &'static [ImplicitOperand] = &[
+ ImplicitOperand {
+ spec: OperandSpec::RegRRR,
+ reg: RegSpec::rflags(),
+ disp: 0i32,
+ write: false,
+ },
+ ImplicitOperand {
+ spec: OperandSpec::RegRRR,
+ reg: RegSpec::rip(),
+ disp: 0,
+ write: true,
+ }
+];
+
+static CBW_OPS: &'static [ImplicitOperand] = &[
+ ImplicitOperand {
+ spec: OperandSpec::RegRRR,
+ reg: RegSpec::al(),
+ disp: 0i32,
+ write: false,
+ },
+ ImplicitOperand {
+ spec: OperandSpec::RegRRR,
+ reg: RegSpec::ax(),
+ disp: 0,
+ write: true,
+ }
+];
+
+static CWDE_OPS: &'static [ImplicitOperand] = &[
+ ImplicitOperand {
+ spec: OperandSpec::RegRRR,
+ reg: RegSpec::ax(),
+ disp: 0i32,
+ write: false,
+ },
+ ImplicitOperand {
+ spec: OperandSpec::RegRRR,
+ reg: RegSpec::eax(),
+ disp: 0,
+ write: true,
+ }
+];
+
+static CDQE_OPS: &'static [ImplicitOperand] = &[
+ ImplicitOperand {
+ spec: OperandSpec::RegRRR,
+ reg: RegSpec::eax(),
+ disp: 0i32,
+ write: false,
+ },
+ ImplicitOperand {
+ spec: OperandSpec::RegRRR,
+ reg: RegSpec::rax(),
+ disp: 0,
+ write: true,
+ }
+];
+
+// note CQD, CDQ, CQO:
+//
+// these are writes to dx/edx/rdx but *not* `*ax`. this is because while these registers "write"
+// sign-extended *ax to *ax:*dx, "writes" to eax:edx do not modify the upper 32 bits of rax. that
+// is to say, that if `rax` is 0x8000_1234_c000_0000 and a `cdq` is executed, the result is:
+// ```
+// rax = 0x8000_1234_c000_0000
+// rdx = 0x0000_0000_ffff_ffff
+// ```
+//
+// cool, huh!!
+static CWD_OPS: &'static [ImplicitOperand] = &[
+ ImplicitOperand {
+ spec: OperandSpec::RegRRR,
+ reg: RegSpec::ax(),
+ disp: 0i32,
+ write: false,
+ },
+ ImplicitOperand {
+ spec: OperandSpec::RegRRR,
+ reg: RegSpec::dx(),
+ disp: 0,
+ write: true,
+ }
+];
+
+static CDQ_OPS: &'static [ImplicitOperand] = &[
+ ImplicitOperand {
+ spec: OperandSpec::RegRRR,
+ reg: RegSpec::eax(),
+ disp: 0i32,
+ write: false,
+ },
+ ImplicitOperand {
+ spec: OperandSpec::RegRRR,
+ reg: RegSpec::edx(),
+ disp: 0,
+ write: true,
+ }
+];
+
+static CQO_OPS: &'static [ImplicitOperand] = &[
+ ImplicitOperand {
+ spec: OperandSpec::RegRRR,
+ reg: RegSpec::rax(),
+ disp: 0i32,
+ write: false,
+ },
+ ImplicitOperand {
+ spec: OperandSpec::RegRRR,
+ reg: RegSpec::rdx(),
+ disp: 0,
+ write: true,
+ }
+];
+
+static PUSHF_OPS: &'static [ImplicitOperand] = &[
+ ImplicitOperand {
+ spec: OperandSpec::RegRRR,
+ reg: RegSpec::rflags(),
+ disp: 0,
+ write: false,
+ },
+ ImplicitOperand {
+ spec: OperandSpec::Disp,
+ reg: RegSpec::rsp(),
+ disp: -8i32,
+ write: true,
+ },
+ // push.. pushes the value (above), then does a RMW on rsp.
+ ImplicitOperand {
+ spec: OperandSpec::RegRRR,
+ reg: RegSpec::rsp(),
+ disp: 0,
+ write: false,
+ },
+ ImplicitOperand {
+ spec: OperandSpec::RegRRR,
+ reg: RegSpec::rsp(),
+ disp: 0,
+ write: true,
+ }
+];
+
+static POPF_OPS: &'static [ImplicitOperand] = &[
+ ImplicitOperand {
+ spec: OperandSpec::Deref,
+ reg: RegSpec::rsp(),
+ disp: 0i32,
+ write: false,
+ },
+ ImplicitOperand {
+ spec: OperandSpec::RegRRR,
+ reg: RegSpec::rflags(),
+ disp: 0,
+ write: true,
+ },
+ ImplicitOperand {
+ spec: OperandSpec::RegRRR,
+ reg: RegSpec::rsp(),
+ disp: 0,
+ write: false,
+ },
+ ImplicitOperand {
+ spec: OperandSpec::RegRRR,
+ reg: RegSpec::rsp(),
+ disp: 0,
+ write: true,
+ }
+];
+
+static SAHF_OPS: &'static [ImplicitOperand] = &[
+ ImplicitOperand {
+ spec: OperandSpec::RegRRR,
+ reg: RegSpec::ah(),
+ disp: 0,
+ write: false,
+ },
+ ImplicitOperand {
+ spec: OperandSpec::RegRRR,
+ reg: RegSpec::rflags(),
+ disp: 0,
+ write: true,
+ }
+];
+
+static LAHF_OPS: &'static [ImplicitOperand] = &[
+ ImplicitOperand {
+ spec: OperandSpec::RegRRR,
+ reg: RegSpec::rflags(),
+ disp: 0,
+ write: false,
+ },
+ ImplicitOperand {
+ spec: OperandSpec::RegRRR,
+ reg: RegSpec::ah(),
+ disp: 0,
+ write: true,
+ }
+];
+
+static MOVS_OPS: &'static [ImplicitOperand] = &[
+ ImplicitOperand {
+ spec: OperandSpec::Deref_rdi,
+ reg: RegSpec::eax(),
+ disp: 0,
+ write: false,
+ },
+ ImplicitOperand {
+ spec: OperandSpec::Deref_rdi,
+ reg: RegSpec::eax(),
+ disp: 1,
+ write: false,
+ },
+ ImplicitOperand {
+ spec: OperandSpec::Deref_rdi,
+ reg: RegSpec::eax(),
+ disp: 0,
+ write: true,
+ },
+ ImplicitOperand {
+ spec: OperandSpec::Deref_rdi,
+ reg: RegSpec::eax(),
+ disp: 1,
+ write: true,
+ },
+];
+
+static LODS_OPS: &'static [ImplicitOperand] = &[
+ ImplicitOperand {
+ spec: OperandSpec::Deref_rdi,
+ reg: RegSpec::eax(),
+ disp: 1,
+ write: false,
+ },
+ ImplicitOperand {
+ spec: OperandSpec::Deref_rdi,
+ reg: RegSpec::eax(),
+ disp: 1,
+ write: true,
+ },
+];
+
+static STOS_OPS: &'static [ImplicitOperand] = &[
+ ImplicitOperand {
+ spec: OperandSpec::Deref_rdi,
+ reg: RegSpec::eax(),
+ disp: 0,
+ write: false,
+ },
+ ImplicitOperand {
+ spec: OperandSpec::Deref_rdi,
+ reg: RegSpec::eax(),
+ disp: 0,
+ write: true,
+ },
+];
+
+static SCAS_OPS: &'static [ImplicitOperand] = &[
+ ImplicitOperand {
+ spec: OperandSpec::Deref_rdi,
+ reg: RegSpec::eax(),
+ disp: 0,
+ write: false,
+ },
+ ImplicitOperand {
+ spec: OperandSpec::Deref_rdi,
+ reg: RegSpec::eax(),
+ disp: 0,
+ write: true,
+ },
+ ImplicitOperand {
+ spec: OperandSpec::RegRRR,
+ reg: RegSpec::rflags(),
+ disp: 0,
+ write: true,
+ }
+];
+
+static RETURN_OPS: &'static [ImplicitOperand] = &[
+ ImplicitOperand {
+ spec: OperandSpec::Deref,
+ reg: RegSpec::rsp(),
+ disp: 0i32,
+ write: false,
+ },
+ ImplicitOperand {
+ spec: OperandSpec::RegRRR,
+ reg: RegSpec::rip(),
+ disp: 0,
+ write: true,
+ },
+ ImplicitOperand {
+ spec: OperandSpec::RegRRR,
+ reg: RegSpec::rsp(),
+ disp: 0,
+ write: false,
+ },
+ ImplicitOperand {
+ spec: OperandSpec::RegRRR,
+ reg: RegSpec::rsp(),
+ disp: 0,
+ write: true,
+ }
+];
+
+static LEAVE_OPS: &'static [ImplicitOperand] = &[
+ ImplicitOperand {
+ spec: OperandSpec::RegRRR,
+ reg: RegSpec::rbp(),
+ disp: 0,
+ write: false,
+ },
+ ImplicitOperand {
+ spec: OperandSpec::RegRRR,
+ reg: RegSpec::rsp(),
+ disp: 0,
+ write: true,
+ },
+ ImplicitOperand {
+ spec: OperandSpec::Deref,
+ reg: RegSpec::rsp(),
+ disp: 0i32,
+ write: false,
+ },
+ ImplicitOperand {
+ spec: OperandSpec::RegRRR,
+ reg: RegSpec::rbp(),
+ disp: 0,
+ write: true,
+ },
+ ImplicitOperand {
+ spec: OperandSpec::RegRRR,
+ reg: RegSpec::rsp(),
+ disp: 0,
+ write: false,
+ },
+ ImplicitOperand {
+ spec: OperandSpec::RegRRR,
+ reg: RegSpec::rsp(),
+ disp: 0,
+ write: true,
+ }
+];
+
+static ENTER_OPS: &'static [ImplicitOperand] = &[
+ ImplicitOperand {
+ spec: OperandSpec::RegRRR,
+ reg: RegSpec::rsp(),
+ disp: 0,
+ write: true,
+ },
+ ImplicitOperand {
+ spec: OperandSpec::RegRRR,
+ reg: RegSpec::rbp(),
+ disp: 0,
+ write: false,
+ },
+ ImplicitOperand {
+ spec: OperandSpec::RegRRR,
+ reg: RegSpec::rbp(),
+ disp: 0,
+ write: true,
+ },
+ ImplicitOperand {
+ spec: OperandSpec::Disp,
+ reg: RegSpec::rsp(),
+ disp: -8i32,
+ write: false,
+ },
+ ImplicitOperand {
+ spec: OperandSpec::RegRRR,
+ reg: RegSpec::rsp(),
+ disp: 0,
+ write: true,
+ }
+];
+
+static XLAT_OPS: &'static [ImplicitOperand] = &[
+ ImplicitOperand {
+ // xlat is the only implicit operand to use a base/index addressing scheme, so note the
+ // base (rbx) and handle the implicit al index in code..?
+ spec: OperandSpec::MemIndexScale,
+ reg: RegSpec::rbx(),
+ disp: 0i32,
+ write: false,
+ },
+ ImplicitOperand {
+ spec: OperandSpec::RegRRR,
+ reg: RegSpec::al(),
+ disp: 0,
+ write: true,
+ },
+];
+
+static CLTS_OPS: &'static [ImplicitOperand] = &[
+ ImplicitOperand {
+ spec: OperandSpec::RegRRR,
+ reg: RegSpec::cr2(),
+ disp: 0i32,
+ write: false,
+ },
+ ImplicitOperand {
+ spec: OperandSpec::RegRRR,
+ reg: RegSpec::cr2(),
+ disp: 0,
+ write: true,
+ },
+];
+
+// the actual implicit operands of `{i,}mul` are broken out by operand count and operation size..
+static MUL_OPS_1OP_BYTE: &'static [ImplicitOperand] = &[
+ ImplicitOperand {
+ spec: OperandSpec::RegRRR,
+ reg: RegSpec::al(),
+ disp: 0i32,
+ write: false,
+ },
+ ImplicitOperand {
+ spec: OperandSpec::RegRRR,
+ reg: RegSpec::ax(),
+ disp: 0i32,
+ write: true,
+ }
+];
+static MUL_OPS_1OP_WORD: &'static [ImplicitOperand] = &[
+ ImplicitOperand {
+ spec: OperandSpec::RegRRR,
+ reg: RegSpec::ax(),
+ disp: 0i32,
+ write: false,
+ },
+ ImplicitOperand {
+ spec: OperandSpec::RegRRR,
+ reg: RegSpec::ax(),
+ disp: 0i32,
+ write: true,
+ },
+ ImplicitOperand {
+ spec: OperandSpec::RegRRR,
+ reg: RegSpec::dx(),
+ disp: 0i32,
+ write: true,
+ }
+];
+static MUL_OPS_1OP_DWORD: &'static [ImplicitOperand] = &[
+ ImplicitOperand {
+ spec: OperandSpec::RegRRR,
+ reg: RegSpec::eax(),
+ disp: 0i32,
+ write: false,
+ },
+ ImplicitOperand {
+ spec: OperandSpec::RegRRR,
+ reg: RegSpec::eax(),
+ disp: 0i32,
+ write: true,
+ },
+ ImplicitOperand {
+ spec: OperandSpec::RegRRR,
+ reg: RegSpec::edx(),
+ disp: 0i32,
+ write: true,
+ }
+];
+static MUL_OPS_1OP_QWORD: &'static [ImplicitOperand] = &[
+ ImplicitOperand {
+ spec: OperandSpec::RegRRR,
+ reg: RegSpec::rax(),
+ disp: 0i32,
+ write: false,
+ },
+ ImplicitOperand {
+ spec: OperandSpec::RegRRR,
+ reg: RegSpec::rax(),
+ disp: 0i32,
+ write: true,
+ },
+ ImplicitOperand {
+ spec: OperandSpec::RegRRR,
+ reg: RegSpec::rdx(),
+ disp: 0i32,
+ write: true,
+ }
+];
+
+// the actual implicit operands of `{i,}div` are broken out by operation size..
+static DIV_OPS_1OP_BYTE: &'static [ImplicitOperand] = &[
+ ImplicitOperand {
+ spec: OperandSpec::RegRRR,
+ reg: RegSpec::ax(),
+ disp: 0i32,
+ write: false,
+ },
+ ImplicitOperand {
+ spec: OperandSpec::RegRRR,
+ reg: RegSpec::ax(),
+ disp: 0i32,
+ write: true,
+ },
+];
+static DIV_OPS_1OP_WORD: &'static [ImplicitOperand] = &[
+ ImplicitOperand {
+ spec: OperandSpec::RegRRR,
+ reg: RegSpec::ax(),
+ disp: 0i32,
+ write: false,
+ },
+ ImplicitOperand {
+ spec: OperandSpec::RegRRR,
+ reg: RegSpec::dx(),
+ disp: 0i32,
+ write: false,
+ },
+ ImplicitOperand {
+ spec: OperandSpec::RegRRR,
+ reg: RegSpec::ax(),
+ disp: 0i32,
+ write: true,
+ },
+ ImplicitOperand {
+ spec: OperandSpec::RegRRR,
+ reg: RegSpec::dx(),
+ disp: 0i32,
+ write: true,
+ }
+];
+static DIV_OPS_1OP_DWORD: &'static [ImplicitOperand] = &[
+ ImplicitOperand {
+ spec: OperandSpec::RegRRR,
+ reg: RegSpec::eax(),
+ disp: 0i32,
+ write: false,
+ },
+ ImplicitOperand {
+ spec: OperandSpec::RegRRR,
+ reg: RegSpec::edx(),
+ disp: 0i32,
+ write: false,
+ },
+ ImplicitOperand {
+ spec: OperandSpec::RegRRR,
+ reg: RegSpec::eax(),
+ disp: 0i32,
+ write: true,
+ },
+ ImplicitOperand {
+ spec: OperandSpec::RegRRR,
+ reg: RegSpec::edx(),
+ disp: 0i32,
+ write: true,
+ }
+];
+static DIV_OPS_1OP_QWORD: &'static [ImplicitOperand] = &[
+ ImplicitOperand {
+ spec: OperandSpec::RegRRR,
+ reg: RegSpec::rax(),
+ disp: 0i32,
+ write: false,
+ },
+ ImplicitOperand {
+ spec: OperandSpec::RegRRR,
+ reg: RegSpec::rdx(),
+ disp: 0i32,
+ write: false,
+ },
+ ImplicitOperand {
+ spec: OperandSpec::RegRRR,
+ reg: RegSpec::rax(),
+ disp: 0i32,
+ write: true,
+ },
+ ImplicitOperand {
+ spec: OperandSpec::RegRRR,
+ reg: RegSpec::rdx(),
+ disp: 0i32,
+ write: true,
+ }
+];
+
+static RDTSC_OPS: &'static [ImplicitOperand] = &[
+ ImplicitOperand {
+ spec: OperandSpec::RegRRR,
+ reg: RegSpec::eax(),
+ disp: 0i32,
+ write: true,
+ },
+ ImplicitOperand {
+ spec: OperandSpec::RegRRR,
+ reg: RegSpec::edx(),
+ disp: 0i32,
+ write: true,
+ }
+];
+
+static RDTSCP_OPS: &'static [ImplicitOperand] = &[
+ ImplicitOperand {
+ spec: OperandSpec::RegRRR,
+ reg: RegSpec::ecx(),
+ disp: 0i32,
+ write: true,
+ },
+ ImplicitOperand {
+ spec: OperandSpec::RegRRR,
+ reg: RegSpec::eax(),
+ disp: 0i32,
+ write: true,
+ },
+ ImplicitOperand {
+ spec: OperandSpec::RegRRR,
+ reg: RegSpec::edx(),
+ disp: 0i32,
+ write: true,
+ }
+];
+
+static RDPMC_OPS: &'static [ImplicitOperand] = &[
+ ImplicitOperand {
+ spec: OperandSpec::RegRRR,
+ reg: RegSpec::ecx(),
+ disp: 0i32,
+ write: false,
+ },
+ ImplicitOperand {
+ spec: OperandSpec::RegRRR,
+ reg: RegSpec::eax(),
+ disp: 0i32,
+ write: true,
+ },
+ ImplicitOperand {
+ spec: OperandSpec::RegRRR,
+ reg: RegSpec::edx(),
+ disp: 0i32,
+ write: true,
+ }
+];
+
+static CPUID_OPS: &'static [ImplicitOperand] = &[
+ ImplicitOperand {
+ spec: OperandSpec::RegRRR,
+ reg: RegSpec::eax(),
+ disp: 0i32,
+ write: false,
+ },
+ ImplicitOperand {
+ spec: OperandSpec::RegRRR,
+ reg: RegSpec::ecx(),
+ disp: 0i32,
+ write: false,
+ },
+ ImplicitOperand {
+ spec: OperandSpec::RegRRR,
+ reg: RegSpec::eax(),
+ disp: 0i32,
+ write: true,
+ },
+ ImplicitOperand {
+ spec: OperandSpec::RegRRR,
+ reg: RegSpec::ecx(),
+ disp: 0i32,
+ write: true,
+ },
+ ImplicitOperand {
+ spec: OperandSpec::RegRRR,
+ reg: RegSpec::edx(),
+ disp: 0i32,
+ write: true,
+ },
+ ImplicitOperand {
+ spec: OperandSpec::RegRRR,
+ reg: RegSpec::ebx(),
+ disp: 0i32,
+ write: true,
+ },
+];
+
+static CALL_OPS: &'static [ImplicitOperand] = &[
+ ImplicitOperand {
+ spec: OperandSpec::RegRRR,
+ reg: RegSpec::rip(),
+ disp: 0,
+ write: false,
+ },
+ ImplicitOperand {
+ spec: OperandSpec::RegRRR,
+ reg: RegSpec::rip(),
+ disp: 0,
+ write: true,
+ },
+ ImplicitOperand {
+ spec: OperandSpec::Disp,
+ reg: RegSpec::rsp(),
+ disp: -8i32,
+ write: true,
+ },
+ // push.. pushes the value (above), then does a RMW on rsp.
+ ImplicitOperand {
+ spec: OperandSpec::RegRRR,
+ reg: RegSpec::rsp(),
+ disp: 0,
+ write: false,
+ },
+ ImplicitOperand {
+ spec: OperandSpec::RegRRR,
+ reg: RegSpec::rsp(),
+ disp: 0,
+ write: true,
+ }
+];
+
+static JMP_OPS: &'static [ImplicitOperand] = &[
+ ImplicitOperand {
+ spec: OperandSpec::RegRRR,
+ reg: RegSpec::rip(),
+ disp: 0,
+ write: true,
+ },
+];
+
+static JMPF_OPS: &'static [ImplicitOperand] = &[
+ ImplicitOperand {
+ spec: OperandSpec::RegRRR,
+ reg: RegSpec::rip(),
+ disp: 0,
+ write: true,
+ },
+ ImplicitOperand {
+ spec: OperandSpec::RegRRR,
+ reg: RegSpec::cs(),
+ disp: 0,
+ write: true,
+ },
+];
+
+static CALLF_OPS: &'static [ImplicitOperand] = &[
+ ImplicitOperand {
+ spec: OperandSpec::RegRRR,
+ reg: RegSpec::rip(),
+ disp: 0,
+ write: false,
+ },
+ ImplicitOperand {
+ spec: OperandSpec::RegRRR,
+ reg: RegSpec::cs(),
+ disp: 0,
+ write: false,
+ },
+ ImplicitOperand {
+ spec: OperandSpec::RegRRR,
+ reg: RegSpec::rip(),
+ disp: 0,
+ write: true,
+ },
+ ImplicitOperand {
+ spec: OperandSpec::Disp,
+ reg: RegSpec::rsp(),
+ disp: -10i32,
+ write: true,
+ },
+ // push.. pushes the value (above), then does a RMW on rsp.
+ ImplicitOperand {
+ spec: OperandSpec::RegRRR,
+ reg: RegSpec::rsp(),
+ disp: 0,
+ write: false,
+ },
+ ImplicitOperand {
+ spec: OperandSpec::RegRRR,
+ reg: RegSpec::rsp(),
+ disp: 0,
+ write: true,
+ }
+];
+
+static RETF_OPS: &'static [ImplicitOperand] = &[
+ ImplicitOperand {
+ spec: OperandSpec::Deref,
+ reg: RegSpec::rsp(),
+ disp: 0i32,
+ write: false,
+ },
+ ImplicitOperand {
+ spec: OperandSpec::RegRRR,
+ reg: RegSpec::rip(),
+ disp: 0,
+ write: true,
+ },
+ ImplicitOperand {
+ spec: OperandSpec::RegRRR,
+ reg: RegSpec::cs(),
+ disp: 0,
+ write: true,
+ },
+ // pop.. pops the value (above), then does a RMW on rsp.
+ ImplicitOperand {
+ spec: OperandSpec::RegRRR,
+ reg: RegSpec::rsp(),
+ disp: 0,
+ write: false,
+ },
+ ImplicitOperand {
+ spec: OperandSpec::RegRRR,
+ reg: RegSpec::rsp(),
+ disp: 0,
+ write: true,
+ }
+];
+
+static LFS_OPS: &'static [ImplicitOperand] = &[
+ ImplicitOperand {
+ spec: OperandSpec::RegRRR,
+ reg: RegSpec::fs(),
+ disp: 0,
+ write: true,
+ }
+];
+
+static LGS_OPS: &'static [ImplicitOperand] = &[
+ ImplicitOperand {
+ spec: OperandSpec::RegRRR,
+ reg: RegSpec::gs(),
+ disp: 0,
+ write: true,
+ }
+];
+
+static LSS_OPS: &'static [ImplicitOperand] = &[
+ ImplicitOperand {
+ spec: OperandSpec::RegRRR,
+ reg: RegSpec::ss(),
+ disp: 0,
+ write: true,
+ }
+];
+
+static CMPXCHG_OPS_BYTE: &'static [ImplicitOperand] = &[
+ ImplicitOperand {
+ spec: OperandSpec::RegRRR,
+ reg: RegSpec::al(),
+ disp: 0,
+ write: false,
+ },
+ ImplicitOperand {
+ spec: OperandSpec::RegRRR,
+ reg: RegSpec::al(),
+ disp: 0,
+ write: true,
+ },
+];
+
+static CMPXCHG_OPS_WORD: &'static [ImplicitOperand] = &[
+ ImplicitOperand {
+ spec: OperandSpec::RegRRR,
+ reg: RegSpec::ax(),
+ disp: 0,
+ write: false,
+ },
+ ImplicitOperand {
+ spec: OperandSpec::RegRRR,
+ reg: RegSpec::ax(),
+ disp: 0,
+ write: true,
+ },
+];
+
+static CMPXCHG_OPS_DWORD: &'static [ImplicitOperand] = &[
+ ImplicitOperand {
+ spec: OperandSpec::RegRRR,
+ reg: RegSpec::eax(),
+ disp: 0,
+ write: false,
+ },
+ ImplicitOperand {
+ spec: OperandSpec::RegRRR,
+ reg: RegSpec::eax(),
+ disp: 0,
+ write: true,
+ },
+];
+
+static CMPXCHG_OPS_QWORD: &'static [ImplicitOperand] = &[
+ ImplicitOperand {
+ spec: OperandSpec::RegRRR,
+ reg: RegSpec::rax(),
+ disp: 0,
+ write: false,
+ },
+ ImplicitOperand {
+ spec: OperandSpec::RegRRR,
+ reg: RegSpec::rax(),
+ disp: 0,
+ write: true,
+ },
+];
+
+static CMPXCHG8B_OPS: &'static [ImplicitOperand] = &[
+ ImplicitOperand {
+ spec: OperandSpec::RegRRR,
+ reg: RegSpec::eax(),
+ disp: 0,
+ write: false,
+ },
+ ImplicitOperand {
+ spec: OperandSpec::RegRRR,
+ reg: RegSpec::edx(),
+ disp: 0,
+ write: false,
+ },
+ ImplicitOperand {
+ spec: OperandSpec::RegRRR,
+ reg: RegSpec::ecx(),
+ disp: 0,
+ write: false,
+ },
+ ImplicitOperand {
+ spec: OperandSpec::RegRRR,
+ reg: RegSpec::ebx(),
+ disp: 0,
+ write: false,
+ },
+ ImplicitOperand {
+ spec: OperandSpec::RegRRR,
+ reg: RegSpec::eax(),
+ disp: 0,
+ write: true,
+ },
+ ImplicitOperand {
+ spec: OperandSpec::RegRRR,
+ reg: RegSpec::edx(),
+ disp: 0,
+ write: true,
+ },
+];
+
+static CMPXCHG16B_OPS: &'static [ImplicitOperand] = &[
+ ImplicitOperand {
+ spec: OperandSpec::RegRRR,
+ reg: RegSpec::rax(),
+ disp: 0,
+ write: false,
+ },
+ ImplicitOperand {
+ spec: OperandSpec::RegRRR,
+ reg: RegSpec::rdx(),
+ disp: 0,
+ write: false,
+ },
+ ImplicitOperand {
+ spec: OperandSpec::RegRRR,
+ reg: RegSpec::rcx(),
+ disp: 0,
+ write: false,
+ },
+ ImplicitOperand {
+ spec: OperandSpec::RegRRR,
+ reg: RegSpec::rbx(),
+ disp: 0,
+ write: false,
+ },
+ ImplicitOperand {
+ spec: OperandSpec::RegRRR,
+ reg: RegSpec::rax(),
+ disp: 0,
+ write: true,
+ },
+ ImplicitOperand {
+ spec: OperandSpec::RegRRR,
+ reg: RegSpec::rdx(),
+ disp: 0,
+ write: true,
+ },
+];
+
+// TODO: register size should be picked by memory access size, but defaulting to rdi for now.
+static MASKMOVQ_OPS: &'static [ImplicitOperand] = &[
+ ImplicitOperand {
+ spec: OperandSpec::Deref,
+ reg: RegSpec::rdi(),
+ disp: 0i32,
+ write: true,
+ },
+];
+
+static MONITOR_OPS: &'static [ImplicitOperand] = &[
+ ImplicitOperand {
+ spec: OperandSpec::Deref,
+ reg: RegSpec::rax(),
+ disp: 0i32,
+ write: false,
+ },
+ ImplicitOperand {
+ spec: OperandSpec::RegRRR,
+ reg: RegSpec::rcx(),
+ disp: 0i32,
+ write: false,
+ },
+ ImplicitOperand {
+ spec: OperandSpec::Deref,
+ reg: RegSpec::rdx(),
+ disp: 0i32,
+ write: false,
+ },
+];
+
+static XMM0_READ_OPS: &'static [ImplicitOperand] = &[
+ ImplicitOperand {
+ spec: OperandSpec::RegRRR,
+ reg: RegSpec::xmm0(),
+ disp: 0i32,
+ write: false,
+ },
+];
+
+static MULX_64B_OPS: &'static [ImplicitOperand] = &[
+ ImplicitOperand {
+ spec: OperandSpec::RegRRR,
+ reg: RegSpec::rdx(),
+ disp: 0i32,
+ write: false,
+ },
+];
+
+static MULX_32B_OPS: &'static [ImplicitOperand] = &[
+ ImplicitOperand {
+ spec: OperandSpec::RegRRR,
+ reg: RegSpec::edx(),
+ disp: 0i32,
+ write: false,
+ },
+];
+
+static EDI_MEMWRITE_OPS: &'static [ImplicitOperand] = &[
+ ImplicitOperand {
+ spec: OperandSpec::Deref,
+ reg: RegSpec::edi(),
+ disp: 0i32,
+ write: false,
+ },
+];
+
+static RDI_MEMWRITE_OPS: &'static [ImplicitOperand] = &[
+ ImplicitOperand {
+ spec: OperandSpec::Deref,
+ reg: RegSpec::rdi(),
+ disp: 0i32,
+ write: false,
+ },
+];
+
+static PCMPESTRI_64B_OPS: &'static [ImplicitOperand] = &[
+ ImplicitOperand {
+ spec: OperandSpec::RegRRR,
+ reg: RegSpec::rax(),
+ disp: 0i32,
+ write: false,
+ },
+ ImplicitOperand {
+ spec: OperandSpec::RegRRR,
+ reg: RegSpec::rdx(),
+ disp: 0i32,
+ write: false,
+ },
+ ImplicitOperand {
+ spec: OperandSpec::RegRRR,
+ reg: RegSpec::ecx(),
+ disp: 0i32,
+ write: true,
+ },
+];
+
+static PCMPESTRI_32B_OPS: &'static [ImplicitOperand] = &[
+ ImplicitOperand {
+ spec: OperandSpec::RegRRR,
+ reg: RegSpec::eax(),
+ disp: 0i32,
+ write: false,
+ },
+ ImplicitOperand {
+ spec: OperandSpec::RegRRR,
+ reg: RegSpec::edx(),
+ disp: 0i32,
+ write: false,
+ },
+ ImplicitOperand {
+ spec: OperandSpec::RegRRR,
+ reg: RegSpec::ecx(),
+ disp: 0i32,
+ write: true,
+ },
+];
+
+static PCMPESTRM_64B_OPS: &'static [ImplicitOperand] = &[
+ ImplicitOperand {
+ spec: OperandSpec::RegRRR,
+ reg: RegSpec::rax(),
+ disp: 0i32,
+ write: false,
+ },
+ ImplicitOperand {
+ spec: OperandSpec::RegRRR,
+ reg: RegSpec::rdx(),
+ disp: 0i32,
+ write: false,
+ },
+ ImplicitOperand {
+ spec: OperandSpec::RegRRR,
+ reg: RegSpec::xmm0(),
+ disp: 0i32,
+ write: true,
+ },
+];
+
+static PCMPESTRM_32B_OPS: &'static [ImplicitOperand] = &[
+ ImplicitOperand {
+ spec: OperandSpec::RegRRR,
+ reg: RegSpec::eax(),
+ disp: 0i32,
+ write: false,
+ },
+ ImplicitOperand {
+ spec: OperandSpec::RegRRR,
+ reg: RegSpec::edx(),
+ disp: 0i32,
+ write: false,
+ },
+ ImplicitOperand {
+ spec: OperandSpec::RegRRR,
+ reg: RegSpec::xmm0(),
+ disp: 0i32,
+ write: true,
+ },
+];
+
+static PCMPISTRI_OPS: &'static [ImplicitOperand] = &[
+ ImplicitOperand {
+ spec: OperandSpec::RegRRR,
+ reg: RegSpec::ecx(),
+ disp: 0i32,
+ write: true,
+ },
+];
+
+static PCMPISTRM_OPS: &'static [ImplicitOperand] = &[
+ ImplicitOperand {
+ spec: OperandSpec::RegRRR,
+ reg: RegSpec::xmm0(),
+ disp: 0i32,
+ write: true,
+ },
+];
+
+static READ_EDX_EAX_OPS: &'static [ImplicitOperand] = &[
+ ImplicitOperand {
+ spec: OperandSpec::RegRRR,
+ reg: RegSpec::eax(),
+ disp: 0i32,
+ write: false,
+ },
+ ImplicitOperand {
+ spec: OperandSpec::RegRRR,
+ reg: RegSpec::edx(),
+ disp: 0i32,
+ write: false,
+ },
+];
+
+static LMSW_OPS: &'static [ImplicitOperand] = &[
+ ImplicitOperand {
+ spec: OperandSpec::RegRRR,
+ reg: RegSpec::cr0(),
+ disp: 0i32,
+ write: false,
+ },
+ ImplicitOperand {
+ spec: OperandSpec::RegRRR,
+ reg: RegSpec::cr0(),
+ disp: 0i32,
+ write: true,
+ },
+];
+
+static SMSW_OPS: &'static [ImplicitOperand] = &[
+ ImplicitOperand {
+ spec: OperandSpec::RegRRR,
+ reg: RegSpec::cr0(),
+ disp: 0i32,
+ write: false,
+ },
+];
+
+static READ_EAX_OPS: &'static [ImplicitOperand] = &[
+ ImplicitOperand {
+ spec: OperandSpec::RegRRR,
+ reg: RegSpec::eax(),
+ disp: 0i32,
+ write: false,
+ },
+];
+
+static WRITE_AL_OPS: &'static [ImplicitOperand] = &[
+ ImplicitOperand {
+ spec: OperandSpec::RegRRR,
+ reg: RegSpec::al(),
+ disp: 0i32,
+ write: true,
+ },
+];
+
+static RW_XMM0TO7_OPS: &'static [ImplicitOperand] = &[
+ ImplicitOperand {
+ spec: OperandSpec::RegRRR,
+ reg: RegSpec::xmm(0),
+ disp: 0i32,
+ write: false,
+ },
+ ImplicitOperand {
+ spec: OperandSpec::RegRRR,
+ reg: RegSpec::xmm(1),
+ disp: 0i32,
+ write: false,
+ },
+ ImplicitOperand {
+ spec: OperandSpec::RegRRR,
+ reg: RegSpec::xmm(2),
+ disp: 0i32,
+ write: false,
+ },
+ ImplicitOperand {
+ spec: OperandSpec::RegRRR,
+ reg: RegSpec::xmm(3),
+ disp: 0i32,
+ write: false,
+ },
+ ImplicitOperand {
+ spec: OperandSpec::RegRRR,
+ reg: RegSpec::xmm(4),
+ disp: 0i32,
+ write: false,
+ },
+ ImplicitOperand {
+ spec: OperandSpec::RegRRR,
+ reg: RegSpec::xmm(5),
+ disp: 0i32,
+ write: false,
+ },
+ ImplicitOperand {
+ spec: OperandSpec::RegRRR,
+ reg: RegSpec::xmm(6),
+ disp: 0i32,
+ write: false,
+ },
+ ImplicitOperand {
+ spec: OperandSpec::RegRRR,
+ reg: RegSpec::xmm(7),
+ disp: 0i32,
+ write: false,
+ },
+ ImplicitOperand {
+ spec: OperandSpec::RegRRR,
+ reg: RegSpec::xmm(0),
+ disp: 0i32,
+ write: true,
+ },
+ ImplicitOperand {
+ spec: OperandSpec::RegRRR,
+ reg: RegSpec::xmm(1),
+ disp: 0i32,
+ write: true,
+ },
+ ImplicitOperand {
+ spec: OperandSpec::RegRRR,
+ reg: RegSpec::xmm(2),
+ disp: 0i32,
+ write: true,
+ },
+ ImplicitOperand {
+ spec: OperandSpec::RegRRR,
+ reg: RegSpec::xmm(3),
+ disp: 0i32,
+ write: true,
+ },
+ ImplicitOperand {
+ spec: OperandSpec::RegRRR,
+ reg: RegSpec::xmm(4),
+ disp: 0i32,
+ write: true,
+ },
+ ImplicitOperand {
+ spec: OperandSpec::RegRRR,
+ reg: RegSpec::xmm(5),
+ disp: 0i32,
+ write: true,
+ },
+ ImplicitOperand {
+ spec: OperandSpec::RegRRR,
+ reg: RegSpec::xmm(6),
+ disp: 0i32,
+ write: true,
+ },
+ ImplicitOperand {
+ spec: OperandSpec::RegRRR,
+ reg: RegSpec::xmm(7),
+ disp: 0i32,
+ write: true,
+ },
+];
+
+static ENCODEKEY_OPS: &'static [ImplicitOperand] = &[
+ ImplicitOperand {
+ spec: OperandSpec::RegRRR,
+ reg: RegSpec::xmm(0),
+ disp: 0i32,
+ write: false,
+ },
+ ImplicitOperand {
+ spec: OperandSpec::RegRRR,
+ reg: RegSpec::xmm(0),
+ disp: 0i32,
+ write: true,
+ },
+ ImplicitOperand {
+ spec: OperandSpec::RegRRR,
+ reg: RegSpec::xmm(1),
+ disp: 0i32,
+ write: true,
+ },
+ ImplicitOperand {
+ spec: OperandSpec::RegRRR,
+ reg: RegSpec::xmm(2),
+ disp: 0i32,
+ write: true,
+ },
+ ImplicitOperand {
+ spec: OperandSpec::RegRRR,
+ reg: RegSpec::xmm(4),
+ disp: 0i32,
+ write: true,
+ },
+ ImplicitOperand {
+ spec: OperandSpec::RegRRR,
+ reg: RegSpec::xmm(5),
+ disp: 0i32,
+ write: true,
+ },
+ ImplicitOperand {
+ spec: OperandSpec::RegRRR,
+ reg: RegSpec::xmm(6),
+ disp: 0i32,
+ write: true,
+ },
+];
+
+static LOADIWKEY_OPS: &'static [ImplicitOperand] = &[
+ ImplicitOperand {
+ spec: OperandSpec::RegRRR,
+ reg: RegSpec::eax(),
+ disp: 0i32,
+ write: false,
+ },
+ ImplicitOperand {
+ spec: OperandSpec::RegRRR,
+ reg: RegSpec::xmm(0),
+ disp: 0i32,
+ write: false,
+ },
+];
+
+static RW_RCX_OPS: &'static [ImplicitOperand] = &[
+ ImplicitOperand {
+ spec: OperandSpec::RegRRR,
+ reg: RegSpec::rcx(),
+ disp: 0,
+ write: false,
+ },
+ ImplicitOperand {
+ spec: OperandSpec::RegRRR,
+ reg: RegSpec::rcx(),
+ disp: 0,
+ write: true,
+ },
+];
+
+static RW_ECX_OPS: &'static [ImplicitOperand] = &[
+ ImplicitOperand {
+ spec: OperandSpec::RegRRR,
+ reg: RegSpec::ecx(),
+ disp: 0,
+ write: false,
+ },
+ ImplicitOperand {
+ spec: OperandSpec::RegRRR,
+ reg: RegSpec::ecx(),
+ disp: 0,
+ write: true,
+ },
+];
+
+static RW_CX_OPS: &'static [ImplicitOperand] = &[
+ ImplicitOperand {
+ spec: OperandSpec::RegRRR,
+ reg: RegSpec::cx(),
+ disp: 0,
+ write: false,
+ },
+ ImplicitOperand {
+ spec: OperandSpec::RegRRR,
+ reg: RegSpec::cx(),
+ disp: 0,
+ write: true,
+ },
+];
+
+const PUSH_OPS_IDX: u16 = 1;
+const POP_OPS_IDX: u16 = 2;
+const JCC_OPS_IDX: u16 = 3;
+const CBW_IDX: u16 = 4;
+const CWDE_IDX: u16 = 5;
+const CDQE_IDX: u16 = 6;
+const CWD_IDX: u16 = 7;
+const CDQ_IDX: u16 = 8;
+const CQO_IDX: u16 = 9;
+const PUSHF_IDX: u16 = 10;
+const POPF_IDX: u16 = 11;
+const SAHF_IDX: u16 = 12;
+const LAHF_IDX: u16 = 13;
+const MOVS_IDX: u16 = 14;
+const LODS_IDX: u16 = 15;
+const STOS_IDX: u16 = 16;
+const SCAS_IDX: u16 = 17;
+const RETURN_IDX: u16 = 18;
+const LEAVE_IDX: u16 = 19;
+const XLAT_IDX: u16 = 20;
+const CLTS_IDX: u16 = 21;
+const MUL_IDX_1OP_BYTE: u16 = 22;
+const MUL_IDX_1OP_WORD: u16 = 23;
+const MUL_IDX_1OP_DWORD: u16 = 24;
+const MUL_IDX_1OP_QWORD: u16 = 25;
+const DIV_IDX_1OP_BYTE: u16 = 26;
+const DIV_IDX_1OP_WORD: u16 = 27;
+const DIV_IDX_1OP_DWORD: u16 = 28;
+const DIV_IDX_1OP_QWORD: u16 = 29;
+const RDTSC_IDX: u16 = 30;
+const RDPMC_IDX: u16 = 31;
+const CPUID_IDX: u16 = 32;
+const CALL_OPS_IDX: u16 = 33;
+const JMP_OPS_IDX: u16 = 34;
+const CALLF_OPS_IDX: u16 = 35;
+const JMPF_OPS_IDX: u16 = 36;
+const LFS_IDX: u16 = 37;
+const LGS_IDX: u16 = 38;
+const LSS_IDX: u16 = 39;
+const CMPXCHG_IDX_BYTE: u16 = 40;
+const CMPXCHG_IDX_WORD: u16 = 41;
+const CMPXCHG_IDX_DWORD: u16 = 42;
+const CMPXCHG_IDX_QWORD: u16 = 43;
+const CMPXCHG8B_IDX: u16 = 44;
+const CMPXCHG16B_IDX: u16 = 45;
+const RDTSCP_IDX: u16 = 46;
+const MASKMOVQ_IDX: u16 = 47;
+const MONITOR_IDX: u16 = 48;
+const XMM0_READ_IDX: u16 = 49;
+const MULX_64B_IDX: u16 = 50;
+const MULX_32B_IDX: u16 = 51;
+const EDI_MEMWRITE_IDX: u16 = 52;
+const RDI_MEMWRITE_IDX: u16 = 53;
+const PCMPESTRI_64B_IDX: u16 = 54;
+const PCMPESTRI_32B_IDX: u16 = 55;
+const PCMPESTRM_64B_IDX: u16 = 56;
+const PCMPESTRM_32B_IDX: u16 = 57;
+const PCMPISTRI_IDX: u16 = 58;
+const PCMPISTRM_IDX: u16 = 59;
+const READ_EDX_EAX_IDX: u16 = 60;
+const RETF_IDX: u16 = 61;
+const LMSW_IDX: u16 = 62;
+const SMSW_IDX: u16 = 63;
+const READ_EAX_IDX: u16 = 64;
+const WRITE_AL_IDX: u16 = 65;
+const RW_XMM0TO7_IDX: u16 = 66;
+const ENCODEKEY_IDX: u16 = 67;
+const LOADIWKEY_IDX: u16 = 68;
+const RW_RCX_IDX: u16 = 69;
+const RW_ECX_IDX: u16 = 70;
+const RW_CX_IDX: u16 = 71;
+const ENTER_IDX: u16 = 72;
+
+static IMPLICIT_OPS_LIST: [&[ImplicitOperand]; 73] = [
+ &[], // implicit ops list 0 is not used
+ PUSH_OPS,
+ POP_OPS,
+ JCC_OPS,
+ CBW_OPS,
+ CWDE_OPS,
+ CDQE_OPS,
+ CWD_OPS,
+ CDQ_OPS,
+ CQO_OPS,
+ PUSHF_OPS,
+ POPF_OPS,
+ SAHF_OPS,
+ LAHF_OPS,
+ MOVS_OPS,
+ LODS_OPS,
+ STOS_OPS,
+ SCAS_OPS,
+ RETURN_OPS,
+ LEAVE_OPS,
+ XLAT_OPS,
+ CLTS_OPS,
+ MUL_OPS_1OP_BYTE,
+ MUL_OPS_1OP_WORD,
+ MUL_OPS_1OP_DWORD,
+ MUL_OPS_1OP_QWORD,
+ DIV_OPS_1OP_BYTE,
+ DIV_OPS_1OP_WORD,
+ DIV_OPS_1OP_DWORD,
+ DIV_OPS_1OP_QWORD,
+ RDTSC_OPS,
+ RDPMC_OPS,
+ CPUID_OPS,
+ CALL_OPS,
+ JMP_OPS,
+ CALLF_OPS,
+ JMPF_OPS,
+ LFS_OPS,
+ LGS_OPS,
+ LSS_OPS,
+ CMPXCHG_OPS_BYTE,
+ CMPXCHG_OPS_WORD,
+ CMPXCHG_OPS_DWORD,
+ CMPXCHG_OPS_QWORD,
+ CMPXCHG8B_OPS,
+ CMPXCHG16B_OPS,
+ RDTSCP_OPS,
+ MASKMOVQ_OPS,
+ MONITOR_OPS,
+ XMM0_READ_OPS,
+ MULX_64B_OPS,
+ MULX_32B_OPS,
+ EDI_MEMWRITE_OPS,
+ RDI_MEMWRITE_OPS,
+ PCMPESTRI_64B_OPS,
+ PCMPESTRI_32B_OPS,
+ PCMPESTRM_64B_OPS,
+ PCMPESTRM_32B_OPS,
+ PCMPISTRI_OPS,
+ PCMPISTRM_OPS,
+ READ_EDX_EAX_OPS,
+ RETF_OPS,
+ LMSW_OPS,
+ SMSW_OPS,
+ READ_EAX_OPS,
+ WRITE_AL_OPS,
+ RW_XMM0TO7_OPS,
+ ENCODEKEY_OPS,
+ LOADIWKEY_OPS,
+ RW_RCX_OPS,
+ RW_ECX_OPS,
+ RW_CX_OPS,
+ ENTER_OPS,
+];
+
+fn opcode2behavior(opc: &Opcode) -> BehaviorDigest {
+ let idx = (*opc as u32) & 0xfff;
+ TABLE[idx as usize]
+}
+
+#[cfg(feature = "_debug_internal_asserts")]
+#[test]
+fn behavior_table_size_is_right() {
+ use strum::EnumCount;
+ assert_eq!(TABLE.len(), super::Opcode::COUNT);
+
+ assert_eq!(opcode2behavior(&Opcode::VMOVLHPS), GENERAL_W_R_R);
+}
+
+/// this table MUST line up with Opcode declaration order in `mod.rs`.
+static TABLE: [BehaviorDigest; 1413] = [
+ /* ADD => */ GENERAL_RW_R_FLAGWRITE,
+ /* OR => */ GENERAL_RW_R_FLAGWRITE,
+ /* ADC => */ GENERAL_RW_R_FLAGRW,
+ /* SBB => */ GENERAL_RW_R_FLAGRW,
+ /* AND => */ GENERAL_RW_R_FLAGWRITE,
+ /* SUB => */ GENERAL_RW_R_FLAGWRITE,
+ /* XOR => */ GENERAL_RW_R_FLAGWRITE,
+ /* CMP => */ GENERAL_R_R_FLAGWRITE,
+ /* ROL => */ GENERAL_RW_R_FLAGWRITE,
+ /* ROR => */ GENERAL_RW_R_FLAGWRITE,
+ /* RCL => */ GENERAL_RW_R_FLAGRW,
+ /* RCR => */ GENERAL_RW_R_FLAGRW,
+ /* SHL => */ GENERAL_RW_R_FLAGWRITE,
+ /* SHR => */ GENERAL_RW_R_FLAGWRITE,
+ /* SAL => */ GENERAL_RW_R_FLAGWRITE,
+ /* SAR => */ GENERAL_RW_R_FLAGWRITE,
+ /* BTC => */ GENERAL_RW_R_FLAGWRITE
+ .set_complex(true),
+ /* BTR => */ GENERAL_RW_R_FLAGWRITE
+ .set_complex(true),
+ /* BTS => */ GENERAL_RW_R_FLAGWRITE
+ .set_complex(true),
+ /* CMPXCHG => */ GENERAL_RW_R_FLAGWRITE
+ .set_nontrivial(true),
+ /* CMPXCHG8B => */ GENERAL_RW_R_FLAGWRITE
+ .set_implicit_ops(CMPXCHG8B_IDX),
+ /* CMPXCHG16B => */ GENERAL_RW_R_FLAGWRITE
+ .set_implicit_ops(CMPXCHG16B_IDX),
+ /* DEC => */ GENERAL_RW_FLAGWRITE,
+ /* INC => */ GENERAL_RW_FLAGWRITE,
+ /* NEG => */ GENERAL_RW_FLAGWRITE,
+ /* NOT => */ GENERAL_RW,
+ /* XADD => */ GENERAL_RW_RW_FLAGRW,
+ /* XCHG => */ GENERAL_RW_RW,
+
+ /* CMPS => */ GENERAL_RW_RW_FLAGWRITE
+ .set_implicit_ops(MOVS_IDX),
+ /* SCAS => */ GENERAL_W_R_FLAGREAD
+ .set_implicit_ops(SCAS_IDX), // TODO: second operand is `aX`, right?
+ /* MOVS => */ GENERAL_W_R_FLAGREAD
+ .set_implicit_ops(MOVS_IDX),
+ /* LODS => */ GENERAL_W_R_FLAGREAD
+ .set_implicit_ops(LODS_IDX),
+ /* STOS => */ GENERAL_W_R_FLAGREAD
+ .set_implicit_ops(STOS_IDX),
+ /* INS => */ GENERAL_W_R,
+ /* OUTS => */ GENERAL_R_R,
+
+ // "Invalid" should never be a publicly-visible Opcode variant..
+ /* Invalid => */ BehaviorDigest::empty()
+ .set_complex(true),
+ /* BT => */ GENERAL_R_R_FLAGWRITE
+ .set_complex(true),
+ /* BSF => */ GENERAL_RW_R_FLAGWRITE,
+ /* BSR => */ GENERAL_RW_R_FLAGWRITE,
+ /* TZCNT => */ GENERAL_RW_R_FLAGWRITE,
+ /* MOVSS => */ GENERAL_RW_R,
+ /* ADDSS => */ GENERAL_RW_R,
+ /* SUBSS => */ GENERAL_RW_R,
+ /* MULSS => */ GENERAL_RW_R,
+ /* DIVSS => */ GENERAL_RW_R,
+ /* MINSS => */ GENERAL_RW_R,
+ /* MAXSS => */ GENERAL_RW_R,
+ /* SQRTSS => */ GENERAL_RW_R,
+ /* MOVSD => */ GENERAL_RW_R,
+ /* SQRTSD => */ GENERAL_RW_R,
+ /* ADDSD => */ GENERAL_RW_R,
+ /* SUBSD => */ GENERAL_RW_R,
+ /* MULSD => */ GENERAL_RW_R,
+ /* DIVSD => */ GENERAL_RW_R,
+ /* MINSD => */ GENERAL_RW_R,
+ /* MAXSD => */ GENERAL_RW_R,
+ /* MOVSLDUP => */ GENERAL_W_R,
+ /* MOVSHDUP => */ GENERAL_W_R,
+ /* MOVDDUP => */ GENERAL_W_R,
+ /* HADDPS => */ GENERAL_RW_R,
+ /* HSUBPS => */ GENERAL_RW_R,
+ /* ADDSUBPD => */ GENERAL_RW_R,
+ /* ADDSUBPS => */ GENERAL_RW_R,
+ /* CVTSI2SS => */ GENERAL_W_R,
+ /* CVTSI2SD => */ GENERAL_W_R,
+ /* CVTTSD2SI => */ GENERAL_W_R,
+ /* CVTTPS2DQ => */ GENERAL_W_R,
+ /* CVTPD2DQ => */ GENERAL_W_R,
+ /* CVTPD2PS => */ GENERAL_W_R,
+ /* CVTPS2DQ => */ GENERAL_W_R,
+ /* CVTSD2SI => */ GENERAL_RW_R,
+ /* CVTSD2SS => */ GENERAL_RW_R,
+ /* CVTTSS2SI => */ GENERAL_RW_R,
+ /* CVTSS2SI => */ GENERAL_RW_R,
+ /* CVTSS2SD => */ GENERAL_RW_R,
+ /* CVTDQ2PD => */ GENERAL_W_R,
+ /* LDDQU => */ GENERAL_W_R,
+ /* MOVZX => */ GENERAL_RW_R,
+ /* MOVSX => */ GENERAL_RW_R,
+ /* MOVSXD => */ GENERAL_RW_R,
+ /* SHRD => */ GENERAL_RW_R_FLAGWRITE
+ .set_operand(2, Access::Read),
+ /* HLT => */ BehaviorDigest::empty()
+ .set_pl0(),
+ /* CALL => */ BehaviorDigest::empty()
+ .set_implicit_ops(CALL_OPS_IDX)
+ .set_pl_any()
+ .set_operand(0, Access::Read),
+ /* CALLF => */ BehaviorDigest::empty()
+ .set_implicit_ops(CALLF_OPS_IDX)
+ .set_pl_any()
+ .set_operand(0, Access::Read),
+ /* JMP => */ BehaviorDigest::empty()
+ .set_implicit_ops(JMP_OPS_IDX)
+ .set_pl_any()
+ .set_operand(0, Access::Read),
+ /* JMPF => */ BehaviorDigest::empty()
+ .set_implicit_ops(JMPF_OPS_IDX)
+ .set_pl_any()
+ .set_operand(0, Access::Read),
+ /* PUSH => */ BehaviorDigest::empty()
+ .set_implicit_ops(PUSH_OPS_IDX)
+ .set_pl_any()
+ .set_operand(0, Access::Read),
+ /* POP => */ BehaviorDigest::empty()
+ .set_implicit_ops(POP_OPS_IDX)
+ .set_pl_any()
+ .set_operand(0, Access::Write),
+ /* LEA => */ GENERAL_W_R,
+ /* NOP => */ GENERAL,
+ /* PREFETCHNTA => */ GENERAL_R,
+ /* PREFETCH0 => */ GENERAL_R,
+ /* PREFETCH1 => */ GENERAL_R,
+ /* PREFETCH2 => */ GENERAL_R,
+ /* POPF => */ BehaviorDigest::empty()
+ .set_implicit_ops(POPF_IDX)
+ .set_pl_any(),
+ /* INT => */ GENERAL_R,
+ /* INTO => */ GENERAL_R_FLAGREAD,
+ // TODO: should there be implicit operands for the iret instructions? they're complex
+ // anyway..
+ /* IRET => */ BehaviorDigest::empty()
+ .set_pl_special()
+ .set_complex(true),
+ /* IRETD => */ BehaviorDigest::empty()
+ .set_pl_special()
+ .set_complex(true),
+ /* IRETQ => */ BehaviorDigest::empty()
+ .set_pl_special()
+ .set_complex(true),
+ /* RETF => */ BehaviorDigest::empty()
+ .set_pl_any()
+ .set_nontrivial(true)
+ .set_implicit_ops(RETF_IDX),
+ /* ENTER => */ BehaviorDigest::empty()
+ .set_implicit_ops(ENTER_IDX)
+ .set_operand(0, Access::Read)
+ .set_operand(1, Access::Read)
+ .set_pl_any(),
+ /* LEAVE => */ BehaviorDigest::empty()
+ .set_implicit_ops(LEAVE_IDX)
+ .set_pl_any(),
+ /* MOV => */ GENERAL_RW_R,
+ /* RETURN => */ BehaviorDigest::empty()
+ .set_implicit_ops(RETURN_IDX)
+ .set_nontrivial(true)
+ .set_pl_any(),
+ /* PUSHF => */ BehaviorDigest::empty()
+ .set_implicit_ops(PUSHF_IDX)
+ .set_pl_any(),
+ /* WAIT => */ GENERAL,
+ /* CBW => */ BehaviorDigest::empty()
+ .set_implicit_ops(CBW_IDX)
+ .set_pl_any(),
+ /* CWDE => */ BehaviorDigest::empty()
+ .set_implicit_ops(CWDE_IDX)
+ .set_pl_any(),
+ /* CDQE => */ BehaviorDigest::empty()
+ .set_implicit_ops(CDQE_IDX)
+ .set_pl_any(),
+ /* CWD => */ BehaviorDigest::empty()
+ .set_implicit_ops(CWD_IDX)
+ .set_pl_any(),
+ /* CDQ => */ BehaviorDigest::empty()
+ .set_implicit_ops(CDQ_IDX)
+ .set_pl_any(),
+ /* CQO => */ BehaviorDigest::empty()
+ .set_implicit_ops(CQO_IDX)
+ .set_pl_any(),
+ /* LAHF => */ BehaviorDigest::empty()
+ .set_implicit_ops(LAHF_IDX)
+ .set_pl_any(),
+ /* SAHF => */ BehaviorDigest::empty()
+ .set_implicit_ops(SAHF_IDX)
+ .set_pl_any(),
+ /* TEST => */ GENERAL_R_R_FLAGWRITE,
+ /* IN => */ BehaviorDigest::empty()
+ .set_complex(true)
+ .set_pl_special()
+ .set_operand(0, Access::Write)
+ .set_operand(1, Access::Read),
+ /* OUT => */ BehaviorDigest::empty()
+ .set_complex(true)
+ .set_pl_special()
+ .set_operand(0, Access::Read)
+ .set_operand(1, Access::Read),
+ /* IMUL => */ BehaviorDigest::empty()
+ .set_pl_any()
+ .set_flags_access(Access::Write)
+ .set_operand(0, Access::Read) // operands are adjusted via non_trivial
+ .set_nontrivial(true),
+ /* JO => */ JCC,
+ /* JNO => */ JCC,
+ /* JB => */ JCC,
+ /* JNB => */ JCC,
+ /* JZ => */ JCC,
+ /* JNZ => */ JCC,
+ /* JA => */ JCC,
+ /* JNA => */ JCC,
+ /* JS => */ JCC,
+ /* JNS => */ JCC,
+ /* JP => */ JCC,
+ /* JNP => */ JCC,
+ /* JL => */ JCC,
+ /* JGE => */ JCC,
+ /* JLE => */ JCC,
+ /* JG => */ JCC,
+ /* CMOVA => */ CMOVCC,
+ /* CMOVB => */ CMOVCC,
+ /* CMOVG => */ CMOVCC,
+ /* CMOVGE => */ CMOVCC,
+ /* CMOVL => */ CMOVCC,
+ /* CMOVLE => */ CMOVCC,
+ /* CMOVNA => */ CMOVCC,
+ /* CMOVNB => */ CMOVCC,
+ /* CMOVNO => */ CMOVCC,
+ /* CMOVNP => */ CMOVCC,
+ /* CMOVNS => */ CMOVCC,
+ /* CMOVNZ => */ CMOVCC,
+ /* CMOVO => */ CMOVCC,
+ /* CMOVP => */ CMOVCC,
+ /* CMOVS => */ CMOVCC,
+ /* CMOVZ => */ CMOVCC,
+ /* DIV => */ BehaviorDigest::empty()
+ .set_pl_any()
+ .set_flags_access(Access::Write)
+ .set_operand(0, Access::Read)
+ .set_nontrivial(true),
+ /* IDIV => */ BehaviorDigest::empty()
+ .set_pl_any()
+ .set_flags_access(Access::Write)
+ .set_operand(0, Access::Read)
+ .set_nontrivial(true),
+ /* MUL => */ BehaviorDigest::empty()
+ .set_pl_any()
+ .set_flags_access(Access::Write)
+ .set_operand(0, Access::Read)
+ .set_nontrivial(true),
+ /* SETO => */ SETCC,
+ /* SETNO => */ SETCC,
+ /* SETB => */ SETCC,
+ /* SETAE => */ SETCC,
+ /* SETZ => */ SETCC,
+ /* SETNZ => */ SETCC,
+ /* SETBE => */ SETCC,
+ /* SETA => */ SETCC,
+ /* SETS => */ SETCC,
+ /* SETNS => */ SETCC,
+ /* SETP => */ SETCC,
+ /* SETNP => */ SETCC,
+ /* SETL => */ SETCC,
+ /* SETGE => */ SETCC,
+ /* SETLE => */ SETCC,
+ /* SETG => */ SETCC,
+ /* CPUID => */ BehaviorDigest::empty()
+ .set_implicit_ops(CPUID_IDX)
+ .set_pl_any(),
+ /* UD0 => */ GENERAL,
+ /* UD1 => */ GENERAL
+ .set_operand(0, Access::None)
+ .set_operand(1, Access::None),
+ /* UD2 => */ GENERAL,
+ /* WBINVD => */ BehaviorDigest::empty()
+ .set_pl0(),
+ /* INVD => */ BehaviorDigest::empty()
+ .set_pl0(),
+ /* SYSRET => */ BehaviorDigest::empty()
+ .set_pl0(),
+ /* CLTS => */ BehaviorDigest::empty()
+ .set_implicit_ops(CLTS_IDX)
+ .set_pl0(),
+ /* SYSCALL => */ BehaviorDigest::empty()
+ .set_pl_any()
+ .set_complex(true),
+ /* LSL => */ BehaviorDigest::empty()
+ .set_pl_any()
+ .set_operand(0, Access::Write)
+ .set_operand(1, Access::Read)
+ .set_flags_access(Access::Write),
+ /* LAR => */ BehaviorDigest::empty()
+ .set_pl_any()
+ .set_operand(0, Access::Write)
+ .set_operand(1, Access::Read)
+ .set_flags_access(Access::Write),
+ /* SGDT => */ BehaviorDigest::empty()
+ .set_pl_special()
+ .set_operand(0, Access::Write),
+ /* SIDT => */ BehaviorDigest::empty()
+ .set_pl_special()
+ .set_operand(0, Access::Write),
+ /* LGDT => */ BehaviorDigest::empty()
+ .set_pl0()
+ .set_operand(0, Access::Read),
+ /* LIDT => */ BehaviorDigest::empty()
+ .set_pl0()
+ .set_operand(0, Access::Read),
+ /* SMSW => */ BehaviorDigest::empty()
+ .set_pl0()
+ .set_operand(0, Access::Write)
+ .set_implicit_ops(SMSW_IDX),
+ /* LMSW => */ BehaviorDigest::empty()
+ .set_pl0()
+ .set_operand(0, Access::Read)
+ .set_implicit_ops(LMSW_IDX),
+ /* SWAPGS => */ BehaviorDigest::empty()
+ .set_pl0(),
+ /* RDTSCP => */ BehaviorDigest::empty()
+ .set_pl_special()
+ .set_implicit_ops(RDTSCP_IDX)
+ .set_complex(true),
+ // TODO: invlpg does not generate a page fault, so it's "memory" only in generating an
+ // address.
+ /* INVLPG => */ BehaviorDigest::empty()
+ .set_pl0()
+ .set_operand(0, Access::Read),
+ // TODO: this is only complex because while the memory access is 512 bytes,
+ // `MemoryAccessSize::bytes_size()` does not report it as such.
+ /* FXSAVE => */ BehaviorDigest::empty()
+ .set_pl_any()
+ .set_operand(0, Access::Write)
+ .set_complex(true),
+ // TODO: this is only complex because while the memory access is 512 bytes,
+ // `MemoryAccessSize::bytes_size()` does not report it as such.
+ /* FXRSTOR => */ BehaviorDigest::empty()
+ .set_pl_any()
+ .set_operand(0, Access::Read)
+ .set_complex(true),
+ /* LDMXCSR => */ BehaviorDigest::empty()
+ .set_operand(0, Access::Read)
+ .set_complex(true)
+ .set_pl_any(),
+ /* STMXCSR => */ BehaviorDigest::empty()
+ .set_operand(0, Access::Write)
+ .set_complex(true)
+ .set_pl_any(),
+ /* XSAVE => */ BehaviorDigest::empty()
+ .set_operand(0, Access::Write)
+ .set_pl_any()
+ .set_complex(true),
+ /* XRSTOR => */ BehaviorDigest::empty()
+ .set_operand(0, Access::Read)
+ .set_pl_any()
+ .set_complex(true),
+ /* XSAVEOPT => */ BehaviorDigest::empty()
+ .set_operand(0, Access::Write)
+ .set_pl_any()
+ .set_complex(true),
+ /* LFENCE => */ GENERAL,
+ /* MFENCE => */ GENERAL,
+ /* SFENCE => */ GENERAL,
+ // in almost all cases `clflush` does not "write" anything, but it is more of a write than
+ // a read; from any other processor's perspective, the cache coherency protocol would
+ // ensure that other processors' caches "are" memory and this would be a no-op for
+ // architectural state. but for some kinds of memory (WC, for example), cache coherency is
+ // more lax and the executing processor's cache is in fact writing up to 64 bytes of novel
+ // data to main memory.
+ /* CLFLUSH => */ GENERAL_W
+ .set_complex(true),
+ // same argument as `clflush`.
+ /* CLFLUSHOPT => */ GENERAL_W
+ .set_complex(true),
+ // same argument as `clflush`.
+ /* CLWB => */ GENERAL_W
+ .set_complex(true),
+ /* WRMSR => */ BehaviorDigest::empty()
+ .set_pl0()
+ .set_complex(true),
+ /* RDTSC => */ BehaviorDigest::empty()
+ .set_implicit_ops(RDTSC_IDX)
+ .set_pl_special()
+ .set_complex(true),
+ /* RDMSR => */ BehaviorDigest::empty()
+ .set_pl0()
+ .set_complex(true),
+ /* RDPMC => */ BehaviorDigest::empty()
+ .set_implicit_ops(RDPMC_IDX)
+ .set_pl_special(),
+ /* SLDT => */ BehaviorDigest::empty()
+ .set_pl_special()
+ .set_operand(0, Access::Write)
+ .set_complex(true),
+ /* STR => */ BehaviorDigest::empty()
+ .set_pl0()
+ .set_operand(0, Access::Write)
+ .set_complex(true),
+ /* LLDT => */ BehaviorDigest::empty()
+ .set_pl0()
+ .set_operand(0, Access::Read)
+ .set_complex(true),
+ /* LTR => */ BehaviorDigest::empty()
+ .set_pl0()
+ .set_operand(0, Access::Read)
+ .set_complex(true),
+ /* VERR => */ GENERAL_R_FLAGWRITE,
+ /* VERW => */ GENERAL_R_FLAGWRITE,
+ /* CMC => */ GENERAL_FLAGRW,
+ /* CLC => */ GENERAL_FLAGRW,
+ /* STC => */ GENERAL_FLAGRW,
+ /* CLI => */ GENERAL_FLAGRW
+ .set_pl_special(),
+ /* STI => */ GENERAL_FLAGRW
+ .set_pl_special(),
+ /* CLD => */ GENERAL_FLAGRW,
+ /* STD => */ GENERAL_FLAGRW,
+ /* JMPE => */ BehaviorDigest::empty()
+ .set_pl_any() // TODO: don't have a processor with jmpe to validate
+ .set_operand(0, Access::Read)
+ .set_implicit_ops(JMP_OPS_IDX),
+ /* POPCNT => */ GENERAL_W_R_FLAGWRITE,
+ /* MOVDQU => */ GENERAL_W_R,
+ /* MOVDQA => */ GENERAL_W_R,
+ /* MOVQ => */ GENERAL_W_R,
+ /* CMPSS => */ GENERAL_RW_R
+ .set_operand(2, Access::Read),
+ /* CMPSD => */ GENERAL_RW_R
+ .set_operand(2, Access::Read),
+ /* UNPCKLPS => */ GENERAL_RW_R,
+ /* UNPCKLPD => */ GENERAL_RW_R,
+ /* UNPCKHPS => */ GENERAL_RW_R,
+ /* UNPCKHPD => */ GENERAL_RW_R,
+ /* PSHUFHW => */ GENERAL_W_R
+ .set_operand(2, Access::Read),
+ /* PSHUFLW => */ GENERAL_W_R
+ .set_operand(2, Access::Read),
+ /* MOVUPS => */ GENERAL_W_R,
+ /* MOVQ2DQ => */ GENERAL_W_R,
+ /* MOVDQ2Q => */ GENERAL_W_R,
+ /* RSQRTSS => */ GENERAL_RW_R,
+ /* RCPSS => */ GENERAL_RW_R,
+
+ /* ANDN => */ GENERAL_W_R_R
+ .set_flags_access(Access::Write),
+ /* BEXTR => */ GENERAL_W_R_R
+ .set_flags_access(Access::Write),
+ /* BLSI => */ GENERAL_W_R_FLAGWRITE,
+ /* BLSMSK => */ GENERAL_W_R_FLAGWRITE,
+ /* BLSR => */ GENERAL_W_R_FLAGWRITE,
+ /* VMCLEAR => */ BehaviorDigest::empty()
+ .set_operand(0, Access::Read)
+ .set_complex(true),
+ /* VMXON => */ BehaviorDigest::empty()
+ .set_pl0()
+ .set_complex(true),
+ /* VMCALL => */ BehaviorDigest::empty()
+ .set_complex(true),
+ /* VMLAUNCH => */ BehaviorDigest::empty()
+ .set_pl0()
+ .set_complex(true),
+ /* VMRESUME => */ BehaviorDigest::empty()
+ .set_pl0()
+ .set_complex(true),
+ /* VMXOFF => */ BehaviorDigest::empty()
+ .set_pl0()
+ .set_complex(true),
+ /* PCONFIG => */ BehaviorDigest::empty()
+ .set_complex(true),
+ /* MONITOR => */ BehaviorDigest::empty()
+ .set_pl_special()
+ .set_implicit_ops(MONITOR_IDX)
+ .set_complex(true),
+ /* MWAIT => */ BehaviorDigest::empty()
+ .set_pl_special()
+ .set_complex(true),
+ /* MONITORX => */ BehaviorDigest::empty()
+ .set_pl_any()
+ .set_implicit_ops(MONITOR_IDX)
+ .set_complex(true),
+ /* MWAITX => */ BehaviorDigest::empty()
+ .set_pl_any()
+ .set_complex(true),
+ /* CLAC => */ BehaviorDigest::empty()
+ .set_pl0()
+ .set_flags_access(Access::Write),
+ /* STAC => */ BehaviorDigest::empty()
+ .set_pl0()
+ .set_flags_access(Access::Write),
+ /* ENCLS => */ BehaviorDigest::empty()
+ .set_pl0()
+ .set_complex(true),
+ /* ENCLV => */ BehaviorDigest::empty()
+ .set_pl0()
+ .set_complex(true),
+ /* XGETBV => */ BehaviorDigest::empty()
+ .set_complex(true),
+ /* XSETBV => */ BehaviorDigest::empty()
+ .set_pl0()
+ .set_complex(true),
+ /* VMFUNC => */ BehaviorDigest::empty()
+ .set_pl_any()
+ .set_complex(true),
+ /* XABORT => */ BehaviorDigest::empty()
+ .set_pl_any()
+ .set_complex(true),
+ /* XBEGIN => */ BehaviorDigest::empty()
+ .set_pl_any()
+ .set_complex(true),
+ /* XEND => */ BehaviorDigest::empty()
+ .set_pl_any()
+ .set_complex(true),
+ /* XTEST => */ BehaviorDigest::empty()
+ .set_pl_any()
+ .set_complex(true),
+ /* ENCLU => */ BehaviorDigest::empty()
+ .set_pl_special()
+ .set_complex(true),
+ /* RDPKRU => */ BehaviorDigest::empty()
+ .set_pl_any()
+ .set_complex(true),
+ /* WRPKRU => */ BehaviorDigest::empty()
+ .set_pl_any()
+ .set_complex(true),
+
+ /* RDPRU => */ BehaviorDigest::empty()
+ .set_pl_any()
+ .set_complex(true),
+ /* CLZERO => */ BehaviorDigest::empty()
+ .set_pl_any()
+ .set_complex(true),
+
+ /* RDSEED => */ BehaviorDigest::empty()
+ .set_operand(0, Access::Write)
+ .set_flags_access(Access::Write)
+ .set_pl_any(),
+ /* RDRAND => */ BehaviorDigest::empty()
+ .set_operand(0, Access::Write)
+ .set_flags_access(Access::Write)
+ .set_pl_any(),
+
+ /* ADDPS => */ GENERAL_RW_R,
+ /* ADDPD => */ GENERAL_RW_R,
+ /* ANDNPS => */ GENERAL_RW_R,
+ /* ANDNPD => */ GENERAL_RW_R,
+ /* ANDPS => */ GENERAL_RW_R,
+ /* ANDPD => */ GENERAL_RW_R,
+ /* BSWAP => */ GENERAL_RW,
+ /* CMPPD => */ GENERAL_RW_R
+ .set_operand(2, Access::Read),
+ /* CMPPS => */ GENERAL_RW_R
+ .set_operand(2, Access::Read),
+ /* COMISD => */ GENERAL_R_R_FLAGWRITE,
+ /* COMISS => */ GENERAL_R_R_FLAGWRITE,
+ /* CVTDQ2PS => */ GENERAL_W_R,
+ /* CVTPI2PS => */ GENERAL_RW_R,
+ // TODO: are these cvtp*2p* instructions targeting mmx actually read-write on the
+ // destination? what happens to the top 16 bits of the destination?
+ /* CVTPI2PD => */ GENERAL_W_R,
+ /* CVTPS2PD => */ GENERAL_W_R,
+ /* CVTPS2PI => */ GENERAL_W_R,
+ /* CVTPD2PI => */ GENERAL_W_R,
+ /* CVTTPS2PI => */ GENERAL_W_R,
+ /* CVTTPD2PI => */ GENERAL_W_R,
+ // exciting: zeroes the upper half of the xmm register, but leaves ymm/zmm unmodified
+ /* CVTTPD2DQ => */ GENERAL_W_R,
+ /* DIVPS => */ GENERAL_RW_R,
+ /* DIVPD => */ GENERAL_RW_R,
+ /* EMMS => */ GENERAL,
+ // TODO: untested, don't have relevant hardware..
+ /* GETSEC => */ BehaviorDigest::empty()
+ .set_pl_any(),
+ /* LFS => */ GENERAL_W_R
+ .set_implicit_ops(LFS_IDX),
+ /* LGS => */ GENERAL_W_R
+ .set_implicit_ops(LGS_IDX),
+ /* LSS => */ GENERAL_W_R
+ .set_implicit_ops(LSS_IDX),
+ /* MASKMOVQ => */ GENERAL_R_R
+ .set_implicit_ops(MASKMOVQ_IDX),
+ /* MASKMOVDQU => */ GENERAL_R_R
+ .set_implicit_ops(MASKMOVQ_IDX),
+ /* MAXPS => */ GENERAL_RW_R,
+ /* MAXPD => */ GENERAL_RW_R,
+ /* MINPS => */ GENERAL_RW_R,
+ /* MINPD => */ GENERAL_RW_R,
+ /* MOVAPS => */ GENERAL_W_R,
+ /* MOVAPD => */ GENERAL_W_R,
+ /* MOVD => */ GENERAL_W_R,
+ /* MOVLPS => */ GENERAL_RW_R,
+ /* MOVLPD => */ GENERAL_RW_R,
+ /* MOVHPS => */ GENERAL_RW_R,
+ /* MOVHPD => */ GENERAL_RW_R,
+ /* MOVLHPS => */ GENERAL_RW_R,
+ /* MOVHLPS => */ GENERAL_RW_R,
+ /* MOVUPD => */ GENERAL_W_R,
+ /* MOVMSKPS => */ GENERAL_RW_R,
+ /* MOVMSKPD => */ GENERAL_RW_R,
+ /* MOVNTI => */ GENERAL_W_R,
+ /* MOVNTPS => */ GENERAL_W_R,
+ /* MOVNTPD => */ GENERAL_W_R,
+ /* EXTRQ => */ GENERAL_RW_R
+ .set_nontrivial(true),
+ /* INSERTQ => */ GENERAL_RW_R
+ .set_nontrivial(true),
+ /* MOVNTSS => */ GENERAL_W_R,
+ /* MOVNTSD => */ GENERAL_W_R,
+ /* MOVNTQ => */ GENERAL_W_R,
+ /* MOVNTDQ => */ GENERAL_W_R,
+ /* MULPS => */ GENERAL_RW_R,
+ /* MULPD => */ GENERAL_RW_R,
+ /* ORPS => */ GENERAL_RW_R,
+ /* ORPD => */ GENERAL_RW_R,
+ /* PACKSSDW => */ GENERAL_RW_R,
+ /* PACKSSWB => */ GENERAL_RW_R,
+ /* PACKUSWB => */ GENERAL_RW_R,
+ /* PADDB => */ GENERAL_RW_R,
+ /* PADDD => */ GENERAL_RW_R,
+ /* PADDQ => */ GENERAL_RW_R,
+ /* PADDSB => */ GENERAL_RW_R,
+ /* PADDSW => */ GENERAL_RW_R,
+ /* PADDUSB => */ GENERAL_RW_R,
+ /* PADDUSW => */ GENERAL_RW_R,
+ /* PADDW => */ GENERAL_RW_R,
+ /* PAND => */ GENERAL_RW_R,
+ /* PANDN => */ GENERAL_RW_R,
+ /* PAVGB => */ GENERAL_RW_R,
+ /* PAVGW => */ GENERAL_RW_R,
+ /* PCMPEQB => */ GENERAL_RW_R,
+ /* PCMPEQD => */ GENERAL_RW_R,
+ /* PCMPEQW => */ GENERAL_RW_R,
+ /* PCMPGTB => */ GENERAL_RW_R,
+ /* PCMPGTD => */ GENERAL_RW_R,
+ /* PCMPGTW => */ GENERAL_RW_R,
+ /* PINSRW => */ GENERAL_RW_R
+ .set_operand(2, Access::Read),
+ /* PMADDWD => */ GENERAL_RW_R,
+ /* PMAXSW => */ GENERAL_RW_R,
+ /* PMAXUB => */ GENERAL_RW_R,
+ /* PMINSW => */ GENERAL_RW_R,
+ /* PMINUB => */ GENERAL_RW_R,
+ /* PMOVMSKB => */ GENERAL_RW_R,
+ /* PMULHUW => */ GENERAL_RW_R,
+ /* PMULHW => */ GENERAL_RW_R,
+ /* PMULLW => */ GENERAL_RW_R,
+ /* PMULUDQ => */ GENERAL_RW_R,
+ /* POR => */ GENERAL_RW_R,
+ /* PSADBW => */ GENERAL_RW_R,
+ /* PSHUFW => */ GENERAL_RW_R
+ .set_operand(2, Access::Read),
+ /* PSHUFD => */ GENERAL_RW_R
+ .set_operand(2, Access::Read),
+ /* PSLLD => */ GENERAL_RW_R,
+ /* PSLLDQ => */ GENERAL_RW_R,
+ /* PSLLQ => */ GENERAL_RW_R,
+ /* PSLLW => */ GENERAL_RW_R,
+ /* PSRAD => */ GENERAL_RW_R,
+ /* PSRAW => */ GENERAL_RW_R,
+ /* PSRLD => */ GENERAL_RW_R,
+ /* PSRLDQ => */ GENERAL_RW_R,
+ /* PSRLQ => */ GENERAL_RW_R,
+ /* PSRLW => */ GENERAL_RW_R,
+ /* PSUBB => */ GENERAL_RW_R,
+ /* PSUBD => */ GENERAL_RW_R,
+ /* PSUBQ => */ GENERAL_RW_R,
+ /* PSUBSB => */ GENERAL_RW_R,
+ /* PSUBSW => */ GENERAL_RW_R,
+ /* PSUBUSB => */ GENERAL_RW_R,
+ /* PSUBUSW => */ GENERAL_RW_R,
+ /* PSUBW => */ GENERAL_RW_R,
+ /* PUNPCKHBW => */ GENERAL_RW_R,
+ /* PUNPCKHDQ => */ GENERAL_RW_R,
+ /* PUNPCKHWD => */ GENERAL_RW_R,
+ /* PUNPCKLBW => */ GENERAL_RW_R,
+ /* PUNPCKLDQ => */ GENERAL_RW_R,
+ /* PUNPCKLWD => */ GENERAL_RW_R,
+ /* PUNPCKLQDQ => */ GENERAL_RW_R,
+ /* PUNPCKHQDQ => */ GENERAL_RW_R,
+ /* PXOR => */ GENERAL_RW_R,
+ /* RCPPS => */ GENERAL_W_R,
+ /* RSM => */ BehaviorDigest::empty()
+ .set_pl_special()
+ .set_complex(true),
+ /* RSQRTPS => */ GENERAL_W_R,
+ /* SHLD => */ GENERAL_RW_R_R
+ .set_flags_access(Access::Write),
+ /* SHUFPD => */ GENERAL_RW_R_R,
+ /* SHUFPS => */ GENERAL_RW_R_R,
+ // TODO: slhd is not real, typo of shld
+ /* SLHD => */ BehaviorDigest::empty(),
+ /* SQRTPS => */ GENERAL_W_R,
+ /* SQRTPD => */ GENERAL_W_R,
+ /* SUBPS => */ GENERAL_RW_R,
+ /* SUBPD => */ GENERAL_RW_R,
+ /* SYSENTER => */ BehaviorDigest::empty()
+ .set_pl_any()
+ .set_complex(true),
+ /* SYSEXIT => */ BehaviorDigest::empty()
+ .set_pl_any()
+ .set_complex(true),
+ /* UCOMISD => */ GENERAL_R_R_FLAGWRITE,
+ /* UCOMISS => */ GENERAL_R_R_FLAGWRITE,
+ /* VMREAD => */ BehaviorDigest::empty()
+ .set_operand(0, Access::Write)
+ .set_operand(1, Access::Read)
+ .set_pl0()
+ .set_complex(true),
+ /* VMWRITE => */ BehaviorDigest::empty()
+ .set_operand(0, Access::Read)
+ .set_operand(1, Access::Read)
+ .set_pl0()
+ .set_complex(true),
+ /* XORPS => */ GENERAL_RW_R,
+ /* XORPD => */ GENERAL_RW_R,
+
+ /* VMOVDDUP => */ GENERAL_W_R,
+ /* VPSHUFLW => */ GENERAL_W_R
+ .set_operand(2, Access::Read),
+ /* VPSHUFHW => */ GENERAL_W_R
+ .set_operand(2, Access::Read),
+ /* VHADDPS => */ GENERAL_W_R_R,
+ /* VHSUBPS => */ GENERAL_W_R_R,
+ /* VADDSUBPS => */ GENERAL_W_R_R,
+ /* VCVTPD2DQ => */ GENERAL_W_R,
+ /* VLDDQU => */ GENERAL_W_R,
+
+ /* VCOMISD => */ GENERAL_R_R_FLAGWRITE,
+ /* VCOMISS => */ GENERAL_R_R_FLAGWRITE,
+ /* VUCOMISD => */ GENERAL_R_R_FLAGWRITE,
+ /* VUCOMISS => */ GENERAL_R_R_FLAGWRITE,
+ /* VADDPD => */ GENERAL_W_R_R,
+ /* VADDPS => */ GENERAL_W_R_R,
+ /* VADDSD => */ GENERAL_W_R_R,
+ /* VADDSS => */ GENERAL_W_R_R,
+ /* VADDSUBPD => */ GENERAL_W_R_R,
+ /* VAESDEC => */ GENERAL_W_R_R,
+ /* VAESDECLAST => */ GENERAL_W_R_R,
+ /* VAESENC => */ GENERAL_W_R_R,
+ /* VAESENCLAST => */ GENERAL_W_R_R,
+ /* VAESIMC => */ GENERAL_W_R,
+ /* VAESKEYGENASSIST => */ GENERAL_W_R_R,
+ /* VBLENDPD => */ GENERAL_W_R_R_IMM8,
+ /* VBLENDPS => */ GENERAL_W_R_R_IMM8,
+ /* VBLENDVPD => */ GENERAL_W_R_R
+ .set_operand(3, Access::Read),
+ /* VBLENDVPS => */ GENERAL_W_R_R
+ .set_operand(3, Access::Read),
+ /* VBROADCASTF128 => */ GENERAL_W_R,
+ /* VBROADCASTI128 => */ GENERAL_W_R,
+ /* VBROADCASTSD => */ GENERAL_W_R,
+ /* VBROADCASTSS => */ GENERAL_W_R,
+ /* VCMPSD => */ GENERAL_W_R_R_IMM8,
+ /* VCMPSS => */ GENERAL_W_R_R_IMM8,
+ /* VCMPPD => */ GENERAL_W_R_R
+ .set_operand(3, Access::Read),
+ /* VCMPPS => */ GENERAL_W_R_R
+ .set_operand(3, Access::Read),
+ // TODO: SUPER suspicious about the RW_R/W_R confusion here.
+ // vcvtss2si says that dest[0:63] are written but omits bits 64 and up.
+ // vcvtsd2si says taht dest[0:63] are written and upper bits are taken from src1.
+ // is src1 used generally?
+ /* VCVTDQ2PD => */ GENERAL_W_R,
+ /* VCVTDQ2PS => */ GENERAL_W_R,
+ /* VCVTPD2PS => */ GENERAL_W_R,
+ /* VCVTPH2PS => */ GENERAL_W_R,
+ /* VCVTPS2DQ => */ GENERAL_W_R,
+ /* VCVTPS2PD => */ GENERAL_W_R,
+ /* VCVTSS2SD => */ GENERAL_W_R_R,
+ /* VCVTSI2SS => */ GENERAL_W_R_R,
+ /* VCVTSI2SD => */ GENERAL_W_R_R,
+ /* VCVTSD2SI => */ GENERAL_RW_R,
+ /* VCVTSD2SS => */ GENERAL_W_R_R,
+ /* VCVTPS2PH => */ GENERAL_W_R_R,
+ /* VCVTSS2SI => */ GENERAL_RW_R,
+ /* VCVTTPD2DQ => */ GENERAL_W_R,
+ /* VCVTTPS2DQ => */ GENERAL_W_R,
+ /* VCVTTSS2SI => */ GENERAL_RW_R,
+ /* VCVTTSD2SI => */ GENERAL_RW_R,
+ /* VDIVPD => */ GENERAL_W_R_R,
+ /* VDIVPS => */ GENERAL_W_R_R,
+ /* VDIVSD => */ GENERAL_W_R_R,
+ /* VDIVSS => */ GENERAL_W_R_R,
+ /* VDPPD => */ GENERAL_W_R_R
+ .set_operand(3, Access::Read),
+ /* VDPPS => */ GENERAL_W_R_R
+ .set_operand(3, Access::Read),
+ /* VEXTRACTF128 => */ GENERAL_W_R
+ .set_operand(2, Access::Read),
+ /* VEXTRACTI128 => */ GENERAL_W_R
+ .set_operand(2, Access::Read),
+ /* VEXTRACTPS => */ GENERAL_W_R
+ .set_operand(2, Access::Read),
+ /* VFMADD132PD => */ GENERAL_RW_R_R,
+ /* VFMADD132PS => */ GENERAL_RW_R_R,
+ /* VFMADD132SD => */ GENERAL_RW_R_R,
+ /* VFMADD132SS => */ GENERAL_RW_R_R,
+ /* VFMADD213PD => */ GENERAL_RW_R_R,
+ /* VFMADD213PS => */ GENERAL_RW_R_R,
+ /* VFMADD213SD => */ GENERAL_RW_R_R,
+ /* VFMADD213SS => */ GENERAL_RW_R_R,
+ /* VFMADD231PD => */ GENERAL_RW_R_R,
+ /* VFMADD231PS => */ GENERAL_RW_R_R,
+ /* VFMADD231SD => */ GENERAL_RW_R_R,
+ /* VFMADD231SS => */ GENERAL_RW_R_R,
+ /* VFMADDSUB132PD => */ GENERAL_RW_R_R,
+ /* VFMADDSUB132PS => */ GENERAL_RW_R_R,
+ /* VFMADDSUB213PD => */ GENERAL_RW_R_R,
+ /* VFMADDSUB213PS => */ GENERAL_RW_R_R,
+ /* VFMADDSUB231PD => */ GENERAL_RW_R_R,
+ /* VFMADDSUB231PS => */ GENERAL_RW_R_R,
+ /* VFMSUB132PD => */ GENERAL_RW_R_R,
+ /* VFMSUB132PS => */ GENERAL_RW_R_R,
+ /* VFMSUB132SD => */ GENERAL_RW_R_R,
+ /* VFMSUB132SS => */ GENERAL_RW_R_R,
+ /* VFMSUB213PD => */ GENERAL_RW_R_R,
+ /* VFMSUB213PS => */ GENERAL_RW_R_R,
+ /* VFMSUB213SD => */ GENERAL_RW_R_R,
+ /* VFMSUB213SS => */ GENERAL_RW_R_R,
+ /* VFMSUB231PD => */ GENERAL_RW_R_R,
+ /* VFMSUB231PS => */ GENERAL_RW_R_R,
+ /* VFMSUB231SD => */ GENERAL_RW_R_R,
+ /* VFMSUB231SS => */ GENERAL_RW_R_R,
+ /* VFMSUBADD132PD => */ GENERAL_RW_R_R,
+ /* VFMSUBADD132PS => */ GENERAL_RW_R_R,
+ /* VFMSUBADD213PD => */ GENERAL_RW_R_R,
+ /* VFMSUBADD213PS => */ GENERAL_RW_R_R,
+ /* VFMSUBADD231PD => */ GENERAL_RW_R_R,
+ /* VFMSUBADD231PS => */ GENERAL_RW_R_R,
+ /* VFNMADD132PD => */ GENERAL_RW_R_R,
+ /* VFNMADD132PS => */ GENERAL_RW_R_R,
+ /* VFNMADD132SD => */ GENERAL_RW_R_R,
+ /* VFNMADD132SS => */ GENERAL_RW_R_R,
+ /* VFNMADD213PD => */ GENERAL_RW_R_R,
+ /* VFNMADD213PS => */ GENERAL_RW_R_R,
+ /* VFNMADD213SD => */ GENERAL_RW_R_R,
+ /* VFNMADD213SS => */ GENERAL_RW_R_R,
+ /* VFNMADD231PD => */ GENERAL_RW_R_R,
+ /* VFNMADD231PS => */ GENERAL_RW_R_R,
+ /* VFNMADD231SD => */ GENERAL_RW_R_R,
+ /* VFNMADD231SS => */ GENERAL_RW_R_R,
+ /* VFNMSUB132PD => */ GENERAL_RW_R_R,
+ /* VFNMSUB132PS => */ GENERAL_RW_R_R,
+ /* VFNMSUB132SD => */ GENERAL_RW_R_R,
+ /* VFNMSUB132SS => */ GENERAL_RW_R_R,
+ /* VFNMSUB213PD => */ GENERAL_RW_R_R,
+ /* VFNMSUB213PS => */ GENERAL_RW_R_R,
+ /* VFNMSUB213SD => */ GENERAL_RW_R_R,
+ /* VFNMSUB213SS => */ GENERAL_RW_R_R,
+ /* VFNMSUB231PD => */ GENERAL_RW_R_R,
+ /* VFNMSUB231PS => */ GENERAL_RW_R_R,
+ /* VFNMSUB231SD => */ GENERAL_RW_R_R,
+ /* VFNMSUB231SS => */ GENERAL_RW_R_R,
+ /* VGATHERDPD => */ BehaviorDigest::empty()
+ .set_complex(true),
+ /* VGATHERDPS => */ BehaviorDigest::empty()
+ .set_complex(true),
+ /* VGATHERQPD => */ BehaviorDigest::empty()
+ .set_complex(true),
+ /* VGATHERQPS => */ BehaviorDigest::empty()
+ .set_complex(true),
+ /* VHADDPD => */ GENERAL_W_R_R,
+ /* VHSUBPD => */ GENERAL_W_R_R,
+ /* VINSERTF128 => */ GENERAL_W_R_R_IMM8,
+ /* VINSERTI128 => */ GENERAL_W_R_R_IMM8,
+ /* VINSERTPS => */ GENERAL_W_R_R_IMM8,
+ /* VMASKMOVDQU => */ GENERAL_R_R
+ .set_nontrivial(true),
+ /* VMASKMOVPD => */ GENERAL_W_R_R,
+ /* VMASKMOVPS => */ GENERAL_W_R_R,
+ /* VMAXPD => */ GENERAL_W_R_R,
+ /* VMAXPS => */ GENERAL_W_R_R,
+ /* VMAXSD => */ GENERAL_W_R_R,
+ /* VMAXSS => */ GENERAL_W_R_R,
+ /* VMINPD => */ GENERAL_W_R_R,
+ /* VMINPS => */ GENERAL_W_R_R,
+ /* VMINSD => */ GENERAL_W_R_R,
+ /* VMINSS => */ GENERAL_W_R_R,
+ /* VMOVAPD => */ GENERAL_W_R,
+ /* VMOVAPS => */ GENERAL_W_R,
+ /* VMOVD => */ GENERAL_W_R,
+ /* VMOVDQA => */ GENERAL_W_R,
+ /* VMOVDQU => */ GENERAL_W_R,
+ /* VMOVHLPS => */ GENERAL_W_R_R,
+ // these four are not actually reached due to check above
+ /* VMOVHPD => */ BehaviorDigest::empty()
+ .set_pl_any()
+ .set_operand(1, Access::Read)
+ .set_nontrivial(true),
+ /* VMOVHPS => */ BehaviorDigest::empty()
+ .set_pl_any()
+ .set_operand(1, Access::Read)
+ .set_nontrivial(true),
+ /* VMOVLHPS => */ GENERAL_W_R_R,
+ /* VMOVLPD => */ BehaviorDigest::empty()
+ .set_pl_any()
+ .set_operand(1, Access::Read)
+ .set_nontrivial(true),
+ /* VMOVLPS => */ BehaviorDigest::empty()
+ .set_pl_any()
+ .set_operand(1, Access::Read)
+ .set_nontrivial(true),
+ /* VMOVMSKPD => */ GENERAL_W_R,
+ /* VMOVMSKPS => */ GENERAL_W_R,
+ /* VMOVNTDQ => */ GENERAL_W_R,
+ /* VMOVNTDQA => */ GENERAL_W_R,
+ /* VMOVNTPD => */ GENERAL_W_R,
+ /* VMOVNTPS => */ GENERAL_W_R,
+ /* VMOVQ => */ GENERAL_W_R,
+ /* VMOVSS => */ GENERAL_W_R_R,
+ /* VMOVSD => */ GENERAL_W_R_R,
+ /* VMOVSHDUP => */ GENERAL_W_R,
+ /* VMOVSLDUP => */ GENERAL_W_R,
+ /* VMOVUPD => */ GENERAL_W_R,
+ /* VMOVUPS => */ GENERAL_W_R,
+ /* VMPSADBW => */ GENERAL_W_R_R
+ .set_operand(3, Access::Read),
+ /* VMULPD => */ GENERAL_W_R_R,
+ /* VMULPS => */ GENERAL_W_R_R,
+ /* VMULSD => */ GENERAL_W_R_R,
+ /* VMULSS => */ GENERAL_W_R_R,
+ /* VPABSB => */ GENERAL_W_R,
+ /* VPABSD => */ GENERAL_W_R,
+ /* VPABSW => */ GENERAL_W_R,
+ /* VPACKSSDW => */ GENERAL_W_R_R,
+ /* VPACKUSDW => */ GENERAL_W_R_R,
+ /* VPACKSSWB => */ GENERAL_W_R_R,
+ /* VPACKUSWB => */ GENERAL_W_R_R,
+ /* VPADDB => */ GENERAL_W_R_R,
+ /* VPADDD => */ GENERAL_W_R_R,
+ /* VPADDQ => */ GENERAL_W_R_R,
+ /* VPADDSB => */ GENERAL_W_R_R,
+ /* VPADDSW => */ GENERAL_W_R_R,
+ /* VPADDUSB => */ GENERAL_W_R_R,
+ /* VPADDUSW => */ GENERAL_W_R_R,
+ /* VPADDW => */ GENERAL_W_R_R,
+ /* VPALIGNR => */ GENERAL_W_R_R_IMM8,
+ /* VANDPD => */ GENERAL_W_R_R,
+ /* VANDPS => */ GENERAL_W_R_R,
+ /* VORPD => */ GENERAL_W_R_R,
+ /* VORPS => */ GENERAL_W_R_R,
+ /* VANDNPD => */ GENERAL_W_R_R,
+ /* VANDNPS => */ GENERAL_W_R_R,
+ /* VPAND => */ GENERAL_W_R_R,
+ /* VPANDN => */ GENERAL_W_R_R,
+ /* VPAVGB => */ GENERAL_W_R_R,
+ /* VPAVGW => */ GENERAL_W_R_R,
+ /* VPBLENDD => */ GENERAL_W_R_R
+ .set_operand(3, Access::Read),
+ /* VPBLENDVB => */ GENERAL_W_R_R
+ .set_operand(3, Access::Read),
+ /* VPBLENDW => */ GENERAL_W_R_R
+ .set_operand(3, Access::Read),
+ /* VPBROADCASTB => */ GENERAL_W_R,
+ /* VPBROADCASTD => */ GENERAL_W_R,
+ /* VPBROADCASTQ => */ GENERAL_W_R,
+ /* VPBROADCASTW => */ GENERAL_W_R,
+ /* VPCLMULQDQ => */ GENERAL_W_R_R
+ .set_operand(3, Access::Read),
+ /* VPCMPEQB => */ GENERAL_W_R_R,
+ /* VPCMPEQD => */ GENERAL_W_R_R,
+ /* VPCMPEQQ => */ GENERAL_W_R_R,
+ /* VPCMPEQW => */ GENERAL_W_R_R,
+ /* VPCMPGTB => */ GENERAL_W_R_R,
+ /* VPCMPGTD => */ GENERAL_W_R_R,
+ /* VPCMPGTQ => */ GENERAL_W_R_R,
+ /* VPCMPGTW => */ GENERAL_W_R_R,
+ /* VPCMPESTRI => */ GENERAL_R_R
+ .set_operand(2, Access::Read)
+ .set_flags_access(Access::Write)
+ .set_nontrivial(true),
+ /* VPCMPESTRM => */ GENERAL_R_R
+ .set_operand(2, Access::Read)
+ .set_flags_access(Access::Write)
+ .set_nontrivial(true),
+ /* VPCMPISTRI => */ GENERAL_R_R
+ .set_operand(2, Access::Read)
+ .set_flags_access(Access::Write)
+ .set_implicit_ops(PCMPISTRI_IDX),
+ /* VPCMPISTRM => */ GENERAL_R_R
+ .set_operand(2, Access::Read)
+ .set_flags_access(Access::Write)
+ .set_implicit_ops(PCMPISTRM_IDX),
+ /* VPERM2F128 => */ GENERAL_W_R_R
+ .set_operand(3, Access::Read),
+ /* VPERM2I128 => */ GENERAL_W_R_R
+ .set_operand(3, Access::Read),
+ /* VPERMD => */ GENERAL_W_R_R,
+ /* VPERMILPD => */ GENERAL_W_R_R,
+ /* VPERMILPS => */ GENERAL_W_R_R,
+ /* VPERMPD => */ GENERAL_W_R_R,
+ /* VPERMPS => */ GENERAL_W_R_R,
+ /* VPERMQ => */ GENERAL_W_R_R,
+ /* VPEXTRB => */ GENERAL_W_R
+ .set_operand(2, Access::Read),
+ /* VPEXTRD => */ GENERAL_W_R
+ .set_operand(2, Access::Read),
+ /* VPEXTRQ => */ GENERAL_W_R
+ .set_operand(2, Access::Read),
+ /* VPEXTRW => */ GENERAL_W_R
+ .set_operand(2, Access::Read),
+ // TODO: complex
+ /* VPGATHERDD => */ BehaviorDigest::empty()
+ .set_complex(true),
+ /* VPGATHERDQ => */ BehaviorDigest::empty()
+ .set_complex(true),
+ /* VPGATHERQD => */ BehaviorDigest::empty()
+ .set_complex(true),
+ /* VPGATHERQQ => */ BehaviorDigest::empty()
+ .set_complex(true),
+ /* VPHADDD => */ GENERAL_W_R_R,
+ /* VPHADDSW => */ GENERAL_W_R_R,
+ /* VPHADDW => */ GENERAL_W_R_R,
+ /* VPMADDUBSW => */ GENERAL_W_R_R,
+ /* VPHMINPOSUW => */ GENERAL_W_R,
+ /* VPHSUBD => */ GENERAL_W_R_R,
+ /* VPHSUBSW => */ GENERAL_W_R_R,
+ /* VPHSUBW => */ GENERAL_W_R_R,
+ /* VPINSRB => */ GENERAL_W_R_R
+ .set_operand(3, Access::Read),
+ /* VPINSRD => */ GENERAL_W_R_R
+ .set_operand(3, Access::Read),
+ /* VPINSRQ => */ GENERAL_W_R_R
+ .set_operand(3, Access::Read),
+ /* VPINSRW => */ GENERAL_W_R_R
+ .set_operand(3, Access::Read),
+ /* VPMADDWD => */ GENERAL_W_R_R,
+ /* VPMASKMOVD => */ GENERAL_W_R_R,
+ /* VPMASKMOVQ => */ GENERAL_W_R_R,
+ /* VPMAXSB => */ GENERAL_W_R_R,
+ /* VPMAXSD => */ GENERAL_W_R_R,
+ /* VPMAXSW => */ GENERAL_W_R_R,
+ /* VPMAXUB => */ GENERAL_W_R_R,
+ /* VPMAXUW => */ GENERAL_W_R_R,
+ /* VPMAXUD => */ GENERAL_W_R_R,
+ /* VPMINSB => */ GENERAL_W_R_R,
+ /* VPMINSW => */ GENERAL_W_R_R,
+ /* VPMINSD => */ GENERAL_W_R_R,
+ /* VPMINUB => */ GENERAL_W_R_R,
+ /* VPMINUW => */ GENERAL_W_R_R,
+ /* VPMINUD => */ GENERAL_W_R_R,
+ /* VPMOVMSKB => */ GENERAL_W_R,
+ /* VPMOVSXBD => */ GENERAL_W_R,
+ /* VPMOVSXBQ => */ GENERAL_W_R,
+ /* VPMOVSXBW => */ GENERAL_W_R,
+ /* VPMOVSXDQ => */ GENERAL_W_R,
+ /* VPMOVSXWD => */ GENERAL_W_R,
+ /* VPMOVSXWQ => */ GENERAL_W_R,
+ /* VPMOVZXBD => */ GENERAL_W_R,
+ /* VPMOVZXBQ => */ GENERAL_W_R,
+ /* VPMOVZXBW => */ GENERAL_W_R,
+ /* VPMOVZXDQ => */ GENERAL_W_R,
+ /* VPMOVZXWD => */ GENERAL_W_R,
+ /* VPMOVZXWQ => */ GENERAL_W_R,
+ /* VPMULDQ => */ GENERAL_W_R_R,
+ /* VPMULHRSW => */ GENERAL_W_R_R,
+ /* VPMULHUW => */ GENERAL_W_R_R,
+ /* VPMULHW => */ GENERAL_W_R_R,
+ /* VPMULLQ => */ GENERAL_W_R_R,
+ /* VPMULLD => */ GENERAL_W_R_R,
+ /* VPMULLW => */ GENERAL_W_R_R,
+ /* VPMULUDQ => */ GENERAL_W_R_R,
+ /* VPOR => */ GENERAL_W_R_R,
+ /* VPSADBW => */ GENERAL_W_R_R,
+ /* VPSHUFB => */ GENERAL_W_R_R,
+ /* VPSHUFD => */ GENERAL_W_R_R,
+ /* VPSIGNB => */ GENERAL_W_R_R,
+ /* VPSIGND => */ GENERAL_W_R_R,
+ /* VPSIGNW => */ GENERAL_W_R_R,
+ /* VPSLLD => */ GENERAL_W_R_R,
+ /* VPSLLDQ => */ GENERAL_W_R_R,
+ /* VPSLLQ => */ GENERAL_W_R_R,
+ /* VPSLLVD => */ GENERAL_W_R_R,
+ /* VPSLLVQ => */ GENERAL_W_R_R,
+ /* VPSLLW => */ GENERAL_W_R_R,
+ /* VPSRAD => */ GENERAL_W_R_R,
+ /* VPSRAVD => */ GENERAL_W_R_R,
+ /* VPSRAW => */ GENERAL_W_R_R,
+ /* VPSRLD => */ GENERAL_W_R_R,
+ /* VPSRLDQ => */ GENERAL_W_R_R,
+ /* VPSRLQ => */ GENERAL_W_R_R,
+ /* VPSRLVD => */ GENERAL_W_R_R,
+ /* VPSRLVQ => */ GENERAL_W_R_R,
+ /* VPSRLW => */ GENERAL_W_R_R,
+ /* VPSUBB => */ GENERAL_W_R_R,
+ /* VPSUBD => */ GENERAL_W_R_R,
+ /* VPSUBQ => */ GENERAL_W_R_R,
+ /* VPSUBSB => */ GENERAL_W_R_R,
+ /* VPSUBSW => */ GENERAL_W_R_R,
+ /* VPSUBUSB => */ GENERAL_W_R_R,
+ /* VPSUBUSW => */ GENERAL_W_R_R,
+ /* VPSUBW => */ GENERAL_W_R_R,
+ /* VPTEST => */ GENERAL_R_R_FLAGWRITE,
+ /* VPUNPCKHBW => */ GENERAL_W_R_R,
+ /* VPUNPCKHDQ => */ GENERAL_W_R_R,
+ /* VPUNPCKHQDQ => */ GENERAL_W_R_R,
+ /* VPUNPCKHWD => */ GENERAL_W_R_R,
+ /* VPUNPCKLBW => */ GENERAL_W_R_R,
+ /* VPUNPCKLDQ => */ GENERAL_W_R_R,
+ /* VPUNPCKLQDQ => */ GENERAL_W_R_R,
+ /* VPUNPCKLWD => */ GENERAL_W_R_R,
+ /* VPXOR => */ GENERAL_W_R_R,
+ /* VRCPPS => */ GENERAL_W_R,
+ /* VROUNDPD => */ GENERAL_W_R_R,
+ /* VROUNDPS => */ GENERAL_W_R_R,
+ /* VROUNDSD => */ GENERAL_W_R_R
+ .set_operand(3, Access::Read),
+ /* VROUNDSS => */ GENERAL_W_R_R
+ .set_operand(3, Access::Read),
+ /* VRSQRTPS => */ GENERAL_W_R,
+ /* VRSQRTSS => */ GENERAL_W_R_R,
+ /* VRCPSS => */ GENERAL_W_R_R,
+ /* VSHUFPD => */ GENERAL_W_R_R
+ .set_operand(3, Access::Read),
+ /* VSHUFPS => */ GENERAL_W_R_R
+ .set_operand(3, Access::Read),
+ /* VSQRTPD => */ GENERAL_W_R,
+ /* VSQRTPS => */ GENERAL_W_R,
+ /* VSQRTSS => */ GENERAL_W_R_R,
+ /* VSQRTSD => */ GENERAL_W_R_R,
+ /* VSUBPD => */ GENERAL_W_R_R,
+ /* VSUBPS => */ GENERAL_W_R_R,
+ /* VSUBSD => */ GENERAL_W_R_R,
+ /* VSUBSS => */ GENERAL_W_R_R,
+ /* VTESTPD => */ GENERAL_R_R
+ .set_flags_access(Access::Write),
+ /* VTESTPS => */ GENERAL_R_R
+ .set_flags_access(Access::Write),
+ /* VUNPCKHPD => */ GENERAL_W_R_R,
+ /* VUNPCKHPS => */ GENERAL_W_R_R,
+ /* VUNPCKLPD => */ GENERAL_W_R_R,
+ /* VUNPCKLPS => */ GENERAL_W_R_R,
+ /* VXORPD => */ GENERAL_W_R_R,
+ /* VXORPS => */ GENERAL_W_R_R,
+ /* VZEROUPPER => */ BehaviorDigest::empty()
+ .set_complex(true),
+ /* VZEROALL => */ BehaviorDigest::empty()
+ .set_complex(true),
+ /* VLDMXCSR => */ BehaviorDigest::empty()
+ .set_operand(0, Access::Read)
+ .set_complex(true)
+ .set_pl_any(),
+ /* VSTMXCSR => */ BehaviorDigest::empty()
+ .set_operand(0, Access::Write)
+ .set_complex(true)
+ .set_pl_any(),
+
+ /* PCLMULQDQ => */ GENERAL_RW_R
+ .set_operand(2, Access::Read),
+ /* AESKEYGENASSIST => */ GENERAL_W_R
+ .set_operand(2, Access::Read),
+ /* AESIMC => */ GENERAL_W_R,
+ /* AESENC => */ GENERAL_RW_R,
+ /* AESENCLAST => */ GENERAL_RW_R,
+ /* AESDEC => */ GENERAL_RW_R,
+ /* AESDECLAST => */ GENERAL_RW_R,
+ /* PCMPGTQ => */ GENERAL_RW_R,
+ /* PCMPISTRM => */ GENERAL_R_R
+ .set_operand(2, Access::Read)
+ .set_flags_access(Access::Write)
+ .set_implicit_ops(PCMPISTRM_IDX),
+ /* PCMPISTRI => */ GENERAL_R_R
+ .set_operand(2, Access::Read)
+ .set_flags_access(Access::Write)
+ .set_implicit_ops(PCMPISTRI_IDX),
+ /* PCMPESTRI => */ GENERAL_R_R
+ .set_operand(2, Access::Read)
+ .set_flags_access(Access::Write)
+ .set_nontrivial(true),
+ /* PACKUSDW => */ GENERAL_RW_R,
+ /* PCMPESTRM => */ GENERAL_R_R
+ .set_operand(2, Access::Read)
+ .set_flags_access(Access::Write)
+ .set_nontrivial(true),
+ /* PCMPEQQ => */ GENERAL_RW_R,
+ /* PTEST => */ GENERAL_R_R
+ .set_flags_access(Access::Write),
+ /* PHMINPOSUW => */ GENERAL_W_R,
+ /* DPPS => */ GENERAL_RW_R
+ .set_operand(2, Access::Read),
+ /* DPPD => */ GENERAL_RW_R
+ .set_operand(2, Access::Read),
+ /* MPSADBW => */ GENERAL_RW_R
+ .set_operand(2, Access::Read),
+ /* PMOVZXDQ => */ GENERAL_RW_R,
+ /* PMOVSXDQ => */ GENERAL_RW_R,
+ /* PMOVZXBD => */ GENERAL_RW_R,
+ /* PMOVSXBD => */ GENERAL_RW_R,
+ /* PMOVZXWQ => */ GENERAL_RW_R,
+ /* PMOVSXWQ => */ GENERAL_RW_R,
+ /* PMOVZXBQ => */ GENERAL_RW_R,
+ /* PMOVSXBQ => */ GENERAL_RW_R,
+ /* PMOVSXWD => */ GENERAL_RW_R,
+ /* PMOVZXWD => */ GENERAL_RW_R,
+ /* PEXTRQ => */ GENERAL_W_R
+ .set_operand(2, Access::Read),
+ /* PEXTRD => */ GENERAL_W_R
+ .set_operand(2, Access::Read),
+ /* PEXTRW => */ GENERAL_W_R
+ .set_operand(2, Access::Read),
+ /* PEXTRB => */ GENERAL_W_R
+ .set_operand(2, Access::Read),
+ /* PMOVSXBW => */ GENERAL_RW_R,
+ /* PMOVZXBW => */ GENERAL_RW_R,
+ /* PINSRQ => */ GENERAL_RW_R
+ .set_operand(2, Access::Read),
+ /* PINSRD => */ GENERAL_RW_R
+ .set_operand(2, Access::Read),
+ /* PINSRB => */ GENERAL_RW_R
+ .set_operand(2, Access::Read),
+ /* EXTRACTPS => */ GENERAL_W_R
+ .set_operand(2, Access::Read),
+ /* INSERTPS => */ GENERAL_W_R
+ .set_operand(2, Access::Read),
+ /* ROUNDSS => */ GENERAL_RW_R
+ .set_operand(2, Access::Read),
+ /* ROUNDSD => */ GENERAL_RW_R
+ .set_operand(2, Access::Read),
+ /* ROUNDPS => */ GENERAL_W_R
+ .set_operand(2, Access::Read),
+ /* ROUNDPD => */ GENERAL_W_R
+ .set_operand(2, Access::Read),
+ /* PMAXSB => */ GENERAL_RW_R,
+ /* PMAXSD => */ GENERAL_RW_R,
+ /* PMAXUW => */ GENERAL_RW_R,
+ /* PMAXUD => */ GENERAL_RW_R,
+ /* PMINSD => */ GENERAL_RW_R,
+ /* PMINSB => */ GENERAL_RW_R,
+ /* PMINUD => */ GENERAL_RW_R,
+ /* PMINUW => */ GENERAL_RW_R,
+ // TODO: need to remove; doesn't exist
+ /* BLENDW => */ BehaviorDigest::empty(),
+ /* PBLENDVB => */ GENERAL_RW_R
+ .set_implicit_ops(XMM0_READ_IDX),
+ /* PBLENDW => */ GENERAL_RW_R
+ .set_operand(2, Access::Read),
+ /* BLENDVPS => */ GENERAL_RW_R
+ .set_implicit_ops(XMM0_READ_IDX),
+ /* BLENDVPD => */ GENERAL_RW_R
+ .set_implicit_ops(XMM0_READ_IDX),
+ /* BLENDPS => */ GENERAL_RW_R
+ .set_operand(2, Access::Read),
+ /* BLENDPD => */ GENERAL_RW_R
+ .set_operand(2, Access::Read),
+ /* PMULDQ => */ GENERAL_RW_R,
+ /* MOVNTDQA => */ GENERAL_W_R,
+ /* PMULLD => */ GENERAL_RW_R,
+ /* PALIGNR => */ GENERAL_RW_R
+ .set_operand(2, Access::Read),
+ /* PSIGNW => */ GENERAL_RW_R,
+ /* PSIGND => */ GENERAL_RW_R,
+ /* PSIGNB => */ GENERAL_RW_R,
+ /* PSHUFB => */ GENERAL_RW_R,
+ /* PMULHRSW => */ GENERAL_RW_R,
+ /* PMADDUBSW => */ GENERAL_RW_R,
+ /* PABSD => */ GENERAL_W_R,
+ /* PABSW => */ GENERAL_W_R,
+ /* PABSB => */ GENERAL_W_R,
+ /* PHSUBSW => */ GENERAL_RW_R,
+ /* PHSUBW => */ GENERAL_RW_R,
+ /* PHSUBD => */ GENERAL_RW_R,
+ /* PHADDD => */ GENERAL_RW_R,
+ /* PHADDSW => */ GENERAL_RW_R,
+ /* PHADDW => */ GENERAL_RW_R,
+ /* HSUBPD => */ GENERAL_RW_R,
+ /* HADDPD => */ GENERAL_RW_R,
+
+ /* SHA1RNDS4 => */ GENERAL_RW_R_R,
+ /* SHA1NEXTE => */ GENERAL_RW_R,
+ /* SHA1MSG1 => */ GENERAL_RW_R,
+ /* SHA1MSG2 => */ GENERAL_RW_R,
+ /* SHA256RNDS2 => */ GENERAL_RW_R
+ .set_implicit_ops(XMM0_READ_IDX),
+ /* SHA256MSG1 => */ GENERAL_RW_R,
+ /* SHA256MSG2 => */ GENERAL_RW_R,
+
+ /* LZCNT => */ GENERAL_W_R
+ .set_flags_access(Access::Write),
+ /* CLGI => */ BehaviorDigest::empty()
+ .set_pl0()
+ .set_complex(true),
+ /* STGI => */ BehaviorDigest::empty()
+ .set_pl0()
+ .set_complex(true),
+ /* SKINIT => */ BehaviorDigest::empty()
+ .set_pl0()
+ .set_complex(true)
+ .set_operand(0, Access::Read),
+ /* VMLOAD => */ BehaviorDigest::empty()
+ .set_pl0()
+ .set_complex(true)
+ .set_operand(0, Access::Read),
+ /* VMMCALL => */ BehaviorDigest::empty()
+ .set_pl_any()
+ .set_complex(true),
+ /* VMSAVE => */ BehaviorDigest::empty()
+ .set_pl0()
+ .set_complex(true)
+ .set_operand(0, Access::Read),
+ /* VMRUN => */ BehaviorDigest::empty()
+ .set_pl0()
+ .set_complex(true)
+ .set_operand(0, Access::Read),
+ /* INVLPGA => */ BehaviorDigest::empty()
+ .set_pl0()
+ .set_operand(0, Access::Read)
+ .set_operand(1, Access::Read),
+ /* INVLPGB => */ BehaviorDigest::empty()
+ .set_pl0()
+ .set_operand(0, Access::Read)
+ .set_operand(1, Access::Read)
+ .set_operand(2, Access::Read),
+ /* TLBSYNC => */ BehaviorDigest::empty()
+ .set_pl0(),
+
+ /* MOVBE => */ GENERAL_W_R,
+
+ /* ADCX => */ GENERAL_RW_R
+ .set_flags_access(Access::ReadWrite),
+ /* ADOX => */ GENERAL_RW_R
+ .set_flags_access(Access::ReadWrite),
+
+ /* PREFETCHW => */ BehaviorDigest::empty()
+ .set_pl_any()
+ .set_operand(0, Access::Read),
+
+ /* RDPID => */ BehaviorDigest::empty()
+ .set_pl_any()
+ .set_operand(0, Access::Write),
+ /* VMPTRLD => */ BehaviorDigest::empty()
+ .set_pl0()
+ .set_operand(0, Access::Read)
+ .set_complex(true),
+ /* VMPTRST => */ BehaviorDigest::empty()
+ .set_pl0()
+ .set_operand(0, Access::Write)
+ .set_complex(true),
+
+ /* BZHI => */ GENERAL_W_R_R
+ .set_flags_access(Access::Write),
+ /* MULX => */ BehaviorDigest::empty()
+ .set_operand(0, Access::Write)
+ .set_operand(1, Access::Write)
+ .set_operand(2, Access::Read)
+ .set_nontrivial(true),
+ /* SHLX => */ GENERAL_W_R_R,
+ /* SHRX => */ GENERAL_W_R_R,
+ /* SARX => */ GENERAL_W_R_R,
+ /* PDEP => */ GENERAL_W_R_R,
+ /* PEXT => */ GENERAL_W_R_R,
+ /* RORX => */ GENERAL_W_R_R,
+ /* XRSTORS => */ BehaviorDigest::empty()
+ .set_pl0()
+ .set_complex(true)
+ .set_operand(0, Access::Read)
+ .set_implicit_ops(READ_EDX_EAX_IDX),
+ /* XRSTORS64 => */ BehaviorDigest::empty()
+ .set_pl0()
+ .set_complex(true)
+ .set_operand(0, Access::Read)
+ .set_implicit_ops(READ_EDX_EAX_IDX),
+ /* XSAVEC => */ BehaviorDigest::empty()
+ .set_pl_any()
+ .set_complex(true)
+ .set_operand(0, Access::Write)
+ .set_implicit_ops(READ_EDX_EAX_IDX),
+ /* XSAVEC64 => */ BehaviorDigest::empty()
+ .set_pl_any()
+ .set_complex(true)
+ .set_operand(0, Access::Write)
+ .set_implicit_ops(READ_EDX_EAX_IDX),
+ /* XSAVES => */ BehaviorDigest::empty()
+ .set_pl0()
+ .set_complex(true)
+ .set_operand(0, Access::Write)
+ .set_implicit_ops(READ_EDX_EAX_IDX),
+ /* XSAVES64 => */ BehaviorDigest::empty()
+ .set_pl0()
+ .set_complex(true)
+ .set_operand(0, Access::Write)
+ .set_implicit_ops(READ_EDX_EAX_IDX),
+
+ /* RDFSBASE => */ BehaviorDigest::empty()
+ .set_pl_any()
+ .set_operand(0, Access::Write),
+ /* RDGSBASE => */ BehaviorDigest::empty()
+ .set_pl_any()
+ .set_operand(0, Access::Write),
+ /* WRFSBASE => */ BehaviorDigest::empty()
+ .set_pl_any()
+ .set_operand(0, Access::Read),
+ /* WRGSBASE => */ BehaviorDigest::empty()
+ .set_pl_any()
+ .set_operand(0, Access::Read),
+
+ /* CRC32 => */ GENERAL_RW_R,
+ /* SALC => */ BehaviorDigest::empty()
+ .set_pl_any()
+ .set_flags_access(Access::Read)
+ .set_implicit_ops(WRITE_AL_IDX),
+ /* XLAT => */ BehaviorDigest::empty()
+ .set_implicit_ops(XLAT_IDX)
+ .set_pl_any(),
+
+ // TODO: none of x87 is verified well.. and what about the bits in the FPU status word..
+ // and what about pushes/pops from the x87 operand stack..
+ // TODO: read st(0), write st(0)
+ /* F2XM1 => */ BehaviorDigest::empty()
+ .set_pl_any(),
+ /* FABS => */ GENERAL, // TODO: this is really an implicit write to st(0)
+ /* FADD => */ GENERAL_RW_R,
+ /* FADDP => */ GENERAL_RW_R,
+ /* FBLD => */ GENERAL_W_R,
+ /* FBSTP => */ GENERAL_W_R,
+ /* FCHS => */ GENERAL_W_R,
+ /* FCMOVB => */ GENERAL_W_R_FLAGREAD,
+ /* FCMOVBE => */ GENERAL_W_R_FLAGREAD,
+ /* FCMOVE => */ GENERAL_W_R_FLAGREAD,
+ /* FCMOVNB => */ GENERAL_W_R_FLAGREAD,
+ /* FCMOVNBE => */ GENERAL_W_R_FLAGREAD,
+ /* FCMOVNE => */ GENERAL_W_R_FLAGREAD,
+ /* FCMOVNU => */ GENERAL_W_R_FLAGREAD,
+ /* FCMOVU => */ GENERAL_W_R_FLAGREAD,
+ /* FCOM => */ GENERAL_R_R,
+ /* FCOMI => */ GENERAL_R_R_FLAGWRITE,
+ /* FCOMIP => */ GENERAL_R_R_FLAGWRITE,
+ /* FCOMP => */ GENERAL_R_R,
+ /* FCOMPP => */ GENERAL_R_R,
+ // TODO: st(0) -> st(0)
+ /* FCOS => */ BehaviorDigest::empty()
+ .set_pl_any(),
+ // TODO: x87 stack pointer dec
+ /* FDECSTP => */ BehaviorDigest::empty()
+ .set_pl_any(),
+ /* FDIV => */ GENERAL_RW_R,
+ // TODO: x87 stack pop
+ /* FDIVP => */ GENERAL_RW_R,
+ /* FDIVR => */ GENERAL_RW_R,
+ // TODO: x87 stack pop
+ /* FDIVRP => */ GENERAL_RW_R,
+ /* FENI8087_NOP => */ BehaviorDigest::empty()
+ .set_pl_any(),
+ /* FDISI8087_NOP => */ BehaviorDigest::empty()
+ .set_pl_any(),
+ // TODO: the behavior here is ... inaccurate. st(i) is not read, but state associated with
+ // that register is modified. so it's kind of read?
+ /* FFREE => */ BehaviorDigest::empty()
+ .set_operand(0, Access::Read)
+ .set_pl_any(),
+ // same as `ffree` above.
+ /* FFREEP => */ BehaviorDigest::empty()
+ .set_operand(0, Access::Read)
+ .set_pl_any(),
+ /* FIADD => */ GENERAL_RW_R,
+ /* FICOM => */ GENERAL_R_R,
+ /* FICOMP => */ GENERAL_R_R,
+ /* FIDIV => */ GENERAL_RW_R,
+ /* FIDIVR => */ GENERAL_RW_R,
+ // TODO: writing to st(0) is only kind of accurate, this *pushes* to the operand stack..
+ /* FILD => */ GENERAL_W_R,
+ /* FIMUL => */ GENERAL_RW_R,
+ // TODO: x87 stack pointer inc
+ /* FINCSTP => */ BehaviorDigest::empty()
+ .set_pl_any(),
+ /* FIST => */ GENERAL_W_R,
+ /* FISTP => */ GENERAL_W_R,
+ /* FISTTP => */ GENERAL_W_R,
+ /* FISUB => */ GENERAL_RW_R,
+ /* FISUBR => */ GENERAL_RW_R,
+ // TODO: writing to st(0) is only kind of accurate, this *pushes* to the operand stack..
+ /* FLD => */ GENERAL_W_R,
+ // TODO: fpu stack write
+ /* FLD1 => */ BehaviorDigest::empty()
+ .set_pl_any(),
+ /* FLDCW => */ GENERAL_R,
+ /* FLDENV => */ GENERAL_R
+ .set_pl_any()
+ .set_complex(true),
+ // TODO: fpu stack write
+ /* FLDL2E => */ BehaviorDigest::empty()
+ .set_pl_any(),
+ // TODO: fpu stack write
+ /* FLDL2T => */ BehaviorDigest::empty()
+ .set_pl_any(),
+ // TODO: fpu stack write
+ /* FLDLG2 => */ BehaviorDigest::empty()
+ .set_pl_any(),
+ // TODO: fpu stack write
+ /* FLDLN2 => */ BehaviorDigest::empty()
+ .set_pl_any(),
+ // TODO: fpu stack write
+ /* FLDPI => */ BehaviorDigest::empty()
+ .set_pl_any(),
+ // TODO: fpu stack write
+ /* FLDZ => */ BehaviorDigest::empty()
+ .set_pl_any(),
+ /* FMUL => */ GENERAL_RW_R,
+ /* FMULP => */ GENERAL_RW_R,
+ // TODO: report change to x87 flags?
+ /* FNCLEX => */ BehaviorDigest::empty()
+ .set_pl_any(),
+ // TODO: report change to x87 flags?
+ /* FNINIT => */ BehaviorDigest::empty()
+ .set_pl_any(),
+ /* FNOP => */ BehaviorDigest::empty()
+ .set_pl_any(),
+ /* FNSAVE => */ BehaviorDigest::empty()
+ .set_pl_any()
+ .set_operand(0, Access::Write)
+ .set_complex(true),
+ /* FNSTCW => */ BehaviorDigest::empty()
+ .set_pl_any()
+ .set_operand(0, Access::Write)
+ .set_complex(true),
+ /* FNSTENV => */ BehaviorDigest::empty()
+ .set_pl_any()
+ .set_operand(0, Access::Write)
+ .set_complex(true),
+ // TODO: never produced..
+ /* FNSTOR => */ BehaviorDigest::empty(),
+ /* FNSTSW => */ BehaviorDigest::empty()
+ .set_pl_any()
+ .set_operand(0, Access::Write)
+ .set_complex(true),
+ // TODO: read st(1) with atan(st(1)/st(0)) and pop
+ /* FPATAN => */ BehaviorDigest::empty()
+ .set_pl_any(),
+ // TODO: read st(0), st(1), write st(0)
+ /* FPREM => */ BehaviorDigest::empty()
+ .set_pl_any(),
+ // TODO: read st(0), st(1), write st(0)
+ /* FPREM1 => */ BehaviorDigest::empty()
+ .set_pl_any(),
+ // TODO: read st(0), write, push?
+ /* FPTAN => */ BehaviorDigest::empty()
+ .set_pl_any(),
+ // TODO: read st(0), write, push?
+ /* FRNDINT => */ BehaviorDigest::empty()
+ .set_pl_any(),
+ /* FRSTOR => */ BehaviorDigest::empty()
+ .set_pl_any()
+ .set_operand(0, Access::Write)
+ .set_complex(true),
+ // TODO: read st(0), st(1)
+ /* FSCALE => */ BehaviorDigest::empty()
+ .set_pl_any(),
+ // TODO: report this as a complex instruction?
+ /* FSETPM287_NOP => */ BehaviorDigest::empty()
+ .set_pl_any(),
+ // TODO: st(0) -> st(0)
+ /* FSIN => */ BehaviorDigest::empty()
+ .set_pl_any(),
+ // TODO: st(0) -> st(0)
+ /* FSINCOS => */ BehaviorDigest::empty()
+ .set_pl_any(),
+ // TODO: st(0) -> st(0)
+ /* FSQRT => */ BehaviorDigest::empty()
+ .set_pl_any(),
+ /* FST => */ GENERAL_W_R,
+ /* FSTP => */ GENERAL_W_R,
+ /* FSTPNCE => */ GENERAL_W_R,
+ /* FSUB => */ GENERAL_RW_R,
+ /* FSUBP => */ GENERAL_RW_R,
+ /* FSUBR => */ GENERAL_RW_R,
+ /* FSUBRP => */ GENERAL_RW_R,
+ // TODO: report change to x87 flags, read of st(0)?
+ /* FTST => */ BehaviorDigest::empty()
+ .set_pl_any(),
+ /* FUCOM => */ GENERAL_R_R,
+ /* FUCOMI => */ GENERAL_R_R_FLAGWRITE,
+ /* FUCOMIP => */ GENERAL_R_R_FLAGWRITE,
+ /* FUCOMP => */ GENERAL_R_R,
+ /* FUCOMPP => */ GENERAL_R_R,
+ // TODO: report change to x87 flags?
+ /* FXAM => */ BehaviorDigest::empty()
+ .set_pl_any(),
+ /* FXCH => */ GENERAL_RW_RW,
+ // TODO: read st(0), write st(0), x87 push
+ /* FXTRACT => */ BehaviorDigest::empty()
+ .set_pl_any(),
+ // TODO: read st(0), write st(0)
+ /* FYL2X => */ BehaviorDigest::empty()
+ .set_pl_any(),
+ // TODO: read st(0), write st(0)
+ /* FYL2XP1 => */ BehaviorDigest::empty()
+ .set_pl_any(),
+
+ /* LOOPNZ => */ BehaviorDigest::empty()
+ .set_pl_any()
+ .set_operand(0, Access::Read)
+ .set_flags_access(Access::ReadWrite)
+ .set_nontrivial(true),
+ /* LOOPZ => */ BehaviorDigest::empty()
+ .set_pl_any()
+ .set_operand(0, Access::Read)
+ .set_flags_access(Access::ReadWrite)
+ .set_nontrivial(true),
+ /* LOOP => */ BehaviorDigest::empty()
+ .set_pl_any()
+ .set_operand(0, Access::Read)
+ .set_flags_access(Access::ReadWrite)
+ .set_nontrivial(true),
+ /* JRCXZ => */ BehaviorDigest::empty()
+ .set_pl_any()
+ .set_operand(0, Access::Read)
+ .set_nontrivial(true),
+
+ // started shipping in Tremont, 2020 sept 23
+ // while this instruction is marked "write, read", the written first operand is a register
+ // interpreteed as an address for a memory destination through the `es` selector.
+ /* MOVDIR64B => */ GENERAL_W_R
+ .set_complex(true),
+ /* MOVDIRI => */ GENERAL_W_R,
+
+ // started shipping in Tiger Lake, 2020 sept 2
+ /* AESDEC128KL => */ GENERAL_RW_R
+ .set_flags_access(Access::Write),
+ /* AESDEC256KL => */ GENERAL_RW_R
+ .set_flags_access(Access::Write),
+ /* AESDECWIDE128KL => */ GENERAL_R
+ .set_implicit_ops(RW_XMM0TO7_IDX)
+ .set_flags_access(Access::Write),
+ /* AESDECWIDE256KL => */ GENERAL_R
+ .set_implicit_ops(RW_XMM0TO7_IDX)
+ .set_flags_access(Access::Write),
+ /* AESENC128KL => */ GENERAL_RW_R
+ .set_flags_access(Access::Write),
+ /* AESENC256KL => */ GENERAL_RW_R
+ .set_flags_access(Access::Write),
+ /* AESENCWIDE128KL => */ GENERAL_R
+ .set_implicit_ops(RW_XMM0TO7_IDX)
+ .set_flags_access(Access::Write),
+ /* AESENCWIDE256KL => */ GENERAL_R
+ .set_implicit_ops(RW_XMM0TO7_IDX)
+ .set_flags_access(Access::Write),
+ /* ENCODEKEY128 => */ GENERAL_W_R
+ .set_implicit_ops(ENCODEKEY_IDX)
+ .set_flags_access(Access::Write),
+ /* ENCODEKEY256 => */ GENERAL_W_R
+ .set_implicit_ops(ENCODEKEY_IDX)
+ .set_flags_access(Access::Write),
+ /* LOADIWKEY => */ GENERAL_R_R
+ .set_implicit_ops(LOADIWKEY_IDX)
+ .set_flags_access(Access::Write),
+
+ // unsure
+ /* HRESET => */ BehaviorDigest::empty()
+ .set_operand(0, Access::Read) // but really, "explicit imm8 operand is ignored"
+ .set_implicit_ops(READ_EAX_IDX)
+ .set_complex(true),
+
+ // 3dnow. note these are yet untested!
+ // the 3dnow DSP instructions (pi2fw, pf2iw, pfnacc, pfpnacc, pswapd)
+ // are even more untested.
+ /* FEMMS => */ GENERAL,
+ /* PI2FW => */ GENERAL_RW_R,
+ /* PI2FD => */ GENERAL_W_R,
+ /* PF2IW => */ GENERAL_RW_R,
+ /* PF2ID => */ GENERAL_W_R,
+ /* PMULHRW => */ GENERAL_RW_R,
+ /* PFCMPGE => */ GENERAL_RW_R,
+ /* PFMIN => */ GENERAL_RW_R,
+ /* PFRCP => */ GENERAL_W_R,
+ /* PFRSQRT => */ GENERAL_W_R,
+ /* PFSUB => */ GENERAL_RW_R,
+ /* PFADD => */ GENERAL_RW_R,
+ /* PFCMPGT => */ GENERAL_RW_R,
+ /* PFMAX => */ GENERAL_RW_R,
+ /* PFRCPIT1 => */ GENERAL_RW_R,
+ /* PFRSQIT1 => */ GENERAL_RW_R,
+ /* PFSUBR => */ GENERAL_RW_R,
+ /* PFACC => */ GENERAL_RW_R,
+ /* PFCMPEQ => */ GENERAL_RW_R,
+ /* PFMUL => */ GENERAL_RW_R,
+ /* PFMULHRW => */ GENERAL_RW_R,
+ /* PFRCPIT2 => */ GENERAL_RW_R,
+ /* PFNACC => */ GENERAL_RW_R,
+ /* PFPNACC => */ GENERAL_RW_R,
+ /* PSWAPD => */ GENERAL_RW_RW,
+ /* PAVGUSB => */ GENERAL_RW_R,
+
+ // ENQCMD
+ // similar to movdir64b, but more complex; the first operand is also an address for a
+ // memory destination.
+ /* ENQCMD => */ GENERAL_W_R
+ .set_flags_access(Access::Write)
+ .set_pl0()
+ .set_complex(true),
+ /* ENQCMDS => */ GENERAL_W_R
+ .set_flags_access(Access::Write)
+ .set_pl0()
+ .set_complex(true),
+
+ // INVPCID
+ // this almost meets the bar to be "complex", given that it manages non-architectural
+ // state not described by the operand iterator. but.. not quite, for now?
+ /* INVEPT => */ GENERAL_R_R,
+ // similar to above.
+ /* INVVPID => */ GENERAL_R_R,
+ // again, similar to `invept` above.
+ /* INVPCID => */ GENERAL_R_R,
+
+ // PTWRITE
+ // TODO: untested
+ /* PTWRITE => */ BehaviorDigest::empty()
+ .set_pl_any()
+ .set_complex(true),
+
+ // GFNI
+ /* GF2P8AFFINEQB => */ GENERAL_RW_R
+ .set_operand(2, Access::Read),
+ /* GF2P8AFFINEINVQB => */ GENERAL_RW_R
+ .set_operand(2, Access::Read),
+ /* GF2P8MULB => */ GENERAL_RW_R,
+
+ // CET
+ /* WRUSS => */ GENERAL_W_R
+ .set_pl0()
+ .set_complex(true),
+ /* WRSS => */ GENERAL_W_R
+ .set_pl_special()
+ .set_complex(true),
+ /* INCSSP => */ GENERAL_R
+ .set_pl_special()
+ .set_complex(true),
+ /* SAVEPREVSSP => */ BehaviorDigest::empty()
+ .set_pl_special()
+ .set_complex(true),
+ /* SETSSBSY => */ BehaviorDigest::empty()
+ .set_pl0()
+ .set_complex(true),
+ /* CLRSSBSY => */ BehaviorDigest::empty()
+ .set_pl0()
+ .set_operand(0, Access::ReadWrite)
+ .set_flags_access(Access::Write)
+ .set_complex(true),
+ /* RSTORSSP => */ BehaviorDigest::empty()
+ .set_pl_special()
+ .set_operand(0, Access::ReadWrite)
+ .set_flags_access(Access::Write)
+ .set_complex(true),
+ /* ENDBR64 => */ BehaviorDigest::empty()
+ .set_pl_any()
+ .set_complex(true),
+ /* ENDBR32 => */ BehaviorDigest::empty()
+ .set_pl_any()
+ .set_complex(true),
+
+ // TDX
+ /* TDCALL => */ BehaviorDigest::empty()
+ .set_pl0()
+ .set_complex(true),
+ /* SEAMRET => */ BehaviorDigest::empty()
+ .set_pl0()
+ .set_complex(true),
+ /* SEAMOPS => */ BehaviorDigest::empty()
+ .set_pl0()
+ .set_complex(true),
+ /* SEAMCALL => */ BehaviorDigest::empty()
+ .set_pl0()
+ .set_complex(true),
+
+ // WAITPKG
+ /* TPAUSE => */ BehaviorDigest::empty()
+ .set_pl_any()
+ .set_complex(true),
+ /* UMONITOR => */ BehaviorDigest::empty()
+ .set_pl_any()
+ .set_complex(true),
+ /* UMWAIT => */ BehaviorDigest::empty()
+ .set_pl_any()
+ .set_complex(true),
+
+ // UINTR
+ /* UIRET => */ BehaviorDigest::empty()
+ .set_pl_any()
+ .set_complex(true),
+ /* TESTUI => */ BehaviorDigest::empty()
+ .set_pl_any()
+ .set_complex(true),
+ /* CLUI => */ BehaviorDigest::empty()
+ .set_pl_any()
+ .set_complex(true),
+ /* STUI => */ BehaviorDigest::empty()
+ .set_pl_any()
+ .set_complex(true),
+ /* SENDUIPI => */ BehaviorDigest::empty()
+ .set_pl_any()
+ .set_complex(true),
+
+ // TSXLDTRK
+ // arguably these should be considered "complex" similar to `uintr` instructions above, but
+ // they are not (at this time). the arbitrary distinction here is that x{sus,res}ldtrk
+ // operate on nothing *but* the trackedness of loads, so interactions with this processor
+ // state can be easily determined by looking for these instructions. additionally, a user
+ // interested in this state is probably already looking for these instructions, so
+ // declaring them complex adds burden to all other use for no benefit.
+ /* XSUSLDTRK => */ BehaviorDigest::empty()
+ .set_pl_any(),
+ /* XRESLDTRK => */ BehaviorDigest::empty()
+ .set_pl_any(),
+
+ // AVX512F
+ /* VALIGND => */ GENERAL_W_R_R
+ .set_operand(3, Access::Read),
+ /* VALIGNQ => */ GENERAL_W_R_R
+ .set_operand(3, Access::Read),
+ /* VBLENDMPD => */ GENERAL_W_R_R,
+ /* VBLENDMPS => */ GENERAL_W_R_R,
+ /* VCOMPRESSPD => */ GENERAL_W_R,
+ /* VCOMPRESSPS => */ GENERAL_W_R,
+ /* VCVTPD2UDQ => */ GENERAL_W_R,
+ /* VCVTTPD2UDQ => */ GENERAL_W_R,
+ /* VCVTPS2UDQ => */ GENERAL_W_R,
+ /* VCVTTPS2UDQ => */ GENERAL_W_R,
+ /* VCVTQQ2PD => */ GENERAL_W_R,
+ /* VCVTQQ2PS => */ GENERAL_W_R,
+ /* VCVTSD2USI => */ GENERAL_W_R,
+ /* VCVTTSD2USI => */ GENERAL_W_R,
+ /* VCVTSS2USI => */ GENERAL_W_R,
+ /* VCVTTSS2USI => */ GENERAL_W_R,
+ /* VCVTUDQ2PD => */ GENERAL_W_R,
+ /* VCVTUDQ2PS => */ GENERAL_W_R,
+ /* VCVTUSI2USD => */ GENERAL_W_R,
+ /* VCVTUSI2USS => */ GENERAL_W_R,
+ /* VEXPANDPD => */ GENERAL_W_R,
+ /* VEXPANDPS => */ GENERAL_W_R,
+ /* VEXTRACTF32X4 => */ GENERAL_W_R
+ .set_operand(2, Access::Read),
+ /* VEXTRACTF64X4 => */ GENERAL_W_R
+ .set_operand(2, Access::Read),
+ /* VEXTRACTI32X4 => */ GENERAL_W_R
+ .set_operand(2, Access::Read),
+ /* VEXTRACTI64X4 => */ GENERAL_W_R
+ .set_operand(2, Access::Read),
+ /* VFIXUPIMMPD => */ GENERAL_RW_R_R
+ .set_operand(3, Access::Read),
+ /* VFIXUPIMMPS => */ GENERAL_RW_R_R
+ .set_operand(3, Access::Read),
+ /* VFIXUPIMMSD => */ GENERAL_RW_R_R
+ .set_operand(3, Access::Read),
+ /* VFIXUPIMMSS => */ GENERAL_RW_R_R
+ .set_operand(3, Access::Read),
+ /* VGETEXPPD => */ GENERAL_W_R,
+ /* VGETEXPPS => */ GENERAL_W_R,
+ /* VGETEXPSD => */ GENERAL_W_R_R,
+ /* VGETEXPSS => */ GENERAL_W_R_R,
+ /* VGETMANTPD => */ GENERAL_W_R
+ .set_operand(2, Access::Read),
+ /* VGETMANTPS => */ GENERAL_W_R
+ .set_operand(2, Access::Read),
+ /* VGETMANTSD => */ GENERAL_W_R_R
+ .set_operand(3, Access::Read),
+ /* VGETMANTSS => */ GENERAL_W_R_R
+ .set_operand(3, Access::Read),
+ /* VINSERTF32X4 => */ GENERAL_W_R_R
+ .set_operand(3, Access::Read),
+ /* VINSERTF64X4 => */ GENERAL_W_R_R
+ .set_operand(3, Access::Read),
+ /* VINSERTI64X4 => */ GENERAL_W_R_R
+ .set_operand(3, Access::Read),
+ /* VMOVDQA32 => */ GENERAL_W_R,
+ /* VMOVDQA64 => */ GENERAL_W_R,
+ /* VMOVDQU32 => */ GENERAL_W_R,
+ /* VMOVDQU64 => */ GENERAL_W_R,
+ /* VPBLENDMD => */ GENERAL_W_R_R,
+ /* VPBLENDMQ => */ GENERAL_W_R_R,
+ /* VPCMPD => */ GENERAL_W_R_R
+ .set_operand(3, Access::Read),
+ /* VPCMPUD => */ GENERAL_W_R_R
+ .set_operand(3, Access::Read),
+ /* VPCMPQ => */ GENERAL_W_R_R
+ .set_operand(3, Access::Read),
+ /* VPCMPUQ => */ GENERAL_W_R_R
+ .set_operand(3, Access::Read),
+ /* VPCOMPRESSQ => */ GENERAL_W_R,
+ /* VPCOMPRESSD => */ GENERAL_W_R,
+ /* VPERMI2D => */ GENERAL_W_R_R,
+ /* VPERMI2Q => */ GENERAL_W_R_R,
+ /* VPERMI2PD => */ GENERAL_W_R_R,
+ /* VPERMI2PS => */ GENERAL_W_R_R,
+ /* VPERMT2D => */ GENERAL_W_R_R,
+ /* VPERMT2Q => */ GENERAL_W_R_R,
+ /* VPERMT2PD => */ GENERAL_W_R_R,
+ /* VPERMT2PS => */ GENERAL_W_R_R,
+ /* VPMAXSQ => */ GENERAL_W_R_R,
+ /* VPMAXUQ => */ GENERAL_W_R_R,
+ /* VPMINSQ => */ GENERAL_W_R_R,
+ /* VPMINUQ => */ GENERAL_W_R_R,
+ /* VPMOVSQB => */ GENERAL_W_R,
+ /* VPMOVUSQB => */ GENERAL_W_R,
+ /* VPMOVSQW => */ GENERAL_W_R,
+ /* VPMOVUSQW => */ GENERAL_W_R,
+ /* VPMOVSQD => */ GENERAL_W_R,
+ /* VPMOVUSQD => */ GENERAL_W_R,
+ /* VPMOVSDB => */ GENERAL_W_R,
+ /* VPMOVUSDB => */ GENERAL_W_R,
+ /* VPMOVSDW => */ GENERAL_W_R,
+ /* VPMOVUSDW => */ GENERAL_W_R,
+ /* VPROLD => */ GENERAL_W_R_R,
+ /* VPROLQ => */ GENERAL_W_R_R,
+ /* VPROLVD => */ GENERAL_W_R_R,
+ /* VPROLVQ => */ GENERAL_W_R_R,
+ /* VPRORD => */ GENERAL_W_R_R,
+ /* VPRORQ => */ GENERAL_W_R_R,
+ /* VPRORRD => */ GENERAL_W_R_R,
+ /* VPRORRQ => */ GENERAL_W_R_R,
+ // TODO: complex
+ /* VPSCATTERDD => */ BehaviorDigest::empty()
+ .set_complex(true),
+ /* VPSCATTERDQ => */ BehaviorDigest::empty()
+ .set_complex(true),
+ /* VPSCATTERQD => */ BehaviorDigest::empty()
+ .set_complex(true),
+ /* VPSCATTERQQ => */ BehaviorDigest::empty()
+ .set_complex(true),
+ /* VPSRAQ => */ GENERAL_W_R_R,
+ /* VPSRAVQ => */ GENERAL_W_R_R,
+ /* VPTESTNMD => */ GENERAL_W_R_R,
+ /* VPTESTNMQ => */ GENERAL_W_R_R,
+ /* VPTERNLOGD => */ GENERAL_W_R_R
+ .set_operand(3, Access::Read),
+ /* VPTERNLOGQ => */ GENERAL_W_R_R
+ .set_operand(3, Access::Read),
+ /* VPTESTMD => */ GENERAL_W_R_R,
+ /* VPTESTMQ => */ GENERAL_W_R_R,
+ /* VRCP14PD => */ GENERAL_W_R,
+ /* VRCP14PS => */ GENERAL_W_R,
+ /* VRCP14SD => */ GENERAL_W_R_R,
+ /* VRCP14SS => */ GENERAL_W_R_R,
+ /* VRNDSCALEPD => */ GENERAL_W_R
+ .set_operand(2, Access::Read),
+ /* VRNDSCALEPS => */ GENERAL_W_R
+ .set_operand(2, Access::Read),
+ /* VRNDSCALESD => */ GENERAL_W_R_R
+ .set_operand(3, Access::Read),
+ /* VRNDSCALESS => */ GENERAL_W_R_R
+ .set_operand(3, Access::Read),
+ /* VRSQRT14PD => */ GENERAL_W_R,
+ /* VRSQRT14PS => */ GENERAL_W_R,
+ /* VRSQRT14SD => */ GENERAL_W_R_R,
+ /* VRSQRT14SS => */ GENERAL_W_R_R,
+ // vvv --- these don't exist..
+ /* VSCALEDPD => */ BehaviorDigest::empty(),
+ /* VSCALEDPS => */ BehaviorDigest::empty(),
+ /* VSCALEDSD => */ BehaviorDigest::empty(),
+ /* VSCALEDSS => */ BehaviorDigest::empty(),
+ /* VSCATTERDD => */ BehaviorDigest::empty(),
+ /* VSCATTERDQ => */ BehaviorDigest::empty(),
+ /* VSCATTERQD => */ BehaviorDigest::empty(),
+ /* VSCATTERQQ => */ BehaviorDigest::empty(),
+ // ^^^ --- these don't exist..
+ /* VSHUFF32X4 => */ GENERAL_W_R_R
+ .set_operand(3, Access::Read),
+ /* VSHUFF64X2 => */ GENERAL_W_R_R
+ .set_operand(3, Access::Read),
+ /* VSHUFI32X4 => */ GENERAL_W_R_R
+ .set_operand(3, Access::Read),
+ /* VSHUFI64X2 => */ GENERAL_W_R_R
+ .set_operand(3, Access::Read),
+
+ // AVX512DQ
+ /* VCVTTPD2QQ => */ GENERAL_W_R,
+ /* VCVTPD2QQ => */ GENERAL_W_R,
+ /* VCVTTPD2UQQ => */ GENERAL_W_R,
+ /* VCVTPD2UQQ => */ GENERAL_W_R,
+ /* VCVTTPS2QQ => */ GENERAL_W_R,
+ /* VCVTPS2QQ => */ GENERAL_W_R,
+ /* VCVTTPS2UQQ => */ GENERAL_W_R,
+ /* VCVTPS2UQQ => */ GENERAL_W_R,
+ /* VCVTUQQ2PD => */ GENERAL_W_R,
+ /* VCVTUQQ2PS => */ GENERAL_W_R,
+ /* VEXTRACTF64X2 => */ GENERAL_W_R
+ .set_operand(2, Access::Read),
+ /* VEXTRACTI64X2 => */ GENERAL_W_R
+ .set_operand(2, Access::Read),
+ /* VFPCLASSPD => */ GENERAL_W_R
+ .set_operand(2, Access::Read),
+ /* VFPCLASSPS => */ GENERAL_W_R
+ .set_operand(2, Access::Read),
+ /* VFPCLASSSD => */ GENERAL_W_R
+ .set_operand(2, Access::Read),
+ /* VFPCLASSSS => */ GENERAL_W_R
+ .set_operand(2, Access::Read),
+ /* VINSERTF64X2 => */ GENERAL_W_R_R
+ .set_operand(3, Access::Read),
+ /* VINSERTI64X2 => */ GENERAL_W_R_R
+ .set_operand(3, Access::Read),
+ /* VPMOVM2D => */ GENERAL_W_R,
+ /* VPMOVM2Q => */ GENERAL_W_R,
+ /* VPMOVB2D => */ GENERAL_W_R,
+ /* VPMOVQ2M => */ GENERAL_W_R,
+ /* VRANGEPD => */ GENERAL_W_R_R
+ .set_operand(3, Access::Read),
+ /* VRANGEPS => */ GENERAL_W_R_R
+ .set_operand(3, Access::Read),
+ /* VRANGESD => */ GENERAL_W_R_R
+ .set_operand(3, Access::Read),
+ /* VRANGESS => */ GENERAL_W_R_R
+ .set_operand(3, Access::Read),
+ /* VREDUCEPD => */ GENERAL_W_R_R
+ .set_operand(2, Access::Read),
+ /* VREDUCEPS => */ GENERAL_W_R_R
+ .set_operand(2, Access::Read),
+ /* VREDUCESD => */ GENERAL_W_R_R
+ .set_operand(3, Access::Read),
+ /* VREDUCESS => */ GENERAL_W_R_R
+ .set_operand(3, Access::Read),
+
+ // AVX512BW
+ /* VDBPSADBW => */ GENERAL_W_R_R
+ .set_operand(3, Access::Read),
+ /* VMOVDQU8 => */ GENERAL_W_R,
+ /* VMOVDQU16 => */ GENERAL_W_R,
+ /* VPBLENDMB => */ GENERAL_W_R_R,
+ /* VPBLENDMW => */ GENERAL_W_R_R,
+ /* VPCMPB => */ GENERAL_W_R_R
+ .set_operand(3, Access::Read),
+ /* VPCMPUB => */ GENERAL_W_R_R
+ .set_operand(3, Access::Read),
+ /* VPCMPW => */ GENERAL_W_R_R
+ .set_operand(3, Access::Read),
+ /* VPCMPUW => */ GENERAL_W_R_R
+ .set_operand(3, Access::Read),
+ /* VPERMW => */ GENERAL_W_R_R,
+ /* VPERMI2B => */ GENERAL_W_R_R,
+ /* VPERMI2W => */ GENERAL_W_R_R,
+ /* VPMOVM2B => */ GENERAL_W_R,
+ /* VPMOVM2W => */ GENERAL_W_R,
+ /* VPMOVB2M => */ GENERAL_W_R,
+ /* VPMOVW2M => */ GENERAL_W_R,
+ /* VPMOVSWB => */ GENERAL_W_R,
+ /* VPMOVUSWB => */ GENERAL_W_R,
+ /* VPSLLVW => */ GENERAL_W_R_R,
+ /* VPSRAVW => */ GENERAL_W_R_R,
+ /* VPSRLVW => */ GENERAL_W_R_R,
+ /* VPTESTNMB => */ GENERAL_W_R_R,
+ /* VPTESTNMW => */ GENERAL_W_R_R,
+ /* VPTESTMB => */ GENERAL_W_R_R,
+ /* VPTESTMW => */ GENERAL_W_R_R,
+
+ // AVX512CD
+ // TODO: this one does not exist
+ /* VPBROADCASTM => */ BehaviorDigest::empty(),
+ /* VPCONFLICTD => */ GENERAL_W_R,
+ /* VPCONFLICTQ => */ GENERAL_W_R,
+ /* VPLZCNTD => */ GENERAL_W_R,
+ /* VPLZCNTQ => */ GENERAL_W_R,
+
+ /* KUNPCKBW => */ GENERAL_W_R_R,
+ /* KUNPCKWD => */ GENERAL_W_R_R,
+ /* KUNPCKDQ => */ GENERAL_W_R_R,
+
+ /* KADDB => */ GENERAL_W_R_R,
+ /* KANDB => */ GENERAL_W_R_R,
+ /* KANDNB => */ GENERAL_W_R_R,
+ /* KMOVB => */ GENERAL_W_R,
+ /* KNOTB => */ GENERAL_W_R,
+ /* KORB => */ GENERAL_W_R_R,
+ /* KORTESTB => */ GENERAL_R_R
+ .set_flags_access(Access::Write),
+ /* KSHIFTLB => */ GENERAL_W_R_R,
+ /* KSHIFTRB => */ GENERAL_W_R_R,
+ /* KTESTB => */ GENERAL_R_R
+ .set_flags_access(Access::Write),
+ /* KXNORB => */ GENERAL_W_R_R,
+ /* KXORB => */ GENERAL_W_R_R,
+ /* KADDW => */ GENERAL_W_R_R,
+ /* KANDW => */ GENERAL_W_R_R,
+ /* KANDNW => */ GENERAL_W_R_R,
+ /* KMOVW => */ GENERAL_W_R,
+ /* KNOTW => */ GENERAL_W_R,
+ /* KORW => */ GENERAL_W_R_R,
+ /* KORTESTW => */ GENERAL_W_R_R
+ .set_flags_access(Access::Write),
+ /* KSHIFTLW => */ GENERAL_W_R_R,
+ /* KSHIFTRW => */ GENERAL_W_R_R,
+ /* KTESTW => */ GENERAL_W_R_R
+ .set_flags_access(Access::Write),
+ /* KXNORW => */ GENERAL_W_R_R,
+ /* KXORW => */ GENERAL_W_R_R,
+ /* KADDD => */ GENERAL_W_R_R,
+ /* KANDD => */ GENERAL_W_R_R,
+ /* KANDND => */ GENERAL_W_R_R,
+ /* KMOVD => */ GENERAL_W_R,
+ /* KNOTD => */ GENERAL_W_R,
+ /* KORD => */ GENERAL_W_R_R,
+ /* KORTESTD => */ GENERAL_W_R_R
+ .set_flags_access(Access::Write),
+ /* KSHIFTLD => */ GENERAL_W_R_R,
+ /* KSHIFTRD => */ GENERAL_W_R_R,
+ /* KTESTD => */ GENERAL_W_R_R
+ .set_flags_access(Access::Write),
+ /* KXNORD => */ GENERAL_W_R_R,
+ /* KXORD => */ GENERAL_W_R_R,
+ /* KADDQ => */ GENERAL_W_R_R,
+ /* KANDQ => */ GENERAL_W_R_R,
+ /* KANDNQ => */ GENERAL_W_R_R,
+ /* KMOVQ => */ GENERAL_W_R,
+ /* KNOTQ => */ GENERAL_W_R,
+ /* KORQ => */ GENERAL_W_R_R,
+ /* KORTESTQ => */ GENERAL_W_R_R
+ .set_flags_access(Access::Write),
+ /* KSHIFTLQ => */ GENERAL_W_R_R,
+ /* KSHIFTRQ => */ GENERAL_W_R_R,
+ /* KTESTQ => */ GENERAL_W_R_R
+ .set_flags_access(Access::Write),
+ /* KXNORQ => */ GENERAL_W_R_R,
+ /* KXORQ => */ GENERAL_W_R_R,
+
+ // AVX512ER
+ /* VEXP2PD => */ GENERAL_W_R,
+ /* VEXP2PS => */ GENERAL_W_R,
+ // TODO: well, this one isn't real.
+ /* VEXP2SD => */ BehaviorDigest::empty(),
+ // TODO: or this one.
+ /* VEXP2SS => */ BehaviorDigest::empty(),
+ /* VRCP28PD => */ GENERAL_W_R,
+ /* VRCP28PS => */ GENERAL_W_R,
+ /* VRCP28SD => */ GENERAL_W_R_R,
+ /* VRCP28SS => */ GENERAL_W_R_R,
+ /* VRSQRT28PD => */ GENERAL_W_R,
+ /* VRSQRT28PS => */ GENERAL_W_R,
+ /* VRSQRT28SD => */ GENERAL_W_R_R,
+ /* VRSQRT28SS => */ GENERAL_W_R_R,
+
+ // AVX512PF
+ /* VGATHERPF0DPD => */ BehaviorDigest::empty()
+ .set_operand(0, Access::Read)
+ .set_complex(true),
+ /* VGATHERPF0DPS => */ BehaviorDigest::empty()
+ .set_operand(0, Access::Read)
+ .set_complex(true),
+ /* VGATHERPF0QPD => */ BehaviorDigest::empty()
+ .set_operand(0, Access::Read)
+ .set_complex(true),
+ /* VGATHERPF0QPS => */ BehaviorDigest::empty()
+ .set_operand(0, Access::Read)
+ .set_complex(true),
+ /* VGATHERPF1DPD => */ BehaviorDigest::empty()
+ .set_operand(0, Access::Read)
+ .set_complex(true),
+ /* VGATHERPF1DPS => */ BehaviorDigest::empty()
+ .set_operand(0, Access::Read)
+ .set_complex(true),
+ /* VGATHERPF1QPD => */ BehaviorDigest::empty()
+ .set_operand(0, Access::Read)
+ .set_complex(true),
+ /* VGATHERPF1QPS => */ BehaviorDigest::empty()
+ .set_operand(0, Access::Read)
+ .set_complex(true),
+ /* VSCATTERPF0DPD => */ BehaviorDigest::empty()
+ .set_operand(0, Access::Read)
+ .set_complex(true),
+ /* VSCATTERPF0DPS => */ BehaviorDigest::empty()
+ .set_operand(0, Access::Read)
+ .set_complex(true),
+ /* VSCATTERPF0QPD => */ BehaviorDigest::empty()
+ .set_operand(0, Access::Read)
+ .set_complex(true),
+ /* VSCATTERPF0QPS => */ BehaviorDigest::empty()
+ .set_operand(0, Access::Read)
+ .set_complex(true),
+ /* VSCATTERPF1DPD => */ BehaviorDigest::empty()
+ .set_operand(0, Access::Read)
+ .set_complex(true),
+ /* VSCATTERPF1DPS => */ BehaviorDigest::empty()
+ .set_operand(0, Access::Read)
+ .set_complex(true),
+ /* VSCATTERPF1QPD => */ BehaviorDigest::empty()
+ .set_operand(0, Access::Read)
+ .set_complex(true),
+ /* VSCATTERPF1QPS => */ BehaviorDigest::empty()
+ .set_operand(0, Access::Read)
+ .set_complex(true),
+
+ // MPX
+ /* BNDMK => */ GENERAL_W_R,
+ /* BNDCL => */ GENERAL_R,
+ /* BNDCU => */ GENERAL_R,
+ /* BNDCN => */ GENERAL_R,
+ /* BNDMOV => */ GENERAL_W_R,
+ /* BNDLDX => */ GENERAL_W_R
+ .set_complex(true),
+ /* BNDSTX => */ GENERAL_W_R
+ .set_complex(true),
+
+ /* VGF2P8AFFINEQB => */ GENERAL_W_R_R
+ .set_operand(3, Access::Read),
+ /* VGF2P8AFFINEINVQB => */ GENERAL_W_R_R
+ .set_operand(3, Access::Read),
+ /* VPSHRDQ => */ GENERAL_W_R_R
+ .set_operand(3, Access::Read),
+ /* VPSHRDD => */ GENERAL_W_R_R
+ .set_operand(3, Access::Read),
+ /* VPSHRDW => */ GENERAL_W_R_R
+ .set_operand(3, Access::Read),
+ /* VPSHLDQ => */ GENERAL_W_R_R
+ .set_operand(3, Access::Read),
+ /* VPSHLDD => */ GENERAL_W_R_R
+ .set_operand(3, Access::Read),
+ /* VPSHLDW => */ GENERAL_W_R_R
+ .set_operand(3, Access::Read),
+ /* VBROADCASTF32X8 => */ GENERAL_W_R,
+ /* VBROADCASTF64X4 => */ GENERAL_W_R,
+ /* VBROADCASTF32X4 => */ GENERAL_W_R,
+ /* VBROADCASTF64X2 => */ GENERAL_W_R,
+ /* VBROADCASTF32X2 => */ GENERAL_W_R,
+ /* VBROADCASTI32X8 => */ GENERAL_W_R,
+ /* VBROADCASTI64X4 => */ GENERAL_W_R,
+ /* VBROADCASTI32X4 => */ GENERAL_W_R,
+ /* VBROADCASTI64X2 => */ GENERAL_W_R,
+ /* VBROADCASTI32X2 => */ GENERAL_W_R,
+ /* VEXTRACTI32X8 => */ GENERAL_W_R
+ .set_operand(2, Access::Read),
+ /* VEXTRACTF32X8 => */ GENERAL_W_R
+ .set_operand(2, Access::Read),
+ /* VINSERTI32X8 => */ GENERAL_W_R_R
+ .set_operand(3, Access::Read),
+ /* VINSERTF32X8 => */ GENERAL_W_R_R
+ .set_operand(3, Access::Read),
+ /* VINSERTI32X4 => */ GENERAL_W_R_R
+ .set_operand(3, Access::Read),
+ /* V4FNMADDSS => */ GENERAL_RW_R_R
+ .set_complex(true),
+ /* V4FNMADDPS => */ GENERAL_RW_R_R
+ .set_complex(true),
+ /* VCVTNEPS2BF16 => */ GENERAL_W_R,
+ /* V4FMADDSS => */ GENERAL_RW_R_R
+ .set_complex(true),
+ /* V4FMADDPS => */ GENERAL_RW_R_R
+ .set_complex(true),
+ /* VCVTNE2PS2BF16 => */ GENERAL_W_R_R,
+ /* VP2INTERSECTD => */ GENERAL_W_R_R,
+ /* VP2INTERSECTQ => */ GENERAL_W_R_R,
+ /* VP4DPWSSDS => */ GENERAL_RW_R_R,
+ /* VP4DPWSSD => */ GENERAL_RW_R_R,
+ /* VPDPWSSDS => */ GENERAL_RW_R_R,
+ /* VPDPWSSD => */ GENERAL_RW_R_R,
+ /* VPDPBUSDS => */ GENERAL_RW_R_R,
+ /* VDPBF16PS => */ GENERAL_RW_R_R,
+ /* VPBROADCASTMW2D => */ GENERAL_W_R,
+ /* VPBROADCASTMB2Q => */ GENERAL_W_R,
+ /* VPMOVD2M => */ GENERAL_W_R,
+ /* VPMOVQD => */ GENERAL_W_R,
+ /* VPMOVWB => */ GENERAL_W_R,
+ /* VPMOVDB => */ GENERAL_W_R,
+ /* VPMOVDW => */ GENERAL_W_R,
+ /* VPMOVQB => */ GENERAL_W_R,
+ /* VPMOVQW => */ GENERAL_W_R,
+ /* VGF2P8MULB => */ GENERAL_RW_R_R,
+ /* VPMADD52HUQ => */ GENERAL_RW_R_R,
+ /* VPMADD52LUQ => */ GENERAL_RW_R_R,
+ /* VPSHUFBITQMB => */ GENERAL_W_R_R,
+ /* VPERMB => */ GENERAL_W_R_R,
+ /* VPEXPANDD => */ GENERAL_W_R,
+ /* VPEXPANDQ => */ GENERAL_W_R,
+ /* VPABSQ => */ GENERAL_W_R,
+ /* VPRORVD => */ GENERAL_W_R_R,
+ /* VPRORVQ => */ GENERAL_W_R_R,
+ /* VPMULTISHIFTQB => */ GENERAL_W_R_R,
+ /* VPERMT2B => */ GENERAL_RW_R_R,
+ /* VPERMT2W => */ GENERAL_RW_R_R,
+ /* VPSHRDVQ => */ GENERAL_RW_R_R,
+ /* VPSHRDVD => */ GENERAL_RW_R_R,
+ /* VPSHRDVW => */ GENERAL_RW_R_R,
+ /* VPSHLDVQ => */ GENERAL_RW_R_R,
+ /* VPSHLDVD => */ GENERAL_RW_R_R,
+ /* VPSHLDVW => */ GENERAL_RW_R_R,
+ /* VPCOMPRESSB => */ GENERAL_W_R,
+ /* VPCOMPRESSW => */ GENERAL_W_R,
+ /* VPEXPANDB => */ GENERAL_W_R,
+ /* VPEXPANDW => */ GENERAL_W_R,
+ /* VPOPCNTD => */ GENERAL_W_R,
+ /* VPOPCNTQ => */ GENERAL_W_R,
+ /* VPOPCNTB => */ GENERAL_W_R,
+ /* VPOPCNTW => */ GENERAL_W_R,
+ /* VSCALEFSS => */ GENERAL_W_R_R,
+ /* VSCALEFSD => */ GENERAL_W_R_R,
+ /* VSCALEFPS => */ GENERAL_W_R_R,
+ /* VSCALEFPD => */ GENERAL_W_R_R,
+ /* VPDPBUSD => */ GENERAL_W_R_R,
+ /* VCVTUSI2SD => */ GENERAL_W_R_R,
+ /* VCVTUSI2SS => */ GENERAL_W_R_R,
+ /* VPXORD => */ GENERAL_W_R_R,
+ /* VPXORQ => */ GENERAL_W_R_R,
+ /* VPORD => */ GENERAL_W_R_R,
+ /* VPORQ => */ GENERAL_W_R_R,
+ /* VPANDND => */ GENERAL_W_R_R,
+ /* VPANDNQ => */ GENERAL_W_R_R,
+ /* VPANDD => */ GENERAL_W_R_R,
+ /* VPANDQ => */ GENERAL_W_R_R,
+
+ /* PSMASH => */ BehaviorDigest::empty()
+ .set_pl0()
+ .set_flags_access(Access::Write)
+ .set_complex(true),
+ /* PVALIDATE => */ BehaviorDigest::empty()
+ .set_pl0()
+ .set_flags_access(Access::Write)
+ .set_complex(true),
+ /* RMPADJUST => */ BehaviorDigest::empty()
+ .set_pl0()
+ .set_flags_access(Access::Write)
+ .set_complex(true),
+ /* RMPUPDATE => */ BehaviorDigest::empty()
+ .set_pl0()
+ .set_flags_access(Access::Write)
+ .set_complex(true),
+];
diff --git a/src/long_mode/mod.rs b/src/long_mode/mod.rs
index 081d20f..c477b16 100644
--- a/src/long_mode/mod.rs
+++ b/src/long_mode/mod.rs
@@ -3,6 +3,8 @@ mod evex;
#[cfg(feature = "fmt")]
mod display;
pub mod uarch;
+#[cfg(feature = "behavior")]
+pub mod behavior;
pub use crate::MemoryAccessSize;
use crate::{Address, Word};
@@ -304,6 +306,13 @@ impl RegSpec {
r12b => 12, r13b => 13, r14b => 14, r15b => 15
);
+ register!(CR,
+ cr0 => 0, cr1 => 1, cr2 => 2, cr3 => 3,
+ cr4 => 4, cr5 => 5, cr6 => 6, cr7 => 7,
+ cr8 => 8, cr9 => 9, cr10 => 10, cr11 => 11,
+ cr12 => 12, cr13 => 13, cr14 => 14, cr15 => 15
+ );
+
#[inline]
pub const fn zmm0() -> RegSpec {
RegSpec { bank: RegisterBank::Z, num: 0 }
@@ -1088,6 +1097,7 @@ const XSAVE: [Opcode; 10] = [
/// an `x86_64` opcode. there sure are a lot of these.
#[allow(non_camel_case_types)]
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
+#[cfg_attr(feature = "_debug_internal_asserts", derive(strum::EnumCount))]
#[non_exhaustive]
#[repr(u32)]
pub enum Opcode {
@@ -1190,8 +1200,11 @@ pub enum Opcode {
LEA,
NOP,
PREFETCHNTA,
+ /// this variant was named incorrectly and will change to `PREFETCHT0` in the future.
PREFETCH0,
+ /// this variant was named incorrectly and will change to `PREFETCHT1` in the future.
PREFETCH1,
+ /// this variant was named incorrectly and will change to `PREFETCHT2` in the future.
PREFETCH2,
// XCHG,
POPF,
@@ -2980,7 +2993,7 @@ impl Opcode {
}
/// get the [`ConditionCode`] for this instruction, if it is in fact conditional. x86's
- /// conditional instructions are `Jcc`, `CMOVcc`, andd `SETcc`.
+ /// conditional instructions are `Jcc`, `CMOVcc`, and `SETcc`.
pub fn condition(&self) -> Option<ConditionCode> {
match self {
Opcode::JO |
diff --git a/src/long_mode/uarch.rs b/src/long_mode/uarch.rs
index 9cfc9de..98c9de2 100644
--- a/src/long_mode/uarch.rs
+++ b/src/long_mode/uarch.rs
@@ -182,7 +182,7 @@ pub mod amd {
.with_avx512_vl()
.with_avx512_bw()
.with_avx512_cd()
- .with_avx512_cd()
+ .with_avx512_vl()
.with_avx512_vbmi()
.with_avx512_vbmi2()
.with_avx512_vpopcntdq()
@@ -195,6 +195,16 @@ pub mod amd {
zen4()
.with_movdir64b()
.with_enqcmd()
+ .with_avx512_dq()
+ .with_avx512_fma()
+ .with_avx512_bw()
+ .with_avx512_vnni()
+ .with_avx512_bitalg()
+ /*
+ * one would imagine the following as well, but there are not features yet:
+ * .with_avx512_vpopcntdq()
+ * .with_avx512_bf16()
+ */
}
}
diff --git a/test/long_mode/behavior.rs b/test/long_mode/behavior.rs
new file mode 100644
index 0000000..986b5f3
--- /dev/null
+++ b/test/long_mode/behavior.rs
@@ -0,0 +1,2416 @@
+#[cfg(all(target_arch = "x86_64"))]
+mod kvm {
+ use asmlinator::x86_64::{GuestAddress, Vm, VcpuExit, kvm_regs, kvm_sregs};
+
+ use yaxpeax_x86::long_mode;
+ use yaxpeax_x86::long_mode::Instruction;
+ use yaxpeax_x86::Exception;
+
+ use rand::prelude::*;
+
+ fn host_decoder() -> long_mode::InstDecoder {
+ // Safety: it's cpuid, everything supports leaf eax=1.
+ let leaf1 = unsafe {
+ core::arch::x86_64::__cpuid(1)
+ };
+ match leaf1.eax {
+ 0x00b40f40 => {
+ // zen 5 (my 9950x)
+ long_mode::uarch::amd::zen5()
+ }
+ 0x00870f10 => {
+ // zen 2 (my 3950x)
+ long_mode::uarch::amd::zen2()
+ }
+ 0x00050657 => {
+ // 10980xe (according to instlatx86)
+ // this is actually "cascade lake" but there's no uarch for that yet
+ long_mode::uarch::intel::skylake()
+ }
+ _ => {
+ // some kind of assumed baseline, haswell-or-later is a total guess on compat.
+ long_mode::uarch::intel::haswell()
+ }
+ }
+ }
+
+ #[derive(Debug, Copy, Clone)]
+ struct ExpectedMemAccess {
+ write: bool,
+ addr: u64,
+ size: u32,
+ }
+
+ #[derive(Debug, Copy, Clone)]
+ struct ExpectedRegAccess {
+ write: bool,
+ reg: RegSpec,
+ }
+
+ #[derive(Debug, Copy, Clone)]
+ struct UnexpectedRegChange {
+ reg: RegSpec,
+ before: u64,
+ after: u64,
+ }
+
+ #[derive(Debug, Clone)]
+ struct MemPatch {
+ addr: u64,
+ bytes: Vec<u8>,
+ }
+
+ #[derive(Debug)]
+ enum CheckErr {
+ ComplexOp(long_mode::behavior::ComplexOp),
+ }
+
+ struct TestAccesses {
+ preserve_rsp: bool,
+ used_regs: [bool; 16],
+ expected_reg: Vec<ExpectedRegAccess>,
+ expected_mem: Vec<ExpectedMemAccess>,
+ }
+
+ struct AccessTestCtx<'a> {
+ regs: &'a mut kvm_regs,
+ accs: TestAccesses,
+ }
+
+ impl<'a> AccessTestCtx<'a> {
+ fn into_expectations(self) -> (Vec<ExpectedRegAccess>, Vec<ExpectedMemAccess>) {
+ let TestAccesses {
+ expected_reg,
+ expected_mem,
+ ..
+ } = self.accs;
+ (expected_reg, expected_mem)
+ }
+
+ // randomize initial test VM state as described by this AccessTestCtx; it's possible that
+ // after this point a `visit_accesses` pass will reset some of these registers to different
+ // values as they may be used for address calculations.
+ //
+ // along the way, deposit any registers this test ctx declares to be "used" as an explicit
+ // entry in `cares`. later testing should not permute these registers, even if they are not
+ // directly declared as read by any operand, implicit or explicit.
+ fn randomize_unused(&mut self, cares: &mut Vec<RegSpec>) {
+ let mut rng = rand::rng();
+
+ if !self.accs.used_regs[0] {
+ self.regs.rax = rng.next_u64();
+ } else {
+ cares.push(RegSpec::rax());
+ }
+ if !self.accs.used_regs[1] {
+ self.regs.rbx = rng.next_u64();
+ } else {
+ cares.push(RegSpec::rbx());
+ }
+ if !self.accs.used_regs[2] {
+ self.regs.rcx = rng.next_u64();
+ } else {
+ cares.push(RegSpec::rcx());
+ }
+ if !self.accs.used_regs[3] {
+ self.regs.rdx = rng.next_u64();
+ } else {
+ cares.push(RegSpec::rdx());
+ }
+ if !self.accs.used_regs[4] {
+ self.regs.rsi = rng.next_u64();
+ } else {
+ cares.push(RegSpec::rsi());
+ }
+ if !self.accs.used_regs[5] {
+ self.regs.rdi = rng.next_u64();
+ } else {
+ cares.push(RegSpec::rdi());
+ }
+ if !self.accs.preserve_rsp {
+ if !self.accs.used_regs[6] {
+ self.regs.rsp = rng.next_u64();
+ } else {
+ cares.push(RegSpec::rsp());
+ }
+ }
+ if !self.accs.used_regs[7] {
+ self.regs.rbp = rng.next_u64();
+ } else {
+ cares.push(RegSpec::rbp());
+ }
+
+ self.regs.r8 = rng.next_u64();
+ self.regs.r9 = rng.next_u64();
+ self.regs.r10 = rng.next_u64();
+ self.regs.r11 = rng.next_u64();
+ self.regs.r12 = rng.next_u64();
+ self.regs.r13 = rng.next_u64();
+ self.regs.r14 = rng.next_u64();
+ self.regs.r15 = rng.next_u64();
+ }
+ }
+
+ use yaxpeax_arch::AddressBase;
+ use yaxpeax_x86::long_mode::{RegSpec, behavior::AccessVisitor};
+ use yaxpeax_x86::long_mode::register_class;
+
+ impl<'a> AccessVisitor for AccessTestCtx<'a> {
+ fn register_read(&mut self, reg: RegSpec) {
+ self.accs.expected_reg.push(ExpectedRegAccess {
+ write: false,
+ reg,
+ });
+ }
+ fn register_write(&mut self, reg: RegSpec) {
+ self.accs.expected_reg.push(ExpectedRegAccess {
+ write: true,
+ reg,
+ });
+ }
+ fn get_register(&mut self, reg: RegSpec) -> Option<u64> {
+ self.register_read(reg);
+
+ let cls = reg.class();
+ match cls {
+ register_class::B | register_class::W | register_class::D | register_class::Q => {
+ static KVM_REG_LUT: [usize; 16] = [
+ 0, 2, 3, 1, 6, 7, 4, 5,
+ 8, 9, 10, 11, 12, 13, 14, 15,
+ ];
+ let kvm_reg_nr = KVM_REG_LUT[reg.num() as usize];
+
+ // some ridiculous circumstances require us to not permute rsp, even
+ // though we *would* set it to a mapped address.
+ let allocated = self.accs.used_regs[reg.num() as usize] ||
+ (reg.num() == RegSpec::rsp().num() && self.accs.preserve_rsp);
+
+ if allocated {
+ let value = unsafe {
+ (self.regs as *mut _ as *mut u64).offset(kvm_reg_nr as isize).read()
+ };
+ Some(value)
+ } else {
+ // register value allocation is done .. carefully.
+ //
+ // see the comment on `map_test_mem` about why these numbers make any
+ // sense.
+ let value = 0x1_0000_0000 + (kvm_reg_nr as u64 + 1) * 0x0200;
+ unsafe {
+ (self.regs as *mut _ as *mut u64).offset(kvm_reg_nr as isize).write(value);
+ }
+ self.accs.used_regs[reg.num() as usize] = true;
+ Some(value)
+ }
+ }
+ other => {
+ panic!("unexpected VcpuExit: {:?}", other);
+ }
+ }
+ }
+ fn memory_read(&mut self, address: Option<u64>, size: u32) {
+ let acc = ExpectedMemAccess {
+ write: false,
+ addr: address.expect("can compute expected address"),
+ size,
+ };
+ self.accs.expected_mem.push(acc);
+ }
+ fn memory_write(&mut self, address: Option<u64>, size: u32) {
+ let acc = ExpectedMemAccess {
+ write: true,
+ addr: address.expect("can compute expected address"),
+ size,
+ };
+ self.accs.expected_mem.push(acc);
+ }
+ }
+
+ fn dump_regs(regs: &kvm_regs) {
+ eprintln!("rip flags ");
+ eprintln!("{:016x} {:016x}", regs.rip, regs.rflags);
+ eprintln!("rax rcx rdx rbx");
+ eprintln!("{:016x} {:016x} {:016x} {:016x}", regs.rax, regs.rcx, regs.rdx, regs.rbx);
+ eprintln!("rsp rbp rsi rdi");
+ eprintln!("{:016x} {:016x} {:016x} {:016x}", regs.rsp, regs.rbp, regs.rsi, regs.rdi);
+ eprintln!("r8 r9 r10 r11");
+ eprintln!("{:016x} {:016x} {:016x} {:016x}", regs.r8, regs.r9, regs.r10, regs.r11);
+ eprintln!("r12 r13 r14 r15");
+ eprintln!("{:016x} {:016x} {:016x} {:016x}", regs.r12, regs.r13, regs.r14, regs.r15);
+ }
+
+ fn run_with_mem_checks(vm: &mut Vm, expected_end: u64, mem_patches: &[MemPatch]) -> Result<(), Exception> {
+ for chunk in 0..=8 {
+ let base = TEST_MEM_BASE.0 + 0x1_0000_0000 * chunk;
+ vm.mem_slice_mut(GuestAddress(base), TEST_MEM_SIZE).fill(0xaa);
+ }
+ // test environments may require constants in memory at known locations (say, in support of
+ // an LGDT test). apply those patches now that we've initialized all extra memory.
+ for patch in mem_patches {
+ let slice = vm.mem_slice_mut(GuestAddress(patch.addr), patch.bytes.len() as u64);
+ slice.copy_from_slice(patch.bytes.as_slice());
+ }
+ let mut exits = 0;
+ let end_pc = loop {
+// eprintln!("about to run! here's some state:");
+// let regs = vm.get_regs().unwrap();
+// dump_regs(&regs);
+// let sregs = vm.get_sregs().unwrap();
+// eprintln!("sregs: {:?}", sregs);
+ let exit = vm.run().expect("can run vcpu");
+ exits += 1;
+ match exit {
+ VcpuExit::MmioRead { .. } |
+ VcpuExit::MmioWrite { .. } => {
+ panic!("shoud not be mmio accesses anymore");
+ }
+ VcpuExit::Debug { pc, info: _ } => {
+ break pc;
+ }
+ VcpuExit::Exception { nr } => {
+ return Err(Exception::vector(nr));
+ }
+ VcpuExit::Hlt => {
+ let regs = vm.get_regs().unwrap();
+ break regs.rip;
+ }
+ other => {
+ eprintln!("unhandled exit: {:?} ... after {}", other, exits);
+ let regs = vm.get_regs().unwrap();
+ eprintln!("regs: {:?}", regs);
+// let sregs = vm.get_sregs().unwrap();
+// eprintln!("sregs: {:?}", sregs);
+ panic!("stop");
+ }
+ }
+ };
+
+ if end_pc != expected_end - 1 && end_pc != expected_end {
+ panic!("single-step ended at {:08x}, expected {:08x}", end_pc, expected_end);
+ }
+
+ /*
+ if !unexpected_mem.is_empty() {
+ eprintln!("memory access surprise!");
+ if expected_mem.is_empty() {
+ eprintln!("expected none");
+ } else {
+ eprintln!("expected:");
+ for acc in expected_mem.iter() {
+ let rw = if acc.write { "write:" } else { " read:" };
+ eprintln!(" {} {} bytes at {:08x}", rw, acc.size, acc.addr);
+ }
+ }
+ eprintln!("unexpected:");
+ for (write, addr, size) in unexpected_mem {
+ let rw = if write { "write:" } else { " read:" };
+ eprintln!(" {} {} bytes at {:08x}", rw, size, addr);
+ }
+ panic!("stop");
+ }
+ */
+ return Ok(());
+ }
+
+ fn check_contains(larger: RegSpec, smaller: RegSpec) -> bool {
+ if larger == smaller {
+ return true;
+ } else if larger.class() == smaller.class() {
+ // no registers in the same class alias
+ return false;
+ } else {
+ match (larger.class(), smaller.class()) {
+ (register_class::Q, register_class::Q) |
+ (register_class::Q, register_class::D) |
+ (register_class::Q, register_class::W) |
+ (register_class::Q, register_class::RB) |
+ (register_class::D, register_class::D) |
+ (register_class::D, register_class::W) |
+ (register_class::D, register_class::RB) |
+ (register_class::W, register_class::W) |
+ (register_class::W, register_class::RB) |
+ (register_class::RB, register_class::RB) => {
+ larger.num() == smaller.num()
+ }
+ (register_class::Q, register_class::B) |
+ (register_class::D, register_class::B) |
+ (register_class::W, register_class::B) => {
+ // top bit selects high/low half of *x registers, so mask it and compare
+ smaller.num() & 0b11 == larger.num()
+ }
+ (register_class::RFLAGS, _) |
+ (_, register_class::RFLAGS) => {
+ false
+ }
+ (register_class::RIP, _) |
+ (_, register_class::RIP) => {
+ false
+ }
+ (register_class::S, _) |
+ (_, register_class::S) => {
+ false
+ }
+ (l, s) => {
+ panic!("unhandled register-contains test: {:?}/{:?}", l, s);
+ }
+ }
+ }
+ }
+ fn reg_mask(reg: RegSpec) -> u64 {
+ match reg.class() {
+ register_class::B => {
+ // non-rex byte regs are al, cl, dl, bl, ah, ch, dh, bh
+ let mask = if reg.num() < 4 {
+ 0xff
+ } else {
+ 0xff00
+ };
+ mask
+ },
+ // but rex byte regs are all low-byte
+ register_class::RB => 0xff,
+ register_class::W => 0xffff,
+ // x86_64 zero-extends 32-bit writes to 64-bit, so writes to "32-bit" registers still
+ // are fully-clobbers.
+ register_class::D => 0xffffffff_ffffffff,
+ register_class::Q => 0xffffffff_ffffffff,
+ register_class::RFLAGS => 0xffffffff_ffffffff,
+ register_class::S => 0xffff,
+ other => {
+ panic!("unhandled register class: {:?}", other);
+ }
+ }
+ }
+
+ fn verify_seg(
+ unexpected_regs: &mut Vec<UnexpectedRegChange>, expected_regs: &[ExpectedRegAccess],
+ changed_reg: RegSpec, before: u16, after: u16,
+ ) {
+ verify_reg(unexpected_regs, expected_regs, changed_reg, before as u64, after as u64)
+ }
+
+ fn verify_reg(
+ unexpected_regs: &mut Vec<UnexpectedRegChange>, expected_regs: &[ExpectedRegAccess],
+ changed_reg: RegSpec, before: u64, after: u64,
+ ) {
+ // the same GPR may appear by different names in `expected_regs`, like as in `xchg ah, al`.
+ // so, compute a diff here and poke out bits as the diff can be accounted for.
+ let mut diff = before ^ after;
+ if diff != 0 {
+ // could be a write. full write? maybe!
+ for e in expected_regs.iter() {
+ if !e.write {
+ continue;
+ }
+
+ if !check_contains(changed_reg, e.reg) {
+ continue;
+ }
+
+ diff &= !reg_mask(e.reg);
+ }
+
+ if diff != 0 {
+ unexpected_regs.push(UnexpectedRegChange {
+ reg: changed_reg,
+ before,
+ after,
+ });
+ }
+ }
+ }
+
+ fn verify_dontcares(written_regs: &[RegSpec], initial_after_regs: &kvm_regs, now_after_regs: &kvm_regs) {
+ let mut bad = false;
+
+ for reg in written_regs.iter() {
+ assert_eq!(reg.class(), register_class::Q);
+
+ static KVM_REG_LUT: [usize; 16] = [
+ 0, 2, 3, 1, 6, 7, 4, 5,
+ 8, 9, 10, 11, 12, 13, 14, 15,
+ ];
+ let kvm_reg_nr = KVM_REG_LUT[reg.num() as usize];
+
+ let initial_after = unsafe {
+ (initial_after_regs as *const _ as *const u64).offset(kvm_reg_nr as isize).read()
+ };
+
+ let now_after = unsafe {
+ (now_after_regs as *const _ as *const u64).offset(kvm_reg_nr as isize).read()
+ };
+
+ if initial_after != now_after {
+ #[cfg(feature = "fmt")]
+ eprintln!("register {} changed after permuting dontcares: {:016x} => {:016x}",
+ reg, initial_after, now_after);
+ bad = true;
+ }
+ }
+
+ if bad {
+// eprintln!("before:");
+// dump_regs(&before_regs);
+ eprintln!("after:");
+ dump_regs(&now_after_regs);
+ eprintln!("initial_after:");
+ dump_regs(&initial_after_regs);
+ panic!("cared about dontcares");
+ }
+ }
+
+ fn compute_dontcares(vm: &Vm, accesses: &[ExpectedRegAccess]) -> Vec<RegSpec> {
+ // use a bitmap for dontcares, mask out bits as registers are seen to be read.
+ let mut reg_bitmap: u32 = 0xffffffff;
+
+ fn reg_to_gpr(reg: RegSpec) -> Option<u8> {
+ match reg.class() {
+ register_class::Q |
+ register_class::D |
+ register_class::W |
+ register_class::RB => {
+ Some(reg.num())
+ }
+ register_class::B => {
+ Some(reg.num() & 0b11)
+ }
+ _ => {
+ None
+ }
+ }
+ }
+
+ if vm.idt_configured() {
+ reg_bitmap &= !(1 << (RegSpec::rsp().num()));
+ }
+
+ for acc in accesses.iter() {
+ if acc.write && acc.reg.class().width() >= 4 {
+ // TODO: if a write goes to a subset of a GPR, the dontcare part is *only* the
+ // written part. currently dontcares are reported as the full width, so
+ // subsequent steps permute the non-written part and trip over it in
+ // verify_dontcares.
+ //
+ // for now, only dontcare if the written register would be a full write (4
+ // bytes, zero-extended to 8, or actually a write to all 8 bytes).
+ continue;
+ }
+
+ if let Some(gpr_num) = reg_to_gpr(acc.reg) {
+ reg_bitmap &= !(1 << gpr_num);
+ }
+ }
+
+ let mut regs = Vec::new();
+
+ for i in 0..16 {
+ if reg_bitmap & (1 << i) != 0 {
+ regs.push(RegSpec::q(i));
+ }
+ }
+
+ regs
+ }
+
+ fn compute_writes(accesses: &[ExpectedRegAccess]) -> Vec<RegSpec> {
+ // same as dontcares, isk
+ let mut reg_bitmap: u32 = 0x00000000;
+
+ fn reg_to_gpr(reg: RegSpec) -> Option<u8> {
+ match reg.class() {
+ register_class::Q |
+ register_class::D |
+ register_class::W |
+ register_class::RB => {
+ Some(reg.num())
+ }
+ register_class::B => {
+ Some(reg.num() & 0b11)
+ }
+ _ => {
+ None
+ }
+ }
+ }
+
+ for acc in accesses.iter() {
+ if !acc.write {
+ continue;
+ }
+
+ if let Some(gpr_num) = reg_to_gpr(acc.reg) {
+ reg_bitmap |= 1 << gpr_num;
+ }
+ }
+
+ let mut regs = Vec::new();
+
+ for i in 0..16 {
+ if reg_bitmap & (1 << i) != 0 {
+ regs.push(RegSpec::q(i));
+ }
+ }
+
+ regs
+ }
+
+ fn permute_dontcares(dontcare_regs: &[RegSpec], regs: &mut kvm_regs) {
+ let mut rng = rand::rng();
+
+ for reg in dontcare_regs {
+ assert_eq!(reg.class(), register_class::Q);
+
+ static KVM_REG_LUT: [usize; 16] = [
+ 0, 2, 3, 1, 6, 7, 4, 5,
+ 8, 9, 10, 11, 12, 13, 14, 15,
+ ];
+ let kvm_reg_nr = KVM_REG_LUT[reg.num() as usize];
+ let rand = rng.next_u64();
+ unsafe {
+ (regs as *mut _ as *mut u64).offset(kvm_reg_nr as isize).write(rand);
+ }
+ }
+ }
+
+ // TODO: this needs some rethinking. see commented-out caller.
+ #[allow(dead_code)]
+ fn permute_memread(expected_mem: &[ExpectedMemAccess], vm: &mut Vm) {
+ for acc in expected_mem.iter() {
+ if acc.write {
+ continue;
+ }
+
+ let mut buf = vec![0; acc.size as usize];
+ let mut rng = rand::rng();
+ rng.fill(&mut buf);
+
+ if acc.addr >= 0x1_0000_0000 {
+ vm.write_mem(GuestAddress(acc.addr), buf.as_slice());
+ } else {
+ // check we're not going to "permute" page tables or something.
+ // instruction text might get clobbered, which would be Weird, but..
+ assert!(acc.addr > vm.page_table_addr().0 + 2 * 0x1000);
+ vm.write_mem(GuestAddress(acc.addr), buf.as_slice());
+ }
+ }
+ }
+
+ fn verify_mem_changes(
+ expected_mem: &[ExpectedMemAccess],
+ vm: &mut Vm,
+ ) {
+ // test the expected writes by process of elimination: reset any expected-to-be-written
+ // areas to the initial pattern. then, anything in test memory that is not the default
+ // pattern must have been an unexpected write.
+ for acc in expected_mem {
+ if !acc.write {
+ continue;
+ }
+
+ let slice = vm.mem_slice_mut(GuestAddress(acc.addr), acc.size as u64);
+ slice.fill(0xaa);
+ }
+
+ struct MemoryDiff {
+ addr: GuestAddress,
+ bytes: Vec<u8>,
+ }
+
+ use std::fmt;
+
+ impl fmt::Display for MemoryDiff {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ write!(f, "diff at 0x{:08x}: ", self.addr.0)?;
+ for b in self.bytes.iter() {
+ write!(f, "{:02x}", b)?;
+ }
+ Ok(())
+ }
+ }
+
+ let mut unexpected_acc = Vec::new();
+ let mut current_diff: Option<MemoryDiff> = None;
+
+ for mem_hunk in 0..=8 {
+ let base = GuestAddress(TEST_MEM_BASE.0 * (mem_hunk + 1));
+ let test_mem = vm.mem_slice(base, TEST_MEM_SIZE);
+ for i in 0..test_mem.len() {
+ if test_mem[i] != 0xaa {
+ if let Some(mut diff) = current_diff.take() {
+ const CHUNK_SIZE: u64 = 128 * 1024;
+
+ let prev_diff_start = diff.addr.0 % CHUNK_SIZE;
+ let prev_diff_tail = prev_diff_start + diff.bytes.len() as u64;
+ let continuation = i as u64 == prev_diff_tail + 1;
+ if continuation {
+ diff.bytes.push(test_mem[i]);
+ } else {
+ unexpected_acc.push(diff);
+
+ let guest_addr = (mem_hunk + 1) * 0x1_0000_0000 + i as u64;
+ current_diff = Some(MemoryDiff {
+ addr: GuestAddress(guest_addr as u64),
+ bytes: vec![test_mem[i]],
+ });
+ }
+ } else {
+ let guest_addr = (mem_hunk + 1) * 0x1_0000_0000 + i as u64;
+ current_diff = Some(MemoryDiff {
+ addr: GuestAddress(guest_addr as u64),
+ bytes: vec![test_mem[i]],
+ });
+ }
+ }
+ }
+
+ if let Some(diff) = current_diff.take() {
+ unexpected_acc.push(diff);
+ }
+ }
+
+ if !unexpected_acc.is_empty() {
+ for diff in unexpected_acc {
+ eprintln!("{}", diff);
+ }
+ let regs = vm.get_regs().unwrap();
+ dump_regs(&regs);
+ panic!("unexpected memory accesses!");
+ }
+ }
+
+ fn verify_reg_changes(
+ expected_regs: &[ExpectedRegAccess],
+ before_regs: &kvm_regs, after_regs: &kvm_regs,
+ before_sregs: &kvm_sregs, after_sregs: &kvm_sregs
+ ) {
+ let mut unexpected_regs = Vec::new();
+
+ verify_reg(&mut unexpected_regs, &expected_regs, RegSpec::rax(), before_regs.rax, after_regs.rax);
+ verify_reg(&mut unexpected_regs, &expected_regs, RegSpec::rcx(), before_regs.rcx, after_regs.rcx);
+ verify_reg(&mut unexpected_regs, &expected_regs, RegSpec::rdx(), before_regs.rdx, after_regs.rdx);
+ verify_reg(&mut unexpected_regs, &expected_regs, RegSpec::rbx(), before_regs.rbx, after_regs.rbx);
+ verify_reg(&mut unexpected_regs, &expected_regs, RegSpec::rsp(), before_regs.rsp, after_regs.rsp);
+ verify_reg(&mut unexpected_regs, &expected_regs, RegSpec::rbp(), before_regs.rbp, after_regs.rbp);
+ verify_reg(&mut unexpected_regs, &expected_regs, RegSpec::rsi(), before_regs.rsi, after_regs.rsi);
+ verify_reg(&mut unexpected_regs, &expected_regs, RegSpec::rdi(), before_regs.rdi, after_regs.rdi);
+ verify_reg(&mut unexpected_regs, &expected_regs, RegSpec::r8(), before_regs.r8, after_regs.r8);
+ verify_reg(&mut unexpected_regs, &expected_regs, RegSpec::r9(), before_regs.r9, after_regs.r9);
+ verify_reg(&mut unexpected_regs, &expected_regs, RegSpec::r10(), before_regs.r10, after_regs.r10);
+ verify_reg(&mut unexpected_regs, &expected_regs, RegSpec::r11(), before_regs.r11, after_regs.r11);
+ verify_reg(&mut unexpected_regs, &expected_regs, RegSpec::r12(), before_regs.r12, after_regs.r12);
+ verify_reg(&mut unexpected_regs, &expected_regs, RegSpec::r13(), before_regs.r13, after_regs.r13);
+ verify_reg(&mut unexpected_regs, &expected_regs, RegSpec::r14(), before_regs.r14, after_regs.r14);
+ verify_reg(&mut unexpected_regs, &expected_regs, RegSpec::r15(), before_regs.r15, after_regs.r15);
+ verify_reg(&mut unexpected_regs, &expected_regs, RegSpec::rflags(), before_regs.rflags, after_regs.rflags);
+
+ verify_seg(&mut unexpected_regs, &expected_regs, RegSpec::cs(), before_sregs.cs.selector, after_sregs.cs.selector);
+ verify_seg(&mut unexpected_regs, &expected_regs, RegSpec::ds(), before_sregs.ds.selector, after_sregs.ds.selector);
+ verify_seg(&mut unexpected_regs, &expected_regs, RegSpec::es(), before_sregs.es.selector, after_sregs.es.selector);
+ verify_seg(&mut unexpected_regs, &expected_regs, RegSpec::fs(), before_sregs.fs.selector, after_sregs.fs.selector);
+ verify_seg(&mut unexpected_regs, &expected_regs, RegSpec::gs(), before_sregs.gs.selector, after_sregs.gs.selector);
+ verify_seg(&mut unexpected_regs, &expected_regs, RegSpec::ss(), before_sregs.ss.selector, after_sregs.ss.selector);
+
+ verify_reg(&mut unexpected_regs, &expected_regs, RegSpec::cr0(), before_sregs.cr0, after_sregs.cr0);
+ verify_reg(&mut unexpected_regs, &expected_regs, RegSpec::cr2(), before_sregs.cr2, after_sregs.cr2);
+ verify_reg(&mut unexpected_regs, &expected_regs, RegSpec::cr3(), before_sregs.cr3, after_sregs.cr3);
+ verify_reg(&mut unexpected_regs, &expected_regs, RegSpec::cr4(), before_sregs.cr4, after_sregs.cr4);
+ verify_reg(&mut unexpected_regs, &expected_regs, RegSpec::cr8(), before_sregs.cr8, after_sregs.cr8);
+
+ if !unexpected_regs.is_empty() {
+ eprintln!("unexpected reg changes:");
+ for change in unexpected_regs {
+ #[cfg(feature = "fmt")]
+ eprintln!(" {}: {:08x} -> {:08x}", change.reg.name(), change.before, change.after);
+ }
+ panic!("stop");
+ }
+ }
+
+ // check the side effects of the instruction that `regs.rip` points to. the side effects are
+ // enumerated across `expected_reg` and `expected_mem`. if this instruction instead raises an
+ // exception, return that instead.
+ //
+ // TODO: it's possible that this instruction permuts either the instruction bytes or vCPU
+ // control structures (GDT, IDT, or page tables). these could be made read-only, but then we'd
+ // need to verify that these structures are not modified via Weird Different Mapping or
+ // whatever. such a mapping shouldn't exist anyway. but making these read-only also implies
+ // moving the stack elsewhere, and the stack would have to be zeroed to not introduce Weirdness
+ // across permutations too.
+ fn check_side_effects(
+ vm: &mut Vm, regs: &kvm_regs, sregs: &kvm_sregs,
+ mem_patches: &[MemPatch],
+ expected_end: u64, expected_reg: &[ExpectedRegAccess], expected_mem: &[ExpectedMemAccess]
+ ) -> Result<(kvm_regs, kvm_sregs), Exception> {
+ run_with_mem_checks(vm, expected_end, mem_patches)?;
+
+ let after_regs = vm.get_regs().unwrap();
+ let after_sregs = vm.get_sregs().unwrap();
+
+ verify_reg_changes(&expected_reg, &regs, &after_regs, &sregs, &after_sregs);
+ verify_mem_changes(&expected_mem, vm);
+
+ Ok((after_regs, after_sregs))
+ }
+
+ // run the VM a few times permuting the "dontcare" registers each time and checking that we
+ // really did not care about them. "4" steps is of course arbitrary, but makes for some kind of
+ // confidence about flag registers in particular, probably.
+ fn test_dontcares(
+ vm: &mut Vm, regs: &mut kvm_regs, sregs: &kvm_sregs,
+ mem_patches: &[MemPatch],
+ expected_end: u64, expected_reg: &[ExpectedRegAccess], expected_mem: &[ExpectedMemAccess],
+ dontcare_regs: &[RegSpec], written_regs: &[RegSpec],
+ first_after_regs: &kvm_regs, _first_after_sregs: &kvm_sregs
+ ) -> Result<(), Exception> {
+ for _ in 0..4 {
+ permute_dontcares(dontcare_regs, regs);
+ // TODO: really need to permute memory dont-care here, rather than reads. it'd probably
+ // be sufficient to pick any other default pattern than 0xaa and pass that selected
+ // pattern to verify..?
+ // permute_memread(expected_mem, vm);
+
+ vm.set_regs(&regs).unwrap();
+
+ let (after_regs, _after_sregs) = check_side_effects(
+ vm, &regs, &sregs,
+ mem_patches,
+ expected_end, expected_reg, expected_mem
+ )?;
+
+ verify_dontcares(written_regs, &first_after_regs, &after_regs);
+ }
+
+ Ok(())
+ }
+
+ fn check_behavior(vm: &mut Vm, inst: &[u8]) -> Result<(), CheckErr> {
+ check_behavior_with_regs(vm, inst, None, &[])
+ }
+
+ // reg_preserves: [bool; 16]
+
+ // `reg_preserves` declares a set of registers, numbered by their *Linux KVM API number*, as in
+ // the position in `kvm_regs`, that must be preserved by the test.
+ fn check_behavior_with_regs(vm: &mut Vm, inst: &[u8], expect_accs: Option<TestAccesses>, mem_patches: &[MemPatch]) -> Result<(), CheckErr> {
+ let decoded = yaxpeax_x86::long_mode::InstDecoder::default()
+ .decode_slice(inst).expect("can decode");
+
+ let mut printed = false;
+ eprint!("checking behavior of ");
+ for b in inst.iter() {
+ if printed {
+ eprint!(" ");
+ }
+ eprint!("{:02x}", b);
+ printed = true;
+ }
+ #[cfg(feature = "fmt")]
+ eprint!(": {}", decoded);
+ eprint!("\n");
+
+ let mut insts = inst.to_vec();
+ // cap things off with a `hlt` to work around single-step sometimes .. not? see comment on
+ // set_single_step. this ensures that even if single-stepping doesn't do the needful, the
+ // next address _will_ get the vCPU back out to us.
+ //
+ // this obviously doesn't work if code is overwritten (so really [TODO] the first page
+ // should be made non-writable), and doesn't work if the one executed instruction is a
+ // call, jump, etc. in those cases the instruction doesn't rmw memory .. .except for
+ // call/ret, where the `rsp` access might. so we might have to just have to skip them?
+ //
+ // alternatively, probably should set up the IDT such that there's a handler for the
+ // exception raised by `TF` that just executes hlt. then everything other than popf will
+ // work out of the box and popf can be caught by kvm single-stepping.
+ insts.push(0xf4);
+ use yaxpeax_arch::LengthedInstruction;
+ assert_eq!(insts.len(), 0.wrapping_offset(decoded.len()) as usize + 1);
+
+ let behavior = decoded.behavior();
+
+ // TODO: this does an infinite loop when run??
+ if decoded.opcode() == long_mode::Opcode::FLDCW {
+ return Ok(());
+ }
+
+ let sregs = vm.get_sregs().unwrap();
+ let mut regs = vm.get_regs().unwrap();
+ // vm.set_single_step(true).expect("can enable single-step");
+ vm.program(insts.as_slice(), &mut regs);
+
+ // a set of registers whose values we are directed to care about. these are subtracted from
+ // dontcares, later.
+ let mut cares = Vec::new();
+
+ let ctx = match expect_accs {
+ Some(mut accs) => {
+ accs.preserve_rsp = vm.idt_configured();
+ let mut ctx = AccessTestCtx {
+ regs: &mut regs,
+ accs,
+ };
+ ctx.randomize_unused(&mut cares);
+ ctx
+ }
+ None => {
+ let accs = TestAccesses {
+ // if an interrupt handler is initialized with rsp pointing to addresses that cause
+ // MMIO exits the vcpu ends up in a loop doing nothing particularly interesting
+ // (seemingly in a loop trying to raise #UD after resetting?). this is a Linux issue
+ // i'm not tracking down right now. instead, if the IDT is initialized then keep the
+ // rsp pointed somewhere "normal" so that exceptions still work right.
+ //
+ // to reproduce this issue, set this to `false` unconditionally, then run
+ // `kvm_verify_popmem`. it will infinite loop in the kernel and you'll see
+ // x86_decode_emulated_instruction failing over and over and over and ...
+ preserve_rsp: vm.idt_configured(),
+ used_regs: [false; 16],
+ expected_reg: Vec::new(),
+ expected_mem: Vec::new(),
+ };
+ let mut ctx = AccessTestCtx {
+ regs: &mut regs,
+ accs,
+ };
+ ctx.randomize_unused(&mut cares);
+ behavior.visit_accesses(&mut ctx).map_err(|e| CheckErr::ComplexOp(e))?;
+
+ ctx
+ }
+ };
+
+ let (expected_reg, mut expected_mem) = ctx.into_expectations();
+ for patch in mem_patches.iter() {
+ expected_mem.push(ExpectedMemAccess {
+ addr: patch.addr,
+ size: patch.bytes.len() as u32,
+ write: true,
+ });
+ }
+
+ let mut dontcare_regs = compute_dontcares(&vm, &expected_reg);
+ dontcare_regs.retain(|reg| {
+ !cares.iter().any(|care| check_contains(*care, *reg))
+ });
+ let written_regs = compute_writes(&expected_reg);
+
+ permute_dontcares(dontcare_regs.as_slice(), &mut regs);
+
+ eprintln!("setting regs to: {:?}", regs);
+ vm.set_regs(&regs).unwrap();
+
+ let expected_end = regs.rip + insts.len() as u64;
+
+ let (after_regs, after_sregs) = match check_side_effects(
+ vm, &regs, &sregs,
+ mem_patches,
+ expected_end, &expected_reg, &expected_mem
+ ) {
+ Ok((a, b)) => (a, b),
+ Err(other) => {
+ let vm_regs = vm.get_regs().unwrap();
+ let vm_sregs = vm.get_sregs().unwrap();
+ let mut prev_rip = [0u8; 8];
+ vm.read_mem(GuestAddress(vm_regs.rsp + 8), &mut prev_rip[..]);
+ let mut buf = [0u8; 8];
+ vm.read_mem(GuestAddress(vm_regs.rsp), &mut buf[..]);
+ if other == Exception::PF {
+ eprintln!(
+ "error code: {:#08x} accessing {:016x} @ rip={:#016x} (cr3={:016x})",
+ u64::from_le_bytes(buf), vm_sregs.cr2,
+ u64::from_le_bytes(prev_rip), vm_sregs.cr3
+ );
+ let mut pdpt = [0u8; 4096];
+ vm.read_mem(vm.page_tables().pdpt_addr(), &mut pdpt[..]);
+ eprintln!("pdpt: {:x?}", &pdpt[..8]);
+ } else if other == Exception::GP {
+ if decoded.opcode() == long_mode::Opcode::MOV {
+ // TODO: should be in the exception list
+ if let long_mode::Operand::Register { reg } = decoded.operand(0) {
+ if reg.class() == long_mode::register_class::S {
+ // mov to segment selector can #GP if the selector is invalid:
+ // > If the DS, ES, FS, or GS register is being loaded and the
+ // > segment pointed to is not a data or readable code segment.
+ return Ok(());
+ }
+ }
+ }
+ }
+ dump_regs(&vm_regs);
+ #[cfg(feature = "fmt")]
+ {
+ panic!("TODO: handle exceptions ({:?})", other);
+ }
+ #[cfg(not(feature = "fmt"))]
+ {
+ let _ = other;
+ panic!("TODO: handle exceptions");
+ }
+ }
+ };
+
+ let res = test_dontcares(
+ vm, &mut regs, &sregs,
+ mem_patches,
+ expected_end, expected_reg.as_slice(), expected_mem.as_slice(),
+ dontcare_regs.as_slice(), written_regs.as_slice(),
+ &after_regs, &after_sregs
+ );
+
+ match res {
+ Ok(()) => {
+ return Ok(());
+ }
+ Err(Exception::PF) => {
+ // TODO: probably should handle `#PF` more precisely?
+ return Ok(());
+ }
+ Err(other) => {
+ #[cfg(feature = "fmt")]
+ {
+ panic!("TODO: handle exceptions ({:?})", other);
+ }
+ #[cfg(not(feature = "fmt"))]
+ {
+ let _ = other;
+ panic!("TODO: handle exceptions");
+ }
+ }
+ }
+ }
+
+ const TEST_MEM_BASE: GuestAddress = GuestAddress(0x1_0000_0000);
+ const TEST_MEM_SIZE: u64 = 128 * 1024;
+
+ // we need to keep accesses from falling into mapped-but-not-backed regions
+ // of guest memory, so we don't get MMIO exits (which would just test
+ // Linux's x86 emulation). control structures are at in the low 1G (really 1M)
+ // of memory, which memory references under test should not touch.
+ //
+ // we'll limit displacements to 511 (arbitrary), which means that 512-byte
+ // increments of 1..16 can distinguish registers. given SIB addressing the
+ // highest address that can be formed is something like...
+ //
+ // > (1G + 15 * 512) + (1G + 16 * 512) * 8 + 512
+ //
+ // or just under 9G + 16k. that access *could* be a wide AVX-512 situation,
+ // so the highest byte addressed can be a few bytes later.
+ //
+ // this can be read as "the first 32k at each 1G may be accessed", but only
+ // GB boundaries at 1, 2, 3, 5, and 9 can be accessed in this way (non-SIB,
+ // then SIB with scale = 1, 2, 4, 8).
+ //
+ // while memory is Yikes Expensive, setting up 128k at each 1G offset that might be
+ // accessed is only 1M 128K, so that's what we'll do here.
+ fn map_test_mem(vm: &mut asmlinator::x86_64::Vm) {
+ let mut base = TEST_MEM_BASE.0;
+ for _ in 0..=8 {
+ vm.add_memory(GuestAddress(base), TEST_MEM_SIZE).expect("can add test mem region");
+ base += 0x1_0000_0000;
+ }
+ }
+
+ fn create_test_vm() -> asmlinator::x86_64::Vm {
+ let mut vm = Vm::create(1024 * 1024).expect("can create vm");
+
+ map_test_mem(&mut vm);
+ unsafe {
+ vm.configure_identity_paging(None);
+ }
+
+ vm
+ }
+
+ #[test]
+ fn kvm_verify_xor_reg_mem() {
+ let mut vm = create_test_vm();
+
+ // `xor rax, [rcx]`. this works. great!
+ let inst: &'static [u8] = &[0x33, 0x01];
+ check_behavior(&mut vm, inst).expect("behavior check is ok");
+
+ // `xor al, [rcx]`. also works. cool!
+ let inst: &'static [u8] = &[0x32, 0x01];
+ check_behavior(&mut vm, inst).expect("behavior check is ok");
+
+ // `xor [rcx], al`. this runs until the VM starts executing in MMIO space and
+ // VcpuExit::Shutdown. what.
+ let inst: &'static [u8] = &[0x30, 0x01];
+ check_behavior(&mut vm, inst).expect("behavior check is ok");
+ }
+
+ #[test]
+ fn kvm_verify_inc() {
+ let mut vm = create_test_vm();
+
+ // `inc eax`
+ let inst: &'static [u8] = &[0xff, 0xc0];
+ check_behavior(&mut vm, inst).expect("behavior check is ok");
+
+ // `inc dword [rax]`
+ let inst: &'static [u8] = &[0xff, 0x00];
+ check_behavior(&mut vm, inst).expect("behavior check is ok");
+ }
+
+ #[test]
+ fn kvm_verify_push() {
+ let mut vm = create_test_vm();
+
+ // `push rax`
+ let inst: &'static [u8] = &[0x50];
+ check_behavior(&mut vm, inst).expect("behavior check is ok");
+ }
+
+ #[test]
+ fn kvm_verify_popmem() {
+ let mut vm = create_test_vm();
+
+ // `pop [rax]`
+ let inst: &'static [u8] = &[0x8f, 0x00];
+ check_behavior(&mut vm, &inst[0..2]).expect("behavior check is ok");
+ }
+
+ #[test]
+ fn kvm_verify_ret() {
+ let mut vm = create_test_vm();
+
+ // `ret`
+ let inst: &'static [u8] = &[0xc3];
+ // TODO: set up ret test to return to some other address. check_behavior() doesn't tolerate
+ // this (yet).
+ vm.write_mem(vm.stack_addr(), &0xff001u64.to_le_bytes());
+ check_behavior(&mut vm, inst).expect("behavior check is ok");
+ }
+
+ /*
+ * TODO: this doesn't fit in the test framework really: `ins` will cause an I/O exit, which
+ * immediately fails the test.
+ #[test]
+ fn kvm_verify_ins() {
+ let mut vm = create_test_vm();
+
+ // `ins byte [rdi], dl`
+ let inst: &'static [u8] = &[0x6c];
+ check_behavior(&mut vm, inst).expect("behavior check is ok");
+ }
+ */
+
+ #[test]
+ fn behavior_verify_kvm() {
+ use yaxpeax_arch::{Decoder, U8Reader};
+ use yaxpeax_x86::long_mode::Instruction;
+
+ let mut vm = create_test_vm();
+ vm.set_single_step(true).expect("can enable single-step");
+
+ let decoder = host_decoder();
+ let mut buf = Instruction::default();
+ let initial_regs = vm.get_regs().unwrap();
+
+ for word in 0..u16::MAX {
+ let inst = word.to_le_bytes();
+ let mut reader = U8Reader::new(&inst);
+ if decoder.decode_into(&mut buf, &mut reader).is_ok() {
+ if [Opcode::FLDENV, Opcode::FNSTENV, Opcode::FRSTOR, Opcode::FNSAVE, Opcode::FNSTCW, Opcode::FNSTSW].contains(&buf.opcode()) {
+ // this needs a more targeted test
+ continue;
+ }
+
+ if [Opcode::INS, Opcode::MOVS, Opcode::OUTS, Opcode::LODS, Opcode::STOS, Opcode::CMPS, Opcode::SCAS].contains(&buf.opcode()) {
+ if buf.prefixes.rep_any() {
+ // `repnz cmps` will carry on for however long memory allows,
+ // `rep movs` runs `rcx`-many times, etc
+ continue;
+ }
+ }
+
+ if buf.opcode() == Opcode::RSM {
+ // SMM is kinda not our problem for now..
+ continue;
+ }
+
+ if buf.opcode() == Opcode::GETSEC {
+ // oh dear
+ continue;
+ }
+
+ if buf.opcode() == Opcode::RDPID {
+ // rdpid is a specialized rdmsr
+ continue;
+ }
+
+ if buf.opcode() == Opcode::RDTSC {
+ // the TSC keeps ticking so eax will change across runs and trip the
+ // "cared about dontcares" check.
+ continue;
+ }
+
+ if buf.opcode() == Opcode::RDPMC {
+ // reading a bogus PMC will just #GP so this needs a more specific test.
+ continue;
+ }
+
+ if buf.opcode() == Opcode::DIV || buf.opcode() == Opcode::IDIV {
+ // if the operand is in memory we're not currently permuting memory, so it
+ // might be zero and just #DE.
+ continue;
+ }
+
+ if buf.opcode() == Opcode::WRMSR || buf.opcode() == Opcode::RDMSR {
+ // TODO: ... okay come on.
+ continue;
+ }
+ if buf.opcode() == Opcode::RETURN {
+ // hard to handle generically here; see `verify_ret`.
+ continue;
+ }
+ if buf.opcode() == Opcode::LEAVE {
+ // TODO: trying to generically handle leave typically gets #SS from popping a
+ // non-canonical address. needs more specific test.
+ continue;
+ }
+ if buf.opcode() == Opcode::JMPF || buf.opcode() == Opcode::RETF || buf.opcode() == Opcode::CALLF {
+ // TODO: trying to is harder. needs more specific test.
+ continue;
+ }
+ if buf.opcode() == Opcode::INT {
+ // TODO: int is complex, but check_behavior() does not tolerate those yet
+ continue;
+ }
+ if buf.opcode() == Opcode::JMP || buf.opcode() == Opcode::CALL {
+ // TODO: needs more specific testing
+ continue;
+ }
+ if buf.opcode() == Opcode::JRCXZ || buf.opcode() == Opcode::LOOP || buf.opcode() == Opcode::LOOPZ || buf.opcode() == Opcode::LOOPNZ {
+ // TODO: also complex
+ continue;
+ }
+ if buf.opcode() == Opcode::IRET || buf.opcode() == Opcode::IRETD || buf.opcode() == Opcode::IRETQ {
+ // TODO: oh dear
+ continue;
+ }
+ if [Opcode::JO, Opcode::JNO, Opcode::JB, Opcode::JNB, Opcode::JZ, Opcode::JNZ, Opcode::JA, Opcode::JNA, Opcode::JS, Opcode::JNS, Opcode::JP, Opcode::JNP, Opcode::JL, Opcode::JGE, Opcode::JLE, Opcode::JG].contains(&buf.opcode()) {
+ // TODO: jmp-related tests that tolerate rip changing.
+ continue;
+ }
+ if [Opcode::SYSCALL, Opcode::SYSRET, Opcode::SYSENTER, Opcode::SYSEXIT].contains(&buf.opcode()) {
+ // TODO: syscall tests
+ continue;
+ }
+
+ if undef::OPCODES.contains(&buf.opcode()) {
+ // TODO: ud tests, etc
+ continue;
+ }
+
+ if buf.opcode() == Opcode::CLTS {
+ // what happens here, access 0xff000?
+ continue;
+ }
+ // some instructions may just be one byte, so figure out the length and only check
+ // that many bytes of instructions for specific behavior..
+ use yaxpeax_arch::LengthedInstruction;
+ let inst_len = 0.wrapping_offset(buf.len()) as usize;
+ if inst_len == 1 {
+ #[cfg(feature = "fmt")]
+ eprintln!("checking behavior of {:02x}: {}", inst[0], buf);
+ } else {
+ #[cfg(feature = "fmt")]
+ eprintln!("checking behavior of {:02x} {:02x}: {}", inst[0], inst[1], buf);
+ }
+ use yaxpeax_x86::long_mode::Opcode;
+ // mov es, word [rax]
+ // does an inf loop too...?
+ if [Opcode::INS, Opcode::OUTS, Opcode::IN, Opcode::OUT].contains(&buf.opcode()) {
+ #[cfg(feature = "fmt")]
+ eprintln!("skipping {}", buf.opcode());
+ continue;
+ }
+ vm.set_regs(&initial_regs).unwrap();
+ check_behavior(&mut vm, &inst[..inst_len]).expect("behavior check is ok");
+ }
+ }
+ }
+
+ #[test]
+ fn behavior_verify_kvm_0f_() {
+ use yaxpeax_arch::{Decoder, U8Reader};
+ use yaxpeax_x86::long_mode::Instruction;
+
+ let mut vm = create_test_vm();
+ vm.set_single_step(true).expect("can enable single-step");
+
+ let decoder = host_decoder();
+ let mut buf = Instruction::default();
+ let initial_regs = vm.get_regs().unwrap();
+
+ for opc in 0xf0..=u8::MAX {
+ for bits in 0..=0x7f {
+ let mut instlen = 0;
+ let suffix = bits & 3;
+ let prefix = (bits >> 2) & 3;
+ let imm = (bits >> 4) & 3;
+ let opers = (bits >> 6) & 1;
+
+ let mut bytes = [0; 6]; // 0x66, 0x0f, inst[0], inst[1]];
+
+ match prefix {
+ 0b00 => { },
+ 0b01 => {
+ bytes[instlen] = 0x66;
+ instlen += 1;
+ }
+ 0b10 => {
+ bytes[instlen] = 0xf2;
+ instlen += 1;
+ }
+ 0b11 => {
+ bytes[instlen] = 0xf3;
+ instlen += 1;
+ }
+ _ => {}
+ }
+
+ bytes[instlen] = 0x0f;
+ instlen += 1;
+
+ match suffix {
+ 0b00 => { },
+ 0b01 => {
+ bytes[instlen] = 0x38;
+ instlen += 1;
+ }
+ 0b10 => {
+ bytes[instlen] = 0x3a;
+ instlen += 1;
+ }
+ _ => {}
+ }
+
+ bytes[instlen] = opc;
+ bytes[instlen + 1] = if opers == 0 {
+ 0x01
+ } else {
+ 0xc1
+ };
+ instlen += 2;
+
+ match imm {
+ 0b00 => { },
+ 0b01 => {
+ bytes[instlen] = 0x00;
+ instlen += 1;
+ },
+ 0b10 => {
+ bytes[instlen] = 0x01;
+ instlen += 1;
+ },
+ _ => {
+ bytes[instlen] = 0xff;
+ instlen += 1;
+ },
+ }
+
+ let mut reader = U8Reader::new(&bytes);
+ if decoder.decode_into(&mut buf, &mut reader).is_ok() {
+ // two byte instructions were covered by `verify_kvm`, novel instructions are three
+ // bytes (or longer..?)
+ use yaxpeax_arch::LengthedInstruction;
+ let decoded_len = 0.wrapping_offset(buf.len()) as usize;
+ if decoded_len != instlen {
+ continue;
+ }
+
+ if not_generic(&buf) {
+ continue;
+ }
+
+ vm.set_regs(&initial_regs).unwrap();
+ check_behavior(&mut vm, &bytes[..instlen]).expect("behavior check is ok");
+ }
+ }
+ }
+ }
+
+ #[test]
+ fn behavior_verify_kvm_avx() {
+ use yaxpeax_arch::{Decoder, U8Reader};
+ use yaxpeax_x86::long_mode::Instruction;
+
+ let mut vm = create_test_vm();
+ vm.set_single_step(true).expect("can enable single-step");
+
+ let decoder = host_decoder();
+ let mut buf = Instruction::default();
+ let initial_regs = vm.get_regs().unwrap();
+
+ for opc in 0xf0..=255 {
+ for prefix in [0x00, 0x66, 0xf2, 0xf3] {
+ for map in 0..3 {
+ for operands in [0x01, 0xc1] {
+ let mut len = 0;
+ let mut bytes = [0; 8];
+
+ if prefix != 0x00 {
+ bytes[len] = prefix;
+ len += 1;
+ }
+
+ bytes[len] = 0x0f;
+ len += 1;
+
+ if map == 1 {
+ bytes[len] = 0x38;
+ len += 1;
+ } else if map == 2 {
+ bytes[len] = 0x3a;
+ len += 1;
+ }
+
+ bytes[len] = opc;
+ len += 1;
+
+ bytes[len] = operands;
+ len += 1;
+
+ let bytes = &bytes[..len];
+ let mut reader = U8Reader::new(&bytes);
+ if decoder.decode_into(&mut buf, &mut reader).is_ok() {
+ use yaxpeax_arch::LengthedInstruction;
+ let inst_len = 0.wrapping_offset(buf.len()) as usize;
+ if inst_len != bytes.len() {
+ continue;
+ }
+
+ if not_generic(&buf) {
+ continue;
+ }
+
+ vm.set_regs(&initial_regs).unwrap();
+ check_behavior(&mut vm, &bytes).expect("behavior check is ok");
+ }
+ }
+ }
+ }
+ }
+ }
+
+ #[test]
+ fn behavior_verify_kvm_avx_imm8() {
+ use yaxpeax_arch::{Decoder, U8Reader};
+ use yaxpeax_x86::long_mode::Instruction;
+
+ let mut vm = create_test_vm();
+ vm.set_single_step(true).expect("can enable single-step");
+
+ let decoder = host_decoder();
+ let mut buf = Instruction::default();
+ let initial_regs = vm.get_regs().unwrap();
+
+ for opc in 0x10..=255 {
+ for prefix in [0x00, 0x66, 0xf2, 0xf3] {
+ for map in 0..3 {
+ for imm in [0u8, 1u8, 2u8, 4u8, 8u8, 16u8, 32u8, 64u8, 128u8, 255u8] {
+ for operands in [0x01, 0xc1] {
+ let mut len = 0;
+ let mut bytes = [0; 8];
+
+ if prefix != 0x00 {
+ bytes[len] = prefix;
+ len += 1;
+ }
+
+ bytes[len] = 0x0f;
+ len += 1;
+
+ if map == 1 {
+ bytes[len] = 0x38;
+ len += 1;
+ } else if map == 2 {
+ bytes[len] = 0x3a;
+ len += 1;
+ }
+
+ bytes[len] = opc;
+ len += 1;
+
+ bytes[len] = operands;
+ len += 1;
+
+ bytes[len] = imm;
+ len += 1;
+
+ let bytes = &bytes[..len];
+ let mut reader = U8Reader::new(&bytes);
+ if decoder.decode_into(&mut buf, &mut reader).is_ok() {
+ use yaxpeax_arch::LengthedInstruction;
+ let inst_len = 0.wrapping_offset(buf.len()) as usize;
+ if inst_len != bytes.len() {
+ continue;
+ }
+
+ if not_generic(&buf) {
+ continue;
+ }
+
+ vm.set_regs(&initial_regs).unwrap();
+ check_behavior(&mut vm, &bytes).expect("behavior check is ok");
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ #[test]
+ fn behavior_verify_kvm_vex() {
+ use yaxpeax_arch::{Decoder, U8Reader};
+ use yaxpeax_x86::long_mode::Instruction;
+
+ let mut vm = create_test_vm();
+ vm.set_single_step(true).expect("can enable single-step");
+
+ let decoder = host_decoder();
+ let mut buf = Instruction::default();
+ let initial_regs = vm.get_regs().unwrap();
+
+ #[allow(non_snake_case)]
+ for opcode in 0x00..=u8::MAX {
+ for prefix_bits in 0x00..0x400u16 {
+ let mmmmm = prefix_bits & 0b11111;
+ let prefix_1 = (0xe0 | mmmmm) as u8;
+
+ let pp = (prefix_bits >> 5) & 0b11;
+ let W = (prefix_bits >> 7) & 1;
+ let L = (prefix_bits >> 8) & 1;
+ let prefix_2 = (0x78 | (W << 7) | (L << 2) | pp) as u8;
+
+ let operands = (prefix_bits >> 9) & 0b1;
+ static OPC_BYTE_TABLE: [u8; 2] = [0xc1, 0x01];
+
+ let bytes: [u8; 5] = [0xc4, prefix_1, prefix_2, opcode, OPC_BYTE_TABLE[operands as usize]];
+ let mut reader = U8Reader::new(&bytes);
+ if decoder.decode_into(&mut buf, &mut reader).is_ok() {
+ // two byte instructions were covered by `verify_kvm`, novel instructions are three
+ // bytes (or longer..?)
+ use yaxpeax_arch::LengthedInstruction;
+ let inst_len = 0.wrapping_offset(buf.len()) as usize;
+ if inst_len != bytes.len() {
+ continue;
+ }
+
+ if not_generic(&buf) {
+ continue;
+ }
+
+ vm.set_regs(&initial_regs).unwrap();
+ let res = check_behavior(&mut vm, &bytes[..inst_len]);
+ match res {
+ Ok(()) => {}
+ Err(CheckErr::ComplexOp(op)) => {
+ // uncheckable but not a failure
+ eprintln!("cannot check {:?}", op);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ #[test]
+ fn behavior_verify_kvm_vex_imm8() {
+ use yaxpeax_arch::{Decoder, U8Reader};
+ use yaxpeax_x86::long_mode::Instruction;
+
+ let mut vm = create_test_vm();
+ vm.set_single_step(true).expect("can enable single-step");
+
+ let decoder = host_decoder();
+ let mut buf = Instruction::default();
+ let initial_regs = vm.get_regs().unwrap();
+
+ #[allow(non_snake_case)]
+ for opcode in 0xc2..=u8::MAX {
+ for prefix_bits in 0x00..0x400u16 {
+ for imm in [0u8, 1u8, 2u8, 4u8, 8u8, 16u8, 32u8, 64u8, 128u8, 255u8] {
+ let mmmmm = prefix_bits & 0b11111;
+ let prefix_1 = (0xe0 | mmmmm) as u8;
+
+ let pp = (prefix_bits >> 5) & 0b11;
+ let W = (prefix_bits >> 7) & 1;
+ let L = (prefix_bits >> 8) & 1;
+ let prefix_2 = (0x78 | (W << 7) | (L << 2) | pp) as u8;
+
+ let operands = (prefix_bits >> 9) & 0b1;
+ static OPC_BYTE_TABLE: [u8; 2] = [0xc1, 0x01];
+
+ let bytes: [u8; 6] = [0xc4, prefix_1, prefix_2, opcode, OPC_BYTE_TABLE[operands as usize], imm];
+ let mut reader = U8Reader::new(&bytes);
+ if decoder.decode_into(&mut buf, &mut reader).is_ok() {
+ // two byte instructions were covered by `verify_kvm`, novel instructions are three
+ // bytes (or longer..?)
+ use yaxpeax_arch::LengthedInstruction;
+ let inst_len = 0.wrapping_offset(buf.len()) as usize;
+ if inst_len != bytes.len() {
+ continue;
+ }
+
+ if not_generic(&buf) {
+ continue;
+ }
+
+ vm.set_regs(&initial_regs).unwrap();
+ let res = check_behavior(&mut vm, &bytes[..inst_len]);
+ match res {
+ Ok(()) => {}
+ Err(CheckErr::ComplexOp(op)) => {
+ // uncheckable but not a failure
+ eprintln!("cannot check {:?}", op);
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ #[test]
+ fn behavior_verify_kvm_evex_noimm() {
+ use yaxpeax_arch::{Decoder, U8Reader};
+ use yaxpeax_x86::long_mode::Instruction;
+
+ let mut vm = create_test_vm();
+ vm.set_single_step(true).expect("can enable single-step");
+
+ let decoder = host_decoder();
+ let mut buf = Instruction::default();
+ let initial_regs = vm.get_regs().unwrap();
+
+ #[allow(non_snake_case)]
+ for opcode in 0x00..=u8::MAX {
+ for prefix_bits in 0x00..0x1000u16 {
+ let mmm = prefix_bits & 0b111;
+ let prefix_1 = (0xf0 | mmm) as u8;
+
+ let pp = (prefix_bits >> 3) & 0b11;
+ let z = (prefix_bits >> 5) & 1;
+ let b = (prefix_bits >> 6) & 1;
+ let W = (prefix_bits >> 7) & 1;
+ let LL = (prefix_bits >> 8) & 0b11;
+ let k = (prefix_bits >> 10) & 11;
+
+ let prefix_2 = (0x7c | (W << 7) | pp) as u8;
+
+ let aaa = [0b000, 0b001, 0b010, 0b111][k as usize];
+ let prefix_3 = (0x08 | aaa | b << 4 | LL << 5 | z << 7) as u8;
+
+ let operands = (prefix_bits >> 9) & 0b1;
+ static OPC_BYTE_TABLE: [u8; 2] = [0xc1, 0x01];
+
+ let bytes: [u8; 6] = [0x62, prefix_1, prefix_2, prefix_3, opcode, OPC_BYTE_TABLE[operands as usize]];
+ let mut reader = U8Reader::new(&bytes);
+ if decoder.decode_into(&mut buf, &mut reader).is_ok() {
+ // two byte instructions were covered by `verify_kvm`, novel instructions are three
+ // bytes (or longer..?)
+ use yaxpeax_arch::LengthedInstruction;
+ let inst_len = 0.wrapping_offset(buf.len()) as usize;
+ if inst_len != bytes.len() {
+ continue;
+ }
+
+ if not_generic(&buf) {
+ continue;
+ }
+
+ vm.set_regs(&initial_regs).unwrap();
+ let res = check_behavior(&mut vm, &bytes[..inst_len]);
+ match res {
+ Ok(()) => {}
+ Err(CheckErr::ComplexOp(op)) => {
+ // uncheckable but not a failure
+ eprintln!("cannot check {:?}", op);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ #[test]
+ fn behavior_verify_kvm_evex_imm() {
+ use yaxpeax_arch::{Decoder, U8Reader};
+ use yaxpeax_x86::long_mode::Instruction;
+
+ let mut vm = create_test_vm();
+ vm.set_single_step(true).expect("can enable single-step");
+
+ let decoder = host_decoder();
+ let mut buf = Instruction::default();
+ let initial_regs = vm.get_regs().unwrap();
+
+ #[allow(non_snake_case)]
+ for opcode in 0x00..=u8::MAX {
+ for imm in [0x00, 0x01, 0x80, 0xff] {
+ for prefix_bits in 0x00..0x1000u16 {
+ let mmm = prefix_bits & 0b111;
+ let prefix_1 = (0xf0 | mmm) as u8;
+
+ let pp = (prefix_bits >> 3) & 0b11;
+ let z = (prefix_bits >> 5) & 1;
+ let b = (prefix_bits >> 6) & 1;
+ let W = (prefix_bits >> 7) & 1;
+ let LL = (prefix_bits >> 8) & 0b11;
+ let k = (prefix_bits >> 10) & 11;
+
+ let prefix_2 = (0x7c | (W << 7) | pp) as u8;
+
+ let aaa = [0b000, 0b001, 0b010, 0b111][k as usize];
+ let prefix_3 = (0x08 | aaa | b << 4 | LL << 5 | z << 7) as u8;
+
+ let operands = (prefix_bits >> 9) & 0b1;
+ static OPC_BYTE_TABLE: [u8; 2] = [0xc1, 0x01];
+
+ let bytes: [u8; 7] = [0x62, prefix_1, prefix_2, prefix_3, opcode, OPC_BYTE_TABLE[operands as usize], imm];
+ let mut reader = U8Reader::new(&bytes);
+ if decoder.decode_into(&mut buf, &mut reader).is_ok() {
+ // two byte instructions were covered by `verify_kvm`, novel instructions are three
+ // bytes (or longer..?)
+ use yaxpeax_arch::LengthedInstruction;
+ let inst_len = 0.wrapping_offset(buf.len()) as usize;
+ if inst_len != bytes.len() {
+ continue;
+ }
+
+ if not_generic(&buf) {
+ continue;
+ }
+
+ vm.set_regs(&initial_regs).unwrap();
+ let res = check_behavior(&mut vm, &bytes[..inst_len]);
+ match res {
+ Ok(()) => {}
+ Err(CheckErr::ComplexOp(op)) => {
+ // uncheckable but not a failure
+ eprintln!("cannot check {:?}", op);
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ // use the generic test harness for a handful of instructions that don't get covered in the
+ // general enumeration above
+ #[test]
+ fn behavior_verify_kvm_misc() {
+ use yaxpeax_arch::{Decoder, U8Reader};
+ use yaxpeax_x86::long_mode::Instruction;
+
+ let mut vm = create_test_vm();
+ vm.set_single_step(true).expect("can enable single-step");
+
+ let decoder = host_decoder();
+ let mut buf = Instruction::default();
+ let initial_regs = vm.get_regs().unwrap();
+
+ static MISC_INSTS: &'static [&'static [u8]] = &[
+ // cmppd xmm0, xmmword [rcx], 0x75
+ &[0x66, 0x0f, 0xc2, 0x01, 0x75],
+ // cmpps xmm0, xmmword [rcx], 0x75
+ &[0x0f, 0xc2, 0x01, 0x75],
+ // shufpd xmm0, xmmword [rcx], 0x75
+ &[0x66, 0x0f, 0xc6, 0x01, 0x75],
+ // shufps xmm0, xmmword [rcx], 0x75
+ &[0x0f, 0xc6, 0x01, 0x75],
+ // lzcnt eax, dword [rcx]
+ &[0xf3, 0x0f, 0xbd, 0x01],
+ // adcx eax, dword [rcx]
+ &[0x66, 0x0f, 0x38, 0xf6, 0x01],
+ // adox eax, dword [rcx]
+ &[0xf3, 0x0f, 0x38, 0xf6, 0x01],
+ // crc32 eax, byte [rcx]
+ &[0xf2, 0x0f, 0x38, 0xf0, 0xc1],
+ ];
+ for bytes in MISC_INSTS.iter() {
+ let mut reader = U8Reader::new(&bytes);
+ if decoder.decode_into(&mut buf, &mut reader).is_ok() {
+ eprint!("checking behavior of {:02x}", bytes[0]);
+ for b in &bytes[1..] {
+ eprint!(" {:02x}", b);
+ }
+ eprint!("\n");
+
+ vm.set_regs(&initial_regs).unwrap();
+ check_behavior(&mut vm, bytes).expect("behavior check is ok");
+ }
+ }
+ }
+
+ use yaxpeax_x86::long_mode::Opcode;
+ use yaxpeax_x86::long_mode::Operand;
+ fn not_generic(instr: &Instruction) -> bool {
+ if sse_gpr::OPCODES.contains(&instr.opcode()) {
+ // not having reads for xmm registers yet, these don't fit well with the test harness..
+ return true;
+ }
+
+ if table_instrs::OPCODES.contains(&instr.opcode()) {
+ // tested under `mod table_instrs`.
+ return true;
+ }
+
+ if ptwrite::OPCODES.contains(&instr.opcode()) {
+ return true;
+ }
+
+ if vm_instrs::OPCODES.contains(&instr.opcode()) {
+ // this generic testing facility is not appropriate for VM instructions.
+ return true;
+ }
+
+ if ctrl_instrs::OPCODES.contains(&instr.opcode()) {
+ // control registers complicate testing against permutations, since those reuse
+ // the VM.
+ return true;
+ }
+
+ if enqcmd::OPCODES.contains(&instr.opcode()) {
+ return true;
+ }
+
+ static COMPLEX: &'static [Opcode] = &[
+ Opcode::SYSCALL,
+ Opcode::SYSRET,
+ Opcode::PREFETCHW,
+ Opcode::PREFETCHNTA,
+ Opcode::PREFETCH2,
+ Opcode::PREFETCH1,
+ Opcode::PREFETCH0,
+ Opcode::MOVDIR64B,
+ ];
+
+ if COMPLEX.contains(&instr.opcode()) {
+ return true;
+ }
+
+ if instr.opcode() == Opcode::INVPCID {
+ // this #UDs in the VM? is it because i'm not setting invpcid in cpuid..
+ return true;
+ }
+
+ if instr.opcode() == Opcode::RDPID {
+ // rdpid is a specialized rdmsr
+ return true;
+ }
+
+ if instr.opcode() == Opcode::RDTSCP {
+ // raises #UD without CPUID leaf 80000001 edx.rdtscp (bit 27)
+ return true;
+ }
+
+ if instr.opcode() == Opcode::INVLPGB {
+ // guest is not configured to permit invlpgb
+ return true;
+ }
+
+ if instr.opcode() == Opcode::TLBSYNC {
+ // guest is not configured to permit tlbsync
+ return true;
+ }
+
+ if cet::OPCODES.contains(&instr.opcode()) {
+ return true;
+ }
+
+ if rands::OPCODES.contains(&instr.opcode()) {
+ return true;
+ }
+
+ if xsave::OPCODES.contains(&instr.opcode()) {
+ return true;
+ }
+
+ if pconfig::OPCODES.contains(&instr.opcode()) {
+ return true;
+ }
+
+ if mpk::OPCODES.contains(&instr.opcode()) {
+ return true;
+ }
+
+ if selector_load::OPCODES.contains(&instr.opcode()) {
+ return true;
+ }
+
+ if undef::OPCODES.contains(&instr.opcode()) {
+ return true;
+ }
+
+ if cmov::OPCODES.contains(&instr.opcode()) {
+ return true;
+ }
+
+ if tdx::OPCODES.contains(&instr.opcode()) {
+ return true;
+ }
+
+ if waitpkg::OPCODES.contains(&instr.opcode()) {
+ return true;
+ }
+
+ if uintr::OPCODES.contains(&instr.opcode()) {
+ return true;
+ }
+
+ if Opcode::MONITOR == instr.opcode() {
+ return true;
+ }
+
+ if Opcode::MWAIT == instr.opcode() {
+ return true;
+ }
+
+ if instr.opcode() == Opcode::LAR || instr.opcode() == Opcode::LSL {
+ // TODO: needs explicit test (conditional write of dest..)
+ return true;
+ }
+
+ if instr.operand_present(0) {
+ if let Operand::Register { reg } = instr.operand(0) {
+ if reg.class() == register_class::CR {
+ return true;
+ }
+
+ if reg.class() == register_class::DR {
+ return true;
+ }
+ }
+ }
+
+ if instr.mem_size().is_some() {
+ if instr.opcode() == Opcode::BT ||
+ instr.opcode() == Opcode::BTS ||
+ instr.opcode() == Opcode::BTR ||
+ instr.opcode() == Opcode::BTC {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ mod cet {
+ use yaxpeax_x86::long_mode::Opcode;
+
+ pub static OPCODES: &'static [Opcode] = &[
+ Opcode::WRUSS,
+ Opcode::WRSS,
+ Opcode::INCSSP,
+ Opcode::SAVEPREVSSP,
+ Opcode::SETSSBSY,
+ Opcode::CLRSSBSY,
+ Opcode::RSTORSSP,
+ Opcode::ENDBR64,
+ Opcode::ENDBR32,
+ ];
+ }
+
+ // TODO: these don't fit in the generic harness because the destination register is scrombled
+ // and checking permutations will assume the instruction depends on some missed read (which
+ // *is* kinda true...)
+ mod rands {
+ use yaxpeax_x86::long_mode::Opcode;
+
+ pub static OPCODES: &'static [Opcode] = &[
+ Opcode::RDRAND,
+ Opcode::RDSEED,
+ ];
+ }
+
+ mod cmov {
+ use yaxpeax_x86::long_mode::Opcode;
+
+ pub static OPCODES: &'static [Opcode] = &[
+ Opcode::CMOVA,
+ Opcode::CMOVNA,
+ Opcode::CMOVB,
+ Opcode::CMOVNB,
+ Opcode::CMOVG,
+ Opcode::CMOVLE,
+ Opcode::CMOVL,
+ Opcode::CMOVGE,
+ Opcode::CMOVO,
+ Opcode::CMOVNO,
+ Opcode::CMOVP,
+ Opcode::CMOVNP,
+ Opcode::CMOVS,
+ Opcode::CMOVNS,
+ Opcode::CMOVZ,
+ Opcode::CMOVNZ,
+ ];
+
+ }
+
+ mod tdx {
+ use yaxpeax_x86::long_mode::Opcode;
+
+ pub static OPCODES: &'static [Opcode] = &[
+ Opcode::TDCALL,
+ Opcode::SEAMRET,
+ Opcode::SEAMOPS,
+ Opcode::SEAMCALL,
+ ];
+
+ }
+
+ mod waitpkg {
+ use yaxpeax_x86::long_mode::Opcode;
+
+ pub static OPCODES: &'static [Opcode] = &[
+ Opcode::TPAUSE,
+ Opcode::UMONITOR,
+ Opcode::UMWAIT,
+ ];
+
+ }
+
+ mod uintr {
+ use yaxpeax_x86::long_mode::Opcode;
+
+ pub static OPCODES: &'static [Opcode] = &[
+ Opcode::UIRET,
+ Opcode::SENDUIPI,
+ Opcode::TESTUI,
+ Opcode::CLUI,
+ Opcode::STUI,
+ ];
+
+ }
+
+ mod undef {
+ use yaxpeax_x86::long_mode::Opcode;
+
+ pub static OPCODES: &'static [Opcode] = &[
+ Opcode::UD0,
+ Opcode::UD1,
+ Opcode::UD2,
+ ];
+
+ }
+
+ // these need standalone testing because loading a bogus selector produces #GP
+ mod selector_load {
+ use yaxpeax_x86::long_mode::Opcode;
+
+ pub static OPCODES: &'static [Opcode] = &[
+ Opcode::LFS,
+ Opcode::LGS,
+ Opcode::LSS,
+ Opcode::SWAPGS,
+ ];
+
+ }
+
+ mod xsave {
+ use yaxpeax_x86::long_mode::Opcode;
+
+ pub static OPCODES: &'static [Opcode] = &[
+ Opcode::FXSAVE,
+ Opcode::FXRSTOR,
+ Opcode::XSAVE,
+ Opcode::XSAVEOPT,
+ Opcode::XSAVEC,
+ Opcode::XSAVEC64,
+ Opcode::XSAVES,
+ Opcode::XSAVES64,
+ Opcode::XRSTOR,
+ Opcode::XRSTORS,
+ Opcode::XRSTORS64,
+ ];
+
+ }
+
+ mod pconfig {
+ use yaxpeax_x86::long_mode::Opcode;
+
+ pub static OPCODES: &'static [Opcode] = &[
+ Opcode::PCONFIG,
+ Opcode::SKINIT,
+ ];
+
+ }
+
+ mod ptwrite {
+ use yaxpeax_x86::long_mode::Opcode;
+
+ pub static OPCODES: &'static [Opcode] = &[
+ Opcode::PTWRITE,
+ ];
+
+ }
+
+ mod mpk {
+ use yaxpeax_x86::long_mode::Opcode;
+
+ pub static OPCODES: &'static [Opcode] = &[
+ Opcode::RDPKRU,
+ Opcode::WRPKRU,
+ ];
+
+ }
+
+ mod ctrl_instrs {
+ use yaxpeax_x86::long_mode::Opcode;
+
+ pub static OPCODES: &'static [Opcode] = &[
+ Opcode::CLTS,
+ Opcode::XGETBV,
+ Opcode::XSETBV,
+ Opcode::LDMXCSR,
+ Opcode::STMXCSR,
+ Opcode::LMSW,
+ Opcode::SMSW,
+ Opcode::SWAPGS,
+ Opcode::RDFSBASE,
+ Opcode::WRFSBASE,
+ Opcode::RDGSBASE,
+ Opcode::WRGSBASE,
+ ];
+
+ }
+
+ mod enqcmd {
+ use yaxpeax_x86::long_mode::Opcode;
+
+ pub static OPCODES: &'static [Opcode] = &[
+ Opcode::ENQCMD,
+ Opcode::ENQCMDS,
+ ];
+
+ }
+
+ // instructions related to operating VT-x/SVM virtual machines.
+ // TODO: these are not (yet) tested.
+ mod vm_instrs {
+ use yaxpeax_x86::long_mode::Opcode;
+
+ pub static OPCODES: &'static [Opcode] = &[
+ Opcode::STGI,
+ Opcode::CLGI,
+ Opcode::VMREAD,
+ Opcode::VMWRITE,
+ Opcode::VMCLEAR,
+ Opcode::VMLAUNCH,
+ Opcode::VMRESUME,
+ Opcode::VMXON,
+ Opcode::VMXOFF,
+ Opcode::VMFUNC,
+ Opcode::VMPTRLD,
+ Opcode::VMPTRST,
+ Opcode::VMMCALL,
+ Opcode::VMLOAD,
+ Opcode::VMSAVE,
+ Opcode::VMRUN,
+ Opcode::VMCALL,
+ ];
+ }
+
+ mod sse_gpr {
+ use yaxpeax_x86::long_mode::Opcode;
+
+ pub static OPCODES: &'static [Opcode] = &[
+ Opcode::PEXTRB,
+ Opcode::PEXTRW,
+ Opcode::PEXTRD,
+ Opcode::EXTRACTPS,
+ ];
+ }
+
+ // check the collection of {l,s}{g,i,l}dt. these instructions are at the combination of
+ // "interesting memory size" and "interesting [non]interaction with prefixes"
+ //
+ // because these modify VM control structures, the VM is not retained across checks in a test.
+ mod table_instrs {
+ use super::create_test_vm;
+ use super::MemPatch;
+ use super::check_behavior_with_regs;
+ use super::TestAccesses;
+ use super::ExpectedRegAccess;
+ use super::ExpectedMemAccess;
+
+ use yaxpeax_arch::{Decoder, U8Reader};
+ use yaxpeax_x86::long_mode::{Instruction, Opcode, RegSpec};
+
+ pub static OPCODES: &'static [Opcode] = &[
+ Opcode::LGDT,
+ Opcode::SGDT,
+ Opcode::LIDT,
+ Opcode::SIDT,
+ Opcode::LLDT,
+ Opcode::SLDT,
+ Opcode::LTR,
+ Opcode::STR,
+ ];
+
+ #[test]
+ fn verify_lgdt() {
+ let decoder = super::host_decoder();
+ let mut buf = Instruction::default();
+
+ const PATCH_ADDR: u64 = 0x1_0000_0000;
+
+ // the instructions below read `[rax]`, so set `rax` as used and declare it in
+ // `used_regs` so randomization does not clobber.
+ let mut used_regs = [false; 16];
+ used_regs[0] = true;
+
+ let tests: Vec<(&'static [u8], TestAccesses)> = vec![
+ // lgdt mword [rax]
+ (&[0x0f, 0x01, 0x10], TestAccesses {
+ preserve_rsp: true,
+ used_regs,
+ expected_reg: vec![ExpectedRegAccess { write: false, reg: RegSpec::rax() }],
+ expected_mem: vec![ExpectedMemAccess { write: false, addr: PATCH_ADDR, size: 10 }],
+ }),
+ (&[0x66, 0x0f, 0x01, 0x10], TestAccesses {
+ preserve_rsp: true,
+ used_regs,
+ expected_reg: vec![ExpectedRegAccess { write: false, reg: RegSpec::rax() }],
+ expected_mem: vec![ExpectedMemAccess { write: false, addr: PATCH_ADDR, size: 10 }],
+ }),
+ (&[0x67, 0x0f, 0x01, 0x10], TestAccesses {
+ preserve_rsp: true,
+ used_regs,
+ expected_reg: vec![ExpectedRegAccess { write: false, reg: RegSpec::rax() }],
+ expected_mem: vec![ExpectedMemAccess { write: false, addr: PATCH_ADDR, size: 10 }],
+ }),
+ ];
+
+ for (inst, accs) in tests.into_iter() {
+ let mut vm = create_test_vm();
+ vm.set_single_step(true).expect("can enable single-step");
+
+ // set up lgdt to re-load the same gdt.
+ let mut patch_bytes = Vec::new();
+ patch_bytes.extend_from_slice(&4095u16.to_le_bytes());
+ patch_bytes.extend_from_slice(&vm.gdt_addr().0.to_le_bytes());
+ let patch = MemPatch {
+ addr: PATCH_ADDR,
+ bytes: patch_bytes,
+ };
+ let mut regs = vm.get_regs().expect("can get regs");
+ regs.rax = patch.addr;
+ vm.set_regs(&regs).expect("can set regs");
+
+ let mut reader = U8Reader::new(&inst);
+ assert!(decoder.decode_into(&mut buf, &mut reader).is_ok());
+
+ check_behavior_with_regs(&mut vm, &inst, Some(accs), &[patch]).expect("behavior check is ok");
+ }
+ }
+
+ #[test]
+ fn verify_lidt() {
+ let decoder = super::host_decoder();
+ let mut buf = Instruction::default();
+
+ const PATCH_ADDR: u64 = 0x1_0000_0000;
+
+ // the instructions below read `[rax]`, so set `rax` as used and declare it in
+ // `used_regs` so randomization does not clobber.
+ let mut used_regs = [false; 16];
+ used_regs[0] = true;
+
+ let tests: Vec<(&'static [u8], TestAccesses)> = vec![
+ // iidt mword [rax]
+ (&[0x0f, 0x01, 0x18], TestAccesses {
+ preserve_rsp: true,
+ used_regs,
+ expected_reg: vec![ExpectedRegAccess { write: false, reg: RegSpec::rax() }],
+ expected_mem: vec![ExpectedMemAccess { write: false, addr: PATCH_ADDR, size: 10 }],
+ }),
+ (&[0x66, 0x0f, 0x01, 0x18], TestAccesses {
+ preserve_rsp: true,
+ used_regs,
+ expected_reg: vec![ExpectedRegAccess { write: false, reg: RegSpec::rax() }],
+ expected_mem: vec![ExpectedMemAccess { write: false, addr: PATCH_ADDR, size: 10 }],
+ }),
+ (&[0x67, 0x0f, 0x01, 0x18], TestAccesses {
+ preserve_rsp: true,
+ used_regs,
+ expected_reg: vec![ExpectedRegAccess { write: false, reg: RegSpec::rax() }],
+ expected_mem: vec![ExpectedMemAccess { write: false, addr: PATCH_ADDR, size: 10 }],
+ }),
+ ];
+
+ for (inst, accs) in tests.into_iter() {
+ let mut vm = create_test_vm();
+ vm.set_single_step(true).expect("can enable single-step");
+
+ // set up lidt to re-load the same idt.
+ let mut patch_bytes = Vec::new();
+ patch_bytes.extend_from_slice(&4095u16.to_le_bytes());
+ patch_bytes.extend_from_slice(&vm.idt_addr().0.to_le_bytes());
+ let patch = MemPatch {
+ addr: PATCH_ADDR,
+ bytes: patch_bytes,
+ };
+ let mut regs = vm.get_regs().expect("can get regs");
+ regs.rax = patch.addr;
+ vm.set_regs(&regs).expect("can set regs");
+
+ let mut reader = U8Reader::new(&inst);
+ assert!(decoder.decode_into(&mut buf, &mut reader).is_ok());
+
+ check_behavior_with_regs(&mut vm, &inst, Some(accs), &[patch]).expect("behavior check is ok");
+ }
+ }
+
+ #[test]
+ fn verify_lldt() {
+ let decoder = super::host_decoder();
+ let mut buf = Instruction::default();
+
+ const PATCH_ADDR: u64 = 0x1_0000_0000;
+
+ // the instructions below read `[rax]`, so set `rax` as used and declare it in
+ // `used_regs` so randomization does not clobber.
+ let mut used_regs = [false; 16];
+ used_regs[0] = true;
+
+ let tests: Vec<(&'static [u8], TestAccesses)> = vec![
+ // iidt mword [rax]
+ (&[0x0f, 0x00, 0x10], TestAccesses {
+ preserve_rsp: true,
+ used_regs,
+ expected_reg: vec![ExpectedRegAccess { write: false, reg: RegSpec::rax() }],
+ expected_mem: vec![ExpectedMemAccess { write: false, addr: PATCH_ADDR, size: 2 }],
+ }),
+ (&[0x66, 0x0f, 0x00, 0x10], TestAccesses {
+ preserve_rsp: true,
+ used_regs,
+ expected_reg: vec![ExpectedRegAccess { write: false, reg: RegSpec::rax() }],
+ expected_mem: vec![ExpectedMemAccess { write: false, addr: PATCH_ADDR, size: 2 }],
+ }),
+ (&[0x67, 0x0f, 0x00, 0x10], TestAccesses {
+ preserve_rsp: true,
+ used_regs,
+ expected_reg: vec![ExpectedRegAccess { write: false, reg: RegSpec::rax() }],
+ expected_mem: vec![ExpectedMemAccess { write: false, addr: PATCH_ADDR, size: 2 }],
+ }),
+ ];
+
+ for (inst, accs) in tests.into_iter() {
+ let mut vm = create_test_vm();
+ vm.set_single_step(true).expect("can enable single-step");
+
+ // quoth SDM:
+ // > If bits 2-15 of the source operand are 0, LDTR is marked invalid and the LLDT
+ // > instruction completes silently. However, all subsequent references to
+ // > descriptors in the LDT (except by the LAR, VERR, VERW or LSL instructions) cause
+ // > a general protection exception (#GP).
+ let mut patch_bytes = Vec::new();
+ patch_bytes.extend_from_slice(&0u16.to_le_bytes());
+ let patch = MemPatch {
+ addr: PATCH_ADDR,
+ bytes: patch_bytes,
+ };
+ let mut regs = vm.get_regs().expect("can get regs");
+ regs.rax = patch.addr;
+ vm.set_regs(&regs).expect("can set regs");
+
+ let mut reader = U8Reader::new(&inst);
+ assert!(decoder.decode_into(&mut buf, &mut reader).is_ok());
+
+ check_behavior_with_regs(&mut vm, &inst, Some(accs), &[patch]).expect("behavior check is ok");
+ }
+ }
+
+ #[test]
+ fn verify_table_stores() {
+ let decoder = super::host_decoder();
+ let mut buf = Instruction::default();
+
+ const PATCH_ADDR: u64 = 0x1_0000_0000;
+
+ // the instructions below read `[rax]`, so set `rax` as used and declare it in
+ // `used_regs` so randomization does not clobber.
+ let mut used_regs = [false; 16];
+ used_regs[0] = true;
+
+ let tests: Vec<(&'static [u8], TestAccesses)> = vec![
+ // sgdt
+ (&[0x0f, 0x01, 0x00], TestAccesses {
+ preserve_rsp: true,
+ used_regs,
+ expected_reg: vec![ExpectedRegAccess { write: false, reg: RegSpec::rax() }],
+ expected_mem: vec![ExpectedMemAccess { write: true, addr: PATCH_ADDR, size: 10 }],
+ }),
+ (&[0x66, 0x0f, 0x01, 0x00], TestAccesses {
+ preserve_rsp: true,
+ used_regs,
+ expected_reg: vec![ExpectedRegAccess { write: false, reg: RegSpec::rax() }],
+ expected_mem: vec![ExpectedMemAccess { write: true, addr: PATCH_ADDR, size: 10 }],
+ }),
+ (&[0x67, 0x0f, 0x01, 0x00], TestAccesses {
+ preserve_rsp: true,
+ used_regs,
+ expected_reg: vec![ExpectedRegAccess { write: false, reg: RegSpec::rax() }],
+ expected_mem: vec![ExpectedMemAccess { write: true, addr: PATCH_ADDR, size: 10 }],
+ }),
+ // sidt
+ (&[0x0f, 0x01, 0x08], TestAccesses {
+ preserve_rsp: true,
+ used_regs,
+ expected_reg: vec![ExpectedRegAccess { write: false, reg: RegSpec::rax() }],
+ expected_mem: vec![ExpectedMemAccess { write: true, addr: PATCH_ADDR, size: 10 }],
+ }),
+ (&[0x66, 0x0f, 0x01, 0x08], TestAccesses {
+ preserve_rsp: true,
+ used_regs,
+ expected_reg: vec![ExpectedRegAccess { write: false, reg: RegSpec::rax() }],
+ expected_mem: vec![ExpectedMemAccess { write: true, addr: PATCH_ADDR, size: 10 }],
+ }),
+ (&[0x67, 0x0f, 0x01, 0x08], TestAccesses {
+ preserve_rsp: true,
+ used_regs,
+ expected_reg: vec![ExpectedRegAccess { write: false, reg: RegSpec::rax() }],
+ expected_mem: vec![ExpectedMemAccess { write: true, addr: PATCH_ADDR, size: 10 }],
+ }),
+ // sldt
+ (&[0x0f, 0x00, 0x00], TestAccesses {
+ preserve_rsp: true,
+ used_regs,
+ expected_reg: vec![ExpectedRegAccess { write: false, reg: RegSpec::rax() }],
+ expected_mem: vec![ExpectedMemAccess { write: true, addr: PATCH_ADDR, size: 2 }],
+ }),
+ (&[0x66, 0x0f, 0x00, 0x00], TestAccesses {
+ preserve_rsp: true,
+ used_regs,
+ expected_reg: vec![ExpectedRegAccess { write: false, reg: RegSpec::rax() }],
+ expected_mem: vec![ExpectedMemAccess { write: true, addr: PATCH_ADDR, size: 2 }],
+ }),
+ (&[0x67, 0x0f, 0x00, 0x00], TestAccesses {
+ preserve_rsp: true,
+ used_regs,
+ expected_reg: vec![ExpectedRegAccess { write: false, reg: RegSpec::rax() }],
+ expected_mem: vec![ExpectedMemAccess { write: true, addr: PATCH_ADDR, size: 2 }],
+ }),
+ ];
+
+ for (inst, accs) in tests.into_iter() {
+ let mut vm = create_test_vm();
+ vm.set_single_step(true).expect("can enable single-step");
+ let mut regs = vm.get_regs().expect("can get regs");
+ regs.rax = PATCH_ADDR;
+ vm.set_regs(&regs).expect("can set regs");
+
+ let mut reader = U8Reader::new(&inst);
+ assert!(decoder.decode_into(&mut buf, &mut reader).is_ok());
+
+ check_behavior_with_regs(&mut vm, &inst, Some(accs), &[]).expect("behavior check is ok");
+ }
+ }
+ }
+}
diff --git a/test/long_mode/mod.rs b/test/long_mode/mod.rs
index f803692..eaca39d 100644
--- a/test/long_mode/mod.rs
+++ b/test/long_mode/mod.rs
@@ -9,6 +9,8 @@ mod display;
mod descriptions;
mod evex_generated;
mod reuse_test;
+#[cfg(feature="behavior")]
+mod behavior;
use std::fmt::Write;
diff --git a/test/long_mode/reuse_test.rs b/test/long_mode/reuse_test.rs
index ad8e890..8742041 100644
--- a/test/long_mode/reuse_test.rs
+++ b/test/long_mode/reuse_test.rs
@@ -1981,18 +1981,18 @@ const INSTRUCTIONS: [&'static [u8]; 1975] = [
#[test]
fn test_against_leftover_data() {
- use super::rand::{thread_rng, Rng};
+ use super::rand::{rngs::ThreadRng, RngExt};
use yaxpeax_arch::U8Reader;
- let mut rng = thread_rng();
+ let mut rng = ThreadRng::default();
let decoder = InstDecoder::default();
for _ in 0..100000 {
- let first_vec = INSTRUCTIONS[rng.gen_range(0..INSTRUCTIONS.len())];
+ let first_vec = INSTRUCTIONS[rng.random_range(0..INSTRUCTIONS.len())];
let mut first_reader = U8Reader::new(first_vec);
let first_decode = decoder.decode(&mut first_reader).unwrap();
- let second_vec = INSTRUCTIONS[rng.gen_range(0..INSTRUCTIONS.len())];
+ let second_vec = INSTRUCTIONS[rng.random_range(0..INSTRUCTIONS.len())];
let mut second_reader = U8Reader::new(second_vec);
let mut reused_decode = decoder.decode(&mut second_reader).unwrap();
let mut first_reader = U8Reader::new(first_vec);