Skip to content

Commit aaf6f24

Browse files
authored
0.3.0 release (#12)
* Fix potential invalid pointer to Asid * Image::add_files_cached() API to simplify the creation of a cached image * More flexible EncoderDecoderBuilder Clone * Improve docs
1 parent f9c16a1 commit aaf6f24

File tree

9 files changed

+149
-64
lines changed

9 files changed

+149
-64
lines changed

CHANGELOG.md

+3
Original file line numberDiff line numberDiff line change
@@ -8,17 +8,20 @@ _This changelog documents only changes relevant to users, internal changes might
88

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

1213
### Changed
1314

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

2326
### Removed
2427

Cargo.toml

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "libipt"
3-
version = "0.3.0-beta.2"
3+
version = "0.3.0"
44
authors = [
55
"sum_catnip <[email protected]>",
66
"Marcondiro <[email protected]>",
@@ -17,6 +17,6 @@ rust-version = "1.82.0"
1717
libipt_master = ["libipt-sys/libipt_master"]
1818

1919
[dependencies]
20-
libipt-sys = { version = "0.2.1-beta.3", git = "https://github.com/sum-catnip/libipt-sys.git" }
20+
libipt-sys = { version = "0.2.1", git = "https://github.com/sum-catnip/libipt-sys.git" }
2121
bitflags = "2.4.1"
2222
num_enum = "0.7.1"

src/block/decoder.rs

+10-11
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ use std::mem::MaybeUninit;
1616
use std::ptr;
1717
use std::ptr::NonNull;
1818

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

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

7776
/// Get the next pending event.
7877
///
79-
/// On success, provides the next event, a `StatusFlag` instance and updates the decoder.
78+
/// On success, provides the next event, a `Status` instance and updates the decoder.
8079
/// Returns `BadQuery` if there is no event.
8180
pub fn event(&mut self) -> Result<(Event, Status), PtError> {
8281
let mut evt = MaybeUninit::<pt_event>::uninit();
@@ -135,16 +134,16 @@ impl BlockDecoder<'_> {
135134
/// Determine the next block of instructions.
136135
///
137136
/// On success, provides the next block of instructions in execution order.
138-
/// Also Returns a `StatusFlag` instance on success.
137+
/// Also Returns a `Status` instance on success.
139138
/// Returns Eos to indicate the end of the trace stream.
140-
/// Subsequent calls to next will continue to return Eos until trace is required to determine the next instruction.
139+
/// Subsequent calls will continue to return Eos until trace is required to determine the next instruction.
141140
/// Returns `BadContext` if the decoder encountered an unexpected packet.
142141
/// Returns `BadOpc` if the decoder encountered unknown packets.
143142
/// Returns `BadPacket` if the decoder encountered unknown packet payloads.
144143
/// Returns `BadQuery` if the decoder got out of sync.
145-
/// Returns Eos if decoding reached the end of the Intel PT buffer.
146-
/// Returns Nomap if the memory at the instruction address can't be read.
147-
/// Returns Nosync if the decoder is out of sync.
144+
/// Returns `Eos` if decoding reached the end of the Intel PT buffer.
145+
/// Returns `Nomap` if the memory at the instruction address can't be read.
146+
/// Returns `Nosync` if the decoder is out of sync.
148147
pub fn decode_next(&mut self) -> Result<(Block, Status), PtError> {
149148
let mut blk = MaybeUninit::<pt_block>::uninit();
150149
let status = extract_status_or_pterr(unsafe {

src/block/mod.rs

+18-19
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ pub use decoder::*;
1212
/// A block of instructions.
1313
///
1414
/// Instructions in this block are executed sequentially but are not necessarily
15-
/// contiguous in memory. Users are expected to follow direct branches.
15+
/// contiguous in memory. Users are expected to follow direct branches.
1616
#[derive(Debug, Clone, Copy)]
1717
#[repr(transparent)]
1818
pub struct Block(pub(super) pt_block);
@@ -31,6 +31,7 @@ impl Block {
3131
self.0.end_ip
3232
}
3333

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

5152
/// The instruction class for the last instruction in this block.
5253
///
53-
/// This field may be set to `Class::Error` to indicate that the instruction
54+
/// This field may be set to `Class::Unknown` to indicate that the instruction
5455
/// class is not available. The block decoder may choose to not provide
5556
/// the instruction class in some cases for performance reasons.
5657
#[must_use]
@@ -68,31 +69,29 @@ impl Block {
6869
/// The raw bytes of the last instruction in this block in case the
6970
/// instruction does not fit entirely into this block's section.
7071
///
71-
/// This field is only valid if truncated is set.
72+
/// This field is `Some`only if `truncated()` is true.
7273
#[must_use]
73-
pub fn raw(&self) -> &[u8] {
74-
&self.0.raw[..self.0.size as usize]
74+
pub fn raw(&self) -> Option<&[u8]> {
75+
if self.truncated() {
76+
Some(&self.0.raw[..self.0.size as usize])
77+
} else {
78+
None
79+
}
7580
}
7681

77-
/// A collection of flags giving additional information about the
78-
/// instructions in this block.
79-
///
80-
/// - all instructions in this block were executed speculatively.
82+
/// All instructions in this block were executed speculatively.
8183
#[must_use]
8284
pub fn speculative(&self) -> bool {
8385
self.0.speculative() > 0
8486
}
8587

86-
/// A collection of flags giving additional information about the
87-
/// instructions in this block.
88-
///
89-
/// - the last instruction in this block is truncated.
88+
/// The last instruction in this block is truncated.
9089
///
9190
/// It starts in this block's section but continues in one or more
9291
/// other sections depending on how fragmented the memory image is.
9392
///
94-
/// The raw bytes for the last instruction are provided in \@raw and
95-
/// its size in \@size in this case.
93+
/// The raw bytes for the last instruction are provided in @raw and
94+
/// its size in @size in this case.
9695
#[must_use]
9796
pub fn truncated(&self) -> bool {
9897
self.0.truncated() > 0
@@ -125,9 +124,9 @@ mod test {
125124
assert_eq!(blk.end_ip(), 2);
126125
assert_eq!(blk.isid(), 3);
127126
assert_eq!(blk.mode(), ExecModeType::Bit32);
128-
assert_eq!(blk.class(), Class::Error);
127+
assert_eq!(blk.class(), Class::Unknown);
129128
assert_eq!(blk.ninsn(), 4);
130-
assert_eq!(blk.raw(), &data[..8]);
129+
assert_eq!(blk.raw(), Some(&data[..8]));
131130
assert!(blk.truncated());
132131
assert!(!blk.speculative());
133132
}
@@ -153,9 +152,9 @@ mod test {
153152
assert_eq!(blk.end_ip(), 2);
154153
assert_eq!(blk.isid(), 3);
155154
assert_eq!(blk.mode(), ExecModeType::Bit32);
156-
assert_eq!(blk.class(), Class::Error);
155+
assert_eq!(blk.class(), Class::Unknown);
157156
assert_eq!(blk.ninsn(), 4);
158-
assert!(blk.raw().len() > 0);
157+
assert!(blk.raw().is_none());
159158
assert!(!blk.truncated());
160159
assert!(!blk.speculative());
161160
}

src/enc_dec_builder/mod.rs

+11-2
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ pub trait PtEncoderDecoder {
5252
Self: Sized;
5353
}
5454

55-
#[derive(Debug, Clone)]
55+
#[derive(Debug)]
5656
#[repr(transparent)]
5757
pub struct EncoderDecoderBuilder<T> {
5858
pub(crate) config: pt_config,
@@ -68,11 +68,20 @@ where
6868
}
6969
}
7070

71+
impl<T> Clone for EncoderDecoderBuilder<T> {
72+
fn clone(&self) -> Self {
73+
Self {
74+
config: self.config,
75+
target: PhantomData,
76+
}
77+
}
78+
}
79+
7180
impl<T> EncoderDecoderBuilder<T>
7281
where
7382
T: PtEncoderDecoder,
7483
{
75-
/// Initializes an EncoderDecoderBuilder instance
84+
/// Create an EncoderDecoderBuilder
7685
pub const fn new() -> Self {
7786
let mut config: pt_config = unsafe { mem::zeroed() };
7887
config.size = size_of::<pt_config>();

src/image/mod.rs

+88-9
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,19 @@ impl Drop for BoxedCallback {
9797
}
9898
}
9999

100+
/// Info of a binary's section that can be used to populate an `Image`
101+
#[derive(Debug, Clone, PartialEq, Eq)]
102+
pub struct SectionInfo {
103+
/// Path of the binary
104+
pub filename: String,
105+
/// Offset of the section in the file
106+
pub offset: u64,
107+
/// Size of the section
108+
pub size: u64,
109+
/// Start virtual address of the section once loaded in memory
110+
pub virtual_address: u64,
111+
}
112+
100113
/// An Image defines the memory image that was traced as a collection
101114
/// of file sections and the virtual addresses at which those sections were loaded.
102115
#[derive(Debug)]
@@ -108,7 +121,9 @@ pub struct Image {
108121
// Any read data callback set by this `Image` instance.
109122
callback: Option<BoxedCallback>,
110123
caches: Vec<Rc<SectionCache>>,
111-
asids: HashSet<Asid>,
124+
// `HashSet` might grow and move the content around, we cannot use `Asid` directly since we
125+
// share a pointer with libipt, and it must be valid for the entire Image (section) lifetime.
126+
asids: HashSet<Rc<Asid>>,
112127
}
113128

114129
impl Image {
@@ -233,7 +248,9 @@ impl Image {
233248
})?;
234249

235250
self.caches.extend_from_slice(&src.caches);
236-
self.asids.extend(&src.asids);
251+
for asid in &src.asids {
252+
self.asids.insert(asid.clone());
253+
}
237254
Ok(res)
238255
}
239256

@@ -252,7 +269,7 @@ impl Image {
252269
) -> Result<(), PtError> {
253270
let asid_ptr = if let Some(a) = asid {
254271
// fixme: use get_or_insert once stable (if ever)
255-
self.asids.insert(*a);
272+
self.asids.insert(Rc::new(*a));
256273
&raw const self.asids.get(a).unwrap().0
257274
} else {
258275
ptr::null()
@@ -275,9 +292,6 @@ impl Image {
275292
);
276293
})?;
277294
self.caches.push(iscache);
278-
if let Some(a) = asid {
279-
self.asids.insert(*a);
280-
}
281295
Ok(())
282296
}
283297

@@ -303,7 +317,7 @@ impl Image {
303317
let cfilename = str_to_cstring_pterror(filename)?;
304318
let asid_ptr = if let Some(a) = asid {
305319
// fixme: use get_or_insert once stable (if ever)
306-
self.asids.insert(*a);
320+
self.asids.insert(Rc::new(*a));
307321
&raw const self.asids.get(a).unwrap().0
308322
} else {
309323
ptr::null()
@@ -318,9 +332,31 @@ impl Image {
318332
vaddr,
319333
)
320334
})?;
321-
if let Some(a) = asid {
322-
self.asids.insert(*a);
335+
Ok(())
336+
}
337+
338+
/// Add multiple file sections to the traced memory image, backed by a cache.
339+
///
340+
/// This is the same as creating a `SectionCache` and subsequently calling `add_cached()` for
341+
/// each section.
342+
pub fn add_files_cached(
343+
&mut self,
344+
sections_info: &[SectionInfo],
345+
asid: Option<&Asid>,
346+
) -> Result<(), PtError> {
347+
let mut image_cache = SectionCache::new(None)?;
348+
349+
let mut isids = Vec::with_capacity(sections_info.len());
350+
for s in sections_info {
351+
let isid = image_cache.add_file(&s.filename, s.offset, s.size, s.virtual_address)?;
352+
isids.push(isid);
323353
}
354+
355+
let rc_cache = Rc::new(image_cache);
356+
for isid in isids {
357+
self.add_cached(rc_cache.clone(), isid, asid)?;
358+
}
359+
324360
Ok(())
325361
}
326362
}
@@ -488,4 +524,47 @@ mod test {
488524
i.add_cached(Rc::new(c), isid, Some(&asid)).unwrap();
489525
assert_eq!(i.remove_by_asid(&asid).unwrap(), 1);
490526
}
527+
528+
#[test]
529+
fn img_extend() {
530+
let file: PathBuf = [env!("CARGO_MANIFEST_DIR"), "testfiles", "garbage.txt"]
531+
.iter()
532+
.collect();
533+
534+
let mut img = Image::new(None).unwrap();
535+
{
536+
let mut img2 = Image::new(None).unwrap();
537+
for i in 0..100 {
538+
let mut cache = SectionCache::new(None).unwrap();
539+
let asid = Asid::new(Some(i), Some(i));
540+
let isid = cache.add_file(file.to_str().unwrap(), i, 1, i).unwrap();
541+
let rc = Rc::new(cache);
542+
img2.add_cached(rc.clone(), isid, Some(&asid)).unwrap()
543+
}
544+
545+
img.extend(&img2).unwrap();
546+
}
547+
548+
for i in 0..100 {
549+
assert_eq!(img.remove_by_asid(&Asid::new(Some(i), Some(i))).unwrap(), 1);
550+
}
551+
}
552+
553+
#[test]
554+
fn img_add_files_cached() {
555+
let file: PathBuf = [env!("CARGO_MANIFEST_DIR"), "testfiles", "garbage.txt"]
556+
.iter()
557+
.collect();
558+
559+
let section = SectionInfo {
560+
filename: file.to_string_lossy().to_string(),
561+
offset: 5,
562+
size: 15,
563+
virtual_address: 0x1337,
564+
};
565+
let mut i = img_with_file();
566+
let asid = Asid::new(Some(3), Some(4));
567+
i.add_files_cached(&[section], Some(&asid)).unwrap();
568+
assert_eq!(i.remove_by_asid(&asid).unwrap(), 1);
569+
}
491570
}

src/insn/class.rs

+5-5
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,10 @@
22
#![allow(clippy::unnecessary_cast)]
33

44
use libipt_sys::{
5-
pt_insn_class_ptic_call, pt_insn_class_ptic_cond_jump, pt_insn_class_ptic_error,
6-
pt_insn_class_ptic_far_call, pt_insn_class_ptic_far_jump, pt_insn_class_ptic_far_return,
7-
pt_insn_class_ptic_jump, pt_insn_class_ptic_other, pt_insn_class_ptic_ptwrite,
8-
pt_insn_class_ptic_return,
5+
pt_insn_class_ptic_call, pt_insn_class_ptic_cond_jump, pt_insn_class_ptic_far_call,
6+
pt_insn_class_ptic_far_jump, pt_insn_class_ptic_far_return, pt_insn_class_ptic_jump,
7+
pt_insn_class_ptic_other, pt_insn_class_ptic_ptwrite, pt_insn_class_ptic_return,
8+
pt_insn_class_ptic_unknown,
99
};
1010
use num_enum::TryFromPrimitive;
1111

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

0 commit comments

Comments
 (0)