Skip to content

Commit

Permalink
Merge pull request #213 from p4zuu/insn_buffer
Browse files Browse the repository at this point in the history
Introduce InsnBuffer and other improvements
  • Loading branch information
joergroedel authored Jan 25, 2024
2 parents f0d72a0 + f88f617 commit 33dc856
Show file tree
Hide file tree
Showing 2 changed files with 248 additions and 43 deletions.
270 changes: 237 additions & 33 deletions src/cpu/insn.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,39 +9,104 @@ extern crate alloc;
use crate::cpu::vc::VcError;
use crate::cpu::vc::VcErrorType;
use crate::error::SvsmError;
use core::ops::{Index, IndexMut};

pub const MAX_INSN_SIZE: usize = 15;
pub const MAX_INSN_FIELD_SIZE: usize = 3;

#[derive(Default, Debug, Copy, Clone)]
pub struct Instruction {
pub prefixes: InstructionField,
pub insn_bytes: [u8; MAX_INSN_SIZE],
pub length: usize,
pub opcode: InstructionField,
pub opnd_bytes: usize,
/// A common structure shared by different fields of an
/// [`Instruction`] struct.
#[derive(Debug, Copy, Clone, Default, PartialEq)]
pub struct InsnBuffer<const N: usize>
where
[u8; N]: Default,
{
/// Internal buffer of constant size `N`.
pub buf: [u8; N],
/// Number of useful bytes to be taken from `buf`.
/// if `nb_bytes = 0`, the corresponding structure has
/// no useful information. Otherwise, only `self.buf[..self.nb_bytes]`
/// is useful.
pub nb_bytes: usize,
}

#[derive(Default, Debug, Copy, Clone)]
pub struct InstructionField {
pub bytes: [u8; MAX_INSN_FIELD_SIZE],
pub nb_bytes: usize,
impl<const N: usize> InsnBuffer<N>
where
[u8; N]: Default,
{
fn new(buf: [u8; N], nb_bytes: usize) -> Self {
Self { buf, nb_bytes }
}
}

impl<const N: usize> Index<usize> for InsnBuffer<N>
where
[u8; N]: Default,
{
type Output = u8;
fn index(&self, i: usize) -> &Self::Output {
&self.buf[i]
}
}

impl<const N: usize> IndexMut<usize> for InsnBuffer<N>
where
[u8; N]: Default,
{
fn index_mut(&mut self, i: usize) -> &mut Self::Output {
&mut self.buf[i]
}
}

/// A view of an x86 instruction.
#[derive(Default, Debug, Copy, Clone, PartialEq)]
pub struct Instruction {
/// Optional x86 instruction prefixes.
pub prefixes: Option<InsnBuffer<MAX_INSN_FIELD_SIZE>>,
/// Raw bytes copied from rip location.
/// After decoding, `self.insn_bytes.nb_bytes` is adjusted
/// to the total len of the instruction, prefix included.
pub insn_bytes: InsnBuffer<MAX_INSN_SIZE>,
/// Mandatory opcode.
pub opcode: InsnBuffer<MAX_INSN_FIELD_SIZE>,
/// Operand size in bytes.
pub opnd_bytes: usize,
}

impl Instruction {
pub fn new(insn_bytes: [u8; MAX_INSN_SIZE]) -> Self {
Self {
prefixes: InstructionField {
bytes: insn_bytes[..MAX_INSN_FIELD_SIZE].try_into().unwrap(),
nb_bytes: 0,
},
insn_bytes,
length: 0,
opcode: InstructionField::default(), // we'll copy content later
prefixes: None,
opcode: InsnBuffer::default(), // we'll copy content later
insn_bytes: InsnBuffer::new(insn_bytes, 0),
opnd_bytes: 4,
}
}

/// Returns the length of the instruction.
///
/// # Returns:
///
/// [`usize`]: The total size of an instruction,
/// prefix included.
pub fn len(&self) -> usize {
self.insn_bytes.nb_bytes
}

/// Returns true if the related [`Instruction`] can be considered empty.
pub fn is_empty(&self) -> bool {
self.insn_bytes.nb_bytes == 0
}

/// Decode the instruction.
/// At the moment, the decoding is very naive since we only need to decode CPUID,
/// IN and OUT (without strings and immediate usage) instructions. A complete decoding
/// of the full x86 instruction set is still TODO.
///
/// # Returns
///
/// [`Result<(), SvsmError>`]: A [`Result`] containing the empty
/// value on success, or an [`SvsmError`] on failure.
pub fn decode(&mut self) -> Result<(), SvsmError> {
/*
* At this point, we only need to handle IOIO (without string and immediate versions)
Expand All @@ -51,12 +116,20 @@ impl Instruction {
// {in, out}w instructions uses a 0x66 operand-size opcode prefix
0x66 => {
if self.insn_bytes[1] == 0xED || self.insn_bytes[1] == 0xEF {
self.prefixes.nb_bytes = 1;
// for prefix length
self.prefixes = Some(InsnBuffer::new(
self.insn_bytes.buf[..MAX_INSN_FIELD_SIZE]
.try_into()
.unwrap(),
1,
));

// for {in, out}w opcode length
self.opcode.nb_bytes = 1;
self.opcode.bytes[0] = self.insn_bytes[1];
self.opcode[0] = self.insn_bytes[1];

self.length = self.prefixes.nb_bytes + self.opcode.nb_bytes;
self.insn_bytes.nb_bytes =
self.prefixes.unwrap().nb_bytes + self.opcode.nb_bytes;
self.opnd_bytes = 2;
return Ok(());
}
Expand All @@ -69,36 +142,32 @@ impl Instruction {
}
// inb and oub register opcodes
0xEC | 0xEE => {
self.prefixes.nb_bytes = 0;

self.opcode.nb_bytes = 1;
self.opcode.bytes[0] = self.insn_bytes[0];
self.opcode[0] = self.insn_bytes[0];

self.length = self.opcode.nb_bytes;
self.insn_bytes.nb_bytes = self.opcode.nb_bytes;
self.opnd_bytes = 1;
Ok(())
}
// inl and outl register opcodes
0xED | 0xEF => {
self.prefixes.nb_bytes = 0;

self.opcode.nb_bytes = 1;
self.opcode.bytes[0] = self.insn_bytes[0];
self.opcode[0] = self.insn_bytes[0];

self.length = self.opcode.nb_bytes;
self.insn_bytes.nb_bytes = self.opcode.nb_bytes;
self.opnd_bytes = 4;
Ok(())
}

0x0F => {
// CPUID opcode
if self.insn_bytes[1] == 0xA2 {
self.prefixes.nb_bytes = 0;

self.opcode.nb_bytes = 2;
self.opcode.bytes[..2].clone_from_slice(&self.insn_bytes[..2]);
let opcode_len = self.opcode.nb_bytes;
self.opcode.buf[..opcode_len]
.clone_from_slice(&self.insn_bytes.buf[..opcode_len]);

self.length = self.opcode.nb_bytes;
self.insn_bytes.nb_bytes = self.opcode.nb_bytes;
return Ok(());
}

Expand All @@ -117,10 +186,145 @@ impl Instruction {
}
}

/// Copy the instruction bytes where rip points to.
///
/// # Arguments
///
/// rip: instruction pointer as [`*const u8`].
///
/// # Returns
///
/// [`[u8; MAX_INSN_SIZE]`]: the 15-byte buffer where rip points to.
///
/// # Safety
///
/// The caller should validate that `rip` is set to a valid address
/// and that the next [`MAX_INSN_SIZE`] bytes are within valid memory.
pub unsafe fn insn_fetch(rip: *const u8) -> [u8; MAX_INSN_SIZE] {
rip.cast::<[u8; MAX_INSN_SIZE]>().read()
}

#[cfg(test)]
mod tests {
use super::{InsnBuffer, Instruction, MAX_INSN_SIZE};

#[test]
fn test_decode_inw() {
let raw_insn: [u8; MAX_INSN_SIZE] = [
0x66, 0xED, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41,
0x41,
];

let mut insn = Instruction::new(raw_insn);
insn.decode().unwrap();

let target = Instruction {
prefixes: Some(InsnBuffer {
buf: [0x66, 0xED, 0x41],
nb_bytes: 1,
}),
insn_bytes: InsnBuffer {
buf: raw_insn,
nb_bytes: 2,
},
opcode: InsnBuffer {
buf: [0xED, 0, 0],
nb_bytes: 1,
},
opnd_bytes: 2,
};

assert_eq!(target, insn);
}

#[test]
fn test_decode_outb() {
let raw_insn: [u8; MAX_INSN_SIZE] = [
0xEE, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41,
0x41,
];

let mut insn = Instruction::new(raw_insn);
insn.decode().unwrap();

let target = Instruction {
prefixes: None,
insn_bytes: InsnBuffer {
buf: raw_insn,
nb_bytes: 1,
},
opcode: InsnBuffer {
buf: [0xEE, 0, 0],
nb_bytes: 1,
},
opnd_bytes: 1,
};

assert_eq!(target, insn);
}

#[test]
fn test_decode_outl() {
let raw_insn: [u8; MAX_INSN_SIZE] = [
0xEF, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41,
0x41,
];

let mut insn = Instruction::new(raw_insn);
insn.decode().unwrap();

let target = Instruction {
prefixes: None,
insn_bytes: InsnBuffer {
buf: raw_insn,
nb_bytes: 1,
},
opcode: InsnBuffer {
buf: [0xEF, 0, 0],
nb_bytes: 1,
},
opnd_bytes: 4,
};

assert_eq!(target, insn);
}

#[test]
fn test_decode_cpuid() {
let raw_insn: [u8; MAX_INSN_SIZE] = [
0x0F, 0xA2, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41,
0x41,
];

let mut insn = Instruction::new(raw_insn);
insn.decode().unwrap();

let target = Instruction {
prefixes: None,
insn_bytes: InsnBuffer {
buf: raw_insn,
nb_bytes: 2,
},
opcode: InsnBuffer {
buf: [0x0F, 0xA2, 0],
nb_bytes: 2,
},
opnd_bytes: 4,
};

assert_eq!(target, insn);
}

#[test]
fn test_decode_failed() {
let raw_insn: [u8; MAX_INSN_SIZE] = [
0x66, 0xEE, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41,
0x41,
];

let mut insn = Instruction::new(raw_insn);
let err = insn.decode();

assert!(err.is_err());
}
}
21 changes: 11 additions & 10 deletions src/cpu/vc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -183,7 +183,7 @@ fn snp_cpuid(ctx: &mut X86ExceptionContext) -> Result<(), SvsmError> {
}

fn vc_finish_insn(ctx: &mut X86ExceptionContext, insn: &Instruction) {
ctx.frame.rip += insn.length;
ctx.frame.rip += insn.len()
}

fn handle_ioio(
Expand All @@ -194,7 +194,7 @@ fn handle_ioio(
let port: u16 = (ctx.regs.rdx & 0xffff) as u16;
let out_value: u64 = ctx.regs.rax as u64;

match insn.opcode.bytes[0] {
match insn.opcode[0] {
0x6C..=0x6F | 0xE4..=0xE7 => Err(SvsmError::Vc(VcError {
rip: ctx.frame.rip,
code: ctx.error_code,
Expand All @@ -206,11 +206,9 @@ fn handle_ioio(
Ok(())
}
0xED => {
let (size, mask) = if insn.prefixes.nb_bytes > 0 {
// inw instruction has a 0x66 operand-size prefix for word-sized operands
(GHCBIOSize::Size16, u16::MAX as u64)
} else {
(GHCBIOSize::Size32, u32::MAX as u64)
let (size, mask) = match insn.prefixes {
Some(prefix) if prefix.nb_bytes > 0 => (GHCBIOSize::Size16, u16::MAX as u64),
_ => (GHCBIOSize::Size32, u32::MAX as u64),
};

let ret = ghcb.ioio_in(port, size)?;
Expand All @@ -220,9 +218,12 @@ fn handle_ioio(
0xEE => ghcb.ioio_out(port, GHCBIOSize::Size8, out_value),
0xEF => {
let mut size: GHCBIOSize = GHCBIOSize::Size32;
if insn.prefixes.nb_bytes > 0 {
// outw instruction has a 0x66 operand-size prefix for word-sized operands.
size = GHCBIOSize::Size16;
if let Some(prefix) = insn.prefixes {
// this is always true at the moment
if prefix.nb_bytes > 0 {
// outw instruction has a 0x66 operand-size prefix for word-sized operands.
size = GHCBIOSize::Size16;
}
}

ghcb.ioio_out(port, size, out_value)
Expand Down

0 comments on commit 33dc856

Please sign in to comment.