iptr_perf_pt_reader/
lib.rs1#![doc = include_str!("../README.md")]
2#![no_std]
3#![deny(missing_docs)]
4
5extern crate alloc;
6
7use core::ffi::CStr;
8
9use alloc::{
10 string::{String, ToString},
11 vec::Vec,
12};
13
14mod error;
15mod util;
16
17pub use crate::error::ReaderError;
18use crate::error::ReaderResult;
19
20const PERF_RECORD_MMAP2: u32 = 10;
22const PERF_RECORD_AUXTRACE: u32 = 71;
24
25#[expect(clippy::cast_possible_truncation)]
27pub fn extract_pt_auxtraces(perf_data: &[u8]) -> ReaderResult<Vec<PerfRecordAuxtrace<'_>>> {
28 let mut pt_auxtraces = Vec::new();
29
30 let (pos, total_size) = read_perf_header(perf_data)?;
31 let mut pos = pos as usize;
32 let end_pos = pos.saturating_add(total_size as usize);
33 let Some(perf_data) = perf_data.get(0..end_pos) else {
34 return Err(ReaderError::UnexpectedEOF);
35 };
36
37 loop {
38 if pos >= end_pos {
39 break;
40 }
41 let perf_header_start_pos = pos;
42 let Some(perf_event_header) = read_perf_event_header(perf_data, &mut pos) else {
43 return Err(ReaderError::UnexpectedEOF);
44 };
45 if perf_event_header.size == 0 {
46 return Err(ReaderError::InvalidPerfData);
48 }
49 match perf_event_header.r#type {
50 PERF_RECORD_AUXTRACE => {
51 let Some(auxtrace) = read_auxtrace(perf_data, &mut pos) else {
52 return Err(ReaderError::UnexpectedEOF);
53 };
54 pt_auxtraces.push(auxtrace);
55 }
56 _ => {
57 pos = perf_header_start_pos.saturating_add(perf_event_header.size as usize);
58 }
59 }
60 }
61
62 Ok(pt_auxtraces)
63}
64
65#[expect(clippy::cast_possible_truncation)]
67pub fn extract_pt_auxtraces_and_mmap_data(
68 perf_data: &[u8],
69) -> ReaderResult<(Vec<PerfRecordAuxtrace<'_>>, Vec<PerfMmap2Header>)> {
70 let mut pt_auxtraces = Vec::new();
71 let mut mmap2_headers = Vec::new();
72
73 let (pos, total_size) = read_perf_header(perf_data)?;
74 let mut pos = pos as usize;
75 let end_pos = pos.saturating_add(total_size as usize);
76 let Some(perf_data) = perf_data.get(0..end_pos) else {
77 return Err(ReaderError::UnexpectedEOF);
78 };
79
80 loop {
81 if pos >= end_pos {
82 break;
83 }
84 let perf_header_start_pos = pos;
85 let Some(perf_event_header) = read_perf_event_header(perf_data, &mut pos) else {
86 return Err(ReaderError::UnexpectedEOF);
87 };
88 if perf_event_header.size == 0 {
89 return Err(ReaderError::InvalidPerfData);
91 }
92 match perf_event_header.r#type {
93 PERF_RECORD_AUXTRACE => {
94 let Some(auxtrace) = read_auxtrace(perf_data, &mut pos) else {
95 return Err(ReaderError::UnexpectedEOF);
96 };
97 pt_auxtraces.push(auxtrace);
98 }
99 PERF_RECORD_MMAP2 => {
100 let end_pos = perf_header_start_pos.saturating_add(perf_event_header.size as usize);
101 let Some(mmap2_header) = read_mmap2(perf_data, pos, end_pos) else {
102 return Err(ReaderError::InvalidPerfData);
103 };
104 mmap2_headers.push(mmap2_header);
105 pos = end_pos;
106 }
107 _ => {
108 pos = perf_header_start_pos.saturating_add(perf_event_header.size as usize);
109 }
110 }
111 }
112
113 Ok((pt_auxtraces, mmap2_headers))
114}
115
116fn read_perf_header(perf_data: &[u8]) -> ReaderResult<(u64, u64)> {
117 let mut pos = 0;
118 let magic = util::read_u64(perf_data, pos).ok_or(ReaderError::UnexpectedEOF)?;
119 pos += 8;
120 if magic.to_le_bytes().as_slice() != b"PERFILE2" {
121 return Err(ReaderError::InvalidPerfData);
122 }
123
124 let _size = util::read_u64(perf_data, pos).ok_or(ReaderError::UnexpectedEOF)?;
125 pos += 8;
126
127 let _attr_size = util::read_u64(perf_data, pos).ok_or(ReaderError::UnexpectedEOF)?;
128 pos += 8;
129
130 let _attrs_section =
131 read_perf_file_section(perf_data, &mut pos).ok_or(ReaderError::UnexpectedEOF)?;
132 let data_section =
133 read_perf_file_section(perf_data, &mut pos).ok_or(ReaderError::UnexpectedEOF)?;
134
135 let (offset, size) = data_section;
136 Ok((offset, size))
137}
138
139fn read_perf_file_section(perf_data: &[u8], pos: &mut usize) -> Option<(u64, u64)> {
140 let offset = util::read_u64(perf_data, *pos)?;
141 *pos += 8;
142 let size = util::read_u64(perf_data, *pos)?;
143 *pos += 8;
144
145 Some((offset, size))
146}
147
148#[expect(unused)]
149struct PerfEventHeader {
150 r#type: u32,
151 misc: u16,
152 size: u16,
153}
154
155fn read_perf_event_header(perf_data: &[u8], pos: &mut usize) -> Option<PerfEventHeader> {
156 let r#type = util::read_u32(perf_data, *pos)?;
157 *pos += 4;
158 let misc = util::read_u16(perf_data, *pos)?;
159 *pos += 2;
160 let size = util::read_u16(perf_data, *pos)?;
161 *pos += 2;
162
163 Some(PerfEventHeader { r#type, misc, size })
164}
165
166pub struct PerfRecordAuxtrace<'a> {
168 pub size: u64,
170 pub offset: u64,
172 pub reference: u64,
174 pub idx: u32,
176 pub tid: u32,
178 pub cpu: u32,
180 pub auxtrace_data: &'a [u8],
182}
183
184#[expect(clippy::cast_possible_truncation)]
185fn read_auxtrace<'a>(perf_data: &'a [u8], pos: &mut usize) -> Option<PerfRecordAuxtrace<'a>> {
186 let size = util::read_u64(perf_data, *pos)?;
187 *pos += 8;
188 let offset = util::read_u64(perf_data, *pos)?;
189 *pos += 8;
190 let reference = util::read_u64(perf_data, *pos)?;
191 *pos += 8;
192 let idx = util::read_u32(perf_data, *pos)?;
193 *pos += 4;
194 let tid = util::read_u32(perf_data, *pos)?;
195 *pos += 4;
196 let cpu = util::read_u32(perf_data, *pos)?;
197 *pos += 4;
198 let _reserved = util::read_u32(perf_data, *pos)?;
199 *pos += 4;
200
201 if size == 0 {
202 return None;
203 }
204 let auxtrace_data = perf_data.get(*pos..(pos.saturating_add(size as usize)))?;
205 *pos = pos.saturating_add(size as usize);
206
207 Some(PerfRecordAuxtrace {
208 size,
209 offset,
210 reference,
211 idx,
212 tid,
213 cpu,
214 auxtrace_data,
215 })
216}
217
218pub struct PerfMmap2Header {
220 pub pid: u32,
222 pub tid: u32,
224 pub addr: u64,
226 pub len: u64,
228 pub pgoff: u64,
230 pub inode: [u8; 24],
232 pub prot: u32,
234 pub flags: u32,
236 pub filename: String,
238}
239
240fn read_mmap2(perf_data: &[u8], start_pos: usize, end_pos: usize) -> Option<PerfMmap2Header> {
241 let mut pos = start_pos;
242 let pid = util::read_u32(perf_data, pos)?;
243 pos += 4;
244 let tid = util::read_u32(perf_data, pos)?;
245 pos += 4;
246 let addr = util::read_u64(perf_data, pos)?;
247 pos += 8;
248 let len = util::read_u64(perf_data, pos)?;
249 pos += 8;
250 let pgoff = util::read_u64(perf_data, pos)?;
251 pos += 8;
252 let inode = *perf_data
253 .get(pos..)
254 .and_then(|buf| buf.first_chunk::<24>())?;
255 pos += 24;
256 let prot = util::read_u32(perf_data, pos)?;
257 pos += 4;
258 let flags = util::read_u32(perf_data, pos)?;
259 pos += 4;
260 if pos >= end_pos {
261 return None;
262 }
263 let filename_buf = perf_data.get(pos..end_pos)?;
264 let filename_c_str = CStr::from_bytes_until_nul(filename_buf).ok()?;
265 let filename_str = filename_c_str.to_str().ok()?;
266 let filename = filename_str.to_string();
267
268 Some(PerfMmap2Header {
269 pid,
270 tid,
271 addr,
272 len,
273 pgoff,
274 inode,
275 prot,
276 flags,
277 filename,
278 })
279}