Skip to main content

starnix_core/perf/
mod.rs

1// Copyright 2025 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::mm::PAGE_SIZE;
6use crate::task::dynamic_thread_spawner::SpawnRequestBuilder;
7use anyhow::Context;
8use fidl_fuchsia_cpu_profiler as profiler;
9use fuchsia_component::client::connect_to_protocol;
10use fuchsia_runtime;
11use futures::StreamExt;
12use futures::channel::mpsc as future_mpsc;
13use std::collections::HashMap;
14use std::error::Error;
15use std::sync::atomic::{AtomicU64, Ordering};
16use std::sync::{Arc, OnceLock, mpsc as sync_mpsc};
17use zerocopy::{Immutable, IntoBytes};
18
19use fxt::TraceRecord;
20use fxt::profiler::ProfilerRecord;
21use fxt::session::SessionParser;
22use seq_lock::{SeqLock, SeqLockable, WriteSize};
23use starnix_logging::{log_error, log_info, log_warn, track_stub};
24use starnix_sync::{LockDepMutex, LockDepRwLock, PerfEventLevel, PerfFormatIdLookupTableLock};
25use starnix_syscalls::{SUCCESS, SyscallArg, SyscallResult};
26use starnix_uapi::arch32::{
27    PERF_EVENT_IOC_DISABLE, PERF_EVENT_IOC_ENABLE, PERF_EVENT_IOC_ID,
28    PERF_EVENT_IOC_MODIFY_ATTRIBUTES, PERF_EVENT_IOC_PAUSE_OUTPUT, PERF_EVENT_IOC_PERIOD,
29    PERF_EVENT_IOC_QUERY_BPF, PERF_EVENT_IOC_REFRESH, PERF_EVENT_IOC_RESET, PERF_EVENT_IOC_SET_BPF,
30    PERF_EVENT_IOC_SET_FILTER, PERF_EVENT_IOC_SET_OUTPUT, PERF_RECORD_MISC_USER,
31    perf_event_sample_format_PERF_SAMPLE_CALLCHAIN, perf_event_sample_format_PERF_SAMPLE_ID,
32    perf_event_sample_format_PERF_SAMPLE_IDENTIFIER, perf_event_sample_format_PERF_SAMPLE_IP,
33    perf_event_sample_format_PERF_SAMPLE_PERIOD, perf_event_sample_format_PERF_SAMPLE_READ,
34    perf_event_sample_format_PERF_SAMPLE_REGS_USER,
35    perf_event_sample_format_PERF_SAMPLE_STACK_USER, perf_event_sample_format_PERF_SAMPLE_TID,
36    perf_event_sample_format_PERF_SAMPLE_TIME, perf_event_type_PERF_RECORD_LOST,
37    perf_event_type_PERF_RECORD_SAMPLE,
38};
39use starnix_uapi::errors::Errno;
40use starnix_uapi::open_flags::OpenFlags;
41use starnix_uapi::uapi::{
42    perf_sample_regs_abi_PERF_SAMPLE_REGS_ABI_32, perf_sample_regs_abi_PERF_SAMPLE_REGS_ABI_64,
43    perf_sample_regs_abi_PERF_SAMPLE_REGS_ABI_NONE,
44};
45use starnix_uapi::user_address::UserRef;
46use starnix_uapi::{
47    errno, error, from_status_like_fdio, perf_event_attr, perf_event_header,
48    perf_event_mmap_page__bindgen_ty_1, perf_event_read_format_PERF_FORMAT_GROUP,
49    perf_event_read_format_PERF_FORMAT_ID, perf_event_read_format_PERF_FORMAT_LOST,
50    perf_event_read_format_PERF_FORMAT_TOTAL_TIME_ENABLED,
51    perf_event_read_format_PERF_FORMAT_TOTAL_TIME_RUNNING, tid_t, uapi,
52};
53
54use crate::security::{self, TargetTaskType};
55use crate::task::Kernel;
56use crate::task::tracing::{LinuxIdentity, PidKoidSession};
57
58static READ_FORMAT_ID_GENERATOR: AtomicU64 = AtomicU64::new(0);
59// Size of the VMO backing each event's metadata page and ring buffer; mmap
60// lengths up to this size are accepted. perf readers map one metadata page
61// plus a power-of-two data area, and commonly ask for megabytes (e.g. 256
62// data pages or more), so leave generous headroom. With circular writes the
63// data area no longer needs to hold a whole session's records.
64//
65// We currently preallocate a fixed size for the VMO because we do not dynamically
66// resize it when mapped.
67// TODO(https://fxbug.dev/540986386): Support dynamic resizing or pass the requested size.
68const ESTIMATED_MMAP_BUFFER_SIZE: u64 = 16 * 1024 * 1024;
69// Size of a PERF_RECORD_LOST record in bytes:
70// perf_event_header (8) + sample_id (8) + lost_events (8) = 24.
71const LOST_RECORD_SIZE: u64 = 24;
72// Register indices in the profiler's register capture block.
73const AARCH64_REG_PC: usize = 32;
74const AARCH64_REG_SP: usize = 31;
75const AARCH32_REG_R15: usize = 15; // AArch32 PC
76const AARCH32_REG_R13: usize = 13; // AArch32 SP
77
78mod event;
79pub use event::{TraceEvent, TraceEventQueue, TraceEventQueueList};
80
81pub mod lockless_ring_buffer;
82
83#[repr(C)]
84#[derive(Copy, Clone, IntoBytes, Immutable)]
85struct LostRecord {
86    header: perf_event_header,
87    sample_id: u64,
88    lost_events: u64,
89}
90
91#[repr(C)]
92#[derive(Copy, Clone, IntoBytes, Immutable)]
93struct PerfMetadataHeader {
94    version: u32,
95    compat_version: u32,
96}
97
98#[repr(C)]
99#[derive(Copy, Clone, IntoBytes, Immutable)]
100struct PerfMetadataValue {
101    lock: u32,
102    index: u32,
103    offset: i64,
104    time_enabled: u64,
105    time_running: u64,
106    __bindgen_anon_1: perf_event_mmap_page__bindgen_ty_1,
107    pmc_width: u16,
108    time_shift: u16,
109    time_mult: u32,
110    time_offset: u64,
111    time_zero: u64,
112    size: u32,
113    __reserved_1: u32,
114    time_cycles: u64,
115    time_mask: u64,
116    __reserved: [u8; 928usize],
117    data_head: u64,
118    data_tail: u64,
119    data_offset: u64,
120    data_size: u64,
121    aux_head: u64,
122    aux_tail: u64,
123    aux_offset: u64,
124    aux_size: u64,
125}
126
127// SAFETY: `PerfMetadataValue` can be safely written to shared memory in 8-byte chunks.
128// This is because it is composed of two u32s followed by only u64s.
129// The first u32 is the `lock` field, which is why HAS_INLINE_SEQUENCE is true.
130unsafe impl SeqLockable for PerfMetadataValue {
131    const WRITE_SIZE: WriteSize = WriteSize::Eight;
132    const HAS_INLINE_SEQUENCE: bool = true;
133    const VMO_NAME: &'static [u8] = b"starnix:perf_event";
134}
135
136struct PerfState {
137    // This table maps a group leader's file object id to its unique u64 "format ID".
138    //
139    // When a sample is generated for any event in a group, we use this
140    // "format ID" from the group leader as the value for *both* the
141    // `PERF_SAMPLE_ID` and `PERF_SAMPLE_IDENTIFIER` fields.
142    format_id_lookup_table: LockDepMutex<HashMap<FileObjectId, u64>, PerfFormatIdLookupTableLock>,
143}
144
145impl Default for PerfState {
146    fn default() -> Self {
147        Self { format_id_lookup_table: Default::default() }
148    }
149}
150
151fn get_perf_state(kernel: &Arc<Kernel>) -> Arc<PerfState> {
152    kernel.expando.get_or_init(PerfState::default)
153}
154
155uapi::check_arch_independent_layout! {
156    perf_event_attr {
157        type_, // "type" is a reserved keyword so add a trailing underscore.
158        size,
159        config,
160        __bindgen_anon_1,
161        sample_type,
162        read_format,
163        _bitfield_1,
164        __bindgen_anon_2,
165        bp_type,
166        __bindgen_anon_3,
167        __bindgen_anon_4,
168        branch_sample_type,
169        sample_regs_user,
170        sample_stack_user,
171        clockid,
172        sample_regs_intr,
173        aux_watermark,
174        sample_max_stack,
175        __reserved_2,
176        aux_sample_size,
177        __reserved_3,
178        sig_data,
179        config3,
180    }
181}
182
183#[derive(Clone, Copy, Debug, PartialEq)]
184enum IoctlOp {
185    Enable,
186    Disable,
187}
188
189struct PerfEventFileState {
190    attr: perf_event_attr,
191    rf_value: u64, // "count" for the config we passed in for the event.
192    // The most recent timestamp (ns) where we changed into an enabled state
193    // i.e. the most recent time we got an ENABLE ioctl().
194    most_recent_enabled_time: u64,
195    // Sum of all previous enablement segment durations (ns). If we are
196    // currently in an enabled state, explicitly does NOT include the current
197    // segment.
198    total_time_running: u64,
199    rf_id: u64,
200    sample_id: u64,
201    _rf_lost: u64,
202    disabled: u64,
203    sample_type: u64,
204    // Handle to blob that stores all the perf data that a user may want.
205    // At the moment it only stores some metadata and backtraces (bts).
206    perf_data_vmo: zx::Vmo,
207    // Channel used to send IoctlOps to start/stop sampling.
208    ioctl_sender: future_mpsc::Sender<(IoctlOp, sync_mpsc::Sender<()>)>,
209}
210
211// Have an implementation for PerfEventFileState because VMO
212// doesn't have Default so we can't derive it.
213impl PerfEventFileState {
214    fn new(
215        attr: perf_event_attr,
216        rf_value: u64,
217        disabled: u64,
218        sample_type: u64,
219        perf_data_vmo: zx::Vmo,
220        ioctl_sender: future_mpsc::Sender<(IoctlOp, sync_mpsc::Sender<()>)>,
221    ) -> PerfEventFileState {
222        PerfEventFileState {
223            attr,
224            rf_value,
225            most_recent_enabled_time: 0,
226            total_time_running: 0,
227            rf_id: 0,
228            sample_id: 0,
229            _rf_lost: 0,
230            disabled,
231            sample_type,
232            perf_data_vmo,
233            ioctl_sender,
234        }
235    }
236}
237
238pub struct PerfEventFile {
239    _tid: tid_t,
240    _cpu: i32,
241    perf_event_file: LockDepRwLock<PerfEventFileState, PerfEventLevel>,
242    // The security state for this PerfEventFile.
243    pub security_state: security::PerfEventState,
244    seq_lock: Arc<OnceLock<Result<SeqLock<PerfMetadataHeader, PerfMetadataValue>, Errno>>>,
245}
246
247// PerfEventFile object that implements FileOps.
248// See https://man7.org/linux/man-pages/man2/perf_event_open.2.html for
249// implementation details.
250// This object can be saved as a FileDescriptor.
251impl FileOps for PerfEventFile {
252    // Don't need to implement seek or sync for PerfEventFile.
253    fileops_impl_nonseekable!();
254    fileops_impl_noop_sync!();
255
256    fn close(self: Box<Self>, file: &FileObjectState, current_task: &CurrentTask) {
257        {
258            let mut perf_event_file = self.perf_event_file.write();
259            // Ensure we disable so we clean up resources if the user closes without disabling.
260            perf_event_file.disabled = 1;
261            ping_receiver(perf_event_file.ioctl_sender.clone(), IoctlOp::Disable);
262        }
263        let perf_state = get_perf_state(&current_task.kernel);
264        let mut events = perf_state.format_id_lookup_table.lock();
265        events.remove(&file.id);
266    }
267
268    // See "Reading results" section of https://man7.org/linux/man-pages/man2/perf_event_open.2.html.
269    fn read(
270        &self,
271        _file: &FileObject,
272        current_task: &CurrentTask,
273        _offset: usize,
274        data: &mut dyn OutputBuffer,
275    ) -> Result<usize, Errno> {
276        // Create/calculate and return the ReadFormatData object.
277        // If we create it earlier we might want to change it and it's immutable once created.
278        let read_format_data = {
279            // Once we get the `value` or count from kernel, we can change this to a read()
280            // call instead of write().
281            let mut perf_event_file = self.perf_event_file.write();
282
283            security::check_perf_event_read_access(current_task, &self)?;
284
285            let mut total_time_running_including_curr = perf_event_file.total_time_running;
286
287            // Only update values if enabled (either by perf_event_attr or ioctl ENABLE call).
288            if perf_event_file.disabled == 0 {
289                // Calculate the value or "count" of the config we're interested in.
290                // This value should reflect the value we are counting (defined in the config).
291                // E.g. for PERF_COUNT_SW_CPU_CLOCK it would return the value from the CPU clock.
292                // For now we just return rf_value + 1.
293                track_stub!(
294                    TODO("https://fxbug.dev/402938671"),
295                    "[perf_event_open] implement read_format value"
296                );
297                perf_event_file.rf_value += 1;
298
299                // Update time duration.
300                let curr_time = zx::MonotonicInstant::get().into_nanos() as u64;
301                total_time_running_including_curr +=
302                    curr_time - perf_event_file.most_recent_enabled_time;
303            }
304
305            let mut output = Vec::<u8>::new();
306            let value = perf_event_file.rf_value.to_ne_bytes();
307            output.extend(value);
308
309            let read_format = perf_event_file.attr.read_format;
310
311            if (read_format & perf_event_read_format_PERF_FORMAT_TOTAL_TIME_ENABLED as u64) != 0 {
312                // Total time (ns) event was enabled and running (currently same as TIME_RUNNING).
313                output.extend(total_time_running_including_curr.to_ne_bytes());
314            }
315            if (read_format & perf_event_read_format_PERF_FORMAT_TOTAL_TIME_RUNNING as u64) != 0 {
316                // Total time (ns) event was enabled and running (currently same as TIME_ENABLED).
317                output.extend(total_time_running_including_curr.to_ne_bytes());
318            }
319            if (read_format & perf_event_read_format_PERF_FORMAT_ID as u64) != 0 {
320                // Adds a 64-bit unique value that corresponds to the event group.
321                output.extend(perf_event_file.rf_id.to_ne_bytes());
322            }
323
324            output
325        };
326
327        // The regular read() call allows the case where the bytes-we-want-to-read-in won't
328        // fit in the output buffer. However, for perf_event_open's read(), "If you attempt to read
329        // into a buffer that is not big enough to hold the data, the error ENOSPC results."
330        if data.available() < read_format_data.len() {
331            return error!(ENOSPC);
332        }
333        track_stub!(
334            TODO("https://fxbug.dev/402453955"),
335            "[perf_event_open] implement remaining error handling"
336        );
337
338        data.write(&read_format_data)
339    }
340
341    fn ioctl(
342        &self,
343        _file: &FileObject,
344        current_task: &CurrentTask,
345        op: u32,
346        _arg: SyscallArg,
347    ) -> Result<SyscallResult, Errno> {
348        track_stub!(
349            TODO("https://fxbug.dev/405463320"),
350            "[perf_event_open] implement PERF_IOC_FLAG_GROUP"
351        );
352        security::check_perf_event_write_access(current_task, &self)?;
353        let mut perf_event_file = self.perf_event_file.write();
354        match op {
355            PERF_EVENT_IOC_ENABLE => {
356                if perf_event_file.disabled != 0 {
357                    perf_event_file.disabled = 0; // 0 = false.
358                    perf_event_file.most_recent_enabled_time =
359                        zx::MonotonicInstant::get().into_nanos() as u64;
360                }
361
362                // If we are sampling, invoke the profiler and collect a sample.
363                // Currently this is an example sample collection.
364                track_stub!(
365                    TODO("https://fxbug.dev/398914921"),
366                    "[perf_event_open] implement full sampling features"
367                );
368                if perf_event_file.attr.freq() == 0
369                // SAFETY: sample_period is a u64 field in a union with u64 sample_freq.
370                // This is always sound regardless of the union's tag.
371                    && unsafe { perf_event_file.attr.__bindgen_anon_1.sample_period != 0 }
372                {
373                    ping_receiver(perf_event_file.ioctl_sender.clone(), IoctlOp::Enable);
374                }
375                return Ok(SUCCESS);
376            }
377            PERF_EVENT_IOC_DISABLE => {
378                if perf_event_file.disabled == 0 {
379                    perf_event_file.disabled = 1; // 1 = true.
380
381                    // Update total_time_running now that the segment has ended.
382                    let curr_time = zx::MonotonicInstant::get().into_nanos() as u64;
383                    perf_event_file.total_time_running +=
384                        curr_time - perf_event_file.most_recent_enabled_time;
385                }
386                if perf_event_file.attr.freq() == 0
387                // SAFETY: sample_period is a u64 field in a union with u64 sample_freq.
388                // This is always sound regardless of the union's tag.
389                    && unsafe { perf_event_file.attr.__bindgen_anon_1.sample_period != 0 }
390                {
391                    ping_receiver(perf_event_file.ioctl_sender.clone(), IoctlOp::Disable);
392                }
393                return Ok(SUCCESS);
394            }
395            PERF_EVENT_IOC_RESET => {
396                perf_event_file.rf_value = 0;
397                return Ok(SUCCESS);
398            }
399            PERF_EVENT_IOC_REFRESH
400            | PERF_EVENT_IOC_PERIOD
401            | PERF_EVENT_IOC_SET_OUTPUT
402            | PERF_EVENT_IOC_SET_FILTER
403            | PERF_EVENT_IOC_ID
404            | PERF_EVENT_IOC_SET_BPF
405            | PERF_EVENT_IOC_PAUSE_OUTPUT
406            | PERF_EVENT_IOC_MODIFY_ATTRIBUTES
407            | PERF_EVENT_IOC_QUERY_BPF => {
408                track_stub!(
409                    TODO("https://fxbug.dev/404941053"),
410                    "[perf_event_open] implement remaining ioctl() calls"
411                );
412                return error!(ENOSYS);
413            }
414            _ => error!(ENOTTY),
415        }
416    }
417
418    // TODO(https://fxbug.dev/460245383) match behavior when mmap() is called multiple times.
419    // Gets called when mmap() is called.
420    // Immediately before sampling, this should get called by the user (e.g. the test
421    // or Perfetto). We will then write the metadata to the VMO and return the pointer to it.
422    fn get_memory(
423        &self,
424        _file: &FileObject,
425        current_task: &CurrentTask,
426        length: Option<usize>,
427        _prot: ProtectionFlags,
428    ) -> Result<Arc<MemoryObject>, Errno> {
429        let buffer_size: u64 = length.unwrap_or(0) as u64;
430        let page_size = zx::system_get_page_size() as u64;
431        if buffer_size <= page_size || buffer_size > ESTIMATED_MMAP_BUFFER_SIZE {
432            return error!(EINVAL);
433        }
434        let data_size = buffer_size - page_size;
435        if !data_size.is_power_of_two() {
436            return error!(EINVAL);
437        }
438
439        self.seq_lock
440            .get_or_init(|| {
441                let perf_event_file = self.perf_event_file.read();
442                let vmo_copy = perf_event_file
443                    .perf_data_vmo
444                    .as_handle_ref()
445                    .duplicate_handle(zx::Rights::SAME_RIGHTS)
446                    .map_err(|status| from_status_like_fdio!(status))?;
447                // SAFETY: See safety requirements on `create_seq_lock`.
448                Ok(unsafe { create_seq_lock(&vmo_copy, buffer_size) })
449            })
450            .as_ref()
451            .map_err(|e| e.clone())?;
452
453        // Write to a MemoryObject and return it (expected return type for get_memory()).
454        security::check_perf_event_read_access(current_task, &self)?;
455        let perf_event_file = self.perf_event_file.read();
456        match perf_event_file
457            .perf_data_vmo
458            .as_handle_ref()
459            .duplicate_handle(zx::Rights::SAME_RIGHTS)
460        {
461            Ok(vmo) => {
462                let vmo: zx::Vmo = vmo.into();
463                let memory = MemoryObject::from(vmo);
464                return Ok(Arc::new(memory));
465            }
466            Err(_) => {
467                track_stub!(
468                    TODO("https://fxbug.dev/416323134"),
469                    "[perf_event_open] handle get_memory() errors"
470                );
471                return error!(EINVAL);
472            }
473        };
474    }
475
476    fn write(
477        &self,
478        _file: &FileObject,
479        _current_task: &CurrentTask,
480        _offset: usize,
481        _data: &mut dyn InputBuffer,
482    ) -> Result<usize, Errno> {
483        track_stub!(
484            TODO("https://fxbug.dev/394960158"),
485            "[perf_event_open] implement perf event functions"
486        );
487        error!(ENOSYS)
488    }
489}
490
491// Given a PerfRecordSample struct, write it via the correct output format
492// (per https://man7.org/linux/man-pages/man2/perf_event_open.2.html) to the VMO.
493// We don't currently support all the sample_types listed in the docs.
494// Input:
495//    PerfRecordSample { pid: 5, tid: 10, nr: 3, ips[nr]: [111, 222, 333] }
496// Human-understandable output:
497//    9 1 40 111 5 10 3 111 222 333
498// Actual output (no spaces or \n in real output, just making it more readable):
499//    0x0000 0x0009                 <-- starts at `offset` bytes
500//    0x0001
501//    0x0040
502//    0x0000 0x0000 0x0000 0x006F   <-- starts at `offset` + 8 bytes
503//    0x0000 0x0000 0x0000 0x0005
504//    0x0000 0x0000 0x0000 0x0010
505//    0x0000 0x0000 0x0000 0x0003
506//    0x0000 0x0000 0x0000 0x006F
507//    0x0000 0x0000 0x0000 0x00DE
508//    0x0000 0x0000 0x0000 0x014D
509//
510//    Returns the length of bytes written. In above case, 8 + 28 = 36.
511//    This information is used to increment the global offset.
512//
513//    If writing to the VMO fails, we log a warning and return the number of
514//    bytes successfully written so far (e.g. only the LOST record if that
515//    succeeded, or 0). The caller uses this returned length to increment the
516//    VMO write offset and update `data_head`, meaning failed writes are
517//    effectively skipped and not exposed to the reader.
518fn write_record_to_vmo(
519    perf_record_sample: PerfRecordSample<'_>,
520    perf_data_vmo: &zx::Vmo,
521    sample_type: u64,
522    sample_id: u64,
523    sample_period: u64,
524    read_format: u64,
525    head: u64,
526    metadata: &PerfMetadataValue,
527    lost_events: &mut u64,
528) -> u64 {
529    let ring_buffer_size = metadata.data_size;
530    if ring_buffer_size == 0 {
531        return 0;
532    }
533    // First, build record to determine its size (so that we can fill out `size` in header).
534    let mut sample = Vec::<u8>::new();
535    // sample_id
536    if (sample_type & perf_event_sample_format_PERF_SAMPLE_IDENTIFIER as u64) != 0 {
537        sample.extend(sample_id.to_ne_bytes());
538    }
539    // ip
540    if (sample_type & perf_event_sample_format_PERF_SAMPLE_IP as u64) != 0 {
541        let ip = perf_record_sample.ips.first().copied().unwrap_or(0);
542        sample.extend(ip.to_ne_bytes());
543    }
544
545    if (sample_type & perf_event_sample_format_PERF_SAMPLE_TID as u64) != 0 {
546        // pid
547        sample.extend(perf_record_sample.pid.unwrap_or(0).to_ne_bytes());
548        // tid
549        sample.extend(perf_record_sample.tid.unwrap_or(0).to_ne_bytes());
550    }
551
552    // time: when the sample was taken.
553    if (sample_type & perf_event_sample_format_PERF_SAMPLE_TIME as u64) != 0 {
554        sample.extend((perf_record_sample.time.into_nanos() as u64).to_ne_bytes());
555    }
556
557    // id
558    if (sample_type & perf_event_sample_format_PERF_SAMPLE_ID as u64) != 0 {
559        sample.extend(sample_id.to_ne_bytes());
560    }
561
562    // sample period
563    if (sample_type & perf_event_sample_format_PERF_SAMPLE_PERIOD as u64) != 0 {
564        sample.extend(sample_period.to_ne_bytes());
565    }
566
567    // read_format value.
568    if (sample_type & perf_event_sample_format_PERF_SAMPLE_READ as u64) != 0 {
569        if (read_format & perf_event_read_format_PERF_FORMAT_GROUP as u64) != 0 {
570            // Group reads start with the number of events followed by each
571            // event's value; only the timebase event exists.
572            sample.extend(1u64.to_ne_bytes());
573            sample.extend(0u64.to_ne_bytes());
574        } else {
575            sample.extend(0u64.to_ne_bytes());
576        }
577    }
578
579    if (sample_type & perf_event_sample_format_PERF_SAMPLE_CALLCHAIN as u64) != 0 {
580        // nr
581        sample.extend(perf_record_sample.ips.len().to_ne_bytes());
582
583        // ips[nr] - list of ips, u64 per ip.
584        for i in perf_record_sample.ips {
585            sample.extend(i.to_ne_bytes());
586        }
587    }
588
589    // User registers (PERF_SAMPLE_REGS_USER): the ABI tag, then one u64 per
590    // bit set in attr.sample_regs_user, in perf register-index order.
591    // Readers request the mask for their own architecture -- a 64-bit reader
592    // requests the arm64 mask even when profiling 32-bit tasks (and relocates
593    // the PC from the PERF_REG_ARM64_PC index itself, matching the Linux
594    // kernel's behavior of capturing native registers).
595    // If the mask is 0 or the ABI is PERF_SAMPLE_REGS_ABI_NONE, no register
596    // values are output.
597    if (sample_type & perf_event_sample_format_PERF_SAMPLE_REGS_USER as u64) != 0 {
598        if perf_record_sample.regs_abi == perf_sample_regs_abi_PERF_SAMPLE_REGS_ABI_NONE as u64
599            || perf_record_sample.sample_regs_user == 0
600            || perf_record_sample.regs.is_empty()
601        {
602            sample.extend((perf_sample_regs_abi_PERF_SAMPLE_REGS_ABI_NONE as u64).to_ne_bytes());
603        } else {
604            sample.extend(perf_record_sample.regs_abi.to_ne_bytes());
605            sample.extend_from_slice(perf_record_sample.regs);
606        }
607    }
608
609    // User stack (PERF_SAMPLE_STACK_USER): size, the raw bytes starting at
610    // the sampled stack pointer, then the filled size. Readers overlay these
611    // bytes at the SP reported in REGS_USER, which is why the snapshot must
612    // begin exactly at it.
613    if (sample_type & perf_event_sample_format_PERF_SAMPLE_STACK_USER as u64) != 0 {
614        let fixed_len = std::mem::size_of::<perf_event_header>() + sample.len() + 16;
615        let header_space = (u16::MAX as usize).saturating_sub(fixed_len) & !7;
616        let requested = (perf_record_sample.sample_stack_user as usize) & !7;
617        let dest_len = requested.min(header_space);
618
619        sample.extend((dest_len as u64).to_ne_bytes());
620        if dest_len > 0 {
621            let data_len = perf_record_sample.stack.len().min(dest_len) & !7;
622            sample.extend_from_slice(&perf_record_sample.stack[..data_len]);
623            // Pad the rest with zeros.
624            sample.resize(sample.len() + (dest_len - data_len), 0);
625            // dyn_size
626            sample.extend((data_len as u64).to_ne_bytes());
627        }
628    }
629    // The remaining sample_type fields are not implemented.
630
631    // Now that we know the sample size, we can calculate the record size.
632    // record_size = perf_event_header_size + sample_size.
633    // perf_event_header is defined to be 8 bytes.
634    let record_len = std::mem::size_of::<perf_event_header>() + sample.len();
635    // Every field above is a u64 (or a u32 pair), so record sizes are always
636    // multiples of 8: ring positions stay 8-byte aligned. Since the
637    // perf_event_header is also 8 bytes, it can never straddle the end of
638    // the data area (which is page-aligned), meaning readers can always
639    // read the header contiguously.
640    if record_len % 8 != 0 {
641        log_error!("Record length {} is not 8-byte aligned, dropping", record_len);
642        *lost_events += 1;
643        return 0;
644    }
645    // The header's size field is a u16. A record that exceeds it would
646    // silently wrap the size and desynchronize every record after it, so
647    // drop it instead.
648    let Ok(record_size) = u16::try_from(record_len) else {
649        log_warn!("Dropping {} byte perf sample record: exceeds u16 record size", record_len);
650        *lost_events += 1;
651        return 0;
652    };
653
654    let perf_event_header = perf_event_header {
655        // These are samples of user-space execution; readers take the
656        // sample's cpu mode from the misc bits.
657        type_: perf_event_type_PERF_RECORD_SAMPLE,
658        misc: PERF_RECORD_MISC_USER as u16,
659        size: record_size,
660    };
661
662    // data_tail is advanced by userspace as it consumes records and is untrusted.
663    // Calculate free space using the standard circular buffer formula, matching Linux's
664    // CIRC_SPACE. This naturally handles wrap-around and invalid future tails.
665    // ring_buffer_size is guaranteed to be a power of two by checks in get_memory().
666    let free_space =
667        (metadata.data_tail.wrapping_sub(head).wrapping_sub(1)) & (ring_buffer_size - 1);
668
669    // Drop the sample if the ring buffer is full, matching Linux's
670    // non-overwrite mode; the drop is reported via PERF_RECORD_LOST once
671    // space frees up.
672    if free_space < record_len as u64 {
673        *lost_events += 1;
674        return 0;
675    }
676
677    let mut bytes_written: u64 = 0;
678
679    // If records were dropped earlier, surface a PERF_RECORD_LOST record as
680    // soon as there is room for it alongside the current sample.
681    if *lost_events > 0 && free_space >= record_len as u64 + LOST_RECORD_SIZE {
682        let lost_header = perf_event_header {
683            type_: perf_event_type_PERF_RECORD_LOST,
684            misc: 0,
685            size: LOST_RECORD_SIZE as u16,
686        };
687        let lost_record = LostRecord { header: lost_header, sample_id, lost_events: *lost_events };
688        if write_circular(
689            perf_data_vmo,
690            metadata.data_offset,
691            ring_buffer_size,
692            head,
693            lost_record.as_bytes(),
694        )
695        .is_ok()
696        {
697            *lost_events = 0;
698            bytes_written += LOST_RECORD_SIZE;
699        }
700    }
701
702    let mut record = Vec::with_capacity(record_len);
703    record.extend_from_slice(perf_event_header.as_bytes());
704    record.extend_from_slice(&sample);
705
706    match write_circular(
707        perf_data_vmo,
708        metadata.data_offset,
709        ring_buffer_size,
710        head + bytes_written,
711        &record,
712    ) {
713        // Return the total size we wrote so the caller can advance data_head.
714        Ok(()) => bytes_written + record_len as u64,
715        Err(e) => {
716            log_warn!("Failed to write PerfRecordSample to VMO due to: {}", e);
717            bytes_written
718        }
719    }
720}
721
722// Writes `data` into the ring buffer's data area at the position
723// corresponding to `head` (a free-running count of bytes ever written),
724// splitting the write across the end of the data area when it wraps around.
725// Readers read records by checking the header size, reading contiguously,
726// and wrapping around to the beginning of the data area if the record is split.
727fn write_circular(
728    vmo: &zx::Vmo,
729    data_offset: u64,
730    ring_buffer_size: u64,
731    head: u64,
732    data: &[u8],
733) -> Result<(), zx::Status> {
734    let position = head % ring_buffer_size;
735    let vmo_offset = data_offset + position;
736    if position + data.len() as u64 <= ring_buffer_size {
737        vmo.write(data, vmo_offset)?;
738    } else {
739        let first_len = (ring_buffer_size - position) as usize;
740        vmo.write(&data[..first_len], vmo_offset)?;
741        vmo.write(&data[first_len..], data_offset)?;
742    }
743    Ok(())
744}
745
746/// Represents a PERF_RECORD_SAMPLE payload to be serialized into the VMO ring buffer.
747/// Fields follow the perf ABI order: TID, TIME, ID, PERIOD, READ, CALLCHAIN, REGS_USER, STACK_USER.
748#[derive(Debug, Clone)]
749struct PerfRecordSample<'a> {
750    pid: Option<u32>,
751    tid: Option<u32>,
752    // Timestamp of when the sample was taken, for PERF_SAMPLE_TIME.
753    time: zx::BootInstant,
754    // Instruction pointers (currently this is the address). First one is `ip` param.
755    ips: Vec<u64>,
756    regs: &'a [u8],
757    stack: &'a [u8],
758    // PERF_SAMPLE_REGS_ABI_32 (1) or PERF_SAMPLE_REGS_ABI_64 (2) for `regs`.
759    regs_abi: u64,
760    sample_regs_user: u64,
761    sample_stack_user: u64,
762}
763
764async fn set_up_profiler(
765    sample_period: zx::MonotonicDuration,
766) -> Result<(profiler::SessionProxy, fidl::AsyncSocket), Errno> {
767    // Configuration for how we want to sample.
768    let sample = profiler::Sample {
769        callgraph: Some(profiler::CallgraphConfig {
770            strategy: Some(profiler::CallgraphStrategy::FramePointer),
771            ..Default::default()
772        }),
773        ..Default::default()
774    };
775
776    let sampling_config = profiler::SamplingConfig {
777        period: Some(sample_period.into_nanos() as u64),
778        timebase: Some(profiler::Counter::PlatformIndependent(profiler::CounterId::Nanoseconds)),
779        sample: Some(sample),
780        ..Default::default()
781    };
782
783    track_stub!(
784        TODO("https://fxbug.dev/398914921"),
785        "[perf_event_open] allow for profiling system-wide not during tests"
786    );
787    let job = fuchsia_runtime::job_default();
788    let koid = job.koid().map_err(|e| errno!(EINVAL, e.to_string()))?;
789    let tasks = vec![
790        // Should return ~1300 samples for 1000 millis.
791        profiler::Task::Job(koid.raw_koid()),
792    ];
793    let targets = profiler::TargetConfig::Tasks(tasks);
794    let config = profiler::Config {
795        configs: Some(vec![sampling_config]),
796        target: Some(targets),
797        ..Default::default()
798    };
799    let (client, server) = fidl::Socket::create_stream();
800    let configure = profiler::SessionConfigureRequest {
801        output: Some(server),
802        config: Some(config),
803        ..Default::default()
804    };
805
806    let proxy = connect_to_protocol::<profiler::SessionMarker>()
807        .context("Error connecting to Profiler protocol");
808    let session_proxy: profiler::SessionProxy = match proxy {
809        Ok(p) => p.clone(),
810        Err(e) => return error!(EINVAL, e),
811    };
812
813    // Must configure before sampling start().
814    let config_request = session_proxy.configure(configure).await;
815    match config_request {
816        Ok(_) => Ok((session_proxy, fidl::AsyncSocket::from_socket(client))),
817        Err(e) => return error!(EINVAL, e),
818    }
819}
820
821// Converts one FXT record from the profiler into a PERF_RECORD_SAMPLE in
822// the ring buffer and publishes the new data_head. Non-sample records are
823// ignored.
824fn process_fxt_record(
825    record: TraceRecord,
826    seq_lock_wrapper: &SeqLock<PerfMetadataHeader, PerfMetadataValue>,
827    perf_data_vmo: &zx::Vmo,
828    sample_type: u64,
829    sample_id: u64,
830    sample_period: u64,
831    read_format: u64,
832    sample_regs_user: u64,
833    sample_stack_user: u64,
834    koid_session: Option<&PidKoidSession>,
835    vmo_write_offset: &mut u64,
836    lost_events: &mut u64,
837) {
838    match record {
839        TraceRecord::Profiler(ProfilerRecord::Backtrace(backtrace)) => {
840            let ips: Vec<u64> = backtrace.data;
841            // Resolve the sampled koids to Linux pid/tid against the live
842            // shared map (one read lock per record; collection is off the hot
843            // path and a live read sees every thread that recorded itself
844            // before it was sampled). If the sample cannot be resolved (e.g.
845            // native Fuchsia thread or without a session), drop the sample.
846            let Some(LinuxIdentity::Thread { pid, tid }) = koid_session.and_then(|s| {
847                s.resolve_koids(
848                    zx::Koid::from_raw(backtrace.process.0),
849                    zx::Koid::from_raw(backtrace.thread.0),
850                )
851            }) else {
852                return;
853            };
854            let time = zx::BootInstant::from_nanos(backtrace.timestamp.max(0));
855            let perf_record_sample = PerfRecordSample {
856                pid: Some(pid as u32),
857                tid: Some(tid as u32),
858                time,
859                ips,
860                regs: &[],
861                stack: &[],
862                regs_abi: perf_sample_regs_abi_PERF_SAMPLE_REGS_ABI_NONE as u64,
863                sample_regs_user,
864                sample_stack_user,
865            };
866            let metadata = seq_lock_wrapper.get();
867            let bytes_written = write_record_to_vmo(
868                perf_record_sample,
869                perf_data_vmo,
870                sample_type,
871                sample_id,
872                sample_period,
873                read_format,
874                *vmo_write_offset,
875                &metadata,
876                lost_events,
877            );
878            // Publish data_head after writing; set_value's
879            // release-ordered stores make the record contents
880            // visible to a reader that observes the new head.
881            if bytes_written > 0 {
882                *vmo_write_offset += bytes_written;
883                let mut metadata = seq_lock_wrapper.get();
884                metadata.data_head = *vmo_write_offset;
885                seq_lock_wrapper.set_value(metadata);
886            }
887        }
888        TraceRecord::LargeBlob(large_blob) => {
889            // The DWARF strategy delivers each sample as a
890            // "stack_sample" blob: [u64 regs_size][regs bytes]
891            // followed by memory chunks of [u64 base][u64 size]
892            // [bytes] (see the profiler's StackSampler).
893            if large_blob.name != "stack_sample" {
894                return;
895            }
896            let Some(blob_metadata) = large_blob.metadata else {
897                return;
898            };
899            let bytes = &large_blob.bytes;
900            if bytes.len() < 8 {
901                return;
902            }
903            let regs_size = u64::from_ne_bytes(bytes[0..8].try_into().unwrap()) as usize;
904            let mut offset = 8;
905            if regs_size == 0 || bytes.len() < offset + regs_size {
906                return;
907            }
908
909            // 33 u64 general registers (r0-r29, lr, sp, pc), with
910            // cpsr following them in the zircon thread state.
911            // Register state of other widths (e.g. an x86_64
912            // thread state) is not supported and skipped by this
913            // size check.
914            const REGS_BYTES: usize = 33 * 8;
915            if regs_size < REGS_BYTES + 8 {
916                return;
917            }
918            #[cfg(target_arch = "aarch64")]
919            let is_32bit = {
920                let cpsr = u64::from_ne_bytes(
921                    bytes[offset + REGS_BYTES..offset + REGS_BYTES + 8].try_into().unwrap(),
922                );
923                (cpsr & zx::sys::ZX_REG_CPSR_ARCH_32_MASK) == zx::sys::ZX_REG_CPSR_ARCH_32_MASK
924            };
925            #[cfg(not(target_arch = "aarch64"))]
926            let is_32bit = false;
927
928            let mut blob_regs = bytes[offset..offset + REGS_BYTES].to_vec();
929            offset += regs_size;
930
931            if is_32bit {
932                // Zircon reports the AArch32 PC in the pc slot
933                // (index 32). 64-bit readers take it from there,
934                // per the Linux compat layout (AArch32 R0-R14
935                // arrive in x0-x14); mirror it into the arm32 R15
936                // slot (index 15) too for 32-bit readers, which
937                // only consume indices 0-15 -- x15 carries no
938                // meaningful value for AArch32 state.
939                let pc_offset = AARCH64_REG_PC * 8;
940                let pc_bytes = blob_regs[pc_offset..pc_offset + 8].to_vec();
941                let r15_offset = AARCH32_REG_R15 * 8;
942                blob_regs[r15_offset..r15_offset + 8].copy_from_slice(&pc_bytes);
943            }
944
945            let sp = if is_32bit {
946                // The arm32 stack pointer is R13.
947                let r13_offset = AARCH32_REG_R13 * 8;
948                u64::from_ne_bytes(blob_regs[r13_offset..r13_offset + 8].try_into().unwrap())
949            } else {
950                let sp_offset = AARCH64_REG_SP * 8;
951                u64::from_ne_bytes(blob_regs[sp_offset..sp_offset + 8].try_into().unwrap())
952            };
953            let pc_offset = AARCH64_REG_PC * 8;
954            let pc = u64::from_ne_bytes(blob_regs[pc_offset..pc_offset + 8].try_into().unwrap());
955
956            // Select the memory chunk that contains the sampled
957            // stack pointer and trim it to start exactly there:
958            // readers overlay the STACK_USER bytes at the SP
959            // reported in REGS_USER. The blob can carry several
960            // captures (the thread-state stack, the
961            // restricted-state struct, and a restricted-SP
962            // stack); selecting by SP keeps the registers and the
963            // stack bytes coherent.
964            let mut stack: &[u8] = &[];
965            while offset + 16 <= bytes.len() {
966                let chunk_base = u64::from_ne_bytes(bytes[offset..offset + 8].try_into().unwrap());
967                offset += 8;
968                let chunk_size = u64::from_ne_bytes(bytes[offset..offset + 8].try_into().unwrap());
969                offset += 8;
970                if offset + chunk_size as usize > bytes.len() {
971                    break;
972                }
973                let data = &bytes[offset..offset + chunk_size as usize];
974                offset += chunk_size as usize;
975                if stack.is_empty() && chunk_base <= sp && sp < chunk_base + chunk_size {
976                    stack = &data[(sp - chunk_base) as usize..];
977                }
978            }
979            if stack.is_empty() {
980                // No capture covers the sampled SP; the sample
981                // cannot be unwound.
982                return;
983            }
984
985            let Some(LinuxIdentity::Thread { pid, tid }) = koid_session.and_then(|s| {
986                s.resolve_koids(
987                    zx::Koid::from_raw(blob_metadata.process.0),
988                    zx::Koid::from_raw(blob_metadata.thread.0),
989                )
990            }) else {
991                return;
992            };
993            let time = zx::BootInstant::from_nanos(blob_metadata.timestamp.max(0));
994            let regs_abi = if is_32bit {
995                perf_sample_regs_abi_PERF_SAMPLE_REGS_ABI_32 as u64
996            } else {
997                perf_sample_regs_abi_PERF_SAMPLE_REGS_ABI_64 as u64
998            };
999            let perf_record_sample = PerfRecordSample {
1000                pid: Some(pid as u32),
1001                tid: Some(tid as u32),
1002                time,
1003                ips: vec![pc],
1004                regs: &blob_regs,
1005                stack,
1006                regs_abi,
1007                sample_regs_user,
1008                sample_stack_user,
1009            };
1010            let metadata = seq_lock_wrapper.get();
1011            let bytes_written = write_record_to_vmo(
1012                perf_record_sample,
1013                perf_data_vmo,
1014                sample_type,
1015                sample_id,
1016                sample_period,
1017                read_format,
1018                *vmo_write_offset,
1019                &metadata,
1020                lost_events,
1021            );
1022            // Publish data_head after writing; set_value's
1023            // release-ordered stores make the record contents
1024            // visible to a reader that observes the new head.
1025            if bytes_written > 0 {
1026                *vmo_write_offset += bytes_written;
1027                let mut metadata = seq_lock_wrapper.get();
1028                metadata.data_head = *vmo_write_offset;
1029                seq_lock_wrapper.set_value(metadata);
1030            }
1031        }
1032        _ => {}
1033    }
1034}
1035
1036// Notifies other thread that we should start/stop sampling.
1037// Once sampling is complete, that profiler session is no longer needed.
1038// At that point, send back notification so that this is no longer blocking
1039// (e.g. so that other profiler sessions can start).
1040fn ping_receiver(
1041    mut ioctl_sender: future_mpsc::Sender<(IoctlOp, sync_mpsc::Sender<()>)>,
1042    command: IoctlOp,
1043) {
1044    log_info!("[perf_event_open] Received sampling command: {:?}", command);
1045    let (profiling_complete_sender, profiling_complete_receiver) = sync_mpsc::channel::<()>();
1046    match ioctl_sender.try_send((command, profiling_complete_sender)) {
1047        Ok(_) => (),
1048        Err(e) => {
1049            if e.is_full() {
1050                log_warn!("[perf_event_open] Failed to send {:?}: Channel full", command);
1051            } else if e.is_disconnected() {
1052                log_warn!("[perf_event_open] Failed to send {:?}: Receiver disconnected", command);
1053            } else {
1054                log_warn!("[perf_event_open] Failed to send {:?} due to {:?}", command, e.source());
1055            }
1056        }
1057    };
1058    // Block on / wait until profiling is complete before returning.
1059    // This notifies that the profiler is free to be used for another session.
1060    let _ = profiling_complete_receiver.recv().unwrap();
1061}
1062
1063// Creates a seq lock for the given VMO. Initializes the seq lock with
1064// known initial values (unknown values default to 0).
1065// Does NOT actually save this as a memory object until mmap() is called.
1066//
1067// # Safety
1068//
1069// The caller must ensure that the kernel maintains exclusive write access to this VMO and
1070// there are only atomic accesses to this memory (see seq_lock lib.rs for details).
1071unsafe fn create_seq_lock(
1072    vmo_handle_ref: &zx::NullableHandle,
1073    buffer_size: u64,
1074) -> SeqLock<PerfMetadataHeader, PerfMetadataValue> {
1075    // Currently we hardcode everything just to get something E2E working.
1076    let metadata_header = PerfMetadataHeader { version: 1, compat_version: 2 };
1077    let page_size = *PAGE_SIZE;
1078    let metadata_value = PerfMetadataValue {
1079        lock: 0,
1080        index: 3,
1081        offset: 19337,
1082        time_enabled: 0,
1083        time_running: 0,
1084        __bindgen_anon_1: perf_event_mmap_page__bindgen_ty_1 { capabilities: 30 },
1085        pmc_width: 0,
1086        time_shift: 0,
1087        time_mult: 0,
1088        time_offset: 0,
1089        time_zero: 0,
1090        size: 0,
1091        __reserved_1: 0,
1092        time_cycles: 0,
1093        time_mask: 0,
1094        __reserved: [0; 928usize],
1095        // This first page (metadata) has finished writing. Start data_head at 0.
1096        data_head: 0,
1097        // Start reading from 0; it is the user's responsibility to increment on their end.
1098        data_tail: 0,
1099        // We know the data will start after 1 page size so we can set this now.
1100        data_offset: page_size,
1101        data_size: buffer_size - page_size,
1102        aux_head: 0,
1103        aux_tail: 0,
1104        aux_offset: 0,
1105        aux_size: 0,
1106    };
1107    let vmo = zx::Vmo::from(vmo_handle_ref.duplicate_handle(zx::Rights::SAME_RIGHTS).unwrap());
1108
1109    // Create a SeqLock and safely initialize the `header` and `value` for it.
1110    // SeqLock is formatted thusly:
1111    //   header_struct : any size, params `version` and `compat_version` should not change
1112    //   sequence_counter : u32, this is the lock and should increment
1113    //   value_struct : any size, each param can change
1114    //
1115    // SAFETY: See safety requirements on `create_seq_lock`.
1116    unsafe {
1117        SeqLock::new_from_vmo(metadata_header, metadata_value, vmo)
1118            .expect("failed to create seq_lock for perf metadata")
1119    }
1120}
1121
1122pub fn sys_perf_event_open(
1123    current_task: &CurrentTask,
1124    attr: UserRef<perf_event_attr>,
1125    // Note that this is pid in Linux docs.
1126    tid: tid_t,
1127    cpu: i32,
1128    group_fd: FdNumber,
1129    _flags: u64,
1130) -> Result<SyscallResult, Errno> {
1131    // So far, the implementation only sets the read_data_format according to the "Reading results"
1132    // section of https://man7.org/linux/man-pages/man2/perf_event_open.2.html for a single event.
1133    // Other features will be added in the future (see below track_stubs).
1134    let perf_event_attrs: perf_event_attr = current_task.read_object(attr)?;
1135
1136    if (perf_event_attrs.sample_type & perf_event_sample_format_PERF_SAMPLE_STACK_USER as u64) != 0
1137    {
1138        if perf_event_attrs.sample_stack_user % 8 != 0 {
1139            return error!(EINVAL);
1140        }
1141    }
1142
1143    if tid == -1 && cpu == -1 {
1144        return error!(EINVAL);
1145    }
1146
1147    let target_task_type = match tid {
1148        -1 => TargetTaskType::AllTasks,
1149        0 => TargetTaskType::CurrentTask,
1150        _ => {
1151            track_stub!(TODO("https://fxbug.dev/409621963"), "[perf_event_open] implement tid > 0");
1152            return error!(ENOSYS);
1153        }
1154    };
1155    security::check_perf_event_open_access(
1156        current_task,
1157        target_task_type,
1158        &perf_event_attrs,
1159        perf_event_attrs.type_.try_into()?,
1160    )?;
1161
1162    // Channel used to send info between notifier and spawned task thread.
1163    // We somewhat arbitrarily picked 8 for now in case we get a bunch of ioctls that are in
1164    // quick succession (instead of something lower).
1165    let (sender, mut receiver) = future_mpsc::channel(8);
1166
1167    let mut perf_event_file = PerfEventFileState::new(
1168        perf_event_attrs,
1169        0,
1170        perf_event_attrs.disabled(),
1171        perf_event_attrs.sample_type,
1172        zx::Vmo::create(ESTIMATED_MMAP_BUFFER_SIZE).unwrap(),
1173        sender,
1174    );
1175
1176    let read_format = perf_event_attrs.read_format;
1177
1178    if (read_format & perf_event_read_format_PERF_FORMAT_TOTAL_TIME_ENABLED as u64) != 0
1179        || (read_format & perf_event_read_format_PERF_FORMAT_TOTAL_TIME_RUNNING as u64) != 0
1180    {
1181        // Only keep track of most_recent_enabled_time if we are currently in ENABLED state,
1182        // as otherwise this param shouldn't be used for calculating anything.
1183        if perf_event_file.disabled == 0 {
1184            perf_event_file.most_recent_enabled_time =
1185                zx::MonotonicInstant::get().into_nanos() as u64;
1186        }
1187        // Initialize this to 0 as we will need to return a time duration later during read().
1188        perf_event_file.total_time_running = 0;
1189    }
1190
1191    let event_id = READ_FORMAT_ID_GENERATOR.fetch_add(1, Ordering::Relaxed);
1192    perf_event_file.rf_id = event_id;
1193
1194    if group_fd.raw() == -1 {
1195        perf_event_file.sample_id = event_id;
1196    } else {
1197        let group_file = current_task.files().get(group_fd)?;
1198        let group_file_object_id = group_file.id;
1199        let perf_state = get_perf_state(&current_task.kernel);
1200        let events = perf_state.format_id_lookup_table.lock();
1201        if let Some(rf_id) = events.get(&group_file_object_id) {
1202            perf_event_file.sample_id = *rf_id;
1203        } else {
1204            return error!(EINVAL);
1205        }
1206    }
1207
1208    if (read_format & perf_event_read_format_PERF_FORMAT_GROUP as u64) != 0 {
1209        track_stub!(
1210            TODO("https://fxbug.dev/402238049"),
1211            "[perf_event_open] implement read_format group"
1212        );
1213        return error!(ENOSYS);
1214    }
1215    if (read_format & perf_event_read_format_PERF_FORMAT_LOST as u64) != 0 {
1216        track_stub!(
1217            TODO("https://fxbug.dev/402260383"),
1218            "[perf_event_open] implement read_format lost"
1219        );
1220    }
1221
1222    // Set up notifier for handling ioctl calls to enable/disable sampling.
1223    let mut vmo_handle_copy =
1224        perf_event_file.perf_data_vmo.as_handle_ref().duplicate_handle(zx::Rights::SAME_RIGHTS);
1225
1226    // SAFETY: sample_period is a u64 field in a union with u64 sample_freq.
1227    // This is always sound regardless of the union's tag.
1228    let sample_period_in_ticks = unsafe { perf_event_file.attr.__bindgen_anon_1.sample_period };
1229    // The sample period from the PERF_COUNT_SW_CPU_CLOCK is
1230    // 1 nanosecond per tick. Convert this duration into zx::duration.
1231    let zx_sample_period = zx::MonotonicDuration::from_nanos(sample_period_in_ticks as i64);
1232
1233    // SeqLock does not get instantiated with metadata values until mmap() is called.
1234    let seq_lock =
1235        Arc::new(OnceLock::<Result<SeqLock<PerfMetadataHeader, PerfMetadataValue>, Errno>>::new());
1236    let cloned_seq_lock = Arc::clone(&seq_lock);
1237    let mut vmo_write_offset = 0;
1238
1239    let closure = async move |kthread_task: &CurrentTask| {
1240        let mut lost_events: u64 = 0;
1241
1242        // Each iteration waits for an Enable and then runs one session.
1243        while let Some((command, profiling_complete_receiver)) = receiver.next().await {
1244            // We only expect Enable when no session is active.
1245            if command != IoctlOp::Enable {
1246                let _ = profiling_complete_receiver.send(());
1247                continue;
1248            }
1249
1250            let (session_proxy, client) = match set_up_profiler(zx_sample_period).await {
1251                Ok(session) => session,
1252                Err(e) => {
1253                    log_warn!("Failed to profile: {}", e);
1254                    let _ = profiling_complete_receiver.send(());
1255                    continue;
1256                }
1257            };
1258
1259            // Record pid/koid mappings before the profiler starts sampling
1260            // so every sampled thread can be resolved. Dropping the session
1261            // at the end of the profiling session ends the recording interest.
1262            let pid_koid_session = kthread_task.kernel().trace_event_manager.open();
1263            let start_request =
1264                profiler::SessionStartRequest { buffer_results: Some(false), ..Default::default() };
1265            if let Err(e) = session_proxy.start(&start_request).await {
1266                log_warn!("Failed to start profiler: {:?}", e);
1267                let _ = profiling_complete_receiver.send(());
1268                continue;
1269            }
1270            let _ = profiling_complete_receiver.send(());
1271
1272            let vmo = zx::Vmo::from(
1273                vmo_handle_copy
1274                    .as_mut()
1275                    .expect("Failed to get VMO handle")
1276                    .as_handle_ref()
1277                    .duplicate_handle(zx::Rights::SAME_RIGHTS)
1278                    .unwrap(),
1279            );
1280            let mut handle_record = |record: TraceRecord| {
1281                // Records can only be written once mmap() has set up the
1282                // ring buffer.
1283                if let Some(Ok(seq_lock_wrapper)) = cloned_seq_lock.get() {
1284                    process_fxt_record(
1285                        record,
1286                        seq_lock_wrapper,
1287                        &vmo,
1288                        perf_event_file.sample_type,
1289                        perf_event_file.sample_id,
1290                        sample_period_in_ticks,
1291                        perf_event_file.attr.read_format,
1292                        perf_event_file.attr.sample_regs_user,
1293                        perf_event_file.attr.sample_stack_user as u64,
1294                        Some(&pid_koid_session),
1295                        &mut vmo_write_offset,
1296                        &mut lost_events,
1297                    );
1298                }
1299            };
1300
1301            // Pump records from the profiler into the ring buffer until a
1302            // Disable arrives: readers poll the ring during the session, and
1303            // the profiler's socket must be drained continuously.
1304            let (stream, _parser_task) = SessionParser::new_async(client);
1305            let mut stream = stream.fuse();
1306            let mut stream_ended = false;
1307            let disable_ack = loop {
1308                if stream_ended {
1309                    // The profiler closed the socket early; only commands
1310                    // remain.
1311                    match receiver.next().await {
1312                        Some((IoctlOp::Disable, ack)) => break Some(ack),
1313                        Some((IoctlOp::Enable, ack)) => {
1314                            // A session is already active.
1315                            let _ = ack.send(());
1316                        }
1317                        None => break None,
1318                    }
1319                } else {
1320                    futures::select_biased! {
1321                        cmd = receiver.next() => match cmd {
1322                            Some((IoctlOp::Disable, ack)) => break Some(ack),
1323                            Some((IoctlOp::Enable, ack)) => {
1324                                // A session is already active.
1325                                let _ = ack.send(());
1326                            }
1327                            None => break None,
1328                        },
1329                        record = stream.next() => match record {
1330                            Some(Ok(record)) => handle_record(record),
1331                            Some(Err(e)) => {
1332                                // The stream is desynchronized and the parser
1333                                // would keep returning this error, so end the
1334                                // session. The teardown below drops the
1335                                // socket, which unblocks the profiler if it is
1336                                // stalled writing to it.
1337                                log_warn!("[perf_event_open] Error parsing FXT: {:?}", e);
1338                                stream_ended = true;
1339                                break None;
1340                            }
1341                            None => {
1342                                log_warn!("[perf_event_open] Profiler stream ended mid-session");
1343                                stream_ended = true;
1344                            }
1345                        },
1346                    }
1347                }
1348            };
1349
1350            // Tear the session down while still draining: the profiler is a
1351            // separate process whose socket writes block, so if its socket is
1352            // full it is stalled mid-write and cannot service stop() or
1353            // reset() until we keep reading. Awaiting either without pumping
1354            // would deadlock the two.
1355            let stop_and_reset = async {
1356                match session_proxy.stop().await {
1357                    Ok(stats) => log_info!(
1358                        "[perf_event_open] profiler samples_collected: {:?}",
1359                        stats.samples_collected.unwrap_or(0)
1360                    ),
1361                    Err(e) => log_warn!("[perf_event_open] Failed to stop profiler: {:?}", e),
1362                }
1363                // Reset flushes the remaining data and closes the socket,
1364                // which is what ends the drain below.
1365                let _ = session_proxy.reset().await;
1366            };
1367            let drain = async move {
1368                if !stream_ended {
1369                    while let Some(record) = stream.next().await {
1370                        match record {
1371                            Ok(record) => handle_record(record),
1372                            Err(e) => {
1373                                // The stream is desynchronized, so further
1374                                // records cannot be parsed.
1375                                log_warn!("[perf_event_open] Error parsing FXT: {:?}", e);
1376                                break;
1377                            }
1378                        }
1379                    }
1380                }
1381                // Hand the profiler a closed socket so that a write it is
1382                // blocked on fails instead of hanging forever.
1383                drop(stream);
1384                drop(_parser_task);
1385            };
1386            futures::join!(stop_and_reset, drain);
1387
1388            // The Disable ioctl returns only after the drain above, so the
1389            // reader sees every record once the ioctl completes.
1390            if let Some(ack) = disable_ack {
1391                let _ = ack.send(());
1392            }
1393        }
1394    };
1395    let req = SpawnRequestBuilder::new()
1396        .with_debug_name("perf-event-sampler")
1397        .with_async_closure(closure)
1398        .build();
1399    current_task.kernel().kthreads.spawner().spawn_from_request(req);
1400
1401    let file = Box::new(PerfEventFile {
1402        _tid: tid,
1403        _cpu: cpu,
1404        perf_event_file: perf_event_file.into(),
1405        security_state: security::perf_event_alloc(current_task),
1406        seq_lock: seq_lock,
1407    });
1408    // TODO: https://fxbug.dev/404739824 - Confirm whether to handle this as a "private" node.
1409    let file_handle = Anon::new_private_file(current_task, file, OpenFlags::RDWR, "[perf_event]");
1410    let file_object_id = file_handle.id;
1411    let file_descriptor: Result<FdNumber, Errno> =
1412        current_task.add_file(file_handle, FdFlags::empty());
1413
1414    match file_descriptor {
1415        Ok(fd) => {
1416            if group_fd.raw() == -1 {
1417                let perf_state = get_perf_state(&current_task.kernel);
1418                let mut events = perf_state.format_id_lookup_table.lock();
1419                events.insert(file_object_id, event_id);
1420            }
1421            Ok(fd.into())
1422        }
1423        Err(_) => {
1424            track_stub!(
1425                TODO("https://fxbug.dev/402453955"),
1426                "[perf_event_open] implement remaining error handling"
1427            );
1428            error!(EMFILE)
1429        }
1430    }
1431}
1432// Syscalls for arch32 usage
1433#[cfg(target_arch = "aarch64")]
1434mod arch32 {
1435    pub use super::sys_perf_event_open as sys_arch32_perf_event_open;
1436}
1437
1438#[cfg(target_arch = "aarch64")]
1439pub use arch32::*;
1440
1441use crate::mm::memory::MemoryObject;
1442use crate::mm::{MemoryAccessorExt, ProtectionFlags};
1443use crate::task::CurrentTask;
1444use crate::vfs::{
1445    Anon, FdFlags, FdNumber, FileObject, FileObjectId, FileObjectState, FileOps, InputBuffer,
1446    OutputBuffer,
1447};
1448use crate::{fileops_impl_nonseekable, fileops_impl_noop_sync};
1449
1450#[cfg(test)]
1451mod tests {
1452    use super::*;
1453    use crate::task::tracing::{TracePerformanceEventManager, ZirconIdentity};
1454
1455    #[::fuchsia::test]
1456    async fn test_process_fxt_record_resolves_pid_tid() {
1457        let manager = Arc::new(TracePerformanceEventManager::new(std::sync::Weak::new()));
1458        let session = manager.open();
1459        manager.record(
1460            42,
1461            43,
1462            ZirconIdentity { process: zx::Koid::from_raw(1001), thread: zx::Koid::from_raw(1002) },
1463        );
1464
1465        let perf_data_vmo = zx::Vmo::create(ESTIMATED_MMAP_BUFFER_SIZE).unwrap();
1466        let vmo_handle_copy =
1467            perf_data_vmo.as_handle_ref().duplicate_handle(zx::Rights::SAME_RIGHTS).unwrap();
1468        // SAFETY: The test maintains exclusive write access to this VMO.
1469        let seq_lock = unsafe { create_seq_lock(&vmo_handle_copy, ESTIMATED_MMAP_BUFFER_SIZE) };
1470
1471        let sample_type = (perf_event_sample_format_PERF_SAMPLE_IP
1472            | perf_event_sample_format_PERF_SAMPLE_TID) as u64;
1473        let mut vmo_write_offset = 0;
1474        let mut lost_events = 0;
1475
1476        // Mapped sample: process 1001, thread 1002 -> should resolve to pid 42, tid 43.
1477        let mapped_record =
1478            TraceRecord::Profiler(ProfilerRecord::Backtrace(fxt::profiler::BacktraceRecord {
1479                timestamp: 1000,
1480                process: fxt::ProcessKoid(1001),
1481                thread: fxt::ThreadKoid(1002),
1482                num_records: 1,
1483                data: vec![0x12345678],
1484            }));
1485        process_fxt_record(
1486            mapped_record,
1487            &seq_lock,
1488            &perf_data_vmo,
1489            sample_type,
1490            0,
1491            0,
1492            0,
1493            0,
1494            0,
1495            Some(&session),
1496            &mut vmo_write_offset,
1497            &mut lost_events,
1498        );
1499
1500        // Header (8 bytes) + IP (8 bytes) + PID/TID (8 bytes) = 24 bytes.
1501        let expected_record_size: u64 = 24;
1502        assert_eq!(vmo_write_offset, expected_record_size);
1503
1504        let metadata = seq_lock.get();
1505        assert_eq!(metadata.data_head, expected_record_size);
1506
1507        let mut record_bytes = [0u8; 24];
1508        perf_data_vmo.read(&mut record_bytes, metadata.data_offset).unwrap();
1509
1510        let record_type = u32::from_ne_bytes(record_bytes[0..4].try_into().unwrap());
1511        assert_eq!(record_type, perf_event_type_PERF_RECORD_SAMPLE);
1512
1513        let misc = u16::from_ne_bytes(record_bytes[4..6].try_into().unwrap());
1514        assert_eq!(misc, PERF_RECORD_MISC_USER as u16);
1515
1516        let size = u16::from_ne_bytes(record_bytes[6..8].try_into().unwrap());
1517        assert_eq!(size, expected_record_size as u16);
1518
1519        let ip = u64::from_ne_bytes(record_bytes[8..16].try_into().unwrap());
1520        assert_eq!(ip, 0x12345678);
1521
1522        let pid = u32::from_ne_bytes(record_bytes[16..20].try_into().unwrap());
1523        assert_eq!(pid, 42);
1524
1525        let tid = u32::from_ne_bytes(record_bytes[20..24].try_into().unwrap());
1526        assert_eq!(tid, 43);
1527
1528        // Unmapped sample: process 9999, thread 9998 -> should be dropped.
1529        let unmapped_record =
1530            TraceRecord::Profiler(ProfilerRecord::Backtrace(fxt::profiler::BacktraceRecord {
1531                timestamp: 2000,
1532                process: fxt::ProcessKoid(9999),
1533                thread: fxt::ThreadKoid(9998),
1534                num_records: 1,
1535                data: vec![0x87654321],
1536            }));
1537        process_fxt_record(
1538            unmapped_record,
1539            &seq_lock,
1540            &perf_data_vmo,
1541            sample_type,
1542            0,
1543            0,
1544            0,
1545            0,
1546            0,
1547            Some(&session),
1548            &mut vmo_write_offset,
1549            &mut lost_events,
1550        );
1551
1552        // Verify that unmapped sample was dropped and no extra bytes were written.
1553        assert_eq!(vmo_write_offset, expected_record_size);
1554        let metadata = seq_lock.get();
1555        assert_eq!(metadata.data_head, expected_record_size);
1556        let mut trailing_bytes = [0u8; 24];
1557        perf_data_vmo
1558            .read(&mut trailing_bytes, metadata.data_offset + expected_record_size)
1559            .unwrap();
1560        assert_eq!(trailing_bytes, [0u8; 24]);
1561    }
1562
1563    #[::fuchsia::test]
1564    async fn test_write_record_to_vmo_regs_and_stack() {
1565        let perf_data_vmo = zx::Vmo::create(ESTIMATED_MMAP_BUFFER_SIZE).unwrap();
1566        let vmo_handle_copy =
1567            perf_data_vmo.as_handle_ref().duplicate_handle(zx::Rights::SAME_RIGHTS).unwrap();
1568        // SAFETY: The test maintains exclusive write access to this VMO.
1569        let seq_lock = unsafe { create_seq_lock(&vmo_handle_copy, ESTIMATED_MMAP_BUFFER_SIZE) };
1570        let metadata = seq_lock.get();
1571        let mut lost_events = 0;
1572        let regs_data = [0x11u8; 16];
1573        let stack_data = [0x22u8; 16];
1574        let sample = PerfRecordSample {
1575            pid: Some(10),
1576            tid: Some(20),
1577            time: zx::BootInstant::from_nanos(123_456_789),
1578            ips: vec![0xdeadbeef],
1579            regs: &regs_data,
1580            stack: &stack_data,
1581            regs_abi: perf_sample_regs_abi_PERF_SAMPLE_REGS_ABI_32 as u64,
1582            sample_regs_user: 3,
1583            sample_stack_user: 16,
1584        };
1585        let sample_type = (perf_event_sample_format_PERF_SAMPLE_IP
1586            | perf_event_sample_format_PERF_SAMPLE_TID
1587            | perf_event_sample_format_PERF_SAMPLE_TIME
1588            | perf_event_sample_format_PERF_SAMPLE_REGS_USER
1589            | perf_event_sample_format_PERF_SAMPLE_STACK_USER) as u64;
1590        let written = write_record_to_vmo(
1591            sample,
1592            &perf_data_vmo,
1593            sample_type,
1594            0,
1595            0,
1596            0,
1597            0,
1598            &metadata,
1599            &mut lost_events,
1600        );
1601        assert!(written > 0);
1602
1603        let mut record_bytes = vec![0u8; written as usize];
1604        perf_data_vmo.read(&mut record_bytes, metadata.data_offset).unwrap();
1605
1606        let record_type = u32::from_ne_bytes(record_bytes[0..4].try_into().unwrap());
1607        assert_eq!(record_type, perf_event_type_PERF_RECORD_SAMPLE);
1608        let misc = u16::from_ne_bytes(record_bytes[4..6].try_into().unwrap());
1609        assert_eq!(misc, PERF_RECORD_MISC_USER as u16);
1610        let size = u16::from_ne_bytes(record_bytes[6..8].try_into().unwrap());
1611        assert_eq!(size as usize, written as usize);
1612
1613        let ip = u64::from_ne_bytes(record_bytes[8..16].try_into().unwrap());
1614        assert_eq!(ip, 0xdeadbeef);
1615
1616        let pid = u32::from_ne_bytes(record_bytes[16..20].try_into().unwrap());
1617        assert_eq!(pid, 10);
1618        let tid = u32::from_ne_bytes(record_bytes[20..24].try_into().unwrap());
1619        assert_eq!(tid, 20);
1620
1621        let time = u64::from_ne_bytes(record_bytes[24..32].try_into().unwrap());
1622        assert_eq!(time, 123_456_789);
1623
1624        // REGS_USER: abi (8 bytes) + regs (16 bytes)
1625        let abi = u64::from_ne_bytes(record_bytes[32..40].try_into().unwrap());
1626        assert_eq!(abi, perf_sample_regs_abi_PERF_SAMPLE_REGS_ABI_32 as u64);
1627        assert_eq!(&record_bytes[40..56], &regs_data);
1628
1629        // STACK_USER: dest_len (8 bytes) + data (16 bytes) + dyn_size (8 bytes)
1630        let stack_len = u64::from_ne_bytes(record_bytes[56..64].try_into().unwrap());
1631        assert_eq!(stack_len, 16);
1632        assert_eq!(&record_bytes[64..80], &stack_data);
1633        let dyn_size = u64::from_ne_bytes(record_bytes[80..88].try_into().unwrap());
1634        assert_eq!(dyn_size, 16);
1635    }
1636
1637    #[::fuchsia::test]
1638    async fn test_write_record_to_vmo_regs_abi_64() {
1639        let perf_data_vmo = zx::Vmo::create(ESTIMATED_MMAP_BUFFER_SIZE).unwrap();
1640        let vmo_handle_copy =
1641            perf_data_vmo.as_handle_ref().duplicate_handle(zx::Rights::SAME_RIGHTS).unwrap();
1642        // SAFETY: The test maintains exclusive write access to this VMO.
1643        let seq_lock = unsafe { create_seq_lock(&vmo_handle_copy, ESTIMATED_MMAP_BUFFER_SIZE) };
1644        let metadata = seq_lock.get();
1645        let mut lost_events = 0;
1646        let regs_data = [0x44u8; 16];
1647        let stack_data = [0x55u8; 16];
1648        let sample = PerfRecordSample {
1649            pid: Some(10),
1650            tid: Some(20),
1651            time: zx::BootInstant::from_nanos(123_456_789),
1652            ips: vec![0xdeadbeef],
1653            regs: &regs_data,
1654            stack: &stack_data,
1655            regs_abi: perf_sample_regs_abi_PERF_SAMPLE_REGS_ABI_64 as u64,
1656            sample_regs_user: 3,
1657            sample_stack_user: 16,
1658        };
1659        let sample_type = (perf_event_sample_format_PERF_SAMPLE_IP
1660            | perf_event_sample_format_PERF_SAMPLE_TID
1661            | perf_event_sample_format_PERF_SAMPLE_TIME
1662            | perf_event_sample_format_PERF_SAMPLE_REGS_USER
1663            | perf_event_sample_format_PERF_SAMPLE_STACK_USER) as u64;
1664        let written = write_record_to_vmo(
1665            sample,
1666            &perf_data_vmo,
1667            sample_type,
1668            0,
1669            0,
1670            0,
1671            0,
1672            &metadata,
1673            &mut lost_events,
1674        );
1675        assert!(written > 0);
1676
1677        let mut record_bytes = vec![0u8; written as usize];
1678        perf_data_vmo.read(&mut record_bytes, metadata.data_offset).unwrap();
1679
1680        // REGS_USER: abi (8 bytes) + regs (16 bytes)
1681        let abi = u64::from_ne_bytes(record_bytes[32..40].try_into().unwrap());
1682        assert_eq!(abi, perf_sample_regs_abi_PERF_SAMPLE_REGS_ABI_64 as u64);
1683        assert_eq!(&record_bytes[40..56], &regs_data);
1684    }
1685
1686    #[::fuchsia::test]
1687    async fn test_write_record_to_vmo_regs_abi_none_when_empty() {
1688        let perf_data_vmo = zx::Vmo::create(ESTIMATED_MMAP_BUFFER_SIZE).unwrap();
1689        let vmo_handle_copy =
1690            perf_data_vmo.as_handle_ref().duplicate_handle(zx::Rights::SAME_RIGHTS).unwrap();
1691        // SAFETY: The test maintains exclusive write access to this VMO.
1692        let seq_lock = unsafe { create_seq_lock(&vmo_handle_copy, ESTIMATED_MMAP_BUFFER_SIZE) };
1693        let metadata = seq_lock.get();
1694        let mut lost_events = 0;
1695        let stack_data = [0x33u8; 8];
1696        let sample = PerfRecordSample {
1697            pid: Some(10),
1698            tid: Some(20),
1699            time: zx::BootInstant::from_nanos(999),
1700            ips: vec![0x1000],
1701            regs: &[],
1702            stack: &stack_data,
1703            regs_abi: perf_sample_regs_abi_PERF_SAMPLE_REGS_ABI_NONE as u64,
1704            sample_regs_user: 3,
1705            sample_stack_user: 8,
1706        };
1707        let sample_type = (perf_event_sample_format_PERF_SAMPLE_IP
1708            | perf_event_sample_format_PERF_SAMPLE_TID
1709            | perf_event_sample_format_PERF_SAMPLE_TIME
1710            | perf_event_sample_format_PERF_SAMPLE_REGS_USER
1711            | perf_event_sample_format_PERF_SAMPLE_STACK_USER) as u64;
1712        let written = write_record_to_vmo(
1713            sample,
1714            &perf_data_vmo,
1715            sample_type,
1716            0,
1717            0,
1718            0,
1719            0,
1720            &metadata,
1721            &mut lost_events,
1722        );
1723        assert!(written > 0);
1724
1725        let mut record_bytes = vec![0u8; written as usize];
1726        perf_data_vmo.read(&mut record_bytes, metadata.data_offset).unwrap();
1727
1728        // REGS_USER: abi (8 bytes) should be PERF_SAMPLE_REGS_ABI_NONE (0), and no regs follow.
1729        let abi = u64::from_ne_bytes(record_bytes[32..40].try_into().unwrap());
1730        assert_eq!(abi, perf_sample_regs_abi_PERF_SAMPLE_REGS_ABI_NONE as u64);
1731
1732        // STACK_USER immediately follows the ABI tag at offset 40.
1733        let stack_len = u64::from_ne_bytes(record_bytes[40..48].try_into().unwrap());
1734        assert_eq!(stack_len, 8);
1735        assert_eq!(&record_bytes[48..56], &stack_data);
1736        let dyn_size = u64::from_ne_bytes(record_bytes[56..64].try_into().unwrap());
1737        assert_eq!(dyn_size, 8);
1738    }
1739}