fxt/
lib.rs

1// Copyright 2023 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#[cfg(test)]
6#[macro_use]
7mod testing;
8
9mod args;
10mod blob;
11mod error;
12mod event;
13mod fxt_builder;
14mod header;
15mod init;
16mod log;
17mod metadata;
18mod objects;
19mod scheduling;
20mod session;
21mod string;
22mod thread;
23
24pub use args::{Arg, ArgValue, RawArg, RawArgValue};
25pub use blob::{BlobRecord, BlobType, LargeBlobMetadata, LargeBlobRecord};
26pub use error::{ParseError, ParseWarning};
27pub use event::{symbolize, EventPayload, EventRecord, RawEventRecord};
28pub use log::LogRecord;
29pub use metadata::{Provider, ProviderEvent};
30pub use objects::{KernelObjRecord, UserspaceObjRecord};
31pub use scheduling::{
32    ContextSwitchEvent, LegacyContextSwitchEvent, SchedulingRecord, ThreadState, ThreadWakeupEvent,
33};
34pub use session::{parse_full_session, SessionParser};
35pub use string::StringRef;
36pub use thread::{ProcessKoid, ThreadKoid};
37
38use crate::blob::{RawBlobRecord, RawLargeBlobRecord};
39use crate::error::ParseResult;
40use crate::init::InitRecord;
41use crate::log::RawLogRecord;
42use crate::metadata::{MetadataRecord, TraceInfoMetadataRecord};
43use crate::objects::{RawKernelObjRecord, RawUserspaceObjRecord};
44use crate::scheduling::RawSchedulingRecord;
45use crate::session::ResolveCtx;
46use crate::string::StringRecord;
47use crate::thread::ThreadRecord;
48use nom::Parser;
49use std::num::NonZero;
50
51#[derive(Clone, Debug, PartialEq)]
52pub enum TraceRecord {
53    Event(EventRecord),
54    Blob(BlobRecord),
55    UserspaceObj(UserspaceObjRecord),
56    KernelObj(KernelObjRecord),
57    Scheduling(SchedulingRecord),
58    Log(LogRecord),
59    LargeBlob(LargeBlobRecord),
60    ProviderEvent { provider: Provider, event: ProviderEvent },
61}
62
63impl TraceRecord {
64    pub fn process(&self) -> Option<ProcessKoid> {
65        match self {
66            Self::Event(EventRecord { process, .. })
67            | Self::Log(LogRecord { process, .. })
68            | Self::UserspaceObj(UserspaceObjRecord { process, .. }) => Some(*process),
69            Self::Scheduling(s) => s.process(),
70            Self::KernelObj(k) => k.process(),
71            Self::Blob(..) | Self::LargeBlob(..) | Self::ProviderEvent { .. } => None,
72        }
73    }
74
75    pub fn thread(&self) -> Option<ThreadKoid> {
76        match self {
77            Self::Event(EventRecord { thread, .. }) | Self::Log(LogRecord { thread, .. }) => {
78                Some(*thread)
79            }
80            Self::Scheduling(s) => Some(s.thread()),
81            Self::Blob(..)
82            | Self::KernelObj(..)
83            | Self::LargeBlob(..)
84            | Self::ProviderEvent { .. }
85            | Self::UserspaceObj(..) => None,
86        }
87    }
88
89    fn resolve(ctx: &mut ResolveCtx, raw: RawTraceRecord<'_>) -> Result<Option<Self>, ParseError> {
90        Ok(match raw {
91            // Callers who want to handle unknown record types should use the raw record types.
92            RawTraceRecord::Unknown { raw_type } => {
93                ctx.add_warning(ParseWarning::UnknownTraceRecordType(raw_type));
94                None
95            }
96
97            RawTraceRecord::Metadata(m) => ctx.on_metadata_record(m)?,
98            RawTraceRecord::Init(i) => {
99                ctx.on_init_record(i);
100                None
101            }
102            RawTraceRecord::String(s) => {
103                ctx.on_string_record(s);
104                None
105            }
106            RawTraceRecord::Thread(t) => {
107                ctx.on_thread_record(t);
108                None
109            }
110            RawTraceRecord::Event(e) => Some(Self::Event(EventRecord::resolve(ctx, e))),
111            RawTraceRecord::Blob(b) => Some(Self::Blob(BlobRecord::resolve(ctx, b))),
112            RawTraceRecord::UserspaceObj(u) => {
113                Some(Self::UserspaceObj(UserspaceObjRecord::resolve(ctx, u)))
114            }
115            RawTraceRecord::KernelObj(k) => Some(Self::KernelObj(KernelObjRecord::resolve(ctx, k))),
116            RawTraceRecord::Scheduling(s) => {
117                SchedulingRecord::resolve(ctx, s).map(Self::Scheduling)
118            }
119            RawTraceRecord::Log(l) => Some(Self::Log(LogRecord::resolve(ctx, l))),
120            RawTraceRecord::LargeBlob(lb) => LargeBlobRecord::resolve(ctx, lb).map(Self::LargeBlob),
121        })
122    }
123}
124
125#[derive(Debug, PartialEq)]
126enum RawTraceRecord<'a> {
127    Metadata(MetadataRecord),
128    Init(InitRecord),
129    String(StringRecord<'a>),
130    Thread(ThreadRecord),
131    Event(RawEventRecord<'a>),
132    Blob(RawBlobRecord<'a>),
133    UserspaceObj(RawUserspaceObjRecord<'a>),
134    KernelObj(RawKernelObjRecord<'a>),
135    Scheduling(RawSchedulingRecord<'a>),
136    Log(RawLogRecord<'a>),
137    LargeBlob(RawLargeBlobRecord<'a>),
138    Unknown { raw_type: u8 },
139}
140
141trace_header! {
142    BaseTraceHeader {}
143}
144
145#[derive(Debug, PartialEq)]
146pub(crate) struct ParsedWithOriginalBytes<'a, T> {
147    pub parsed: T,
148    pub bytes: &'a [u8],
149}
150
151const METADATA_RECORD_TYPE: u8 = 0;
152const INIT_RECORD_TYPE: u8 = 1;
153const STRING_RECORD_TYPE: u8 = 2;
154const THREAD_RECORD_TYPE: u8 = 3;
155const EVENT_RECORD_TYPE: u8 = 4;
156const BLOB_RECORD_TYPE: u8 = 5;
157const USERSPACE_OBJ_RECORD_TYPE: u8 = 6;
158const KERNEL_OBJ_RECORD_TYPE: u8 = 7;
159const SCHEDULING_RECORD_TYPE: u8 = 8;
160const LOG_RECORD_TYPE: u8 = 9;
161const LARGE_RECORD_TYPE: u8 = 15;
162
163impl<'a> RawTraceRecord<'a> {
164    fn parse(buf: &'a [u8]) -> ParseResult<'a, ParsedWithOriginalBytes<'a, Self>> {
165        use nom::combinator::map;
166        let base_header = BaseTraceHeader::parse(buf)?.1;
167        let size_bytes = base_header.size_words() as usize * 8;
168        if size_bytes == 0 {
169            return Err(nom::Err::Failure(ParseError::InvalidSize));
170        }
171        if size_bytes > buf.len() {
172            return Err(nom::Err::Incomplete(nom::Needed::Size(
173                NonZero::new(size_bytes - buf.len()).unwrap(),
174            )));
175        }
176
177        let (buf, rem) = buf.split_at(size_bytes);
178        let (_, parsed) = match base_header.raw_type() {
179            METADATA_RECORD_TYPE => map(MetadataRecord::parse, |m| Self::Metadata(m)).parse(buf),
180            INIT_RECORD_TYPE => map(InitRecord::parse, |i| Self::Init(i)).parse(buf),
181            STRING_RECORD_TYPE => map(StringRecord::parse, |s| Self::String(s)).parse(buf),
182            THREAD_RECORD_TYPE => map(ThreadRecord::parse, |t| Self::Thread(t)).parse(buf),
183            EVENT_RECORD_TYPE => map(RawEventRecord::parse, |e| Self::Event(e)).parse(buf),
184            BLOB_RECORD_TYPE => map(RawBlobRecord::parse, |b| Self::Blob(b)).parse(buf),
185            USERSPACE_OBJ_RECORD_TYPE => {
186                map(RawUserspaceObjRecord::parse, |u| Self::UserspaceObj(u)).parse(buf)
187            }
188            KERNEL_OBJ_RECORD_TYPE => {
189                map(RawKernelObjRecord::parse, |k| Self::KernelObj(k)).parse(buf)
190            }
191            SCHEDULING_RECORD_TYPE => {
192                map(RawSchedulingRecord::parse, |s| Self::Scheduling(s)).parse(buf)
193            }
194            LOG_RECORD_TYPE => map(RawLogRecord::parse, |l| Self::Log(l)).parse(buf),
195            LARGE_RECORD_TYPE => map(RawLargeBlobRecord::parse, |l| Self::LargeBlob(l)).parse(buf),
196            raw_type => Ok((&[][..], Self::Unknown { raw_type })),
197        }?;
198
199        Ok((rem, ParsedWithOriginalBytes { parsed, bytes: buf }))
200    }
201
202    fn is_magic_number(&self) -> bool {
203        matches!(
204            self,
205            Self::Metadata(MetadataRecord::TraceInfo(TraceInfoMetadataRecord::MagicNumber)),
206        )
207    }
208}
209
210/// Take the first `unpadded_len` bytes from a buffer, returning a suffix beginning at the next
211/// world-aligned region and discarding padding bytes.
212fn take_n_padded<'a>(unpadded_len: usize, buf: &'a [u8]) -> ParseResult<'a, &'a [u8]> {
213    let padded_len = unpadded_len + word_padding(unpadded_len);
214    if padded_len > buf.len() {
215        return Err(nom::Err::Incomplete(nom::Needed::Size(
216            NonZero::new(padded_len - buf.len()).unwrap(),
217        )));
218    }
219    let (with_padding, rem) = buf.split_at(padded_len);
220    let (unpadded, _padding) = with_padding.split_at(unpadded_len);
221    Ok((rem, unpadded))
222}
223
224fn word_padding(unpadded_len: usize) -> usize {
225    match unpadded_len % 8 {
226        0 | 8 => 0,
227        nonzero => 8 - nonzero,
228    }
229}
230
231#[cfg(test)]
232mod tests {
233    use super::*;
234
235    #[test]
236    fn take_empty_bytes() {
237        let (trailing, parsed) = take_n_padded(0, &[1, 1, 1, 1]).unwrap();
238        assert_eq!(parsed, [] as [u8; 0]);
239        assert_eq!(trailing, [1, 1, 1, 1]);
240    }
241
242    #[test]
243    fn take_unpadded_bytes() {
244        let (trailing, parsed) = take_n_padded(8, &[5, 5, 5, 5, 5, 5, 5, 5, 1, 1, 1, 1]).unwrap();
245        assert_eq!(parsed, [5, 5, 5, 5, 5, 5, 5, 5]);
246        assert_eq!(trailing, [1, 1, 1, 1]);
247    }
248
249    #[test]
250    fn take_padded_bytes() {
251        let (trailing, parsed) = take_n_padded(6, &[5, 5, 5, 5, 5, 5, 0, 0, 1, 1, 1, 1]).unwrap();
252        assert_eq!(parsed, [5, 5, 5, 5, 5, 5]);
253        assert_eq!(trailing, [1, 1, 1, 1],);
254    }
255}