Skip to main content

qcode_vm/
table.rs

1//! Unicorn-shaped hooks: callbacks the machine calls from inside
2//! [`Vm::run`](crate::Vm::run).
3//!
4//! Each registration installs a [`Hook`] whose interrupt
5//! carries a code from a reserved range, and files the callback under it.
6//! When a run reaches such an interrupt the callback runs with the machine
7//! stopped at the site, the machine resumes, and the run goes on — unless the
8//! callback asks to [`stop`](HookAction::Stop), in which case the run returns
9//! [`VmExit::HookStop`](crate::VmExit::HookStop). Interrupts with any other code, and every other
10//! exit, come back to the caller as before.
11//!
12//! Several hooks may watch the same site. They fire in registration order,
13//! each from its own interrupt, and a stop ends the run before the later ones
14//! run: Unicorn's chain with Qiling's veto.
15//!
16//! Instruction hooks are different in kind: they answer an architecture user
17//! op the interpreter has no semantics for — `syscall`, `rdtsc`, `cpuid` —
18//! and so must supply its result. [`InsnAction::Handled`] carries that.
19
20use qcode::value::ValueId;
21
22use crate::{
23    hook::{BlockView, Emitter, Hook, Site},
24    vm::{Interrupt, InterruptKind},
25};
26
27/// A registered hook, for [`hook_del`](crate::Vm::hook_del).
28#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
29pub struct HookId(pub u64);
30
31/// What a code or memory hook wants the run to do next.
32#[derive(Debug, Clone, Copy, PartialEq, Eq)]
33pub enum HookAction {
34    /// Resume the guest and carry on.
35    Continue,
36    /// Leave the machine stopped at the site and return from the run.
37    Stop,
38}
39
40/// What an instruction hook did about the user op it was called for.
41#[derive(Debug, Clone, Copy, PartialEq, Eq)]
42pub enum InsnAction {
43    /// The op's effect has been applied; this is its result, if it declares
44    /// one. The machine resumes past it.
45    Handled(Option<u128>),
46    /// Not this hook's op: the next hook is asked, and if none handles it the
47    /// interrupt is returned to the caller.
48    Unhandled,
49    /// Leave the machine stopped at the op and return from the run.
50    Stop,
51}
52
53/// A guest memory access a hook observes.
54#[derive(Debug, Clone, Copy, PartialEq, Eq)]
55pub struct MemAccess {
56    /// The guest instruction making the access.
57    pub pc: Option<u64>,
58    pub addr: u64,
59    pub size: u64,
60    /// The value being written; `None` for a read, or a value wider than
61    /// 64 bits.
62    pub value: Option<u64>,
63}
64
65/// Interrupt codes the table owns. Anything at or above this is a table
66/// hook's; user injectors keep their codes below it.
67pub const TABLE_CODES: u64 = 1 << 62;
68
69pub(crate) type CodeCallback<S> = Box<dyn FnMut(&mut crate::Vm<S>, u64) -> HookAction>;
70pub(crate) type MemCallback<S> = Box<dyn FnMut(&mut crate::Vm<S>, &MemAccess) -> HookAction>;
71pub(crate) type InsnCallback<S> = Box<dyn FnMut(&mut crate::Vm<S>, &Interrupt) -> InsnAction>;
72
73pub(crate) enum Callback<S> {
74    Code(CodeCallback<S>),
75    Mem(MemCallback<S>),
76    /// For the user op called `name`, or any user op when `None`.
77    Insn {
78        name: Option<Box<str>>,
79        callback: InsnCallback<S>,
80    },
81}
82
83/// The registered callbacks, by hook.
84pub(crate) struct HookTable<S> {
85    next: u64,
86    pub(crate) callbacks: Vec<(HookId, Callback<S>)>,
87}
88
89impl<S> Default for HookTable<S> {
90    fn default() -> Self {
91        Self {
92            next: 0,
93            callbacks: Vec::new(),
94        }
95    }
96}
97
98impl<S> HookTable<S> {
99    pub(crate) fn register(&mut self, callback: Callback<S>) -> HookId {
100        let id = HookId(self.next);
101        self.next += 1;
102        self.callbacks.push((id, callback));
103        id
104    }
105
106    pub(crate) fn remove(&mut self, id: HookId) -> bool {
107        let before = self.callbacks.len();
108        self.callbacks.retain(|(have, _)| *have != id);
109        self.callbacks.len() != before
110    }
111
112    /// The interrupt code a hook's injected interrupts carry.
113    pub(crate) fn code(id: HookId) -> u64 {
114        TABLE_CODES + id.0
115    }
116
117    /// The hook an explicit interrupt belongs to, if it is the table's.
118    pub(crate) fn owner(interrupt: &Interrupt) -> Option<HookId> {
119        match interrupt.kind {
120            InterruptKind::Explicit { code } if code >= TABLE_CODES => {
121                Some(HookId(code - TABLE_CODES))
122            }
123            _ => None,
124        }
125    }
126}
127
128/// Whether `addr` lies in `begin..=end`, or anywhere when `begin > end` —
129/// Unicorn's convention for "no range".
130pub(crate) fn in_range(begin: u64, end: u64, addr: u64) -> bool {
131    begin > end || (begin..=end).contains(&addr)
132}
133
134/// Stops before every guest instruction in a range: `hook_code`.
135pub(crate) struct CodeRangeHook {
136    pub begin: u64,
137    pub end: u64,
138    pub code: u64,
139}
140
141impl Hook for CodeRangeHook {
142    fn sites(&mut self, block: &BlockView<'_>) -> Vec<Site> {
143        block
144            .addresses()
145            .into_iter()
146            .filter(|site| {
147                matches!(site, Site::Address { address, .. } if in_range(self.begin, self.end, *address))
148            })
149            .collect()
150    }
151
152    fn instrument(&mut self, _site: &Site, emit: &mut Emitter<'_>) {
153        let address = emit.constant(emit.address().unwrap_or_default(), 8);
154        emit.interrupt(self.code, &[address]);
155    }
156}
157
158/// Stops before every load from guest memory in a range, with the address
159/// and width: `hook_mem_read`. The range check is IR, as in
160/// [`WriteWatch`](crate::hook::WriteWatch).
161pub(crate) struct ReadWatch {
162    pub begin: u64,
163    pub end: u64,
164    pub code: u64,
165}
166
167impl Hook for ReadWatch {
168    fn sites(&mut self, block: &BlockView<'_>) -> Vec<Site> {
169        block.loads()
170    }
171
172    fn instrument(&mut self, _site: &Site, emit: &mut Emitter<'_>) {
173        let Some((ptr, size)) = emit.load_operands() else {
174            return;
175        };
176        let cond = emit.in_range(ptr, self.begin, self.end);
177        let args: Vec<ValueId> = vec![emit.zext(ptr, 8), emit.constant(size as u64, 8)];
178        emit.interrupt_if(cond, self.code, &args);
179    }
180}