aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authoriximeow <me@iximeow.net>2026-05-13 04:30:27 +0000
committeriximeow <me@iximeow.net>2026-05-13 04:30:27 +0000
commit89549f17a48236b890f4af254e75c379455a00f1 (patch)
treeecbbb3f6d9175a2ec0f1497c5923f335bb999e17
parent6f0426bc30ae65aac2f08cbf90aafa823f4554bc (diff)
add behavior fuzzing, fix some stuff it noticed
-rw-r--r--fuzz/Cargo.toml6
-rw-r--r--fuzz/fuzz_targets/behavior_does_not_panic.rs138
-rw-r--r--src/long_mode/behavior.rs580
-rw-r--r--src/long_mode/mod.rs5
4 files changed, 605 insertions, 124 deletions
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/src/long_mode/behavior.rs b/src/long_mode/behavior.rs
index 65d9276..a8ad541 100644
--- a/src/long_mode/behavior.rs
+++ b/src/long_mode/behavior.rs
@@ -131,7 +131,28 @@ impl Instruction {
}
};
if behavior.is_nontrivial() {
- if self.opcode() == Opcode::MULX {
+ 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
@@ -185,12 +206,6 @@ impl Instruction {
}
}
- if let Some(evex) = self.prefixes.evex() {
- if evex.mask_reg() != 0 {
- behavior = behavior
- .set_operand(0, Access::ReadWrite);
- }
- }
InstBehavior {
inst: self,
behavior
@@ -372,7 +387,7 @@ pub enum PrivilegeLevel {
#[derive(Copy, Clone)]
pub struct InstOperands<'inst> {
inst: InstBehavior<'inst>,
- implicit_ops: &'static [(Operand, Access)],
+ implicit_ops: &'static [ImplicitOperand],
}
impl<'inst> InstOperands<'inst> {
@@ -421,14 +436,21 @@ impl<'inst> Iterator for AccessIter<'inst> {
// we're going to keep searching through the loop.
self.next += 1;
if let Some(acc) = self.operands.inst.flags_access() {
- return Some((Operand::Register { reg: RegSpec::rflags() }, acc));
+ 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) {
- return Some(entry.clone());
+ 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.
@@ -468,6 +490,7 @@ pub struct OperandIter<'inst> {
/// 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,
@@ -476,6 +499,36 @@ pub struct ImplicitOperand {
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;
@@ -525,56 +578,33 @@ impl<'inst> InstBehavior<'inst> {
}
// TODO: all of these should be a `set_complex` bit.
- if self.inst.opcode == Opcode::WRMSR {
- Some(ComplexOp::WRMSR)
- } else if self.inst.opcode == Opcode::PREFETCHNTA {
- Some(ComplexOp::PREFETCHNTA)
- } else if self.inst.opcode == Opcode::PREFETCH2 {
- Some(ComplexOp::PREFETCHT2)
- } else if self.inst.opcode == Opcode::PREFETCH1 {
- Some(ComplexOp::PREFETCHT1)
- } else if self.inst.opcode == Opcode::PREFETCH0 {
- Some(ComplexOp::PREFETCHT0)
- } else if self.inst.opcode == Opcode::BT && self.inst.operands[0] != OperandSpec::RegMMM {
- Some(ComplexOp::BT)
- } else if self.inst.opcode == Opcode::BTS && self.inst.operands[0] != OperandSpec::RegMMM {
- Some(ComplexOp::BTS)
- } else if self.inst.opcode == Opcode::BTR && self.inst.operands[0] != OperandSpec::RegMMM {
- Some(ComplexOp::BTR)
- } else if self.inst.opcode == Opcode::BTC && self.inst.operands[0] != OperandSpec::RegMMM {
- Some(ComplexOp::BTC)
- } else if self.inst.opcode == Opcode::VPGATHERDD {
- Some(ComplexOp::VPGATHERDD)
- } else if self.inst.opcode == Opcode::VPGATHERDQ {
- Some(ComplexOp::VPGATHERDQ)
- } else if self.inst.opcode == Opcode::VPGATHERQD {
- Some(ComplexOp::VPGATHERQD)
- } else if self.inst.opcode == Opcode::VPGATHERQQ {
- Some(ComplexOp::VPGATHERQQ)
- } else if self.inst.opcode == Opcode::VGATHERDPD {
- Some(ComplexOp::VGATHERDPD)
- } else if self.inst.opcode == Opcode::VGATHERDPS {
- Some(ComplexOp::VGATHERDPS)
- } else if self.inst.opcode == Opcode::VGATHERQPD {
- Some(ComplexOp::VGATHERQPD)
- } else if self.inst.opcode == Opcode::VGATHERQPS {
- Some(ComplexOp::VGATHERQPS)
- } else if self.inst.opcode == Opcode::VPSCATTERDD {
- Some(ComplexOp::VPSCATTERDD)
- } else if self.inst.opcode == Opcode::VPSCATTERDQ {
- Some(ComplexOp::VPSCATTERDQ)
- } else if self.inst.opcode == Opcode::VPSCATTERQD {
- Some(ComplexOp::VPSCATTERQD)
- } else if self.inst.opcode == Opcode::VPSCATTERQQ {
- Some(ComplexOp::VPSCATTERQQ)
- } else if self.inst.opcode == Opcode::MOVDIR64B {
- Some(ComplexOp::MOVDIR64B)
- } else if self.inst.opcode == Opcode::ENQCMD {
- Some(ComplexOp::ENQCMD)
- } else if self.inst.opcode == Opcode::ENQCMDS {
- Some(ComplexOp::ENQCMDS)
+ 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 {
- None
+ let comp: ComplexOp = unsafe { core::mem::transmute::<Opcode, ComplexOp>(self.inst.opcode) };
+ Some(comp)
}
}
@@ -593,11 +623,15 @@ impl<'inst> InstBehavior<'inst> {
return Err(op);
}
+ let implicit_ops = if let Some(ops) = self.implicit_oplist() {
+ ops
+ } else {
+ &[]
+ };
+
Ok(InstOperands {
inst: *self,
- // TODO: actually select an implicit operands array based on... something from the
- // instruction behavior, maybe?
- implicit_ops: &[],
+ implicit_ops,
})
}
@@ -653,6 +687,92 @@ impl<'inst> InstBehavior<'inst> {
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 => {
panic!("not-yet-handled memory operand: {:?}", other);
}
@@ -761,16 +881,22 @@ impl<'inst> InstBehavior<'inst> {
OperandSpec::RegRRR_maskmerge_sae |
OperandSpec::RegRRR_maskmerge_sae_noround => {
v.register_read(self.inst.regs[0]);
- v.register_read(RegSpec::mask(self.inst.prefixes.evex_unchecked().mask_reg()));
+ 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]);
- v.register_read(RegSpec::mask(self.inst.prefixes.evex_unchecked().mask_reg()));
+ 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]);
- v.register_read(RegSpec::mask(self.inst.prefixes.evex_unchecked().mask_reg()));
+ 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 |
@@ -781,11 +907,14 @@ impl<'inst> InstBehavior<'inst> {
OperandSpec::ImmInDispField => {
// no register/memory access to report.
}
- _other => {
+ 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 {
@@ -826,16 +955,22 @@ impl<'inst> InstBehavior<'inst> {
OperandSpec::RegRRR_maskmerge |
OperandSpec::RegRRR_maskmerge_sae |
OperandSpec::RegRRR_maskmerge_sae_noround => {
- v.register_read(RegSpec::mask(self.inst.prefixes.evex_unchecked().mask_reg()));
+ 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 => {
- v.register_read(RegSpec::mask(self.inst.prefixes.evex_unchecked().mask_reg()));
+ 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 => {
- v.register_read(RegSpec::mask(self.inst.prefixes.evex_unchecked().mask_reg()));
+ 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 |
@@ -847,11 +982,14 @@ impl<'inst> InstBehavior<'inst> {
OperandSpec::ImmInDispField => {
// no register/memory access to report.
}
- _other => {
+ 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: the memory access is coded as a read and no
// instruction has a similar "fake" memory write.
v.memory_write(addr, size as u32);
@@ -928,11 +1066,11 @@ impl Access {
LUT[bits as usize]
}
- fn is_read(&self) -> bool {
+ pub fn is_read(&self) -> bool {
*self as u8 & 0b01 != 0
}
- fn is_write(&self) -> bool {
+ pub fn is_write(&self) -> bool {
*self as u8 & 0b10 != 0
}
}
@@ -1166,74 +1304,267 @@ impl BehaviorDigest {
/// *ptr |= (1 << bit)
/// ```
#[non_exhaustive]
+#[repr(u32)] // same repr as `Opcode`
#[derive(Copy, Clone, Debug)]
pub enum ComplexOp {
+ /// TODO: document,
+ IN = (Opcode::IN as u32),
+ OUT = (Opcode::OUT as u32),
+
+ IRET = (Opcode::IRET as u32),
+ IRETD = (Opcode::IRETD as u32),
+ IRETQ = (Opcode::IRETQ 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),
+ PCONFIG = (Opcode::PCONFIG as u32),
+ ENCLS = (Opcode::ENCLS as u32),
+ ENCLV = (Opcode::ENCLV as u32),
+ XGETBV = (Opcode::XGETBV as u32),
+ XSETBV = (Opcode::XSETBV as u32),
+ VMFUNC = (Opcode::VMFUNC as u32),
+ XEND = (Opcode::XEND as u32),
+ XTEST = (Opcode::XTEST as u32),
+ ENCLU = (Opcode::ENCLU as u32),
+ RDPKRU = (Opcode::RDPKRU as u32),
+ WRPKRU = (Opcode::WRPKRU as u32),
+ CLZERO = (Opcode::CLZERO as u32),
+
/// rdmsr/wrmsr are considered "complex" for reasons described in the enum doc comment.
- RDMSR,
- WRMSR,
+ RDMSR = (Opcode::RDMSR as u32),
+ WRMSR = (Opcode::WRMSR as u32),
/// string instructions are considered "complex" for reasons described in the enum doc comment.
- MOVS,
- STOS,
- LODS,
- SCAS,
- CMPS,
+ 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),
/// prefetch instructions are considered "complex" for reasons described in the enum doc
/// comment.
- PREFETCHNTA,
- PREFETCHT2,
- PREFETCHT1,
- PREFETCHT0,
+ PREFETCHNTA = (Opcode::PREFETCHNTA as u32),
+ PREFETCHT2 = (Opcode::PREFETCH2 as u32),
+ PREFETCHT1 = (Opcode::PREFETCH1 as u32),
+ PREFETCHT0 = (Opcode::PREFETCH0 as u32),
/// scatter/gather instructions are considered "complex" for reasons described in the enum doc
/// comment.
- VPGATHERDD,
- VPGATHERDQ,
- VPGATHERQD,
- VPGATHERQQ,
- VGATHERDPD,
- VGATHERDPS,
- VGATHERQPD,
- VGATHERQPS,
-
- VPSCATTERDD,
- VPSCATTERDQ,
- VPSCATTERQD,
- VPSCATTERQQ,
+ 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),
/// bit test/set/reset/complement instructions are conditionally complex depending on their
/// destination operand form, as described in the enum doc comment.
- BT,
- BTC,
- BTR,
- BTS,
+ BT = (Opcode::BT as u32),
+ BTC = (Opcode::BTC as u32),
+ BTR = (Opcode::BTR as u32),
+ BTS = (Opcode::BTS as u32),
+
+ /// TODO: document
+ SYSCALL = (Opcode::SYSCALL as u32),
+ SYSRET = (Opcode::SYSRET as u32),
+
+ /// TODO: document
+ SYSENTER = (Opcode::SYSENTER as u32),
+ SYSEXIT = (Opcode::SYSEXIT as u32),
+
+ /// TODO: document
+ STR = (Opcode::STR as u32),
+ LTR = (Opcode::LTR as u32),
+ SLDT = (Opcode::SLDT as u32),
+ LLDT = (Opcode::LLDT as u32),
+ RSM = (Opcode::RSM as u32),
+
+ /// TODO: document
+ CLGI = (Opcode::CLGI as u32),
+ STGI = (Opcode::STGI as u32),
+ 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),
/// TODO: document
- SYSCALL,
- SYSRET,
+ VZEROUPPER = (Opcode::VZEROUPPER as u32),
+ VZEROALL = (Opcode::VZEROALL as u32),
/// TODO: document
- SWAPGS,
- RDFSBASE,
- WRFSBASE,
- RDGSBASE,
- WRGSBASE,
+ 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),
/// 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,
+ MOVDIR64B = (Opcode::MOVDIR64B as u32),
+
+ /// TODO: document
+ ENQCMD = (Opcode::ENQCMD as u32),
+ ENQCMDS = (Opcode::ENQCMDS as u32),
+
+ /// TODO: document
+ V4FNMADDSS = (Opcode::V4FNMADDSS as u32),
+ V4FNMADDPS = (Opcode::V4FNMADDPS as u32),
+ V4FMADDSS = (Opcode::V4FMADDSS as u32),
+ V4FMADDPS = (Opcode::V4FMADDPS as u32),
+
+ /// TODO: document
+ 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),
/// TODO: document
- ENQCMD,
- ENQCMDS,
+ MONITOR = (Opcode::MONITOR as u32),
+ MONITORX = (Opcode::MONITORX as u32),
+ MWAIT = (Opcode::MWAIT as u32),
+ MWAITX = (Opcode::MWAITX as u32),
/// TODO: document
- V4FNMADDSS,
- V4FNMADDPS,
- V4FMADDSS,
- V4FMADDPS,
+ XABORT = (Opcode::XABORT as u32),
+ XBEGIN = (Opcode::XBEGIN as u32),
+
+ /// TODO: document
+ RDPRU = (Opcode::RDPRU as u32),
+
+ /// TODO: document
+ HRESET = (Opcode::HRESET as u32),
+
+ /// TODO: document
+ TPAUSE = (Opcode::TPAUSE as u32),
+ UMONITOR = (Opcode::UMONITOR as u32),
+ UMWAIT = (Opcode::UMWAIT as u32),
+
+ /// TODO: document
+ VMXON = (Opcode::VMXON as u32),
+ VMXOFF = (Opcode::VMXOFF as u32),
+
+ /// TODO: document
+ 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),
+
+ /// TODO: document MPX
+ BNDLDX = (Opcode::BNDLDX as u32),
+ BNDSTX = (Opcode::BNDSTX as u32),
+
+ /// TODO: document TDX
+ TDCALL = (Opcode::TDCALL as u32),
+ SEAMRET = (Opcode::SEAMRET as u32),
+ SEAMOPS = (Opcode::SEAMOPS as u32),
+ SEAMCALL = (Opcode::SEAMCALL as u32),
+
+ /// TODO: document PSMASH
+ PSMASH = (Opcode::PSMASH as u32),
+ PVALIDATE = (Opcode::PVALIDATE as u32),
+ RMPADJUST = (Opcode::RMPADJUST as u32),
+ RMPUPDATE = (Opcode::RMPUPDATE as u32),
+
+ /// TODO: document CET
+ 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),
+
+ /// TODO: document PTWRITE
+ PTWRITE = (Opcode::PTWRITE as u32),
+ /*
+ 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
+ *
+
+ */
}
/// a visitor for collecting architectural accesses for an `Instruction`. used with
@@ -1921,7 +2252,7 @@ static ENTER_OPS: &'static [ImplicitOperand] = &[
write: true,
},
ImplicitOperand {
- spec: OperandSpec::Deref,
+ spec: OperandSpec::Disp,
reg: RegSpec::rsp(),
disp: -8i32,
write: false,
@@ -2332,7 +2663,7 @@ static CALLF_OPS: &'static [ImplicitOperand] = &[
static RETF_OPS: &'static [ImplicitOperand] = &[
ImplicitOperand {
- spec: OperandSpec::Disp,
+ spec: OperandSpec::Deref,
reg: RegSpec::rsp(),
disp: 0i32,
write: false,
@@ -3117,8 +3448,6 @@ static IMPLICIT_OPS_LIST: [&[ImplicitOperand]; 73] = [
ENTER_OPS,
];
-#[inline(never)]
-#[unsafe(no_mangle)]
fn opcode2behavior(opc: &Opcode) -> Option<BehaviorDigest> {
use Opcode::*;
if opc == &MUL || opc == &IMUL || opc == &DIV || opc == &IDIV || opc == &NOP || opc == &CMPXCHG {
@@ -3277,7 +3606,7 @@ fn opcode2behavior(opc: &Opcode) -> Option<BehaviorDigest> {
.set_complex(true),
RETF => BehaviorDigest::empty()
.set_pl_any()
- .set_complex(true)
+ .set_nontrivial(true)
.set_implicit_ops(RETF_IDX),
ENTER => BehaviorDigest::empty()
.set_implicit_ops(ENTER_IDX)
@@ -3290,7 +3619,7 @@ fn opcode2behavior(opc: &Opcode) -> Option<BehaviorDigest> {
MOV => GENERAL_RW_R,
RETURN => BehaviorDigest::empty()
.set_implicit_ops(RETURN_IDX)
- .set_complex(true)
+ .set_nontrivial(true)
.set_pl_any(),
PUSHF => BehaviorDigest::empty()
.set_implicit_ops(PUSHF_IDX)
@@ -3712,8 +4041,10 @@ fn opcode2behavior(opc: &Opcode) -> Option<BehaviorDigest> {
MOVNTI => GENERAL_W_R,
MOVNTPS => GENERAL_W_R,
MOVNTPD => GENERAL_W_R,
- EXTRQ => GENERAL_RW_R,
- INSERTQ => GENERAL_RW_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,
@@ -4571,8 +4902,9 @@ fn opcode2behavior(opc: &Opcode) -> Option<BehaviorDigest> {
FLD1 => BehaviorDigest::empty()
.set_pl_any(),
FLDCW => GENERAL_R,
- FLDENV => BehaviorDigest::empty()
- .set_pl_any(),
+ FLDENV => GENERAL_R
+ .set_pl_any()
+ .set_complex(true),
// TODO: fpu stack write
FLDL2E => BehaviorDigest::empty()
.set_pl_any(),
diff --git a/src/long_mode/mod.rs b/src/long_mode/mod.rs
index 1b5caf1..04b89ff 100644
--- a/src/long_mode/mod.rs
+++ b/src/long_mode/mod.rs
@@ -462,6 +462,9 @@ impl OperandSpec {
fn is_memory(&self) -> bool {
(*self as u8) & 0x80 != 0
}
+ fn is_masked(&self) -> bool {
+ (*self as u8) & 0x40 != 0
+ }
}
/// an `avx512` merging mode.
@@ -7128,6 +7131,8 @@ fn read_operands<
instruction.imm = read_num(words, 1)? as u64;
instruction.operands[0] = OperandSpec::ImmInDispField;
instruction.operands[1] = OperandSpec::ImmU8;
+ // because there is an implied push of the adjusted base pointer
+ instruction.mem_size = 8;
instruction.operand_count = 2;
}
OperandCase::Fw => {