forked from paradigmxyz/revm-inspectors
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathedge_cov.rs
More file actions
118 lines (105 loc) · 3.86 KB
/
edge_cov.rs
File metadata and controls
118 lines (105 loc) · 3.86 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
use alloc::{vec, vec::Vec};
use alloy_primitives::{map::DefaultHashBuilder, Address, U256};
use core::{
fmt,
hash::{BuildHasher, Hash, Hasher},
};
use revm::{
bytecode::opcode::{self},
interpreter::{
interpreter_types::{InputsTr, Jumps},
Interpreter,
},
Inspector,
};
// This is the maximum number of edges that can be tracked. There is a tradeoff between performance
// and precision (less collisions).
const MAX_EDGE_COUNT: usize = 65536;
/// An `Inspector` that tracks [edge coverage](https://clang.llvm.org/docs/SanitizerCoverage.html#edge-coverage).
/// Covered edges will not wrap to zero e.g. a loop edge hit more than 255 will still be retained.
// see https://github.com/AFLplusplus/AFLplusplus/blob/5777ceaf23f48ae4ceae60e4f3a79263802633c6/instrumentation/afl-llvm-pass.so.cc#L810-L829
#[derive(Clone)]
pub struct EdgeCovInspector {
/// Map of hitcounts that can be diffed against to determine if new coverage was reached.
hitcount: Vec<u8>,
hash_builder: DefaultHashBuilder,
}
impl fmt::Debug for EdgeCovInspector {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("EdgeCovInspector")
.field("hitcount_len", &self.hitcount.len())
.finish_non_exhaustive()
}
}
impl EdgeCovInspector {
/// Create a new `EdgeCovInspector` with `MAX_EDGE_COUNT` size.
pub fn new() -> Self {
Self { hitcount: vec![0; MAX_EDGE_COUNT], hash_builder: DefaultHashBuilder::default() }
}
/// Reset the hitcount to zero.
pub fn reset(&mut self) {
self.hitcount.fill(0);
}
/// Get an immutable reference to the hitcount.
pub fn get_hitcount(&self) -> &[u8] {
self.hitcount.as_slice()
}
/// Consume the inspector and take ownership of the hitcount.
pub fn into_hitcount(self) -> Vec<u8> {
self.hitcount
}
/// Mark the edge, H(address, pc, jump_dest), as hit.
fn store_hit(&mut self, address: Address, pc: usize, jump_dest: U256) {
let mut hasher = self.hash_builder.build_hasher();
address.hash(&mut hasher);
pc.hash(&mut hasher);
jump_dest.hash(&mut hasher);
// The hash is used to index into the hitcount array,
// so it must be modulo the maximum edge count.
let edge_id = (hasher.finish() % MAX_EDGE_COUNT as u64) as usize;
self.hitcount[edge_id] = self.hitcount[edge_id].checked_add(1).unwrap_or(1);
}
#[cold]
fn do_step(&mut self, interp: &mut Interpreter) {
let address = interp.input.target_address().1; // TODO track context for delegatecall?
let current_pc = interp.bytecode.pc();
match interp.bytecode.opcode() {
opcode::JUMP => {
// unconditional jump
if let Ok(jump_dest) = interp.stack.peek(0) {
self.store_hit(address, current_pc, jump_dest);
}
}
opcode::JUMPI => {
if let Ok(stack_value) = interp.stack.peek(1) {
let jump_dest = if !stack_value.is_zero() {
// branch taken
interp.stack.peek(0)
} else {
// fall through
Ok(U256::from(current_pc + 1))
};
if let Ok(jump_dest) = jump_dest {
self.store_hit(address, current_pc, jump_dest);
}
}
}
_ => {
// no-op
}
}
}
}
impl Default for EdgeCovInspector {
fn default() -> Self {
Self::new()
}
}
impl<CTX> Inspector<CTX> for EdgeCovInspector {
#[inline]
fn step(&mut self, interp: &mut Interpreter, _context: &mut CTX) {
if matches!(interp.bytecode.opcode(), opcode::JUMP | opcode::JUMPI) {
self.do_step(interp);
}
}
}