From 93b7dcac120de45ef1f944da5580772275d60092 Mon Sep 17 00:00:00 2001 From: Wei Liu Date: Thu, 3 Dec 2020 12:55:10 +0000 Subject: [PATCH] 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 --- .../src/arch/x86/emulator/instructions/cmp.rs | 380 ++++++++++++++++++ .../src/arch/x86/emulator/instructions/mod.rs | 1 + hypervisor/src/arch/x86/emulator/mod.rs | 6 + 3 files changed, 387 insertions(+) create mode 100644 hypervisor/src/arch/x86/emulator/instructions/cmp.rs diff --git a/hypervisor/src/arch/x86/emulator/instructions/cmp.rs b/hypervisor/src/arch/x86/emulator/instructions/cmp.rs new file mode 100644 index 000000000..40f6eca6a --- /dev/null +++ b/hypervisor/src/arch/x86/emulator/instructions/cmp.rs @@ -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, + ) -> Result<(), EmulationError> { + 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, + ) -> Result<(), EmulationError> { + 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, + ) -> Result<(), EmulationError> { + 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 InstructionHandler for Cmp_rm64_r64 { + cmp_rm_r!(u64); +} + +pub struct Cmp_rm32_r32; +impl InstructionHandler for Cmp_rm32_r32 { + cmp_rm_r!(u32); +} + +pub struct Cmp_rm16_r16; +impl InstructionHandler for Cmp_rm16_r16 { + cmp_rm_r!(u16); +} + +pub struct Cmp_rm8_r8; +impl InstructionHandler for Cmp_rm8_r8 { + cmp_rm_r!(u8); +} + +pub struct Cmp_r64_rm64; +impl InstructionHandler for Cmp_r64_rm64 { + cmp_r_rm!(u64); +} + +pub struct Cmp_r32_rm32; +impl InstructionHandler for Cmp_r32_rm32 { + cmp_r_rm!(u32); +} + +pub struct Cmp_r16_rm16; +impl InstructionHandler for Cmp_r16_rm16 { + cmp_r_rm!(u16); +} + +pub struct Cmp_r8_rm8; +impl InstructionHandler for Cmp_r8_rm8 { + cmp_r_rm!(u8); +} + +pub struct Cmp_AL_imm8; +impl InstructionHandler for Cmp_AL_imm8 { + cmp_rm_imm!(u8, u8); +} + +pub struct Cmp_AX_imm16; +impl InstructionHandler for Cmp_AX_imm16 { + cmp_rm_imm!(u16, u16); +} + +pub struct Cmp_EAX_imm32; +impl InstructionHandler for Cmp_EAX_imm32 { + cmp_rm_imm!(u32, u32); +} + +pub struct Cmp_RAX_imm32; +impl InstructionHandler for Cmp_RAX_imm32 { + cmp_rm_imm!(u32, u64); +} + +pub struct Cmp_rm8_imm8; +impl InstructionHandler for Cmp_rm8_imm8 { + cmp_rm_imm!(u8, u8); +} + +pub struct Cmp_rm16_imm16; +impl InstructionHandler for Cmp_rm16_imm16 { + cmp_rm_imm!(u16, u16); +} + +pub struct Cmp_rm32_imm32; +impl InstructionHandler for Cmp_rm32_imm32 { + cmp_rm_imm!(u32, u32); +} + +pub struct Cmp_rm64_imm32; +impl InstructionHandler for Cmp_rm64_imm32 { + cmp_rm_imm!(u32, u64); +} + +pub struct Cmp_rm16_imm8; +impl InstructionHandler for Cmp_rm16_imm8 { + cmp_rm_imm!(u8, u16); +} + +pub struct Cmp_rm32_imm8; +impl InstructionHandler for Cmp_rm32_imm8 { + cmp_rm_imm!(u8, u32); +} + +pub struct Cmp_rm64_imm8; +impl InstructionHandler 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(()) + } +} diff --git a/hypervisor/src/arch/x86/emulator/instructions/mod.rs b/hypervisor/src/arch/x86/emulator/instructions/mod.rs index 8640a4d77..efb8bed10 100644 --- a/hypervisor/src/arch/x86/emulator/instructions/mod.rs +++ b/hypervisor/src/arch/x86/emulator/instructions/mod.rs @@ -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( diff --git a/hypervisor/src/arch/x86/emulator/mod.rs b/hypervisor/src/arch/x86/emulator/mod.rs index 871554095..e5eda61df 100644 --- a/hypervisor/src/arch/x86/emulator/mod.rs +++ b/hypervisor/src/arch/x86/emulator/mod.rs @@ -497,6 +497,12 @@ impl<'a, T: CpuStateManager> Emulator<'a, T> { fn get_handler(code: Code) -> Option>> { let handler: Option>> = 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),