xref: /cloud-hypervisor/hypervisor/src/arch/x86/emulator/instructions/cmp.rs (revision 7bf0cc1ed518c9d854caeec24f30715c1414fc56)
1 //
2 // Copyright © 2020 Microsoft
3 //
4 // SPDX-License-Identifier: Apache-2.0
5 //
6 
7 #![allow(non_camel_case_types, clippy::upper_case_acronyms)]
8 
9 //
10 // CMP-Compare Two Operands
11 //
12 
13 use crate::arch::emulator::{EmulationError, PlatformEmulator};
14 use crate::arch::x86::emulator::instructions::*;
15 use crate::arch::x86::regs::*;
16 use crate::arch::x86::Exception;
17 
18 // CMP affects OF, SF, ZF, AF, PF and CF
19 const FLAGS_MASK: u64 = CF | PF | AF | ZF | SF | OF;
20 
21 // TODO: Switch to inline asm when that's stable. Executing CMP (or any arithmetic instructions)
22 // natively and extracting RFLAGS will be much faster and make the code simpler.
23 fn calc_rflags_cpazso(op0: u64, op1: u64, op_size: usize) -> u64 {
24     let op_bits = op_size * 8;
25     let msb_shift = op_bits - 1;
26     // CMP is the same as SUB.
27     let result = op0.wrapping_sub(op1);
28 
29     // Carry-out vector for SUB.
30     let cout = (!op0 & op1) | ((!op0 ^ op1) & result);
31 
32     let cf = ((cout >> msb_shift) & 0x1) << CF_SHIFT;
33 
34     // PF only needs the least significant byte. XOR its higher 4 bits with its lower 4 bits then
35     // use the value directly.
36     let pf = ((0x9669 >> ((result ^ (result >> 4)) & 0xf)) & 0x1) << PF_SHIFT;
37 
38     // AF cares about the lowest 4 bits (nibble). msb_shift is 3 in this case.
39     let af = ((cout >> 3) & 0x1) << AF_SHIFT;
40 
41     let zf = u64::from(result & (!0u64 >> (63 - msb_shift)) == 0) << ZF_SHIFT;
42 
43     let sf = ((result >> msb_shift) & 0x1) << SF_SHIFT;
44 
45     // Overflow happens when two operands have the same sign but the result has a different sign.
46     let of = ((((op0 ^ op1) & (op0 ^ result)) >> msb_shift) & 0x1) << OF_SHIFT;
47 
48     cf | pf | af | zf | sf | of
49 }
50 
51 macro_rules! cmp_rm_r {
52     ($bound:ty) => {
53         fn emulate(
54             &self,
55             insn: &Instruction,
56             state: &mut T,
57             platform: &mut dyn PlatformEmulator<CpuState = T>,
58         ) -> Result<(), EmulationError<Exception>> {
59             let op0_value = get_op(&insn, 0, std::mem::size_of::<$bound>(), state, platform)
60                 .map_err(EmulationError::PlatformEmulationError)?;
61             let op1_value = get_op(&insn, 1, std::mem::size_of::<$bound>(), state, platform)
62                 .map_err(EmulationError::PlatformEmulationError)?;
63 
64             let cpazso = calc_rflags_cpazso(op0_value, op1_value, std::mem::size_of::<$bound>());
65 
66             state.set_flags((state.flags() & !FLAGS_MASK) | cpazso);
67 
68             Ok(())
69         }
70     };
71 }
72 
73 macro_rules! cmp_r_rm {
74     ($bound:ty) => {
75         fn emulate(
76             &self,
77             insn: &Instruction,
78             state: &mut T,
79             platform: &mut dyn PlatformEmulator<CpuState = T>,
80         ) -> Result<(), EmulationError<Exception>> {
81             let op0_value = get_op(&insn, 0, std::mem::size_of::<$bound>(), state, platform)
82                 .map_err(EmulationError::PlatformEmulationError)?;
83             let op1_value = get_op(&insn, 1, std::mem::size_of::<$bound>(), state, platform)
84                 .map_err(EmulationError::PlatformEmulationError)?;
85 
86             let cpazso = calc_rflags_cpazso(op0_value, op1_value, std::mem::size_of::<$bound>());
87 
88             state.set_flags((state.flags() & !FLAGS_MASK) | cpazso);
89 
90             Ok(())
91         }
92     };
93 }
94 
95 macro_rules! cmp_rm_imm {
96     ($imm:ty, $bound:ty) => {
97         fn emulate(
98             &self,
99             insn: &Instruction,
100             state: &mut T,
101             platform: &mut dyn PlatformEmulator<CpuState = T>,
102         ) -> Result<(), EmulationError<Exception>> {
103             let op0_value = get_op(&insn, 0, std::mem::size_of::<$bound>(), state, platform)
104                 .map_err(EmulationError::PlatformEmulationError)?;
105             let op1_value = get_op(&insn, 1, std::mem::size_of::<$imm>(), state, platform)
106                 .map_err(EmulationError::PlatformEmulationError)?;
107 
108             let cpazso = calc_rflags_cpazso(op0_value, op1_value, std::mem::size_of::<$bound>());
109 
110             state.set_flags((state.flags() & !FLAGS_MASK) | cpazso);
111 
112             Ok(())
113         }
114     };
115 }
116 
117 pub struct Cmp_rm64_r64;
118 impl<T: CpuStateManager> InstructionHandler<T> for Cmp_rm64_r64 {
119     cmp_rm_r!(u64);
120 }
121 
122 pub struct Cmp_rm32_r32;
123 impl<T: CpuStateManager> InstructionHandler<T> for Cmp_rm32_r32 {
124     cmp_rm_r!(u32);
125 }
126 
127 pub struct Cmp_rm16_r16;
128 impl<T: CpuStateManager> InstructionHandler<T> for Cmp_rm16_r16 {
129     cmp_rm_r!(u16);
130 }
131 
132 pub struct Cmp_rm8_r8;
133 impl<T: CpuStateManager> InstructionHandler<T> for Cmp_rm8_r8 {
134     cmp_rm_r!(u8);
135 }
136 
137 pub struct Cmp_r64_rm64;
138 impl<T: CpuStateManager> InstructionHandler<T> for Cmp_r64_rm64 {
139     cmp_r_rm!(u64);
140 }
141 
142 pub struct Cmp_r32_rm32;
143 impl<T: CpuStateManager> InstructionHandler<T> for Cmp_r32_rm32 {
144     cmp_r_rm!(u32);
145 }
146 
147 pub struct Cmp_r16_rm16;
148 impl<T: CpuStateManager> InstructionHandler<T> for Cmp_r16_rm16 {
149     cmp_r_rm!(u16);
150 }
151 
152 pub struct Cmp_r8_rm8;
153 impl<T: CpuStateManager> InstructionHandler<T> for Cmp_r8_rm8 {
154     cmp_r_rm!(u8);
155 }
156 
157 pub struct Cmp_AL_imm8;
158 impl<T: CpuStateManager> InstructionHandler<T> for Cmp_AL_imm8 {
159     cmp_rm_imm!(u8, u8);
160 }
161 
162 pub struct Cmp_AX_imm16;
163 impl<T: CpuStateManager> InstructionHandler<T> for Cmp_AX_imm16 {
164     cmp_rm_imm!(u16, u16);
165 }
166 
167 pub struct Cmp_EAX_imm32;
168 impl<T: CpuStateManager> InstructionHandler<T> for Cmp_EAX_imm32 {
169     cmp_rm_imm!(u32, u32);
170 }
171 
172 pub struct Cmp_RAX_imm32;
173 impl<T: CpuStateManager> InstructionHandler<T> for Cmp_RAX_imm32 {
174     cmp_rm_imm!(u32, u64);
175 }
176 
177 pub struct Cmp_rm8_imm8;
178 impl<T: CpuStateManager> InstructionHandler<T> for Cmp_rm8_imm8 {
179     cmp_rm_imm!(u8, u8);
180 }
181 
182 pub struct Cmp_rm16_imm16;
183 impl<T: CpuStateManager> InstructionHandler<T> for Cmp_rm16_imm16 {
184     cmp_rm_imm!(u16, u16);
185 }
186 
187 pub struct Cmp_rm32_imm32;
188 impl<T: CpuStateManager> InstructionHandler<T> for Cmp_rm32_imm32 {
189     cmp_rm_imm!(u32, u32);
190 }
191 
192 pub struct Cmp_rm64_imm32;
193 impl<T: CpuStateManager> InstructionHandler<T> for Cmp_rm64_imm32 {
194     cmp_rm_imm!(u32, u64);
195 }
196 
197 pub struct Cmp_rm16_imm8;
198 impl<T: CpuStateManager> InstructionHandler<T> for Cmp_rm16_imm8 {
199     cmp_rm_imm!(u8, u16);
200 }
201 
202 pub struct Cmp_rm32_imm8;
203 impl<T: CpuStateManager> InstructionHandler<T> for Cmp_rm32_imm8 {
204     cmp_rm_imm!(u8, u32);
205 }
206 
207 pub struct Cmp_rm64_imm8;
208 impl<T: CpuStateManager> InstructionHandler<T> for Cmp_rm64_imm8 {
209     cmp_rm_imm!(u8, u64);
210 }
211 
212 #[cfg(test)]
213 mod tests {
214     #![allow(unused_mut)]
215 
216     use super::*;
217     use crate::arch::x86::emulator::mock_vmm::*;
218 
219     #[test]
220     // cmp ah,al
221     fn test_cmp_rm8_r8_1() {
222         let rax: u64 = 0x0;
223         let ip: u64 = 0x1000;
224         let cpu_id = 0;
225         let insn = [0x38, 0xc4]; // cmp ah,al
226         let mut vmm = MockVmm::new(ip, vec![(Register::RAX, rax)], None);
227         assert!(vmm.emulate_first_insn(cpu_id, &insn).is_ok());
228 
229         let rflags: u64 = vmm.cpu_state(cpu_id).unwrap().flags() & FLAGS_MASK;
230         assert_eq!(0b1000100, rflags);
231     }
232 
233     #[test]
234     // cmp eax,100
235     fn test_cmp_rm32_imm8_1() {
236         let rax: u64 = 0xabcdef;
237         let ip: u64 = 0x1000;
238         let cpu_id = 0;
239         let insn = [0x83, 0xf8, 0x64]; // cmp eax,100
240         let mut vmm = MockVmm::new(ip, vec![(Register::RAX, rax)], None);
241         assert!(vmm.emulate_first_insn(cpu_id, &insn).is_ok());
242 
243         let rflags: u64 = vmm.cpu_state(cpu_id).unwrap().flags() & FLAGS_MASK;
244         assert_eq!(0b100, rflags);
245     }
246 
247     #[test]
248     // cmp eax,-1
249     fn test_cmp_rm32_imm8_2() {
250         let rax: u64 = 0xabcdef;
251         let ip: u64 = 0x1000;
252         let cpu_id = 0;
253         let insn = [0x83, 0xf8, 0xff]; // cmp eax,-1
254         let mut vmm = MockVmm::new(ip, vec![(Register::RAX, rax)], None);
255         assert!(vmm.emulate_first_insn(cpu_id, &insn).is_ok());
256 
257         let rflags: u64 = vmm.cpu_state(cpu_id).unwrap().flags() & FLAGS_MASK;
258         assert_eq!(0b101, rflags);
259     }
260 
261     #[test]
262     // cmp rax,rbx
263     fn test_cmp_rm64_r64() {
264         let rax: u64 = 0xabcdef;
265         let rbx: u64 = 0x1234;
266         let ip: u64 = 0x1000;
267         let cpu_id = 0;
268         let insn = [0x48, 0x39, 0xd8, 0x00, 0xc3]; // cmp rax,rbx + two bytes garbage
269         let mut vmm = MockVmm::new(ip, vec![(Register::RAX, rax), (Register::RBX, rbx)], None);
270         assert!(vmm.emulate_first_insn(cpu_id, &insn).is_ok());
271 
272         let rflags: u64 = vmm.cpu_state(cpu_id).unwrap().flags() & FLAGS_MASK;
273         assert_eq!(0b100, rflags);
274     }
275 
276     #[test]
277     fn test_cmp_64() {
278         let data = [
279             (0xabcdef, 0x1234, 0b100),
280             (0x0, 0x101, 0b1001_0101),
281             (0x0, 0x8000_0000_0000_0000, 0b1000_1000_0101),
282             (0x1234abcd, 0x1234abcd, 0b100_0100),
283             (0x1234abcd, 0xdeadbeef, 0b1001_0101),
284             (0xffff_ffff_ffff_ffff, 0xdeadbeef, 0b1000_0000),
285             (0xffff_ffff_ffff_ffff, 0x0, 0b1000_0100),
286         ];
287 
288         for d in data.iter() {
289             let rax = d.0;
290             let rbx = d.1;
291             let insn = [0x48, 0x39, 0xd8]; // cmp rax,rbx
292             let mut vmm = MockVmm::new(
293                 0x1000,
294                 vec![(Register::RAX, rax), (Register::RBX, rbx)],
295                 None,
296             );
297             assert!(vmm.emulate_first_insn(0, &insn).is_ok());
298 
299             let rflags: u64 = vmm.cpu_state(0).unwrap().flags() & FLAGS_MASK;
300             assert_eq!(d.2, rflags);
301         }
302     }
303 
304     #[test]
305     fn test_cmp_32() {
306         let data = [
307             (0xabcdef, 0x1234, 0b100),
308             (0x0, 0x101, 0b1001_0101),
309             (0x0, 0x8000_0000_0000_0000, 0b100_0100), // Same as cmp 0,0 due to truncation
310             (0x1234abcd, 0x1234abcd, 0b100_0100),
311             (0x1234abcd, 0xdeadbeef, 0b1_0101),
312             (0xffff_ffff_ffff_ffff, 0xdeadbeef, 0b0), // Same as cmp 0xffffffff,0xdeadbeef
313             (0xffff_ffff, 0x0, 0b1000_0100),
314         ];
315 
316         for d in data.iter() {
317             let rax = d.0;
318             let rbx = d.1;
319             let insn = [0x39, 0xd8]; // cmp eax,ebx
320             let mut vmm = MockVmm::new(
321                 0x1000,
322                 vec![(Register::RAX, rax), (Register::RBX, rbx)],
323                 None,
324             );
325             assert!(vmm.emulate_first_insn(0, &insn).is_ok());
326 
327             let rflags: u64 = vmm.cpu_state(0).unwrap().flags() & FLAGS_MASK;
328             assert_eq!(d.2, rflags);
329         }
330     }
331 }
332