use qcode::value::ValueId;
use crate::{
hook::{BlockView, Emitter, Hook, Site},
vm::{Interrupt, InterruptKind},
};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct HookId(pub u64);
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum HookAction {
Continue,
Stop,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum InsnAction {
Handled(Option<u128>),
Unhandled,
Stop,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct MemAccess {
pub pc: Option<u64>,
pub addr: u64,
pub size: u64,
pub value: Option<u64>,
}
pub const TABLE_CODES: u64 = 1 << 62;
pub(crate) type CodeCallback<S> = Box<dyn FnMut(&mut crate::Vm<S>, u64) -> HookAction>;
pub(crate) type MemCallback<S> = Box<dyn FnMut(&mut crate::Vm<S>, &MemAccess) -> HookAction>;
pub(crate) type InsnCallback<S> = Box<dyn FnMut(&mut crate::Vm<S>, &Interrupt) -> InsnAction>;
pub(crate) enum Callback<S> {
Code(CodeCallback<S>),
Mem(MemCallback<S>),
Insn {
name: Option<Box<str>>,
callback: InsnCallback<S>,
},
}
pub(crate) struct HookTable<S> {
next: u64,
pub(crate) callbacks: Vec<(HookId, Callback<S>)>,
}
impl<S> Default for HookTable<S> {
fn default() -> Self {
Self {
next: 0,
callbacks: Vec::new(),
}
}
}
impl<S> HookTable<S> {
pub(crate) fn register(&mut self, callback: Callback<S>) -> HookId {
let id = HookId(self.next);
self.next += 1;
self.callbacks.push((id, callback));
id
}
pub(crate) fn remove(&mut self, id: HookId) -> bool {
let before = self.callbacks.len();
self.callbacks.retain(|(have, _)| *have != id);
self.callbacks.len() != before
}
pub(crate) fn code(id: HookId) -> u64 {
TABLE_CODES + id.0
}
pub(crate) fn owner(interrupt: &Interrupt) -> Option<HookId> {
match interrupt.kind {
InterruptKind::Explicit { code } if code >= TABLE_CODES => {
Some(HookId(code - TABLE_CODES))
}
_ => None,
}
}
}
pub(crate) fn in_range(begin: u64, end: u64, addr: u64) -> bool {
begin > end || (begin..=end).contains(&addr)
}
pub(crate) struct CodeRangeHook {
pub begin: u64,
pub end: u64,
pub code: u64,
}
impl Hook for CodeRangeHook {
fn sites(&mut self, block: &BlockView<'_>) -> Vec<Site> {
block
.addresses()
.into_iter()
.filter(|site| {
matches!(site, Site::Address { address, .. } if in_range(self.begin, self.end, *address))
})
.collect()
}
fn instrument(&mut self, _site: &Site, emit: &mut Emitter<'_>) {
let address = emit.constant(emit.address().unwrap_or_default(), 8);
emit.interrupt(self.code, &[address]);
}
}
pub(crate) struct ReadWatch {
pub begin: u64,
pub end: u64,
pub code: u64,
}
impl Hook for ReadWatch {
fn sites(&mut self, block: &BlockView<'_>) -> Vec<Site> {
block.loads()
}
fn instrument(&mut self, _site: &Site, emit: &mut Emitter<'_>) {
let Some((ptr, size)) = emit.load_operands() else {
return;
};
let cond = emit.in_range(ptr, self.begin, self.end);
let args: Vec<ValueId> = vec![emit.zext(ptr, 8), emit.constant(size as u64, 8)];
emit.interrupt_if(cond, self.code, &args);
}
}