1use 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 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
107const MAX_15_BIT_LEN: usize = 0x7FFF;
109const _: () = assert!((crate::MAX_RECORD_SIZE_WORDS - 1) * 8 <= MAX_15_BIT_LEN);
112
113fn 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
120pub 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 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 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 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 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 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 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}