Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

0.3.0 #12

Merged
merged 6 commits into from
Jan 29, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,20 @@ _This changelog documents only changes relevant to users, internal changes might

- This [CHANGELOG](./CHANGELOG.md) 🎉
- Explicit [MSRV](Cargo.toml)
- New `Image::add_files_cached()` API to simplify the creation of a cached image for simple use cases.

### Changed

- `ConfigBuilder` and `Config` have been replaced by `EncoderDecoderBuilder`
- Decoders/Encoder `::new()` have been replaced by `EncoderDecoderBuilder.build()`
- Decoders/Encoder `.get_config()` have been replaced by `.used_builder()`
- Block/Insn decoders `.image()` now returns `&mut Image` instead of `Result<Image,...>`
- Block `raw()` now returns an `Option::<&[u8]>`
- `Image.copy()` has been replaced by `Image.extend()`
- `Image.add_cached()` now takes a `Rc<SectionCache>` instead of `&mut SectionCache` to ensure that the cache outlives the `Image`.
- Many packet/event types methods now take a `&self` instead of consuming `self`
- Some simple methods are now `const`
- ~~`Class:Error`~~ -> `Class::Unknown`

### Removed

Expand Down
4 changes: 2 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "libipt"
version = "0.3.0-beta.2"
version = "0.3.0"
authors = [
"sum_catnip <[email protected]>",
"Marcondiro <[email protected]>",
Expand All @@ -17,6 +17,6 @@ rust-version = "1.82.0"
libipt_master = ["libipt-sys/libipt_master"]

[dependencies]
libipt-sys = { version = "0.2.1-beta.3", git = "https://github.com/sum-catnip/libipt-sys.git" }
libipt-sys = { version = "0.2.1", git = "https://github.com/sum-catnip/libipt-sys.git" }
bitflags = "2.4.1"
num_enum = "0.7.1"
21 changes: 10 additions & 11 deletions src/block/decoder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ use std::mem::MaybeUninit;
use std::ptr;
use std::ptr::NonNull;

/// The decoder will work on the buffer defined in a Config, it shall contain
/// The decoder will work on the buffer defined in the builder, it shall contain
/// raw trace data and remain valid for the lifetime of the decoder.
///
/// The decoder needs to be synchronized before it can be used.
Expand All @@ -28,9 +28,9 @@ pub struct BlockDecoder<'a> {
}

