From eec7ad9e2459c028976bbc3531e5ada31f268337 Mon Sep 17 00:00:00 2001 From: Edd Barrett Date: Mon, 31 Oct 2022 14:36:17 +0000 Subject: [PATCH] Start work on our own Intel PT decoder. This is the first step: a packet parser, i.e. taking the raw bytes given to us by the PT chip and parsing them into Rust structs representing PT packets. I arrived at the approach you see in this change after trying several other, less-satisfying, ways of parsing binary packets (with bitwise field granularity). Initially I was parsing packets by hand, which was very fiddly indeed and was what prompted me to look for tools to help. The `packed_struct` crate worked, but lead to some quite verbose code (and I wrote lots of macros to try and work around that). Still unsatisfied, I found `binrw`, which was almost perfect, only let down by the unmaintained and bit-rotted `modular_bitfield` crate, which is required for bitwise parsing (see https://github.com/Robbepop/modular-bitfield/issues/65). This led me to a solution using `deku`, which I've been very happy with: https://github.com/sharksforarms/deku Not all kinds of packets are implemented: only enough to parse a tiny little trace to completion. We can add new packets on-demand as we see them crop up in the wild. --- Cargo.toml | 1 + build.rs | 4 + src/collect/mod.rs | 2 +- src/decode/mod.rs | 27 +- src/decode/ykpt/mod.rs | 61 ++++ src/decode/ykpt/packet_parser/mod.rs | 295 +++++++++++++++++++ src/decode/ykpt/packet_parser/packets.rs | 356 +++++++++++++++++++++++ src/errors.rs | 4 + 8 files changed, 747 insertions(+), 3 deletions(-) create mode 100644 src/decode/ykpt/mod.rs create mode 100644 src/decode/ykpt/packet_parser/mod.rs create mode 100644 src/decode/ykpt/packet_parser/packets.rs diff --git a/Cargo.toml b/Cargo.toml index 221c3d5..fb7d89a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,6 +11,7 @@ tempfile = "3.1.0" phdrs = { git = "https://github.com/softdevteam/phdrs" } strum = { version = "0.24.1", features = ["derive", "strum_macros"] } strum_macros = "0.24.3" +deku = "0.14.1" [build-dependencies] cc = "1.0.62" diff --git a/build.rs b/build.rs index 4a0a777..3367a37 100644 --- a/build.rs +++ b/build.rs @@ -144,6 +144,10 @@ fn main() { } println!("cargo:rustc-link-lib=static=ipt"); } + + #[cfg(target_arch = "x86_64")] + println!("cargo:rustc-cfg=decoder_ykpt"); + c_build.include("src/util"); c_build.include("src"); // to find `hwtracer_private.h`. c_build.compile("hwtracer_c"); diff --git a/src/collect/mod.rs b/src/collect/mod.rs index 85e1fad..9a95f7e 100644 --- a/src/collect/mod.rs +++ b/src/collect/mod.rs @@ -232,7 +232,7 @@ impl TraceCollectorBuilder { _pt_conf, )?))); #[cfg(not(collector_perf))] - unreachable!(); + return Err(HWTracerError::CollectorUnavailable(self.kind)); } } } diff --git a/src/decode/mod.rs b/src/decode/mod.rs index c512e64..628a009 100644 --- a/src/decode/mod.rs +++ b/src/decode/mod.rs @@ -9,9 +9,15 @@ pub(crate) mod libipt; #[cfg(decoder_libipt)] use libipt::LibIPTTraceDecoder; +#[cfg(decoder_ykpt)] +mod ykpt; +#[cfg(decoder_ykpt)] +use ykpt::YkPTTraceDecoder; + #[derive(Clone, Copy, Debug, EnumIter)] pub enum TraceDecoderKind { LibIPT, + YkPT, } impl TraceDecoderKind { @@ -29,10 +35,16 @@ impl TraceDecoderKind { fn match_platform(&self) -> Result<(), HWTracerError> { match self { Self::LibIPT => { + #[cfg(decoder_libipt)] + return Ok(()); #[cfg(not(decoder_libipt))] return Err(HWTracerError::DecoderUnavailable(Self::LibIPT)); - #[cfg(decoder_libipt)] + } + Self::YkPT => { + #[cfg(decoder_ykpt)] return Ok(()); + #[cfg(not(decoder_ykpt))] + return Err(HWTracerError::DecoderUnavailable(Self::YkPT)); } } } @@ -76,7 +88,18 @@ impl TraceDecoderBuilder { pub fn build(self) -> Result, HWTracerError> { self.kind.match_platform()?; match self.kind { - TraceDecoderKind::LibIPT => Ok(Box::new(LibIPTTraceDecoder::new())), + TraceDecoderKind::LibIPT => { + #[cfg(decoder_libipt)] + return Ok(Box::new(LibIPTTraceDecoder::new())); + #[cfg(not(decoder_libipt))] + return Err(HWTracerError::DecoderUnavailable(self.kind)); + } + TraceDecoderKind::YkPT => { + #[cfg(decoder_ykpt)] + return Ok(Box::new(YkPTTraceDecoder::new())); + #[cfg(not(decoder_ykpt))] + return Err(HWTracerError::DecoderUnavailable(self.kind)); + } } } } diff --git a/src/decode/ykpt/mod.rs b/src/decode/ykpt/mod.rs new file mode 100644 index 0000000..f4aeff2 --- /dev/null +++ b/src/decode/ykpt/mod.rs @@ -0,0 +1,61 @@ +//! The Yk PT trace decoder. + +use crate::{decode::TraceDecoder, errors::HWTracerError, Block, Trace}; + +mod packet_parser; +use packet_parser::PacketParser; + +pub(crate) struct YkPTTraceDecoder {} + +impl TraceDecoder for YkPTTraceDecoder { + fn new() -> Self { + Self {} + } + + fn iter_blocks<'t>( + &'t self, + trace: &'t dyn Trace, + ) -> Box> + '_> { + let itr = YkPTBlockIterator { + errored: false, + parser: PacketParser::new(trace.bytes()), + }; + Box::new(itr) + } +} + +/// Iterate over the blocks of an Intel PT trace using the fast Yk PT decoder. +struct YkPTBlockIterator<'t> { + /// Set to true when an error has occured. + errored: bool, + /// PT packet iterator. + parser: PacketParser<'t>, +} + +impl<'t> Iterator for YkPTBlockIterator<'t> { + type Item = Result; + + fn next(&mut self) -> Option { + if !self.errored { + // FIXME: For now this is dummy code to prevent dead-code warnings. Later block binding + // logic will go here. + self.parser.next().unwrap().unwrap(); + } + None + } +} + +#[cfg(test)] +mod tests { + use crate::{ + collect::TraceCollectorBuilder, + decode::{test_helpers, TraceDecoderKind}, + }; + + #[ignore] // FIXME + #[test] + fn ten_times_as_many_blocks() { + let tc = TraceCollectorBuilder::new().build().unwrap(); + test_helpers::ten_times_as_many_blocks(tc, TraceDecoderKind::YkPT); + } +} diff --git a/src/decode/ykpt/packet_parser/mod.rs b/src/decode/ykpt/packet_parser/mod.rs new file mode 100644 index 0000000..e9b879e --- /dev/null +++ b/src/decode/ykpt/packet_parser/mod.rs @@ -0,0 +1,295 @@ +//! A packet parser for the Yk PT trace decoder. + +use crate::errors::HWTracerError; +use deku::{bitvec::BitSlice, DekuRead}; +use std::iter::Iterator; + +mod packets; +use packets::*; + +#[derive(Clone, Copy, Debug)] +enum PacketParserState { + /// Initial state, waiting for a PSB packet. + Init, + /// The "normal" decoding state. + Normal, + /// We are decoding a PSB+ sequence. + PSBPlus, +} + +impl PacketParserState { + /// Returns the kinds of packet that are valid for the state. + fn valid_packets(&self) -> &'static [PacketKind] { + // Note that the parser will attempt to match packet kinds in the order that they appear in + // the returned slice. For best performance, the returned slice should be sorted, most + // frequently expected packet kinds first. + // + // OPT: The order below is a rough guess based on what limited traces I've seen. Benchmark + // and optimise. + match self { + Self::Init => &[PacketKind::PSB], + Self::Normal => &[ + PacketKind::ShortTNT, + PacketKind::PAD, + PacketKind::FUP, + PacketKind::TIP, + PacketKind::CYC, + PacketKind::LongTNT, + PacketKind::PSB, + PacketKind::MODE, + PacketKind::TIPPGE, + PacketKind::TIPPGD, + ], + Self::PSBPlus => &[PacketKind::CBR, PacketKind::PSBEND], + } + } + + /// Check if the parser needs to transition to a new state as a result of parsing a certain + /// kind of packet. + fn transition(&mut self, pkt_kind: PacketKind) { + let new = match (*self, pkt_kind) { + (Self::Init, PacketKind::PSB) => Self::PSBPlus, + (Self::Normal, PacketKind::PSB) => Self::PSBPlus, + (Self::PSBPlus, PacketKind::PSBEND) => Self::Normal, + _ => return, // No state transition. + }; + *self = new; + } +} + +pub(super) struct PacketParser<'t> { + /// The raw bytes of the PT trace we are iterating over. + bytes: &'t [u8], + /// The parser operates as a state machine. This field keeps track of which state we are in. + state: PacketParserState, + /// The most recent Target IP (TIP) value that we've seen. This is needed because updated TIP + /// values are sometimes compressed using bits from the previous TIP value. + prev_tip: usize, +} + +/// Attempt to read the packet of type `$packet` using deku. On success wrap the packet up into the +/// corresponding discriminant of `Packet`. +macro_rules! read_to_packet { + ($packet: ty, $bits: expr, $discr: expr) => { + <$packet>::read($bits, ()).and_then(|(r, p)| Ok((r, $discr(p)))) + }; +} + +/// Same as `read_to_packet!`, but with extra logic for dealing with packets which encode a TIP. +macro_rules! read_to_packet_tip { + ($packet: ty, $bits: expr, $discr: expr, $prev_tip: expr) => { + <$packet>::read($bits, ()).and_then(|(r, p)| { + let ret = if p.needs_prev_tip() { + Ok((r, $discr(p, Some($prev_tip)))) + } else { + Ok((r, $discr(p, None))) + }; + ret + }) + }; +} + +impl<'t> PacketParser<'t> { + pub(super) fn new(bytes: &'t [u8]) -> Self { + Self { + bytes, + state: PacketParserState::Init, + prev_tip: 0, + } + } + + /// Attempt to parse a packet of the specified `PacketKind`. + fn parse_kind(&mut self, kind: PacketKind) -> Option { + let bits = BitSlice::from_slice(self.bytes).ok()?; + let parse_res = match kind { + PacketKind::PSB => { + read_to_packet!(PSBPacket, bits, Packet::PSB) + } + PacketKind::CBR => read_to_packet!(CBRPacket, bits, Packet::CBR), + PacketKind::PSBEND => read_to_packet!(PSBENDPacket, bits, Packet::PSBEND), + PacketKind::PAD => read_to_packet!(PADPacket, bits, Packet::PAD), + PacketKind::MODE => read_to_packet!(MODEPacket, bits, Packet::MODE), + PacketKind::TIPPGE => { + read_to_packet_tip!(TIPPGEPacket, bits, Packet::TIPPGE, self.prev_tip) + } + PacketKind::TIPPGD => { + read_to_packet_tip!(TIPPGDPacket, bits, Packet::TIPPGD, self.prev_tip) + } + PacketKind::ShortTNT => read_to_packet!(ShortTNTPacket, bits, Packet::ShortTNT), + PacketKind::LongTNT => read_to_packet!(LongTNTPacket, bits, Packet::LongTNT), + PacketKind::TIP => read_to_packet_tip!(TIPPacket, bits, Packet::TIP, self.prev_tip), + PacketKind::FUP => read_to_packet_tip!(FUPPacket, bits, Packet::FUP, self.prev_tip), + PacketKind::CYC => read_to_packet!(CYCPacket, bits, Packet::CYC), + }; + if let Ok((remain, pkt)) = parse_res { + self.bytes = remain.as_raw_slice(); + Some(pkt) + } else { + None + } + } + + /// Attempt to parse a packet for the current parser state. + fn parse_state(&mut self) -> Result { + for kind in self.state.valid_packets() { + if let Some(pkt) = self.parse_kind(*kind) { + if *kind == PacketKind::PSBEND { + self.state = PacketParserState::Normal; + } + return Ok(pkt); + } + } + Err(HWTracerError::TraceParseError(format!( + "In state {:?}, failed to parse packet: {}", + self.state, + self.byte_stream_str(8, ", ") + ))) + } + + /// Returns a string showing a binary formatted peek at the next `nbytes` bytes of + /// `self.bytes`. Bytes in the output are separated by `sep`. + /// + /// This is used to format error messages, but is also useful when debugging. + fn byte_stream_str(&self, nbytes: usize, sep: &str) -> String { + use std::cmp::min; + let nbytes = min(nbytes, self.bytes.len()); + let mut vals = Vec::new(); + for i in 0..nbytes { + vals.push(format!("{:08b}", self.bytes[i])); + } + + if self.bytes.len() > nbytes { + vals.push("...".to_owned()); + } + + format!("{}", vals.join(sep)) + } + + /// Attempt to parse a packet. + fn parse_packet(&mut self) -> Result { + // Attempt to parse a packet. + let pkt = self.parse_state()?; + + // If the packet contains an updated TIP, then cache it. + if let Some(tip) = pkt.target_ip() { + self.prev_tip = tip; + } + + // See if the packet we just parsed triggers a state transition. + self.state.transition(pkt.kind()); + + Ok(pkt) + } +} + +impl<'t> Iterator for PacketParser<'t> { + type Item = Result; + + fn next(&mut self) -> Option { + if !self.bytes.is_empty() { + Some(self.parse_packet()) + } else { + None + } + } +} + +#[cfg(test)] +mod tests { + use super::{packets::*, PacketParser}; + use crate::{ + collect::{test_helpers::trace_closure, TraceCollectorBuilder}, + test_helpers::work_loop, + }; + + /// Parse the packets of a small trace, checking the basic structure of the decoded trace. + #[test] + fn parse_small_trace() { + let tc = TraceCollectorBuilder::new().build().unwrap(); + let trace = trace_closure(&tc, || work_loop(3)); + + #[derive(Clone, Copy, Debug)] + enum TestState { + /// Start here. + Init, + /// Saw the start of the PSB+ sequence. + SawPSBPlusStart, + /// Saw the end of the PSB+ sequence. + SawPSBPlusEnd, + /// Saw the packet generation enable packet. + SawPacketGenEnable, + /// Saw a TNT packet. + SawTNT, + /// Saw the packet generation disable packet. + SawPacketGenDisable, + } + + let mut ts = TestState::Init; + for pkt in PacketParser::new(trace.bytes()) { + dbg!(&ts, &pkt); + ts = match (ts, pkt.unwrap().kind()) { + (TestState::Init, PacketKind::PSB) => TestState::SawPSBPlusStart, + (TestState::SawPSBPlusStart, PacketKind::PSBEND) => TestState::SawPSBPlusEnd, + (TestState::SawPSBPlusEnd, PacketKind::TIPPGE) => TestState::SawPacketGenEnable, + (TestState::SawPacketGenEnable, PacketKind::ShortTNT) + | (TestState::SawPacketGenEnable, PacketKind::LongTNT) => TestState::SawTNT, + (TestState::SawTNT, PacketKind::TIPPGD) => TestState::SawPacketGenDisable, + (ts, _) => ts, + }; + } + assert!(matches!(ts, TestState::SawPacketGenDisable)); + } + + /// Test target IP decompression when the `IPBytes = 0b000`. + #[test] + fn ipbytes_decompress_000() { + let ipbytes0 = IPBytes::new(0b000); + assert_eq!( + TargetIP::from_bits(0, 0).decompress(ipbytes0, Some(0xdeafcafedeadcafe)), + None + ); + } + + /// Test target IP decompression when the `IPBytes = 0b001`. + #[test] + fn ipbytes_decompress_001() { + let ipb = IPBytes::new(0b001); + assert_eq!( + TargetIP::from_bits(16, 0x000000000000cccc).decompress(ipb, Some(0xa1a2a3a4a5a69999)), + Some(0xa1a2a3a4a5a6cccc) + ); + } + + /// Test target IP decompression when the `IPBytes = 0b010`. + #[test] + fn ipbytes_decompress_010() { + let ipb = IPBytes::new(0b010); + assert_eq!( + TargetIP::from_bits(32, 0x00000000bbbbbbbb).decompress(ipb, Some(0xcccccccc99999999)), + Some(0xccccccccbbbbbbbb) + ); + } + + /// Test target IP decompression when the `IPBytes = 0b011`. + #[test] + fn ipbytes_decompress_011() { + let ipb = IPBytes::new(0b011); + + // Bit 47 zero-extend. + assert_eq!(TargetIP::from_bits(48, 0).decompress(ipb, None), Some(0)); + assert_eq!( + TargetIP::from_bits(48, 0x0000010203040506).decompress(ipb, None), + Some(0x0000010203040506) + ); + + // Bit 47 one-extend. + assert_eq!( + TargetIP::from_bits(48, 1 << 47).decompress(ipb, None), + Some(0xffff800000000000) + ); + assert_eq!( + TargetIP::from_bits(48, 0x0000887766554433).decompress(ipb, None), + Some(0xffff887766554433) + ); + } +} diff --git a/src/decode/ykpt/packet_parser/packets.rs b/src/decode/ykpt/packet_parser/packets.rs new file mode 100644 index 0000000..8f194da --- /dev/null +++ b/src/decode/ykpt/packet_parser/packets.rs @@ -0,0 +1,356 @@ +//! Intel PT packets and their constituents. + +use deku::prelude::*; +use std::convert::TryFrom; + +/// The `IPBytes` field common to all IP packets. +/// +/// This tells us what kind of compression was used for a `TargetIP`. +#[derive(Clone, Copy, Debug, DekuRead)] +pub(in crate::decode::ykpt) struct IPBytes { + #[deku(bits = "3")] + val: u8, +} + +impl IPBytes { + #[cfg(test)] + pub(in crate::decode::ykpt) fn new(val: u8) -> Self { + debug_assert!(val >> 3 == 0); + Self { val } + } + + /// Returns `true` if we need the previous TIP value to make sense of the new one. + pub(super) fn needs_prev_tip(&self) -> bool { + match self.val { + 0b001 | 0b010 | 0b100 => true, + _ => false, + } + } +} + +/// The `TargetIP` fields in packets which update the TIP. +/// +/// This is a variable-width field depending upon the value if `IPBytes` in the containing packet. +#[derive(Debug, DekuRead)] +#[deku(id = "ip_bytes_val", ctx = "ip_bytes_val: u8")] +pub(in crate::decode::ykpt) enum TargetIP { + #[deku(id = "0b000")] + OutOfContext, + #[deku(id = "0b001")] + Ip16(u16), + #[deku(id = "0b010")] + Ip32(u32), + #[deku(id_pat = "0b011 | 0b100")] + Ip48(#[deku(bits = "48")] u64), + #[deku(id = "0b110")] + Ip64(u64), +} + +impl TargetIP { + #[cfg(test)] + pub(in crate::decode::ykpt) fn from_bits(bits: u8, val: u64) -> Self { + match bits { + 0 => Self::OutOfContext, + 16 => Self::Ip16(u16::try_from(val).unwrap()), + 32 => Self::Ip32(u32::try_from(val).unwrap()), + 48 => Self::Ip48(val), + 64 => Self::Ip64(val), + _ => panic!(), + } + } + + /// Decompress a `TargetIP` and `IPBytes` pair into an instruction pointer address. + /// + /// Returns `None` if the target IP was "out of context". + pub(in crate::decode::ykpt) fn decompress( + &self, + ip_bytes: IPBytes, + prev_tip: Option, + ) -> Option { + let res = match ip_bytes.val { + 0b000 => { + debug_assert!(matches!(self, Self::OutOfContext)); + return None; + } + 0b001 => { + // The result is bytes 63..=16 from `prev_tip` and bytes 15..=0 from `ip`. + if let Self::Ip16(v) = self { + prev_tip.unwrap() & 0xffffffffffff0000 | usize::try_from(*v).unwrap() + } else { + unreachable!(); + } + } + 0b010 => { + // The result is bytes 63..=32 from `prev_tip` and bytes 31..=0 from `ip`. + if let Self::Ip32(v) = self { + prev_tip.unwrap() & 0xffffffff00000000 | usize::try_from(*v).unwrap() + } else { + unreachable!(); + } + } + 0b011 => { + // The result is bits 0..=47 from the IP, with the remaining high-order bits + // extended with the value of bit 47. + if let Self::Ip48(v) = self { + debug_assert!(v >> 48 == 0); + // Extract the value of bit 47. + let b47 = (v & (1 << 47)) >> 47; + // Copy the value of bit 47 across all 64 bits. + let all = u64::wrapping_sub(!b47 & 0x1, 1); + // Restore bits 47..=0 to arrive at the result. + usize::try_from(all & 0xffff000000000000 | v).unwrap() + } else { + unreachable!(); + } + } + 0b100 => todo!(), + 0b101 => unreachable!(), // reserved by Intel. + 0b110 => { + // Uncompressed IP. + if let Self::Ip64(v) = self { + usize::try_from(*v).unwrap() + } else { + unreachable!(); + } + } + 0b111 => unreachable!(), // reserved by Intel. + _ => todo!("IPBytes: {:03b}", ip_bytes.val), + }; + Some(usize::try_from(res).unwrap()) + } +} + +/// Packet Stream Boundary (PSB) packet. +#[derive(Debug, PartialEq, DekuRead)] +#[deku(magic = b"\x02\x82\x02\x82\x02\x82\x02\x82\x02\x82\x02\x82\x02\x82\x02\x82")] +pub(in crate::decode::ykpt) struct PSBPacket {} + +/// Core Bus Ratio (CBR) packet. +#[deku_derive(DekuRead)] +#[derive(Debug)] +#[deku(magic = b"\x02\x03")] +pub(in crate::decode::ykpt) struct CBRPacket { + #[deku(temp)] + unused: u16, +} + +/// End of PSB+ sequence (PSBEND) packet. +#[derive(Debug, DekuRead)] +#[deku(magic = b"\x02\x23")] +pub(in crate::decode::ykpt) struct PSBENDPacket {} + +/// Padding (PAD) packet. +#[derive(Debug, DekuRead)] +#[deku(magic = b"\x00")] +pub(in crate::decode::ykpt) struct PADPacket {} + +/// Mode (MODE.*) packet. +#[deku_derive(DekuRead)] +#[derive(Debug)] +#[deku(magic = b"\x99")] +pub(in crate::decode::ykpt) struct MODEPacket { + // If we ever need to actually interpret the data inside the `MODE.*` packets, it would be best + // to split this struct out into multiple structs and have deku parse the different mode kinds + // independently. + #[deku(temp)] + unused: u8, +} + +/// Packet Generation Enable (TIP.PGE) packet. +#[deku_derive(DekuRead)] +#[derive(Debug)] +pub(in crate::decode::ykpt) struct TIPPGEPacket { + ip_bytes: IPBytes, + #[deku(bits = "5", assert = "*magic & 0x1f == 0x11", temp)] + magic: u8, + #[deku(ctx = "ip_bytes.val")] + target_ip: TargetIP, +} + +impl TIPPGEPacket { + fn target_ip(&self, prev_tip: Option) -> Option { + self.target_ip.decompress(self.ip_bytes, prev_tip) + } + + pub(super) fn needs_prev_tip(&self) -> bool { + self.ip_bytes.needs_prev_tip() + } +} + +/// Short Taken/Not-Taken (TNT) packet. +#[deku_derive(DekuRead)] +#[derive(Debug)] +pub(in crate::decode::ykpt) struct ShortTNTPacket { + /// Bits encoding the branch decisions **and** a stop bit. + /// + /// The deku assertion here is subtle: we know that the `branches` field must contain a stop + /// bit terminating the field, but if the stop bit appears in place of the first branch, then + /// this is not a short TNT packet at all; it's a long TNT packet. + /// + /// FIXME: marked `temp` until we actually use the field. + #[deku(bits = "7", assert = "*branches != 0x1", temp)] + branches: u8, + #[deku(bits = "1", assert = "*magic == false", temp)] + magic: bool, +} + +/// Long Taken/Not-Taken (TNT) packet. +#[deku_derive(DekuRead)] +#[derive(Debug)] +#[deku(magic = b"\x02\xa3")] +pub(in crate::decode::ykpt) struct LongTNTPacket { + /// Bits encoding the branch decisions **and** a stop bit. + /// + /// FIXME: marked `temp` until we actually use the field. + #[deku(bits = "48", temp)] + branches: u64, +} + +/// Target IP (TIP) packet. +#[deku_derive(DekuRead)] +#[derive(Debug)] +pub(in crate::decode::ykpt) struct TIPPacket { + ip_bytes: IPBytes, + #[deku(bits = "5", assert = "*magic & 0x1f == 0x0d", temp)] + magic: u8, + #[deku(ctx = "ip_bytes.val")] + target_ip: TargetIP, +} + +impl TIPPacket { + fn target_ip(&self, prev_tip: Option) -> Option { + self.target_ip.decompress(self.ip_bytes, prev_tip) + } + + pub(super) fn needs_prev_tip(&self) -> bool { + self.ip_bytes.needs_prev_tip() + } +} + +/// Packet Generation Disable (TIP.PGD) packet. +#[deku_derive(DekuRead)] +#[derive(Debug)] +pub(in crate::decode::ykpt) struct TIPPGDPacket { + ip_bytes: IPBytes, + #[deku(bits = "5", assert = "*magic & 0x1f == 0x1", temp)] + magic: u8, + #[deku(ctx = "ip_bytes.val")] + target_ip: TargetIP, +} + +impl TIPPGDPacket { + fn target_ip(&self, prev_tip: Option) -> Option { + self.target_ip.decompress(self.ip_bytes, prev_tip) + } + + pub(super) fn needs_prev_tip(&self) -> bool { + self.ip_bytes.needs_prev_tip() + } +} + +/// Flow Update (FUP) packet. +#[deku_derive(DekuRead)] +#[derive(Debug)] +pub(in crate::decode::ykpt) struct FUPPacket { + ip_bytes: IPBytes, + #[deku(bits = "5", assert = "*magic & 0x1f == 0b11101", temp)] + magic: u8, + #[deku(ctx = "ip_bytes.val")] + target_ip: TargetIP, +} + +impl FUPPacket { + fn target_ip(&self, prev_tip: Option) -> Option { + self.target_ip.decompress(self.ip_bytes, prev_tip) + } + + pub(super) fn needs_prev_tip(&self) -> bool { + self.ip_bytes.needs_prev_tip() + } +} + +/// Cycle count (CYC) packet. +#[deku_derive(DekuRead)] +#[derive(Debug)] +pub(in crate::decode::ykpt) struct CYCPacket { + #[deku(bits = "5", temp)] + unused: u8, + #[deku(bits = "1", temp)] + exp: bool, + #[deku(bits = "2", assert = "*magic & 0x3 == 0b11", temp)] + magic: u8, + /// A CYC packet is variable length and has 0 or more "extended" bytes. + #[deku( + bits = 8, + cond = "*exp == true", + until = "|e: &u8| e & 0x01 != 0x01", + temp + )] + extended: Vec, +} + +#[derive(Clone, Copy, Debug, PartialEq)] +pub(super) enum PacketKind { + PSB, + CBR, + PSBEND, + PAD, + MODE, + TIPPGE, + TIPPGD, + ShortTNT, + LongTNT, + TIP, + FUP, + CYC, +} + +/// The top-level representation of an Intel Processor Trace packet. +/// +/// Variants with an `Option` may cache the previous TIP value (at the time the packet was +/// created). This may be needed to get the updated TIP value from the packet. +#[derive(Debug)] +pub(in crate::decode::ykpt) enum Packet { + PSB(PSBPacket), + CBR(CBRPacket), + PSBEND(PSBENDPacket), + PAD(PADPacket), + MODE(MODEPacket), + TIPPGE(TIPPGEPacket, Option), + TIPPGD(TIPPGDPacket, Option), + ShortTNT(ShortTNTPacket), + LongTNT(LongTNTPacket), + TIP(TIPPacket, Option), + FUP(FUPPacket, Option), + CYC(CYCPacket), +} + +impl Packet { + /// If the packet contains a TIP update, return the IP value. + pub(in crate::decode::ykpt) fn target_ip(&self) -> Option { + match self { + Self::TIPPGE(p, prev_tip) => p.target_ip(*prev_tip), + Self::TIPPGD(p, prev_tip) => p.target_ip(*prev_tip), + Self::TIP(p, prev_tip) => p.target_ip(*prev_tip), + Self::FUP(p, prev_tip) => p.target_ip(*prev_tip), + _ => None, + } + } + + pub(super) fn kind(&self) -> PacketKind { + match self { + Self::PSB(_) => PacketKind::PSB, + Self::CBR(_) => PacketKind::CBR, + Self::PSBEND(_) => PacketKind::PSBEND, + Self::PAD(_) => PacketKind::PAD, + Self::MODE(_) => PacketKind::MODE, + Self::TIPPGE(..) => PacketKind::TIPPGE, + Self::TIPPGD(..) => PacketKind::TIPPGD, + Self::ShortTNT(_) => PacketKind::ShortTNT, + Self::LongTNT(_) => PacketKind::LongTNT, + Self::TIP(..) => PacketKind::TIP, + Self::FUP(..) => PacketKind::FUP, + Self::CYC(_) => PacketKind::CYC, + } + } +} diff --git a/src/errors.rs b/src/errors.rs index fc8f527..10b35e5 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -28,6 +28,8 @@ pub enum HWTracerError { BadConfig(String), /// An unknown error. Used sparingly for C code which doesn't set errno. Unknown, + /// Failed to decode trace. + TraceParseError(String), /// Any other error. Custom(Box), } @@ -55,6 +57,7 @@ impl Display for HWTracerError { HWTracerError::AlreadyStopped => write!(f, "Can't stop an inactice collector"), HWTracerError::BadConfig(ref s) => write!(f, "{}", s), HWTracerError::Custom(ref bx) => write!(f, "{}", bx), + HWTracerError::TraceParseError(ref s) => write!(f, "failed to parse trace: {}", s), HWTracerError::Unknown => write!(f, "Unknown error"), } } @@ -77,6 +80,7 @@ impl Error for HWTracerError { HWTracerError::BadConfig(_) => None, HWTracerError::Errno(_) => None, HWTracerError::Custom(ref bx) => Some(bx.as_ref()), + HWTracerError::TraceParseError(_) => None, HWTracerError::Unknown => None, } }