x86emu/emulator/
fast_path.rs

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4//! Emulation fast paths for specific use cases.
5
6use 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;
14
15const PAGE_SIZE: u32 = 4096;
16
17/// 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> {
34    if cpu.rflags().trap() {
35        return None;
36    }
37
38    let bitness = bitness(cpu.cr0(), cpu.efer(), cpu.segment(Segment::CS));
39    let mut decoder = iced_x86::Decoder::new(bitness.into(), instruction_bytes, 0);
40    decoder.set_ip(cpu.rip());
41
42    let instr = decoder.decode();
43    let mut rflags = cpu.rflags();
44    let (address, bit) = match instr.code() {
45        // [lock] bts m, r
46        //
47        // Used by Windows and Linux kernel drivers.
48        iced_x86::Code::Bts_rm64_r64 | iced_x86::Code::Bts_rm32_r32
49            if instr.op0_kind() == OpKind::Memory =>
50        {
51            let op_size = instr.memory_size().size() as u8 as i64;
52
53            // When in the register form, the offset is treated as a signed value
54            let bit_offset = cpu.gp_sign_extend(instr.op1_register().into());
55
56            let address_size = instruction::address_size(&instr);
57
58            let bit_base = instruction::memory_op_offset(cpu, &instr, 0);
59            let address_mask = u64::MAX >> (64 - address_size * 8);
60            let address = bit_base
61                .wrapping_add_signed(op_size * bit_offset.div_euclid(op_size * 8))
62                & address_mask;
63
64            let bit = bit_offset.rem_euclid(op_size * 8) as u32;
65
66            rflags.set_carry(false);
67            (address, bit)
68        }
69        // [lock] or m, r
70        //
71        // Used by DPDK.
72        iced_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
76            if instr.op0_kind() == OpKind::Memory =>
77        {
78            let address = instruction::memory_op_offset(cpu, &instr, 0);
79            let mask = cpu.gp(instr.op1_register().into());
80            if !mask.is_power_of_two() {
81                tracing::debug!(mask, "fast path set bit: or without exactly one bit");
82                return 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 => {
88            tracing::debug!(error = ?decoder.last_error(), "fast path set bit decode failure");
89            return None;
90        }
91        _ => {
92            tracing::debug!(bytes = ?instruction_bytes[..instr.len()], "unsupported instruction for fast path set bit");
93            return None;
94        }
95    };
96
97    let seg = cpu.segment(instr.memory_segment().into());
98    let offset = page_offset(address.wrapping_add(seg.base));
99
100    // Ensure the access doesn't straddle a page boundary.
101    if offset > PAGE_SIZE - instr.memory_size().size() as u32 {
102        return None;
103    }
104
105    // If there is a possibility of a segmentation violation, take the slow
106    // path.
107    if matches!(bitness, Bitness::Bit32 | Bitness::Bit16)
108        && page_offset(seg.base | seg.limit.wrapping_add(1) as u64) != 0
109    {
110        return None;
111    }
112
113    let bit_in_page = offset * 8 + bit;
114    tracing::trace!(bit_in_page, "fast path set bit");
115
116    cpu.set_rip(instr.next_ip());
117    cpu.set_rflags(rflags);
118    Some(bit_in_page)
119}
120
121fn page_offset(address: u64) -> u32 {
122    address as u32 & (PAGE_SIZE - 1)
123}