use crate::abi::Arch;
use super::{Backend, Exit, GuestMemory, Vcpu, VcpuError};
const MAX_STEPS: u64 = 50_000_000;
const RAX: usize = 0;
const RCX: usize = 1;
const RDX: usize = 2;
const RBX: usize = 3;
const RSP: usize = 4;
const RBP: usize = 5;
const RSI: usize = 6;
const RDI: usize = 7;
const R8: usize = 8;
const R9: usize = 9;
const R10: usize = 10;
#[derive(Debug)]
pub struct X86Backend {
guest: Arch,
}
impl X86Backend {
pub fn new(guest: Arch) -> Result<Self, VcpuError> {
Ok(Self { guest })
}
}
impl Backend for X86Backend {
fn name(&self) -> &'static str {
"interp-x86"
}
fn guest_arch(&self) -> Arch {
self.guest
}
fn new_vcpu(&self, entry: u64, stack: u64) -> Result<Box<dyn Vcpu>, VcpuError> {
match self.guest {
Arch::X86_64 => Ok(Box::new(X86Interp::new(entry, stack))),
Arch::Aarch64 => Err(VcpuError::Backend(
"interp-x86 backend only supports x86-64 guests".into(),
)),
}
}
}
enum Step {
Next,
Branched,
Syscall,
Illegal,
Fault {
addr: u64,
write: bool,
},
}
#[derive(Default, Clone, Copy)]
#[allow(clippy::struct_excessive_bools)]
struct Flags {
cf: bool,
zf: bool,
sf: bool,
of: bool,
pf: bool,
}
#[derive(Clone, Copy, Default)]
#[allow(clippy::struct_excessive_bools)]
struct Rex {
w: bool,
r: bool,
x: bool,
b: bool,
}
impl Rex {
fn from_byte(byte: u8) -> Self {
Self {
w: byte & 0x08 != 0,
r: byte & 0x04 != 0,
x: byte & 0x02 != 0,
b: byte & 0x01 != 0,
}
}
}
#[derive(Clone, Copy)]
struct ModRm {
reg: usize,
kind: RmKind,
}
#[derive(Clone, Copy)]
enum RmKind {
Reg(usize),
Mem(u64),
MemRip(i64),
}
#[derive(Clone, Copy)]
enum Operand {
Reg(usize),
Reg8Hi(usize),
Mem(u64),
}
fn resolve(kind: RmKind, end_pc: u64) -> Operand {
match kind {
RmKind::Reg(r) => Operand::Reg(r),
RmKind::Mem(a) => Operand::Mem(a),
RmKind::MemRip(disp) => Operand::Mem((end_pc as i64).wrapping_add(disp) as u64),
}
}
fn resolve8(kind: RmKind, end_pc: u64, has_rex: bool) -> Operand {
match kind {
RmKind::Reg(r) => reg8_operand(r, has_rex),
_ => resolve(kind, end_pc),
}
}
fn reg8_operand(r: usize, has_rex: bool) -> Operand {
if !has_rex && (4..=7).contains(&r) {
Operand::Reg8Hi(r - 4)
} else {
Operand::Reg(r)
}
}
#[derive(Clone, Copy, PartialEq, Eq)]
enum AluOp {
Add,
Or,
And,
Sub,
Xor,
Cmp,
Test,
}
#[derive(Clone, Copy)]
enum SseOp {
Add,
Sub,
Mul,
Div,
Min,
Max,
Sqrt,
}
#[derive(Clone, Copy)]
enum BitOp {
And,
Andn,
Or,
Xor,
}
#[derive(Clone, Copy)]
enum BitTestOp {
Bt,
Bts,
Btr,
Btc,
}
#[derive(Clone, Copy)]
enum FpuOp {
Add,
Mul,
Com,
Comp,
Sub,
SubR,
Div,
DivR,
}
impl FpuOp {
fn from_reg(reg: u8) -> Self {
match reg & 7 {
0 => Self::Add,
1 => Self::Mul,
2 => Self::Com,
3 => Self::Comp,
4 => Self::Sub,
5 => Self::SubR,
6 => Self::Div,
_ => Self::DivR,
}
}
fn from_reg_reversed(reg: u8) -> Option<Self> {
Some(match reg & 7 {
0 => Self::Add,
1 => Self::Mul,
4 => Self::SubR,
5 => Self::Sub,
6 => Self::DivR,
7 => Self::Div,
_ => return None,
})
}
}
fn fpu_binop(op: FpuOp, dst: f64, src: f64) -> f64 {
match op {
FpuOp::Add => dst + src,
FpuOp::Mul => dst * src,
FpuOp::Sub => dst - src,
FpuOp::SubR => src - dst,
FpuOp::Div => dst / src,
FpuOp::DivR => src / dst,
FpuOp::Com | FpuOp::Comp => dst, }
}
#[derive(Clone, Copy)]
enum MemWidth {
F32,
F64,
I16,
I32,
}
#[allow(clippy::cast_precision_loss)] fn f80_to_f64(mantissa: u64, exp: u16, sign: bool) -> f64 {
let value = if exp == 0 && mantissa == 0 {
0.0
} else if exp == 0x7fff {
if mantissa == (1u64 << 63) {
f64::INFINITY
} else {
f64::NAN
}
} else {
let m = mantissa as f64 / (1u64 << 63) as f64;
m * 2f64.powi(i32::from(exp) - 16383)
};
if sign { -value } else { value }
}
fn f64_to_f80_bytes(v: f64) -> [u8; 10] {
let bits = v.to_bits();
let sign = bits >> 63;
let biased_exp = (bits >> 52) & 0x7ff;
let frac = bits & 0x000f_ffff_ffff_ffff;
let (mantissa, exp): (u64, u64) = if biased_exp == 0x7ff {
if frac == 0 {
(1u64 << 63, 0x7fff)
} else {
(0xC000_0000_0000_0000, 0x7fff)
}
} else if biased_exp == 0 {
if frac == 0 {
(0, 0)
} else {
let shift = frac.leading_zeros();
let mantissa = frac << shift;
let unbiased = -1011i64 - i64::from(shift);
(mantissa, (unbiased + 16383) as u64)
}
} else {
let mantissa = (1u64 << 63) | (frac << 11);
let unbiased = biased_exp as i64 - 1023;
(mantissa, (unbiased + 16383) as u64)
};
let mut out = [0u8; 10];
out[0..8].copy_from_slice(&mantissa.to_le_bytes());
let exp16 = (exp as u16) | ((sign as u16) << 15);
out[8..10].copy_from_slice(&exp16.to_le_bytes());
out
}
fn fpu_read_f32(mem: &GuestMemory, addr: u64) -> Result<f64, Step> {
let mut b = [0u8; 4];
mem.read(addr, &mut b)
.map_err(|_| Step::Fault { addr, write: false })?;
Ok(f64::from(f32::from_le_bytes(b)))
}
#[allow(clippy::cast_possible_truncation)] fn fpu_write_f32(mem: &mut GuestMemory, addr: u64, v: f64) -> Result<(), Step> {
let bytes = (v as f32).to_le_bytes();
mem.write(addr, &bytes)
.map_err(|_| Step::Fault { addr, write: true })
}
fn fpu_read_f64(mem: &GuestMemory, addr: u64) -> Result<f64, Step> {
let mut b = [0u8; 8];
mem.read(addr, &mut b)
.map_err(|_| Step::Fault { addr, write: false })?;
Ok(f64::from_le_bytes(b))
}
fn fpu_write_f64(mem: &mut GuestMemory, addr: u64, v: f64) -> Result<(), Step> {
mem.write(addr, &v.to_le_bytes())
.map_err(|_| Step::Fault { addr, write: true })
}
fn fpu_read_f80(mem: &GuestMemory, addr: u64) -> Result<f64, Step> {
let mut b = [0u8; 10];
mem.read(addr, &mut b)
.map_err(|_| Step::Fault { addr, write: false })?;
let mantissa = u64::from_le_bytes(b[0..8].try_into().unwrap());
let se = u16::from_le_bytes(b[8..10].try_into().unwrap());
Ok(f80_to_f64(mantissa, se & 0x7fff, se & 0x8000 != 0))
}
fn fpu_write_f80(mem: &mut GuestMemory, addr: u64, v: f64) -> Result<(), Step> {
mem.write(addr, &f64_to_f80_bytes(v))
.map_err(|_| Step::Fault { addr, write: true })
}
fn fpu_read_int(mem: &GuestMemory, addr: u64, width: u32) -> Result<i64, Step> {
let n = (width / 8) as usize;
let mut b = [0u8; 8];
mem.read(addr, &mut b[..n])
.map_err(|_| Step::Fault { addr, write: false })?;
Ok(sign_extend_w(u64::from_le_bytes(b), width))
}
fn fpu_write_int(mem: &mut GuestMemory, addr: u64, val: i64, width: u32) -> Result<(), Step> {
let n = (width / 8) as usize;
let bytes = (val as u64).to_le_bytes();
mem.write(addr, &bytes[..n])
.map_err(|_| Step::Fault { addr, write: true })
}
#[allow(clippy::cast_precision_loss)] fn fpu_read_src(mem: &GuestMemory, addr: u64, w: MemWidth) -> Result<f64, Step> {
match w {
MemWidth::F32 => fpu_read_f32(mem, addr),
MemWidth::F64 => fpu_read_f64(mem, addr),
MemWidth::I16 => fpu_read_int(mem, addr, 16).map(|v| v as f64),
MemWidth::I32 => fpu_read_int(mem, addr, 32).map(|v| v as f64),
}
}
#[allow(clippy::many_single_char_names)] fn f64_lane_binop(dst: u128, src: u128, lanes: usize, f: impl Fn(f64, f64) -> f64) -> u128 {
let (d, s) = (dst.to_le_bytes(), src.to_le_bytes());
let mut out = d;
for i in 0..lanes {
let o = i * 8;
let a = f64::from_le_bytes(d[o..o + 8].try_into().unwrap());
let b = f64::from_le_bytes(s[o..o + 8].try_into().unwrap());
out[o..o + 8].copy_from_slice(&f(a, b).to_le_bytes());
}
u128::from_le_bytes(out)
}
#[allow(clippy::many_single_char_names)] fn f64_lane_unop(dst: u128, src: u128, lanes: usize, f: impl Fn(f64) -> f64) -> u128 {
let (d, s) = (dst.to_le_bytes(), src.to_le_bytes());
let mut out = d;
for i in 0..lanes {
let o = i * 8;
let b = f64::from_le_bytes(s[o..o + 8].try_into().unwrap());
out[o..o + 8].copy_from_slice(&f(b).to_le_bytes());
}
u128::from_le_bytes(out)
}
#[allow(clippy::many_single_char_names)] fn f32_lane_binop(dst: u128, src: u128, lanes: usize, f: impl Fn(f32, f32) -> f32) -> u128 {
let (d, s) = (dst.to_le_bytes(), src.to_le_bytes());
let mut out = d;
for i in 0..lanes {
let o = i * 4;
let a = f32::from_le_bytes(d[o..o + 4].try_into().unwrap());
let b = f32::from_le_bytes(s[o..o + 4].try_into().unwrap());
out[o..o + 4].copy_from_slice(&f(a, b).to_le_bytes());
}
u128::from_le_bytes(out)
}
#[allow(clippy::many_single_char_names)] fn f32_lane_unop(dst: u128, src: u128, lanes: usize, f: impl Fn(f32) -> f32) -> u128 {
let (d, s) = (dst.to_le_bytes(), src.to_le_bytes());
let mut out = d;
for i in 0..lanes {
let o = i * 4;
let b = f32::from_le_bytes(s[o..o + 4].try_into().unwrap());
out[o..o + 4].copy_from_slice(&f(b).to_le_bytes());
}
u128::from_le_bytes(out)
}
fn u64_from_le(bytes: &[u8]) -> u64 {
let mut b = [0u8; 8];
b[..bytes.len()].copy_from_slice(bytes);
u64::from_le_bytes(b)
}
fn unpck(dst: u128, src: u128, lane_bytes: usize, high: bool) -> u128 {
let (d, s) = (dst.to_le_bytes(), src.to_le_bytes());
let half = 16 / lane_bytes / 2;
let base = if high { half } else { 0 };
let mut out = [0u8; 16];
for i in 0..half {
let src_off = (base + i) * lane_bytes;
let o0 = (2 * i) * lane_bytes;
let o1 = (2 * i + 1) * lane_bytes;
out[o0..o0 + lane_bytes].copy_from_slice(&d[src_off..src_off + lane_bytes]);
out[o1..o1 + lane_bytes].copy_from_slice(&s[src_off..src_off + lane_bytes]);
}
u128::from_le_bytes(out)
}
fn pack_shift_right(v: u128, lane_bits: u32, count: u32) -> u128 {
if count >= lane_bits {
return 0;
}
let mask = (1u128 << lane_bits) - 1;
let lanes = 128 / lane_bits;
let mut out = 0u128;
for i in 0..lanes {
let lane = (v >> (i * lane_bits)) & mask;
out |= (lane >> count) << (i * lane_bits);
}
out
}
fn pack_shift_left(v: u128, lane_bits: u32, count: u32) -> u128 {
if count >= lane_bits {
return 0;
}
let mask = (1u128 << lane_bits) - 1;
let lanes = 128 / lane_bits;
let mut out = 0u128;
for i in 0..lanes {
let lane = (v >> (i * lane_bits)) & mask;
out |= ((lane << count) & mask) << (i * lane_bits);
}
out
}
const fn mask_w(v: u64, width: u32) -> u64 {
match width {
8 => v & 0xff,
16 => v & 0xffff,
32 => v & 0xffff_ffff,
_ => v,
}
}
const fn sign_extend_128(v: u128, bits: u32) -> i128 {
let shift = 128 - bits;
((v << shift) as i128) >> shift
}
const fn fits_signed(v: i128, width: u32) -> bool {
let max = (1i128 << (width - 1)) - 1;
let min = -(1i128 << (width - 1));
v >= min && v <= max
}
const fn fits_unsigned(v: u128, width: u32) -> bool {
v < (1u128 << width)
}
fn parity(v: u8) -> bool {
v.count_ones().is_multiple_of(2)
}
fn sign_bit(v: u64, width: u32) -> bool {
(v >> (width - 1)) & 1 == 1
}
const fn sign_extend_w(v: u64, width: u32) -> i64 {
if width >= 64 {
v as i64
} else {
let shift = 64 - width;
((v << shift) as i64) >> shift
}
}
fn fetch_u8(mem: &GuestMemory, pc: u64) -> Result<(u8, u64), Step> {
let mut b = [0u8; 1];
mem.read(pc, &mut b).map_err(|_| Step::Fault {
addr: pc,
write: false,
})?;
Ok((b[0], pc + 1))
}
fn fetch_i8(mem: &GuestMemory, pc: u64) -> Result<(i8, u64), Step> {
let (b, next) = fetch_u8(mem, pc)?;
Ok((b as i8, next))
}
fn fetch_u16(mem: &GuestMemory, pc: u64) -> Result<(u16, u64), Step> {
let mut b = [0u8; 2];
mem.read(pc, &mut b).map_err(|_| Step::Fault {
addr: pc,
write: false,
})?;
Ok((u16::from_le_bytes(b), pc + 2))
}
fn fetch_i16(mem: &GuestMemory, pc: u64) -> Result<(i16, u64), Step> {
let (v, next) = fetch_u16(mem, pc)?;
Ok((v as i16, next))
}
fn fetch_u32(mem: &GuestMemory, pc: u64) -> Result<(u32, u64), Step> {
let mut b = [0u8; 4];
mem.read(pc, &mut b).map_err(|_| Step::Fault {
addr: pc,
write: false,
})?;
Ok((u32::from_le_bytes(b), pc + 4))
}
fn fetch_i32(mem: &GuestMemory, pc: u64) -> Result<(i32, u64), Step> {
let (v, next) = fetch_u32(mem, pc)?;
Ok((v as i32, next))
}
fn fetch_u64(mem: &GuestMemory, pc: u64) -> Result<(u64, u64), Step> {
let mut b = [0u8; 8];
mem.read(pc, &mut b).map_err(|_| Step::Fault {
addr: pc,
write: false,
})?;
Ok((u64::from_le_bytes(b), pc + 8))
}
fn imm_for_width(mem: &GuestMemory, pc: u64, width: u32) -> Result<(i64, u64), Step> {
match width {
8 => {
let (v, p) = fetch_i8(mem, pc)?;
Ok((i64::from(v), p))
}
16 => {
let (v, p) = fetch_i16(mem, pc)?;
Ok((i64::from(v), p))
}
_ => {
let (v, p) = fetch_i32(mem, pc)?;
Ok((i64::from(v), p))
}
}
}
macro_rules! fetch {
($e:expr) => {
match $e {
Ok(v) => v,
Err(s) => return s,
}
};
}
#[derive(Clone)]
#[allow(clippy::struct_excessive_bools)] struct X86Interp {
gpr: [u64; 16],
xmm: [u128; 16],
rip: u64,
flags: Flags,
df: bool,
fs_base: u64,
st: [f64; 8],
fpu_top: u8,
fpu_c0: bool,
fpu_c1: bool,
fpu_c2: bool,
fpu_c3: bool,
fpu_cw: u16,
tsc: u64,
prng: u64,
}
impl X86Interp {
fn new(entry: u64, stack: u64) -> Self {
let mut gpr = [0u64; 16];
gpr[RSP] = stack;
Self {
gpr,
xmm: [0u128; 16],
rip: entry,
flags: Flags::default(),
df: false,
fs_base: 0,
st: [0.0; 8],
fpu_top: 0,
fpu_c0: false,
fpu_c1: false,
fpu_c2: false,
fpu_c3: false,
fpu_cw: 0x037F, tsc: 0,
prng: 0x9E37_79B9_7F4A_7C15, }
}
fn next(&mut self, pc: u64) -> Step {
self.rip = pc;
Step::Next
}
fn jump(&mut self, target: u64) -> Step {
self.rip = target;
Step::Branched
}
fn push(&mut self, mem: &mut GuestMemory, val: u64) -> Result<(), Step> {
let sp = self.gpr[RSP].wrapping_sub(8);
mem.write(sp, &val.to_le_bytes()).map_err(|_| Step::Fault {
addr: sp,
write: true,
})?;
self.gpr[RSP] = sp;
Ok(())
}
fn pop(&mut self, mem: &GuestMemory) -> Result<u64, Step> {
let sp = self.gpr[RSP];
let mut b = [0u8; 8];
mem.read(sp, &mut b).map_err(|_| Step::Fault {
addr: sp,
write: false,
})?;
self.gpr[RSP] = sp.wrapping_add(8);
Ok(u64::from_le_bytes(b))
}
fn decode_modrm(&self, mem: &GuestMemory, pc: u64, rex: Rex) -> Result<(ModRm, u64), Step> {
let (byte, pc) = fetch_u8(mem, pc)?;
let md = byte >> 6;
let reg = usize::from((byte >> 3) & 7) | (usize::from(rex.r) << 3);
let rm_field = byte & 7;
if md == 0b11 {
let rm = usize::from(rm_field) | (usize::from(rex.b) << 3);
return Ok((
ModRm {
reg,
kind: RmKind::Reg(rm),
},
pc,
));
}
if rm_field == 0b100 {
let (sib, pc) = fetch_u8(mem, pc)?;
let scale = 1u64 << (sib >> 6);
let idx_field = (sib >> 3) & 7;
let base_field = sib & 7;
let index = if idx_field == 0b100 && !rex.x {
None
} else {
Some(usize::from(idx_field) | (usize::from(rex.x) << 3))
};
let (base, disp, pc) = if base_field == 0b101 && md == 0b00 {
let (d, pc) = fetch_i32(mem, pc)?;
(None, i64::from(d), pc)
} else {
let b = usize::from(base_field) | (usize::from(rex.b) << 3);
match md {
0b01 => {
let (d, pc) = fetch_i8(mem, pc)?;
(Some(b), i64::from(d), pc)
}
0b10 => {
let (d, pc) = fetch_i32(mem, pc)?;
(Some(b), i64::from(d), pc)
}
_ => (Some(b), 0i64, pc),
}
};
let base_val = base.map_or(0, |b| self.gpr[b]);
let index_val = index.map_or(0, |i| self.gpr[i]);
let addr = (base_val.wrapping_add(index_val.wrapping_mul(scale)) as i64)
.wrapping_add(disp) as u64;
return Ok((
ModRm {
reg,
kind: RmKind::Mem(addr),
},
pc,
));
}
if rm_field == 0b101 && md == 0b00 {
let (disp, pc) = fetch_i32(mem, pc)?;
return Ok((
ModRm {
reg,
kind: RmKind::MemRip(i64::from(disp)),
},
pc,
));
}
let base = usize::from(rm_field) | (usize::from(rex.b) << 3);
let (disp, pc) = match md {
0b01 => {
let (d, pc) = fetch_i8(mem, pc)?;
(i64::from(d), pc)
}
0b10 => {
let (d, pc) = fetch_i32(mem, pc)?;
(i64::from(d), pc)
}
_ => (0i64, pc),
};
let addr = (self.gpr[base] as i64).wrapping_add(disp) as u64;
Ok((
ModRm {
reg,
kind: RmKind::Mem(addr),
},
pc,
))
}
fn read_operand(&self, mem: &GuestMemory, op: Operand, width: u32) -> Result<u64, Step> {
match op {
Operand::Reg(r) => Ok(mask_w(self.gpr[r], width)),
Operand::Reg8Hi(r) => Ok((self.gpr[r] >> 8) & 0xff),
Operand::Mem(a) => {
let n = (width / 8) as usize;
let mut b = [0u8; 8];
mem.read(a, &mut b[..n]).map_err(|_| Step::Fault {
addr: a,
write: false,
})?;
Ok(u64::from_le_bytes(b))
}
}
}
fn write_operand(
&mut self,
mem: &mut GuestMemory,
op: Operand,
val: u64,
width: u32,
) -> Result<(), Step> {
match op {
Operand::Reg(r) => {
self.gpr[r] = match width {
8 => (self.gpr[r] & !0xffu64) | (val & 0xff),
16 => (self.gpr[r] & !0xffffu64) | (val & 0xffff),
_ => mask_w(val, width),
};
Ok(())
}
Operand::Reg8Hi(r) => {
self.gpr[r] = (self.gpr[r] & !0xff00u64) | ((val & 0xff) << 8);
Ok(())
}
Operand::Mem(a) => {
let n = (width / 8) as usize;
let bytes = val.to_le_bytes();
mem.write(a, &bytes[..n]).map_err(|_| Step::Fault {
addr: a,
write: true,
})
}
}
}
fn xmm_read_lo(&self, mem: &GuestMemory, op: Operand, width: u32) -> Result<u64, Step> {
match op {
Operand::Reg(r) => Ok(mask_w(self.xmm[r] as u64, width)),
Operand::Mem(a) => {
let n = (width / 8) as usize;
let mut b = [0u8; 8];
mem.read(a, &mut b[..n]).map_err(|_| Step::Fault {
addr: a,
write: false,
})?;
Ok(u64::from_le_bytes(b))
}
Operand::Reg8Hi(_) => unreachable!("SSE decode never yields an 8-bit-high operand"),
}
}
fn xmm_read128(&self, mem: &GuestMemory, op: Operand) -> Result<u128, Step> {
match op {
Operand::Reg(r) => Ok(self.xmm[r]),
Operand::Mem(a) => {
let mut b = [0u8; 16];
mem.read(a, &mut b).map_err(|_| Step::Fault {
addr: a,
write: false,
})?;
Ok(u128::from_le_bytes(b))
}
Operand::Reg8Hi(_) => unreachable!("SSE decode never yields an 8-bit-high operand"),
}
}
fn xmm_write128(&mut self, mem: &mut GuestMemory, op: Operand, val: u128) -> Result<(), Step> {
match op {
Operand::Reg(r) => {
self.xmm[r] = val;
Ok(())
}
Operand::Mem(a) => mem.write(a, &val.to_le_bytes()).map_err(|_| Step::Fault {
addr: a,
write: true,
}),
Operand::Reg8Hi(_) => unreachable!("SSE decode never yields an 8-bit-high operand"),
}
}
fn add_flags(&mut self, a: u64, b: u64, width: u32) -> u64 {
if width == 64 {
let (r, cf) = a.overflowing_add(b);
self.flags = Flags {
cf,
zf: r == 0,
sf: sign_bit(r, 64),
of: (((a ^ r) & (b ^ r)) >> 63) & 1 == 1,
pf: parity(r as u8),
};
r
} else {
let (a32, b32) = (a as u32, b as u32);
let (r, cf) = a32.overflowing_add(b32);
self.flags = Flags {
cf,
zf: r == 0,
sf: sign_bit(u64::from(r), 32),
of: (((a32 ^ r) & (b32 ^ r)) >> 31) & 1 == 1,
pf: parity(r as u8),
};
u64::from(r)
}
}
fn sub_flags(&mut self, a: u64, b: u64, width: u32) -> u64 {
if width == 64 {
let r = a.wrapping_sub(b);
self.flags = Flags {
cf: a < b,
zf: r == 0,
sf: sign_bit(r, 64),
of: (((a ^ b) & (a ^ r)) >> 63) & 1 == 1,
pf: parity(r as u8),
};
r
} else {
let (a32, b32) = (a as u32, b as u32);
let r = a32.wrapping_sub(b32);
self.flags = Flags {
cf: a32 < b32,
zf: r == 0,
sf: sign_bit(u64::from(r), 32),
of: (((a32 ^ b32) & (a32 ^ r)) >> 31) & 1 == 1,
pf: parity(r as u8),
};
u64::from(r)
}
}
fn logic_flags(&mut self, r: u64, width: u32) -> u64 {
let r = mask_w(r, width);
self.flags = Flags {
cf: false,
of: false,
zf: r == 0,
sf: sign_bit(r, width),
pf: parity(r as u8),
};
r
}
fn apply_alu(&mut self, op: AluOp, a: u64, b: u64, width: u32) -> u64 {
match op {
AluOp::Add => self.add_flags(a, b, width),
AluOp::Sub | AluOp::Cmp => self.sub_flags(a, b, width),
AluOp::And | AluOp::Test => self.logic_flags(a & b, width),
AluOp::Or => self.logic_flags(a | b, width),
AluOp::Xor => self.logic_flags(a ^ b, width),
}
}
fn inc_dec_flags(&mut self, a: u64, sub: bool, width: u32) -> u64 {
let saved_cf = self.flags.cf;
let r = if sub {
self.sub_flags(a, 1, width)
} else {
self.add_flags(a, 1, width)
};
self.flags.cf = saved_cf;
r
}
fn shl_flags(&mut self, a: u64, amt: u8, width: u32) -> u64 {
let a = mask_w(a, width);
let amtu = u32::from(amt);
let cf = (a >> (width - amtu)) & 1 == 1;
let r = mask_w(a << amtu, width);
self.flags.cf = cf;
self.flags.zf = r == 0;
self.flags.sf = sign_bit(r, width);
self.flags.pf = parity(r as u8);
if amt == 1 {
self.flags.of = sign_bit(r, width) != cf;
}
r
}
fn shr_flags(&mut self, a: u64, amt: u8, width: u32) -> u64 {
let a = mask_w(a, width);
let amtu = u32::from(amt);
let cf = (a >> (amtu - 1)) & 1 == 1;
let r = mask_w(a >> amtu, width);
self.flags.cf = cf;
self.flags.zf = r == 0;
self.flags.sf = sign_bit(r, width);
self.flags.pf = parity(r as u8);
if amt == 1 {
self.flags.of = sign_bit(a, width);
}
r
}
fn sar_flags(&mut self, a: u64, amt: u8, width: u32) -> u64 {
let amtu = u32::from(amt);
let signed = sign_extend_w(mask_w(a, width), width);
let cf = ((signed >> (amtu - 1)) & 1) == 1;
let r = mask_w((signed >> amtu) as u64, width);
self.flags.cf = cf;
self.flags.zf = r == 0;
self.flags.sf = sign_bit(r, width);
self.flags.pf = parity(r as u8);
if amt == 1 {
self.flags.of = false;
}
r
}
fn cond_holds(&self, cc: u8) -> bool {
let f = &self.flags;
match cc & 0xf {
0x0 => f.of,
0x1 => !f.of,
0x2 => f.cf,
0x3 => !f.cf,
0x4 => f.zf,
0x5 => !f.zf,
0x6 => f.cf || f.zf,
0x7 => !f.cf && !f.zf,
0x8 => f.sf,
0x9 => !f.sf,
0xA => f.pf,
0xB => !f.pf,
0xC => f.sf != f.of,
0xD => f.sf == f.of,
0xE => f.zf || (f.sf != f.of),
_ => !f.zf && (f.sf == f.of), }
}
fn lea(&mut self, mem: &mut GuestMemory, pc: u64, rex: Rex, width: u32) -> Step {
let (modrm, pc2) = fetch!(self.decode_modrm(mem, pc, rex));
let addr = match resolve(modrm.kind, pc2) {
Operand::Mem(a) => a,
Operand::Reg(_) | Operand::Reg8Hi(_) => return Step::Illegal, };
self.gpr[modrm.reg] = mask_w(addr, width);
self.next(pc2)
}
fn mov_rm_gv(&mut self, mem: &mut GuestMemory, pc: u64, rex: Rex, width: u32) -> Step {
let (modrm, pc2) = fetch!(self.decode_modrm(mem, pc, rex));
let rm_op = resolve(modrm.kind, pc2);
let val = mask_w(self.gpr[modrm.reg], width);
fetch!(self.write_operand(mem, rm_op, val, width));
self.next(pc2)
}
fn mov_gv_rm(&mut self, mem: &mut GuestMemory, pc: u64, rex: Rex, width: u32) -> Step {
let (modrm, pc2) = fetch!(self.decode_modrm(mem, pc, rex));
let rm_op = resolve(modrm.kind, pc2);
let val = fetch!(self.read_operand(mem, rm_op, width));
self.gpr[modrm.reg] = mask_w(val, width);
self.next(pc2)
}
fn mov_imm(&mut self, mem: &mut GuestMemory, pc: u64, rex: Rex, width: u32) -> Step {
let (modrm, pc2) = fetch!(self.decode_modrm(mem, pc, rex));
if modrm.reg != 0 {
return Step::Illegal; }
let (imm, pc3) = fetch!(fetch_i32(mem, pc2));
let rm_op = resolve(modrm.kind, pc3);
let val = mask_w(i64::from(imm) as u64, width);
fetch!(self.write_operand(mem, rm_op, val, width));
self.next(pc3)
}
fn alu_rm_gv(
&mut self,
mem: &mut GuestMemory,
pc: u64,
rex: Rex,
width: u32,
op: AluOp,
store: bool,
) -> Step {
let (modrm, pc2) = fetch!(self.decode_modrm(mem, pc, rex));
let rm_op = resolve(modrm.kind, pc2);
let a = fetch!(self.read_operand(mem, rm_op, width));
let b = mask_w(self.gpr[modrm.reg], width);
let r = self.apply_alu(op, a, b, width);
if store {
fetch!(self.write_operand(mem, rm_op, r, width));
}
self.next(pc2)
}
fn alu_gv_rm(
&mut self,
mem: &mut GuestMemory,
pc: u64,
rex: Rex,
width: u32,
op: AluOp,
) -> Step {
let (modrm, pc2) = fetch!(self.decode_modrm(mem, pc, rex));
let rm_op = resolve(modrm.kind, pc2);
let b = fetch!(self.read_operand(mem, rm_op, width));
let a = mask_w(self.gpr[modrm.reg], width);
let r = self.apply_alu(op, a, b, width);
if op != AluOp::Cmp {
self.gpr[modrm.reg] = mask_w(r, width);
}
self.next(pc2)
}
fn group1_imm(
&mut self,
mem: &mut GuestMemory,
pc: u64,
rex: Rex,
has_rex: bool,
width: u32,
imm8: bool,
) -> Step {
let (modrm, pc2) = fetch!(self.decode_modrm(mem, pc, rex));
let (imm, pc3): (i64, u64) = if imm8 {
let (v, p) = fetch!(fetch_i8(mem, pc2));
(i64::from(v), p)
} else {
fetch!(imm_for_width(mem, pc2, width))
};
let op = match modrm.reg {
0 => AluOp::Add,
1 => AluOp::Or,
4 => AluOp::And,
5 => AluOp::Sub,
6 => AluOp::Xor,
7 => AluOp::Cmp,
_ => return Step::Illegal, };
let rm_op = if width == 8 {
resolve8(modrm.kind, pc3, has_rex)
} else {
resolve(modrm.kind, pc3)
};
let a = fetch!(self.read_operand(mem, rm_op, width));
let b = mask_w(imm as u64, width);
let r = self.apply_alu(op, a, b, width);
if op != AluOp::Cmp {
fetch!(self.write_operand(mem, rm_op, r, width));
}
self.next(pc3)
}
fn alu_rm_gv8(
&mut self,
mem: &mut GuestMemory,
pc: u64,
rex: Rex,
has_rex: bool,
op: AluOp,
store: bool,
) -> Step {
let (modrm, pc2) = fetch!(self.decode_modrm(mem, pc, rex));
let rm_op = resolve8(modrm.kind, pc2, has_rex);
let reg_op = reg8_operand(modrm.reg, has_rex);
let a = fetch!(self.read_operand(mem, rm_op, 8));
let b = fetch!(self.read_operand(mem, reg_op, 8));
let r = self.apply_alu(op, a, b, 8);
if store {
fetch!(self.write_operand(mem, rm_op, r, 8));
}
self.next(pc2)
}
fn alu_gv_rm8(
&mut self,
mem: &mut GuestMemory,
pc: u64,
rex: Rex,
has_rex: bool,
op: AluOp,
) -> Step {
let (modrm, pc2) = fetch!(self.decode_modrm(mem, pc, rex));
let rm_op = resolve8(modrm.kind, pc2, has_rex);
let reg_op = reg8_operand(modrm.reg, has_rex);
let b = fetch!(self.read_operand(mem, rm_op, 8));
let a = fetch!(self.read_operand(mem, reg_op, 8));
let r = self.apply_alu(op, a, b, 8);
if op != AluOp::Cmp {
fetch!(self.write_operand(mem, reg_op, r, 8));
}
self.next(pc2)
}
fn xchg(
&mut self,
mem: &mut GuestMemory,
pc: u64,
rex: Rex,
has_rex: bool,
width: u32,
) -> Step {
let (modrm, pc2) = fetch!(self.decode_modrm(mem, pc, rex));
let (rm_op, reg_op) = if width == 8 {
(
resolve8(modrm.kind, pc2, has_rex),
reg8_operand(modrm.reg, has_rex),
)
} else {
(resolve(modrm.kind, pc2), Operand::Reg(modrm.reg))
};
let a = fetch!(self.read_operand(mem, rm_op, width));
let b = fetch!(self.read_operand(mem, reg_op, width));
fetch!(self.write_operand(mem, rm_op, b, width));
fetch!(self.write_operand(mem, reg_op, a, width));
self.next(pc2)
}
fn group3(
&mut self,
mem: &mut GuestMemory,
pc: u64,
rex: Rex,
has_rex: bool,
width: u32,
) -> Step {
let (modrm, pc2) = fetch!(self.decode_modrm(mem, pc, rex));
let rm_op_at = |end_pc| {
if width == 8 {
resolve8(modrm.kind, end_pc, has_rex)
} else {
resolve(modrm.kind, end_pc)
}
};
match modrm.reg {
0 | 1 => {
let (imm, pc3) = fetch!(imm_for_width(mem, pc2, width));
let rm_op = rm_op_at(pc3);
let a = fetch!(self.read_operand(mem, rm_op, width));
self.apply_alu(AluOp::Test, a, mask_w(imm as u64, width), width);
self.next(pc3)
}
2 => {
let rm_op = rm_op_at(pc2);
let a = fetch!(self.read_operand(mem, rm_op, width));
let r = mask_w(!a, width);
fetch!(self.write_operand(mem, rm_op, r, width));
self.next(pc2)
}
3 => {
let rm_op = rm_op_at(pc2);
let a = fetch!(self.read_operand(mem, rm_op, width));
let r = self.sub_flags(0, a, width); fetch!(self.write_operand(mem, rm_op, r, width));
self.next(pc2)
}
4 => self.mul_op(mem, rm_op_at(pc2), width, false, pc2),
5 => self.mul_op(mem, rm_op_at(pc2), width, true, pc2),
6 => self.div_op(mem, rm_op_at(pc2), width, false, pc2),
_ => self.div_op(mem, rm_op_at(pc2), width, true, pc2), }
}
fn group4(&mut self, mem: &mut GuestMemory, pc: u64, rex: Rex, has_rex: bool) -> Step {
let (modrm, pc2) = fetch!(self.decode_modrm(mem, pc, rex));
match modrm.reg {
0 | 1 => {
let rm_op = resolve8(modrm.kind, pc2, has_rex);
let a = fetch!(self.read_operand(mem, rm_op, 8));
let r = self.inc_dec_flags(a, modrm.reg == 1, 8);
fetch!(self.write_operand(mem, rm_op, r, 8));
self.next(pc2)
}
_ => Step::Illegal,
}
}
fn group5(&mut self, mem: &mut GuestMemory, pc: u64, rex: Rex, width: u32) -> Step {
let (modrm, pc2) = fetch!(self.decode_modrm(mem, pc, rex));
match modrm.reg {
0 | 1 => {
let rm_op = resolve(modrm.kind, pc2);
let a = fetch!(self.read_operand(mem, rm_op, width));
let r = self.inc_dec_flags(a, modrm.reg == 1, width);
fetch!(self.write_operand(mem, rm_op, r, width));
self.next(pc2)
}
2 => {
let rm_op = resolve(modrm.kind, pc2);
let target = fetch!(self.read_operand(mem, rm_op, 64));
fetch!(self.push(mem, pc2));
self.jump(target)
}
4 => {
let rm_op = resolve(modrm.kind, pc2);
let target = fetch!(self.read_operand(mem, rm_op, 64));
self.jump(target)
}
6 => {
let rm_op = resolve(modrm.kind, pc2);
let val = fetch!(self.read_operand(mem, rm_op, 64));
fetch!(self.push(mem, val));
self.next(pc2)
}
_ => Step::Illegal, }
}
fn write_wide_result(&mut self, width: u32, p: u128) {
let lo = p as u64;
let hi = (p >> width) as u64;
match width {
8 => self.gpr[RAX] = (self.gpr[RAX] & !0xffffu64) | (lo & 0xffff),
16 => {
self.gpr[RAX] = (self.gpr[RAX] & !0xffffu64) | (lo & 0xffff);
self.gpr[RDX] = (self.gpr[RDX] & !0xffffu64) | (hi & 0xffff);
}
32 => {
self.gpr[RAX] = lo & 0xffff_ffff;
self.gpr[RDX] = hi & 0xffff_ffff;
}
_ => {
self.gpr[RAX] = lo;
self.gpr[RDX] = hi;
}
}
}
fn mul_op(
&mut self,
mem: &GuestMemory,
rm_op: Operand,
width: u32,
signed: bool,
pc2: u64,
) -> Step {
let src = fetch!(self.read_operand(mem, rm_op, width));
let a = mask_w(self.gpr[RAX], width);
let cf = if signed {
let av = sign_extend_128(u128::from(a), width);
let bv = sign_extend_128(u128::from(src), width);
let p = av * bv;
self.write_wide_result(width, p as u128);
!fits_signed(p, width)
} else {
let p = u128::from(a) * u128::from(src);
self.write_wide_result(width, p);
!fits_unsigned(p, width)
};
self.flags.cf = cf;
self.flags.of = cf;
self.next(pc2)
}
fn write_div_result(&mut self, width: u32, q: u64, r: u64) {
match width {
8 => self.gpr[RAX] = (self.gpr[RAX] & !0xffffu64) | (q & 0xff) | ((r & 0xff) << 8),
16 => {
self.gpr[RAX] = (self.gpr[RAX] & !0xffffu64) | (q & 0xffff);
self.gpr[RDX] = (self.gpr[RDX] & !0xffffu64) | (r & 0xffff);
}
32 => {
self.gpr[RAX] = q & 0xffff_ffff;
self.gpr[RDX] = r & 0xffff_ffff;
}
_ => {
self.gpr[RAX] = q;
self.gpr[RDX] = r;
}
}
}
fn div_op(
&mut self,
mem: &GuestMemory,
rm_op: Operand,
width: u32,
signed: bool,
pc2: u64,
) -> Step {
let divisor = fetch!(self.read_operand(mem, rm_op, width));
let bits = width * 2;
let dividend: u128 = match width {
8 => u128::from(self.gpr[RAX] & 0xffff),
16 => u128::from(((self.gpr[RDX] & 0xffff) << 16) | (self.gpr[RAX] & 0xffff)),
32 => u128::from(((self.gpr[RDX] & 0xffff_ffff) << 32) | (self.gpr[RAX] & 0xffff_ffff)),
_ => (u128::from(self.gpr[RDX]) << 64) | u128::from(self.gpr[RAX]),
};
if signed {
let dividend_s = sign_extend_128(dividend, bits);
let divisor_s = sign_extend_128(u128::from(divisor), width);
if divisor_s == 0 {
return Step::Illegal;
}
let q = dividend_s / divisor_s;
let r = dividend_s % divisor_s;
if !fits_signed(q, width) {
return Step::Illegal;
}
self.write_div_result(width, q as u64, r as u64);
} else {
let divisor_u = u128::from(divisor);
if divisor_u == 0 {
return Step::Illegal;
}
let q = dividend / divisor_u;
let r = dividend % divisor_u;
if !fits_unsigned(q, width) {
return Step::Illegal;
}
self.write_div_result(width, q as u64, r as u64);
}
self.next(pc2)
}
fn imul_imm(
&mut self,
mem: &mut GuestMemory,
pc: u64,
rex: Rex,
width: u32,
imm8: bool,
) -> Step {
let (modrm, pc2) = fetch!(self.decode_modrm(mem, pc, rex));
let (imm, pc3): (i64, u64) = if imm8 {
let (v, p) = fetch!(fetch_i8(mem, pc2));
(i64::from(v), p)
} else {
fetch!(imm_for_width(mem, pc2, width))
};
let rm_op = resolve(modrm.kind, pc3);
let b = fetch!(self.read_operand(mem, rm_op, width));
let av = sign_extend_128(u128::from(b), width);
let bv = i128::from(imm);
let p = av * bv;
let cf = !fits_signed(p, width);
self.gpr[modrm.reg] = mask_w(p as u128 as u64, width);
self.flags.cf = cf;
self.flags.of = cf;
self.next(pc3)
}
fn group2(
&mut self,
mem: &mut GuestMemory,
pc: u64,
rex: Rex,
width: u32,
by_cl: bool,
) -> Step {
let (modrm, pc2) = fetch!(self.decode_modrm(mem, pc, rex));
if !matches!(modrm.reg, 4..=7) {
return Step::Illegal; }
let (count, pc3) = if by_cl {
(self.gpr[RCX] as u8, pc2)
} else {
fetch!(fetch_u8(mem, pc2))
};
let mask = if width == 64 { 63 } else { 31 };
let amt = count & mask;
let rm_op = resolve(modrm.kind, pc3);
if amt == 0 {
return self.next(pc3); }
let a = fetch!(self.read_operand(mem, rm_op, width));
let r = match modrm.reg {
4 | 6 => self.shl_flags(a, amt, width), 5 => self.shr_flags(a, amt, width),
_ => self.sar_flags(a, amt, width),
};
fetch!(self.write_operand(mem, rm_op, r, width));
self.next(pc3)
}
fn bit_scan(
&mut self,
mem: &GuestMemory,
pc: u64,
rex: Rex,
width: u32,
rep: u8,
reverse: bool,
) -> Step {
let (modrm, pc2) = fetch!(self.decode_modrm(mem, pc, rex));
let rm_op = resolve(modrm.kind, pc2);
let src = fetch!(self.read_operand(mem, rm_op, width));
let count_form = rep == 1; if src == 0 {
self.flags = Flags {
zf: true,
cf: count_form,
sf: false,
of: false,
pf: false,
};
if count_form {
self.gpr[modrm.reg] = mask_w(u64::from(width), width);
}
} else {
let lz_in_width = src.leading_zeros() - (64 - width);
let result = if reverse {
width - 1 - lz_in_width
} else {
src.trailing_zeros()
};
self.gpr[modrm.reg] = mask_w(u64::from(result), width);
self.flags = Flags {
zf: count_form && result == 0,
cf: false,
sf: false,
of: false,
pf: false,
};
}
self.next(pc2)
}
fn popcnt(&mut self, mem: &GuestMemory, pc: u64, rex: Rex, width: u32) -> Step {
let (modrm, pc2) = fetch!(self.decode_modrm(mem, pc, rex));
let rm_op = resolve(modrm.kind, pc2);
let src = fetch!(self.read_operand(mem, rm_op, width));
let count = src.count_ones();
self.gpr[modrm.reg] = u64::from(count);
self.flags = Flags {
zf: count == 0,
cf: false,
sf: false,
of: false,
pf: false,
};
self.next(pc2)
}
fn apply_bit_test(
&mut self,
mem: &mut GuestMemory,
rm_op: Operand,
width: u32,
bit_idx: u64,
op: BitTestOp,
) -> Result<(), Step> {
let a = self.read_operand(mem, rm_op, width)?;
let bit = (bit_idx % u64::from(width)) as u32;
self.flags.cf = (a >> bit) & 1 == 1;
let mask = 1u64 << bit;
let r = match op {
BitTestOp::Bt => return Ok(()),
BitTestOp::Bts => a | mask,
BitTestOp::Btr => a & !mask,
BitTestOp::Btc => a ^ mask,
};
self.write_operand(mem, rm_op, r, width)
}
fn bt_ev_gv(
&mut self,
mem: &mut GuestMemory,
pc: u64,
rex: Rex,
width: u32,
op: BitTestOp,
) -> Step {
let (modrm, pc2) = fetch!(self.decode_modrm(mem, pc, rex));
let rm_op = resolve(modrm.kind, pc2);
let bit_idx = self.gpr[modrm.reg];
fetch!(self.apply_bit_test(mem, rm_op, width, bit_idx, op));
self.next(pc2)
}
fn bt_group_imm(&mut self, mem: &mut GuestMemory, pc: u64, rex: Rex, width: u32) -> Step {
let (modrm, pc2) = fetch!(self.decode_modrm(mem, pc, rex));
let (imm, pc3) = fetch!(fetch_u8(mem, pc2));
let op = match modrm.reg {
4 => BitTestOp::Bt,
5 => BitTestOp::Bts,
6 => BitTestOp::Btr,
7 => BitTestOp::Btc,
_ => return Step::Illegal, };
let rm_op = resolve(modrm.kind, pc3);
fetch!(self.apply_bit_test(mem, rm_op, width, u64::from(imm), op));
self.next(pc3)
}
fn shift_double(&mut self, dest: u64, src: u64, count: u32, width: u32, left: bool) -> u64 {
let d = mask_w(dest, width);
let s = mask_w(src, width);
let (result, cf) = if left {
let wide = (u128::from(d) << width) | u128::from(s);
let result = mask_w(((wide << count) >> width) as u64, width);
(result, (d >> (width - count)) & 1 == 1)
} else {
let wide = (u128::from(s) << width) | u128::from(d);
let result = mask_w((wide >> count) as u64, width);
(result, (d >> (count - 1)) & 1 == 1)
};
self.flags.cf = cf;
self.flags.zf = result == 0;
self.flags.sf = sign_bit(result, width);
self.flags.pf = parity(result as u8);
if count == 1 {
self.flags.of = sign_bit(result, width) != sign_bit(d, width);
}
result
}
fn shld_shrd(
&mut self,
mem: &mut GuestMemory,
pc: u64,
rex: Rex,
width: u32,
left: bool,
by_cl: bool,
) -> Step {
let (modrm, pc2) = fetch!(self.decode_modrm(mem, pc, rex));
let (count, pc3) = if by_cl {
(self.gpr[RCX] as u8, pc2)
} else {
fetch!(fetch_u8(mem, pc2))
};
let mask = if width == 64 { 63 } else { 31 };
let amt = count & mask;
let rm_op = resolve(modrm.kind, pc3);
if amt == 0 {
return self.next(pc3);
}
let dest = fetch!(self.read_operand(mem, rm_op, width));
let src = mask_w(self.gpr[modrm.reg], width);
let r = self.shift_double(dest, src, u32::from(amt), width, left);
fetch!(self.write_operand(mem, rm_op, r, width));
self.next(pc3)
}
fn xadd(
&mut self,
mem: &mut GuestMemory,
pc: u64,
rex: Rex,
has_rex: bool,
width: u32,
) -> Step {
let (modrm, pc2) = fetch!(self.decode_modrm(mem, pc, rex));
let (rm_op, reg_op) = if width == 8 {
(
resolve8(modrm.kind, pc2, has_rex),
reg8_operand(modrm.reg, has_rex),
)
} else {
(resolve(modrm.kind, pc2), Operand::Reg(modrm.reg))
};
let dest = fetch!(self.read_operand(mem, rm_op, width));
let src = fetch!(self.read_operand(mem, reg_op, width));
let sum = self.add_flags(dest, src, width);
fetch!(self.write_operand(mem, reg_op, dest, width)); fetch!(self.write_operand(mem, rm_op, sum, width)); self.next(pc2)
}
fn cmpxchg(
&mut self,
mem: &mut GuestMemory,
pc: u64,
rex: Rex,
has_rex: bool,
width: u32,
) -> Step {
let (modrm, pc2) = fetch!(self.decode_modrm(mem, pc, rex));
let (rm_op, reg_op) = if width == 8 {
(
resolve8(modrm.kind, pc2, has_rex),
reg8_operand(modrm.reg, has_rex),
)
} else {
(resolve(modrm.kind, pc2), Operand::Reg(modrm.reg))
};
let dest = fetch!(self.read_operand(mem, rm_op, width));
let acc = fetch!(self.read_operand(mem, Operand::Reg(RAX), width));
self.sub_flags(acc, dest, width); if acc == dest {
let src = fetch!(self.read_operand(mem, reg_op, width));
fetch!(self.write_operand(mem, rm_op, src, width));
} else {
fetch!(self.write_operand(mem, Operand::Reg(RAX), dest, width));
}
self.next(pc2)
}
fn cmpxchg8b16b(&mut self, mem: &mut GuestMemory, modrm: ModRm, pc2: u64, rex: Rex) -> Step {
let Operand::Mem(addr) = resolve(modrm.kind, pc2) else {
return Step::Illegal; };
if rex.w {
let lo = fetch!(self.read_operand(mem, Operand::Mem(addr), 64));
let hi = fetch!(self.read_operand(mem, Operand::Mem(addr.wrapping_add(8)), 64));
let cur = (u128::from(hi) << 64) | u128::from(lo);
let expect = (u128::from(self.gpr[RDX]) << 64) | u128::from(self.gpr[RAX]);
if cur == expect {
let new_lo = self.gpr[RBX];
let new_hi = self.gpr[RCX];
fetch!(self.write_operand(mem, Operand::Mem(addr), new_lo, 64));
fetch!(self.write_operand(mem, Operand::Mem(addr.wrapping_add(8)), new_hi, 64));
self.flags.zf = true;
} else {
self.gpr[RAX] = lo;
self.gpr[RDX] = hi;
self.flags.zf = false;
}
} else {
let cur = fetch!(self.read_operand(mem, Operand::Mem(addr), 64));
let expect = (mask_w(self.gpr[RDX], 32) << 32) | mask_w(self.gpr[RAX], 32);
if cur == expect {
let new = (mask_w(self.gpr[RCX], 32) << 32) | mask_w(self.gpr[RBX], 32);
fetch!(self.write_operand(mem, Operand::Mem(addr), new, 64));
self.flags.zf = true;
} else {
self.gpr[RAX] = mask_w(cur, 32);
self.gpr[RDX] = mask_w(cur >> 32, 32);
self.flags.zf = false;
}
}
self.next(pc2)
}
fn rdrand_or_seed(
&mut self,
mem: &mut GuestMemory,
modrm: ModRm,
pc2: u64,
width: u32,
) -> Step {
let RmKind::Reg(r) = modrm.kind else {
return Step::Illegal;
};
self.prng = self.prng.wrapping_add(0x9E37_79B9_7F4A_7C15);
let mut z = self.prng;
z = (z ^ (z >> 30)).wrapping_mul(0xBF58_476D_1CE4_E5B9);
z = (z ^ (z >> 27)).wrapping_mul(0x94D0_49BB_1331_11EB);
z ^= z >> 31;
fetch!(self.write_operand(mem, Operand::Reg(r), mask_w(z, width), width));
self.flags = Flags {
cf: true,
zf: false,
sf: false,
of: false,
pf: false,
};
self.next(pc2)
}
fn group9_c7(&mut self, mem: &mut GuestMemory, pc: u64, rex: Rex, width: u32) -> Step {
let (modrm, pc2) = fetch!(self.decode_modrm(mem, pc, rex));
match modrm.reg {
1 => self.cmpxchg8b16b(mem, modrm, pc2, rex),
6 | 7 => self.rdrand_or_seed(mem, modrm, pc2, width),
_ => Step::Illegal,
}
}
fn movnti(&mut self, mem: &mut GuestMemory, pc: u64, rex: Rex, width: u32) -> Step {
let (modrm, pc2) = fetch!(self.decode_modrm(mem, pc, rex));
let rm_op @ Operand::Mem(_) = resolve(modrm.kind, pc2) else {
return Step::Illegal; };
let val = mask_w(self.gpr[modrm.reg], width);
fetch!(self.write_operand(mem, rm_op, val, width));
self.next(pc2)
}
fn group15_ae(&mut self, mem: &GuestMemory, pc: u64, rex: Rex) -> Step {
let (modrm, pc2) = fetch!(self.decode_modrm(mem, pc, rex));
match (modrm.kind, modrm.reg) {
(RmKind::Reg(_), 5..=7) | (RmKind::Mem(_) | RmKind::MemRip(_), 7) => self.next(pc2),
_ => Step::Illegal,
}
}
fn cpuid(&mut self) {
let leaf = self.gpr[RAX] as u32;
let (eax, ebx, ecx, edx): (u32, u32, u32, u32) = match leaf {
0 => (7, 0x756E_6547, 0x6C65_746E, 0x4965_6E69),
1 => (0x0007_06A1, 0x0100_0800, 0x4080_2000, 0x0608_8111),
0x8000_0000 => (0x8000_0004, 0, 0, 0), 0x8000_0001 => (0, 0, 0, 0x2800_0800),
0x8000_0002..=0x8000_0004 => Self::cpuid_brand_leaf(leaf),
_ => (0, 0, 0, 0),
};
self.gpr[RAX] = u64::from(eax);
self.gpr[RBX] = u64::from(ebx);
self.gpr[RCX] = u64::from(ecx);
self.gpr[RDX] = u64::from(edx);
}
fn cpuid_brand_leaf(leaf: u32) -> (u32, u32, u32, u32) {
const TEXT: &[u8] = b"nixvm software x86-64 CPU";
let mut brand = [0u8; 48];
brand[..TEXT.len()].copy_from_slice(TEXT);
let base = ((leaf - 0x8000_0002) * 16) as usize;
let word =
|off: usize| u32::from_le_bytes(brand[base + off..base + off + 4].try_into().unwrap());
(word(0), word(4), word(8), word(12))
}
fn rdtsc_tick(&mut self) -> u64 {
self.tsc = self.tsc.wrapping_add(1);
self.tsc
}
fn movs(&mut self, mem: &mut GuestMemory, pc: u64, width: u32, rep: u8) -> Step {
let step = u64::from(width / 8);
let n = (width / 8) as usize;
let mut count: u64 = if rep == 0 { 1 } else { self.gpr[RCX] };
while count > 0 {
let mut b = [0u8; 8];
fetch!(
mem.read(self.gpr[RSI], &mut b[..n])
.map_err(|_| Step::Fault {
addr: self.gpr[RSI],
write: false
})
);
fetch!(mem.write(self.gpr[RDI], &b[..n]).map_err(|_| Step::Fault {
addr: self.gpr[RDI],
write: true
}));
self.gpr[RSI] = self.advance_ptr(self.gpr[RSI], step);
self.gpr[RDI] = self.advance_ptr(self.gpr[RDI], step);
count -= 1;
if rep != 0 {
self.gpr[RCX] = count;
}
}
self.next(pc)
}
fn stos(&mut self, mem: &mut GuestMemory, pc: u64, width: u32, rep: u8) -> Step {
let step = u64::from(width / 8);
let n = (width / 8) as usize;
let val = mask_w(self.gpr[RAX], width);
let mut count: u64 = if rep == 0 { 1 } else { self.gpr[RCX] };
while count > 0 {
let bytes = val.to_le_bytes();
fetch!(
mem.write(self.gpr[RDI], &bytes[..n])
.map_err(|_| Step::Fault {
addr: self.gpr[RDI],
write: true
})
);
self.gpr[RDI] = self.advance_ptr(self.gpr[RDI], step);
count -= 1;
if rep != 0 {
self.gpr[RCX] = count;
}
}
self.next(pc)
}
fn lods(&mut self, mem: &mut GuestMemory, pc: u64, width: u32, rep: u8) -> Step {
let step = u64::from(width / 8);
let n = (width / 8) as usize;
let mut count: u64 = if rep == 0 { 1 } else { self.gpr[RCX] };
while count > 0 {
let mut b = [0u8; 8];
fetch!(
mem.read(self.gpr[RSI], &mut b[..n])
.map_err(|_| Step::Fault {
addr: self.gpr[RSI],
write: false
})
);
let v = u64::from_le_bytes(b);
fetch!(self.write_operand(mem, Operand::Reg(RAX), v, width));
self.gpr[RSI] = self.advance_ptr(self.gpr[RSI], step);
count -= 1;
if rep != 0 {
self.gpr[RCX] = count;
}
}
self.next(pc)
}
fn scas(&mut self, mem: &mut GuestMemory, pc: u64, width: u32, rep: u8) -> Step {
let step = u64::from(width / 8);
let n = (width / 8) as usize;
let a = mask_w(self.gpr[RAX], width);
let mut count: u64 = if rep == 0 { 1 } else { self.gpr[RCX] };
while count > 0 {
let mut b = [0u8; 8];
fetch!(
mem.read(self.gpr[RDI], &mut b[..n])
.map_err(|_| Step::Fault {
addr: self.gpr[RDI],
write: false
})
);
self.sub_flags(a, mask_w(u64::from_le_bytes(b), width), width);
self.gpr[RDI] = self.advance_ptr(self.gpr[RDI], step);
count -= 1;
if rep != 0 {
self.gpr[RCX] = count;
}
if !self.rep_continues(rep, count) {
break;
}
}
self.next(pc)
}
fn cmps(&mut self, mem: &mut GuestMemory, pc: u64, width: u32, rep: u8) -> Step {
let step = u64::from(width / 8);
let n = (width / 8) as usize;
let mut count: u64 = if rep == 0 { 1 } else { self.gpr[RCX] };
while count > 0 {
let (mut bs, mut bd) = ([0u8; 8], [0u8; 8]);
fetch!(
mem.read(self.gpr[RSI], &mut bs[..n])
.map_err(|_| Step::Fault {
addr: self.gpr[RSI],
write: false
})
);
fetch!(
mem.read(self.gpr[RDI], &mut bd[..n])
.map_err(|_| Step::Fault {
addr: self.gpr[RDI],
write: false
})
);
let (vs, vd) = (u64::from_le_bytes(bs), u64::from_le_bytes(bd));
self.sub_flags(mask_w(vs, width), mask_w(vd, width), width);
self.gpr[RSI] = self.advance_ptr(self.gpr[RSI], step);
self.gpr[RDI] = self.advance_ptr(self.gpr[RDI], step);
count -= 1;
if rep != 0 {
self.gpr[RCX] = count;
}
if !self.rep_continues(rep, count) {
break;
}
}
self.next(pc)
}
fn advance_ptr(&self, ptr: u64, step: u64) -> u64 {
if self.df {
ptr.wrapping_sub(step)
} else {
ptr.wrapping_add(step)
}
}
fn rep_continues(&self, rep: u8, count: u64) -> bool {
if count == 0 {
return false;
}
match rep {
1 => self.flags.zf,
2 => !self.flags.zf,
_ => true,
}
}
#[allow(clippy::too_many_lines, clippy::too_many_arguments)]
fn exec_0f(
&mut self,
mem: &mut GuestMemory,
pc: u64,
rex: Rex,
has_rex: bool,
width: u32,
opsize16: bool,
rep: u8,
) -> Step {
let (op2, pc) = fetch!(fetch_u8(mem, pc));
match op2 {
0x05 => Step::Syscall, 0x40..=0x4F => {
let cc = op2 & 0x0f;
let (modrm, pc2) = fetch!(self.decode_modrm(mem, pc, rex));
if self.cond_holds(cc) {
let rm_op = resolve(modrm.kind, pc2);
let v = fetch!(self.read_operand(mem, rm_op, width));
self.gpr[modrm.reg] = mask_w(v, width);
}
self.next(pc2)
}
0x80..=0x8F => {
let cc = op2 & 0x0f;
let (rel, pc2) = fetch!(fetch_i32(mem, pc));
if self.cond_holds(cc) {
self.jump((pc2 as i64).wrapping_add(i64::from(rel)) as u64)
} else {
self.next(pc2)
}
}
0x90..=0x9F => {
let cc = op2 & 0x0f;
let (modrm, pc2) = fetch!(self.decode_modrm(mem, pc, rex));
let rm_op = resolve8(modrm.kind, pc2, has_rex);
let v = u64::from(self.cond_holds(cc));
fetch!(self.write_operand(mem, rm_op, v, 8));
self.next(pc2)
}
0xAF => {
let (modrm, pc2) = fetch!(self.decode_modrm(mem, pc, rex));
let rm_op = resolve(modrm.kind, pc2);
let b = fetch!(self.read_operand(mem, rm_op, width));
let a = mask_w(self.gpr[modrm.reg], width);
let av = sign_extend_128(u128::from(a), width);
let bv = sign_extend_128(u128::from(b), width);
let p = av * bv;
let cf = !fits_signed(p, width);
self.gpr[modrm.reg] = mask_w(p as u128 as u64, width);
self.flags.cf = cf;
self.flags.of = cf;
self.next(pc2)
}
0xB6 | 0xB7 | 0xBE | 0xBF => {
let src_width = if op2 == 0xB6 || op2 == 0xBE { 8 } else { 16 };
let signed = op2 == 0xBE || op2 == 0xBF;
let (modrm, pc2) = fetch!(self.decode_modrm(mem, pc, rex));
let rm_op = if src_width == 8 {
resolve8(modrm.kind, pc2, has_rex)
} else {
resolve(modrm.kind, pc2)
};
let raw = fetch!(self.read_operand(mem, rm_op, src_width));
let val = if signed {
sign_extend_w(raw, src_width) as u64
} else {
raw
};
self.gpr[modrm.reg] = mask_w(val, width);
self.next(pc2)
}
0xA3 => self.bt_ev_gv(mem, pc, rex, width, BitTestOp::Bt),
0xAB => self.bt_ev_gv(mem, pc, rex, width, BitTestOp::Bts),
0xB3 => self.bt_ev_gv(mem, pc, rex, width, BitTestOp::Btr),
0xBB => self.bt_ev_gv(mem, pc, rex, width, BitTestOp::Btc),
0xBA => self.bt_group_imm(mem, pc, rex, width),
0xA4 => self.shld_shrd(mem, pc, rex, width, true, false),
0xA5 => self.shld_shrd(mem, pc, rex, width, true, true),
0xAC => self.shld_shrd(mem, pc, rex, width, false, false),
0xAD => self.shld_shrd(mem, pc, rex, width, false, true),
0xBC => self.bit_scan(mem, pc, rex, width, rep, false), 0xBD => self.bit_scan(mem, pc, rex, width, rep, true), 0xB8 if rep == 1 => self.popcnt(mem, pc, rex, width),
0xC8..=0xCF => {
let r = usize::from(op2 - 0xC8) | (usize::from(rex.b) << 3);
let bs = match width {
64 => self.gpr[r].swap_bytes(),
16 => u64::from((self.gpr[r] as u16).swap_bytes()),
_ => u64::from((self.gpr[r] as u32).swap_bytes()),
};
fetch!(self.write_operand(mem, Operand::Reg(r), bs, width));
self.next(pc)
}
0xA2 => {
self.cpuid();
self.next(pc)
}
0x31 => {
let t = self.rdtsc_tick();
self.gpr[RAX] = t & 0xffff_ffff;
self.gpr[RDX] = t >> 32;
self.next(pc)
}
0x01 => {
let (b, pc2) = fetch!(fetch_u8(mem, pc));
match b {
0xF9 => {
let t = self.rdtsc_tick();
self.gpr[RAX] = t & 0xffff_ffff;
self.gpr[RDX] = t >> 32;
self.gpr[RCX] = 0;
self.next(pc2)
}
0xD0 => {
self.gpr[RAX] = 0x3;
self.gpr[RDX] = 0;
self.next(pc2)
}
_ => Step::Illegal,
}
}
0xC0 => self.xadd(mem, pc, rex, has_rex, 8),
0xC1 => self.xadd(mem, pc, rex, has_rex, width),
0xB0 => self.cmpxchg(mem, pc, rex, has_rex, 8),
0xB1 => self.cmpxchg(mem, pc, rex, has_rex, width),
0xC3 => self.movnti(mem, pc, rex, width),
0xAE => self.group15_ae(mem, pc, rex),
0xC7 => self.group9_c7(mem, pc, rex, width),
_ => self.exec_0f_sse(mem, pc, rex, opsize16, rep, op2),
}
}
#[allow(clippy::too_many_lines)] fn exec_0f_sse(
&mut self,
mem: &mut GuestMemory,
pc: u64,
rex: Rex,
opsize16: bool,
rep: u8,
op2: u8,
) -> Step {
let gw = if rex.w { 64 } else { 32 };
match op2 {
0x10 | 0x11 => self.sse_move(mem, pc, rex, rep, op2 == 0x11),
0x12 => self.sse_movhlps(mem, pc, rex),
0x14 => self.sse_unpck(mem, pc, rex, if opsize16 { 8 } else { 4 }, false),
0x15 => self.sse_unpck(mem, pc, rex, if opsize16 { 8 } else { 4 }, true),
0x16 => self.sse_movlhps(mem, pc, rex),
0x28 | 0x29 | 0x6F | 0x7F => self.sse_movaps(mem, pc, rex, matches!(op2, 0x29 | 0x7F)),
0x38 => self.exec_0f_38(mem, pc, rex),
0x50 => self.sse_movmskp(mem, pc, rex, opsize16),
0x60 => self.sse_unpck(mem, pc, rex, 1, false), 0x61 => self.sse_unpck(mem, pc, rex, 2, false), 0x62 => self.sse_unpck(mem, pc, rex, 4, false), 0x64 => self.sse_pcmpgt(mem, pc, rex, 1), 0x66 => self.sse_pcmpgt(mem, pc, rex, 4), 0x68 => self.sse_unpck(mem, pc, rex, 1, true), 0x69 => self.sse_unpck(mem, pc, rex, 2, true), 0x6A => self.sse_unpck(mem, pc, rex, 4, true), 0x6C => self.sse_unpck(mem, pc, rex, 8, false), 0x6D => self.sse_unpck(mem, pc, rex, 8, true), 0x6E => self.sse_movd_load(mem, pc, rex, gw),
0x7E if rep == 1 => self.sse_movq_xmm_load(mem, pc, rex),
0x7E => self.sse_movd_store(mem, pc, rex, gw),
0xD6 => self.sse_movq_store(mem, pc, rex),
0xD7 => self.sse_pmovmskb(mem, pc, rex),
0x2A => self.sse_cvtsi2sx(mem, pc, rex, gw, rep),
0x2C => self.sse_cvt_sx2si(mem, pc, rex, gw, rep, false),
0x2D => self.sse_cvt_sx2si(mem, pc, rex, gw, rep, true),
0x2E | 0x2F => self.sse_comis(mem, pc, rex, opsize16),
0x51 => self.sse_arith(mem, pc, rex, opsize16, rep, SseOp::Sqrt),
0x54 | 0xDB => self.sse_bitwise(mem, pc, rex, BitOp::And),
0x57 | 0xEF => self.sse_bitwise(mem, pc, rex, BitOp::Xor),
0x58 => self.sse_arith(mem, pc, rex, opsize16, rep, SseOp::Add),
0x59 => self.sse_arith(mem, pc, rex, opsize16, rep, SseOp::Mul),
0x5A => self.sse_cvt_ss_sd(mem, pc, rex, rep),
0x5C => self.sse_arith(mem, pc, rex, opsize16, rep, SseOp::Sub),
0x5D => self.sse_arith(mem, pc, rex, opsize16, rep, SseOp::Min),
0x5E => self.sse_arith(mem, pc, rex, opsize16, rep, SseOp::Div),
0x5F => self.sse_arith(mem, pc, rex, opsize16, rep, SseOp::Max),
0x70 => self.sse_pshuf(mem, pc, rex, rep, opsize16),
0x72 => self.sse_shift_imm_group(mem, pc, rex, 4),
0x73 => self.sse_shift_imm_group(mem, pc, rex, 8),
0x74 => self.sse_pcmpeq(mem, pc, rex, 1),
0x76 => self.sse_pcmpeq(mem, pc, rex, 4),
0xC6 => self.sse_shuf(mem, pc, rex, opsize16),
0xD4 => self.sse_paddsub(mem, pc, rex, 8, true), 0xDA => self.sse_pminmaxub(mem, pc, rex, true), 0xDE => self.sse_pminmaxub(mem, pc, rex, false), 0xDF => self.sse_bitwise(mem, pc, rex, BitOp::Andn),
0xEB => self.sse_bitwise(mem, pc, rex, BitOp::Or),
0xFA => self.sse_paddsub(mem, pc, rex, 4, false), 0xFB => self.sse_paddsub(mem, pc, rex, 8, false), 0xFC => self.sse_paddsubb(mem, pc, rex, true),
0xF8 => self.sse_paddsubb(mem, pc, rex, false),
0xFE => self.sse_paddsub(mem, pc, rex, 4, true), _ => Step::Illegal,
}
}
fn sse_move(&mut self, mem: &mut GuestMemory, pc: u64, rex: Rex, rep: u8, store: bool) -> Step {
let (modrm, pc2) = fetch!(self.decode_modrm(mem, pc, rex));
let rm_op = resolve(modrm.kind, pc2);
let lane_bits = match rep {
1 => Some(32u32),
2 => Some(64u32),
_ => None,
};
match (lane_bits, store) {
(None, false) => {
let v = fetch!(self.xmm_read128(mem, rm_op));
self.xmm[modrm.reg] = v;
}
(None, true) => {
let v = self.xmm[modrm.reg];
fetch!(self.xmm_write128(mem, rm_op, v));
}
(Some(w), false) => {
let is_reg = matches!(rm_op, Operand::Reg(_));
let lo = fetch!(self.xmm_read_lo(mem, rm_op, w));
self.xmm[modrm.reg] = if is_reg {
(self.xmm[modrm.reg] & !u128::from(mask_w(u64::MAX, w))) | u128::from(lo)
} else {
u128::from(lo)
};
}
(Some(w), true) => match rm_op {
Operand::Reg(r) => {
let src = self.xmm[modrm.reg];
self.xmm[r] = (self.xmm[r] & !u128::from(mask_w(u64::MAX, w)))
| u128::from(mask_w(src as u64, w));
}
Operand::Mem(a) => {
let src = mask_w(self.xmm[modrm.reg] as u64, w);
let n = (w / 8) as usize;
let bytes = src.to_le_bytes();
fetch!(mem.write(a, &bytes[..n]).map_err(|_| Step::Fault {
addr: a,
write: true
}));
}
Operand::Reg8Hi(_) => unreachable!("SSE decode never yields an 8-bit-high operand"),
},
}
self.next(pc2)
}
fn sse_movaps(&mut self, mem: &mut GuestMemory, pc: u64, rex: Rex, store: bool) -> Step {
let (modrm, pc2) = fetch!(self.decode_modrm(mem, pc, rex));
let rm_op = resolve(modrm.kind, pc2);
if store {
let v = self.xmm[modrm.reg];
fetch!(self.xmm_write128(mem, rm_op, v));
} else {
let v = fetch!(self.xmm_read128(mem, rm_op));
self.xmm[modrm.reg] = v;
}
self.next(pc2)
}
fn sse_movd_load(&mut self, mem: &mut GuestMemory, pc: u64, rex: Rex, gw: u32) -> Step {
let (modrm, pc2) = fetch!(self.decode_modrm(mem, pc, rex));
let rm_op = resolve(modrm.kind, pc2);
let val = fetch!(self.read_operand(mem, rm_op, gw));
self.xmm[modrm.reg] = u128::from(val);
self.next(pc2)
}
fn sse_movd_store(&mut self, mem: &mut GuestMemory, pc: u64, rex: Rex, gw: u32) -> Step {
let (modrm, pc2) = fetch!(self.decode_modrm(mem, pc, rex));
let rm_op = resolve(modrm.kind, pc2);
let val = mask_w(self.xmm[modrm.reg] as u64, gw);
fetch!(self.write_operand(mem, rm_op, val, gw));
self.next(pc2)
}
fn sse_movq_xmm_load(&mut self, mem: &mut GuestMemory, pc: u64, rex: Rex) -> Step {
let (modrm, pc2) = fetch!(self.decode_modrm(mem, pc, rex));
let rm_op = resolve(modrm.kind, pc2);
let lo = fetch!(self.xmm_read_lo(mem, rm_op, 64));
self.xmm[modrm.reg] = u128::from(lo);
self.next(pc2)
}
fn sse_movq_store(&mut self, mem: &mut GuestMemory, pc: u64, rex: Rex) -> Step {
let (modrm, pc2) = fetch!(self.decode_modrm(mem, pc, rex));
let rm_op = resolve(modrm.kind, pc2);
let lo = self.xmm[modrm.reg] as u64;
match rm_op {
Operand::Reg(r) => self.xmm[r] = u128::from(lo),
Operand::Mem(a) => {
fetch!(mem.write(a, &lo.to_le_bytes()).map_err(|_| Step::Fault {
addr: a,
write: true
}));
}
Operand::Reg8Hi(_) => unreachable!("SSE decode never yields an 8-bit-high operand"),
}
self.next(pc2)
}
fn sse_pmovmskb(&mut self, mem: &GuestMemory, pc: u64, rex: Rex) -> Step {
let (modrm, pc2) = fetch!(self.decode_modrm(mem, pc, rex));
let rm_op = resolve(modrm.kind, pc2);
let src = fetch!(self.xmm_read128(mem, rm_op));
let bytes = src.to_le_bytes();
let mut mask = 0u64;
for (i, b) in bytes.iter().enumerate() {
if b & 0x80 != 0 {
mask |= 1 << i;
}
}
self.gpr[modrm.reg] = mask;
self.next(pc2)
}
#[allow(clippy::cast_precision_loss)] fn sse_cvtsi2sx(&mut self, mem: &mut GuestMemory, pc: u64, rex: Rex, gw: u32, rep: u8) -> Step {
let (modrm, pc2) = fetch!(self.decode_modrm(mem, pc, rex));
let rm_op = resolve(modrm.kind, pc2);
let raw = fetch!(self.read_operand(mem, rm_op, gw));
let ival = sign_extend_w(raw, gw);
self.xmm[modrm.reg] = if rep == 2 {
let bits = u128::from((ival as f64).to_bits());
(self.xmm[modrm.reg] & !u128::from(u64::MAX)) | bits
} else {
let bits = u128::from((ival as f32).to_bits());
(self.xmm[modrm.reg] & !u128::from(u32::MAX)) | bits
};
self.next(pc2)
}
fn sse_cvt_sx2si(
&mut self,
mem: &GuestMemory,
pc: u64,
rex: Rex,
gw: u32,
rep: u8,
round: bool,
) -> Step {
let (modrm, pc2) = fetch!(self.decode_modrm(mem, pc, rex));
let rm_op = resolve(modrm.kind, pc2);
let result: i64 = if rep == 2 {
let bits = fetch!(self.xmm_read_lo(mem, rm_op, 64));
let f = f64::from_bits(bits);
if round {
f.round_ties_even() as i64
} else {
f.trunc() as i64
}
} else {
let bits = fetch!(self.xmm_read_lo(mem, rm_op, 32));
let f = f32::from_bits(bits as u32);
if round {
f.round_ties_even() as i64
} else {
f.trunc() as i64
}
};
self.gpr[modrm.reg] = if gw == 64 {
result as u64
} else {
mask_w(result as u64, 32)
};
self.next(pc2)
}
fn sse_cvt_ss_sd(&mut self, mem: &mut GuestMemory, pc: u64, rex: Rex, rep: u8) -> Step {
let (modrm, pc2) = fetch!(self.decode_modrm(mem, pc, rex));
let rm_op = resolve(modrm.kind, pc2);
match rep {
2 => {
let bits = fetch!(self.xmm_read_lo(mem, rm_op, 64));
let f = f64::from_bits(bits) as f32;
self.xmm[modrm.reg] =
(self.xmm[modrm.reg] & !u128::from(u32::MAX)) | u128::from(f.to_bits());
}
1 => {
let bits = fetch!(self.xmm_read_lo(mem, rm_op, 32));
let f = f64::from(f32::from_bits(bits as u32));
self.xmm[modrm.reg] =
(self.xmm[modrm.reg] & !u128::from(u64::MAX)) | u128::from(f.to_bits());
}
_ => return Step::Illegal, }
self.next(pc2)
}
fn sse_comis(&mut self, mem: &GuestMemory, pc: u64, rex: Rex, opsize16: bool) -> Step {
let (modrm, pc2) = fetch!(self.decode_modrm(mem, pc, rex));
let rm_op = resolve(modrm.kind, pc2);
let (unordered, gt, lt) = if opsize16 {
let a = f64::from_bits(self.xmm[modrm.reg] as u64);
let bits = fetch!(self.xmm_read_lo(mem, rm_op, 64));
let b = f64::from_bits(bits);
(a.is_nan() || b.is_nan(), a > b, a < b)
} else {
let a = f32::from_bits(self.xmm[modrm.reg] as u32);
let bits = fetch!(self.xmm_read_lo(mem, rm_op, 32));
let b = f32::from_bits(bits as u32);
(a.is_nan() || b.is_nan(), a > b, a < b)
};
self.flags = Flags {
cf: unordered || lt,
zf: unordered || (!gt && !lt),
pf: unordered,
of: false,
sf: false,
};
self.next(pc2)
}
fn sse_arith(
&mut self,
mem: &mut GuestMemory,
pc: u64,
rex: Rex,
opsize16: bool,
rep: u8,
op: SseOp,
) -> Step {
let (modrm, pc2) = fetch!(self.decode_modrm(mem, pc, rex));
let rm_op = resolve(modrm.kind, pc2);
let dst = self.xmm[modrm.reg];
let apply_f64 = |dst: u128, src: u128, lanes: usize| match op {
SseOp::Add => f64_lane_binop(dst, src, lanes, |a, b| a + b),
SseOp::Sub => f64_lane_binop(dst, src, lanes, |a, b| a - b),
SseOp::Mul => f64_lane_binop(dst, src, lanes, |a, b| a * b),
SseOp::Div => f64_lane_binop(dst, src, lanes, |a, b| a / b),
SseOp::Min => f64_lane_binop(dst, src, lanes, |a, b| if a < b { a } else { b }),
SseOp::Max => f64_lane_binop(dst, src, lanes, |a, b| if a > b { a } else { b }),
SseOp::Sqrt => f64_lane_unop(dst, src, lanes, f64::sqrt),
};
let apply_f32 = |dst: u128, src: u128, lanes: usize| match op {
SseOp::Add => f32_lane_binop(dst, src, lanes, |a, b| a + b),
SseOp::Sub => f32_lane_binop(dst, src, lanes, |a, b| a - b),
SseOp::Mul => f32_lane_binop(dst, src, lanes, |a, b| a * b),
SseOp::Div => f32_lane_binop(dst, src, lanes, |a, b| a / b),
SseOp::Min => f32_lane_binop(dst, src, lanes, |a, b| if a < b { a } else { b }),
SseOp::Max => f32_lane_binop(dst, src, lanes, |a, b| if a > b { a } else { b }),
SseOp::Sqrt => f32_lane_unop(dst, src, lanes, f32::sqrt),
};
let result = match rep {
2 => {
let bits = fetch!(self.xmm_read_lo(mem, rm_op, 64));
apply_f64(dst, u128::from(bits), 1)
}
1 => {
let bits = fetch!(self.xmm_read_lo(mem, rm_op, 32));
apply_f32(dst, u128::from(bits), 1)
}
_ if opsize16 => {
let src = fetch!(self.xmm_read128(mem, rm_op));
apply_f64(dst, src, 2)
}
_ => {
let src = fetch!(self.xmm_read128(mem, rm_op));
apply_f32(dst, src, 4)
}
};
self.xmm[modrm.reg] = result;
self.next(pc2)
}
fn sse_bitwise(&mut self, mem: &mut GuestMemory, pc: u64, rex: Rex, op: BitOp) -> Step {
let (modrm, pc2) = fetch!(self.decode_modrm(mem, pc, rex));
let rm_op = resolve(modrm.kind, pc2);
let src = fetch!(self.xmm_read128(mem, rm_op));
let dst = self.xmm[modrm.reg];
self.xmm[modrm.reg] = match op {
BitOp::And => dst & src,
BitOp::Andn => !dst & src,
BitOp::Or => dst | src,
BitOp::Xor => dst ^ src,
};
self.next(pc2)
}
fn sse_pcmpeq(&mut self, mem: &mut GuestMemory, pc: u64, rex: Rex, lane_bytes: usize) -> Step {
let (modrm, pc2) = fetch!(self.decode_modrm(mem, pc, rex));
let rm_op = resolve(modrm.kind, pc2);
let src = fetch!(self.xmm_read128(mem, rm_op));
let dst = self.xmm[modrm.reg];
let (d, s) = (dst.to_le_bytes(), src.to_le_bytes());
let mut out = [0u8; 16];
for lane in (0..16).step_by(lane_bytes) {
let eq = d[lane..lane + lane_bytes] == s[lane..lane + lane_bytes];
let fill = if eq { 0xffu8 } else { 0u8 };
out[lane..lane + lane_bytes].fill(fill);
}
self.xmm[modrm.reg] = u128::from_le_bytes(out);
self.next(pc2)
}
fn sse_paddsubb(&mut self, mem: &mut GuestMemory, pc: u64, rex: Rex, add: bool) -> Step {
let (modrm, pc2) = fetch!(self.decode_modrm(mem, pc, rex));
let rm_op = resolve(modrm.kind, pc2);
let src = fetch!(self.xmm_read128(mem, rm_op));
let dst = self.xmm[modrm.reg];
let (d, s) = (dst.to_le_bytes(), src.to_le_bytes());
let mut out = [0u8; 16];
for i in 0..16 {
out[i] = if add {
d[i].wrapping_add(s[i])
} else {
d[i].wrapping_sub(s[i])
};
}
self.xmm[modrm.reg] = u128::from_le_bytes(out);
self.next(pc2)
}
fn sse_movmskp(&mut self, mem: &GuestMemory, pc: u64, rex: Rex, opsize16: bool) -> Step {
let (modrm, pc2) = fetch!(self.decode_modrm(mem, pc, rex));
let rm_op = resolve(modrm.kind, pc2);
let src = fetch!(self.xmm_read128(mem, rm_op));
let bytes = src.to_le_bytes();
let lane_bytes = if opsize16 { 8 } else { 4 };
let mut result = 0u64;
for (lane, chunk) in bytes.chunks(lane_bytes).enumerate() {
if chunk[lane_bytes - 1] & 0x80 != 0 {
result |= 1 << lane;
}
}
self.gpr[modrm.reg] = result;
self.next(pc2)
}
fn sse_movhlps(&mut self, mem: &GuestMemory, pc: u64, rex: Rex) -> Step {
let (modrm, pc2) = fetch!(self.decode_modrm(mem, pc, rex));
let rm_op = resolve(modrm.kind, pc2);
let Operand::Reg(r) = rm_op else {
return Step::Illegal;
};
let src_hi = self.xmm[r] >> 64;
self.xmm[modrm.reg] = (self.xmm[modrm.reg] & !u128::from(u64::MAX)) | src_hi;
self.next(pc2)
}
fn sse_movlhps(&mut self, mem: &GuestMemory, pc: u64, rex: Rex) -> Step {
let (modrm, pc2) = fetch!(self.decode_modrm(mem, pc, rex));
let rm_op = resolve(modrm.kind, pc2);
let Operand::Reg(r) = rm_op else {
return Step::Illegal;
};
let src_lo = self.xmm[r] & u128::from(u64::MAX);
self.xmm[modrm.reg] = (self.xmm[modrm.reg] & u128::from(u64::MAX)) | (src_lo << 64);
self.next(pc2)
}
fn sse_unpck(
&mut self,
mem: &mut GuestMemory,
pc: u64,
rex: Rex,
lane_bytes: usize,
high: bool,
) -> Step {
let (modrm, pc2) = fetch!(self.decode_modrm(mem, pc, rex));
let rm_op = resolve(modrm.kind, pc2);
let src = fetch!(self.xmm_read128(mem, rm_op));
let dst = self.xmm[modrm.reg];
self.xmm[modrm.reg] = unpck(dst, src, lane_bytes, high);
self.next(pc2)
}
fn sse_pshuf(
&mut self,
mem: &mut GuestMemory,
pc: u64,
rex: Rex,
rep: u8,
opsize16: bool,
) -> Step {
let (modrm, pc2) = fetch!(self.decode_modrm(mem, pc, rex));
let (imm, pc3) = fetch!(fetch_u8(mem, pc2));
let rm_op = resolve(modrm.kind, pc3);
let src = fetch!(self.xmm_read128(mem, rm_op));
let s = src.to_le_bytes();
let mut out = [0u8; 16];
if opsize16 {
for i in 0..4 {
let sel = usize::from((imm >> (2 * i)) & 3);
out[i * 4..i * 4 + 4].copy_from_slice(&s[sel * 4..sel * 4 + 4]);
}
} else if rep == 1 || rep == 2 {
let shuf_base = if rep == 1 { 4 } else { 0 }; let pass_base = 4 - shuf_base;
for w in 0..4 {
let o = (pass_base + w) * 2;
out[o..o + 2].copy_from_slice(&s[o..o + 2]);
}
for i in 0..4 {
let sel = shuf_base + usize::from((imm >> (2 * i)) & 3);
let o = (shuf_base + i) * 2;
out[o..o + 2].copy_from_slice(&s[sel * 2..sel * 2 + 2]);
}
} else {
return Step::Illegal; }
self.xmm[modrm.reg] = u128::from_le_bytes(out);
self.next(pc3)
}
fn sse_shuf(&mut self, mem: &mut GuestMemory, pc: u64, rex: Rex, opsize16: bool) -> Step {
let (modrm, pc2) = fetch!(self.decode_modrm(mem, pc, rex));
let (imm, pc3) = fetch!(fetch_u8(mem, pc2));
let rm_op = resolve(modrm.kind, pc3);
let src = fetch!(self.xmm_read128(mem, rm_op));
let dst = self.xmm[modrm.reg];
let (d, s) = (dst.to_le_bytes(), src.to_le_bytes());
let mut out = [0u8; 16];
if opsize16 {
let sel0 = usize::from(imm & 1);
let sel1 = usize::from((imm >> 1) & 1);
out[0..8].copy_from_slice(&d[sel0 * 8..sel0 * 8 + 8]);
out[8..16].copy_from_slice(&s[sel1 * 8..sel1 * 8 + 8]);
} else {
let sels = [imm & 3, (imm >> 2) & 3, (imm >> 4) & 3, (imm >> 6) & 3];
for (i, &sel) in sels.iter().enumerate() {
let sel = usize::from(sel);
let o = i * 4;
if i < 2 {
out[o..o + 4].copy_from_slice(&d[sel * 4..sel * 4 + 4]);
} else {
out[o..o + 4].copy_from_slice(&s[sel * 4..sel * 4 + 4]);
}
}
}
self.xmm[modrm.reg] = u128::from_le_bytes(out);
self.next(pc3)
}
fn sse_shift_imm_group(
&mut self,
mem: &mut GuestMemory,
pc: u64,
rex: Rex,
lane_bytes: u32,
) -> Step {
let (modrm, pc2) = fetch!(self.decode_modrm(mem, pc, rex));
let (imm, pc3) = fetch!(fetch_u8(mem, pc2));
let rm_op = resolve(modrm.kind, pc3);
let dst = fetch!(self.xmm_read128(mem, rm_op));
let count = u32::from(imm);
let result = match (lane_bytes, modrm.reg) {
(4, 2) => pack_shift_right(dst, 32, count),
(4, 6) => pack_shift_left(dst, 32, count),
(8, 2) => pack_shift_right(dst, 64, count),
(8, 6) => pack_shift_left(dst, 64, count),
(8, 3) => {
if count >= 16 { 0 } else { dst >> (count * 8) } }
(8, 7) => {
if count >= 16 { 0 } else { dst << (count * 8) } }
_ => return Step::Illegal, };
fetch!(self.xmm_write128(mem, rm_op, result));
self.next(pc3)
}
fn exec_0f_38(&mut self, mem: &mut GuestMemory, pc: u64, rex: Rex) -> Step {
let (op3, pc) = fetch!(fetch_u8(mem, pc));
match op3 {
0x00 => self.sse_pshufb(mem, pc, rex),
_ => Step::Illegal,
}
}
fn sse_pshufb(&mut self, mem: &mut GuestMemory, pc: u64, rex: Rex) -> Step {
let (modrm, pc2) = fetch!(self.decode_modrm(mem, pc, rex));
let rm_op = resolve(modrm.kind, pc2);
let src = fetch!(self.xmm_read128(mem, rm_op));
let dst = self.xmm[modrm.reg];
let (d, s) = (dst.to_le_bytes(), src.to_le_bytes());
let mut out = [0u8; 16];
for i in 0..16 {
out[i] = if d[i] & 0x80 != 0 {
0
} else {
s[usize::from(d[i] & 0x0f)]
};
}
self.xmm[modrm.reg] = u128::from_le_bytes(out);
self.next(pc2)
}
#[allow(clippy::many_single_char_names)] fn sse_paddsub(
&mut self,
mem: &mut GuestMemory,
pc: u64,
rex: Rex,
lane_bytes: usize,
add: bool,
) -> Step {
let (modrm, pc2) = fetch!(self.decode_modrm(mem, pc, rex));
let rm_op = resolve(modrm.kind, pc2);
let src = fetch!(self.xmm_read128(mem, rm_op));
let dst = self.xmm[modrm.reg];
let (d, s) = (dst.to_le_bytes(), src.to_le_bytes());
let mut out = [0u8; 16];
for lane in (0..16).step_by(lane_bytes) {
let a = u64_from_le(&d[lane..lane + lane_bytes]);
let b = u64_from_le(&s[lane..lane + lane_bytes]);
let r = if add {
a.wrapping_add(b)
} else {
a.wrapping_sub(b)
};
out[lane..lane + lane_bytes].copy_from_slice(&r.to_le_bytes()[..lane_bytes]);
}
self.xmm[modrm.reg] = u128::from_le_bytes(out);
self.next(pc2)
}
fn sse_pminmaxub(&mut self, mem: &mut GuestMemory, pc: u64, rex: Rex, is_min: bool) -> Step {
let (modrm, pc2) = fetch!(self.decode_modrm(mem, pc, rex));
let rm_op = resolve(modrm.kind, pc2);
let src = fetch!(self.xmm_read128(mem, rm_op));
let dst = self.xmm[modrm.reg];
let (d, s) = (dst.to_le_bytes(), src.to_le_bytes());
let mut out = [0u8; 16];
for i in 0..16 {
out[i] = if is_min {
d[i].min(s[i])
} else {
d[i].max(s[i])
};
}
self.xmm[modrm.reg] = u128::from_le_bytes(out);
self.next(pc2)
}
fn sse_pcmpgt(&mut self, mem: &mut GuestMemory, pc: u64, rex: Rex, lane_bytes: usize) -> Step {
let (modrm, pc2) = fetch!(self.decode_modrm(mem, pc, rex));
let rm_op = resolve(modrm.kind, pc2);
let src = fetch!(self.xmm_read128(mem, rm_op));
let dst = self.xmm[modrm.reg];
let (d, s) = (dst.to_le_bytes(), src.to_le_bytes());
let mut out = [0u8; 16];
for lane in (0..16).step_by(lane_bytes) {
let a = sign_extend_w(
u64_from_le(&d[lane..lane + lane_bytes]),
lane_bytes as u32 * 8,
);
let b = sign_extend_w(
u64_from_le(&s[lane..lane + lane_bytes]),
lane_bytes as u32 * 8,
);
let fill = if a > b { 0xffu8 } else { 0u8 };
out[lane..lane + lane_bytes].fill(fill);
}
self.xmm[modrm.reg] = u128::from_le_bytes(out);
self.next(pc2)
}
fn st_idx(&self, i: u8) -> usize {
((self.fpu_top.wrapping_add(i)) & 7) as usize
}
fn st_get(&self, i: u8) -> f64 {
self.st[self.st_idx(i)]
}
fn st_set(&mut self, i: u8, v: f64) {
let idx = self.st_idx(i);
self.st[idx] = v;
}
fn fpu_push(&mut self, v: f64) {
self.fpu_top = self.fpu_top.wrapping_sub(1) & 7;
self.st[self.fpu_top as usize] = v;
}
fn fpu_pop(&mut self) -> f64 {
let v = self.st[self.fpu_top as usize];
self.fpu_top = (self.fpu_top + 1) & 7;
v
}
fn fpu_init(&mut self) {
self.st = [0.0; 8];
self.fpu_top = 0;
self.fpu_c0 = false;
self.fpu_c1 = false;
self.fpu_c2 = false;
self.fpu_c3 = false;
self.fpu_cw = 0x037F;
}
fn fpu_compare(&mut self, a: f64, b: f64) {
let unordered = a.is_nan() || b.is_nan();
let (lt, gt) = (a < b, a > b);
self.fpu_c0 = unordered || lt;
self.fpu_c2 = unordered;
self.fpu_c3 = unordered || (!lt && !gt); self.fpu_c1 = false;
}
fn fpu_comi(&mut self, i: u8, pop: bool) {
let a = self.st_get(0);
let b = self.st_get(i);
let unordered = a.is_nan() || b.is_nan();
let (lt, gt) = (a < b, a > b);
self.flags = Flags {
cf: unordered || lt,
zf: unordered || (!lt && !gt), pf: unordered,
of: false,
sf: false,
};
if pop {
self.fpu_pop();
}
}
fn round_per_cw(&self, v: f64) -> f64 {
match (self.fpu_cw >> 10) & 3 {
1 => v.floor(),
2 => v.ceil(),
3 => v.trunc(),
_ => v.round_ties_even(),
}
}
fn fpu_sw(&self) -> u16 {
let mut sw = (u16::from(self.fpu_top) & 7) << 11;
if self.fpu_c0 {
sw |= 1 << 8;
}
if self.fpu_c1 {
sw |= 1 << 9;
}
if self.fpu_c2 {
sw |= 1 << 10;
}
if self.fpu_c3 {
sw |= 1 << 14;
}
sw
}
fn fpu_arith_st0(&mut self, op: FpuOp, src: f64) {
match op {
FpuOp::Com => self.fpu_compare(self.st_get(0), src),
FpuOp::Comp => {
self.fpu_compare(self.st_get(0), src);
self.fpu_pop();
}
_ => {
let dst = self.st_get(0);
self.st_set(0, fpu_binop(op, dst, src));
}
}
}
fn fpu_arith_mem(
&mut self,
mem: &mut GuestMemory,
reg: usize,
kind: RmKind,
pc2: u64,
w: MemWidth,
) -> Step {
let op = FpuOp::from_reg((reg & 7) as u8);
let addr = match resolve(kind, pc2) {
Operand::Mem(a) => a,
Operand::Reg(_) | Operand::Reg8Hi(_) => return Step::Illegal, };
let src = fetch!(fpu_read_src(mem, addr, w));
self.fpu_arith_st0(op, src);
self.next(pc2)
}
fn fpu_d8(&mut self, mem: &mut GuestMemory, modrm: ModRm, pc2: u64) -> Step {
match modrm.kind {
RmKind::Reg(r) => {
let op = FpuOp::from_reg((modrm.reg & 7) as u8);
let src = self.st_get((r & 7) as u8);
self.fpu_arith_st0(op, src);
self.next(pc2)
}
_ => self.fpu_arith_mem(mem, modrm.reg, modrm.kind, pc2, MemWidth::F32),
}
}
#[allow(clippy::too_many_lines)] #[allow(clippy::single_match_else)] fn fpu_d9(&mut self, mem: &mut GuestMemory, modrm: ModRm, pc2: u64) -> Step {
match modrm.kind {
RmKind::Reg(r) => {
let reg = modrm.reg & 7;
let rm = (r & 7) as u8;
match reg {
0 => {
let v = self.st_get(rm);
self.fpu_push(v);
self.next(pc2)
}
1 => {
let a = self.st_get(0);
let b = self.st_get(rm);
self.st_set(0, b);
self.st_set(rm, a);
self.next(pc2)
}
2 if rm == 0 => self.next(pc2), 4 => match rm {
0 => {
self.st_set(0, -self.st_get(0)); self.next(pc2)
}
1 => {
self.st_set(0, self.st_get(0).abs()); self.next(pc2)
}
4 => {
self.fpu_compare(self.st_get(0), 0.0); self.next(pc2)
}
_ => Step::Illegal, },
5 => {
let Some(c) = (match rm {
0 => Some(1.0), 1 => Some(std::f64::consts::LOG2_10), 2 => Some(std::f64::consts::LOG2_E), 3 => Some(std::f64::consts::PI), 4 => Some(std::f64::consts::LOG10_2), 5 => Some(std::f64::consts::LN_2), 6 => Some(0.0), _ => None,
}) else {
return Step::Illegal;
};
self.fpu_push(c);
self.next(pc2)
}
6 => match rm {
6 => {
self.fpu_top = self.fpu_top.wrapping_sub(1) & 7; self.next(pc2)
}
7 => {
self.fpu_top = (self.fpu_top + 1) & 7; self.next(pc2)
}
_ => Step::Illegal,
},
7 => match rm {
2 => {
self.st_set(0, self.st_get(0).sqrt()); self.next(pc2)
}
4 => {
let v = self.round_per_cw(self.st_get(0)); self.st_set(0, v);
self.next(pc2)
}
_ => Step::Illegal,
},
_ => Step::Illegal, }
}
_ => {
let addr = match resolve(modrm.kind, pc2) {
Operand::Mem(a) => a,
Operand::Reg(_) | Operand::Reg8Hi(_) => return Step::Illegal,
};
match modrm.reg & 7 {
0 => {
let v = fetch!(fpu_read_f32(mem, addr)); self.fpu_push(v);
self.next(pc2)
}
2 => {
let v = self.st_get(0); fetch!(fpu_write_f32(mem, addr, v));
self.next(pc2)
}
3 => {
let v = self.st_get(0); fetch!(fpu_write_f32(mem, addr, v));
self.fpu_pop();
self.next(pc2)
}
5 => {
let mut b = [0u8; 2];
fetch!(
mem.read(addr, &mut b)
.map_err(|_| Step::Fault { addr, write: false })
);
self.fpu_cw = u16::from_le_bytes(b);
self.next(pc2)
}
7 => {
let bytes = self.fpu_cw.to_le_bytes();
fetch!(
mem.write(addr, &bytes)
.map_err(|_| Step::Fault { addr, write: true })
);
self.next(pc2)
}
_ => Step::Illegal,
}
}
}
}
fn fpu_da(&mut self, mem: &mut GuestMemory, modrm: ModRm, pc2: u64) -> Step {
match modrm.kind {
RmKind::Reg(r) => {
if (modrm.reg & 7) == 5 && (r & 7) == 1 {
self.fpu_compare(self.st_get(0), self.st_get(1)); self.fpu_pop();
self.fpu_pop();
self.next(pc2)
} else {
Step::Illegal }
}
_ => self.fpu_arith_mem(mem, modrm.reg, modrm.kind, pc2, MemWidth::I32),
}
}
#[allow(clippy::single_match_else)] #[allow(clippy::cast_precision_loss)] fn fpu_db(&mut self, mem: &mut GuestMemory, modrm: ModRm, pc2: u64) -> Step {
match modrm.kind {
RmKind::Reg(r) => {
let rm = (r & 7) as u8;
match modrm.reg & 7 {
4 => match rm {
2 => self.next(pc2), 3 => {
self.fpu_init(); self.next(pc2)
}
_ => Step::Illegal,
},
5 | 6 => {
self.fpu_comi(rm, false); self.next(pc2)
}
_ => Step::Illegal, }
}
_ => {
let addr = match resolve(modrm.kind, pc2) {
Operand::Mem(a) => a,
Operand::Reg(_) | Operand::Reg8Hi(_) => return Step::Illegal,
};
match modrm.reg & 7 {
0 => {
let v = fetch!(fpu_read_int(mem, addr, 32)); self.fpu_push(v as f64);
self.next(pc2)
}
2 => {
let v = self.round_per_cw(self.st_get(0)); fetch!(fpu_write_int(mem, addr, v as i64, 32));
self.next(pc2)
}
3 => {
let v = self.round_per_cw(self.st_get(0)); fetch!(fpu_write_int(mem, addr, v as i64, 32));
self.fpu_pop();
self.next(pc2)
}
5 => {
let v = fetch!(fpu_read_f80(mem, addr)); self.fpu_push(v);
self.next(pc2)
}
7 => {
let v = self.st_get(0); fetch!(fpu_write_f80(mem, addr, v));
self.fpu_pop();
self.next(pc2)
}
_ => Step::Illegal,
}
}
}
}
fn fpu_dc(&mut self, mem: &mut GuestMemory, modrm: ModRm, pc2: u64) -> Step {
match modrm.kind {
RmKind::Reg(r) => {
let Some(op) = FpuOp::from_reg_reversed((modrm.reg & 7) as u8) else {
return Step::Illegal;
};
let i = (r & 7) as u8;
let dst = self.st_get(i);
let src = self.st_get(0);
self.st_set(i, fpu_binop(op, dst, src));
self.next(pc2)
}
_ => self.fpu_arith_mem(mem, modrm.reg, modrm.kind, pc2, MemWidth::F64),
}
}
#[allow(clippy::single_match_else)] fn fpu_dd(&mut self, mem: &mut GuestMemory, modrm: ModRm, pc2: u64) -> Step {
match modrm.kind {
RmKind::Reg(r) => {
let i = (r & 7) as u8;
match modrm.reg & 7 {
0 => self.next(pc2), 2 => {
let v = self.st_get(0); self.st_set(i, v);
self.next(pc2)
}
3 => {
let v = self.st_get(0); self.st_set(i, v);
self.fpu_pop();
self.next(pc2)
}
4 => {
self.fpu_compare(self.st_get(0), self.st_get(i)); self.next(pc2)
}
5 => {
self.fpu_compare(self.st_get(0), self.st_get(i)); self.fpu_pop();
self.next(pc2)
}
_ => Step::Illegal,
}
}
_ => {
let addr = match resolve(modrm.kind, pc2) {
Operand::Mem(a) => a,
Operand::Reg(_) | Operand::Reg8Hi(_) => return Step::Illegal,
};
match modrm.reg & 7 {
0 => {
let v = fetch!(fpu_read_f64(mem, addr)); self.fpu_push(v);
self.next(pc2)
}
2 => {
let v = self.st_get(0); fetch!(fpu_write_f64(mem, addr, v));
self.next(pc2)
}
3 => {
let v = self.st_get(0); fetch!(fpu_write_f64(mem, addr, v));
self.fpu_pop();
self.next(pc2)
}
7 => {
let sw = self.fpu_sw(); fetch!(
mem.write(addr, &sw.to_le_bytes())
.map_err(|_| Step::Fault { addr, write: true })
);
self.next(pc2)
}
_ => Step::Illegal,
}
}
}
}
fn fpu_de(&mut self, mem: &mut GuestMemory, modrm: ModRm, pc2: u64) -> Step {
match modrm.kind {
RmKind::Reg(r) => {
let reg = modrm.reg & 7;
let rm = (r & 7) as u8;
if reg == 3 && rm == 1 {
self.fpu_compare(self.st_get(0), self.st_get(1)); self.fpu_pop();
self.fpu_pop();
return self.next(pc2);
}
let Some(op) = FpuOp::from_reg_reversed(reg as u8) else {
return Step::Illegal;
};
let dst = self.st_get(rm);
let src = self.st_get(0);
self.st_set(rm, fpu_binop(op, dst, src));
self.fpu_pop();
self.next(pc2)
}
_ => self.fpu_arith_mem(mem, modrm.reg, modrm.kind, pc2, MemWidth::I16),
}
}
#[allow(clippy::single_match_else)] #[allow(clippy::cast_precision_loss)] fn fpu_df(&mut self, mem: &mut GuestMemory, modrm: ModRm, pc2: u64) -> Step {
match modrm.kind {
RmKind::Reg(r) => {
let reg = modrm.reg & 7;
let rm = (r & 7) as u8;
match reg {
4 if rm == 0 => {
let sw = u64::from(self.fpu_sw()); fetch!(self.write_operand(mem, Operand::Reg(RAX), sw, 16));
self.next(pc2)
}
5 | 6 => {
self.fpu_comi(rm, true); self.next(pc2)
}
_ => Step::Illegal, }
}
_ => {
let addr = match resolve(modrm.kind, pc2) {
Operand::Mem(a) => a,
Operand::Reg(_) | Operand::Reg8Hi(_) => return Step::Illegal,
};
match modrm.reg & 7 {
0 => {
let v = fetch!(fpu_read_int(mem, addr, 16)); self.fpu_push(v as f64);
self.next(pc2)
}
2 => {
let v = self.round_per_cw(self.st_get(0)); fetch!(fpu_write_int(mem, addr, v as i64, 16));
self.next(pc2)
}
3 => {
let v = self.round_per_cw(self.st_get(0)); fetch!(fpu_write_int(mem, addr, v as i64, 16));
self.fpu_pop();
self.next(pc2)
}
5 => {
let v = fetch!(fpu_read_int(mem, addr, 64)); self.fpu_push(v as f64);
self.next(pc2)
}
7 => {
let v = self.round_per_cw(self.st_get(0)); fetch!(fpu_write_int(mem, addr, v as i64, 64));
self.fpu_pop();
self.next(pc2)
}
_ => Step::Illegal,
}
}
}
}
fn exec_x87(&mut self, mem: &mut GuestMemory, pc: u64, rex: Rex, esc: u8) -> Step {
let (modrm, pc2) = fetch!(self.decode_modrm(mem, pc, rex));
match esc {
0xD8 => self.fpu_d8(mem, modrm, pc2),
0xD9 => self.fpu_d9(mem, modrm, pc2),
0xDA => self.fpu_da(mem, modrm, pc2),
0xDB => self.fpu_db(mem, modrm, pc2),
0xDC => self.fpu_dc(mem, modrm, pc2),
0xDD => self.fpu_dd(mem, modrm, pc2),
0xDE => self.fpu_de(mem, modrm, pc2),
_ => self.fpu_df(mem, modrm, pc2), }
}
#[allow(clippy::too_many_lines)]
fn exec(&mut self, mem: &mut GuestMemory) -> Step {
let mut pc = self.rip;
let mut opsize16 = false;
let mut rep: u8 = 0; loop {
let (b, next) = fetch!(fetch_u8(mem, pc));
match b {
0x66 => opsize16 = true,
0xF3 => rep = 1,
0xF2 => rep = 2,
0xF0 => {} _ => break,
}
pc = next;
}
let (b0, pc) = fetch!(fetch_u8(mem, pc));
let (rex, has_rex, opcode, pc) = if (0x40..=0x4f).contains(&b0) {
let (op, pc2) = fetch!(fetch_u8(mem, pc));
(Rex::from_byte(b0), true, op, pc2)
} else {
(Rex::default(), false, b0, pc)
};
let width = if rex.w {
64
} else if opsize16 {
16
} else {
32
};
match opcode {
0x50..=0x57 => {
let r = usize::from(opcode - 0x50) | (usize::from(rex.b) << 3);
let val = self.gpr[r];
fetch!(self.push(mem, val));
self.next(pc)
}
0x58..=0x5F => {
let r = usize::from(opcode - 0x58) | (usize::from(rex.b) << 3);
let val = fetch!(self.pop(mem));
self.gpr[r] = val;
self.next(pc)
}
0x68 => {
let (imm, pc2) = fetch!(fetch_i32(mem, pc));
fetch!(self.push(mem, i64::from(imm) as u64));
self.next(pc2)
}
0x6A => {
let (imm, pc2) = fetch!(fetch_i8(mem, pc));
fetch!(self.push(mem, i64::from(imm) as u64));
self.next(pc2)
}
0x8D => self.lea(mem, pc, rex, width),
0x89 => self.mov_rm_gv(mem, pc, rex, width),
0x8B => self.mov_gv_rm(mem, pc, rex, width),
0x88 => {
let (modrm, pc2) = fetch!(self.decode_modrm(mem, pc, rex));
let rm_op = resolve8(modrm.kind, pc2, has_rex);
let val = fetch!(self.read_operand(mem, reg8_operand(modrm.reg, has_rex), 8));
fetch!(self.write_operand(mem, rm_op, val, 8));
self.next(pc2)
}
0x8A => {
let (modrm, pc2) = fetch!(self.decode_modrm(mem, pc, rex));
let rm_op = resolve8(modrm.kind, pc2, has_rex);
let val = fetch!(self.read_operand(mem, rm_op, 8));
fetch!(self.write_operand(mem, reg8_operand(modrm.reg, has_rex), val, 8));
self.next(pc2)
}
0xB0..=0xB7 => {
let r = usize::from(opcode - 0xB0) | (usize::from(rex.b) << 3);
let (imm, pc2) = fetch!(fetch_u8(mem, pc));
fetch!(self.write_operand(mem, reg8_operand(r, has_rex), u64::from(imm), 8));
self.next(pc2)
}
0xC6 => {
let (modrm, pc2) = fetch!(self.decode_modrm(mem, pc, rex));
if modrm.reg != 0 {
return Step::Illegal;
}
let (imm, pc3) = fetch!(fetch_u8(mem, pc2));
let rm_op = resolve8(modrm.kind, pc3, has_rex);
fetch!(self.write_operand(mem, rm_op, u64::from(imm), 8));
self.next(pc3)
}
0x63 => {
let (modrm, pc2) = fetch!(self.decode_modrm(mem, pc, rex));
let rm_op = resolve(modrm.kind, pc2);
let raw = fetch!(self.read_operand(mem, rm_op, 32));
let val = if width == 64 {
sign_extend_w(raw, 32) as u64
} else {
raw
};
self.gpr[modrm.reg] = mask_w(val, width);
self.next(pc2)
}
0x69 => self.imul_imm(mem, pc, rex, width, false),
0x6B => self.imul_imm(mem, pc, rex, width, true),
0x86 => self.xchg(mem, pc, rex, has_rex, 8),
0x87 => self.xchg(mem, pc, rex, has_rex, width),
0x91..=0x97 => {
let r = usize::from(opcode - 0x90) | (usize::from(rex.b) << 3);
let a = fetch!(self.read_operand(mem, Operand::Reg(RAX), width));
let b = fetch!(self.read_operand(mem, Operand::Reg(r), width));
fetch!(self.write_operand(mem, Operand::Reg(RAX), b, width));
fetch!(self.write_operand(mem, Operand::Reg(r), a, width));
self.next(pc)
}
0x98 => {
match width {
64 => self.gpr[RAX] = sign_extend_w(mask_w(self.gpr[RAX], 32), 32) as u64,
16 => {
let v = sign_extend_w(mask_w(self.gpr[RAX], 8), 8) as u64;
self.gpr[RAX] = (self.gpr[RAX] & !0xffffu64) | (v & 0xffff);
}
_ => {
let v = sign_extend_w(mask_w(self.gpr[RAX], 16), 16) as u64;
self.gpr[RAX] = mask_w(v, 32);
}
}
self.next(pc)
}
0x99 => {
match width {
64 => {
self.gpr[RDX] = if sign_bit(self.gpr[RAX], 64) {
u64::MAX
} else {
0
}
}
16 => {
let d = if sign_bit(self.gpr[RAX] & 0xffff, 16) {
0xffffu64
} else {
0
};
self.gpr[RDX] = (self.gpr[RDX] & !0xffffu64) | d;
}
_ => {
self.gpr[RDX] = if sign_bit(self.gpr[RAX] & 0xffff_ffff, 32) {
0xffff_ffff
} else {
0
};
}
}
self.next(pc)
}
0xA4 => self.movs(mem, pc, 8, rep),
0xA5 => self.movs(mem, pc, width, rep),
0xA6 => self.cmps(mem, pc, 8, rep),
0xA7 => self.cmps(mem, pc, width, rep),
0xAA => self.stos(mem, pc, 8, rep),
0xAB => self.stos(mem, pc, width, rep),
0xAC => self.lods(mem, pc, 8, rep),
0xAD => self.lods(mem, pc, width, rep),
0xAE => self.scas(mem, pc, 8, rep),
0xAF => self.scas(mem, pc, width, rep),
0xFC => {
self.df = false;
self.next(pc)
}
0xFD => {
self.df = true;
self.next(pc)
}
0xC9 => {
self.gpr[RSP] = self.gpr[RBP];
let val = fetch!(self.pop(mem));
self.gpr[RBP] = val;
self.next(pc)
}
0xB8..=0xBF => {
let r = usize::from(opcode - 0xB8) | (usize::from(rex.b) << 3);
if rex.w {
let (imm, pc2) = fetch!(fetch_u64(mem, pc));
self.gpr[r] = imm; self.next(pc2)
} else {
let (imm, pc2) = fetch!(fetch_u32(mem, pc));
self.gpr[r] = u64::from(imm); self.next(pc2)
}
}
0xC7 => self.mov_imm(mem, pc, rex, width),
0x00 => self.alu_rm_gv8(mem, pc, rex, has_rex, AluOp::Add, true),
0x02 => self.alu_gv_rm8(mem, pc, rex, has_rex, AluOp::Add),
0x01 => self.alu_rm_gv(mem, pc, rex, width, AluOp::Add, true),
0x03 => self.alu_gv_rm(mem, pc, rex, width, AluOp::Add),
0x09 => self.alu_rm_gv(mem, pc, rex, width, AluOp::Or, true),
0x0B => self.alu_gv_rm(mem, pc, rex, width, AluOp::Or),
0x21 => self.alu_rm_gv(mem, pc, rex, width, AluOp::And, true),
0x23 => self.alu_gv_rm(mem, pc, rex, width, AluOp::And),
0x28 => self.alu_rm_gv8(mem, pc, rex, has_rex, AluOp::Sub, true),
0x2A => self.alu_gv_rm8(mem, pc, rex, has_rex, AluOp::Sub),
0x29 => self.alu_rm_gv(mem, pc, rex, width, AluOp::Sub, true),
0x2B => self.alu_gv_rm(mem, pc, rex, width, AluOp::Sub),
0x30 => self.alu_rm_gv8(mem, pc, rex, has_rex, AluOp::Xor, true),
0x32 => self.alu_gv_rm8(mem, pc, rex, has_rex, AluOp::Xor),
0x31 => self.alu_rm_gv(mem, pc, rex, width, AluOp::Xor, true),
0x33 => self.alu_gv_rm(mem, pc, rex, width, AluOp::Xor),
0x38 => self.alu_rm_gv8(mem, pc, rex, has_rex, AluOp::Cmp, false),
0x3A => self.alu_gv_rm8(mem, pc, rex, has_rex, AluOp::Cmp),
0x39 => self.alu_rm_gv(mem, pc, rex, width, AluOp::Cmp, false),
0x3B => self.alu_gv_rm(mem, pc, rex, width, AluOp::Cmp),
0x84 => self.alu_rm_gv8(mem, pc, rex, has_rex, AluOp::Test, false),
0x85 => self.alu_rm_gv(mem, pc, rex, width, AluOp::Test, false),
0x80 => self.group1_imm(mem, pc, rex, has_rex, 8, true),
0x81 => self.group1_imm(mem, pc, rex, has_rex, width, false),
0x83 => self.group1_imm(mem, pc, rex, has_rex, width, true),
0xF6 => self.group3(mem, pc, rex, has_rex, 8),
0xF7 => self.group3(mem, pc, rex, has_rex, width),
0xFE => self.group4(mem, pc, rex, has_rex),
0xFF => self.group5(mem, pc, rex, width),
0xC1 => self.group2(mem, pc, rex, width, false),
0xD3 => self.group2(mem, pc, rex, width, true),
0xE8 => {
let (rel, pc2) = fetch!(fetch_i32(mem, pc));
fetch!(self.push(mem, pc2));
self.jump((pc2 as i64).wrapping_add(i64::from(rel)) as u64)
}
0xC3 => {
let target = fetch!(self.pop(mem));
self.jump(target)
}
0xE9 => {
let (rel, pc2) = fetch!(fetch_i32(mem, pc));
self.jump((pc2 as i64).wrapping_add(i64::from(rel)) as u64)
}
0xEB => {
let (rel, pc2) = fetch!(fetch_i8(mem, pc));
self.jump((pc2 as i64).wrapping_add(i64::from(rel)) as u64)
}
0x70..=0x7F => {
let cc = opcode & 0x0f;
let (rel, pc2) = fetch!(fetch_i8(mem, pc));
if self.cond_holds(cc) {
self.jump((pc2 as i64).wrapping_add(i64::from(rel)) as u64)
} else {
self.next(pc2)
}
}
0x90 | 0x9B => self.next(pc),
0xD8..=0xDF => self.exec_x87(mem, pc, rex, opcode),
0x0F => self.exec_0f(mem, pc, rex, has_rex, width, opsize16, rep),
_ => Step::Illegal,
}
}
}
impl Vcpu for X86Interp {
fn run(&mut self, mem: &mut GuestMemory) -> Result<Exit, VcpuError> {
for _ in 0..MAX_STEPS {
match self.exec(mem) {
Step::Next | Step::Branched => {}
Step::Syscall => return Ok(Exit::Syscall),
Step::Illegal => return Ok(Exit::IllegalInstruction { pc: self.rip }),
Step::Fault { addr, write } => return Ok(Exit::MemFault { addr, write }),
}
}
Ok(Exit::Interrupted)
}
fn syscall_nr(&self) -> u64 {
self.gpr[RAX]
}
fn syscall_args(&self) -> [u64; 6] {
[
self.gpr[RDI],
self.gpr[RSI],
self.gpr[RDX],
self.gpr[R10],
self.gpr[R8],
self.gpr[R9],
]
}
fn set_syscall_ret(&mut self, value: u64) {
self.gpr[RAX] = value;
self.rip = self.rip.wrapping_add(2); }
fn reg(&self, idx: usize) -> u64 {
if idx < 16 { self.gpr[idx] } else { 0 }
}
fn set_reg(&mut self, idx: usize, value: u64) {
if idx < 16 {
self.gpr[idx] = value;
}
}
fn pc(&self) -> u64 {
self.rip
}
fn set_pc(&mut self, pc: u64) {
self.rip = pc;
}
fn sp(&self) -> u64 {
self.gpr[RSP]
}
fn set_sp(&mut self, sp: u64) {
self.gpr[RSP] = sp;
}
fn set_tls(&mut self, value: u64) {
self.fs_base = value;
}
fn fork(&self) -> Box<dyn Vcpu> {
Box::new(self.clone())
}
fn reset(&mut self, entry: u64, sp: u64) {
self.gpr = [0; 16];
self.xmm = [0; 16];
self.gpr[RSP] = sp;
self.rip = entry;
self.flags = Flags::default();
self.df = false;
self.fs_base = 0;
self.fpu_init();
}
}
#[cfg(test)]
mod tests {
#![allow(clippy::float_cmp)]
use super::*;
use crate::vcpu::Prot;
fn mem() -> GuestMemory {
let mut m = GuestMemory::new(0x1_0000, 16 * crate::vcpu::mem::PAGE_SIZE);
m.map(0x1_0000, 16 * crate::vcpu::mem::PAGE_SIZE, Prot::rwx())
.unwrap();
m
}
const CODE: u64 = 0x1_1000;
const STACK: u64 = 0x1_F000;
fn run_one(mem: &mut GuestMemory, code: &[u8]) -> X86Interp {
mem.write_init(CODE, code).unwrap();
let mut cpu = X86Interp::new(CODE, STACK);
cpu.exec(mem);
cpu
}
#[test]
fn mov_imm32_zero_extends() {
let mut m = mem();
let cpu = run_one(&mut m, &[0xB8, 0x78, 0x56, 0x34, 0x12]);
assert_eq!(cpu.gpr[RAX], 0x1234_5678);
assert_eq!(cpu.rip, CODE + 5);
}
#[test]
fn movabs_imm64() {
let mut m = mem();
let mut code = vec![0x48, 0xB8];
code.extend_from_slice(&0x0102_0304_0506_0708u64.to_le_bytes());
let cpu = run_one(&mut m, &code);
assert_eq!(cpu.gpr[RAX], 0x0102_0304_0506_0708);
}
#[test]
fn mov_reg_reg() {
let mut m = mem();
let mut cpu = X86Interp::new(CODE, STACK);
cpu.gpr[RAX] = 0xdead_beef;
m.write_init(CODE, &[0x48, 0x89, 0xC3]).unwrap();
cpu.exec(&mut m);
assert_eq!(cpu.gpr[RBX], 0xdead_beef);
}
#[test]
fn mov_mem_roundtrip_with_disp_and_sib() {
let mut m = mem();
let mut cpu = X86Interp::new(CODE, STACK);
cpu.gpr[RAX] = 0x1122_3344_5566_7788;
cpu.gpr[RBX] = 0x1_2000; m.write_init(CODE, &[0x48, 0x89, 0x43, 0x10]).unwrap();
cpu.exec(&mut m);
assert_eq!(m.read_u64(0x1_2010).unwrap(), 0x1122_3344_5566_7788);
m.write_init(CODE, &[0x48, 0x8B, 0x4B, 0x10]).unwrap();
cpu.rip = CODE;
cpu.exec(&mut m);
assert_eq!(cpu.gpr[RCX], 0x1122_3344_5566_7788);
}
#[test]
fn lea_computes_address_without_reading_memory() {
let mut m = mem();
let mut cpu = X86Interp::new(CODE, STACK);
cpu.gpr[RBX] = 0x1_2000;
m.write_init(CODE, &[0x48, 0x8D, 0x43, 0x20]).unwrap();
cpu.exec(&mut m);
assert_eq!(cpu.gpr[RAX], 0x1_2020);
}
#[test]
fn lea_rip_relative() {
let mut m = mem();
let mut cpu = X86Interp::new(CODE, STACK);
m.write_init(CODE, &[0x48, 0x8D, 0x05, 0x10, 0x00, 0x00, 0x00])
.unwrap();
cpu.exec(&mut m);
assert_eq!(cpu.gpr[RAX], CODE + 7 + 0x10);
}
#[test]
fn add_sets_overflow_and_carry() {
let mut m = mem();
let mut cpu = X86Interp::new(CODE, STACK);
cpu.gpr[RAX] = 0x7fff_ffff;
cpu.gpr[RCX] = 1;
m.write_init(CODE, &[0x01, 0xC8]).unwrap();
cpu.exec(&mut m);
assert_eq!(cpu.gpr[RAX], 0x8000_0000);
assert!(cpu.flags.of, "signed overflow must set OF");
assert!(cpu.flags.sf);
assert!(!cpu.flags.cf);
assert!(!cpu.flags.zf);
}
#[test]
fn sub_sets_carry_on_borrow() {
let mut m = mem();
let mut cpu = X86Interp::new(CODE, STACK);
cpu.gpr[RAX] = 1;
cpu.gpr[RCX] = 2;
m.write_init(CODE, &[0x29, 0xC8]).unwrap();
cpu.exec(&mut m);
assert_eq!(cpu.gpr[RAX], 0xffff_ffff); assert!(cpu.flags.cf, "1 - 2 unsigned borrows");
assert!(cpu.flags.sf);
assert!(!cpu.flags.zf);
}
#[test]
fn cmp_does_not_store() {
let mut m = mem();
let mut cpu = X86Interp::new(CODE, STACK);
cpu.gpr[RAX] = 5;
cpu.gpr[RCX] = 5;
m.write_init(CODE, &[0x39, 0xC8]).unwrap();
cpu.exec(&mut m);
assert_eq!(cpu.gpr[RAX], 5, "CMP must not write back");
assert!(cpu.flags.zf);
}
#[test]
fn and_or_xor_clear_cf_and_of() {
let mut m = mem();
let mut cpu = X86Interp::new(CODE, STACK);
cpu.gpr[RAX] = 0xff;
cpu.gpr[RCX] = 0x0f;
m.write_init(CODE, &[0x21, 0xC8]).unwrap();
cpu.exec(&mut m);
assert_eq!(cpu.gpr[RAX], 0x0f);
assert!(!cpu.flags.cf);
assert!(!cpu.flags.of);
}
#[test]
fn test_instruction_is_and_without_store() {
let mut m = mem();
let mut cpu = X86Interp::new(CODE, STACK);
cpu.gpr[RAX] = 0x0f;
cpu.gpr[RCX] = 0xf0;
m.write_init(CODE, &[0x85, 0xC8]).unwrap();
cpu.exec(&mut m);
assert_eq!(cpu.gpr[RAX], 0x0f, "TEST must not write back");
assert!(cpu.flags.zf, "0x0f & 0xf0 == 0");
}
#[test]
fn group1_imm_add_and_cmp() {
let mut m = mem();
let mut cpu = X86Interp::new(CODE, STACK);
cpu.gpr[RAX] = 10;
m.write_init(CODE, &[0x83, 0xC0, 0x05]).unwrap();
cpu.exec(&mut m);
assert_eq!(cpu.gpr[RAX], 15);
m.write_init(CODE, &[0x83, 0xF8, 0x0F]).unwrap();
cpu.rip = CODE;
cpu.exec(&mut m);
assert_eq!(cpu.gpr[RAX], 15, "CMP must not write back");
assert!(cpu.flags.zf);
}
#[test]
fn inc_dec_leave_carry_flag_alone() {
let mut m = mem();
let mut cpu = X86Interp::new(CODE, STACK);
cpu.gpr[RAX] = 41;
cpu.flags.cf = true; m.write_init(CODE, &[0xFF, 0xC0]).unwrap();
cpu.exec(&mut m);
assert_eq!(cpu.gpr[RAX], 42);
assert!(cpu.flags.cf, "INC must not touch CF");
m.write_init(CODE, &[0xFF, 0xC8]).unwrap();
cpu.rip = CODE;
cpu.exec(&mut m);
assert_eq!(cpu.gpr[RAX], 41);
assert!(cpu.flags.cf, "DEC must not touch CF");
}
#[test]
fn neg_sets_carry_unless_zero() {
let mut m = mem();
let mut cpu = X86Interp::new(CODE, STACK);
cpu.gpr[RAX] = 5;
m.write_init(CODE, &[0xF7, 0xD8]).unwrap();
cpu.exec(&mut m);
assert_eq!(cpu.gpr[RAX], 0xffff_fffb); assert!(cpu.flags.cf);
cpu.gpr[RAX] = 0;
cpu.rip = CODE;
cpu.exec(&mut m);
assert_eq!(cpu.gpr[RAX], 0);
assert!(!cpu.flags.cf, "NEG 0 must clear CF");
}
#[test]
fn shifts_by_immediate_and_cl() {
let mut m = mem();
let mut cpu = X86Interp::new(CODE, STACK);
cpu.gpr[RAX] = 1;
m.write_init(CODE, &[0xC1, 0xE0, 0x04]).unwrap();
cpu.exec(&mut m);
assert_eq!(cpu.gpr[RAX], 0x10);
cpu.gpr[RCX] = 2;
m.write_init(CODE, &[0xD3, 0xE8]).unwrap();
cpu.rip = CODE;
cpu.exec(&mut m);
assert_eq!(cpu.gpr[RAX], 0x4);
cpu.gpr[RAX] = 0xffff_fffe; m.write_init(CODE, &[0xC1, 0xF8, 0x01]).unwrap();
cpu.rip = CODE;
cpu.exec(&mut m);
assert_eq!(cpu.gpr[RAX] as u32 as i32, -1);
}
#[test]
fn push_pop_roundtrip() {
let mut m = mem();
let mut cpu = X86Interp::new(CODE, STACK);
cpu.gpr[RAX] = 0x1234;
m.write_init(CODE, &[0x50, 0x5B]).unwrap();
cpu.exec(&mut m); assert_eq!(cpu.gpr[RSP], STACK - 8);
cpu.exec(&mut m); assert_eq!(cpu.gpr[RBX], 0x1234);
assert_eq!(cpu.gpr[RSP], STACK);
}
#[test]
fn push_immediates() {
let mut m = mem();
let mut cpu = X86Interp::new(CODE, STACK);
let mut code = vec![0x6A, 0x7F, 0x68];
code.extend_from_slice(&(-1i32).to_le_bytes());
m.write_init(CODE, &code).unwrap();
cpu.exec(&mut m);
assert_eq!(m.read_u64(STACK - 8).unwrap(), 0x7f);
cpu.exec(&mut m);
assert_eq!(m.read_u64(STACK - 16).unwrap(), u64::MAX);
}
#[test]
fn call_and_ret() {
let mut m = mem();
let mut cpu = X86Interp::new(CODE, STACK);
let mut code = vec![0xE8];
code.extend_from_slice(&3i32.to_le_bytes()); code.push(0x90); code.push(0x90);
code.push(0x90);
code.push(0xC3); m.write_init(CODE, &code).unwrap();
cpu.exec(&mut m); assert_eq!(cpu.rip, CODE + 8);
assert_eq!(m.read_u64(STACK - 8).unwrap(), CODE + 5); cpu.exec(&mut m); assert_eq!(cpu.rip, CODE + 5);
assert_eq!(cpu.gpr[RSP], STACK);
}
#[test]
fn jmp_rel8_and_rel32() {
let mut m = mem();
let mut cpu = X86Interp::new(CODE, STACK);
m.write_init(CODE, &[0xEB, 0x02]).unwrap();
cpu.exec(&mut m);
assert_eq!(cpu.rip, CODE + 4);
let mut code = vec![0xE9];
code.extend_from_slice(&(-0x100i32).to_le_bytes());
m.write_init(CODE, &code).unwrap();
cpu.rip = CODE;
cpu.exec(&mut m);
assert_eq!(cpu.rip, (CODE + 5).wrapping_sub(0x100));
}
#[test]
fn jcc_conditions() {
let mut m = mem();
let mut cpu = X86Interp::new(CODE, STACK);
cpu.gpr[RAX] = 7;
m.write_init(CODE, &[0x39, 0xC0, 0x74, 0x02]).unwrap(); cpu.exec(&mut m); assert!(cpu.flags.zf);
cpu.exec(&mut m); assert_eq!(cpu.rip, CODE + 2 + 2 + 2);
cpu.rip = CODE + 2;
m.write_init(CODE + 2, &[0x75, 0x02]).unwrap(); cpu.exec(&mut m);
assert_eq!(cpu.rip, CODE + 4);
cpu.gpr[RAX] = 1;
cpu.gpr[RCX] = 2;
m.write_init(CODE, &[0x39, 0xC8]).unwrap(); cpu.rip = CODE;
cpu.exec(&mut m);
assert!(cpu.flags.cf, "1 < 2 unsigned sets CF");
m.write_init(CODE, &[0x72, 0x02]).unwrap(); cpu.rip = CODE;
cpu.exec(&mut m);
assert_eq!(cpu.rip, CODE + 2 + 2);
m.write_init(CODE, &[0x73, 0x02]).unwrap(); cpu.rip = CODE;
cpu.exec(&mut m);
assert_eq!(cpu.rip, CODE + 2);
cpu.gpr[RAX] = 0xffff_ffff; cpu.gpr[RCX] = 1;
m.write_init(CODE, &[0x39, 0xC8]).unwrap(); cpu.rip = CODE;
cpu.exec(&mut m);
assert!(cpu.flags.sf != cpu.flags.of, "-1 < 1 signed");
m.write_init(CODE, &[0x7C, 0x02]).unwrap(); cpu.rip = CODE;
cpu.exec(&mut m);
assert_eq!(cpu.rip, CODE + 2 + 2);
m.write_init(CODE, &[0x7D, 0x02]).unwrap(); cpu.rip = CODE;
cpu.exec(&mut m);
assert_eq!(cpu.rip, CODE + 2);
}
#[test]
fn jcc_rel32_two_byte_opcode() {
let mut m = mem();
let mut cpu = X86Interp::new(CODE, STACK);
cpu.flags.zf = true;
let mut code = vec![0x0F, 0x84];
code.extend_from_slice(&0x100i32.to_le_bytes());
m.write_init(CODE, &code).unwrap();
cpu.exec(&mut m);
assert_eq!(cpu.rip, CODE + 6 + 0x100);
}
#[test]
fn write_then_exit_group_traps_with_right_nr_and_args() {
let mut m = mem();
let msg_addr = 0x1_2000u64;
m.write_init(msg_addr, b"hi\n").unwrap();
let code: Vec<u8> = vec![
0xBF, 0x01, 0x00, 0x00, 0x00, 0xBE, 0x00, 0x20, 0x01, 0x00, 0xBA, 0x03, 0x00, 0x00, 0x00, 0xB8, 0x01, 0x00, 0x00, 0x00, 0x0F, 0x05, 0xB8, 0xE7, 0x00, 0x00, 0x00, 0x31, 0xFF, 0x0F, 0x05, ];
m.write_init(CODE, &code).unwrap();
let mut cpu = X86Interp::new(CODE, STACK);
let syscall_pc = CODE + 20;
match cpu.run(&mut m).unwrap() {
Exit::Syscall => {}
other => panic!("expected Exit::Syscall, got {other:?}"),
}
assert_eq!(cpu.pc(), syscall_pc, "rip must stay on the syscall opcode");
assert_eq!(cpu.syscall_nr(), 1, "SYS_write");
assert_eq!(cpu.syscall_args()[0], 1);
assert_eq!(cpu.syscall_args()[1], msg_addr);
assert_eq!(cpu.syscall_args()[2], 3);
cpu.set_syscall_ret(3); assert_eq!(cpu.pc(), syscall_pc + 2);
match cpu.run(&mut m).unwrap() {
Exit::Syscall => {}
other => panic!("expected Exit::Syscall, got {other:?}"),
}
assert_eq!(cpu.syscall_nr(), 231, "SYS_exit_group");
assert_eq!(cpu.syscall_args()[0], 0);
}
#[test]
fn illegal_opcode_surfaces_as_exit() {
let mut m = mem();
let mut cpu = X86Interp::new(CODE, STACK);
m.write_init(CODE, &[0x0F, 0xFF]).unwrap();
match cpu.run(&mut m).unwrap() {
Exit::IllegalInstruction { pc } => assert_eq!(pc, CODE),
other => panic!("expected IllegalInstruction, got {other:?}"),
}
}
#[test]
fn fork_and_reset() {
let mut cpu = X86Interp::new(CODE, STACK);
cpu.gpr[RAX] = 42;
let forked = cpu.fork();
assert_eq!(forked.reg(RAX), 42);
cpu.reset(0x2_0000, 0x3_0000);
assert_eq!(cpu.pc(), 0x2_0000);
assert_eq!(cpu.sp(), 0x3_0000);
assert_eq!(cpu.gpr[RAX], 0);
}
#[test]
fn mov_r8_imm8_and_high_byte_regs() {
let mut m = mem();
let mut cpu = X86Interp::new(CODE, STACK);
let code = [0xB0, 0x12, 0xB4, 0x34, 0x88, 0xC1, 0x8A, 0xDC];
m.write_init(CODE, &code).unwrap();
cpu.exec(&mut m); assert_eq!(cpu.gpr[RAX] & 0xff, 0x12);
cpu.exec(&mut m); assert_eq!(
(cpu.gpr[RAX] >> 8) & 0xff,
0x34,
"AH is the high byte of RAX"
);
cpu.exec(&mut m); assert_eq!(cpu.gpr[RCX] & 0xff, 0x12);
cpu.exec(&mut m); assert_eq!(cpu.gpr[RBX] & 0xff, 0x34);
}
#[test]
fn mov_r8_via_rex_uses_low_byte_not_high_byte() {
let mut m = mem();
let mut cpu = X86Interp::new(CODE, STACK);
cpu.gpr[RDI] = 0xffff_ffff_ffff_ff00;
m.write_init(CODE, &[0x40, 0xB7, 0x7F]).unwrap();
cpu.exec(&mut m);
assert_eq!(cpu.gpr[RDI], 0xffff_ffff_ffff_ff7f);
}
#[test]
fn alu_8bit_forms_add_sub_xor_cmp_and_group1() {
let mut m = mem();
let mut cpu = X86Interp::new(CODE, STACK);
let code: Vec<u8> = vec![
0xB0, 0x05, 0xB1, 0x03, 0x00, 0xC8, 0x28, 0xC8, 0x38, 0xC8, 0x30, 0xC0, 0x80, 0xC0, 0x0A, 0x80, 0xF8, 0x0A, ];
m.write_init(CODE, &code).unwrap();
cpu.exec(&mut m); cpu.exec(&mut m); cpu.exec(&mut m); assert_eq!(cpu.gpr[RAX] & 0xff, 8);
cpu.exec(&mut m); assert_eq!(cpu.gpr[RAX] & 0xff, 5);
cpu.exec(&mut m); assert!(!cpu.flags.cf, "5 - 3 does not borrow");
assert!(!cpu.flags.zf);
cpu.exec(&mut m); assert_eq!(cpu.gpr[RAX] & 0xff, 0);
assert!(cpu.flags.zf);
cpu.exec(&mut m); assert_eq!(cpu.gpr[RAX] & 0xff, 10);
cpu.exec(&mut m); assert!(cpu.flags.zf, "10 == 10");
assert_eq!(cpu.gpr[RAX] & 0xff, 10, "CMP must not write back");
}
#[test]
fn movzx_and_movsx_sign_and_zero_extend() {
let mut m = mem();
let mut cpu = X86Interp::new(CODE, STACK);
cpu.gpr[RAX] = 0xdead_beef_dead_beef;
m.write_init(CODE, &[0xB0, 0x80]).unwrap(); cpu.exec(&mut m);
m.write_init(CODE, &[0x48, 0x0F, 0xB6, 0xC0]).unwrap();
cpu.rip = CODE;
cpu.exec(&mut m);
assert_eq!(cpu.gpr[RAX], 0x80, "MOVZX zero-extends");
m.write_init(CODE, &[0x48, 0x0F, 0xBE, 0xD8]).unwrap();
cpu.rip = CODE;
cpu.exec(&mut m);
assert_eq!(cpu.gpr[RBX], 0xffff_ffff_ffff_ff80, "MOVSX sign-extends");
cpu.gpr[RCX] = 0x8000;
m.write_init(CODE, &[0x0F, 0xB7, 0xC1]).unwrap();
cpu.rip = CODE;
cpu.exec(&mut m);
assert_eq!(cpu.gpr[RAX], 0x8000);
m.write_init(CODE, &[0x0F, 0xBF, 0xD1]).unwrap();
cpu.rip = CODE;
cpu.exec(&mut m);
assert_eq!(
cpu.gpr[RDX], 0xffff_8000,
"MOVSX from 16-bit, 32-bit dest zero-extends the upper 32 bits of RDX"
);
}
#[test]
fn movsxd_sign_extends_dword_to_qword() {
let mut m = mem();
let mut cpu = X86Interp::new(CODE, STACK);
cpu.gpr[RCX] = 0xffff_ffff_8000_0000; m.write_init(CODE, &[0x48, 0x63, 0xC1]).unwrap();
cpu.exec(&mut m);
assert_eq!(cpu.gpr[RAX], 0xffff_ffff_8000_0000);
}
#[test]
fn cmovcc_and_setcc_driven_by_cmp() {
let mut m = mem();
let mut cpu = X86Interp::new(CODE, STACK);
cpu.gpr[RAX] = 1;
cpu.gpr[RCX] = 1;
cpu.gpr[RDX] = 0xffff_ffff_ffff_ffff;
cpu.gpr[RBX] = 0;
cpu.gpr[R8] = 0;
let code = [
0x39, 0xC8, 0x48, 0x0F, 0x44, 0xD0, 0x41, 0x0F, 0x94, 0xC0, 0x0F, 0x95, 0xC3,
];
m.write_init(CODE, &code).unwrap();
cpu.exec(&mut m); assert!(cpu.flags.zf);
cpu.exec(&mut m); assert_eq!(cpu.gpr[RDX], 1);
cpu.exec(&mut m); assert_eq!(cpu.gpr[R8] & 0xff, 1);
cpu.exec(&mut m); assert_eq!(cpu.gpr[RBX] & 0xff, 0);
}
#[test]
fn cmovcc_does_not_write_when_condition_is_false() {
let mut m = mem();
let mut cpu = X86Interp::new(CODE, STACK);
cpu.gpr[RAX] = 1;
cpu.gpr[RCX] = 2;
cpu.gpr[RDX] = 0x1234;
let code = [0x39, 0xC8, 0x48, 0x0F, 0x44, 0xD0];
m.write_init(CODE, &code).unwrap();
cpu.exec(&mut m);
assert!(!cpu.flags.zf);
cpu.exec(&mut m);
assert_eq!(
cpu.gpr[RDX], 0x1234,
"CMOVcc must not write when the condition is false"
);
}
#[test]
fn mul_and_div_pair() {
let mut m = mem();
let mut cpu = X86Interp::new(CODE, STACK);
cpu.gpr[RAX] = 6;
cpu.gpr[RCX] = 7;
m.write_init(CODE, &[0xF7, 0xE1]).unwrap();
cpu.exec(&mut m);
assert_eq!(cpu.gpr[RAX] & 0xffff_ffff, 42);
assert_eq!(cpu.gpr[RDX] & 0xffff_ffff, 0);
assert!(!cpu.flags.cf, "42 fits in 32 bits, no overflow into edx");
m.write_init(CODE, &[0xF7, 0xF1]).unwrap();
cpu.rip = CODE;
cpu.exec(&mut m);
assert_eq!(cpu.gpr[RAX] & 0xffff_ffff, 6);
assert_eq!(cpu.gpr[RDX] & 0xffff_ffff, 0);
cpu.gpr[RAX] = 0xffff_ffec; cpu.gpr[RDX] = 0xffff_ffff; cpu.gpr[RCX] = 7;
m.write_init(CODE, &[0xF7, 0xF9]).unwrap();
cpu.rip = CODE;
cpu.exec(&mut m);
assert_eq!(cpu.gpr[RAX] as u32 as i32, -2);
assert_eq!(cpu.gpr[RDX] as u32 as i32, -6);
}
#[test]
fn mul_8bit_and_div_by_zero_is_illegal() {
let mut m = mem();
let mut cpu = X86Interp::new(CODE, STACK);
cpu.gpr[RAX] = 20; cpu.gpr[RCX] = 3; m.write_init(CODE, &[0xF6, 0xE1]).unwrap();
cpu.exec(&mut m);
assert_eq!(
cpu.gpr[RAX] & 0xffff,
60,
"AX = AL * CL for an 8-bit operand"
);
cpu.gpr[RCX] = 0;
m.write_init(CODE, &[0xF6, 0xF1]).unwrap();
cpu.rip = CODE;
match cpu.run(&mut m).unwrap() {
Exit::IllegalInstruction { .. } => {}
other => panic!("expected IllegalInstruction on divide-by-zero, got {other:?}"),
}
}
#[test]
fn imul_two_and_three_operand_forms() {
let mut m = mem();
let mut cpu = X86Interp::new(CODE, STACK);
cpu.gpr[RAX] = 6;
cpu.gpr[RCX] = 7;
m.write_init(CODE, &[0x0F, 0xAF, 0xC1]).unwrap();
cpu.exec(&mut m);
assert_eq!(cpu.gpr[RAX] & 0xffff_ffff, 42);
assert!(!cpu.flags.cf);
let mut code = vec![0x69, 0xD1];
code.extend_from_slice(&100i32.to_le_bytes());
m.write_init(CODE, &code).unwrap();
cpu.rip = CODE;
cpu.exec(&mut m);
assert_eq!(cpu.gpr[RDX] & 0xffff_ffff, 700);
m.write_init(CODE, &[0x6B, 0xD9, 0x05]).unwrap();
cpu.rip = CODE;
cpu.exec(&mut m);
assert_eq!(cpu.gpr[RBX] & 0xffff_ffff, 35);
}
#[test]
fn cdq_cqo_and_cwde_cdqe() {
let mut m = mem();
let mut cpu = X86Interp::new(CODE, STACK);
cpu.gpr[RAX] = 0xffff_ffff_8000_0000; m.write_init(CODE, &[0x99]).unwrap();
cpu.exec(&mut m);
assert_eq!(cpu.gpr[RDX] as u32, 0xffff_ffff);
cpu.gpr[RAX] = 0x8000_0000; m.write_init(CODE, &[0x98]).unwrap();
cpu.rip = CODE;
cpu.exec(&mut m);
assert_eq!(
cpu.gpr[RAX], 0,
"AX=0 sign-extends to EAX=0, clearing the upper 32 bits"
);
cpu.gpr[RAX] = 0xffff_ffff_ffff_8000;
m.write_init(CODE, &[0x48, 0x98]).unwrap();
cpu.rip = CODE;
cpu.exec(&mut m);
assert_eq!(cpu.gpr[RAX], 0xffff_ffff_ffff_8000);
cpu.gpr[RAX] = 0xffff_ffff;
m.write_init(CODE, &[0x48, 0x99]).unwrap();
cpu.rip = CODE;
cpu.exec(&mut m);
assert_eq!(cpu.gpr[RDX], 0, "rax's sign bit (bit 63) is 0 here");
}
#[test]
fn not_and_group4_inc_dec_byte() {
let mut m = mem();
let mut cpu = X86Interp::new(CODE, STACK);
cpu.gpr[RAX] = 0x0000_0000_ffff_0f0f;
m.write_init(CODE, &[0xF7, 0xD0]).unwrap();
cpu.exec(&mut m);
assert_eq!(cpu.gpr[RAX], 0x0000_f0f0);
cpu.gpr[RCX] = 0x7f;
m.write_init(CODE, &[0xFE, 0xC1]).unwrap();
cpu.rip = CODE;
cpu.exec(&mut m);
assert_eq!(cpu.gpr[RCX] & 0xff, 0x80);
m.write_init(CODE, &[0xFE, 0xC9]).unwrap();
cpu.rip = CODE;
cpu.exec(&mut m);
assert_eq!(cpu.gpr[RCX] & 0xff, 0x7f);
}
#[test]
fn group5_call_jmp_and_push_indirect() {
let mut m = mem();
let mut cpu = X86Interp::new(CODE, STACK);
cpu.gpr[RAX] = CODE + 0x100;
m.write_init(CODE, &[0xFF, 0xD0]).unwrap();
cpu.exec(&mut m);
assert_eq!(cpu.rip, CODE + 0x100);
assert_eq!(
m.read_u64(STACK - 8).unwrap(),
CODE + 2,
"return address pushed"
);
cpu.gpr[RBX] = CODE + 0x200;
m.write_init(CODE + 0x100, &[0xFF, 0xE3]).unwrap();
cpu.exec(&mut m);
assert_eq!(cpu.rip, CODE + 0x200);
cpu.gpr[RCX] = 0xdead_beef;
m.write_init(CODE + 0x200, &[0xFF, 0xF1]).unwrap();
cpu.exec(&mut m);
assert_eq!(m.read_u64(STACK - 16).unwrap(), 0xdead_beef);
}
#[test]
fn leave_restores_rsp_from_rbp() {
let mut m = mem();
let mut cpu = X86Interp::new(CODE, STACK);
cpu.gpr[RBP] = STACK - 0x40;
m.write_init(STACK - 0x40, &0x1122_3344u64.to_le_bytes())
.unwrap();
m.write_init(CODE, &[0xC9]).unwrap();
cpu.exec(&mut m);
assert_eq!(cpu.gpr[RBP], 0x1122_3344);
assert_eq!(cpu.gpr[RSP], STACK - 0x40 + 8);
}
#[test]
fn xchg_swaps_registers_and_memory() {
let mut m = mem();
let mut cpu = X86Interp::new(CODE, STACK);
cpu.gpr[RAX] = 1;
cpu.gpr[RCX] = 2;
m.write_init(CODE, &[0x91]).unwrap();
cpu.exec(&mut m);
assert_eq!(cpu.gpr[RAX], 2);
assert_eq!(cpu.gpr[RCX], 1);
cpu.gpr[RBX] = 0x1_8000;
m.write_init(0x1_8000, &0x99u64.to_le_bytes()).unwrap();
cpu.gpr[RDX] = 0x77;
m.write_init(CODE, &[0x87, 0x13]).unwrap();
cpu.rip = CODE;
cpu.exec(&mut m);
assert_eq!(cpu.gpr[RDX] & 0xffff_ffff, 0x99);
assert_eq!(m.read_u64(0x1_8000).unwrap() & 0xffff_ffff, 0x77);
}
#[test]
fn cpuid_leaf0_reports_vendor_string_and_max_leaf() {
let mut m = mem();
let mut cpu = X86Interp::new(CODE, STACK);
cpu.gpr[RAX] = 0;
m.write_init(CODE, &[0x0F, 0xA2]).unwrap();
cpu.exec(&mut m);
assert_eq!(cpu.gpr[RAX] as u32, 7, "max standard leaf");
let mut vendor = Vec::new();
vendor.extend_from_slice(&(cpu.gpr[RBX] as u32).to_le_bytes());
vendor.extend_from_slice(&(cpu.gpr[RDX] as u32).to_le_bytes());
vendor.extend_from_slice(&(cpu.gpr[RCX] as u32).to_le_bytes());
assert_eq!(
vendor, b"GenuineIntel",
"EBX/EDX/ECX spell the vendor string"
);
}
#[test]
fn cpuid_leaf1_edx_has_sse2_bit() {
let mut m = mem();
let mut cpu = X86Interp::new(CODE, STACK);
cpu.gpr[RAX] = 1;
m.write_init(CODE, &[0x0F, 0xA2]).unwrap();
cpu.exec(&mut m);
assert_ne!(
cpu.gpr[RDX] as u32 & (1 << 26),
0,
"SSE2 feature bit (EDX bit 26) is set"
);
assert_ne!(
cpu.gpr[RDX] as u32 & (1 << 0),
0,
"FPU feature bit (EDX bit 0) is set"
);
}
#[test]
fn rdtsc_increases_across_reads() {
let mut m = mem();
let mut cpu = X86Interp::new(CODE, STACK);
m.write_init(CODE, &[0x0F, 0x31]).unwrap();
cpu.exec(&mut m);
let first = (cpu.gpr[RDX] << 32) | (cpu.gpr[RAX] & 0xffff_ffff);
cpu.rip = CODE;
cpu.exec(&mut m);
let second = (cpu.gpr[RDX] << 32) | (cpu.gpr[RAX] & 0xffff_ffff);
assert!(
second > first,
"RDTSC must return a monotonically increasing counter"
);
}
#[test]
fn rdrand_sets_cf_and_a_nonzero_value() {
let mut m = mem();
let mut cpu = X86Interp::new(CODE, STACK);
m.write_init(CODE, &[0x0F, 0xC7, 0xF0]).unwrap();
cpu.exec(&mut m);
assert!(cpu.flags.cf, "RDRAND always reports success");
assert_ne!(cpu.gpr[RAX] as u32, 0);
}
#[test]
fn cmpxchg_success_sets_zf_and_stores_src_failure_loads_accumulator() {
let mut m = mem();
let mut cpu = X86Interp::new(CODE, STACK);
cpu.gpr[RAX] = 5; cpu.gpr[RCX] = 42; cpu.gpr[RBX] = 5; m.write_init(CODE, &[0x0F, 0xB1, 0xCB]).unwrap();
cpu.exec(&mut m);
assert!(cpu.flags.zf, "a match sets ZF");
assert_eq!(cpu.gpr[RBX] & 0xffff_ffff, 42, "dest <- src on a match");
cpu.gpr[RCX] = 99;
cpu.rip = CODE;
cpu.exec(&mut m);
assert!(!cpu.flags.zf, "a mismatch clears ZF");
assert_eq!(
cpu.gpr[RAX] & 0xffff_ffff,
42,
"accumulator <- dest on a mismatch"
);
assert_eq!(
cpu.gpr[RBX] & 0xffff_ffff,
42,
"a mismatch leaves dest untouched"
);
}
#[test]
fn xadd_returns_old_dest_value_and_sums() {
let mut m = mem();
let mut cpu = X86Interp::new(CODE, STACK);
cpu.gpr[RAX] = 10; cpu.gpr[RCX] = 5; m.write_init(CODE, &[0x0F, 0xC1, 0xC8]).unwrap();
cpu.exec(&mut m);
assert_eq!(
cpu.gpr[RCX] & 0xffff_ffff,
10,
"reg gets the old dest value"
);
assert_eq!(cpu.gpr[RAX] & 0xffff_ffff, 15, "dest becomes dest + src");
}
#[test]
fn lock_add_updates_memory_and_sets_flags() {
let mut m = mem();
let mut cpu = X86Interp::new(CODE, STACK);
let addr = 0x1_2000u64;
m.write_init(addr, &10u64.to_le_bytes()).unwrap();
cpu.gpr[RBX] = addr;
cpu.gpr[RCX] = 5;
m.write_init(CODE, &[0xF0, 0x01, 0x0B]).unwrap();
cpu.exec(&mut m);
assert_eq!(
m.read_u32(addr).unwrap(),
15,
"LOCK ADD still performs the add on memory"
);
assert!(!cpu.flags.zf);
}
#[test]
fn rep_movsb_block_copy() {
let mut m = mem();
let mut cpu = X86Interp::new(CODE, STACK);
let src = 0x1_2000u64;
let dst = 0x1_3000u64;
m.write_init(src, b"hello, nixvm!").unwrap();
cpu.gpr[RSI] = src;
cpu.gpr[RDI] = dst;
cpu.gpr[RCX] = 13;
m.write_init(CODE, &[0xF3, 0xA4]).unwrap();
cpu.exec(&mut m);
assert_eq!(cpu.gpr[RCX], 0);
assert_eq!(cpu.gpr[RSI], src + 13);
assert_eq!(cpu.gpr[RDI], dst + 13);
let mut buf = [0u8; 13];
m.read(dst, &mut buf).unwrap();
assert_eq!(&buf, b"hello, nixvm!");
}
#[test]
fn rep_stosb_and_repe_scasb_and_cmpsb() {
let mut m = mem();
let mut cpu = X86Interp::new(CODE, STACK);
let dst = 0x1_2000u64;
cpu.gpr[RAX] = 0x41; cpu.gpr[RDI] = dst;
cpu.gpr[RCX] = 8;
m.write_init(CODE, &[0xF3, 0xAA]).unwrap();
cpu.exec(&mut m);
assert_eq!(cpu.gpr[RCX], 0);
assert_eq!(cpu.gpr[RDI], dst + 8);
let mut buf = [0u8; 8];
m.read(dst, &mut buf).unwrap();
assert_eq!(&buf, b"AAAAAAAA");
cpu.gpr[RAX] = 0x41;
cpu.gpr[RDI] = dst;
cpu.gpr[RCX] = 8;
m.write_init(CODE, &[0xF3, 0xAE]).unwrap();
cpu.rip = CODE;
cpu.exec(&mut m);
assert_eq!(cpu.gpr[RCX], 0);
assert!(cpu.flags.zf);
let dst2 = 0x1_3000u64;
m.write_init(dst2, b"AAAAAAAA").unwrap();
cpu.gpr[RSI] = dst;
cpu.gpr[RDI] = dst2;
cpu.gpr[RCX] = 8;
m.write_init(CODE, &[0xF3, 0xA6]).unwrap();
cpu.rip = CODE;
cpu.exec(&mut m);
assert_eq!(cpu.gpr[RCX], 0);
assert!(cpu.flags.zf, "all 8 bytes matched");
}
#[test]
fn cld_std_control_string_op_direction() {
let mut m = mem();
let mut cpu = X86Interp::new(CODE, STACK);
let base = 0x1_2000u64;
m.write_init(base, b"XYZ").unwrap();
cpu.gpr[RSI] = base + 2; cpu.gpr[RDI] = 0x1_3000 + 2;
cpu.gpr[RCX] = 3;
m.write_init(CODE, &[0xFD, 0xF3, 0xA4]).unwrap();
cpu.exec(&mut m); assert!(cpu.df);
cpu.exec(&mut m); assert_eq!(cpu.gpr[RSI], base - 1);
let mut buf = [0u8; 3];
m.read(0x1_3000, &mut buf).unwrap();
assert_eq!(&buf, b"XYZ");
}
#[test]
fn assembled_loop_sums_one_to_five() {
let mut m = mem();
let mut cpu = X86Interp::new(CODE, STACK);
let code: Vec<u8> = vec![
0xB9, 0x05, 0x00, 0x00, 0x00, 0x31, 0xD2, 0x01, 0xCA, 0xFF, 0xC9, 0x75, 0xFA, ];
m.write_init(CODE, &code).unwrap();
cpu.rip = CODE;
let exit = cpu.run(&mut m).unwrap();
match exit {
Exit::IllegalInstruction { .. } | Exit::MemFault { .. } => {}
other => panic!("unexpected exit before the loop could fall through: {other:?}"),
}
assert_eq!(cpu.gpr[RDX] & 0xffff_ffff, 15, "1+2+3+4+5 == 15");
}
#[test]
fn sse_movsd_load_store_and_scalar_arith() {
let mut m = mem();
let mut cpu = X86Interp::new(CODE, STACK);
let a_addr = 0x1_2000u64;
let b_addr = 0x1_2008u64;
let out_addr = 0x1_2010u64;
m.write_init(a_addr, &3.0f64.to_le_bytes()).unwrap();
m.write_init(b_addr, &4.0f64.to_le_bytes()).unwrap();
cpu.gpr[RAX] = a_addr;
cpu.gpr[RBX] = b_addr;
cpu.gpr[RCX] = out_addr;
m.write_init(CODE, &[0xF2, 0x0F, 0x10, 0x00]).unwrap();
cpu.exec(&mut m);
assert_eq!(cpu.xmm[0] as u64, 3.0f64.to_bits(), "MOVSD load from [rax]");
assert_eq!(
cpu.xmm[0] >> 64,
0,
"MOVSD mem-load zeroes the upper 64 bits"
);
m.write_init(CODE, &[0xF2, 0x0F, 0x10, 0x0B]).unwrap();
cpu.rip = CODE;
cpu.exec(&mut m);
assert_eq!(cpu.xmm[1] as u64, 4.0f64.to_bits());
cpu.xmm[3] = 0xdead_beefu128 << 64;
m.write_init(CODE, &[0xF2, 0x0F, 0x10, 0xD9]).unwrap();
cpu.rip = CODE;
cpu.exec(&mut m);
assert_eq!(
cpu.xmm[3] >> 64,
0xdead_beef,
"reg-reg MOVSD preserves dest's upper bits"
);
assert_eq!(cpu.xmm[3] as u64, 4.0f64.to_bits());
m.write_init(CODE, &[0xF2, 0x0F, 0x58, 0xC1]).unwrap();
cpu.rip = CODE;
cpu.exec(&mut m);
assert_eq!(f64::from_bits(cpu.xmm[0] as u64), 7.0);
m.write_init(CODE, &[0xF2, 0x0F, 0x11, 0x01]).unwrap();
cpu.rip = CODE;
cpu.exec(&mut m);
assert_eq!(f64::from_bits(m.read_u64(out_addr).unwrap()), 7.0);
m.write_init(CODE, &[0xF2, 0x0F, 0x59, 0xC1]).unwrap();
cpu.rip = CODE;
cpu.exec(&mut m);
assert_eq!(f64::from_bits(cpu.xmm[0] as u64), 28.0);
m.write_init(CODE, &[0xF2, 0x0F, 0x5E, 0xC1]).unwrap();
cpu.rip = CODE;
cpu.exec(&mut m);
assert_eq!(f64::from_bits(cpu.xmm[0] as u64), 7.0);
m.write_init(CODE, &[0xF2, 0x0F, 0x51, 0xD0]).unwrap();
cpu.rip = CODE;
cpu.exec(&mut m);
assert_eq!(f64::from_bits(cpu.xmm[2] as u64), 7.0f64.sqrt());
}
#[test]
fn sse_cvtsi2sd_and_cvttsd2si_round_trip() {
let mut m = mem();
let mut cpu = X86Interp::new(CODE, STACK);
cpu.gpr[RAX] = (-42i64) as u64;
m.write_init(CODE, &[0xF2, 0x0F, 0x2A, 0xC0]).unwrap();
cpu.exec(&mut m);
assert_eq!(f64::from_bits(cpu.xmm[0] as u64), -42.0);
m.write_init(CODE, &[0xF2, 0x0F, 0x2C, 0xC8]).unwrap();
cpu.rip = CODE;
cpu.exec(&mut m);
assert_eq!(
cpu.gpr[RCX] as u32 as i32, -42,
"CVTTSD2SI truncates back to the original int"
);
}
#[test]
fn sse_ucomisd_sets_zf_cf_pf() {
let mut m = mem();
let mut cpu = X86Interp::new(CODE, STACK);
cpu.xmm[0] = u128::from(1.0f64.to_bits());
cpu.xmm[1] = u128::from(2.0f64.to_bits());
m.write_init(CODE, &[0x66, 0x0F, 0x2E, 0xC1]).unwrap();
cpu.exec(&mut m);
assert!(cpu.flags.cf, "1.0 < 2.0 sets CF");
assert!(!cpu.flags.zf);
assert!(!cpu.flags.pf);
cpu.xmm[1] = u128::from(1.0f64.to_bits());
m.write_init(CODE, &[0x66, 0x0F, 0x2E, 0xC1]).unwrap();
cpu.rip = CODE;
cpu.exec(&mut m);
assert!(!cpu.flags.cf);
assert!(cpu.flags.zf, "1.0 == 1.0 sets ZF");
assert!(!cpu.flags.pf);
cpu.xmm[1] = u128::from(0.5f64.to_bits());
m.write_init(CODE, &[0x66, 0x0F, 0x2E, 0xC1]).unwrap();
cpu.rip = CODE;
cpu.exec(&mut m);
assert!(!cpu.flags.cf);
assert!(!cpu.flags.zf);
assert!(!cpu.flags.pf, "1.0 > 0.5 clears CF/ZF/PF");
cpu.xmm[1] = u128::from(f64::NAN.to_bits());
m.write_init(CODE, &[0x66, 0x0F, 0x2E, 0xC1]).unwrap();
cpu.rip = CODE;
cpu.exec(&mut m);
assert!(
cpu.flags.cf && cpu.flags.zf && cpu.flags.pf,
"an unordered compare sets CF/ZF/PF"
);
assert!(!cpu.flags.of && !cpu.flags.sf, "OF/SF are always cleared");
}
#[test]
fn sse_pxor_zeroes_register() {
let mut m = mem();
let mut cpu = X86Interp::new(CODE, STACK);
cpu.xmm[0] = 0xdead_beef_dead_beef_dead_beef_dead_beefu128;
m.write_init(CODE, &[0x66, 0x0F, 0xEF, 0xC0]).unwrap();
cpu.exec(&mut m);
assert_eq!(cpu.xmm[0], 0);
}
#[test]
fn sse_pcmpeqb_and_pmovmskb() {
let mut m = mem();
let mut cpu = X86Interp::new(CODE, STACK);
let a: [u8; 16] = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16];
let mut b = a;
b[0] = 0xFF; b[15] = 0xFF; cpu.xmm[0] = u128::from_le_bytes(a);
cpu.xmm[1] = u128::from_le_bytes(b);
m.write_init(CODE, &[0x66, 0x0F, 0x74, 0xC1]).unwrap();
cpu.exec(&mut m);
let mask_bytes = cpu.xmm[0].to_le_bytes();
assert_eq!(mask_bytes[0], 0x00, "unequal byte 0 -> all-zero lane");
assert_eq!(mask_bytes[1], 0xff, "equal byte 1 -> all-one lane");
assert_eq!(mask_bytes[15], 0x00, "unequal byte 15 -> all-zero lane");
m.write_init(CODE, &[0x66, 0x0F, 0xD7, 0xC0]).unwrap();
cpu.rip = CODE;
cpu.exec(&mut m);
assert_eq!(cpu.gpr[RAX], 0x7ffe, "bits 1..=14 set, bits 0 and 15 clear");
}
#[test]
fn bsf_bsr_and_zero_source_sets_zf() {
let mut m = mem();
let mut cpu = X86Interp::new(CODE, STACK);
cpu.gpr[RAX] = 0b0101_0000; m.write_init(CODE, &[0x0F, 0xBC, 0xC8]).unwrap();
cpu.exec(&mut m);
assert_eq!(cpu.gpr[RCX] & 0xffff_ffff, 4);
assert!(!cpu.flags.zf);
m.write_init(CODE, &[0x0F, 0xBD, 0xD0]).unwrap();
cpu.rip = CODE;
cpu.exec(&mut m);
assert_eq!(cpu.gpr[RDX] & 0xffff_ffff, 6);
assert!(!cpu.flags.zf);
cpu.gpr[RSI] = 0;
cpu.gpr[RBX] = 0x1234;
m.write_init(CODE, &[0x0F, 0xBC, 0xDE]).unwrap();
cpu.rip = CODE;
cpu.exec(&mut m);
assert!(cpu.flags.zf, "BSF of a zero source sets ZF");
assert_eq!(
cpu.gpr[RBX], 0x1234,
"BSF must not modify the destination when the source is zero"
);
}
#[test]
fn popcnt_counts_bits_and_sets_zf() {
let mut m = mem();
let mut cpu = X86Interp::new(CODE, STACK);
cpu.gpr[RAX] = 0b1011_0110; m.write_init(CODE, &[0xF3, 0x0F, 0xB8, 0xC8]).unwrap();
cpu.exec(&mut m);
assert_eq!(cpu.gpr[RCX], 5);
assert!(!cpu.flags.zf);
cpu.gpr[RAX] = 0;
m.write_init(CODE, &[0xF3, 0x0F, 0xB8, 0xC8]).unwrap();
cpu.rip = CODE;
cpu.exec(&mut m);
assert_eq!(cpu.gpr[RCX], 0);
assert!(cpu.flags.zf);
}
#[test]
fn bt_register_and_immediate_forms_set_cf() {
let mut m = mem();
let mut cpu = X86Interp::new(CODE, STACK);
cpu.gpr[RAX] = 0b0000_0100; cpu.gpr[RCX] = 2;
m.write_init(CODE, &[0x0F, 0xA3, 0xC8]).unwrap();
cpu.exec(&mut m);
assert!(cpu.flags.cf, "bit 2 of 0b100 is set");
assert_eq!(cpu.gpr[RAX], 0b0000_0100, "BT must not modify the operand");
cpu.gpr[RCX] = 1;
m.write_init(CODE, &[0x0F, 0xA3, 0xC8]).unwrap();
cpu.rip = CODE;
cpu.exec(&mut m);
assert!(!cpu.flags.cf, "bit 1 of 0b100 is clear");
m.write_init(CODE, &[0x0F, 0xBA, 0xE8, 0x00]).unwrap();
cpu.rip = CODE;
cpu.exec(&mut m);
assert!(!cpu.flags.cf, "bit 0 of 0b100 was clear before the set");
assert_eq!(cpu.gpr[RAX] & 0xff, 0b0000_0101, "BTS sets bit 0");
}
#[test]
fn shld_shrd_numeric_results() {
let mut m = mem();
let mut cpu = X86Interp::new(CODE, STACK);
cpu.gpr[RAX] = 0x0000_0001; cpu.gpr[RCX] = 0x8000_0000; m.write_init(CODE, &[0x0F, 0xA4, 0xC8, 0x04]).unwrap();
cpu.exec(&mut m);
assert_eq!(cpu.gpr[RAX] & 0xffff_ffff, 0x18);
cpu.gpr[RAX] = 0x8000_0000; cpu.gpr[RCX] = 0x0000_000f; m.write_init(CODE, &[0x0F, 0xAC, 0xC8, 0x04]).unwrap();
cpu.rip = CODE;
cpu.exec(&mut m);
assert_eq!(cpu.gpr[RAX] & 0xffff_ffff, 0xf800_0000);
}
#[test]
fn bswap_reverses_byte_order() {
let mut m = mem();
let mut cpu = X86Interp::new(CODE, STACK);
cpu.gpr[RAX] = 0x1122_3344;
m.write_init(CODE, &[0x0F, 0xC8]).unwrap();
cpu.exec(&mut m);
assert_eq!(cpu.gpr[RAX], 0x4433_2211);
cpu.gpr[RCX] = 0x0102_0304_0506_0708;
m.write_init(CODE, &[0x48, 0x0F, 0xC9]).unwrap();
cpu.rip = CODE;
cpu.exec(&mut m);
assert_eq!(cpu.gpr[RCX], 0x0807_0605_0403_0201);
}
#[test]
fn pshufd_permutes_dword_lanes() {
let mut m = mem();
let mut cpu = X86Interp::new(CODE, STACK);
let lanes: [u32; 4] = [0x1111_1111, 0x2222_2222, 0x3333_3333, 0x4444_4444];
let mut bytes = [0u8; 16];
for (i, v) in lanes.iter().enumerate() {
bytes[i * 4..i * 4 + 4].copy_from_slice(&v.to_le_bytes());
}
cpu.xmm[1] = u128::from_le_bytes(bytes);
m.write_init(CODE, &[0x66, 0x0F, 0x70, 0xC1, 0x1B]).unwrap();
cpu.exec(&mut m);
let out = cpu.xmm[0].to_le_bytes();
assert_eq!(
u32::from_le_bytes(out[0..4].try_into().unwrap()),
0x4444_4444
);
assert_eq!(
u32::from_le_bytes(out[4..8].try_into().unwrap()),
0x3333_3333
);
assert_eq!(
u32::from_le_bytes(out[8..12].try_into().unwrap()),
0x2222_2222
);
assert_eq!(
u32::from_le_bytes(out[12..16].try_into().unwrap()),
0x1111_1111
);
}
#[test]
fn punpcklbw_interleaves_bytes() {
let mut m = mem();
let mut cpu = X86Interp::new(CODE, STACK);
cpu.xmm[0] = u128::from_le_bytes([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]);
cpu.xmm[1] = u128::from_le_bytes([
101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116,
]);
m.write_init(CODE, &[0x66, 0x0F, 0x60, 0xC1]).unwrap();
cpu.exec(&mut m);
let out = cpu.xmm[0].to_le_bytes();
assert_eq!(
out,
[
1, 101, 2, 102, 3, 103, 4, 104, 5, 105, 6, 106, 7, 107, 8, 108
]
);
}
#[test]
fn shufps_selects_lanes() {
let mut m = mem();
let mut cpu = X86Interp::new(CODE, STACK);
let d: [u32; 4] = [10, 20, 30, 40];
let s: [u32; 4] = [50, 60, 70, 80];
let (mut db, mut sb) = ([0u8; 16], [0u8; 16]);
for i in 0..4 {
db[i * 4..i * 4 + 4].copy_from_slice(&d[i].to_le_bytes());
sb[i * 4..i * 4 + 4].copy_from_slice(&s[i].to_le_bytes());
}
cpu.xmm[0] = u128::from_le_bytes(db);
cpu.xmm[1] = u128::from_le_bytes(sb);
let imm = 0b01_00_11_10u8;
m.write_init(CODE, &[0x0F, 0xC6, 0xC1, imm]).unwrap();
cpu.exec(&mut m);
let out = cpu.xmm[0].to_le_bytes();
assert_eq!(
u32::from_le_bytes(out[0..4].try_into().unwrap()),
30,
"lane0 <- dst[2]"
);
assert_eq!(
u32::from_le_bytes(out[4..8].try_into().unwrap()),
40,
"lane1 <- dst[3]"
);
assert_eq!(
u32::from_le_bytes(out[8..12].try_into().unwrap()),
50,
"lane2 <- src[0]"
);
assert_eq!(
u32::from_le_bytes(out[12..16].try_into().unwrap()),
60,
"lane3 <- src[1]"
);
}
#[test]
fn pslldq_shifts_whole_register_by_bytes() {
let mut m = mem();
let mut cpu = X86Interp::new(CODE, STACK);
cpu.xmm[0] = u128::from_le_bytes([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]);
m.write_init(CODE, &[0x66, 0x0F, 0x73, 0xF8, 0x02]).unwrap();
cpu.exec(&mut m);
let out = cpu.xmm[0].to_le_bytes();
assert_eq!(&out[0..2], &[0, 0], "low 2 bytes are zero-filled");
assert_eq!(
&out[2..16],
&[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14],
"bytes shifted up by 2, top 2 bytes dropped"
);
}
#[test]
fn fld_fadd_fstp_m64_roundtrip() {
let mut m = mem();
let mut cpu = X86Interp::new(CODE, STACK);
let (a, b, c) = (0x1_2000u64, 0x1_2008u64, 0x1_2010u64);
m.write_init(a, &2.5f64.to_le_bytes()).unwrap();
m.write_init(b, &4.0f64.to_le_bytes()).unwrap();
cpu.gpr[RBX] = a;
cpu.gpr[RCX] = b;
cpu.gpr[RDX] = c;
m.write_init(CODE, &[0xDD, 0x03, 0xDC, 0x01, 0xDD, 0x1A])
.unwrap();
cpu.exec(&mut m); cpu.exec(&mut m); cpu.exec(&mut m); assert_eq!(m.read_u64(c).unwrap(), 6.5f64.to_bits());
assert_eq!(cpu.fpu_top, 0, "FLD's push and FSTP's pop must cancel out");
}
#[test]
fn fmulp_multiplies_and_pops() {
let mut m = mem();
let mut cpu = X86Interp::new(CODE, STACK);
let (p1, p2) = (0x1_2000u64, 0x1_2008u64);
m.write_init(p1, &2.0f64.to_le_bytes()).unwrap();
m.write_init(p2, &3.0f64.to_le_bytes()).unwrap();
cpu.gpr[RBX] = p1;
cpu.gpr[RCX] = p2;
m.write_init(CODE, &[0xDD, 0x03, 0xDD, 0x01, 0xDE, 0xC9])
.unwrap();
cpu.exec(&mut m);
cpu.exec(&mut m);
cpu.exec(&mut m);
assert_eq!(cpu.st_get(0), 6.0);
assert_eq!(cpu.fpu_top, 7, "FMULP pops one value off the stack");
}
#[test]
fn fild_fsqrt_int_to_float() {
let mut m = mem();
let mut cpu = X86Interp::new(CODE, STACK);
let p = 0x1_2000u64;
m.write_init(p, &16i32.to_le_bytes()).unwrap();
cpu.gpr[RBX] = p;
m.write_init(CODE, &[0xDB, 0x03, 0xD9, 0xFA]).unwrap();
cpu.exec(&mut m); cpu.exec(&mut m); assert_eq!(cpu.st_get(0), 4.0);
}
#[test]
fn fld1_fldz_constants() {
let mut m = mem();
let mut cpu = X86Interp::new(CODE, STACK);
m.write_init(CODE, &[0xD9, 0xE8, 0xD9, 0xEE]).unwrap();
cpu.exec(&mut m);
assert_eq!(cpu.st_get(0), 1.0);
cpu.exec(&mut m);
assert_eq!(cpu.st_get(0), 0.0, "FLDZ pushes 0.0 as the new ST(0)");
assert_eq!(cpu.st_get(1), 1.0, "FLD1's value is still ST(1)");
}
#[test]
fn fcomi_sets_eflags_for_less_greater_equal() {
let mut m = mem();
let (p_one, p_two) = (0x1_2000u64, 0x1_2008u64);
m.write_init(p_one, &1.0f64.to_le_bytes()).unwrap();
m.write_init(p_two, &2.0f64.to_le_bytes()).unwrap();
let code = [0xDD, 0x03, 0xDD, 0x01, 0xDB, 0xF1];
let mut less = X86Interp::new(CODE, STACK);
less.gpr[RBX] = p_two;
less.gpr[RCX] = p_one;
m.write_init(CODE, &code).unwrap();
less.exec(&mut m);
less.exec(&mut m);
less.exec(&mut m);
assert!(less.flags.cf, "ST(0)=1.0 < ST(1)=2.0 sets CF");
assert!(!less.flags.zf);
let mut greater = X86Interp::new(CODE, STACK);
greater.gpr[RBX] = p_one;
greater.gpr[RCX] = p_two;
m.write_init(CODE, &code).unwrap();
greater.exec(&mut m);
greater.exec(&mut m);
greater.exec(&mut m);
assert!(!greater.flags.cf, "ST(0)=2.0 > ST(1)=1.0 clears CF");
assert!(!greater.flags.zf);
let mut equal = X86Interp::new(CODE, STACK);
equal.gpr[RBX] = p_one;
equal.gpr[RCX] = p_one;
m.write_init(CODE, &code).unwrap();
equal.exec(&mut m);
equal.exec(&mut m);
equal.exec(&mut m);
assert!(!equal.flags.cf);
assert!(equal.flags.zf, "equal operands set ZF");
}
#[test]
fn fistp_rounds_per_control_word_truncate_mode() {
let mut m = mem();
let mut cpu = X86Interp::new(CODE, STACK);
let (cw_addr, val_addr, out_addr) = (0x1_2000u64, 0x1_2008u64, 0x1_2010u64);
m.write_init(cw_addr, &0x0F7Fu16.to_le_bytes()).unwrap(); m.write_init(val_addr, &3.75f64.to_le_bytes()).unwrap();
cpu.gpr[RBX] = cw_addr;
cpu.gpr[RCX] = val_addr;
cpu.gpr[RDX] = out_addr;
m.write_init(CODE, &[0xD9, 0x2B, 0xDD, 0x01, 0xDB, 0x1A])
.unwrap();
cpu.exec(&mut m); cpu.exec(&mut m); cpu.exec(&mut m); assert_eq!(
m.read_u32(out_addr).unwrap() as i32,
3,
"round-toward-zero (FLDCW RC=11) truncates 3.75 to 3"
);
}
#[test]
fn fxch_swaps_st0_and_st1() {
let mut m = mem();
let mut cpu = X86Interp::new(CODE, STACK);
let (p1, p2) = (0x1_2000u64, 0x1_2008u64);
m.write_init(p1, &1.0f64.to_le_bytes()).unwrap();
m.write_init(p2, &2.0f64.to_le_bytes()).unwrap();
cpu.gpr[RBX] = p1;
cpu.gpr[RCX] = p2;
m.write_init(CODE, &[0xDD, 0x03, 0xDD, 0x01, 0xD9, 0xC9])
.unwrap();
cpu.exec(&mut m); cpu.exec(&mut m); cpu.exec(&mut m); assert_eq!(cpu.st_get(0), 1.0);
assert_eq!(cpu.st_get(1), 2.0);
}
}