mirror of
https://github.com/cloud-hypervisor/cloud-hypervisor.git
synced 2025-02-22 11:22:26 +00:00
hypervisor: emulator: emulate CMP
Unfortunately Rust stable does not yet have inline ASM support the flag calculation will have to be implemented in software. Signed-off-by: Wei Liu <liuwe@microsoft.com>
This commit is contained in:
parent
dd3844a892
commit
93b7dcac12
380
hypervisor/src/arch/x86/emulator/instructions/cmp.rs
Normal file
380
hypervisor/src/arch/x86/emulator/instructions/cmp.rs
Normal file
@ -0,0 +1,380 @@
|
||||
//
|
||||
// Copyright © 2020 Microsoft
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
|
||||
#![allow(non_camel_case_types)]
|
||||
|
||||
//
|
||||
// CMP-Compare Two Operands
|
||||
//
|
||||
|
||||
extern crate iced_x86;
|
||||
|
||||
use crate::arch::emulator::{EmulationError, PlatformEmulator};
|
||||
use crate::arch::x86::emulator::instructions::*;
|
||||
use crate::arch::x86::Exception;
|
||||
|
||||
// CMP affects OF, SF, ZF, AF, PF and CF
|
||||
const CF_SHIFT: usize = 0;
|
||||
const PF_SHIFT: usize = 2;
|
||||
const AF_SHIFT: usize = 4;
|
||||
const ZF_SHIFT: usize = 6;
|
||||
const SF_SHIFT: usize = 7;
|
||||
const OF_SHIFT: usize = 11;
|
||||
|
||||
const CF: u64 = 1 << CF_SHIFT;
|
||||
const PF: u64 = 1 << PF_SHIFT;
|
||||
const AF: u64 = 1 << AF_SHIFT;
|
||||
const ZF: u64 = 1 << ZF_SHIFT;
|
||||
const SF: u64 = 1 << SF_SHIFT;
|
||||
const OF: u64 = 1 << OF_SHIFT;
|
||||
|
||||
const FLAGS_MASK: u64 = CF | PF | AF | ZF | SF | OF;
|
||||
|
||||
// TODO: Switch to inline asm when that's stable. Executing CMP (or any arthimetic instructions)
|
||||
// natively and extracting RFLAGS will be much faster and make the code simpler.
|
||||
fn calc_rflags_cpazso(op0: u64, op1: u64, op_size: usize) -> u64 {
|
||||
let op_bits = op_size * 8;
|
||||
let msb_shift = op_bits - 1;
|
||||
// CMP is the same as SUB.
|
||||
let result = op0.wrapping_sub(op1);
|
||||
|
||||
// Carry-out vector for SUB.
|
||||
let cout = (!op0 & op1) | ((!op0 ^ op1) & result);
|
||||
|
||||
let cf = ((cout >> msb_shift) & 0x1) << CF_SHIFT;
|
||||
|
||||
// PF only needs the least significant byte. XOR its higher 4 bits with its lower 4 bits then
|
||||
// use the value directly.
|
||||
let pf = ((0x9669 >> ((result ^ (result >> 4)) & 0xf)) & 0x1) << PF_SHIFT;
|
||||
|
||||
// AF cares about the lowest 4 bits (nibble). msb_shift is 3 in this case.
|
||||
let af = ((cout >> 3) & 0x1) << AF_SHIFT;
|
||||
|
||||
let zf = if result & (!0u64 >> (63 - msb_shift)) == 0 {
|
||||
1
|
||||
} else {
|
||||
0
|
||||
} << ZF_SHIFT;
|
||||
|
||||
let sf = ((result >> msb_shift) & 0x1) << SF_SHIFT;
|
||||
|
||||
// Overflow happens when two operands have the same sign but the result has a different sign.
|
||||
let of = ((((op0 ^ op1) & (op0 ^ result)) >> msb_shift) & 0x1) << OF_SHIFT;
|
||||
|
||||
cf | pf | af | zf | sf | of
|
||||
}
|
||||
|
||||
macro_rules! cmp_rm_r {
|
||||
($bound:ty) => {
|
||||
fn emulate(
|
||||
&self,
|
||||
insn: &Instruction,
|
||||
state: &mut T,
|
||||
platform: &mut dyn PlatformEmulator<CpuState = T>,
|
||||
) -> Result<(), EmulationError<Exception>> {
|
||||
let op0_value = get_op(&insn, 0, std::mem::size_of::<$bound>(), state, platform)
|
||||
.map_err(EmulationError::PlatformEmulationError)?;
|
||||
let op1_value = get_op(&insn, 1, std::mem::size_of::<$bound>(), state, platform)
|
||||
.map_err(EmulationError::PlatformEmulationError)?;
|
||||
|
||||
let cpazso = calc_rflags_cpazso(op0_value, op1_value, std::mem::size_of::<$bound>());
|
||||
|
||||
state.set_flags((state.flags() & !FLAGS_MASK) | cpazso);
|
||||
|
||||
state.set_ip(insn.ip());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! cmp_r_rm {
|
||||
($bound:ty) => {
|
||||
fn emulate(
|
||||
&self,
|
||||
insn: &Instruction,
|
||||
state: &mut T,
|
||||
platform: &mut dyn PlatformEmulator<CpuState = T>,
|
||||
) -> Result<(), EmulationError<Exception>> {
|
||||
let op0_value = get_op(&insn, 0, std::mem::size_of::<$bound>(), state, platform)
|
||||
.map_err(EmulationError::PlatformEmulationError)?;
|
||||
let op1_value = get_op(&insn, 1, std::mem::size_of::<$bound>(), state, platform)
|
||||
.map_err(EmulationError::PlatformEmulationError)?;
|
||||
|
||||
let cpazso = calc_rflags_cpazso(op0_value, op1_value, std::mem::size_of::<$bound>());
|
||||
|
||||
state.set_flags((state.flags() & !FLAGS_MASK) | cpazso);
|
||||
|
||||
state.set_ip(insn.ip());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! cmp_rm_imm {
|
||||
($imm:ty, $bound:ty) => {
|
||||
fn emulate(
|
||||
&self,
|
||||
insn: &Instruction,
|
||||
state: &mut T,
|
||||
platform: &mut dyn PlatformEmulator<CpuState = T>,
|
||||
) -> Result<(), EmulationError<Exception>> {
|
||||
let op0_value = get_op(&insn, 0, std::mem::size_of::<$bound>(), state, platform)
|
||||
.map_err(EmulationError::PlatformEmulationError)?;
|
||||
let op1_value = get_op(&insn, 1, std::mem::size_of::<$imm>(), state, platform)
|
||||
.map_err(EmulationError::PlatformEmulationError)?;
|
||||
|
||||
let cpazso = calc_rflags_cpazso(op0_value, op1_value, std::mem::size_of::<$bound>());
|
||||
|
||||
state.set_flags((state.flags() & !FLAGS_MASK) | cpazso);
|
||||
|
||||
state.set_ip(insn.ip());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
pub struct Cmp_rm64_r64;
|
||||
impl<T: CpuStateManager> InstructionHandler<T> for Cmp_rm64_r64 {
|
||||
cmp_rm_r!(u64);
|
||||
}
|
||||
|
||||
pub struct Cmp_rm32_r32;
|
||||
impl<T: CpuStateManager> InstructionHandler<T> for Cmp_rm32_r32 {
|
||||
cmp_rm_r!(u32);
|
||||
}
|
||||
|
||||
pub struct Cmp_rm16_r16;
|
||||
impl<T: CpuStateManager> InstructionHandler<T> for Cmp_rm16_r16 {
|
||||
cmp_rm_r!(u16);
|
||||
}
|
||||
|
||||
pub struct Cmp_rm8_r8;
|
||||
impl<T: CpuStateManager> InstructionHandler<T> for Cmp_rm8_r8 {
|
||||
cmp_rm_r!(u8);
|
||||
}
|
||||
|
||||
pub struct Cmp_r64_rm64;
|
||||
impl<T: CpuStateManager> InstructionHandler<T> for Cmp_r64_rm64 {
|
||||
cmp_r_rm!(u64);
|
||||
}
|
||||
|
||||
pub struct Cmp_r32_rm32;
|
||||
impl<T: CpuStateManager> InstructionHandler<T> for Cmp_r32_rm32 {
|
||||
cmp_r_rm!(u32);
|
||||
}
|
||||
|
||||
pub struct Cmp_r16_rm16;
|
||||
impl<T: CpuStateManager> InstructionHandler<T> for Cmp_r16_rm16 {
|
||||
cmp_r_rm!(u16);
|
||||
}
|
||||
|
||||
pub struct Cmp_r8_rm8;
|
||||
impl<T: CpuStateManager> InstructionHandler<T> for Cmp_r8_rm8 {
|
||||
cmp_r_rm!(u8);
|
||||
}
|
||||
|
||||
pub struct Cmp_AL_imm8;
|
||||
impl<T: CpuStateManager> InstructionHandler<T> for Cmp_AL_imm8 {
|
||||
cmp_rm_imm!(u8, u8);
|
||||
}
|
||||
|
||||
pub struct Cmp_AX_imm16;
|
||||
impl<T: CpuStateManager> InstructionHandler<T> for Cmp_AX_imm16 {
|
||||
cmp_rm_imm!(u16, u16);
|
||||
}
|
||||
|
||||
pub struct Cmp_EAX_imm32;
|
||||
impl<T: CpuStateManager> InstructionHandler<T> for Cmp_EAX_imm32 {
|
||||
cmp_rm_imm!(u32, u32);
|
||||
}
|
||||
|
||||
pub struct Cmp_RAX_imm32;
|
||||
impl<T: CpuStateManager> InstructionHandler<T> for Cmp_RAX_imm32 {
|
||||
cmp_rm_imm!(u32, u64);
|
||||
}
|
||||
|
||||
pub struct Cmp_rm8_imm8;
|
||||
impl<T: CpuStateManager> InstructionHandler<T> for Cmp_rm8_imm8 {
|
||||
cmp_rm_imm!(u8, u8);
|
||||
}
|
||||
|
||||
pub struct Cmp_rm16_imm16;
|
||||
impl<T: CpuStateManager> InstructionHandler<T> for Cmp_rm16_imm16 {
|
||||
cmp_rm_imm!(u16, u16);
|
||||
}
|
||||
|
||||
pub struct Cmp_rm32_imm32;
|
||||
impl<T: CpuStateManager> InstructionHandler<T> for Cmp_rm32_imm32 {
|
||||
cmp_rm_imm!(u32, u32);
|
||||
}
|
||||
|
||||
pub struct Cmp_rm64_imm32;
|
||||
impl<T: CpuStateManager> InstructionHandler<T> for Cmp_rm64_imm32 {
|
||||
cmp_rm_imm!(u32, u64);
|
||||
}
|
||||
|
||||
pub struct Cmp_rm16_imm8;
|
||||
impl<T: CpuStateManager> InstructionHandler<T> for Cmp_rm16_imm8 {
|
||||
cmp_rm_imm!(u8, u16);
|
||||
}
|
||||
|
||||
pub struct Cmp_rm32_imm8;
|
||||
impl<T: CpuStateManager> InstructionHandler<T> for Cmp_rm32_imm8 {
|
||||
cmp_rm_imm!(u8, u32);
|
||||
}
|
||||
|
||||
pub struct Cmp_rm64_imm8;
|
||||
impl<T: CpuStateManager> InstructionHandler<T> for Cmp_rm64_imm8 {
|
||||
cmp_rm_imm!(u8, u64);
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
#![allow(unused_mut)]
|
||||
|
||||
use super::*;
|
||||
use crate::arch::x86::emulator::mock_vmm::*;
|
||||
|
||||
macro_rules! hashmap {
|
||||
($( $key: expr => $val: expr ),*) => {{
|
||||
let mut map = ::std::collections::HashMap::new();
|
||||
$( map.insert($key, $val); )*
|
||||
map
|
||||
}}
|
||||
}
|
||||
|
||||
#[test]
|
||||
// cmp ah,al
|
||||
fn test_cmp_rm8_r8_1() -> MockResult {
|
||||
let rax: u64 = 0x0;
|
||||
let ip: u64 = 0x1000;
|
||||
let cpu_id = 0;
|
||||
let insn = [0x38, 0xc4]; // cmp ah,al
|
||||
let mut vmm = MockVMM::new(ip, hashmap![Register::RAX => rax], None);
|
||||
assert!(vmm.emulate_first_insn(cpu_id, &insn).is_ok());
|
||||
|
||||
let rflags: u64 = vmm.cpu_state(cpu_id).unwrap().flags() & FLAGS_MASK;
|
||||
assert_eq!(0b1000100, rflags);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
// cmp eax,100
|
||||
fn test_cmp_rm32_imm8_1() -> MockResult {
|
||||
let rax: u64 = 0xabcdef;
|
||||
let ip: u64 = 0x1000;
|
||||
let cpu_id = 0;
|
||||
let insn = [0x83, 0xf8, 0x64]; // cmp eax,100
|
||||
let mut vmm = MockVMM::new(ip, hashmap![Register::RAX => rax], None);
|
||||
assert!(vmm.emulate_first_insn(cpu_id, &insn).is_ok());
|
||||
|
||||
let rflags: u64 = vmm.cpu_state(cpu_id).unwrap().flags() & FLAGS_MASK;
|
||||
assert_eq!(0b100, rflags);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
// cmp eax,-1
|
||||
fn test_cmp_rm32_imm8_2() -> MockResult {
|
||||
let rax: u64 = 0xabcdef;
|
||||
let ip: u64 = 0x1000;
|
||||
let cpu_id = 0;
|
||||
let insn = [0x83, 0xf8, 0xff]; // cmp eax,-1
|
||||
let mut vmm = MockVMM::new(ip, hashmap![Register::RAX => rax], None);
|
||||
assert!(vmm.emulate_first_insn(cpu_id, &insn).is_ok());
|
||||
|
||||
let rflags: u64 = vmm.cpu_state(cpu_id).unwrap().flags() & FLAGS_MASK;
|
||||
assert_eq!(0b101, rflags);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
// cmp rax,rbx
|
||||
fn test_cmp_rm64_r64() -> MockResult {
|
||||
let rax: u64 = 0xabcdef;
|
||||
let rbx: u64 = 0x1234;
|
||||
let ip: u64 = 0x1000;
|
||||
let cpu_id = 0;
|
||||
let insn = [0x48, 0x39, 0xd8, 0x00, 0xc3]; // cmp rax,rbx + two bytes garbage
|
||||
let mut vmm = MockVMM::new(
|
||||
ip,
|
||||
hashmap![Register::RAX => rax, Register::RBX => rbx],
|
||||
None,
|
||||
);
|
||||
assert!(vmm.emulate_first_insn(cpu_id, &insn).is_ok());
|
||||
|
||||
let rflags: u64 = vmm.cpu_state(cpu_id).unwrap().flags() & FLAGS_MASK;
|
||||
assert_eq!(0b100, rflags);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_cmp_64() -> MockResult {
|
||||
let data = [
|
||||
(0xabcdef, 0x1234, 0b100),
|
||||
(0x0, 0x101, 0b1001_0101),
|
||||
(0x0, 0x8000_0000_0000_0000, 0b1000_1000_0101),
|
||||
(0x1234abcd, 0x1234abcd, 0b100_0100),
|
||||
(0x1234abcd, 0xdeadbeef, 0b1001_0101),
|
||||
(0xffff_ffff_ffff_ffff, 0xdeadbeef, 0b1000_0000),
|
||||
(0xffff_ffff_ffff_ffff, 0x0, 0b1000_0100),
|
||||
];
|
||||
|
||||
for d in data.iter() {
|
||||
let rax = d.0;
|
||||
let rbx = d.1;
|
||||
let insn = [0x48, 0x39, 0xd8]; // cmp rax,rbx
|
||||
let mut vmm = MockVMM::new(
|
||||
0x1000,
|
||||
hashmap![Register::RAX => rax, Register::RBX => rbx],
|
||||
None,
|
||||
);
|
||||
assert!(vmm.emulate_first_insn(0, &insn).is_ok());
|
||||
|
||||
let rflags: u64 = vmm.cpu_state(0).unwrap().flags() & FLAGS_MASK;
|
||||
assert_eq!(d.2, rflags);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_cmp_32() -> MockResult {
|
||||
let data = [
|
||||
(0xabcdef, 0x1234, 0b100),
|
||||
(0x0, 0x101, 0b1001_0101),
|
||||
(0x0, 0x8000_0000_0000_0000, 0b100_0100), // Same as cmp 0,0 due to truncation
|
||||
(0x1234abcd, 0x1234abcd, 0b100_0100),
|
||||
(0x1234abcd, 0xdeadbeef, 0b1_0101),
|
||||
(0xffff_ffff_ffff_ffff, 0xdeadbeef, 0b0), // Same as cmp 0xffffffff,0xdeadbeef
|
||||
(0xffff_ffff, 0x0, 0b1000_0100),
|
||||
];
|
||||
|
||||
for d in data.iter() {
|
||||
let rax = d.0;
|
||||
let rbx = d.1;
|
||||
let insn = [0x39, 0xd8]; // cmp eax,ebx
|
||||
let mut vmm = MockVMM::new(
|
||||
0x1000,
|
||||
hashmap![Register::RAX => rax, Register::RBX => rbx],
|
||||
None,
|
||||
);
|
||||
assert!(vmm.emulate_first_insn(0, &insn).is_ok());
|
||||
|
||||
let rflags: u64 = vmm.cpu_state(0).unwrap().flags() & FLAGS_MASK;
|
||||
assert_eq!(d.2, rflags);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
@ -11,6 +11,7 @@ use crate::arch::x86::emulator::CpuStateManager;
|
||||
use crate::arch::x86::Exception;
|
||||
use iced_x86::*;
|
||||
|
||||
pub mod cmp;
|
||||
pub mod mov;
|
||||
|
||||
fn get_op<T: CpuStateManager>(
|
||||
|
@ -497,6 +497,12 @@ impl<'a, T: CpuStateManager> Emulator<'a, T> {
|
||||
fn get_handler(code: Code) -> Option<Box<dyn InstructionHandler<T>>> {
|
||||
let handler: Option<Box<dyn InstructionHandler<T>>> = gen_handler_match!(
|
||||
code,
|
||||
// CMP
|
||||
(cmp, Cmp_rm32_r32),
|
||||
(cmp, Cmp_rm8_r8),
|
||||
(cmp, Cmp_rm32_imm8),
|
||||
(cmp, Cmp_rm64_r64),
|
||||
// MOV
|
||||
(mov, Mov_r8_rm8),
|
||||
(mov, Mov_r8_imm8),
|
||||
(mov, Mov_r16_imm16),
|
||||
|
Loading…
x
Reference in New Issue
Block a user