1use std::collections::BTreeMap;
10
11pub type Address = u64;
12
13#[derive(
15 Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, serde::Serialize, serde::Deserialize,
16)]
17pub enum FunctionDiscoveryReason {
18 Entry,
19 CrtMain,
20 CallTarget,
21 TailCall,
22 UserSeed,
23 CodePointer,
27}
28
29#[derive(
31 Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, serde::Serialize, serde::Deserialize,
32)]
33pub enum EdgeKind {
34 Entry,
35 DirectBranch,
36 ConditionalBranch,
37 Fallthrough,
38 CallTarget,
39 CallFallthrough,
40 JumpTableTarget,
41 TailCall,
42 Speculative,
43}
44
45#[derive(
47 Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, serde::Serialize, serde::Deserialize,
48)]
49pub enum DiscoveryKind {
50 Function {
51 reason: FunctionDiscoveryReason,
52 },
53 Block {
54 function: Address,
56 edge_kind: EdgeKind,
57 },
58}
59
60#[derive(
62 Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, serde::Serialize, serde::Deserialize,
63)]
64pub struct DiscoveryKey {
65 pub target: Address,
66 pub kind: DiscoveryKind,
67 pub source_block: Option<Address>,
71}
72
73#[derive(Clone, Default, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
76pub enum DiscoveryProvenance {
77 LoaderEntry,
78
79 DirectLift {
80 source_addr: Address,
81 },
82
83 Optimization {
84 pass: String,
85 assumption: Option<String>,
86 },
87
88 UserSeed,
89
90 #[default]
91 Unknown,
92}
93#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
95pub struct Discovery {
96 pub target: Address,
97 pub kind: DiscoveryKind,
98 pub name: Option<String>,
100 pub source_function: Option<Address>,
103 pub source_block: Option<Address>,
104 pub source_addr: Option<Address>,
105 pub provenance: DiscoveryProvenance,
106}
107
108impl Discovery {
109 pub fn function(target: Address) -> Self {
110 Self {
111 target,
112 kind: DiscoveryKind::Function {
113 reason: FunctionDiscoveryReason::CallTarget,
114 },
115 name: None,
116 source_function: None,
117 source_block: None,
118 source_addr: None,
119 provenance: DiscoveryProvenance::Unknown,
120 }
121 }
122
123 pub fn entry(target: Address) -> Self {
124 Self::function(target)
125 .with_function_reason(FunctionDiscoveryReason::Entry)
126 .with_provenance(DiscoveryProvenance::LoaderEntry)
127 }
128
129 pub fn block(target: Address, function: Address) -> Self {
130 Self {
131 target,
132 kind: DiscoveryKind::Block {
133 function,
134 edge_kind: EdgeKind::DirectBranch,
135 },
136 name: None,
137 source_function: Some(function),
138 source_block: None,
139 source_addr: None,
140 provenance: DiscoveryProvenance::Unknown,
141 }
142 }
143
144 pub fn key(&self) -> DiscoveryKey {
145 DiscoveryKey {
146 target: self.target,
147 kind: self.kind.clone(),
148 source_block: self.source_block,
149 }
150 }
151
152 pub fn with_name(mut self, name: Option<String>) -> Self {
153 self.name = name;
154 self
155 }
156
157 pub fn from_addr(mut self, addr: Address) -> Self {
158 self.source_addr = Some(addr);
159 if matches!(self.provenance, DiscoveryProvenance::Unknown) {
160 self.provenance = DiscoveryProvenance::DirectLift { source_addr: addr };
161 }
162 self
163 }
164
165 pub fn from_block_addr(mut self, addr: Address) -> Self {
166 self.source_block = Some(addr);
167 self
168 }
169
170 pub fn with_edge_kind(mut self, edge_kind: EdgeKind) -> Self {
171 if let DiscoveryKind::Block {
172 edge_kind: existing,
173 ..
174 } = &mut self.kind
175 {
176 *existing = edge_kind;
177 }
178 self
179 }
180
181 pub fn with_function_reason(mut self, reason: FunctionDiscoveryReason) -> Self {
182 if let DiscoveryKind::Function {
183 reason: existing, ..
184 } = &mut self.kind
185 {
186 *existing = reason;
187 }
188 self
189 }
190
191 pub fn with_provenance(mut self, provenance: DiscoveryProvenance) -> Self {
192 self.provenance = provenance;
193 self
194 }
195}
196
197#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
198pub enum DiscoveryState {
199 Pending,
200 Lifted,
201 Failed { reason: String },
202 Skipped { reason: String },
203}
204
205#[derive(Default, Clone, Debug, serde::Serialize, serde::Deserialize)]
207pub struct DiscoveryQueue {
208 pending: BTreeMap<DiscoveryKey, Discovery>,
209 states: BTreeMap<DiscoveryKey, DiscoveryState>,
210}
211
212impl DiscoveryQueue {
213 pub fn insert(&mut self, discovery: Discovery) -> bool {
215 let key = discovery.key();
216 if matches!(
217 self.states.get(&key),
218 Some(
219 DiscoveryState::Lifted
220 | DiscoveryState::Failed { .. }
221 | DiscoveryState::Skipped { .. }
222 )
223 ) {
224 return false;
225 }
226 let inserted = !self.pending.contains_key(&key);
227 self.pending.entry(key.clone()).or_insert(discovery);
228 self.states.entry(key).or_insert(DiscoveryState::Pending);
229 inserted
230 }
231
232 pub fn drain(&mut self) -> Vec<Discovery> {
233 std::mem::take(&mut self.pending).into_values().collect()
234 }
235
236 pub fn iter(&self) -> impl Iterator<Item = &Discovery> + '_ {
237 self.pending.values()
238 }
239
240 pub fn is_empty(&self) -> bool {
241 self.pending.is_empty()
242 }
243
244 pub fn pending_len(&self) -> usize {
245 self.pending.len()
246 }
247
248 pub fn mark_lifted(&mut self, key: DiscoveryKey) {
249 self.states.insert(key, DiscoveryState::Lifted);
250 }
251
252 pub fn mark_failed(&mut self, key: DiscoveryKey, reason: impl Into<String>) {
253 self.states.insert(
254 key,
255 DiscoveryState::Failed {
256 reason: reason.into(),
257 },
258 );
259 }
260
261 pub fn mark_skipped(&mut self, key: DiscoveryKey, reason: impl Into<String>) {
262 self.states.insert(
263 key,
264 DiscoveryState::Skipped {
265 reason: reason.into(),
266 },
267 );
268 }
269
270 pub fn state(&self, key: &DiscoveryKey) -> Option<&DiscoveryState> {
271 self.states.get(key)
272 }
273
274 pub fn states(&self) -> impl Iterator<Item = (&DiscoveryKey, &DiscoveryState)> + '_ {
275 self.states.iter()
276 }
277
278 pub fn lifted_seeds(&self) -> Vec<CodeSeed> {
284 self.states
285 .iter()
286 .filter(|(_, state)| matches!(state, DiscoveryState::Lifted))
287 .map(|(key, _)| CodeSeed::from_key(key))
288 .collect()
289 }
290}
291
292#[derive(Clone, Copy, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
297pub enum CodeSeed {
298 Function { target: Address },
300 Block {
302 function: Address,
303 target: Address,
304 edge_kind: EdgeKind,
305 },
306}
307
308impl CodeSeed {
309 fn from_key(key: &DiscoveryKey) -> Self {
310 match key.kind {
311 DiscoveryKind::Function { .. } => CodeSeed::Function { target: key.target },
312 DiscoveryKind::Block {
313 function,
314 edge_kind,
315 } => CodeSeed::Block {
316 function,
317 target: key.target,
318 edge_kind,
319 },
320 }
321 }
322
323 pub fn into_discovery(self) -> Discovery {
328 match self {
329 CodeSeed::Function { target } => {
330 Discovery::function(target).with_provenance(DiscoveryProvenance::UserSeed)
331 }
332 CodeSeed::Block {
333 function,
334 target,
335 edge_kind,
336 } => Discovery::block(target, function)
337 .with_edge_kind(edge_kind)
338 .with_provenance(DiscoveryProvenance::UserSeed),
339 }
340 }
341}
342
343#[cfg(test)]
344mod tests {
345 use super::*;
346
347 #[test]
348 fn dedups_exact_key_not_target() {
349 let mut q = DiscoveryQueue::default();
350 assert!(q.insert(Discovery::block(0x1000, 0x900)));
351 assert!(!q.insert(Discovery::block(0x1000, 0x900)));
352 assert!(q.insert(Discovery::block(0x1000, 0x900).from_block_addr(0x910)));
353 assert!(q.insert(Discovery::block(0x1000, 0x900).from_block_addr(0x920)));
354 assert!(q.insert(Discovery::block(0x1000, 0xa00)));
355 assert!(q.insert(Discovery::function(0x1000)));
356
357 let all: Vec<_> = q.iter().collect();
358 assert_eq!(all.len(), 5);
359 }
360
361 #[test]
362 fn terminal_state_blocks_requeue_of_same_key() {
363 let mut q = DiscoveryQueue::default();
364 let d = Discovery::function(0x10);
365 let key = d.key();
366 assert!(q.insert(d.clone()));
367 q.drain();
368 q.mark_failed(key, "decode");
369 assert!(!q.insert(d));
370 assert!(q.is_empty());
371 }
372
373 #[test]
374 fn terminal_target_from_one_source_does_not_suppress_another_source() {
375 let mut q = DiscoveryQueue::default();
376 let first = Discovery::block(0x1000, 0x900).from_block_addr(0x910);
377 let second = Discovery::block(0x1000, 0x900).from_block_addr(0x920);
378 let first_key = first.key();
379
380 assert!(q.insert(first));
381 q.drain();
382 q.mark_lifted(first_key);
383
384 assert!(q.insert(second));
385 assert_eq!(q.pending_len(), 1);
386 }
387
388 #[test]
389 fn drain_empties_pending_but_keeps_state() {
390 let mut q = DiscoveryQueue::default();
391 let d = Discovery::function(0x10);
392 let key = d.key();
393 q.insert(d);
394 let drained = q.drain();
395 assert_eq!(drained.len(), 1);
396 assert!(q.is_empty());
397 assert_eq!(q.state(&key), Some(&DiscoveryState::Pending));
398 }
399}