1use crate::init::Ticks;
6use crate::session::ResolveCtx;
7use crate::thread::{ProcessKoid, ProcessRef, ThreadKoid, ThreadRef};
8use crate::{
9 PROFILER_RECORD_TYPE, ParseError, ParseResult, ParseWarning, take_n_padded, trace_header,
10};
11use flyweights::FlyStr;
12use nom::Parser;
13use nom::combinator::all_consuming;
14use nom::number::complete::le_u64;
15
16const MODULE_RECORD_TYPE: u8 = 0;
17const MAPPING_RECORD_TYPE: u8 = 1;
18const BACKTRACE_RECORD_TYPE: u8 = 2;
19
20macro_rules! profiler_header {
21 ($name:ident $(($profiler_ty:expr))? { $($record_specific:tt)* }) => {
22 trace_header! {
23 $name (PROFILER_RECORD_TYPE) {
24 $($record_specific)*
25 u8, profiler_sub_type: 16, 19;
26 u8, thread_ref: 20, 27;
27 } => |_h: &$name| {
28 $(if _h.profiler_sub_type() != $profiler_ty {
29 return Err(ParseError::WrongType {
30 context: stringify!($name),
31 expected: $profiler_ty,
32 observed: _h.profiler_sub_type(),
33 });
34 })?
35 Ok(())
36 }
37 }
38 };
39}
40
41profiler_header! {BaseProfilerRecordHeader{}}
42
43profiler_header! {
44 ModuleRecordHeader(MODULE_RECORD_TYPE) {
45 u16, module_id: 28, 43;
47 u8, name_length: 44, 51;
49 u8, build_id_length: 52, 59;
51 }
52}
53
54profiler_header! {
55 MmapRecordHeader(MAPPING_RECORD_TYPE) {
56 u16, module_id: 28, 43;
58 u8, flags: 44, 46;
60 }
61}
62
63profiler_header! {
64 BacktraceRecordHeader(BACKTRACE_RECORD_TYPE) {
65 u8, num_records: 28, 35;
67 u32, flags: 36, 63;
69 }
70}
71
72#[derive(Debug, PartialEq)]
73pub(super) enum RawProfilerRecordType<'a> {
74 RawModuleRecord(RawModuleRecord<'a>),
75 RawMappingRecord(RawMappingRecord),
76 RawBacktraceRecord(RawBacktraceRecord),
77 Unknown { raw_type: u8 },
78}
79
80#[derive(Debug, PartialEq)]
81pub(super) struct RawModuleRecord<'a> {
82 ticks: Ticks,
83 process: ProcessRef,
84 thread: ThreadRef,
85 module_id: u16,
86 name: String,
87 build_id: &'a [u8],
88}
89
90impl<'a> RawModuleRecord<'a> {
91 pub(super) fn parse(buf: &'a [u8]) -> ParseResult<'a, Self> {
92 let (buf, header) = ModuleRecordHeader::parse(buf)?;
93 let (rem, payload) = header.take_payload(buf)?;
94 let (payload, ticks) = Ticks::parse(payload)?;
95 let (payload, process) = ProcessRef::parse(header.thread_ref(), payload)?;
96 let (payload, thread) = ThreadRef::parse(header.thread_ref(), payload)?;
97 let (payload, name) = parse_padded_module_name(header.name_length() as usize, payload)?;
98 let (empty, build_id) =
99 all_consuming(|p| take_n_padded(header.build_id_length() as usize, p))
100 .parse(payload)?;
101 assert!(empty.is_empty(), "all_consuming must not return any remaining buffer");
102 Ok((rem, Self { ticks, process, thread, module_id: header.module_id(), name, build_id }))
103 }
104}
105
106pub(crate) fn parse_padded_module_name(unpadded_len: usize, buf: &[u8]) -> ParseResult<'_, String> {
107 let (rem, bytes) = take_n_padded(unpadded_len, buf)?;
108 Ok((rem, String::from_utf8_lossy(bytes).into_owned()))
109}
110
111#[derive(Debug, PartialEq)]
112pub(super) struct RawMappingRecord {
113 ticks: Ticks,
114 process: ProcessRef,
115 thread: ThreadRef,
116 module_id: u16,
117 start_addr: u64,
118 range: u64,
119 vaddr: u64,
120 flags: u8,
121}
122
123impl RawMappingRecord {
124 pub(super) fn parse(buf: &[u8]) -> ParseResult<'_, Self> {
125 let (buf, header) = MmapRecordHeader::parse(buf)?;
126 let (rem, payload) = header.take_payload(buf)?;
127 let (payload, ticks) = Ticks::parse(payload)?;
128 let (payload, process) = ProcessRef::parse(header.thread_ref(), payload)?;
129 let (payload, thread) = ThreadRef::parse(header.thread_ref(), payload)?;
130 let (payload, start_addr) = le_u64(payload)?;
131 let (payload, range) = le_u64(payload)?;
132 let (empty, vaddr) = le_u64(payload)?;
133 assert!(empty.is_empty(), "after vaddr must not return any remaining buffer");
134 Ok((
135 rem,
136 Self {
137 ticks,
138 process,
139 thread,
140 module_id: header.module_id(),
141 start_addr,
142 range,
143 vaddr,
144 flags: header.flags(),
145 },
146 ))
147 }
148}
149
150#[derive(Debug, PartialEq)]
151pub(super) struct RawBacktraceRecord {
152 ticks: Ticks,
153 process: ProcessRef,
154 thread: ThreadRef,
155 num_records: u8,
156 data: Vec<u64>,
157}
158
159impl RawBacktraceRecord {
160 pub(super) fn parse(buf: &[u8]) -> ParseResult<'_, Self> {
161 let (buf, header) = BacktraceRecordHeader::parse(buf)?;
162 let (rem, payload) = header.take_payload(buf)?;
163 let (payload, ticks) = Ticks::parse(payload)?;
164 let (payload, process) = ProcessRef::parse(header.thread_ref(), payload)?;
165 let (payload, thread) = ThreadRef::parse(header.thread_ref(), payload)?;
166 let (empty, data) = all_consuming(nom::multi::count(le_u64, header.num_records() as usize))
167 .parse(payload)?;
168 assert!(empty.is_empty(), "all_consuming must not return any remaining buffer");
169 Ok((rem, Self { ticks, process, thread, num_records: header.num_records(), data }))
170 }
171}
172
173impl<'a> RawProfilerRecordType<'a> {
174 pub(super) fn parse(buf: &'a [u8]) -> ParseResult<'a, Self> {
175 use nom::combinator::map;
176 match BaseProfilerRecordHeader::parse(buf)?.1.profiler_sub_type() {
177 MODULE_RECORD_TYPE => {
178 map(RawModuleRecord::parse, |module| Self::RawModuleRecord(module)).parse(buf)
179 }
180 MAPPING_RECORD_TYPE => {
181 map(RawMappingRecord::parse, |mapping| Self::RawMappingRecord(mapping)).parse(buf)
182 }
183 BACKTRACE_RECORD_TYPE => {
184 map(RawBacktraceRecord::parse, |backtrace| Self::RawBacktraceRecord(backtrace))
185 .parse(buf)
186 }
187 unknown => Ok((buf, Self::Unknown { raw_type: unknown })),
188 }
189 }
190}
191
192#[derive(Clone, Debug, PartialEq)]
193pub enum ProfilerRecord {
194 Module(ModuleRecord),
195 Mapping(MappingRecord),
196 Backtrace(BacktraceRecord),
197}
198
199impl ProfilerRecord {
200 pub(super) fn resolve<'a>(
201 ctx: &mut ResolveCtx,
202 raw: RawProfilerRecordType<'a>,
203 ) -> Option<Self> {
204 match raw {
205 RawProfilerRecordType::RawModuleRecord(raw) => {
206 Some(Self::Module(ModuleRecord::resolve(ctx, raw)))
207 }
208 RawProfilerRecordType::RawMappingRecord(raw) => {
209 Some(Self::Mapping(MappingRecord::resolve(ctx, raw)))
210 }
211 RawProfilerRecordType::RawBacktraceRecord(raw) => {
212 Some(Self::Backtrace(BacktraceRecord::resolve(ctx, raw)))
213 }
214 RawProfilerRecordType::Unknown { raw_type } => {
215 ctx.add_warning(ParseWarning::UnknownProfilerRecordType(raw_type));
216 None
217 }
218 }
219 }
220}
221
222#[derive(Clone, Debug, PartialEq)]
223pub struct ModuleRecord {
224 pub timestamp: i64,
225 pub process: ProcessKoid,
226 pub thread: ThreadKoid,
227 pub module_id: u16,
228 pub name: FlyStr,
229 pub build_id: Vec<u8>,
230}
231
232impl ModuleRecord {
233 pub(super) fn resolve<'a>(ctx: &mut ResolveCtx, raw: RawModuleRecord<'a>) -> Self {
234 Self {
235 timestamp: ctx.resolve_ticks(raw.ticks),
236 process: ctx.resolve_process(raw.process),
237 thread: ctx.resolve_thread(raw.thread),
238 module_id: raw.module_id,
239 name: raw.name.into(),
240 build_id: raw.build_id.to_vec(),
241 }
242 }
243}
244
245#[derive(Clone, Debug, PartialEq)]
246pub struct MappingRecord {
247 pub timestamp: i64,
248 pub process: ProcessKoid,
249 pub thread: ThreadKoid,
250 pub module_id: u16,
251 pub start_addr: u64,
252 pub range: u64,
253 pub vaddr: u64,
254 pub flags: u8,
255}
256
257impl MappingRecord {
258 pub(super) fn resolve(ctx: &mut ResolveCtx, raw: RawMappingRecord) -> Self {
259 Self {
260 timestamp: ctx.resolve_ticks(raw.ticks),
261 process: ctx.resolve_process(raw.process),
262 thread: ctx.resolve_thread(raw.thread),
263 module_id: raw.module_id,
264 start_addr: raw.start_addr,
265 range: raw.range,
266 vaddr: raw.vaddr,
267 flags: raw.flags,
268 }
269 }
270}
271
272#[derive(Clone, Debug, PartialEq)]
273pub struct BacktraceRecord {
274 pub timestamp: i64,
275 pub process: ProcessKoid,
276 pub thread: ThreadKoid,
277 pub num_records: u8,
278 pub data: Vec<u64>,
279}
280
281impl BacktraceRecord {
282 pub(super) fn resolve(ctx: &mut ResolveCtx, raw: RawBacktraceRecord) -> Self {
283 Self {
284 timestamp: ctx.resolve_ticks(raw.ticks),
285 process: ctx.resolve_process(raw.process),
286 thread: ctx.resolve_thread(raw.thread),
287 num_records: raw.num_records,
288 data: raw.data,
289 }
290 }
291}
292
293#[cfg(test)]
294mod tests {
295 use super::*;
296
297 #[test]
298 fn test_parse_module_record_from_hardcoded_bytes() {
299 let mut buffer = vec![];
300
301 let record_type: u64 = 10;
302 let sub_type: u64 = 0; let thread_ref: u64 = 0; let module_id: u64 = 99;
305 let name_len: u64 = 14; let build_id_len: u64 = 20;
307 let size_words: u64 = 9; let header_val: u64 = (build_id_len << 52)
310 | (name_len << 44)
311 | (module_id << 28)
312 | (thread_ref << 20)
313 | (sub_type << 16)
314 | (size_words << 4)
315 | record_type;
316
317 buffer.extend_from_slice(&header_val.to_le_bytes());
318 buffer.extend_from_slice(&[0x88, 0x13, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]); buffer.extend_from_slice(&[0x7b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]); buffer.extend_from_slice(&[0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]); buffer.extend_from_slice(&[
322 0x74, 0x65, 0x73, 0x74, 0x5f, 0x6d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x2e, 0x73, 0x6f,
324 0x00, 0x00,
325 ]);
326 buffer.extend_from_slice(&[
327 0x68, 0x92, 0x0b, 0xd0, 0x6a, 0x81, 0x49, 0xb5, 0x80, 0x04, 0xce, 0xa8, 0xa0, 0xdd,
329 0x26, 0x0b, 0x05, 0xe8, 0x7d, 0x57,
330 ]);
331 buffer.extend_from_slice(&[0; 4]); let (empty, raw_record) = RawProfilerRecordType::parse(&buffer).unwrap();
334 assert!(empty.is_empty(), "Buffer should be fully consumed");
335
336 let mut ctx = ResolveCtx::new();
337 let resolved = ProfilerRecord::resolve(&mut ctx, raw_record).unwrap();
338
339 let expected = ProfilerRecord::Module(ModuleRecord {
340 timestamp: ctx.resolve_ticks(Ticks(5000)),
341 process: ProcessKoid::from(123u64),
342 thread: ThreadKoid::from(0u64),
343 module_id: 99,
344 name: "test_module.so".into(),
345 build_id: vec![
346 0x68, 0x92, 0x0b, 0xd0, 0x6a, 0x81, 0x49, 0xb5, 0x80, 0x04, 0xce, 0xa8, 0xa0, 0xdd,
347 0x26, 0x0b, 0x05, 0xe8, 0x7d, 0x57,
348 ],
349 });
350 assert_eq!(resolved, expected);
351 }
352
353 #[test]
354 fn test_parse_mapping_record_from_hardcoded_bytes() {
355 let mut buffer = vec![];
356
357 let record_type: u64 = 10;
358 let sub_type: u64 = 1; let thread_ref: u64 = 0; let module_id: u64 = 101;
361 let flags: u64 = 1;
362 let size_words: u64 = 7; let header_val: u64 = (flags << 44)
365 | (module_id << 28)
366 | (thread_ref << 20)
367 | (sub_type << 16)
368 | (size_words << 4)
369 | record_type;
370
371 buffer.extend_from_slice(&header_val.to_le_bytes());
372 buffer.extend_from_slice(&[0x70, 0x17, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]); buffer.extend_from_slice(&[0xc8, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]); buffer.extend_from_slice(&[0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]); buffer.extend_from_slice(&[0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]); buffer.extend_from_slice(&[0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]); buffer.extend_from_slice(&[0x00, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]); let (empty, raw_record) = RawProfilerRecordType::parse(&buffer).unwrap();
380 assert!(empty.is_empty(), "Buffer should be fully consumed");
381
382 let mut ctx = ResolveCtx::new();
383 let resolved = ProfilerRecord::resolve(&mut ctx, raw_record).unwrap();
384
385 let expected = ProfilerRecord::Mapping(MappingRecord {
386 timestamp: ctx.resolve_ticks(Ticks(6000)),
387 process: ProcessKoid::from(456u64),
388 thread: ThreadKoid::from(0u64),
389 module_id: 101,
390 start_addr: 0x1000,
391 range: 0x2000,
392 vaddr: 0x3000,
393 flags: 1,
394 });
395 assert_eq!(resolved, expected);
396 }
397
398 #[test]
399 fn test_parse_backtrace_record_from_hardcoded_bytes() {
400 let mut buffer = vec![];
401
402 let record_type: u64 = 10;
403 let sub_type: u64 = 2; let thread_ref: u64 = 0; let num_records: u64 = 2;
406 let flags: u64 = 2;
407 let size_words: u64 = 6; let header_val: u64 = (flags << 36)
410 | (num_records << 28)
411 | (thread_ref << 20)
412 | (sub_type << 16)
413 | (size_words << 4)
414 | record_type;
415
416 buffer.extend_from_slice(&header_val.to_le_bytes());
417 buffer.extend_from_slice(&[0x58, 0x1B, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]); buffer.extend_from_slice(&[0x15, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]); buffer.extend_from_slice(&[0xDB, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]); buffer.extend_from_slice(&[0x99, 0x14, 0x4b, 0x96, 0x2a, 0x40, 0x00, 0x00]); buffer.extend_from_slice(&[0x3b, 0x29, 0xe0, 0x4a, 0x00, 0x00, 0x00, 0x00]); let (empty, raw_record) = RawProfilerRecordType::parse(&buffer).unwrap();
424 assert!(empty.is_empty(), "Buffer should be fully consumed");
425
426 let mut ctx = ResolveCtx::new();
427 let resolved = ProfilerRecord::resolve(&mut ctx, raw_record).unwrap();
428
429 let expected = ProfilerRecord::Backtrace(BacktraceRecord {
430 timestamp: ctx.resolve_ticks(Ticks(7000)),
431 process: ProcessKoid::from(789u64),
432 thread: ThreadKoid::from(987u64),
433 num_records: 2,
434 data: vec![0x402a964b1499, 0x4ae0293b],
435 });
436 assert_eq!(resolved, expected);
437 }
438}