1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278
//! Port-mapped IO vmexit controller.
//!
//! Port-mapped I/O is a method of communication between external devices and the CPU,
//! where each device is assigned a unique port address that is used as an operand in the in/out instruction family.
//! This allows the CPU to directly read or write data to the devices.
//! There are different variations of the in/out instructions, depending on the type and size of the operands used.
//!
//! In virtual machine management, the VMM can choose to allow or trap port-mapped I/O instructions executed by the guest.
//! However, in this section, all I/O instructions are trapped to the host, giving the VMM control over their behavior
//! to hand-on the concept of the instruction emulation.
//!
//! Instruction emulation contains two steps; 1) decode the "semantic" of the instruction; 2) emulate the "semantic" of the instruction.
//! Through the implementation, you could follow the way that traditional virtualization emulates the instruction.
//!
//! ## Tasks
//! In this part, You need to emulate the 18 in/out instructions.
//! The given template conducts the step 2, emulation.
//!
//! Decode the instruction and initialize the [`IoInstruction`] instance with the corresponding opcode and registers.
//! The controller will then use this information to forward the request to the appropriate handler.
//! When handling Outsw_DX_m8, Outsw_DX_m16 and Outsd_DX_m32, it is necessary to copy the memory contents by translating guest virtual address to host virtual address.
//! You can translate the guest address to the host address by [`Probe`].
use alloc::{
boxed::Box,
collections::btree_map::{BTreeMap, Entry},
format,
};
use iced_x86::{Code, Instruction};
use kev::{
vcpu::{GenericVCpuState, Rflags, VmexitResult},
vm::Gva,
vmcs::{BasicExitReason, ExitReason, Field},
Probe, VmError,
};
/// Trait that represent handlers for port-mapped devices.
pub trait PioHandler
where
Self: Send + Sync,
{
/// handle I/O instructions on the device indicated by the port with the operands included in direction.
fn handle(
&self,
port: u16,
direction: Direction,
p: &dyn Probe,
generic_vcpu_state: &mut GenericVCpuState,
) -> Result<VmexitResult, VmError>;
}
/// Pio vmexit controller.
pub struct Controller {
pios: BTreeMap<u16, Box<dyn PioHandler>>,
}
impl Controller {
/// Create a new pio controller.
pub fn new() -> Self {
Self {
pios: BTreeMap::new(),
}
}
/// Insert pio handler to the index.
///
/// Return false if pio handler for index is exists.
/// Otherwise, return true.
pub fn register(&mut self, port: u16, pio: impl PioHandler + 'static) -> bool {
match self.pios.entry(port) {
Entry::Occupied(_) => false,
Entry::Vacant(v) => {
v.insert(Box::new(pio));
true
}
}
}
}
impl kev::vmexits::VmexitController for Controller {
fn handle<P: kev::Probe>(
&mut self,
reason: ExitReason,
p: &mut P,
generic_vcpu_state: &mut GenericVCpuState,
) -> Result<VmexitResult, kev::VmError> {
match reason.get_basic_reason() {
BasicExitReason::IoInstruction => {
let insn = generic_vcpu_state.vmcs.get_instruction(p)?;
self.handle_ioinsn(insn, p, generic_vcpu_state)
.and_then(|s| generic_vcpu_state.vmcs.forward_rip().map(|_| s))
}
_ => Err(kev::VmError::HandleVmexitFailed(reason)),
}
}
}
#[derive(Debug)]
/// Possible prefix of io instruction.
pub enum Prefix {
/// Has none of prefix.
None,
/// Has repe or rep prefix.
Rep,
/// Has repne.
Repne,
}
#[derive(Debug)]
/// Direction and the Value of the instruction
pub enum Direction {
/// Input byte from I/O port into AL.
InbAl,
/// Input word from I/O port into AX.
InwAx,
/// Input double word from I/O port into EAX.
IndEax,
/// Input byte from I/O port into memory.
Inbm(Gva),
/// Input word from I/O port into memory.
Inwm(Gva),
/// Input double word from I/O port into memory.
Indm(Gva),
/// Output a byte (1 byte)
Outb(u8),
/// Output a word (2 bytes)
Outw(u16),
/// Output a double word (4 bytes)
Outd(u32),
}
#[derive(Debug)]
/// The decoded information of io instruction.
pub struct IoInstruction {
/// Port of the io iostruction
pub port: u16,
/// Direction and value of the io instruction
pub direction: Direction,
}
impl Controller {
fn handle_ioinsn_one<P: Probe>(
&self,
insn: Instruction,
p: &mut P,
generic_vcpu_state: &mut GenericVCpuState,
) -> Result<VmexitResult, VmError> {
// Hint
// - Use [`Probe::gva2hva`] to translate guest memory to host memory.
let IoInstruction { port, direction } = match insn.code() {
// -- in families.
// in al, dx
// Input byte from I/O port in DX into AL.
Code::In_AL_DX => todo!(),
// in ax, dx
// Input word from I/O port in DX into AX.
Code::In_AX_DX => todo!(),
// in eax, dx
// Input doubleword from I/O port in DX into EAX.
Code::In_EAX_DX => todo!(),
// in al, imm8
// Input byte from imm8 I/O port address into AL.
Code::In_AL_imm8 => todo!(),
// in ax, imm8
// Input word from imm8 I/O port address into AX.
Code::In_AX_imm8 => todo!(),
// in eax, imm8
// Input dword from imm8 I/O port address into EAX.
Code::In_EAX_imm8 => todo!(),
// insb
// Input byte from I/O port specified in DX into memory location specified in ES:(E)DI or RDI.*
Code::Insb_m8_DX => todo!(),
// insw
// Input word from I/O port specified in DX into memory location specified in ES:(E)DI or RDI.1
Code::Insw_m16_DX => todo!(),
// insd
// Input doubleword from I/O port specified in DX into memory location specified in ES:(E)DI or RDI.1
Code::Insd_m32_DX => todo!(),
// -- out families.
// out dx, al
// Output byte from AL into I/O port in DX.
Code::Out_DX_AL => todo!(),
// out dx, ax
// Output word from AX into I/O port in DX.
Code::Out_DX_AX => todo!(),
// out dx, eax
// Output double word from AX into I/O port in DX.
Code::Out_DX_EAX => todo!(),
// out imm8, al
// Output byte from AL into I/O port in imm8.
Code::Out_imm8_AL => todo!(),
// out imm8, ax
// Output byte from AL into I/O port in imm8.
Code::Out_imm8_AX => todo!(),
// out imm8, eax
// Output double word from EAX into I/O port in imm8.
Code::Out_imm8_EAX => todo!(),
// outsb
// Output byte from memory location specified in DS:(E)SI or RSI to I/O port specified in DX**.
Code::Outsb_DX_m8 => todo!(),
// outsw
// Output word from memory location specified in DS:(E)SI or RSI to I/O port specified in DX**.
Code::Outsw_DX_m16 => todo!(),
// outsd
// Output doubleword from memory location specified in DS:(E)SI or RSI to I/O port specified in DX**.
Code::Outsd_DX_m32 => todo!(),
_ => unreachable!(),
};
let result = if let Some(handler) = self.pios.get(&port) {
handler.handle(port, direction, p, generic_vcpu_state)
} else {
Err(VmError::ControllerError(Box::new(format!(
"Unknown io port: 0x{port:x}"
))))
};
let df = Rflags::from_bits_truncate(generic_vcpu_state.vmcs.read(Field::GuestRflags)?)
.contains(Rflags::DF);
match insn.code() {
Code::Insb_m8_DX if !df => {
generic_vcpu_state.gprs.rdi = generic_vcpu_state.gprs.rdi.overflowing_add(1).0
}
Code::Insb_m8_DX if df => {
generic_vcpu_state.gprs.rdi = generic_vcpu_state.gprs.rdi.overflowing_sub(1).0
}
Code::Insw_m16_DX if !df => {
generic_vcpu_state.gprs.rdi = generic_vcpu_state.gprs.rdi.overflowing_add(2).0
}
Code::Insw_m16_DX if df => {
generic_vcpu_state.gprs.rdi = generic_vcpu_state.gprs.rdi.overflowing_sub(2).0
}
Code::Insd_m32_DX if !df => {
generic_vcpu_state.gprs.rdi = generic_vcpu_state.gprs.rdi.overflowing_add(4).0
}
Code::Insd_m32_DX if df => {
generic_vcpu_state.gprs.rdi = generic_vcpu_state.gprs.rdi.overflowing_sub(4).0
}
Code::Outsb_DX_m8 if !df => {
generic_vcpu_state.gprs.rsi = generic_vcpu_state.gprs.rsi.overflowing_add(1).0
}
Code::Outsb_DX_m8 if df => {
generic_vcpu_state.gprs.rsi = generic_vcpu_state.gprs.rsi.overflowing_sub(1).0
}
Code::Outsw_DX_m16 if !df => {
generic_vcpu_state.gprs.rsi = generic_vcpu_state.gprs.rsi.overflowing_add(2).0
}
Code::Outsw_DX_m16 if df => {
generic_vcpu_state.gprs.rsi = generic_vcpu_state.gprs.rsi.overflowing_sub(2).0
}
Code::Outsd_DX_m32 if !df => {
generic_vcpu_state.gprs.rsi = generic_vcpu_state.gprs.rsi.overflowing_add(4).0
}
Code::Outsd_DX_m32 if df => {
generic_vcpu_state.gprs.rsi = generic_vcpu_state.gprs.rsi.overflowing_sub(4).0
}
_ => (),
}
result
}
fn handle_ioinsn<P: Probe>(
&self,
insn: Instruction,
p: &mut P,
generic_vcpu_state: &mut GenericVCpuState,
) -> Result<VmexitResult, VmError> {
if insn.has_rep_prefix() || insn.has_repne_prefix() {
while generic_vcpu_state.gprs.rcx != 0 {
let result = self.handle_ioinsn_one(insn, p, generic_vcpu_state);
generic_vcpu_state.gprs.rcx -= 1;
match result {
Ok(VmexitResult::Ok) => (),
r => return r,
}
}
Ok(VmexitResult::Ok)
} else {
self.handle_ioinsn_one(insn, p, generic_vcpu_state)
}
}
}