xref: /cloud-hypervisor/hypervisor/src/arch/x86/emulator/instructions/cmp.rs (revision 9af2968a7dc47b89bf07ea9dc5e735084efcfa3a)
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 arthimetic 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 = if result & (!0u64 >> (63 - msb_shift)) == 0 {
42         1
43     } else {
44         0
45     } << ZF_SHIFT;
46 
47     let sf = ((result >> msb_shift) & 0x1) << SF_SHIFT;
48 
49     // Overflow happens when two operands have the same sign but the result has a different sign.
50     let of = ((((op0 ^ op1) & (op0 ^ result)) >> msb_shift) & 0x1) << OF_SHIFT;
51 
52     cf | pf | af | zf | sf | of
53 }
54 
55 macro_rules! cmp_rm_r {
56     ($bound:ty) => {
57         fn emulate(
58             &self,
59             insn: &Instruction,
60             state: &mut T,
61             platform: &mut dyn PlatformEmulator<CpuState = T>,
62         ) -> Result<(), EmulationError<Exception>> {
63             let op0_value = get_op(&insn, 0, std::mem::size_of::<$bound>(), state, platform)
64                 .map_err(EmulationError::PlatformEmulationError)?;
65             let op1_value = get_op(&insn, 1, std::mem::size_of::<$bound>(), state, platform)
66                 .map_err(EmulationError::PlatformEmulationError)?;
67 
68             let cpazso = calc_rflags_cpazso(op0_value, op1_value, std::mem::size_of::<$bound>());
69 
70             state.set_flags((state.flags() & !FLAGS_MASK) | cpazso);
71 
72             Ok(())
73         }
74     };
75 }
76 
77 macro_rules! cmp_r_rm {
78     ($bound:ty) => {
79         fn emulate(
80             &self,
81             insn: &Instruction,
82             state: &mut T,
83             platform: &mut dyn PlatformEmulator<CpuState = T>,
84         ) -> Result<(), EmulationError<Exception>> {
85             let op0_value = get_op(&insn, 0, std::mem::size_of::<$bound>(), state, platform)
86                 .map_err(EmulationError::PlatformEmulationError)?;
87             let op1_value = get_op(&insn, 1, std::mem::size_of::<$bound>(), state, platform)
88                 .map_err(EmulationError::PlatformEmulationError)?;
89 
90             let cpazso = calc_rflags_cpazso(op0_value, op1_value, std::mem::size_of::<$bound>());
91 
92             state.set_flags((state.flags() & !FLAGS_MASK) | cpazso);
93 
94             Ok(())
95         }
96     };
97 }
98 
99 macro_rules! cmp_rm_imm {
100     ($imm:ty, $bound:ty) => {
101         fn emulate(
102             &self,
103             insn: &Instruction,
104             state: &mut T,
105             platform: &mut dyn PlatformEmulator<CpuState = T>,
106         ) -> Result<(), EmulationError<Exception>> {
107             let op0_value = get_op(&insn, 0, std::mem::size_of::<$bound>(), state, platform)
108                 .map_err(EmulationError::PlatformEmulationError)?;
109             let op1_value = get_op(&insn, 1, std::mem::size_of::<$imm>(), state, platform)
110                 .map_err(EmulationError::PlatformEmulationError)?;
111 
112             let cpazso = calc_rflags_cpazso(op0_value, op1_value, std::mem::size_of::<$bound>());
113 
114             state.set_flags((state.flags() & !FLAGS_MASK) | cpazso);
115 
116             Ok(())
117         }
118     };
119 }
120 
121 pub struct Cmp_rm64_r64;
122 impl<T: CpuStateManager> InstructionHandler<T> for Cmp_rm64_r64 {
123     cmp_rm_r!(u64);
124 }
125 
126 pub struct Cmp_rm32_r32;
127 impl<T: CpuStateManager> InstructionHandler<T> for Cmp_rm32_r32 {
128     cmp_rm_r!(u32);
129 }
130 
131 pub struct Cmp_rm16_r16;
132 impl<T: CpuStateManager> InstructionHandler<T> for Cmp_rm16_r16 {
133     cmp_rm_r!(u16);
134 }
135 
136 pub struct Cmp_rm8_r8;
137 impl<T: CpuStateManager> InstructionHandler<T> for Cmp_rm8_r8 {
138     cmp_rm_r!(u8);
139 }
140 
141 pub struct Cmp_r64_rm64;
142 impl<T: CpuStateManager> InstructionHandler<T> for Cmp_r64_rm64 {
143     cmp_r_rm!(u64);
144 }
145 
146 pub struct Cmp_r32_rm32;
147 impl<T: CpuStateManager> InstructionHandler<T> for Cmp_r32_rm32 {
148     cmp_r_rm!(u32);
149 }
150 
151 pub struct Cmp_r16_rm16;
152 impl<T: CpuStateManager> InstructionHandler<T> for Cmp_r16_rm16 {
153     cmp_r_rm!(u16);
154 }
155 
156 pub struct Cmp_r8_rm8;
157 impl<T: CpuStateManager> InstructionHandler<T> for Cmp_r8_rm8 {
158     cmp_r_rm!(u8);
159 }
160 
161 pub struct Cmp_AL_imm8;
162 impl<T: CpuStateManager> InstructionHandler<T> for Cmp_AL_imm8 {
163     cmp_rm_imm!(u8, u8);
164 }
165 
166 pub struct Cmp_AX_imm16;
167 impl<T: CpuStateManager> InstructionHandler<T> for Cmp_AX_imm16 {
168     cmp_rm_imm!(u16, u16);
169 }
170 
171 pub struct Cmp_EAX_imm32;
172 impl<T: CpuStateManager> InstructionHandler<T> for Cmp_EAX_imm32 {
173     cmp_rm_imm!(u32, u32);
174 }
175 
176 pub struct Cmp_RAX_imm32;
177 impl<T: CpuStateManager> InstructionHandler<T> for Cmp_RAX_imm32 {
178     cmp_rm_imm!(u32, u64);
179 }
180 
181 pub struct Cmp_rm8_imm8;
182 impl<T: CpuStateManager> InstructionHandler<T> for Cmp_rm8_imm8 {
183     cmp_rm_imm!(u8, u8);
184 }
185 
186 pub struct Cmp_rm16_imm16;
187 impl<T: CpuStateManager> InstructionHandler<T> for Cmp_rm16_imm16 {
188     cmp_rm_imm!(u16, u16);
189 }
190 
191 pub struct Cmp_rm32_imm32;
192 impl<T: CpuStateManager> InstructionHandler<T> for Cmp_rm32_imm32 {
193     cmp_rm_imm!(u32, u32);
194 }
195 
196 pub struct Cmp_rm64_imm32;
197 impl<T: CpuStateManager> InstructionHandler<T> for Cmp_rm64_imm32 {
198     cmp_rm_imm!(u32, u64);
199 }
200 
201 pub struct Cmp_rm16_imm8;
202 impl<T: CpuStateManager> InstructionHandler<T> for Cmp_rm16_imm8 {
203     cmp_rm_imm!(u8, u16);
204 }
205 
206 pub struct Cmp_rm32_imm8;
207 impl<T: CpuStateManager> InstructionHandler<T> for Cmp_rm32_imm8 {
208     cmp_rm_imm!(u8, u32);
209 }
210 
211 pub struct Cmp_rm64_imm8;
212 impl<T: CpuStateManager> InstructionHandler<T> for Cmp_rm64_imm8 {
213     cmp_rm_imm!(u8, u64);
214 }
215 
216 #[cfg(test)]
217 mod tests {
218     #![allow(unused_mut)]
219 
220     use super::*;
221     use crate::arch::x86::emulator::mock_vmm::*;
222 
223     #[test]
224     // cmp ah,al
225     fn test_cmp_rm8_r8_1() {
226         let rax: u64 = 0x0;
227         let ip: u64 = 0x1000;
228         let cpu_id = 0;
229         let insn = [0x38, 0xc4]; // cmp ah,al
230         let mut vmm = MockVmm::new(ip, vec![(Register::RAX, rax)], None);
231         assert!(vmm.emulate_first_insn(cpu_id, &insn).is_ok());
232 
233         let rflags: u64 = vmm.cpu_state(cpu_id).unwrap().flags() & FLAGS_MASK;
234         assert_eq!(0b1000100, rflags);
235     }
236 
237     #[test]
238     // cmp eax,100
239     fn test_cmp_rm32_imm8_1() {
240         let rax: u64 = 0xabcdef;
241         let ip: u64 = 0x1000;
242         let cpu_id = 0;
243         let insn = [0x83, 0xf8, 0x64]; // cmp eax,100
244         let mut vmm = MockVmm::new(ip, vec![(Register::RAX, rax)], None);
245         assert!(vmm.emulate_first_insn(cpu_id, &insn).is_ok());
246 
247         let rflags: u64 = vmm.cpu_state(cpu_id).unwrap().flags() & FLAGS_MASK;
248         assert_eq!(0b100, rflags);
249     }
250 
251     #[test]
252     // cmp eax,-1
253     fn test_cmp_rm32_imm8_2() {
254         let rax: u64 = 0xabcdef;
255         let ip: u64 = 0x1000;
256         let cpu_id = 0;
257         let insn = [0x83, 0xf8, 0xff]; // cmp eax,-1
258         let mut vmm = MockVmm::new(ip, vec![(Register::RAX, rax)], None);
259         assert!(vmm.emulate_first_insn(cpu_id, &insn).is_ok());
260 
261         let rflags: u64 = vmm.cpu_state(cpu_id).unwrap().flags() & FLAGS_MASK;
262         assert_eq!(0b101, rflags);
263     }
264 
265     #[test]
266     // cmp rax,rbx
267     fn test_cmp_rm64_r64() {
268         let rax: u64 = 0xabcdef;
269         let rbx: u64 = 0x1234;
270         let ip: u64 = 0x1000;
271         let cpu_id = 0;
272         let insn = [0x48, 0x39, 0xd8, 0x00, 0xc3]; // cmp rax,rbx + two bytes garbage
273         let mut vmm = MockVmm::new(ip, vec![(Register::RAX, rax), (Register::RBX, rbx)], None);
274         assert!(vmm.emulate_first_insn(cpu_id, &insn).is_ok());
275 
276         let rflags: u64 = vmm.cpu_state(cpu_id).unwrap().flags() & FLAGS_MASK;
277         assert_eq!(0b100, rflags);
278     }
279 
280     #[test]
281     fn test_cmp_64() {
282         let data = [
283             (0xabcdef, 0x1234, 0b100),
284             (0x0, 0x101, 0b1001_0101),
285             (0x0, 0x8000_0000_0000_0000, 0b1000_1000_0101),
286             (0x1234abcd, 0x1234abcd, 0b100_0100),
287             (0x1234abcd, 0xdeadbeef, 0b1001_0101),
288             (0xffff_ffff_ffff_ffff, 0xdeadbeef, 0b1000_0000),
289             (0xffff_ffff_ffff_ffff, 0x0, 0b1000_0100),
290         ];
291 
292         for d in data.iter() {
293             let rax = d.0;
294             let rbx = d.1;
295             let insn = [0x48, 0x39, 0xd8]; // cmp rax,rbx
296             let mut vmm = MockVmm::new(
297                 0x1000,
298                 vec![(Register::RAX, rax), (Register::RBX, rbx)],
299                 None,
300             );
301             assert!(vmm.emulate_first_insn(0, &insn).is_ok());
302 
303             let rflags: u64 = vmm.cpu_state(0).unwrap().flags() & FLAGS_MASK;
304             assert_eq!(d.2, rflags);
305         }
306     }
307 
308     #[test]
309     fn test_cmp_32() {
310         let data = [
311             (0xabcdef, 0x1234, 0b100),
312             (0x0, 0x101, 0b1001_0101),
313             (0x0, 0x8000_0000_0000_0000, 0b100_0100), // Same as cmp 0,0 due to truncation
314             (0x1234abcd, 0x1234abcd, 0b100_0100),
315             (0x1234abcd, 0xdeadbeef, 0b1_0101),
316             (0xffff_ffff_ffff_ffff, 0xdeadbeef, 0b0), // Same as cmp 0xffffffff,0xdeadbeef
317             (0xffff_ffff, 0x0, 0b1000_0100),
318         ];
319 
320         for d in data.iter() {
321             let rax = d.0;
322             let rbx = d.1;
323             let insn = [0x39, 0xd8]; // cmp eax,ebx
324             let mut vmm = MockVmm::new(
325                 0x1000,
326                 vec![(Register::RAX, rax), (Register::RBX, rbx)],
327                 None,
328             );
329             assert!(vmm.emulate_first_insn(0, &insn).is_ok());
330 
331             let rflags: u64 = vmm.cpu_state(0).unwrap().flags() & FLAGS_MASK;
332             assert_eq!(d.2, rflags);
333         }
334     }
335 }
336