Skip to main content

iptr_edge_analyzer/
lib.rs

1#![doc = include_str!("../README.md")]
2#![deny(missing_docs)]
3#![cfg_attr(docsrs, feature(doc_cfg))]
4
5mod control_flow_cache;
6pub mod control_flow_handler;
7mod diagnose;
8pub mod error;
9pub mod memory_reader;
10mod static_analyzer;
11mod tnt_buffer;
12
13use std::num::NonZero;
14
15use iptr_decoder::{DecoderContext, HandlePacket, IpReconstructionPattern};
16
17#[cfg(feature = "cache")]
18use crate::control_flow_cache::ControlFlowCacheManager;
19pub use crate::{
20    control_flow_handler::{ControlFlowTransitionKind, HandleControlFlow},
21    diagnose::DiagnosticInformation,
22    memory_reader::ReadMemory,
23};
24use crate::{
25    error::{AnalyzerError, AnalyzerResult},
26    static_analyzer::StaticControlFlowAnalyzer,
27    tnt_buffer::TntBufferManager,
28};
29
30/// TNT bits processing status
31#[derive(Clone, Copy, Debug)]
32enum TntProceed {
33    /// During the process of current TNT bits, there
34    /// is no deferred TIP detected
35    Continue,
36    /// During the process of current TNT bits, a deferred
37    /// TIP is detected.
38    Break {
39        /// Before this deferred TIP, there are already this number
40        /// of TNT bits processed.
41        processed_bit_count: u32,
42    },
43}
44
45/// Status for determining the semantic of next TIP packet
46#[derive(Clone, Copy, Debug)]
47enum PreTipStatus {
48    /// There is nothing related to the next TIP packet, or
49    /// the status is not yet determined
50    ///
51    /// For example, after the last TNT bit, the next CFG
52    /// node is still a direct branch. In this case, no TIP packet
53    /// status is forced.
54    Normal,
55    /// The next CFG node is an indirect transition
56    PendingIndirect,
57    /// There is a FUP packet before this packet. So there must be
58    /// a TIP or TIP.PGD packet.
59    PendingFup,
60    /// There is an OVF packet before this packet. So there must be
61    /// a FUP, TIP or TIP.PGE packet.
62    PendingOvf,
63}
64
65/// An edge analyzer that implements [`HandlePacket`] trait.
66///
67/// The analyzer will trace the control flow during the Intel PT packets, and invoke
68/// corresponding callbacks in the given control flow handler that implements
69/// [`HandleControlFlow`].
70pub struct EdgeAnalyzer<H: HandleControlFlow, R: ReadMemory> {
71    /// IP-reconstruction-specific field.
72    ///
73    /// This is not always be the last IP in the packet. It has
74    /// special semantic according to Intel. Do not use thie field
75    /// until you know what you are doing.
76    last_ip: u64,
77    /// Address of previous basic block
78    ///
79    /// Instruction address will never be zero
80    ///
81    /// # Important Notes
82    ///
83    /// This field is only accessed in the handler methods in [`HandlePacket`].
84    /// For performance consideration, this field will never be updated during
85    /// internal parsing methods such as [`handle_tnt_buffer32`][Self::handle_tnt_buffer32].
86    /// As a result, you should never read this field in those methods.
87    last_bb: Option<NonZero<u64>>,
88    /// Status of the next TIP packet.
89    pre_tip_status: PreTipStatus,
90    /// Buffering the TNT bits for better cache.
91    tnt_buffer_manager: TntBufferManager,
92    /// Caches used to speed up TNT bits resolution without querying the CFG.
93    #[cfg(feature = "cache")]
94    cache_manager: ControlFlowCacheManager<Option<H::CachedKey>>,
95    /// CFG node maintainer
96    static_analyzer: StaticControlFlowAnalyzer,
97    /// Diagnose-related metrics
98    #[cfg(all(feature = "cache", feature = "more_diagnose"))]
99    cache_trailing_bits_hit_count: usize,
100    /// Diagnose-related metrics
101    #[cfg(all(feature = "cache", feature = "more_diagnose"))]
102    cache_8bit_hit_count: usize,
103    /// Diagnose-related metrics
104    #[cfg(all(feature = "cache", feature = "more_diagnose"))]
105    cache_32bit_hit_count: usize,
106    /// Diagnose-related metrics
107    #[cfg(all(feature = "cache", feature = "more_diagnose"))]
108    cache_missed_bit_count: usize,
109    /// Passed control flow handler
110    handler: H,
111    /// Passed memory reader
112    reader: R,
113}
114
115impl<H: HandleControlFlow, R: ReadMemory> EdgeAnalyzer<H, R> {
116    /// Create a new edge analyzer
117    #[must_use]
118    pub fn new(handler: H, reader: R) -> Self {
119        Self {
120            last_ip: 0,
121            last_bb: None,
122            pre_tip_status: PreTipStatus::Normal,
123            tnt_buffer_manager: TntBufferManager::new(),
124            #[cfg(feature = "cache")]
125            cache_manager: ControlFlowCacheManager::new(),
126            static_analyzer: StaticControlFlowAnalyzer::new(),
127            #[cfg(all(feature = "cache", feature = "more_diagnose"))]
128            cache_32bit_hit_count: 0,
129            #[cfg(all(feature = "cache", feature = "more_diagnose"))]
130            cache_8bit_hit_count: 0,
131            #[cfg(all(feature = "cache", feature = "more_diagnose"))]
132            cache_trailing_bits_hit_count: 0,
133            #[cfg(all(feature = "cache", feature = "more_diagnose"))]
134            cache_missed_bit_count: 0,
135            handler,
136            reader,
137        }
138    }
139
140    /// Consume the total structure and return the ownership of handler and reader
141    pub fn into_handler_and_reader(self) -> (H, R) {
142        (self.handler, self.reader)
143    }
144
145    /// Get shared reference to control flow handler
146    pub fn handler(&self) -> &H {
147        &self.handler
148    }
149
150    /// Get shared reference to memory reader
151    pub fn reader(&self) -> &R {
152        &self.reader
153    }
154
155    /// Perform IP reconstruction and update the `last_ip` field,
156    /// returns the full-width IP address
157    fn reconstruct_ip_and_update_last(
158        &mut self,
159        ip_reconstruction_pattern: IpReconstructionPattern,
160    ) -> Option<u64> {
161        if !iptr_decoder::utils::reconstruct_ip_and_update_last(
162            &mut self.last_ip,
163            ip_reconstruction_pattern,
164        ) {
165            return None;
166        }
167
168        Some(self.last_ip)
169    }
170
171    /// Process the given TNT bit, querying the CFG graph without
172    /// using any cache.
173    ///
174    /// This function will return a tuple `(cached_key, tnt_proceed)` on success.
175    /// The return value is similar to [`handle_tnt_buffer8`][Self::handle_tnt_buffer8].
176    ///
177    /// Note that this function does not detect infinite loop
178    #[expect(
179        clippy::enum_glob_use,
180        clippy::items_after_statements,
181        clippy::needless_continue
182    )]
183    fn process_tnt_bit_without_querying_cache(
184        &mut self,
185        context: &DecoderContext,
186        last_bb_ref: &mut u64,
187        is_taken: bool,
188    ) -> AnalyzerResult<TntProceed, H, R> {
189        #[cfg(all(feature = "cache", feature = "more_diagnose"))]
190        {
191            self.cache_missed_bit_count += 1;
192        }
193        let mut last_bb = *last_bb_ref;
194        let mut tnt_bit_processed = false;
195        let tnt_proceed;
196        'cfg_traverse: loop {
197            let cfg_node =
198                self.static_analyzer
199                    .resolve(&mut self.reader, context.tracee_mode(), last_bb)?;
200            let terminator = cfg_node.terminator;
201            use static_analyzer::CfgTerminator::*;
202            match terminator {
203                Branch { r#true, r#false } => {
204                    if tnt_bit_processed {
205                        tnt_proceed = TntProceed::Continue;
206                        break 'cfg_traverse;
207                    }
208                    let r#false = r#false as u64 | (r#true & 0xFFFF_FFFF_0000_0000);
209                    last_bb = if is_taken { r#true } else { r#false };
210                    self.handler
211                        .on_new_block(last_bb, ControlFlowTransitionKind::ConditionalBranch, true)
212                        .map_err(AnalyzerError::ControlFlowHandler)?;
213                    tnt_bit_processed = true;
214                    // Continue to eat all direct goto and direct call (useful for last bit before TIP)
215                    continue 'cfg_traverse;
216                }
217                DirectGoto { target } => {
218                    last_bb = target;
219                    self.handler
220                        .on_new_block(last_bb, ControlFlowTransitionKind::DirectJump, true)
221                        .map_err(AnalyzerError::ControlFlowHandler)?;
222                    continue 'cfg_traverse;
223                }
224                DirectCall { target } => {
225                    last_bb = target;
226                    self.handler
227                        .on_new_block(last_bb, ControlFlowTransitionKind::DirectCall, true)
228                        .map_err(AnalyzerError::ControlFlowHandler)?;
229                    continue 'cfg_traverse;
230                }
231                IndirectGoto
232                | IndirectCall
233                | FarTransfers {
234                    next_instruction: _,
235                } => {
236                    if tnt_bit_processed {
237                        tnt_proceed = TntProceed::Continue;
238                        break 'cfg_traverse;
239                    }
240                    // Wait for deferred TIP
241                    tnt_proceed = TntProceed::Break {
242                        processed_bit_count: 0,
243                    };
244                    break 'cfg_traverse;
245                }
246                NearRet => {
247                    if tnt_bit_processed {
248                        tnt_proceed = TntProceed::Continue;
249                        break 'cfg_traverse;
250                    }
251                    if !is_taken {
252                        // If return is not compressed, then an immediate TIP packet will be generated.
253                        // If return is compressed, then a taken bit will be generated
254                        return Err(AnalyzerError::InvalidPacket);
255                    }
256                    return Err(AnalyzerError::UnsupportedReturnCompression);
257                }
258            }
259        }
260        *last_bb_ref = last_bb;
261
262        Ok(tnt_proceed)
263    }
264
265    /// Process all remaining TNT bits inside tnt buffer manager
266    fn process_all_pending_tnts(&mut self, context: &DecoderContext) -> AnalyzerResult<(), H, R> {
267        let Some(last_bb) = self.last_bb else {
268            return Ok(());
269        };
270        // There are already some valid TNT packets here since
271        // last_bb is not uninitialized
272        let mut last_bb = last_bb.get();
273        // Clear the pending tnt buffers.
274        let tnt_buffer = self.tnt_buffer_manager.take();
275        let res = self.handle_maybe_full_tnt_buffer(context, &mut last_bb, tnt_buffer);
276        self.last_bb = NonZero::new(last_bb);
277        res
278    }
279
280    /// Handle TIP or TIP.PGD since TIP.PGD can replace TIP packets if
281    /// the destination goes out of ranges.
282    #[expect(clippy::redundant_else)]
283    fn handle_tip_or_tip_pgd_packet(
284        &mut self,
285        context: &DecoderContext,
286        ip_reconstruction_pattern: IpReconstructionPattern,
287        is_pgd: bool,
288    ) -> AnalyzerResult<(), H, R> {
289        let Some(new_last_bb) = self.reconstruct_ip_and_update_last(ip_reconstruction_pattern)
290        else {
291            // Out-of-context IP
292            if is_pgd {
293                // SYSCALL into kernel codes...
294                self.pre_tip_status = PreTipStatus::Normal;
295                return Ok(());
296            } else {
297                // Single TIP packet emit a out-of-context IP?
298                return Err(AnalyzerError::InvalidPacket);
299            }
300        };
301        // If pgd goes out of context, we cannot determin pre tip status since the
302        // memory reader may also miss page. So we should put the ip reconstruction first.
303
304        // For FUP, it flushes the CPU's internal TNT buffer, and thus we should process all
305        // pending TNTs, otherwise they would just be lost.
306        self.process_all_pending_tnts(context)?;
307        self.last_bb = NonZero::new(new_last_bb);
308        match self.pre_tip_status {
309            PreTipStatus::Normal | PreTipStatus::PendingIndirect => {
310                self.handler
311                    .on_new_block(new_last_bb, ControlFlowTransitionKind::Indirect, false)
312                    .map_err(AnalyzerError::ControlFlowHandler)?;
313                self.pre_tip_status = PreTipStatus::Normal;
314            }
315            PreTipStatus::PendingFup => {
316                self.handler
317                    .on_new_block(new_last_bb, ControlFlowTransitionKind::NewBlock, false)
318                    .map_err(AnalyzerError::ControlFlowHandler)?;
319                self.pre_tip_status = PreTipStatus::Normal;
320                self.tnt_buffer_manager.clear();
321                return Ok(());
322            }
323            PreTipStatus::PendingOvf => {
324                // OVF should be followed by FUP or TIP.PGE
325                return Err(AnalyzerError::InvalidPacket);
326            }
327        }
328
329        Ok(())
330    }
331}
332
333impl<H, R> HandlePacket for EdgeAnalyzer<H, R>
334where
335    H: HandleControlFlow,
336    R: ReadMemory,
337    AnalyzerError<H, R>: std::error::Error,
338{
339    type Error = AnalyzerError<H, R>;
340
341    fn at_decode_begin(&mut self) -> Result<(), Self::Error> {
342        self.last_ip = 0;
343        self.last_bb = None;
344        self.pre_tip_status = PreTipStatus::Normal;
345        self.tnt_buffer_manager.clear();
346        self.handler
347            .at_decode_begin()
348            .map_err(AnalyzerError::ControlFlowHandler)?;
349        self.reader
350            .at_decode_begin()
351            .map_err(AnalyzerError::MemoryReader)?;
352        #[cfg(all(feature = "cache", feature = "more_diagnose"))]
353        {
354            self.cache_32bit_hit_count = 0;
355            self.cache_8bit_hit_count = 0;
356            self.cache_trailing_bits_hit_count = 0;
357            self.cache_missed_bit_count = 0;
358        }
359
360        Ok(())
361    }
362
363    fn on_short_tnt_packet(
364        &mut self,
365        context: &DecoderContext,
366        packet_byte: NonZero<u8>,
367        highest_bit: u32,
368    ) -> Result<(), Self::Error> {
369        if highest_bit == 0 {
370            // No TNT bits
371            return Ok(());
372        }
373        let Some(last_bb) = self.last_bb else {
374            // No previous TIP given. Silently ignore those TNTs
375            return Ok(());
376        };
377        let mut last_bb = last_bb.get();
378        if let Some(full_tnt_buffer) = self.tnt_buffer_manager.extend_with_short_tnt(packet_byte) {
379            let res = self.handle_full_tnt_buffer(context, &mut last_bb, full_tnt_buffer);
380            self.last_bb = NonZero::new(last_bb);
381            res?;
382        }
383
384        Ok(())
385    }
386
387    fn on_long_tnt_packet(
388        &mut self,
389        context: &DecoderContext,
390        packet_bytes: NonZero<u64>,
391        highest_bit: u32,
392    ) -> Result<(), Self::Error> {
393        if highest_bit == u32::MAX {
394            // No TNT bits
395            return Ok(());
396        }
397        let Some(last_bb) = self.last_bb else {
398            // No previous TIP given. Silently ignore those TNTs
399            return Ok(());
400        };
401        let mut last_bb = last_bb.get();
402        if let Some(full_tnt_buffer) = self.tnt_buffer_manager.extend_with_long_tnt(packet_bytes) {
403            let res = self.handle_full_tnt_buffer(context, &mut last_bb, full_tnt_buffer);
404            self.last_bb = NonZero::new(last_bb);
405            res?;
406        }
407
408        Ok(())
409    }
410
411    fn on_tip_packet(
412        &mut self,
413        context: &DecoderContext,
414        ip_reconstruction_pattern: IpReconstructionPattern,
415    ) -> Result<(), Self::Error> {
416        self.handle_tip_or_tip_pgd_packet(context, ip_reconstruction_pattern, false)?;
417        Ok(())
418    }
419
420    fn on_tip_pgd_packet(
421        &mut self,
422        context: &DecoderContext,
423        ip_reconstruction_pattern: IpReconstructionPattern,
424    ) -> Result<(), Self::Error> {
425        self.handle_tip_or_tip_pgd_packet(context, ip_reconstruction_pattern, true)?;
426
427        self.last_bb = None;
428        self.tnt_buffer_manager.clear();
429        Ok(())
430    }
431
432    fn on_tip_pge_packet(
433        &mut self,
434        _context: &DecoderContext,
435        ip_reconstruction_pattern: IpReconstructionPattern,
436    ) -> Result<(), Self::Error> {
437        if matches!(self.pre_tip_status, PreTipStatus::PendingOvf) {
438            let Some(last_bb) = self.reconstruct_ip_and_update_last(ip_reconstruction_pattern)
439            else {
440                // Any IP compression that follows the OVF is guaranteed to
441                // use as a reference `LastIP` the IP payload of an IP packet
442                return Err(AnalyzerError::InvalidPacket);
443            };
444            self.last_bb = NonZero::new(last_bb);
445            self.pre_tip_status = PreTipStatus::Normal;
446            self.tnt_buffer_manager.clear();
447            self.handler
448                .on_new_block(last_bb, ControlFlowTransitionKind::NewBlock, false)
449                .map_err(AnalyzerError::ControlFlowHandler)?;
450            return Ok(());
451        }
452        if let Some(last_bb) = self.reconstruct_ip_and_update_last(ip_reconstruction_pattern) {
453            self.last_bb = NonZero::new(last_bb);
454            self.handler
455                .on_new_block(last_bb, ControlFlowTransitionKind::NewBlock, false)
456                .map_err(AnalyzerError::ControlFlowHandler)?;
457        }
458        self.pre_tip_status = PreTipStatus::Normal;
459        self.tnt_buffer_manager.clear();
460
461        Ok(())
462    }
463
464    fn on_fup_packet(
465        &mut self,
466        _context: &DecoderContext,
467        ip_reconstruction_pattern: IpReconstructionPattern,
468    ) -> Result<(), Self::Error> {
469        if matches!(self.pre_tip_status, PreTipStatus::PendingOvf) {
470            self.pre_tip_status = PreTipStatus::Normal;
471            let Some(last_bb) = self.reconstruct_ip_and_update_last(ip_reconstruction_pattern)
472            else {
473                // Any IP compression that follows the OVF is guaranteed to
474                // use as a reference `LastIP` the IP payload of an IP packet
475                return Err(AnalyzerError::InvalidPacket);
476            };
477            self.last_bb = NonZero::new(last_bb);
478            self.tnt_buffer_manager.clear();
479
480            return Ok(());
481        }
482        self.reconstruct_ip_and_update_last(ip_reconstruction_pattern);
483        self.pre_tip_status = PreTipStatus::PendingFup;
484
485        Ok(())
486    }
487
488    fn on_ovf_packet(&mut self, _context: &DecoderContext) -> Result<(), Self::Error> {
489        self.pre_tip_status = PreTipStatus::PendingOvf;
490        Ok(())
491    }
492
493    fn on_psb_packet(&mut self, _context: &DecoderContext) -> Result<(), Self::Error> {
494        self.last_bb = None;
495        self.last_ip = 0;
496        self.pre_tip_status = PreTipStatus::Normal;
497        self.tnt_buffer_manager.clear();
498
499        Ok(())
500    }
501}