impl PtEncoderDecoder for BlockDecoder<'_> {
/// Allocate an Intel PT block decoder.
/// Create an Intel PT block decoder.
///
/// The decoder will work on the buffer defined in @config,
/// The decoder will work on the buffer defined in @builder,
/// it shall contain raw trace data and remain valid for the lifetime of the decoder.
/// The decoder needs to be synchronized before it can be used.
fn new_from_builder(builder: &EncoderDecoderBuilder<Self>) -> Result<Self, PtError> {
Expand All @@ -51,8 +51,7 @@ impl PtEncoderDecoder for BlockDecoder<'_> {
impl BlockDecoder<'_> {
/// Return the current address space identifier.
///
/// On success, provides the current address space identifier in @asid.
/// Returns Asid on success, a `PtError` otherwise.
/// On success, provides the current address space identifier a `PtError` otherwise.
pub fn asid(&self) -> Result<Asid, PtError> {
let mut asid = MaybeUninit::<pt_asid>::uninit();
ensure_ptok(unsafe {
Expand All @@ -76,7 +75,7 @@ impl BlockDecoder<'_> {

/// Get the next pending event.
///
/// On success, provides the next event, a `StatusFlag` instance and updates the decoder.
/// On success, provides the next event, a `Status` instance and updates the decoder.
/// Returns `BadQuery` if there is no event.
pub fn event(&mut self) -> Result<(Event, Status), PtError> {
let mut evt = MaybeUninit::<pt_event>::uninit();
Expand Down Expand Up @@ -135,16 +134,16 @@ impl BlockDecoder<'_> {
/// Determine the next block of instructions.
///
/// On success, provides the next block of instructions in execution order.
/// Also Returns a `StatusFlag` instance on success.
/// Also Returns a `Status` instance on success.
/// Returns Eos to indicate the end of the trace stream.
/// Subsequent calls to next will continue to return Eos until trace is required to determine the next instruction.
/// Subsequent calls will continue to return Eos until trace is required to determine the next instruction.
/// Returns `BadContext` if the decoder encountered an unexpected packet.
/// Returns `BadOpc` if the decoder encountered unknown packets.
/// Returns `BadPacket` if the decoder encountered unknown packet payloads.
/// Returns `BadQuery` if the decoder got out of sync.
/// Returns Eos if decoding reached the end of the Intel PT buffer.
/// Returns Nomap if the memory at the instruction address can't be read.
/// Returns Nosync if the decoder is out of sync.
/// Returns `Eos` if decoding reached the end of the Intel PT buffer.
/// Returns `Nomap` if the memory at the instruction address can't be read.
/// Returns `Nosync` if the decoder is out of sync.
pub fn decode_next(&mut self) -> Result<(Block, Status), PtError> {
let mut blk = MaybeUninit::<pt_block>::uninit();
let status = extract_status_or_pterr(unsafe {
Expand Down
37 changes: 18 additions & 19 deletions src/block/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ pub use decoder::*;
/// A block of instructions.
///
/// Instructions in this block are executed sequentially but are not necessarily
/// contiguous in memory. Users are expected to follow direct branches.
/// contiguous in memory. Users are expected to follow direct branches.
#[derive(Debug, Clone, Copy)]
#[repr(transparent)]
pub struct Block(pub(super) pt_block);
Expand All @@ -31,6 +31,7 @@ impl Block {
self.0.end_ip
}

// TODO: make this more rusty? at least with an Option? &Image?
/// The image section that contains the instructions in this block.
///
/// A value of zero means that the section did not have an identifier.
Expand All @@ -50,7 +51,7 @@ impl Block {

/// The instruction class for the last instruction in this block.
///
/// This field may be set to `Class::Error` to indicate that the instruction
/// This field may be set to `Class::Unknown` to indicate that the instruction
/// class is not available. The block decoder may choose to not provide
/// the instruction class in some cases for performance reasons.
#[must_use]
Expand All @@ -68,31 +69,29 @@ impl Block {
/// The raw bytes of the last instruction in this block in case the
/// instruction does not fit entirely into this block's section.
///
/// This field is only valid if truncated is set.
/// This field is `Some`only if `truncated()` is true.
#[must_use]
pub fn raw(&self) -> &[u8] {
&self.0.raw[..self.0.size as usize]
pub fn raw(&self) -> Option<&[u8]> {
if self.truncated() {
Some(&self.0.raw[..self.0.size as usize])
} else {
None
}
}

/// A collection of flags giving additional information about the
/// instructions in this block.
///
/// - all instructions in this block were executed speculatively.
/// All instructions in this block were executed speculatively.
#[must_use]
pub fn speculative(&self) -> bool {
self.0.speculative() > 0
}

/// A collection of flags giving additional information about the
/// instructions in this block.
///
/// - the last instruction in this block is truncated.
/// The last instruction in this block is truncated.
///
/// It starts in this block's section but continues in one or more
/// other sections depending on how fragmented the memory image is.
///
/// The raw bytes for the last instruction are provided in \@raw and
/// its size in \@size in this case.
/// The raw bytes for the last instruction are provided in @raw and
/// its size in @size in this case.
#[must_use]
pub fn truncated(&self) -> bool {
self.0.truncated() > 0
Expand Down Expand Up @@ -125,9 +124,9 @@ mod test {
assert_eq!(blk.end_ip(), 2);
assert_eq!(blk.isid(), 3);
assert_eq!(blk.mode(), ExecModeType::Bit32);
assert_eq!(blk.class(), Class::Error);
assert_eq!(blk.class(), Class::Unknown);
assert_eq!(blk.ninsn(), 4);
assert_eq!(blk.raw(), &data[..8]);
assert_eq!(blk.raw(), Some(&data[..8]));
assert!(blk.truncated());
assert!(!blk.speculative());
}
Expand All @@ -153,9 +152,9 @@ mod test {
assert_eq!(blk.end_ip(), 2);
assert_eq!(blk.isid(), 3);
assert_eq!(blk.mode(), ExecModeType::Bit32);
assert_eq!(blk.class(), Class::Error);
assert_eq!(blk.class(), Class::Unknown);
assert_eq!(blk.ninsn(), 4);
assert!(blk.raw().len() > 0);
assert!(blk.raw().is_none());
assert!(!blk.truncated());
assert!(!blk.speculative());
}
Expand Down
13 changes: 11 additions & 2 deletions src/enc_dec_builder/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ pub trait PtEncoderDecoder {
Self: Sized;
}

#[derive(Debug, Clone)]
#[derive(Debug)]
#[repr(transparent)]
pub struct EncoderDecoderBuilder<T> {
pub(crate) config: pt_config,
Expand All @@ -68,11 +68,20 @@ where
}
}

impl<T> Clone for EncoderDecoderBuilder<T> {
fn clone(&self) -> Self {
Self {
config: self.config,
target: PhantomData,
}
}
}

impl<T> EncoderDecoderBuilder<T>
where
T: PtEncoderDecoder,
{
/// Initializes an EncoderDecoderBuilder instance
/// Create an EncoderDecoderBuilder
pub const fn new() -> Self {
let mut config: pt_config = unsafe { mem::zeroed() };
config.size = size_of::<pt_config>();
Expand Down
97 changes: 88 additions & 9 deletions src/image/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,19 @@ impl Drop for BoxedCallback {
}
}

/// Info of a binary's section that can be used to populate an `Image`
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct SectionInfo {
/// Path of the binary
pub filename: String,
/// Offset of the section in the file
pub offset: u64,
/// Size of the section
pub size: u64,
/// Start virtual address of the section once loaded in memory
pub virtual_address: u64,
}

/// An Image defines the memory image that was traced as a collection
/// of file sections and the virtual addresses at which those sections were loaded.
#[derive(Debug)]
Expand All @@ -108,7 +121,9 @@ pub struct Image {
// Any read data callback set by this `Image` instance.
callback: Option<BoxedCallback>,
caches: Vec<Rc<SectionCache>>,
asids: HashSet<Asid>,
// `HashSet` might grow and move the content around, we cannot use `Asid` directly since we
// share a pointer with libipt, and it must be valid for the entire Image (section) lifetime.
asids: HashSet<Rc<Asid>>,
}

impl Image {
Expand Down Expand Up @@ -233,7 +248,9 @@ impl Image {
})?;

self.caches.extend_from_slice(&src.caches);
self.asids.extend(&src.asids);
for asid in &src.asids {
self.asids.insert(asid.clone());
}
Ok(res)
}

Expand All @@ -252,7 +269,7 @@ impl Image {
) -> Result<(), PtError> {
let asid_ptr = if let Some(a) = asid {
// fixme: use get_or_insert once stable (if ever)
self.asids.insert(*a);
self.asids.insert(Rc::new(*a));
&raw const self.asids.get(a).unwrap().0
} else {
ptr::null()
Expand All @@ -275,9 +292,6 @@ impl Image {
);
})?;
self.caches.push(iscache);
if let Some(a) = asid {
self.asids.insert(*a);
}
Ok(())
}

Expand All @@ -303,7 +317,7 @@ impl Image {
let cfilename = str_to_cstring_pterror(filename)?;
let asid_ptr = if let Some(a) = asid {
// fixme: use get_or_insert once stable (if ever)
self.asids.insert(*a);
self.asids.insert(Rc::new(*a));
&raw const self.asids.get(a).unwrap().0
} else {
ptr::null()
Expand All @@ -318,9 +332,31 @@ impl Image {
vaddr,
)
})?;
if let Some(a) = asid {
self.asids.insert(*a);
Ok(())
}

/// Add multiple file sections to the traced memory image, backed by a cache.
///
/// This is the same as creating a `SectionCache` and subsequently calling `add_cached()` for
/// each section.
pub fn add_files_cached(
&mut self,
sections_info: &[SectionInfo],
asid: Option<&Asid>,
) -> Result<(), PtError> {
let mut image_cache = SectionCache::new(None)?;

let mut isids = Vec::with_capacity(sections_info.len());
for s in sections_info {
let isid = image_cache.add_file(&s.filename, s.offset, s.size, s.virtual_address)?;
isids.push(isid);
}

let rc_cache = Rc::new(image_cache);
for isid in isids {
self.add_cached(rc_cache.clone(), isid, asid)?;
}

Ok(())
}
}
Expand Down Expand Up @@ -488,4 +524,47 @@ mod test {
i.add_cached(Rc::new(c), isid, Some(&asid)).unwrap();
assert_eq!(i.remove_by_asid(&asid).unwrap(), 1);
}

#[test]
fn img_extend() {
let file: PathBuf = [env!("CARGO_MANIFEST_DIR"), "testfiles", "garbage.txt"]
.iter()
.collect();

let mut img = Image::new(None).unwrap();
{
let mut img2 = Image::new(None).unwrap();
for i in 0..100 {
let mut cache = SectionCache::new(None).unwrap();
let asid = Asid::new(Some(i), Some(i));
let isid = cache.add_file(file.to_str().unwrap(), i, 1, i).unwrap();
let rc = Rc::new(cache);
img2.add_cached(rc.clone(), isid, Some(&asid)).unwrap()
}

img.extend(&img2).unwrap();
}

for i in 0..100 {
assert_eq!(img.remove_by_asid(&Asid::new(Some(i), Some(i))).unwrap(), 1);
}
}

#[test]
fn img_add_files_cached() {
let file: PathBuf = [env!("CARGO_MANIFEST_DIR"), "testfiles", "garbage.txt"]
.iter()
.collect();

let section = SectionInfo {
filename: file.to_string_lossy().to_string(),
offset: 5,
size: 15,
virtual_address: 0x1337,
};
let mut i = img_with_file();
let asid = Asid::new(Some(3), Some(4));
i.add_files_cached(&[section], Some(&asid)).unwrap();
assert_eq!(i.remove_by_asid(&asid).unwrap(), 1);
}
}
10 changes: 5 additions & 5 deletions src/insn/class.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@
#![allow(clippy::unnecessary_cast)]

use libipt_sys::{
pt_insn_class_ptic_call, pt_insn_class_ptic_cond_jump, pt_insn_class_ptic_error,
pt_insn_class_ptic_far_call, pt_insn_class_ptic_far_jump, pt_insn_class_ptic_far_return,
pt_insn_class_ptic_jump, pt_insn_class_ptic_other, pt_insn_class_ptic_ptwrite,
pt_insn_class_ptic_return,
pt_insn_class_ptic_call, pt_insn_class_ptic_cond_jump, pt_insn_class_ptic_far_call,
pt_insn_class_ptic_far_jump, pt_insn_class_ptic_far_return, pt_insn_class_ptic_jump,
pt_insn_class_ptic_other, pt_insn_class_ptic_ptwrite, pt_insn_class_ptic_return,
pt_insn_class_ptic_unknown,
};
use num_enum::TryFromPrimitive;

Expand All @@ -21,7 +21,7 @@ pub enum Class {
/// The instruction is a near conditional jump.
CondJump = pt_insn_class_ptic_cond_jump as u32,
/// The instruction could not be classified.
Error = pt_insn_class_ptic_error as u32,
Unknown = pt_insn_class_ptic_unknown as u32,
/// The instruction is a call-like far transfer.
/// E.g. SYSCALL, SYSENTER, or FAR CALL.
FarCall = pt_insn_class_ptic_far_call as u32,
Expand Down
Loading