trace_recorder_parser/snapshot/
recorder_data.rs

1use crate::snapshot::event::{Event, EventParser, EventRecord, EventType};
2use crate::snapshot::markers::{DebugMarker, MarkerBytes};
3use crate::snapshot::object_properties::{ObjectProperties, ObjectPropertyTable};
4use crate::snapshot::symbol_table::{SymbolCrc6, SymbolTable};
5use crate::snapshot::Error;
6use crate::time::Frequency;
7use crate::types::{
8    Endianness, FloatEncoding, KernelPortIdentity, KernelVersion, ObjectClass, ObjectHandle,
9    OffsetBytes, Protocol, TrimmedString,
10};
11use byteordered::ByteOrdered;
12use std::collections::{BTreeMap, VecDeque};
13use std::io::{Read, Seek, SeekFrom};
14use tracing::{debug, error, warn};
15
16#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
17pub struct RecorderData {
18    pub protocol: Protocol,
19    pub kernel_version: KernelVersion,
20    pub kernel_port: KernelPortIdentity,
21    pub endianness: Endianness,
22    pub minor_version: u8,
23    pub irq_priority_order: u8,
24    pub filesize: u32,
25    // Note that num_events can be greater than max_events when the buffer is full/overwritten
26    pub num_events: u32,
27    pub max_events: u32,
28    pub next_free_index: u32,
29    pub buffer_is_full: bool,
30    pub frequency: Frequency,
31    pub abs_time_last_event: u32,
32    pub abs_time_last_event_second: u32,
33    pub recorder_active: bool,
34    pub isr_tail_chaining_threshold: u32,
35    pub heap_mem_usage: u32,
36    pub heap_mem_max_usage: u32,
37    pub is_using_16bit_handles: bool,
38    pub object_property_table: ObjectPropertyTable,
39    pub symbol_table: SymbolTable,
40    pub float_encoding: FloatEncoding,
41    pub internal_error_occured: bool,
42    pub system_info: String,
43
44    /// Offset of the recorder data start markers
45    start_offset: OffsetBytes,
46    /// Offset of the recorder data event data
47    event_data_offset: OffsetBytes,
48    // TODO - add user event buffer offset here when supported
49}
50
51impl RecorderData {
52    pub fn locate_and_parse<R: Read + Seek>(r: &mut R) -> Result<Self, Error> {
53        let mut tmp_buffer = VecDeque::with_capacity(1024);
54        let mut r = ByteOrdered::native(r);
55
56        // Locate the start marker bytes
57        let mut offset = r.stream_position()?;
58        tmp_buffer.clear();
59        tmp_buffer.resize(MarkerBytes::SIZE, 0);
60        r.read_exact(tmp_buffer.make_contiguous())?;
61        let start_offset = loop {
62            if tmp_buffer.make_contiguous() == MarkerBytes::Start.as_bytes() {
63                break offset;
64            }
65
66            let _ = tmp_buffer.pop_front();
67            tmp_buffer.push_back(r.read_u8()?);
68            offset += 1;
69        };
70
71        debug!(start_offset = start_offset, "Found start markers");
72        r.seek(SeekFrom::Start(start_offset))?;
73        MarkerBytes::Start.read(&mut r)?;
74
75        let kvi_pos = r.stream_position()?;
76        let mut kernel_version_identity: [u8; 2] = [0; 2];
77        r.read_exact(&mut kernel_version_identity)?;
78        let kernel_version = KernelVersion(kernel_version_identity);
79        let kernel_port = kernel_version
80            .port_identity()
81            .map_err(|e| Error::KernelVersion(kvi_pos, e.0))?;
82        let endianness = kernel_version
83            .endianness()
84            .map_err(|e| Error::KernelVersion(kvi_pos, e.0))?;
85        debug!(kernel_version = %kernel_version, kernel_port = %kernel_port, endianness = ?endianness, "Found kernel version");
86        let minor_version = r.read_u8()?;
87        debug!(minor_version = minor_version, "Found minor version");
88
89        if kernel_port != KernelPortIdentity::FreeRtos {
90            warn!("Kernel port {kernel_port} is not officially supported");
91        }
92
93        if minor_version != 7 {
94            warn!("Version {minor_version} is not officially supported");
95        }
96
97        let irq_priority_order = r.read_u8()?;
98
99        // The remaining fields are endian-aware
100        let mut r = ByteOrdered::new(r.into_inner(), byteordered::Endianness::from(endianness));
101        let filesize = r.read_u32()?;
102        debug!(filesize = filesize, "Found recorder data region size");
103
104        let num_events = r.read_u32()?;
105        let max_events = r.read_u32()?;
106        let next_free_index = r.read_u32()?;
107        let buffer_is_full = r.read_u32()?;
108        let frequency = Frequency(r.read_u32()?);
109        let abs_time_last_event = r.read_u32()?;
110        let abs_time_last_event_second = r.read_u32()?;
111        let recorder_active = r.read_u32()?;
112        let isr_tail_chaining_threshold = r.read_u32()?;
113        let heap_mem_max_usage = r.read_u32()?;
114        let heap_mem_usage = r.read_u32()?;
115        DebugMarker::Marker0.read(&mut r)?;
116        let is_using_16bit_handles = r.read_u32()? != 0;
117
118        if is_using_16bit_handles {
119            return Err(Error::Unsupported16bitHandles);
120        }
121
122        if frequency.is_unitless() {
123            warn!("Time base frequency is zero, units will be in ticks only");
124        }
125
126        // Object property table starts here
127        let object_property_table_offset = r.stream_position()?;
128        let num_object_classes = r.read_u32()?;
129        let object_property_table_size = r.read_u32()?;
130        debug!(
131            object_property_table_offset = object_property_table_offset,
132            num_object_classes = num_object_classes,
133            object_property_table_size = object_property_table_size,
134            "Found object property table region"
135        );
136
137        let num_object_classes_u16_allocation_size_words =
138            round_up_nearest_2(num_object_classes) as usize;
139        let num_object_classes_u8_allocation_size_words =
140            round_up_nearest_4(num_object_classes) as usize;
141
142        // This is used to calculate the index in the dynamic object table
143        // (handle - 1 - nofStaticObjects = index)
144        let num_objects_per_class: Vec<u16> = if is_using_16bit_handles {
145            let mut words = vec![0; num_object_classes_u16_allocation_size_words];
146            r.read_u16_into(&mut words)?;
147            words
148        } else {
149            let mut words = vec![0; num_object_classes_u8_allocation_size_words];
150            r.read_exact(&mut words)?;
151            words.into_iter().map(|w| w.into()).collect()
152        };
153
154        let mut name_len_per_class = vec![0; num_object_classes_u8_allocation_size_words];
155        r.read_exact(&mut name_len_per_class)?;
156
157        let mut total_bytes_per_class = vec![0; num_object_classes_u8_allocation_size_words];
158        r.read_exact(&mut total_bytes_per_class)?;
159
160        let mut start_index_of_class = vec![0; num_object_classes_u16_allocation_size_words];
161        r.read_u16_into(&mut start_index_of_class)?;
162
163        let pos_at_prop_table = r.stream_position()?;
164        let mut queue_object_properties = BTreeMap::new();
165        let mut semaphore_object_properties = BTreeMap::new();
166        let mut mutex_object_properties = BTreeMap::new();
167        let mut task_object_properties = BTreeMap::new();
168        let mut isr_object_properties = BTreeMap::new();
169        let mut timer_object_properties = BTreeMap::new();
170        let mut event_group_object_properties = BTreeMap::new();
171        let mut stream_buffer_object_properties = BTreeMap::new();
172        let mut message_buffer_object_properties = BTreeMap::new();
173        for obj_class in ObjectClass::enumerate().iter() {
174            let obj_class_index = obj_class.into_usize();
175            let num_objects = num_objects_per_class[obj_class_index];
176            let name_len = name_len_per_class[obj_class_index];
177            let total_bytes_per_obj = total_bytes_per_class[obj_class_index];
178            let start_index = start_index_of_class[obj_class_index];
179
180            if total_bytes_per_obj == 0 {
181                error!("Skipping empty object class {obj_class} property table entry");
182                // Keep on trying
183                continue;
184            }
185
186            if obj_class_index as u32 >= num_object_classes {
187                warn!("Skipping unsupported object class {obj_class} property table entry");
188                r.seek(SeekFrom::Current(i64::from(
189                    total_bytes_per_obj as u32 * num_objects as u32,
190                )))?;
191                continue;
192            }
193
194            let class_offset = r.stream_position()?;
195            if (class_offset - pos_at_prop_table) != u64::from(start_index) {
196                warn!("Offset of object class {obj_class} {class_offset}, relative to the property table {} doesn't match the reported start index {start_index}", class_offset - pos_at_prop_table);
197            }
198            let end_of_class =
199                class_offset + u64::from(num_objects as u32 * total_bytes_per_obj as u32);
200
201            // Object handles (traceHandle) == object index + 1
202            let mut raw_obj_handle = 1;
203
204            // Read each entry in the class
205            while r.stream_position()? < end_of_class {
206                let obj_start_pos = r.stream_position()?;
207
208                // Zero length name is invalid (pretty sure), but try and tolerate it
209                if name_len == 0 {
210                    warn!("Skipping object class {obj_class} entry because name length is zero");
211                    r.seek(SeekFrom::Current(i64::from(total_bytes_per_obj)))?;
212                    continue;
213                }
214
215                // Read object name
216                tmp_buffer.clear();
217                tmp_buffer.resize(name_len as _, 0);
218                r.read_exact(tmp_buffer.make_contiguous())?;
219
220                if tmp_buffer[0] == 0 {
221                    // Empty entry
222                    r.seek(SeekFrom::Current(i64::from(total_bytes_per_obj - name_len)))?;
223                    continue;
224                }
225
226                // First name byte can be 0x01 to indicate a used object that hasn't had a name set yet
227                let name = if tmp_buffer[0] == 0x01 {
228                    None
229                } else {
230                    Some(TrimmedString::from_raw(tmp_buffer.make_contiguous()).into())
231                };
232
233                // Read properties
234                let mut properties = [0; 4];
235                for p in properties.iter_mut().take(obj_class.properties_size()) {
236                    *p = r.read_u8()?;
237                }
238
239                // SAFETY: we initialize the raw_obj_handle to 1 above and only ever
240                // increment
241                let obj_handle = ObjectHandle::new_unchecked(raw_obj_handle);
242                raw_obj_handle = raw_obj_handle.saturating_add(1);
243
244                match obj_class {
245                    ObjectClass::Queue => {
246                        let obj = ObjectProperties::new(name, properties);
247                        debug!("Found object property {obj} at {obj_start_pos}");
248                        queue_object_properties.insert(obj_handle, obj);
249                    }
250                    ObjectClass::Semaphore => {
251                        let obj = ObjectProperties::new(name, properties);
252                        debug!("Found object property {obj} at {obj_start_pos}");
253                        semaphore_object_properties.insert(obj_handle, obj);
254                    }
255                    ObjectClass::Mutex => {
256                        let obj = ObjectProperties::new(name, properties);
257                        debug!("Found object property {obj} at {obj_start_pos}");
258                        mutex_object_properties.insert(obj_handle, obj);
259                    }
260                    ObjectClass::Task => {
261                        let obj = ObjectProperties::new(name, properties);
262                        debug!("Found object property {obj} at {obj_start_pos}");
263                        task_object_properties.insert(obj_handle, obj);
264                    }
265                    ObjectClass::Isr => {
266                        let obj = ObjectProperties::new(name, properties);
267                        debug!("Found object property {obj} at {obj_start_pos}");
268                        isr_object_properties.insert(obj_handle, obj);
269                    }
270                    ObjectClass::Timer => {
271                        let obj = ObjectProperties::new(name, properties);
272                        debug!("Found object property {obj} at {obj_start_pos}");
273                        timer_object_properties.insert(obj_handle, obj);
274                    }
275                    ObjectClass::EventGroup => {
276                        let obj = ObjectProperties::new(name, properties);
277                        debug!("Found object property {obj} at {obj_start_pos}");
278                        event_group_object_properties.insert(obj_handle, obj);
279                    }
280                    ObjectClass::StreamBuffer => {
281                        let obj = ObjectProperties::new(name, properties);
282                        debug!("Found object property {obj} at {obj_start_pos}");
283                        stream_buffer_object_properties.insert(obj_handle, obj);
284                    }
285                    ObjectClass::MessageBuffer => {
286                        let obj = ObjectProperties::new(name, properties);
287                        debug!("Found object property {obj} at {obj_start_pos}");
288                        message_buffer_object_properties.insert(obj_handle, obj);
289                    }
290                    ObjectClass::StateMachine => {
291                        // NOTE: unsupported
292                    }
293                }
294            }
295        }
296
297        // Seek past any remaining unused bytes from aligned allocation
298        let pos_after_prop_table = r.stream_position()?;
299        let prop_table_bytes_read = (pos_after_prop_table - pos_at_prop_table) as i64;
300        let prop_table_allocation_size = i64::from(round_up_nearest_4(object_property_table_size));
301        if prop_table_bytes_read < prop_table_allocation_size {
302            r.seek(SeekFrom::Current(
303                prop_table_allocation_size - prop_table_bytes_read,
304            ))?;
305        }
306
307        DebugMarker::Marker1.read(&mut r)?;
308
309        // Symbol table starts here
310        let symbol_table_offset = r.stream_position()?;
311        let symbol_table_size = r.read_u32()?;
312        debug!(
313            symbol_table_offset = symbol_table_offset,
314            symbol_table_size = symbol_table_size,
315            "Found symbol table region"
316        );
317
318        // symbolTableType.nextFreeSymbolIndex is initialized to 1,
319        // so the first 4 bytes are zero initialized.
320        // Entry 0 is reserved. Any reference to entry 0 implies NULL
321        let next_free_symbol_index = r.read_u32()?;
322        if next_free_symbol_index > symbol_table_size {
323            warn!("Next free symbol index {next_free_symbol_index} exceeds symbol table size {symbol_table_size}");
324        }
325        let end_of_symbol_table_region =
326            r.stream_position()? + u64::from(round_up_nearest_4(symbol_table_size));
327        let start_of_symbol_table_bytes = r.stream_position()?;
328        let end_of_symbol_entries = start_of_symbol_table_bytes + u64::from(next_free_symbol_index);
329
330        let unused_index_slot = r.read_u8()?;
331        if unused_index_slot != 0 {
332            warn!(
333                "Reserved symbol table entry 0 contains an invalid value 0x{unused_index_slot:X}"
334            );
335        }
336
337        // Read in the populated symbol table entries
338        let mut symbol_table = SymbolTable::default();
339        while r.stream_position()? < end_of_symbol_entries {
340            let start_of_symbol_table_entry = r.stream_position()?;
341
342            // 4-byte metadata
343            let _next_entry_index = r.read_u16()?;
344            let channel = r.read_u16()?;
345            // Followed by (double) null-terminated symbol string
346            tmp_buffer.clear();
347            loop {
348                let sym_byte = r.read_u8()?;
349                if sym_byte == 0 {
350                    // They double null-terminate for some reason, I think it's a bug and a waste :/
351                    let extra_null = r.read_u8()?;
352                    if extra_null != 0 {
353                        warn!(
354                            "Found non-zero NULL terminated symbol table entry at offeset {}",
355                            r.stream_position()?
356                        );
357                    }
358                    break;
359                } else {
360                    tmp_buffer.push_back(sym_byte);
361                }
362            }
363            let crc = SymbolCrc6::new(tmp_buffer.make_contiguous());
364            symbol_table.insert(
365                ObjectHandle::new(
366                    ((start_of_symbol_table_entry - start_of_symbol_table_bytes) & 0xFFFF) as u32,
367                )
368                .ok_or(Error::InvalidSymbolTableIndex(start_of_symbol_table_entry))?,
369                ObjectHandle::new(channel.into()),
370                crc,
371                TrimmedString::from_raw(tmp_buffer.make_contiguous()).into(),
372            );
373        }
374
375        // Seek past the unused symbol table entries
376        r.seek(SeekFrom::Start(end_of_symbol_table_region))?;
377
378        // Used for lookups - Up to 64 linked lists within the symbol table
379        // connecting all entries with the same 6 bit checksum.
380        // This field holds the current list heads.
381        // (index == crc6 of symbol, data == symbol table index)
382        // Only used for fast lookups on-device, so we skip over it.
383        r.seek(SeekFrom::Current(
384            (std::mem::size_of::<u16>() * SymbolTable::NUM_LATEST_ENTRY_OF_CHECKSUMS) as _,
385        ))?;
386
387        // When TRC_CFG_INCLUDE_FLOAT_SUPPORT == 1, the value should be (float) 1,
388        // otherwise (u32) 0.
389        // Also used for endian detection of floats
390        let float_encoding = FloatEncoding::from_bits(r.read_u32()?);
391
392        let internal_error_occured = r.read_u32()?;
393        if internal_error_occured != 0 {
394            warn!("The 'internal_error_occured' field is set to {internal_error_occured}");
395        }
396
397        DebugMarker::Marker2.read(&mut r)?;
398
399        // Read systemInfo string
400        tmp_buffer.clear();
401        tmp_buffer.resize(NUM_SYSTEM_INFO_BYTES, 0);
402        r.read_exact(tmp_buffer.make_contiguous())?;
403        let system_info = TrimmedString::from_raw(tmp_buffer.make_contiguous()).0;
404        if !system_info.is_empty() {
405            debug!(system_info = %system_info, "Found system info");
406        }
407
408        DebugMarker::Marker3.read(&mut r)?;
409
410        // Store the offset of the event data, 4-byte records, and skip over it
411        let event_data_offset = r.stream_position()?;
412        r.seek(SeekFrom::Current(4 * i64::from(max_events)))?;
413
414        // If TRC_CFG_USE_SEPARATE_USER_EVENT_BUFFER == 1 then this will be the bufferID field
415        // otherwise it's the first 16 bits of the endOfSecondaryBlocks field
416        let maybe_user_event_buffer_id = r.read_u16()?;
417        if maybe_user_event_buffer_id == 0 {
418            // TRC_CFG_USE_SEPARATE_USER_EVENT_BUFFER == 0
419            // Read the rest of endOfSecondaryBlocks (always zero)
420            let end_of_secondary_blocks = r.read_u16()?;
421            if end_of_secondary_blocks != 0 {
422                warn!("End of secondary blocks field ({end_of_secondary_blocks}) should be zero");
423            }
424        } else {
425            // TODO - add support for this and put info in the data
426            return Err(Error::UnsupportedUserEventBuffer);
427        }
428
429        MarkerBytes::End.read(&mut r)?;
430
431        Ok(RecorderData {
432            protocol: Protocol::Snapshot,
433            kernel_version,
434            kernel_port,
435            endianness,
436            minor_version,
437            irq_priority_order,
438            filesize,
439            num_events,
440            max_events,
441            next_free_index,
442            buffer_is_full: buffer_is_full != 0,
443            frequency,
444            abs_time_last_event,
445            abs_time_last_event_second,
446            recorder_active: recorder_active != 0,
447            isr_tail_chaining_threshold,
448            heap_mem_usage,
449            heap_mem_max_usage,
450            is_using_16bit_handles,
451            object_property_table: ObjectPropertyTable {
452                queue_object_properties,
453                semaphore_object_properties,
454                mutex_object_properties,
455                task_object_properties,
456                isr_object_properties,
457                timer_object_properties,
458                event_group_object_properties,
459                stream_buffer_object_properties,
460                message_buffer_object_properties,
461            },
462            symbol_table,
463            float_encoding,
464            internal_error_occured: internal_error_occured != 0,
465            system_info,
466
467            // Internal stuff
468            start_offset,
469            event_data_offset,
470        })
471    }
472
473    pub fn event_records<'r, R: Read + Seek + Send>(
474        &'r self,
475        r: &'r mut R,
476    ) -> Result<Box<dyn Iterator<Item = Result<EventRecord, Error>> + Send + 'r>, Error> {
477        if (self.num_events < self.max_events) || ((self.num_events % self.max_events) == 0) {
478            // Buffer is still still contiguous, can iterate from start of memory
479            let num_events_clamped = std::cmp::min(self.num_events, self.max_events);
480            r.seek(SeekFrom::Start(self.event_data_offset))?;
481            Ok(Box::new((0..num_events_clamped).map(|_| {
482                let mut record = [0; EventRecord::SIZE];
483                r.read_exact(&mut record)?;
484                Ok(EventRecord::new(record))
485            })))
486        } else {
487            // Buffer full and has wrapped, chain the two regions together
488            // starting at the tail to end of the buffer region, then start
489            // of the memory region to head
490
491            // Seek to the tail
492            let num_tail_region_events = self.max_events - self.next_free_index;
493            let tail_offset = self.next_free_index * EventRecord::SIZE as u32;
494            r.seek(SeekFrom::Start(
495                self.event_data_offset + u64::from(tail_offset),
496            ))?;
497
498            let iter = (0..self.max_events).map(move |event_index| {
499                let mut record = [0; EventRecord::SIZE];
500                r.read_exact(&mut record)?;
501
502                // Last tail record, seek to the start of the memory region for head region
503                if event_index + 1 == num_tail_region_events {
504                    r.seek(SeekFrom::Start(self.event_data_offset))?;
505                }
506
507                Ok(EventRecord::new(record))
508            });
509
510            Ok(Box::new(iter))
511        }
512    }
513
514    pub fn events<'r, R: Read + Seek + Send>(
515        &'r self,
516        r: &'r mut R,
517    ) -> Result<impl Iterator<Item = Result<(EventType, Event), Error>> + 'r, Error> {
518        let mut parser = EventParser::new(self.endianness.into());
519        let iter = self.event_records(r)?.filter_map(move |item| match item {
520            Ok(er) => match parser
521                .parse(&self.object_property_table, &self.symbol_table, er)
522                .map_err(Error::from)
523            {
524                Ok(maybe_ev) => maybe_ev.map(Ok),
525                Err(e) => Some(Err(e)),
526            },
527            Err(e) => Some(Err(e)),
528        });
529        Ok(iter)
530    }
531}
532
533/// Max size of the system info string
534const NUM_SYSTEM_INFO_BYTES: usize = 80;
535
536// Rounded up to the closest multiple of 2
537// Used in the data struct allocation to avoid alignment issues
538fn round_up_nearest_2(n: u32) -> u32 {
539    2 * ((n + 1) / 2)
540}
541
542// Rounded up to the closest multiple of 4
543// Used in the data struct allocation to avoid alignment issues
544fn round_up_nearest_4(n: u32) -> u32 {
545    4 * ((n + 3) / 4)
546}