Skip to main content

fxt/
blob.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
5use crate::args::{Arg, RawArg};
6use crate::error::ParseWarning;
7use crate::fxt_builder::FxtBuilder;
8use crate::init::Ticks;
9use crate::session::ResolveCtx;
10use crate::string::StringRef;
11use crate::thread::{ProcessKoid, ProcessRef, ThreadKoid, ThreadRef};
12use crate::{
13    BLOB_RECORD_TYPE, LARGE_RECORD_TYPE, ParseResult, Provider, take_n_padded, trace_header,
14};
15use flyweights::FlyStr;
16use nom::Parser;
17use nom::combinator::all_consuming;
18use nom::number::complete::le_u64;
19
20const BLOB_TYPE_DATA: u8 = 0x01;
21const BLOB_TYPE_LAST_BRANCH: u8 = 0x02;
22const BLOB_TYPE_PERFETTO: u8 = 0x03;
23
24const LARGE_BLOB_WITH_METADATA_TYPE: u8 = 0;
25const LARGE_BLOB_NO_METADATA_TYPE: u8 = 1;
26
27#[derive(Clone, Debug, PartialEq)]
28pub struct BlobRecord {
29    pub provider: Option<Provider>,
30    pub name: FlyStr,
31    pub ty: BlobType,
32    pub bytes: Vec<u8>,
33}
34
35impl BlobRecord {
36    pub(super) fn resolve(ctx: &mut ResolveCtx, raw: RawBlobRecord<'_>) -> Self {
37        Self {
38            provider: ctx.current_provider(),
39            name: ctx.resolve_str(raw.name),
40            ty: raw.ty,
41            bytes: raw.bytes.to_owned(),
42        }
43    }
44}
45
46#[derive(Debug, PartialEq)]
47pub(super) struct RawBlobRecord<'a> {
48    name: StringRef<'a>,
49    ty: BlobType,
50    bytes: &'a [u8],
51}
52
53impl<'a> RawBlobRecord<'a> {
54    pub(super) fn parse(buf: &'a [u8]) -> ParseResult<'a, Self> {
55        let (buf, header) = BlobHeader::parse(buf)?;
56        let ty = BlobType::from(header.blob_format_type());
57        let (rem, payload) = header.take_payload(buf)?;
58        let (payload, name) = StringRef::parse(header.name_ref(), payload)?;
59
60        // NB: most records are parsed with all_consuming for the last element to ensure we've
61        // used all of the payload reported in the header, but in practice it seems that some
62        // providers emit some trailing words.
63        let (_should_be_empty, bytes) = take_n_padded(header.payload_len() as usize, payload)?;
64        Ok((rem, Self { name, ty, bytes }))
65    }
66}
67
68#[derive(Clone, Debug, PartialEq)]
69#[repr(u8)]
70pub enum BlobType {
71    Data = BLOB_TYPE_DATA,
72    LastBranch = BLOB_TYPE_LAST_BRANCH,
73    Perfetto = BLOB_TYPE_PERFETTO,
74    Unknown { raw: u8 },
75}
76
77impl From<u8> for BlobType {
78    fn from(raw: u8) -> Self {
79        match raw {
80            BLOB_TYPE_DATA => BlobType::Data,
81            BLOB_TYPE_LAST_BRANCH => BlobType::LastBranch,
82            BLOB_TYPE_PERFETTO => BlobType::Perfetto,
83            raw => BlobType::Unknown { raw },
84        }
85    }
86}
87
88impl Into<u8> for BlobType {
89    fn into(self) -> u8 {
90        match self {
91            BlobType::Data => BLOB_TYPE_DATA,
92            BlobType::LastBranch => BLOB_TYPE_LAST_BRANCH,
93            BlobType::Perfetto => BLOB_TYPE_PERFETTO,
94            BlobType::Unknown { raw } => raw,
95        }
96    }
97}
98
99trace_header! {
100    BlobHeader (BLOB_RECORD_TYPE) {
101        u16, name_ref: 16, 31;
102        u16, payload_len: 32, 46;
103        u8, blob_format_type: 48, 55;
104    }
105}
106
107/// Largest value of the 15-bit inline string length and blob payload length header fields.
108const MAX_15_BIT_LEN: usize = 0x7FFF;
109// A record that fits in `MAX_RECORD_SIZE_WORDS` keeps both lengths within their fields, so
110// `build_blob_record()` only needs to check the record size.
111const _: () = assert!((crate::MAX_RECORD_SIZE_WORDS - 1) * 8 <= MAX_15_BIT_LEN);
112
113/// Returns the size in words of the blob record that [`build_blob_record()`] builds for a
114/// `name_len`-byte name and a `payload_len`-byte payload: the header word, then the name and
115/// the payload, each padded to 8 bytes.
116fn blob_record_size_words(name_len: usize, payload_len: usize) -> usize {
117    1 + name_len.div_ceil(8) + payload_len.div_ceil(8)
118}
119
120/// Builds the words of a blob record of type `ty` named `name` that carries `payload`.
121///
122/// The name is written inline, so the record doesn't depend on a string record elsewhere in
123/// the trace.
124///
125/// Returns `None` if the record is longer than [`crate::MAX_RECORD_SIZE_WORDS`].
126pub fn build_blob_record(name: &str, ty: BlobType, payload: &[u8]) -> Option<Vec<u64>> {
127    if blob_record_size_words(name.len(), payload.len()) > crate::MAX_RECORD_SIZE_WORDS {
128        return None;
129    }
130    let mut header = BlobHeader::empty();
131    // Lossless: the size check above keeps both lengths within their 15-bit fields.
132    header.set_name_ref(fxt_layout::StringRefHeader::inline(name.len() as u16).bits());
133    header.set_payload_len(payload.len() as u16);
134    header.set_blob_format_type(ty.into());
135    Some(FxtBuilder::new(header).atom(name).atom(payload).build())
136}
137
138#[derive(Clone, Debug, PartialEq)]
139pub struct LargeBlobRecord {
140    pub provider: Option<Provider>,
141    pub ty: BlobType,
142    pub category: FlyStr,
143    pub name: FlyStr,
144    pub bytes: Vec<u8>,
145    pub metadata: Option<LargeBlobMetadata>,
146}
147
148impl LargeBlobRecord {
149    pub(super) fn resolve(ctx: &mut ResolveCtx, raw: RawLargeBlobRecord<'_>) -> Option<Self> {
150        let (bytes, metadata) = match raw.payload {
151            RawLargeBlobPayload::BytesAndMetadata(bytes, metadata) => {
152                (bytes.to_owned(), Some(LargeBlobMetadata::resolve(ctx, metadata)))
153            }
154            RawLargeBlobPayload::BytesOnly(bytes) => (bytes.to_owned(), None),
155            RawLargeBlobPayload::UnknownLargeBlobType { raw_type, .. } => {
156                ctx.add_warning(ParseWarning::UnknownLargeBlobType(raw_type));
157                return None;
158            }
159        };
160        Some(Self {
161            provider: ctx.current_provider(),
162            ty: raw.ty,
163            category: ctx.resolve_str(raw.category),
164            name: ctx.resolve_str(raw.name),
165            bytes,
166            metadata,
167        })
168    }
169}
170
171#[derive(Debug, PartialEq)]
172pub(super) struct RawLargeBlobRecord<'a> {
173    ty: BlobType,
174    category: StringRef<'a>,
175    name: StringRef<'a>,
176    payload: RawLargeBlobPayload<'a>,
177}
178
179#[derive(Debug, PartialEq)]
180enum RawLargeBlobPayload<'a> {
181    BytesOnly(&'a [u8]),
182    BytesAndMetadata(&'a [u8], RawLargeBlobMetadata<'a>),
183    UnknownLargeBlobType { raw_type: u8, remaining_bytes: &'a [u8] },
184}
185
186impl<'a> RawLargeBlobRecord<'a> {
187    pub(super) fn parse(buf: &'a [u8]) -> ParseResult<'a, Self> {
188        let (buf, header) = LargeBlobHeader::parse(buf)?;
189        let ty = BlobType::from(header.blob_format_type());
190        let (rem, payload) = header.take_payload(buf)?;
191        let (payload, format_header) =
192            nom::combinator::map(le_u64, LargeBlobFormatHeader).parse(payload)?;
193        let (payload, category) = StringRef::parse(format_header.category_ref(), payload)?;
194        let (payload, name) = StringRef::parse(format_header.name_ref(), payload)?;
195        let (payload, metadata) = match header.large_record_type() {
196            LARGE_BLOB_WITH_METADATA_TYPE => {
197                let (payload, ticks) = Ticks::parse(payload)?;
198                let (payload, process) = ProcessRef::parse(format_header.thread_ref(), payload)?;
199                let (payload, thread) = ThreadRef::parse(format_header.thread_ref(), payload)?;
200                let (payload, args) = RawArg::parse_n(format_header.num_args(), payload)?;
201                (payload, Some(RawLargeBlobMetadata { ticks, process, thread, args }))
202            }
203            LARGE_BLOB_NO_METADATA_TYPE => (payload, None),
204            unknown => {
205                // Without knowing which metadata type we have, we don't know where the blob size
206                // and bytes payload start, so we'll put them all together.
207                let payload = RawLargeBlobPayload::UnknownLargeBlobType {
208                    raw_type: unknown,
209                    remaining_bytes: payload,
210                };
211                return Ok((rem, Self { ty, category, name, payload }));
212            }
213        };
214        let (payload, blob_size) = le_u64(payload)?;
215
216        let (empty, bytes) =
217            all_consuming(|p| take_n_padded(blob_size as usize, p)).parse(payload)?;
218        assert_eq!(empty, [] as [u8; 0], "all_consuming must not return any trailing bytes");
219
220        let payload = if let Some(metadata) = metadata {
221            RawLargeBlobPayload::BytesAndMetadata(bytes, metadata)
222        } else {
223            RawLargeBlobPayload::BytesOnly(bytes)
224        };
225
226        Ok((rem, Self { ty, category, name, payload }))
227    }
228}
229
230#[derive(Clone, Debug, PartialEq)]
231pub struct LargeBlobMetadata {
232    pub timestamp: i64,
233    pub process: ProcessKoid,
234    pub thread: ThreadKoid,
235    pub args: Vec<Arg>,
236}
237
238impl LargeBlobMetadata {
239    fn resolve(ctx: &mut ResolveCtx, raw: RawLargeBlobMetadata<'_>) -> Self {
240        Self {
241            timestamp: ctx.resolve_ticks(raw.ticks),
242            process: ctx.resolve_process(raw.process),
243            thread: ctx.resolve_thread(raw.thread),
244            args: Arg::resolve_n(ctx, raw.args),
245        }
246    }
247}
248
249#[derive(Debug, PartialEq)]
250pub(super) struct RawLargeBlobMetadata<'a> {
251    ticks: Ticks,
252    process: ProcessRef,
253    thread: ThreadRef,
254    args: Vec<RawArg<'a>>,
255}
256
257trace_header! {
258    LargeBlobHeader (max_size_bit: 35) (u32) (LARGE_RECORD_TYPE) {
259        u8, large_record_type: 36, 39;
260        u8, blob_format_type: 40, 43;
261    }
262}
263
264bitfield::bitfield! {
265    struct LargeBlobFormatHeader(u64);
266    impl Debug;
267
268    u16, category_ref, set_category_ref: 15, 0;
269    u16, name_ref, set_name_ref: 31, 16;
270
271    // These are only meaningful if large_record_type includes metadata.
272    u8, num_args, set_num_args: 35, 32;
273    u8, thread_ref, set_thread_ref: 43, 36;
274}
275
276#[cfg(test)]
277mod tests {
278    use super::*;
279    use crate::RawTraceRecord;
280    use std::num::{NonZeroU8, NonZeroU16};
281
282    #[test]
283    fn blob_name_index() {
284        let payload = &[
285            1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23,
286        ][..];
287
288        let mut header = BlobHeader::empty();
289        header.set_name_ref(15);
290        header.set_payload_len(payload.len() as u16);
291        header.set_blob_format_type(BLOB_TYPE_DATA);
292
293        assert_parses_to_record!(
294            FxtBuilder::new(header).atom(payload).build(),
295            RawTraceRecord::Blob(RawBlobRecord {
296                name: StringRef::Index(NonZeroU16::new(15).unwrap()),
297                ty: BlobType::Data,
298                bytes: &payload,
299            }),
300        );
301    }
302
303    #[test]
304    fn blob_long_blob() {
305        let payload = &[0xEFu8; 1024][..];
306
307        let mut header = BlobHeader::empty();
308        header.set_name_ref(15);
309        header.set_payload_len(payload.len() as u16);
310        header.set_blob_format_type(BLOB_TYPE_DATA);
311
312        assert_parses_to_record!(
313            FxtBuilder::new(header).atom(payload).build(),
314            RawTraceRecord::Blob(RawBlobRecord {
315                name: StringRef::Index(NonZeroU16::new(15).unwrap()),
316                ty: BlobType::Data,
317                bytes: &payload,
318            }),
319        );
320    }
321
322    #[test]
323    fn blob_name_inline() {
324        let name = "foo_blob";
325        let payload = &[
326            1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24,
327            25,
328        ][..];
329
330        let mut header = BlobHeader::empty();
331        header.set_name_ref(fxt_layout::StringRefHeader::inline(name.len() as u16).bits());
332        header.set_payload_len(payload.len() as u16);
333        header.set_blob_format_type(BLOB_TYPE_PERFETTO);
334
335        assert_parses_to_record!(
336            FxtBuilder::new(header).atom(name).atom(payload).build(),
337            RawTraceRecord::Blob(RawBlobRecord {
338                name: StringRef::Inline(name),
339                ty: BlobType::Perfetto,
340                bytes: payload,
341            }),
342        );
343    }
344
345    /// Checks that `build_blob_record()` builds a record of the size that
346    /// `blob_record_size_words()` reports, and that it parses back to its inputs.
347    fn check_built_blob_record(name: &str, ty: BlobType, payload: &[u8]) {
348        let words = build_blob_record(name, ty.clone(), payload).expect("record fits");
349        assert_eq!(words.len(), blob_record_size_words(name.len(), payload.len()));
350        assert_parses_to_record!(
351            words,
352            RawTraceRecord::Blob(RawBlobRecord {
353                name: StringRef::Inline(name),
354                ty,
355                bytes: payload
356            }),
357        );
358    }
359
360    #[test]
361    fn build_blob_record_pads_name_and_payload() {
362        // Neither length is a whole number of words.
363        check_built_blob_record("abc", BlobType::Perfetto, &[1, 2, 3, 4, 5, 6, 7, 8, 9]);
364    }
365
366    #[test]
367    fn build_blob_record_empty_payload() {
368        check_built_blob_record("foo_blob", BlobType::Data, &[]);
369    }
370
371    #[test]
372    fn build_blob_record_largest_record() {
373        let name = "foo_blob";
374        let payload = vec![0xEF; (crate::MAX_RECORD_SIZE_WORDS - 2) * 8];
375        assert_eq!(blob_record_size_words(name.len(), payload.len()), crate::MAX_RECORD_SIZE_WORDS);
376        check_built_blob_record(name, BlobType::Perfetto, &payload);
377    }
378
379    #[test]
380    fn build_blob_record_rejects_oversized_record() {
381        let payload = vec![0xEF; (crate::MAX_RECORD_SIZE_WORDS - 2) * 8 + 1];
382        assert_eq!(build_blob_record("foo_blob", BlobType::Perfetto, &payload), None);
383    }
384
385    #[test]
386    fn large_blob_no_metadata() {
387        let payload = &[
388            1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24,
389            25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36,
390        ][..];
391
392        let mut header = LargeBlobHeader::empty();
393        header.set_blob_format_type(BLOB_TYPE_DATA);
394        header.set_large_record_type(LARGE_BLOB_NO_METADATA_TYPE);
395
396        let mut format_header = LargeBlobFormatHeader(0);
397        format_header.set_category_ref(7);
398        format_header.set_name_ref(8);
399
400        assert_parses_to_record!(
401            FxtBuilder::new(header)
402                .atom(format_header.0.to_le_bytes())
403                .atom(payload.len().to_le_bytes())
404                .atom(payload)
405                .build(),
406            RawTraceRecord::LargeBlob(RawLargeBlobRecord {
407                ty: BlobType::Data,
408                category: StringRef::Index(NonZeroU16::new(7).unwrap()),
409                name: StringRef::Index(NonZeroU16::new(8).unwrap()),
410                payload: RawLargeBlobPayload::BytesOnly(payload),
411            })
412        );
413    }
414
415    #[test]
416    fn large_blob_with_metadata() {
417        let payload = &[
418            1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24,
419            25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36,
420        ][..];
421
422        let mut header = LargeBlobHeader::empty();
423        header.set_blob_format_type(BLOB_TYPE_DATA);
424        header.set_large_record_type(LARGE_BLOB_WITH_METADATA_TYPE);
425
426        let mut format_header = LargeBlobFormatHeader(0);
427        format_header.set_category_ref(7);
428        format_header.set_name_ref(8);
429        format_header.set_thread_ref(31);
430        format_header.set_num_args(1);
431
432        let mut arg_header = crate::args::U32Header::empty();
433        arg_header.set_name_ref(10);
434        arg_header.set_value(100);
435
436        assert_parses_to_record!(
437            FxtBuilder::new(header)
438                .atom(format_header.0.to_le_bytes())
439                .atom(1024u64.to_le_bytes())
440                .atom_words(FxtBuilder::new(arg_header).build())
441                .atom(payload.len().to_le_bytes())
442                .atom(payload)
443                .build(),
444            RawTraceRecord::LargeBlob(RawLargeBlobRecord {
445                ty: BlobType::Data,
446                category: StringRef::Index(NonZeroU16::new(7).unwrap()),
447                name: StringRef::Index(NonZeroU16::new(8).unwrap()),
448                payload: RawLargeBlobPayload::BytesAndMetadata(
449                    payload,
450                    RawLargeBlobMetadata {
451                        ticks: Ticks(1024),
452                        process: ProcessRef::Index(NonZeroU8::new(31).unwrap()),
453                        thread: ThreadRef::Index(NonZeroU8::new(31).unwrap()),
454                        args: vec![RawArg {
455                            name: StringRef::Index(NonZeroU16::new(10).unwrap()),
456                            value: crate::args::RawArgValue::Unsigned32(100),
457                        }],
458                    },
459                )
460            })
461        );
462    }
463
464    #[test]
465    fn large_blob_large_size() {
466        let payload = vec![0xEFu8; 32768];
467        let mut header = LargeBlobHeader::empty();
468        header.set_blob_format_type(BLOB_TYPE_DATA);
469        header.set_large_record_type(LARGE_BLOB_NO_METADATA_TYPE);
470
471        let mut format_header = LargeBlobFormatHeader(0);
472        format_header.set_category_ref(7);
473        format_header.set_name_ref(8);
474
475        let mut buf = Vec::new();
476        // size in words: Large Blob Header (1 word) + format_header (1 word) + payload_len (1 word) + 32768 bytes payload (4096 words) = 4099 words.
477        let size_words = (8 + 8 + 8 + payload.len() as u64) / 8;
478        header.set_size_words(size_words as u32);
479
480        buf.extend_from_slice(&header.0.to_le_bytes());
481        buf.extend_from_slice(&format_header.0.to_le_bytes());
482        buf.extend_from_slice(&(payload.len() as u64).to_le_bytes());
483        buf.extend_from_slice(&payload);
484
485        assert_parses_to_record!(
486            buf,
487            RawTraceRecord::LargeBlob(RawLargeBlobRecord {
488                ty: BlobType::Data,
489                category: StringRef::Index(NonZeroU16::new(7).unwrap()),
490                name: StringRef::Index(NonZeroU16::new(8).unwrap()),
491                payload: RawLargeBlobPayload::BytesOnly(&payload[..]),
492            })
493        );
494    }
495}