1use crate::args::{Arg, RawArg};
6use crate::error::ParseWarning;
7use crate::init::Ticks;
8use crate::session::ResolveCtx;
9use crate::string::StringRef;
10use crate::thread::{ProcessKoid, ProcessRef, ThreadKoid, ThreadRef};
11use crate::{
12 take_n_padded, trace_header, ParseResult, Provider, BLOB_RECORD_TYPE, LARGE_RECORD_TYPE,
13};
14use flyweights::FlyStr;
15use nom::combinator::all_consuming;
16use nom::number::complete::le_u64;
17use nom::Parser;
18
19const BLOB_TYPE_DATA: u8 = 0x01;
20const BLOB_TYPE_LAST_BRANCH: u8 = 0x02;
21const BLOB_TYPE_PERFETTO: u8 = 0x03;
22
23const LARGE_BLOB_WITH_METADATA_TYPE: u8 = 0;
24const LARGE_BLOB_NO_METADATA_TYPE: u8 = 1;
25
26#[derive(Clone, Debug, PartialEq)]
27pub struct BlobRecord {
28 pub provider: Option<Provider>,
29 pub name: FlyStr,
30 pub ty: BlobType,
31 pub bytes: Vec<u8>,
32}
33
34impl BlobRecord {
35 pub(super) fn resolve(ctx: &mut ResolveCtx, raw: RawBlobRecord<'_>) -> Self {
36 Self {
37 provider: ctx.current_provider(),
38 name: ctx.resolve_str(raw.name),
39 ty: raw.ty,
40 bytes: raw.bytes.to_owned(),
41 }
42 }
43}
44
45#[derive(Debug, PartialEq)]
46pub(super) struct RawBlobRecord<'a> {
47 name: StringRef<'a>,
48 ty: BlobType,
49 bytes: &'a [u8],
50}
51
52impl<'a> RawBlobRecord<'a> {
53 pub(super) fn parse(buf: &'a [u8]) -> ParseResult<'a, Self> {
54 let (buf, header) = BlobHeader::parse(buf)?;
55 let ty = BlobType::from(header.blob_format_type());
56 let (rem, payload) = header.take_payload(buf)?;
57 let (payload, name) = StringRef::parse(header.name_ref(), payload)?;
58
59 let (_should_be_empty, bytes) = take_n_padded(header.payload_len() as usize, payload)?;
63 Ok((rem, Self { name, ty, bytes }))
64 }
65}
66
67#[derive(Clone, Debug, PartialEq)]
68#[repr(u8)]
69pub enum BlobType {
70 Data = BLOB_TYPE_DATA,
71 LastBranch = BLOB_TYPE_LAST_BRANCH,
72 Perfetto = BLOB_TYPE_PERFETTO,
73 Unknown { raw: u8 },
74}
75
76impl From<u8> for BlobType {
77 fn from(raw: u8) -> Self {
78 match raw {
79 BLOB_TYPE_DATA => BlobType::Data,
80 BLOB_TYPE_LAST_BRANCH => BlobType::LastBranch,
81 BLOB_TYPE_PERFETTO => BlobType::Perfetto,
82 raw => BlobType::Unknown { raw },
83 }
84 }
85}
86
87impl Into<u8> for BlobType {
88 fn into(self) -> u8 {
89 match self {
90 BlobType::Data => BLOB_TYPE_DATA,
91 BlobType::LastBranch => BLOB_TYPE_LAST_BRANCH,
92 BlobType::Perfetto => BLOB_TYPE_PERFETTO,
93 BlobType::Unknown { raw } => raw,
94 }
95 }
96}
97
98trace_header! {
99 BlobHeader (BLOB_RECORD_TYPE) {
100 u16, name_ref: 16, 31;
101 u16, payload_len: 32, 46;
102 u8, blob_format_type: 48, 55;
103 }
104}
105
106#[derive(Clone, Debug, PartialEq)]
107pub struct LargeBlobRecord {
108 pub provider: Option<Provider>,
109 pub ty: BlobType,
110 pub category: FlyStr,
111 pub name: FlyStr,
112 pub bytes: Vec<u8>,
113 pub metadata: Option<LargeBlobMetadata>,
114}
115
116impl LargeBlobRecord {
117 pub(super) fn resolve(ctx: &mut ResolveCtx, raw: RawLargeBlobRecord<'_>) -> Option<Self> {
118 let (bytes, metadata) = match raw.payload {
119 RawLargeBlobPayload::BytesAndMetadata(bytes, metadata) => {
120 (bytes.to_owned(), Some(LargeBlobMetadata::resolve(ctx, metadata)))
121 }
122 RawLargeBlobPayload::BytesOnly(bytes) => (bytes.to_owned(), None),
123 RawLargeBlobPayload::UnknownLargeBlobType { raw_type, .. } => {
124 ctx.add_warning(ParseWarning::UnknownLargeBlobType(raw_type));
125 return None;
126 }
127 };
128 Some(Self {
129 provider: ctx.current_provider(),
130 ty: raw.ty,
131 category: ctx.resolve_str(raw.category),
132 name: ctx.resolve_str(raw.name),
133 bytes,
134 metadata,
135 })
136 }
137}
138
139#[derive(Debug, PartialEq)]
140pub(super) struct RawLargeBlobRecord<'a> {
141 ty: BlobType,
142 category: StringRef<'a>,
143 name: StringRef<'a>,
144 payload: RawLargeBlobPayload<'a>,
145}
146
147#[derive(Debug, PartialEq)]
148enum RawLargeBlobPayload<'a> {
149 BytesOnly(&'a [u8]),
150 BytesAndMetadata(&'a [u8], RawLargeBlobMetadata<'a>),
151 UnknownLargeBlobType { raw_type: u8, remaining_bytes: &'a [u8] },
152}
153
154impl<'a> RawLargeBlobRecord<'a> {
155 pub(super) fn parse(buf: &'a [u8]) -> ParseResult<'a, Self> {
156 let (buf, header) = LargeBlobHeader::parse(buf)?;
157 let ty = BlobType::from(header.blob_format_type());
158 let (rem, payload) = header.take_payload(buf)?;
159 let (payload, format_header) =
160 nom::combinator::map(le_u64, LargeBlobFormatHeader).parse(payload)?;
161 let (payload, category) = StringRef::parse(format_header.category_ref(), payload)?;
162 let (payload, name) = StringRef::parse(format_header.name_ref(), payload)?;
163 let (payload, metadata) = match header.large_record_type() {
164 LARGE_BLOB_WITH_METADATA_TYPE => {
165 let (payload, ticks) = Ticks::parse(payload)?;
166 let (payload, process) = ProcessRef::parse(format_header.thread_ref(), payload)?;
167 let (payload, thread) = ThreadRef::parse(format_header.thread_ref(), payload)?;
168 let (payload, args) = RawArg::parse_n(format_header.num_args(), payload)?;
169 (payload, Some(RawLargeBlobMetadata { ticks, process, thread, args }))
170 }
171 LARGE_BLOB_NO_METADATA_TYPE => (payload, None),
172 unknown => {
173 let payload = RawLargeBlobPayload::UnknownLargeBlobType {
176 raw_type: unknown,
177 remaining_bytes: payload,
178 };
179 return Ok((rem, Self { ty, category, name, payload }));
180 }
181 };
182 let (payload, blob_size) = le_u64(payload)?;
183
184 let (empty, bytes) =
185 all_consuming(|p| take_n_padded(blob_size as usize, p)).parse(payload)?;
186 assert_eq!(empty, [] as [u8; 0], "all_consuming must not return any trailing bytes");
187
188 let payload = if let Some(metadata) = metadata {
189 RawLargeBlobPayload::BytesAndMetadata(bytes, metadata)
190 } else {
191 RawLargeBlobPayload::BytesOnly(bytes)
192 };
193
194 Ok((rem, Self { ty, category, name, payload }))
195 }
196}
197
198#[derive(Clone, Debug, PartialEq)]
199pub struct LargeBlobMetadata {
200 pub timestamp: i64,
201 pub process: ProcessKoid,
202 pub thread: ThreadKoid,
203 pub args: Vec<Arg>,
204}
205
206impl LargeBlobMetadata {
207 fn resolve(ctx: &mut ResolveCtx, raw: RawLargeBlobMetadata<'_>) -> Self {
208 Self {
209 timestamp: ctx.resolve_ticks(raw.ticks),
210 process: ctx.resolve_process(raw.process),
211 thread: ctx.resolve_thread(raw.thread),
212 args: Arg::resolve_n(ctx, raw.args),
213 }
214 }
215}
216
217#[derive(Debug, PartialEq)]
218pub(super) struct RawLargeBlobMetadata<'a> {
219 ticks: Ticks,
220 process: ProcessRef,
221 thread: ThreadRef,
222 args: Vec<RawArg<'a>>,
223}
224
225trace_header! {
226 LargeBlobHeader (max_size_bit: 35) (u32) (LARGE_RECORD_TYPE) {
227 u8, large_record_type: 36, 39;
228 u8, blob_format_type: 40, 43;
229 }
230}
231
232bitfield::bitfield! {
233 struct LargeBlobFormatHeader(u64);
234 impl Debug;
235
236 u16, category_ref, set_category_ref: 15, 0;
237 u16, name_ref, set_name_ref: 31, 16;
238
239 u8, num_args, set_num_args: 35, 32;
241 u8, thread_ref, set_thread_ref: 43, 36;
242}
243
244#[cfg(test)]
245mod tests {
246 use super::*;
247 use crate::fxt_builder::FxtBuilder;
248 use crate::RawTraceRecord;
249 use std::num::{NonZeroU16, NonZeroU8};
250
251 #[test]
252 fn blob_name_index() {
253 let payload = &[
254 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23,
255 ][..];
256
257 let mut header = BlobHeader::empty();
258 header.set_name_ref(15);
259 header.set_payload_len(payload.len() as u16);
260 header.set_blob_format_type(BLOB_TYPE_DATA);
261
262 assert_parses_to_record!(
263 FxtBuilder::new(header).atom(payload).build(),
264 RawTraceRecord::Blob(RawBlobRecord {
265 name: StringRef::Index(NonZeroU16::new(15).unwrap()),
266 ty: BlobType::Data,
267 bytes: &payload,
268 }),
269 );
270 }
271
272 #[test]
273 fn blob_long_blob() {
274 let payload = &[0xEFu8; 1024][..];
275
276 let mut header = BlobHeader::empty();
277 header.set_name_ref(15);
278 header.set_payload_len(payload.len() as u16);
279 header.set_blob_format_type(BLOB_TYPE_DATA);
280
281 assert_parses_to_record!(
282 FxtBuilder::new(header).atom(payload).build(),
283 RawTraceRecord::Blob(RawBlobRecord {
284 name: StringRef::Index(NonZeroU16::new(15).unwrap()),
285 ty: BlobType::Data,
286 bytes: &payload,
287 }),
288 );
289 }
290
291 #[test]
292 fn blob_name_inline() {
293 let name = "foo_blob";
294 let payload = &[
295 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24,
296 25,
297 ][..];
298
299 let mut header = BlobHeader::empty();
300 header.set_name_ref(name.len() as u16 | crate::string::STRING_REF_INLINE_BIT);
301 header.set_payload_len(payload.len() as u16);
302 header.set_blob_format_type(BLOB_TYPE_PERFETTO);
303
304 assert_parses_to_record!(
305 FxtBuilder::new(header).atom(name).atom(payload).build(),
306 RawTraceRecord::Blob(RawBlobRecord {
307 name: StringRef::Inline(name),
308 ty: BlobType::Perfetto,
309 bytes: payload,
310 }),
311 );
312 }
313
314 #[test]
315 fn large_blob_no_metadata() {
316 let payload = &[
317 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24,
318 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36,
319 ][..];
320
321 let mut header = LargeBlobHeader::empty();
322 header.set_blob_format_type(BLOB_TYPE_DATA);
323 header.set_large_record_type(LARGE_BLOB_NO_METADATA_TYPE);
324
325 let mut format_header = LargeBlobFormatHeader(0);
326 format_header.set_category_ref(7);
327 format_header.set_name_ref(8);
328
329 assert_parses_to_record!(
330 FxtBuilder::new(header)
331 .atom(format_header.0.to_le_bytes())
332 .atom(payload.len().to_le_bytes())
333 .atom(payload)
334 .build(),
335 RawTraceRecord::LargeBlob(RawLargeBlobRecord {
336 ty: BlobType::Data,
337 category: StringRef::Index(NonZeroU16::new(7).unwrap()),
338 name: StringRef::Index(NonZeroU16::new(8).unwrap()),
339 payload: RawLargeBlobPayload::BytesOnly(payload),
340 })
341 );
342 }
343
344 #[test]
345 fn large_blob_with_metadata() {
346 let payload = &[
347 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24,
348 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36,
349 ][..];
350
351 let mut header = LargeBlobHeader::empty();
352 header.set_blob_format_type(BLOB_TYPE_DATA);
353 header.set_large_record_type(LARGE_BLOB_WITH_METADATA_TYPE);
354
355 let mut format_header = LargeBlobFormatHeader(0);
356 format_header.set_category_ref(7);
357 format_header.set_name_ref(8);
358 format_header.set_thread_ref(31);
359 format_header.set_num_args(1);
360
361 let mut arg_header = crate::args::U32Header::empty();
362 arg_header.set_name_ref(10);
363 arg_header.set_value(100);
364
365 assert_parses_to_record!(
366 FxtBuilder::new(header)
367 .atom(format_header.0.to_le_bytes())
368 .atom(1024u64.to_le_bytes())
369 .atom(FxtBuilder::new(arg_header).build())
370 .atom(payload.len().to_le_bytes())
371 .atom(payload)
372 .build(),
373 RawTraceRecord::LargeBlob(RawLargeBlobRecord {
374 ty: BlobType::Data,
375 category: StringRef::Index(NonZeroU16::new(7).unwrap()),
376 name: StringRef::Index(NonZeroU16::new(8).unwrap()),
377 payload: RawLargeBlobPayload::BytesAndMetadata(
378 payload,
379 RawLargeBlobMetadata {
380 ticks: Ticks(1024),
381 process: ProcessRef::Index(NonZeroU8::new(31).unwrap()),
382 thread: ThreadRef::Index(NonZeroU8::new(31).unwrap()),
383 args: vec![RawArg {
384 name: StringRef::Index(NonZeroU16::new(10).unwrap()),
385 value: crate::args::RawArgValue::Unsigned32(100),
386 }],
387 },
388 )
389 })
390 );
391 }
392}