1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
34//! Emulation fast paths for specific use cases.
56use super::instruction;
7use crate::Cpu;
8use crate::emulator::arith::ArithOp;
9use crate::emulator::arith::OrOp;
10use crate::registers::Bitness;
11use crate::registers::Segment;
12use crate::registers::bitness;
13use iced_x86::OpKind;
1415const PAGE_SIZE: u32 = 4096;
1617/// Emulate atomic single-bit writes to a page.
18///
19/// This decodes `bts` and `or` instructions (which can and probably will have
20/// lock prefixes). It assumes the caller knows which physical page is being
21/// modified.
22///
23/// This is more efficient than going through the full emulation path for cases
24/// where the guest is likely to be performing single-bit writes. This is
25/// particularly useful for emulating Hyper-V monitor pages.
26///
27/// If the fast path is possible, updates the register state, advances the
28/// instruction pointer, and returns the bit number being set within the 4K
29/// page.
30///
31/// If the fast path is impossible, returns `None`. The caller should use the
32/// full emulator.
33pub fn emulate_fast_path_set_bit<T: Cpu>(instruction_bytes: &[u8], cpu: &mut T) -> Option<u32> {
34if cpu.rflags().trap() {
35return None;
36 }
3738let bitness = bitness(cpu.cr0(), cpu.efer(), cpu.segment(Segment::CS));
39let mut decoder = iced_x86::Decoder::new(bitness.into(), instruction_bytes, 0);
40 decoder.set_ip(cpu.rip());
4142let instr = decoder.decode();
43let mut rflags = cpu.rflags();
44let (address, bit) = match instr.code() {
45// [lock] bts m, r
46 //
47 // Used by Windows and Linux kernel drivers.
48iced_x86::Code::Bts_rm64_r64 | iced_x86::Code::Bts_rm32_r32
49if instr.op0_kind() == OpKind::Memory =>
50 {
51let op_size = instr.memory_size().size() as u8 as i64;
5253// When in the register form, the offset is treated as a signed value
54let bit_offset = cpu.gp_sign_extend(instr.op1_register().into());
5556let address_size = instruction::address_size(&instr);
5758let bit_base = instruction::memory_op_offset(cpu, &instr, 0);
59let address_mask = u64::MAX >> (64 - address_size * 8);
60let address = bit_base
61 .wrapping_add_signed(op_size * bit_offset.div_euclid(op_size * 8))
62 & address_mask;
6364let bit = bit_offset.rem_euclid(op_size * 8) as u32;
6566 rflags.set_carry(false);
67 (address, bit)
68 }
69// [lock] or m, r
70 //
71 // Used by DPDK.
72iced_x86::Code::Or_rm32_r32
73 | iced_x86::Code::Or_rm64_r64
74 | iced_x86::Code::Or_rm8_r8
75 | iced_x86::Code::Or_rm16_r16
76if instr.op0_kind() == OpKind::Memory =>
77 {
78let address = instruction::memory_op_offset(cpu, &instr, 0);
79let mask = cpu.gp(instr.op1_register().into());
80if !mask.is_power_of_two() {
81tracing::debug!(mask, "fast path set bit: or without exactly one bit");
82return None;
83 }
84 OrOp::update_flags(&mut rflags, instr.memory_size().size(), mask, 0, mask);
85 (address, mask.trailing_zeros())
86 }
87 iced_x86::Code::INVALID => {
88tracing::debug!(error = ?decoder.last_error(), "fast path set bit decode failure");
89return None;
90 }
91_ => {
92tracing::debug!(bytes = ?instruction_bytes[..instr.len()], "unsupported instruction for fast path set bit");
93return None;
94 }
95 };
9697let seg = cpu.segment(instr.memory_segment().into());
98let offset = page_offset(address.wrapping_add(seg.base));
99100// Ensure the access doesn't straddle a page boundary.
101if offset > PAGE_SIZE - instr.memory_size().size() as u32 {
102return None;
103 }
104105// If there is a possibility of a segmentation violation, take the slow
106 // path.
107if matches!(bitness, Bitness::Bit32 | Bitness::Bit16)
108 && page_offset(seg.base | seg.limit.wrapping_add(1) as u64) != 0
109{
110return None;
111 }
112113let bit_in_page = offset * 8 + bit;
114tracing::trace!(bit_in_page, "fast path set bit");
115116 cpu.set_rip(instr.next_ip());
117 cpu.set_rflags(rflags);
118Some(bit_in_page)
119}
120121fn page_offset(address: u64) -> u32 {
122 address as u32 & (PAGE_SIZE - 1)
123}