1use qcode::{
29 context::Context,
30 space::MemorySpaceId,
31 value::{
32 BasicBlock, BlockId, InstructionId, ValueId,
33 insn::{Binop, IntBinop, Mnemonic, Store, VM_INTERRUPT},
34 },
35};
36use rustc_hash::FxHashSet;
37
38use crate::inject::CodeInjector;
39
40#[derive(Debug, Clone, PartialEq, Eq)]
43pub enum Site {
44 BlockEntry { address: u64, anchor: InstructionId },
46 Address { address: u64, anchor: InstructionId },
48 Store { insn: InstructionId },
50 Load { insn: InstructionId },
52 Compare { insn: InstructionId },
54}
55
56impl Site {
57 pub fn anchor(&self) -> InstructionId {
59 match self {
60 Self::BlockEntry { anchor, .. } | Self::Address { anchor, .. } => *anchor,
61 Self::Store { insn } | Self::Load { insn } | Self::Compare { insn } => *insn,
62 }
63 }
64}
65
66pub struct BlockView<'a> {
68 pub ctx: &'a Context<'static>,
69 pub block: BlockId,
70}
71
72impl BlockView<'_> {
73 pub fn address(&self) -> Option<u64> {
75 BasicBlock::from_id(self.ctx, self.block).address()
76 }
77
78 pub fn entry(&self) -> Option<Site> {
85 let address = self.address()?;
86 let anchor = BasicBlock::from_id(self.ctx, self.block)
87 .instructions()
88 .find(|insn| !is_interrupt_op(self.ctx, insn.id))
89 .map(|insn| insn.id)?;
90 Some(Site::BlockEntry { address, anchor })
91 }
92
93 pub fn addresses(&self) -> Vec<Site> {
101 let block = BasicBlock::from_id(self.ctx, self.block);
102 let continued = if block.address().is_none() {
103 block
104 .predecessors()
105 .filter_map(|(_, pred)| {
106 BasicBlock::from_id(self.ctx, pred)
107 .instructions()
108 .last()
109 .and_then(|insn| insn.address())
110 })
111 .collect::<Vec<u64>>()
112 } else {
113 Vec::new()
114 };
115 let mut sites = Vec::new();
116 let mut seen = None;
117 for insn in block.instructions() {
118 if is_interrupt_op(self.ctx, insn.id) {
122 continue;
123 }
124 let at = insn.address();
125 if at.is_some() && at != seen {
126 let first_run = seen.is_none();
127 seen = at;
128 let address = at.unwrap_or_default();
129 if first_run && continued.contains(&address) {
130 continue;
131 }
132 sites.push(Site::Address {
133 address,
134 anchor: insn.id,
135 });
136 }
137 }
138 sites
139 }
140
141 pub fn at(&self, address: u64) -> Option<Site> {
144 self.addresses()
145 .into_iter()
146 .find(|site| matches!(site, Site::Address { address: at, .. } if *at == address))
147 }
148
149 fn is_ram(&self, space: qcode::space::LocalMemorySpaceId) -> bool {
150 space.qualify(self.block.func) == MemorySpaceId::Shared(self.ctx.shared.default_space)
151 }
152
153 pub fn stores(&self) -> Vec<Site> {
155 BasicBlock::from_id(self.ctx, self.block)
156 .instructions()
157 .filter(|insn| matches!(insn.mnemonic(), Mnemonic::Store(store) if self.is_ram(store.space)))
158 .map(|insn| Site::Store { insn: insn.id })
159 .collect()
160 }
161
162 pub fn loads(&self) -> Vec<Site> {
164 BasicBlock::from_id(self.ctx, self.block)
165 .instructions()
166 .filter(
167 |insn| matches!(insn.mnemonic(), Mnemonic::Load(load) if self.is_ram(load.space)),
168 )
169 .map(|insn| Site::Load { insn: insn.id })
170 .collect()
171 }
172
173 pub fn compares(&self) -> Vec<Site> {
175 BasicBlock::from_id(self.ctx, self.block)
176 .instructions()
177 .filter(|insn| {
178 matches!(insn.mnemonic(), Mnemonic::Binop(binary) if binary.op.is_comparison()
179 && matches!(binary.op, Binop::Int(_)))
180 })
181 .map(|insn| Site::Compare { insn: insn.id })
182 .collect()
183 }
184}
185
186pub trait Hook {
188 fn sites(&mut self, block: &BlockView<'_>) -> Vec<Site>;
191
192 fn instrument(&mut self, site: &Site, emit: &mut Emitter<'_>);
194}
195
196pub struct Emitter<'a> {
203 ctx: &'a mut Context<'static>,
204 block: BlockId,
206 anchor: InstructionId,
207 address: Option<u64>,
209}
210
211impl<'a> Emitter<'a> {
212 pub fn new(ctx: &'a mut Context<'static>, site: &Site) -> Self {
213 let anchor = site.anchor();
214 let block = qcode::value::Instruction::from_id(ctx, anchor)
215 .parent()
216 .map(|block| block.id)
217 .expect("a site's anchor is in a block");
218 let address = match site {
219 Site::BlockEntry { address, .. } | Site::Address { address, .. } => Some(*address),
220 _ => qcode::value::Instruction::from_id(ctx, anchor).address(),
221 };
222 Self {
223 ctx,
224 block,
225 anchor,
226 address,
227 }
228 }
229
230 pub fn ctx(&self) -> &Context<'static> {
231 self.ctx
232 }
233
234 pub fn address(&self) -> Option<u64> {
236 self.address
237 }
238
239 pub fn constant(&self, value: u64, size: usize) -> ValueId {
241 self.ctx.shared.get_const(value, size)
242 }
243
244 pub fn size_of(&self, value: ValueId) -> usize {
246 self.ctx
247 .stored_type_of(value)
248 .map(|ty| self.ctx.shared.types.size_of(ty))
249 .unwrap_or(0)
250 }
251
252 pub fn store_operands(&self) -> Option<(ValueId, usize, ValueId)> {
255 let insn = self.ctx.instruction(self.anchor);
256 let Mnemonic::Store(Store { ptr, size, src, .. }) = insn.mnemonic() else {
257 return None;
258 };
259 let func = self.anchor.func;
260 Some((ptr.qualify(func), *size, src.qualify(func)))
261 }
262
263 pub fn load_operands(&self) -> Option<(ValueId, usize)> {
265 let insn = self.ctx.instruction(self.anchor);
266 let Mnemonic::Load(load) = insn.mnemonic() else {
267 return None;
268 };
269 Some((load.ptr.qualify(self.anchor.func), load.size))
270 }
271
272 pub fn binop_operands(&self) -> Option<(Binop, ValueId, ValueId)> {
274 let insn = self.ctx.instruction(self.anchor);
275 let Mnemonic::Binop(binary) = insn.mnemonic() else {
276 return None;
277 };
278 let func = self.anchor.func;
279 Some((
280 binary.op,
281 binary.lhs.qualify(func),
282 binary.rhs.qualify(func),
283 ))
284 }
285
286 pub fn binop(&mut self, op: IntBinop, lhs: ValueId, rhs: ValueId) -> ValueId {
292 let (block, anchor) = (self.block, self.anchor);
293 let mut builder = self.ctx.builder(block);
294 builder.set_insert_point_before(anchor);
295 builder.push_binop(Binop::Int(op), lhs, rhs).id()
296 }
297
298 pub fn zext(&mut self, value: ValueId, size: usize) -> ValueId {
300 if self.size_of(value) == size {
301 return value;
302 }
303 let (block, anchor) = (self.block, self.anchor);
304 let mut builder = self.ctx.builder(block);
305 builder.set_insert_point_before(anchor);
306 builder.push_zext(value, size).id()
307 }
308
309 pub fn in_range(&mut self, value: ValueId, begin: u64, end: u64) -> ValueId {
312 let value = self.zext(value, 8);
313 let offset = self.binop(IntBinop::Sub, value, self.constant(begin, 8));
314 let length = self.constant(end.wrapping_sub(begin).wrapping_add(1), 8);
315 self.binop(IntBinop::Less, offset, length)
316 }
317
318 pub fn interrupt(&mut self, code: u64, args: &[ValueId]) -> InstructionId {
321 let (block, anchor, address) = (self.block, self.anchor, self.address);
322 crate::inject::insert_interrupt(self.ctx, block, Some(anchor), address, code, args)
323 }
324
325 pub fn interrupt_if(&mut self, cond: ValueId, code: u64, args: &[ValueId]) -> InstructionId {
333 let (block, anchor, address) = (self.block, self.anchor, self.address);
334 let func = block.func;
335 let rest = self.ctx.split_block_before(block, anchor);
336 let hook = self.ctx.body_mut(func).make_block();
337 let interrupt = crate::inject::insert_interrupt(self.ctx, hook, None, address, code, args);
338 {
339 let mut builder = self.ctx.builder(hook);
340 if let Some(address) = address {
341 builder.set_address(address);
342 }
343 builder.finalize(rest);
344 }
345 {
346 let mut builder = self.ctx.builder(block);
350 builder.push_cbranch(cond, hook, rest);
351 }
352 self.block = rest;
353 interrupt
354 }
355}
356
357pub struct HookInjector<H> {
360 pub hook: H,
361 done: FxHashSet<InstructionId>,
362}
363
364impl<H: Hook> HookInjector<H> {
365 pub fn new(hook: H) -> Self {
366 Self {
367 hook,
368 done: FxHashSet::default(),
369 }
370 }
371}
372
373impl<H: Hook> CodeInjector for HookInjector<H> {
374 fn inject(&mut self, ctx: &mut Context<'static>, block: BlockId) {
375 let sites = self.hook.sites(&BlockView { ctx, block });
376 for site in sites {
377 if !self.done.insert(site.anchor()) {
378 continue;
379 }
380 let mut emit = Emitter::new(ctx, &site);
383 self.hook.instrument(&site, &mut emit);
384 }
385 }
386}
387
388pub fn is_interrupt_op(ctx: &Context<'static>, insn: InstructionId) -> bool {
390 match ctx.instruction(insn).mnemonic() {
391 Mnemonic::PCodeOp(op) => ctx.shared.pcode_ops[op.id].as_ref() == VM_INTERRUPT,
392 _ => false,
393 }
394}
395
396fn in_range(begin: u64, end: u64, addr: u64) -> bool {
402 begin > end || (begin..=end).contains(&addr)
403}
404
405#[derive(Debug, Clone)]
408pub struct BlockEntryHook {
409 pub begin: u64,
410 pub end: u64,
411 pub code: u64,
412}
413
414impl Hook for BlockEntryHook {
415 fn sites(&mut self, block: &BlockView<'_>) -> Vec<Site> {
416 block
417 .entry()
418 .filter(|site| matches!(site, Site::BlockEntry { address, .. } if in_range(self.begin, self.end, *address)))
419 .into_iter()
420 .collect()
421 }
422
423 fn instrument(&mut self, _site: &Site, emit: &mut Emitter<'_>) {
424 let address = emit.constant(emit.address().unwrap_or_default(), 8);
425 emit.interrupt(self.code, &[address]);
426 }
427}
428
429#[derive(Debug, Clone)]
432pub struct AddressHook {
433 pub addresses: FxHashSet<u64>,
434 pub code: u64,
435}
436
437impl AddressHook {
438 pub fn new(addresses: impl IntoIterator<Item = u64>, code: u64) -> Self {
439 Self {
440 addresses: addresses.into_iter().collect(),
441 code,
442 }
443 }
444}
445
446impl Hook for AddressHook {
447 fn sites(&mut self, block: &BlockView<'_>) -> Vec<Site> {
448 block
449 .addresses()
450 .into_iter()
451 .filter(|site| matches!(site, Site::Address { address, .. } if self.addresses.contains(address)))
452 .collect()
453 }
454
455 fn instrument(&mut self, _site: &Site, emit: &mut Emitter<'_>) {
456 let address = emit.constant(emit.address().unwrap_or_default(), 8);
457 emit.interrupt(self.code, &[address]);
458 }
459}
460
461#[derive(Debug, Clone)]
468pub struct WriteWatch {
469 pub begin: u64,
470 pub end: u64,
471 pub code: u64,
472}
473
474impl Hook for WriteWatch {
475 fn sites(&mut self, block: &BlockView<'_>) -> Vec<Site> {
476 block.stores()
477 }
478
479 fn instrument(&mut self, _site: &Site, emit: &mut Emitter<'_>) {
480 let Some((ptr, size, value)) = emit.store_operands() else {
481 return;
482 };
483 let cond = emit.in_range(ptr, self.begin, self.end);
484 let mut args = vec![emit.zext(ptr, 8), emit.constant(size as u64, 8)];
485 if size <= 8 {
486 args.push(emit.zext(value, 8));
487 }
488 emit.interrupt_if(cond, self.code, &args);
489 }
490}
491
492#[derive(Debug, Clone)]
496pub struct CompareHook {
497 pub code: u64,
498}
499
500impl Hook for CompareHook {
501 fn sites(&mut self, block: &BlockView<'_>) -> Vec<Site> {
502 block.compares()
503 }
504
505 fn instrument(&mut self, _site: &Site, emit: &mut Emitter<'_>) {
506 let Some((_, lhs, rhs)) = emit.binop_operands() else {
507 return;
508 };
509 emit.interrupt(self.code, &[lhs, rhs]);
510 }
511}