Skip to main content

iptr_perf_pt_reader/
lib.rs

1#![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
20/// Value of `type`` field for mmaped perf header
21const PERF_RECORD_MMAP2: u32 = 10;
22/// Value of `type` field for auxtrace header
23const PERF_RECORD_AUXTRACE: u32 = 71;
24
25/// Extract raw Intel PT traces from `perf.data`.
26#[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            // This will lead to infinite loop
47            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/// Extract raw Intel PT traces alongwith mmaped information from `perf.data`.
66#[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            // This will lead to infinite loop
90            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
166/// AUXTRACE in `perf.data`
167pub struct PerfRecordAuxtrace<'a> {
168    /// Size of [`auxtrace_data`][Self::auxtrace_data]
169    pub size: u64,
170    /// Offset
171    pub offset: u64,
172    /// Referemce
173    pub reference: u64,
174    /// Index of auxtrace in perf.data
175    pub idx: u32,
176    /// Thread id
177    pub tid: u32,
178    /// CPU id
179    pub cpu: u32,
180    /// Real Intel PT data
181    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
218/// Mmap2 header in `perf.data`
219pub struct PerfMmap2Header {
220    /// Process id
221    pub pid: u32,
222    /// Thread id
223    pub tid: u32,
224    /// Target address
225    pub addr: u64,
226    /// Mmaped length
227    pub len: u64,
228    /// Offset of file
229    pub pgoff: u64,
230    /// inode information
231    pub inode: [u8; 24],
232    /// Permissions
233    pub prot: u32,
234    /// Mmaped flags
235    pub flags: u32,
236    /// Mmaped filename
237    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}