use std::collections::HashMap;
use vmi_core::{Architecture, Hex, Registers, Va, VmiError, VmiOs, VmiState};
pub enum RecipeControlFlow {
Continue,
Break,
Repeat,
Skip,
Goto(usize),
}
pub type RecipeStepFn<Os, T> = Box<
dyn Fn(&mut RecipeContext<'_, Os, T>) -> Result<RecipeControlFlow, VmiError>
+ Send
+ Sync
+ 'static,
>;
pub type SymbolCache = HashMap<String, Va>;
pub type ImageSymbolCache = HashMap<String, SymbolCache>;
pub struct Recipe<Os, T>
where
Os: VmiOs,
{
pub(super) steps: Vec<RecipeStepFn<Os, T>>,
pub(super) data: T,
}
impl<Os, T> Recipe<Os, T>
where
Os: VmiOs,
{
pub fn new(data: T) -> Self {
Self {
steps: Vec::new(),
data,
}
}
pub fn step<F>(mut self, f: F) -> Self
where
F: Fn(&mut RecipeContext<'_, Os, T>) -> Result<RecipeControlFlow, VmiError>
+ Send
+ Sync
+ 'static,
{
self.steps.push(Box::new(f));
self
}
}
pub struct RecipeContext<'a, Os, T>
where
Os: VmiOs,
{
pub vmi: &'a VmiState<'a, Os>,
pub registers: &'a mut <Os::Architecture as Architecture>::Registers,
pub data: &'a mut T,
pub cache: &'a mut ImageSymbolCache,
}
pub struct RecipeExecutor<Os, T>
where
Os: VmiOs,
{
recipe: Recipe<Os, T>,
cache: ImageSymbolCache,
original_registers: Option<<Os::Architecture as Architecture>::Registers>,
index: Option<usize>,
previous_stack_pointer: Option<Va>,
}
impl<Os, T> RecipeExecutor<Os, T>
where
Os: VmiOs,
{
pub fn new(recipe: Recipe<Os, T>) -> Self {
Self {
recipe,
cache: ImageSymbolCache::new(),
original_registers: None,
index: None,
previous_stack_pointer: None,
}
}
pub fn execute(
&mut self,
vmi: &VmiState<Os>,
) -> Result<Option<<Os::Architecture as Architecture>::Registers>, VmiError> {
if self.has_stack_pointer_decreased(vmi) {
return Ok(None);
}
let index = match &mut self.index {
Some(index) => index,
None => {
self.original_registers = Some(*vmi.registers());
self.index.insert(0)
}
};
if let Some(step) = self.recipe.steps.get(*index) {
tracing::debug!(index, "recipe step");
let mut registers = *vmi.registers();
let next = step(&mut RecipeContext {
vmi,
registers: &mut registers,
data: &mut self.recipe.data,
cache: &mut self.cache,
})?;
self.previous_stack_pointer = Some(Va(registers.stack_pointer()));
match next {
RecipeControlFlow::Continue => {
*index += 1;
return Ok(Some(registers));
}
RecipeControlFlow::Break => {}
RecipeControlFlow::Repeat => {
return Ok(Some(registers));
}
RecipeControlFlow::Skip => {
*index += 2;
return Ok(Some(registers));
}
RecipeControlFlow::Goto(i) => {
*index = i;
return Ok(Some(registers));
}
}
}
tracing::debug!(
result = %Hex(vmi.registers().result()),
"recipe finished"
);
self.index = None;
let original_registers = self.original_registers.expect("original_registers");
Ok(Some(original_registers))
}
fn has_stack_pointer_decreased(&self, vmi: &VmiState<Os>) -> bool {
let previous_stack_pointer = match self.previous_stack_pointer {
Some(previous_stack_pointer) => previous_stack_pointer,
None => return false,
};
let current_stack_pointer = Va(vmi.registers().stack_pointer());
let result = current_stack_pointer < previous_stack_pointer;
if result {
tracing::trace!(
%previous_stack_pointer,
%current_stack_pointer,
"stack pointer decreased"
);
}
result
}
pub fn reset(&mut self) {
self.index = None;
}
pub fn done(&self) -> bool {
self.index.is_none() && self.original_registers.is_some()
}
}