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::borrow::Cow;
14use std::collections::HashMap;
15use std::error::Error;
16use std::sync::atomic::{AtomicU64, Ordering};
17use std::sync::{Arc, OnceLock, mpsc as sync_mpsc};
18use zerocopy::{Immutable, IntoBytes};
19#[cfg(target_arch = "aarch64")]
20use zx::sys::zx_restricted_state_t;
21
22use fxt::TraceRecord;
23use fxt::profiler::ProfilerRecord;
24use fxt::session::SessionParser;
25use seq_lock::{SeqLock, SeqLockable, WriteSize};
26use starnix_logging::{log_error, log_info, log_warn, track_stub};
27use starnix_sync::{LockDepMutex, LockDepRwLock, PerfEventLevel, PerfFormatIdLookupTableLock};
28use starnix_syscalls::{SUCCESS, SyscallArg, SyscallResult};
29use starnix_uapi::arch32::{
30    PERF_EVENT_IOC_DISABLE, PERF_EVENT_IOC_ENABLE, PERF_EVENT_IOC_ID,
31    PERF_EVENT_IOC_MODIFY_ATTRIBUTES, PERF_EVENT_IOC_PAUSE_OUTPUT, PERF_EVENT_IOC_PERIOD,
32    PERF_EVENT_IOC_QUERY_BPF, PERF_EVENT_IOC_REFRESH, PERF_EVENT_IOC_RESET, PERF_EVENT_IOC_SET_BPF,
33    PERF_EVENT_IOC_SET_FILTER, PERF_EVENT_IOC_SET_OUTPUT, PERF_RECORD_MISC_USER,
34    perf_event_sample_format_PERF_SAMPLE_CALLCHAIN, perf_event_sample_format_PERF_SAMPLE_ID,
35    perf_event_sample_format_PERF_SAMPLE_IDENTIFIER, perf_event_sample_format_PERF_SAMPLE_IP,
36    perf_event_sample_format_PERF_SAMPLE_PERIOD, perf_event_sample_format_PERF_SAMPLE_READ,
37    perf_event_sample_format_PERF_SAMPLE_REGS_USER,
38    perf_event_sample_format_PERF_SAMPLE_STACK_USER, perf_event_sample_format_PERF_SAMPLE_TID,
39    perf_event_sample_format_PERF_SAMPLE_TIME, perf_event_type_PERF_RECORD_LOST,
40    perf_event_type_PERF_RECORD_SAMPLE,
41};
42use starnix_uapi::auth::{CAP_PERFMON, CAP_SYS_ADMIN};
43use starnix_uapi::errors::Errno;
44use starnix_uapi::open_flags::OpenFlags;
45use starnix_uapi::restricted_aspace::RESTRICTED_ASPACE_HIGHEST_ADDRESS;
46use starnix_uapi::uapi::{
47    perf_callchain_context_PERF_CONTEXT_KERNEL, perf_callchain_context_PERF_CONTEXT_USER,
48    perf_sample_regs_abi_PERF_SAMPLE_REGS_ABI_32, perf_sample_regs_abi_PERF_SAMPLE_REGS_ABI_64,
49    perf_sample_regs_abi_PERF_SAMPLE_REGS_ABI_NONE,
50};
51use starnix_uapi::user_address::UserRef;
52use starnix_uapi::{
53    errno, error, from_status_like_fdio, perf_event_attr, perf_event_header,
54    perf_event_mmap_page__bindgen_ty_1, perf_event_read_format_PERF_FORMAT_GROUP,
55    perf_event_read_format_PERF_FORMAT_ID, perf_event_read_format_PERF_FORMAT_LOST,
56    perf_event_read_format_PERF_FORMAT_TOTAL_TIME_ENABLED,
57    perf_event_read_format_PERF_FORMAT_TOTAL_TIME_RUNNING, tid_t, uapi,
58};
59
60use crate::security::{self, PerfEventOpenType, PerfEventTarget, PerfEventType};
61use crate::task::Kernel;
62use crate::task::tracing::{LinuxIdentity, PidKoidSession};
63
64static READ_FORMAT_ID_GENERATOR: AtomicU64 = AtomicU64::new(0);
65// Size of the VMO backing each event's metadata page and ring buffer; mmap
66// lengths up to this size are accepted. perf readers map one metadata page
67// plus a power-of-two data area, and commonly ask for megabytes (e.g. 256
68// data pages or more), so leave generous headroom. With circular writes the
69// data area no longer needs to hold a whole session's records.
70//
71// We currently preallocate a fixed size for the VMO because we do not dynamically
72// resize it when mapped.
73// TODO(https://fxbug.dev/540986386): Support dynamic resizing or pass the requested size.
74const ESTIMATED_MMAP_BUFFER_SIZE: u64 = 16 * 1024 * 1024;
75// Size of a PERF_RECORD_LOST record in bytes:
76// perf_event_header (8) + sample_id (8) + lost_events (8) = 24.
77const LOST_RECORD_SIZE: u64 = 24;
78// Register indices in the profiler's register capture block.
79const AARCH64_REG_PC: usize = 32;
80const AARCH64_REG_SP: usize = 31;
81const AARCH64_REG_LR: usize = 30;
82// AArch32 state arrives in the same block, R0-R14 in x0-x14 (the Linux
83// compat layout); the AArch32 PC, LR and SP are R15, R14 and R13.
84const AARCH32_REG_PC: usize = 15; // R15
85const AARCH32_REG_LR: usize = 14; // R14
86const AARCH32_REG_SP: usize = 13; // R13
87// Size of the 33 u64 general registers (r0-r29, lr, sp, pc) at the start of
88// both the profiler's register block and `zx_restricted_state_t`.
89const REGS_BYTES: usize = 33 * 8;
90#[cfg(target_arch = "aarch64")]
91const GUEST_ASPACE_BASE: u64 = starnix_uapi::restricted_aspace::RESTRICTED_ASPACE_BASE as u64;
92const GUEST_ASPACE_END: u64 = RESTRICTED_ASPACE_HIGHEST_ADDRESS as u64;
93#[cfg(target_arch = "aarch64")]
94const RESTRICTED_STATE_SIZE: usize = std::mem::size_of::<zx_restricted_state_t>();
95#[cfg(target_arch = "aarch64")]
96const RESTRICTED_STATE_CPSR_OFFSET: usize = std::mem::offset_of!(zx_restricted_state_t, cpsr);
97#[cfg(target_arch = "aarch64")]
98const _: () = {
99    assert!(std::mem::offset_of!(zx_restricted_state_t, sp) == AARCH64_REG_SP * 8);
100    assert!(std::mem::offset_of!(zx_restricted_state_t, pc) == AARCH64_REG_PC * 8);
101    assert!(REGS_BYTES <= RESTRICTED_STATE_CPSR_OFFSET);
102};
103
104mod event;
105pub use event::{TraceEvent, TraceEventQueue, TraceEventQueueList};
106
107#[repr(C)]
108#[derive(Copy, Clone, IntoBytes, Immutable)]
109struct LostRecord {
110    header: perf_event_header,
111    sample_id: u64,
112    lost_events: u64,
113}
114
115#[repr(C)]
116#[derive(Copy, Clone, IntoBytes, Immutable)]
117struct PerfMetadataHeader {
118    version: u32,
119    compat_version: u32,
120}
121
122#[repr(C)]
123#[derive(Copy, Clone, IntoBytes, Immutable)]
124struct PerfMetadataValue {
125    lock: u32,
126    index: u32,
127    offset: i64,
128    time_enabled: u64,
129    time_running: u64,
130    __bindgen_anon_1: perf_event_mmap_page__bindgen_ty_1,
131    pmc_width: u16,
132    time_shift: u16,
133    time_mult: u32,
134    time_offset: u64,
135    time_zero: u64,
136    size: u32,
137    __reserved_1: u32,
138    time_cycles: u64,
139    time_mask: u64,
140    __reserved: [u8; 928usize],
141    data_head: u64,
142    data_tail: u64,
143    data_offset: u64,
144    data_size: u64,
145    aux_head: u64,
146    aux_tail: u64,
147    aux_offset: u64,
148    aux_size: u64,
149}
150
151// SAFETY: `PerfMetadataValue` can be safely written to shared memory in 8-byte chunks.
152// This is because it is composed of two u32s followed by only u64s.
153// The first u32 is the `lock` field, which is why HAS_INLINE_SEQUENCE is true.
154unsafe impl SeqLockable for PerfMetadataValue {
155    const WRITE_SIZE: WriteSize = WriteSize::Eight;
156    const HAS_INLINE_SEQUENCE: bool = true;
157    const VMO_NAME: &'static [u8] = b"starnix:perf_event";
158}
159
160struct PerfState {
161    // This table maps a group leader's file object id to its unique u64 "format ID".
162    //
163    // When a sample is generated for any event in a group, we use this
164    // "format ID" from the group leader as the value for *both* the
165    // `PERF_SAMPLE_ID` and `PERF_SAMPLE_IDENTIFIER` fields.
166    format_id_lookup_table: LockDepMutex<HashMap<FileObjectId, u64>, PerfFormatIdLookupTableLock>,
167}
168
169impl Default for PerfState {
170    fn default() -> Self {
171        Self { format_id_lookup_table: Default::default() }
172    }
173}
174
175fn get_perf_state(kernel: &Arc<Kernel>) -> Arc<PerfState> {
176    kernel.expando.get_or_init(PerfState::default)
177}
178
179uapi::check_arch_independent_layout! {
180    perf_event_attr {
181        type_, // "type" is a reserved keyword so add a trailing underscore.
182        size,
183        config,
184        __bindgen_anon_1,
185        sample_type,
186        read_format,
187        _bitfield_1,
188        __bindgen_anon_2,
189        bp_type,
190        __bindgen_anon_3,
191        __bindgen_anon_4,
192        branch_sample_type,
193        sample_regs_user,
194        sample_stack_user,
195        clockid,
196        sample_regs_intr,
197        aux_watermark,
198        sample_max_stack,
199        __reserved_2,
200        aux_sample_size,
201        __reserved_3,
202        sig_data,
203        config3,
204    }
205}
206
207#[derive(Clone, Copy, Debug, PartialEq)]
208enum IoctlOp {
209    Enable,
210    Disable,
211}
212
213struct PerfEventFileState {
214    attr: perf_event_attr,
215    rf_value: u64, // "count" for the config we passed in for the event.
216    // The most recent timestamp (ns) where we changed into an enabled state
217    // i.e. the most recent time we got an ENABLE ioctl().
218    most_recent_enabled_time: u64,
219    // Sum of all previous enablement segment durations (ns). If we are
220    // currently in an enabled state, explicitly does NOT include the current
221    // segment.
222    total_time_running: u64,
223    rf_id: u64,
224    sample_id: u64,
225    _rf_lost: u64,
226    disabled: u64,
227    sample_type: u64,
228    // Handle to blob that stores all the perf data that a user may want.
229    // At the moment it only stores some metadata and backtraces (bts).
230    perf_data_vmo: zx::Vmo,
231    // Channel used to send IoctlOps to start/stop sampling.
232    ioctl_sender: future_mpsc::Sender<(IoctlOp, sync_mpsc::Sender<()>)>,
233}
234
235// Have an implementation for PerfEventFileState because VMO
236// doesn't have Default so we can't derive it.
237impl PerfEventFileState {
238    fn new(
239        attr: perf_event_attr,
240        rf_value: u64,
241        disabled: u64,
242        sample_type: u64,
243        perf_data_vmo: zx::Vmo,
244        ioctl_sender: future_mpsc::Sender<(IoctlOp, sync_mpsc::Sender<()>)>,
245    ) -> PerfEventFileState {
246        PerfEventFileState {
247            attr,
248            rf_value,
249            most_recent_enabled_time: 0,
250            total_time_running: 0,
251            rf_id: 0,
252            sample_id: 0,
253            _rf_lost: 0,
254            disabled,
255            sample_type,
256            perf_data_vmo,
257            ioctl_sender,
258        }
259    }
260}
261
262pub struct PerfEventFile {
263    _tid: tid_t,
264    _cpu: i32,
265    perf_event_file: LockDepRwLock<PerfEventFileState, PerfEventLevel>,
266    // The security state for this PerfEventFile.
267    pub security_state: security::PerfEventState,
268    seq_lock: Arc<OnceLock<Result<SeqLock<PerfMetadataHeader, PerfMetadataValue>, Errno>>>,
269}
270
271// PerfEventFile object that implements FileOps.
272// See https://man7.org/linux/man-pages/man2/perf_event_open.2.html for
273// implementation details.
274// This object can be saved as a FileDescriptor.
275impl FileOps for PerfEventFile {
276    // Don't need to implement seek or sync for PerfEventFile.
277    fileops_impl_nonseekable!();
278    fileops_impl_noop_sync!();
279
280    fn close(self: Box<Self>, file: &FileObjectState, current_task: &CurrentTask) {
281        {
282            let mut perf_event_file = self.perf_event_file.write();
283            // Ensure we disable so we clean up resources if the user closes without disabling.
284            perf_event_file.disabled = 1;
285            ping_receiver(perf_event_file.ioctl_sender.clone(), IoctlOp::Disable);
286        }
287        let perf_state = get_perf_state(&current_task.kernel);
288        let mut events = perf_state.format_id_lookup_table.lock();
289        events.remove(&file.id);
290    }
291
292    // See "Reading results" section of https://man7.org/linux/man-pages/man2/perf_event_open.2.html.
293    fn read(
294        &self,
295        _file: &FileObject,
296        current_task: &CurrentTask,
297        _offset: usize,
298        data: &mut dyn OutputBuffer,
299    ) -> Result<usize, Errno> {
300        security::check_perf_event_read_access(current_task, self)?;
301
302        // Create/calculate and return the ReadFormatData object.
303        // If we create it earlier we might want to change it and it's immutable once created.
304        let read_format_data = {
305            // Once we get the `value` or count from kernel, we can change this to a read()
306            // call instead of write().
307            let mut perf_event_file = self.perf_event_file.write();
308
309            let mut total_time_running_including_curr = perf_event_file.total_time_running;
310
311            // Only update values if enabled (either by perf_event_attr or ioctl ENABLE call).
312            if perf_event_file.disabled == 0 {
313                // Calculate the value or "count" of the config we're interested in.
314                // This value should reflect the value we are counting (defined in the config).
315                // E.g. for PERF_COUNT_SW_CPU_CLOCK it would return the value from the CPU clock.
316                // For now we just return rf_value + 1.
317                track_stub!(
318                    TODO("https://fxbug.dev/402938671"),
319                    "[perf_event_open] implement read_format value"
320                );
321                perf_event_file.rf_value += 1;
322
323                // Update time duration.
324                let curr_time = zx::MonotonicInstant::get().into_nanos() as u64;
325                total_time_running_including_curr +=
326                    curr_time - perf_event_file.most_recent_enabled_time;
327            }
328
329            let mut output = Vec::<u8>::new();
330            let value = perf_event_file.rf_value.to_ne_bytes();
331            output.extend(value);
332
333            let read_format = perf_event_file.attr.read_format;
334
335            if (read_format & perf_event_read_format_PERF_FORMAT_TOTAL_TIME_ENABLED as u64) != 0 {
336                // Total time (ns) event was enabled and running (currently same as TIME_RUNNING).
337                output.extend(total_time_running_including_curr.to_ne_bytes());
338            }
339            if (read_format & perf_event_read_format_PERF_FORMAT_TOTAL_TIME_RUNNING as u64) != 0 {
340                // Total time (ns) event was enabled and running (currently same as TIME_ENABLED).
341                output.extend(total_time_running_including_curr.to_ne_bytes());
342            }
343            if (read_format & perf_event_read_format_PERF_FORMAT_ID as u64) != 0 {
344                // Adds a 64-bit unique value that corresponds to the event group.
345                output.extend(perf_event_file.rf_id.to_ne_bytes());
346            }
347
348            output
349        };
350
351        // The regular read() call allows the case where the bytes-we-want-to-read-in won't
352        // fit in the output buffer. However, for perf_event_open's read(), "If you attempt to read
353        // into a buffer that is not big enough to hold the data, the error ENOSPC results."
354        if data.available() < read_format_data.len() {
355            return error!(ENOSPC);
356        }
357        track_stub!(
358            TODO("https://fxbug.dev/402453955"),
359            "[perf_event_open] implement remaining error handling"
360        );
361
362        data.write(&read_format_data)
363    }
364
365    fn ioctl(
366        &self,
367        _file: &FileObject,
368        current_task: &CurrentTask,
369        op: u32,
370        _arg: SyscallArg,
371    ) -> Result<SyscallResult, Errno> {
372        track_stub!(
373            TODO("https://fxbug.dev/405463320"),
374            "[perf_event_open] implement PERF_IOC_FLAG_GROUP"
375        );
376        security::check_perf_event_write_access(current_task, self)?;
377        let mut perf_event_file = self.perf_event_file.write();
378        match op {
379            PERF_EVENT_IOC_ENABLE => {
380                if perf_event_file.disabled != 0 {
381                    perf_event_file.disabled = 0; // 0 = false.
382                    perf_event_file.most_recent_enabled_time =
383                        zx::MonotonicInstant::get().into_nanos() as u64;
384                }
385
386                // If we are sampling, invoke the profiler and collect a sample.
387                // Currently this is an example sample collection.
388                track_stub!(
389                    TODO("https://fxbug.dev/398914921"),
390                    "[perf_event_open] implement full sampling features"
391                );
392                if sampling_period(&perf_event_file.attr).is_some() {
393                    ping_receiver(perf_event_file.ioctl_sender.clone(), IoctlOp::Enable);
394                }
395                return Ok(SUCCESS);
396            }
397            PERF_EVENT_IOC_DISABLE => {
398                if perf_event_file.disabled == 0 {
399                    perf_event_file.disabled = 1; // 1 = true.
400
401                    // Update total_time_running now that the segment has ended.
402                    let curr_time = zx::MonotonicInstant::get().into_nanos() as u64;
403                    perf_event_file.total_time_running +=
404                        curr_time - perf_event_file.most_recent_enabled_time;
405                }
406                if sampling_period(&perf_event_file.attr).is_some() {
407                    ping_receiver(perf_event_file.ioctl_sender.clone(), IoctlOp::Disable);
408                }
409                return Ok(SUCCESS);
410            }
411            PERF_EVENT_IOC_RESET => {
412                perf_event_file.rf_value = 0;
413                return Ok(SUCCESS);
414            }
415            PERF_EVENT_IOC_REFRESH
416            | PERF_EVENT_IOC_PERIOD
417            | PERF_EVENT_IOC_SET_OUTPUT
418            | PERF_EVENT_IOC_SET_FILTER
419            | PERF_EVENT_IOC_ID
420            | PERF_EVENT_IOC_SET_BPF
421            | PERF_EVENT_IOC_PAUSE_OUTPUT
422            | PERF_EVENT_IOC_MODIFY_ATTRIBUTES
423            | PERF_EVENT_IOC_QUERY_BPF => {
424                track_stub!(
425                    TODO("https://fxbug.dev/404941053"),
426                    "[perf_event_open] implement remaining ioctl() calls"
427                );
428                return error!(ENOSYS);
429            }
430            _ => error!(ENOTTY),
431        }
432    }
433
434    // TODO(https://fxbug.dev/460245383) match behavior when mmap() is called multiple times.
435    // Gets called when mmap() is called.
436    // Immediately before sampling, this should get called by the user (e.g. the test
437    // or Perfetto). We will then write the metadata to the VMO and return the pointer to it.
438    fn get_memory(
439        &self,
440        _file: &FileObject,
441        current_task: &CurrentTask,
442        length: Option<usize>,
443        prot: ProtectionFlags,
444    ) -> Result<Arc<MemoryObject>, Errno> {
445        if prot.contains(ProtectionFlags::EXEC) {
446            return error!(EPERM);
447        }
448        let buffer_size: u64 = length.unwrap_or(0) as u64;
449        let page_size = zx::system_get_page_size() as u64;
450        if buffer_size <= page_size || buffer_size > ESTIMATED_MMAP_BUFFER_SIZE {
451            return error!(EINVAL);
452        }
453        let data_size = buffer_size - page_size;
454        if !data_size.is_power_of_two() {
455            return error!(EINVAL);
456        }
457
458        security::check_perf_event_read_access(current_task, self)?;
459
460        self.seq_lock
461            .get_or_init(|| {
462                let perf_event_file = self.perf_event_file.read();
463                let vmo_copy = perf_event_file
464                    .perf_data_vmo
465                    .as_handle_ref()
466                    .duplicate_handle(zx::Rights::SAME_RIGHTS)
467                    .map_err(|status| from_status_like_fdio!(status))?;
468                // SAFETY: See safety requirements on `create_seq_lock`.
469                Ok(unsafe { create_seq_lock(&vmo_copy, buffer_size) })
470            })
471            .as_ref()
472            .map_err(|e| e.clone())?;
473
474        // Write to a MemoryObject and return it (expected return type for get_memory()).
475        let perf_event_file = self.perf_event_file.read();
476        match perf_event_file
477            .perf_data_vmo
478            .as_handle_ref()
479            .duplicate_handle(zx::Rights::SAME_RIGHTS)
480        {
481            Ok(vmo) => {
482                let vmo: zx::Vmo = vmo.into();
483                let memory = MemoryObject::from(vmo);
484                return Ok(Arc::new(memory));
485            }
486            Err(_) => {
487                track_stub!(
488                    TODO("https://fxbug.dev/416323134"),
489                    "[perf_event_open] handle get_memory() errors"
490                );
491                return error!(EINVAL);
492            }
493        };
494    }
495
496    // The perf data buffer is mapped through memory shared with Starnix, which isn't executable.
497    fn can_map_executable(&self) -> bool {
498        false
499    }
500
501    fn write(
502        &self,
503        _file: &FileObject,
504        _current_task: &CurrentTask,
505        _offset: usize,
506        _data: &mut dyn InputBuffer,
507    ) -> Result<usize, Errno> {
508        track_stub!(
509            TODO("https://fxbug.dev/394960158"),
510            "[perf_event_open] implement perf event functions"
511        );
512        error!(ENOSYS)
513    }
514}
515
516// Given a PerfRecordSample struct, write it via the correct output format
517// (per https://man7.org/linux/man-pages/man2/perf_event_open.2.html) to the VMO.
518// We don't currently support all the sample_types listed in the docs.
519// Input:
520//    PerfRecordSample { pid: 5, tid: 10, nr: 3, ips[nr]: [111, 222, 333] }
521// Human-understandable output:
522//    9 1 40 111 5 10 3 111 222 333
523// Actual output (no spaces or \n in real output, just making it more readable):
524//    0x0000 0x0009                 <-- starts at `offset` bytes
525//    0x0001
526//    0x0040
527//    0x0000 0x0000 0x0000 0x006F   <-- starts at `offset` + 8 bytes
528//    0x0000 0x0000 0x0000 0x0005
529//    0x0000 0x0000 0x0000 0x0010
530//    0x0000 0x0000 0x0000 0x0003
531//    0x0000 0x0000 0x0000 0x006F
532//    0x0000 0x0000 0x0000 0x00DE
533//    0x0000 0x0000 0x0000 0x014D
534//
535//    Returns the length of bytes written. In above case, 8 + 28 = 36.
536//    This information is used to increment the global offset.
537//
538//    If writing to the VMO fails, we log a warning and return the number of
539//    bytes successfully written so far (e.g. only the LOST record if that
540//    succeeded, or 0). The caller uses this returned length to increment the
541//    VMO write offset and update `data_head`, meaning failed writes are
542//    effectively skipped and not exposed to the reader.
543fn write_record_to_vmo(
544    perf_record_sample: PerfRecordSample<'_>,
545    perf_data_vmo: &zx::Vmo,
546    sample_type: u64,
547    sample_id: u64,
548    sample_period: u64,
549    read_format: u64,
550    head: u64,
551    metadata: &PerfMetadataValue,
552    lost_events: &mut u64,
553) -> u64 {
554    let ring_buffer_size = metadata.data_size;
555    if ring_buffer_size == 0 {
556        return 0;
557    }
558    // First, build record to determine its size (so that we can fill out `size` in header).
559    let mut sample = Vec::<u8>::new();
560    // sample_id
561    if (sample_type & perf_event_sample_format_PERF_SAMPLE_IDENTIFIER as u64) != 0 {
562        sample.extend(sample_id.to_ne_bytes());
563    }
564    // ip
565    if (sample_type & perf_event_sample_format_PERF_SAMPLE_IP as u64) != 0 {
566        let ip = perf_record_sample.ips.first().copied().unwrap_or(0);
567        sample.extend(ip.to_ne_bytes());
568    }
569
570    if (sample_type & perf_event_sample_format_PERF_SAMPLE_TID as u64) != 0 {
571        // pid
572        sample.extend(perf_record_sample.pid.unwrap_or(0).to_ne_bytes());
573        // tid
574        sample.extend(perf_record_sample.tid.unwrap_or(0).to_ne_bytes());
575    }
576
577    // time: when the sample was taken.
578    if (sample_type & perf_event_sample_format_PERF_SAMPLE_TIME as u64) != 0 {
579        sample.extend((perf_record_sample.time.into_nanos() as u64).to_ne_bytes());
580    }
581
582    // id
583    if (sample_type & perf_event_sample_format_PERF_SAMPLE_ID as u64) != 0 {
584        sample.extend(sample_id.to_ne_bytes());
585    }
586
587    // sample period
588    if (sample_type & perf_event_sample_format_PERF_SAMPLE_PERIOD as u64) != 0 {
589        sample.extend(sample_period.to_ne_bytes());
590    }
591
592    // read_format value.
593    if (sample_type & perf_event_sample_format_PERF_SAMPLE_READ as u64) != 0 {
594        if (read_format & perf_event_read_format_PERF_FORMAT_GROUP as u64) != 0 {
595            // Group reads start with the number of events followed by each
596            // event's value; only the timebase event exists.
597            sample.extend(1u64.to_ne_bytes());
598            sample.extend(0u64.to_ne_bytes());
599        } else {
600            sample.extend(0u64.to_ne_bytes());
601        }
602    }
603
604    if (sample_type & perf_event_sample_format_PERF_SAMPLE_CALLCHAIN as u64) != 0 {
605        // Callchains are structured into sections, each introduced by a
606        // PERF_CONTEXT_* marker, and readers rely on these markers to split
607        // kernel and user frames.
608        //
609        // Starnix normal-mode execution (>= GUEST_ASPACE_END, above
610        // restricted_return_loop) represents kernel execution from the guest's
611        // perspective and belongs in PERF_CONTEXT_KERNEL; restricted-mode
612        // guest execution (< GUEST_ASPACE_END) belongs in PERF_CONTEXT_USER.
613        // Both sections are always emitted; readers that unwind user stacks
614        // themselves (DWARF) stop at the PERF_CONTEXT_USER marker.
615
616        // Reserve nr, then fill it in once the markers and ips are written.
617        let nr_offset = sample.len();
618        sample.extend(0u64.to_ne_bytes());
619        let callchain_start = sample.len();
620
621        sample.extend(perf_callchain_context_PERF_CONTEXT_KERNEL.to_ne_bytes());
622        for ip in perf_record_sample.ips.iter().filter(|&&ip| ip >= GUEST_ASPACE_END) {
623            sample.extend(ip.to_ne_bytes());
624        }
625        sample.extend(perf_callchain_context_PERF_CONTEXT_USER.to_ne_bytes());
626        for ip in perf_record_sample.ips.iter().filter(|&&ip| ip < GUEST_ASPACE_END) {
627            sample.extend(ip.to_ne_bytes());
628        }
629
630        // nr, counting the markers and ips.
631        let nr = ((sample.len() - callchain_start) / std::mem::size_of::<u64>()) as u64;
632        sample[nr_offset..callchain_start].copy_from_slice(&nr.to_ne_bytes());
633    }
634
635    // User registers (PERF_SAMPLE_REGS_USER): the ABI tag, then one u64 per
636    // bit set in attr.sample_regs_user, in perf register-index order.
637    // Readers request the mask for their own architecture -- a 64-bit reader
638    // requests the arm64 mask even when profiling 32-bit tasks (and relocates
639    // the PC from the PERF_REG_ARM64_PC index itself, matching the Linux
640    // kernel's behavior of capturing native registers).
641    // If the mask is 0 or the ABI is PERF_SAMPLE_REGS_ABI_NONE, no register
642    // values are output.
643    if (sample_type & perf_event_sample_format_PERF_SAMPLE_REGS_USER as u64) != 0 {
644        if perf_record_sample.regs_abi == perf_sample_regs_abi_PERF_SAMPLE_REGS_ABI_NONE as u64
645            || perf_record_sample.sample_regs_user == 0
646            || perf_record_sample.regs.is_empty()
647        {
648            sample.extend((perf_sample_regs_abi_PERF_SAMPLE_REGS_ABI_NONE as u64).to_ne_bytes());
649        } else {
650            sample.extend(perf_record_sample.regs_abi.to_ne_bytes());
651            sample.extend_from_slice(perf_record_sample.regs);
652        }
653    }
654
655    // User stack (PERF_SAMPLE_STACK_USER): size, the raw bytes starting at
656    // the sampled stack pointer, then the filled size. Readers overlay these
657    // bytes at the SP reported in REGS_USER, which is why the snapshot must
658    // begin exactly at it.
659    if (sample_type & perf_event_sample_format_PERF_SAMPLE_STACK_USER as u64) != 0 {
660        let fixed_len = std::mem::size_of::<perf_event_header>() + sample.len() + 16;
661        let header_space = (u16::MAX as usize).saturating_sub(fixed_len) & !7;
662        let requested = (perf_record_sample.sample_stack_user as usize) & !7;
663        let dest_len = requested.min(header_space);
664
665        sample.extend((dest_len as u64).to_ne_bytes());
666        if dest_len > 0 {
667            let data_len = perf_record_sample.stack.len().min(dest_len) & !7;
668            sample.extend_from_slice(&perf_record_sample.stack[..data_len]);
669            // Pad the rest with zeros.
670            sample.resize(sample.len() + (dest_len - data_len), 0);
671            // dyn_size
672            sample.extend((data_len as u64).to_ne_bytes());
673        }
674    }
675    // The remaining sample_type fields are not implemented.
676
677    // Now that we know the sample size, we can calculate the record size.
678    // record_size = perf_event_header_size + sample_size.
679    // perf_event_header is defined to be 8 bytes.
680    let record_len = std::mem::size_of::<perf_event_header>() + sample.len();
681    // Every field above is a u64 (or a u32 pair), so record sizes are always
682    // multiples of 8: ring positions stay 8-byte aligned. Since the
683    // perf_event_header is also 8 bytes, it can never straddle the end of
684    // the data area (which is page-aligned), meaning readers can always
685    // read the header contiguously.
686    if record_len % 8 != 0 {
687        log_error!("Record length {} is not 8-byte aligned, dropping", record_len);
688        *lost_events += 1;
689        return 0;
690    }
691    // The header's size field is a u16. A record that exceeds it would
692    // silently wrap the size and desynchronize every record after it, so
693    // drop it instead.
694    let Ok(record_size) = u16::try_from(record_len) else {
695        log_warn!("Dropping {} byte perf sample record: exceeds u16 record size", record_len);
696        *lost_events += 1;
697        return 0;
698    };
699
700    let perf_event_header = perf_event_header {
701        // These are samples of user-space execution; readers take the
702        // sample's cpu mode from the misc bits.
703        type_: perf_event_type_PERF_RECORD_SAMPLE,
704        misc: PERF_RECORD_MISC_USER as u16,
705        size: record_size,
706    };
707
708    // data_tail is advanced by userspace as it consumes records and is untrusted.
709    // Calculate free space using the standard circular buffer formula, matching Linux's
710    // CIRC_SPACE. This naturally handles wrap-around and invalid future tails.
711    // ring_buffer_size is guaranteed to be a power of two by checks in get_memory().
712    let free_space =
713        (metadata.data_tail.wrapping_sub(head).wrapping_sub(1)) & (ring_buffer_size - 1);
714
715    // Drop the sample if the ring buffer is full, matching Linux's
716    // non-overwrite mode; the drop is reported via PERF_RECORD_LOST once
717    // space frees up.
718    if free_space < record_len as u64 {
719        *lost_events += 1;
720        return 0;
721    }
722
723    let mut bytes_written: u64 = 0;
724
725    // If records were dropped earlier, surface a PERF_RECORD_LOST record as
726    // soon as there is room for it alongside the current sample.
727    if *lost_events > 0 && free_space >= record_len as u64 + LOST_RECORD_SIZE {
728        let lost_header = perf_event_header {
729            type_: perf_event_type_PERF_RECORD_LOST,
730            misc: 0,
731            size: LOST_RECORD_SIZE as u16,
732        };
733        let lost_record = LostRecord { header: lost_header, sample_id, lost_events: *lost_events };
734        if write_circular(
735            perf_data_vmo,
736            metadata.data_offset,
737            ring_buffer_size,
738            head,
739            lost_record.as_bytes(),
740        )
741        .is_ok()
742        {
743            *lost_events = 0;
744            bytes_written += LOST_RECORD_SIZE;
745        }
746    }
747
748    let mut record = Vec::with_capacity(record_len);
749    record.extend_from_slice(perf_event_header.as_bytes());
750    record.extend_from_slice(&sample);
751
752    match write_circular(
753        perf_data_vmo,
754        metadata.data_offset,
755        ring_buffer_size,
756        head + bytes_written,
757        &record,
758    ) {
759        // Return the total size we wrote so the caller can advance data_head.
760        Ok(()) => bytes_written + record_len as u64,
761        Err(e) => {
762            log_warn!("Failed to write PerfRecordSample to VMO due to: {}", e);
763            bytes_written
764        }
765    }
766}
767
768// Writes `data` into the ring buffer's data area at the position
769// corresponding to `head` (a free-running count of bytes ever written),
770// splitting the write across the end of the data area when it wraps around.
771// Readers read records by checking the header size, reading contiguously,
772// and wrapping around to the beginning of the data area if the record is split.
773fn write_circular(
774    vmo: &zx::Vmo,
775    data_offset: u64,
776    ring_buffer_size: u64,
777    head: u64,
778    data: &[u8],
779) -> Result<(), zx::Status> {
780    let position = head % ring_buffer_size;
781    let vmo_offset = data_offset + position;
782    if position + data.len() as u64 <= ring_buffer_size {
783        vmo.write(data, vmo_offset)?;
784    } else {
785        let first_len = (ring_buffer_size - position) as usize;
786        vmo.write(&data[..first_len], vmo_offset)?;
787        vmo.write(&data[first_len..], data_offset)?;
788    }
789    Ok(())
790}
791
792/// Represents a PERF_RECORD_SAMPLE payload to be serialized into the VMO ring buffer.
793/// Fields follow the perf ABI order: TID, TIME, ID, PERIOD, READ, CALLCHAIN, REGS_USER, STACK_USER.
794#[derive(Debug, Clone)]
795struct PerfRecordSample<'a> {
796    pid: Option<u32>,
797    tid: Option<u32>,
798    // Timestamp of when the sample was taken, for PERF_SAMPLE_TIME.
799    time: zx::BootInstant,
800    // Instruction pointers (currently this is the address). First one is `ip` param.
801    ips: Vec<u64>,
802    regs: &'a [u8],
803    stack: &'a [u8],
804    // PERF_SAMPLE_REGS_ABI_32 (1) or PERF_SAMPLE_REGS_ABI_64 (2) for `regs`.
805    regs_abi: u64,
806    sample_regs_user: u64,
807    sample_stack_user: u64,
808}
809
810fn callgraph_config(sample_type: u64, sample_stack_user: u32) -> profiler::CallgraphConfig {
811    // Events that asked for register and stack state (PERF_SAMPLE_REGS_USER /
812    // PERF_SAMPLE_STACK_USER) need raw stack snapshots, which only the DWARF
813    // strategy captures. Events that just want a callchain
814    // (PERF_SAMPLE_CALLCHAIN) are served by the in-kernel sampler, which
815    // unwinds via frame pointers without suspending the sampled thread.
816    let want_stack_samples = (sample_type
817        & (perf_event_sample_format_PERF_SAMPLE_REGS_USER as u64
818            | perf_event_sample_format_PERF_SAMPLE_STACK_USER as u64))
819        != 0;
820    let strategy = if want_stack_samples {
821        profiler::CallgraphStrategy::Dwarf
822    } else {
823        profiler::CallgraphStrategy::FramePointer
824    };
825    let stack_size = ((sample_type & perf_event_sample_format_PERF_SAMPLE_STACK_USER as u64) != 0)
826        .then_some(u64::from(sample_stack_user));
827    profiler::CallgraphConfig { strategy: Some(strategy), stack_size, ..Default::default() }
828}
829
830async fn set_up_profiler(
831    sample_period: zx::MonotonicDuration,
832    callgraph: profiler::CallgraphConfig,
833) -> Result<(profiler::SessionProxy, fidl::AsyncSocket), Errno> {
834    let sample = profiler::Sample { callgraph: Some(callgraph), ..Default::default() };
835
836    let sampling_config = profiler::SamplingConfig {
837        period: Some(sample_period.into_nanos() as u64),
838        timebase: Some(profiler::Counter::PlatformIndependent(profiler::CounterId::Nanoseconds)),
839        sample: Some(sample),
840        ..Default::default()
841    };
842
843    track_stub!(
844        TODO("https://fxbug.dev/398914921"),
845        "[perf_event_open] allow for profiling system-wide not during tests"
846    );
847    let job = fuchsia_runtime::job_default();
848    let koid = job.koid().map_err(|e| errno!(EINVAL, e.to_string()))?;
849    let tasks = vec![
850        // Should return ~1300 samples for 1000 millis.
851        profiler::Task::Job(koid.raw_koid()),
852    ];
853    let targets = profiler::TargetConfig::Tasks(tasks);
854    let config = profiler::Config {
855        configs: Some(vec![sampling_config]),
856        target: Some(targets),
857        ..Default::default()
858    };
859    let (client, server) = fidl::Socket::create_stream();
860    let configure = profiler::SessionConfigureRequest {
861        output: Some(server),
862        config: Some(config),
863        ..Default::default()
864    };
865
866    let proxy = connect_to_protocol::<profiler::SessionMarker>()
867        .context("Error connecting to Profiler protocol");
868    let session_proxy: profiler::SessionProxy = match proxy {
869        Ok(p) => p.clone(),
870        Err(e) => return error!(EINVAL, e),
871    };
872
873    // Must configure before sampling start().
874    let config_request = session_proxy.configure(configure).await;
875    match config_request {
876        Ok(_) => Ok((session_proxy, fidl::AsyncSocket::from_socket(client))),
877        Err(e) => return error!(EINVAL, e),
878    }
879}
880
881// Returns true if `cpsr` indicates AArch32 execution.
882#[cfg(target_arch = "aarch64")]
883fn cpsr_is_aarch32(cpsr: u64) -> bool {
884    (cpsr & zx::sys::ZX_REG_CPSR_ARCH_32_MASK) == zx::sys::ZX_REG_CPSR_ARCH_32_MASK
885}
886
887#[cfg(not(target_arch = "aarch64"))]
888fn cpsr_is_aarch32(_cpsr: u64) -> bool {
889    false
890}
891
892// Searches `chunks` for the thread's `zx_restricted_state_t` capture and
893// returns the saved guest registers (`x0..x30, sp, pc`) and `cpsr`.
894//
895// A chunk matches if it is page-aligned in the Starnix address space, has
896// the size of `zx_restricted_state_t`, and holds a guest PC and SP within
897// the restricted address space.
898#[cfg(target_arch = "aarch64")]
899fn guest_regs_from_restricted_state<'a>(chunks: &[(u64, &'a [u8])]) -> Option<(&'a [u8], u64)> {
900    chunks.iter().find_map(|&(base, data)| {
901        if base < GUEST_ASPACE_END || base % *PAGE_SIZE != 0 || data.len() != RESTRICTED_STATE_SIZE
902        {
903            return None;
904        }
905        let read_reg =
906            |index: usize| u64::from_ne_bytes(data[index * 8..index * 8 + 8].try_into().unwrap());
907        let cpsr = u32::from_ne_bytes(
908            data[RESTRICTED_STATE_CPSR_OFFSET..RESTRICTED_STATE_CPSR_OFFSET + 4]
909                .try_into()
910                .unwrap(),
911        );
912        let cpsr = u64::from(cpsr);
913        // AArch32 execution keeps its stack pointer in R13 (x13).
914        let sp =
915            if cpsr_is_aarch32(cpsr) { read_reg(AARCH32_REG_SP) } else { read_reg(AARCH64_REG_SP) };
916        let pc = read_reg(AARCH64_REG_PC);
917        if !(GUEST_ASPACE_BASE..GUEST_ASPACE_END).contains(&pc) || sp >= GUEST_ASPACE_END {
918            return None;
919        }
920        Some((&data[..REGS_BYTES], cpsr))
921    })
922}
923
924#[cfg(not(target_arch = "aarch64"))]
925fn guest_regs_from_restricted_state<'a>(_chunks: &[(u64, &'a [u8])]) -> Option<(&'a [u8], u64)> {
926    None
927}
928
929// Converts one FXT record from the profiler into a PERF_RECORD_SAMPLE in
930// the ring buffer and publishes the new data_head. Non-sample records are
931// ignored.
932fn process_fxt_record(
933    record: TraceRecord,
934    seq_lock_wrapper: &SeqLock<PerfMetadataHeader, PerfMetadataValue>,
935    perf_data_vmo: &zx::Vmo,
936    sample_type: u64,
937    sample_id: u64,
938    sample_period: u64,
939    read_format: u64,
940    sample_regs_user: u64,
941    sample_stack_user: u64,
942    koid_session: Option<&PidKoidSession>,
943    vmo_write_offset: &mut u64,
944    lost_events: &mut u64,
945) {
946    match record {
947        TraceRecord::Profiler(ProfilerRecord::Backtrace(backtrace)) => {
948            let ips: Vec<u64> = backtrace.data;
949            // Resolve the sampled koids to Linux pid/tid against the live
950            // shared map (one read lock per record; collection is off the hot
951            // path and a live read sees every thread that recorded itself
952            // before it was sampled). If the sample cannot be resolved (e.g.
953            // native Fuchsia thread or without a session), drop the sample.
954            let Some(LinuxIdentity::Thread { pid, tid }) = koid_session.and_then(|s| {
955                s.resolve_koids(
956                    zx::Koid::from_raw(backtrace.process.0),
957                    zx::Koid::from_raw(backtrace.thread.0),
958                )
959            }) else {
960                return;
961            };
962            let time = zx::BootInstant::from_nanos(backtrace.timestamp.max(0));
963            let perf_record_sample = PerfRecordSample {
964                pid: Some(pid as u32),
965                tid: Some(tid as u32),
966                time,
967                ips,
968                regs: &[],
969                stack: &[],
970                regs_abi: perf_sample_regs_abi_PERF_SAMPLE_REGS_ABI_NONE as u64,
971                sample_regs_user,
972                sample_stack_user,
973            };
974            let metadata = seq_lock_wrapper.get();
975            let bytes_written = write_record_to_vmo(
976                perf_record_sample,
977                perf_data_vmo,
978                sample_type,
979                sample_id,
980                sample_period,
981                read_format,
982                *vmo_write_offset,
983                &metadata,
984                lost_events,
985            );
986            // Publish data_head after writing; set_value's
987            // release-ordered stores make the record contents
988            // visible to a reader that observes the new head.
989            if bytes_written > 0 {
990                *vmo_write_offset += bytes_written;
991                let mut metadata = seq_lock_wrapper.get();
992                metadata.data_head = *vmo_write_offset;
993                seq_lock_wrapper.set_value(metadata);
994            }
995        }
996        TraceRecord::LargeBlob(large_blob) => {
997            // The DWARF strategy delivers each sample as a
998            // "stack_sample" blob: [u64 regs_size][regs bytes]
999            // followed by memory chunks of [u64 base][u64 size]
1000            // [bytes] (see the profiler's StackSampler).
1001            if large_blob.name != "stack_sample" {
1002                return;
1003            }
1004            let Some(blob_metadata) = large_blob.metadata else {
1005                return;
1006            };
1007            let bytes = &large_blob.bytes;
1008            if bytes.len() < 8 {
1009                return;
1010            }
1011            let regs_size = u64::from_ne_bytes(bytes[0..8].try_into().unwrap()) as usize;
1012            let mut offset = 8;
1013            if regs_size == 0 || bytes.len() < offset + regs_size {
1014                return;
1015            }
1016
1017            // 33 u64 general registers (r0-r29, lr, sp, pc), with
1018            // cpsr following them in the zircon thread state.
1019            // Register state of other widths (e.g. an x86_64
1020            // thread state) is not supported and skipped by this
1021            // size check.
1022            if regs_size < REGS_BYTES + 8 {
1023                return;
1024            }
1025            // The size check above guarantees the 8 cpsr bytes are present.
1026            let thread_cpsr = u64::from_ne_bytes(
1027                bytes[offset + REGS_BYTES..offset + REGS_BYTES + 8].try_into().unwrap(),
1028            );
1029            let thread_regs = &bytes[offset..offset + REGS_BYTES];
1030            offset += regs_size;
1031
1032            // The memory captures that follow the registers: the
1033            // thread-state stack, then, for a thread sampled while
1034            // running Starnix code, the restricted-state struct and
1035            // (if it could be read) the stack at the guest SP.
1036            let mut chunks = Vec::<(u64, &[u8])>::new();
1037            while offset + 16 <= bytes.len() {
1038                let chunk_base = u64::from_ne_bytes(bytes[offset..offset + 8].try_into().unwrap());
1039                offset += 8;
1040                let chunk_size = u64::from_ne_bytes(bytes[offset..offset + 8].try_into().unwrap());
1041                offset += 8;
1042                if offset + chunk_size as usize > bytes.len() {
1043                    break;
1044                }
1045                chunks.push((chunk_base, &bytes[offset..offset + chunk_size as usize]));
1046                offset += chunk_size as usize;
1047            }
1048
1049            // If the thread was sampled in Starnix (e.g. during a syscall),
1050            // report the guest registers saved in its restricted state rather
1051            // than Starnix's own registers. If no valid restricted-state
1052            // chunk is present, fall back to the thread registers.
1053            let pc_offset = AARCH64_REG_PC * 8;
1054            let thread_pc =
1055                u64::from_ne_bytes(thread_regs[pc_offset..pc_offset + 8].try_into().unwrap());
1056            let in_starnix = thread_pc >= GUEST_ASPACE_END;
1057            let guest_state =
1058                if in_starnix { guest_regs_from_restricted_state(&chunks) } else { None };
1059            let reports_guest_state = guest_state.is_some();
1060            let (regs, cpsr) = guest_state.unwrap_or((thread_regs, thread_cpsr));
1061            let is_32bit = cpsr_is_aarch32(cpsr);
1062
1063            // Borrow the register bytes directly; only AArch32 needs a
1064            // mutable copy to populate the sp/lr/r15 slots below.
1065            let mut blob_regs = Cow::Borrowed(regs);
1066            if is_32bit {
1067                let blob_regs = blob_regs.to_mut();
1068                // Zircon reports the AArch32 PC in the pc slot
1069                // (index 32). 64-bit readers take it from there,
1070                // per the Linux compat layout (AArch32 R0-R14
1071                // arrive in x0-x14); mirror it into the arm32 R15
1072                // slot (index 15) too for 32-bit readers, which
1073                // only consume indices 0-15 -- x15 carries no
1074                // meaningful value for AArch32 state.
1075                let r15_offset = AARCH32_REG_PC * 8;
1076                blob_regs.copy_within(pc_offset..pc_offset + 8, r15_offset);
1077                // Likewise, AArch32 execution keeps its stack pointer
1078                // and link register in R13 and R14; the sp and lr
1079                // slots hold SP_EL0 and X30, which it never updates.
1080                // Report R13/R14 in the sp/lr slots, as Linux does for
1081                // compat tasks (compat_sp / compat_lr).
1082                let r13_offset = AARCH32_REG_SP * 8;
1083                let sp_offset = AARCH64_REG_SP * 8;
1084                blob_regs.copy_within(r13_offset..r13_offset + 8, sp_offset);
1085                let r14_offset = AARCH32_REG_LR * 8;
1086                let lr_offset = AARCH64_REG_LR * 8;
1087                blob_regs.copy_within(r14_offset..r14_offset + 8, lr_offset);
1088            }
1089
1090            let read_reg = |index: usize| {
1091                u64::from_ne_bytes(blob_regs[index * 8..index * 8 + 8].try_into().unwrap())
1092            };
1093            let sp = if is_32bit {
1094                // The arm32 stack pointer is R13.
1095                read_reg(AARCH32_REG_SP)
1096            } else {
1097                read_reg(AARCH64_REG_SP)
1098            };
1099            let pc = read_reg(AARCH64_REG_PC);
1100
1101            // Select the memory chunk that contains the sampled
1102            // stack pointer and trim it to start exactly there:
1103            // readers overlay the STACK_USER bytes at the SP
1104            // reported in REGS_USER. The blob can carry several
1105            // captures (the thread-state stack, the
1106            // restricted-state struct, and a restricted-SP
1107            // stack); selecting by SP keeps the registers and the
1108            // stack bytes coherent. If no chunk covers the SP, emit
1109            // the sample with an empty stack.
1110            let stack: &[u8] = chunks
1111                .iter()
1112                .find_map(|&(chunk_base, data)| {
1113                    (chunk_base <= sp && sp - chunk_base < data.len() as u64)
1114                        .then(|| &data[(sp - chunk_base) as usize..])
1115                })
1116                .unwrap_or(&[]);
1117
1118            let Some(LinuxIdentity::Thread { pid, tid }) = koid_session.and_then(|s| {
1119                s.resolve_koids(
1120                    zx::Koid::from_raw(blob_metadata.process.0),
1121                    zx::Koid::from_raw(blob_metadata.thread.0),
1122                )
1123            }) else {
1124                return;
1125            };
1126            let time = zx::BootInstant::from_nanos(blob_metadata.timestamp.max(0));
1127            let regs_abi = if is_32bit {
1128                perf_sample_regs_abi_PERF_SAMPLE_REGS_ABI_32 as u64
1129            } else {
1130                perf_sample_regs_abi_PERF_SAMPLE_REGS_ABI_64 as u64
1131            };
1132            let perf_record_sample = PerfRecordSample {
1133                pid: Some(pid as u32),
1134                tid: Some(tid as u32),
1135                time,
1136                // The Starnix PC (if any) goes to the kernel section
1137                // of the callchain, the user PC to the user section.
1138                ips: if reports_guest_state { vec![thread_pc, pc] } else { vec![pc] },
1139                regs: &blob_regs,
1140                stack,
1141                regs_abi,
1142                sample_regs_user,
1143                sample_stack_user,
1144            };
1145            let metadata = seq_lock_wrapper.get();
1146            let bytes_written = write_record_to_vmo(
1147                perf_record_sample,
1148                perf_data_vmo,
1149                sample_type,
1150                sample_id,
1151                sample_period,
1152                read_format,
1153                *vmo_write_offset,
1154                &metadata,
1155                lost_events,
1156            );
1157            // Publish data_head after writing; set_value's
1158            // release-ordered stores make the record contents
1159            // visible to a reader that observes the new head.
1160            if bytes_written > 0 {
1161                *vmo_write_offset += bytes_written;
1162                let mut metadata = seq_lock_wrapper.get();
1163                metadata.data_head = *vmo_write_offset;
1164                seq_lock_wrapper.set_value(metadata);
1165            }
1166        }
1167        _ => {}
1168    }
1169}
1170
1171/// Returns the sampling period requested by `attr`, or `None` if this is not a
1172/// sampling event. `attr` carries either a frequency (Hz) or a period in the
1173/// same union, selected by the `freq` bit; the timebase counts nanoseconds.
1174fn sampling_period(attr: &perf_event_attr) -> Option<zx::MonotonicDuration> {
1175    // SAFETY: Reading a union field requires its bytes to be initialized and to
1176    // be a valid value for the field's type. `attr` is filled in by `read_object`
1177    // (or safe/Default construction), and this union's only fields, `sample_period`
1178    // and `sample_freq`, are both 8-byte `u64`s, so all 8 bytes are initialized
1179    // regardless of which field was written. Any 8 bytes are a valid `u64`, so
1180    // this read is sound.
1181    let value = unsafe { attr.__bindgen_anon_1.sample_period };
1182    if value == 0 {
1183        return None;
1184    }
1185    let nanos = if attr.freq() != 0 { (1_000_000_000 / value).max(1) } else { value };
1186    Some(zx::MonotonicDuration::from_nanos(nanos as i64))
1187}
1188
1189// Notifies other thread that we should start/stop sampling.
1190// Once sampling is complete, that profiler session is no longer needed.
1191// At that point, send back notification so that this is no longer blocking
1192// (e.g. so that other profiler sessions can start).
1193fn ping_receiver(
1194    mut ioctl_sender: future_mpsc::Sender<(IoctlOp, sync_mpsc::Sender<()>)>,
1195    command: IoctlOp,
1196) {
1197    log_info!("[perf_event_open] Received sampling command: {:?}", command);
1198    let (profiling_complete_sender, profiling_complete_receiver) = sync_mpsc::channel::<()>();
1199    match ioctl_sender.try_send((command, profiling_complete_sender)) {
1200        Ok(_) => (),
1201        Err(e) => {
1202            if e.is_full() {
1203                log_warn!("[perf_event_open] Failed to send {:?}: Channel full", command);
1204            } else if e.is_disconnected() {
1205                log_warn!("[perf_event_open] Failed to send {:?}: Receiver disconnected", command);
1206            } else {
1207                log_warn!("[perf_event_open] Failed to send {:?} due to {:?}", command, e.source());
1208            }
1209        }
1210    };
1211    // Block on / wait until profiling is complete before returning.
1212    // This notifies that the profiler is free to be used for another session.
1213    let _ = profiling_complete_receiver.recv().unwrap();
1214}
1215
1216// Creates a seq lock for the given VMO. Initializes the seq lock with
1217// known initial values (unknown values default to 0).
1218// Does NOT actually save this as a memory object until mmap() is called.
1219//
1220// # Safety
1221//
1222// The caller must ensure that the kernel maintains exclusive write access to this VMO and
1223// there are only atomic accesses to this memory (see seq_lock lib.rs for details).
1224unsafe fn create_seq_lock(
1225    vmo_handle_ref: &zx::NullableHandle,
1226    buffer_size: u64,
1227) -> SeqLock<PerfMetadataHeader, PerfMetadataValue> {
1228    // Currently we hardcode everything just to get something E2E working.
1229    let metadata_header = PerfMetadataHeader { version: 1, compat_version: 2 };
1230    let page_size = *PAGE_SIZE;
1231    let metadata_value = PerfMetadataValue {
1232        lock: 0,
1233        index: 3,
1234        offset: 19337,
1235        time_enabled: 0,
1236        time_running: 0,
1237        __bindgen_anon_1: perf_event_mmap_page__bindgen_ty_1 { capabilities: 30 },
1238        pmc_width: 0,
1239        time_shift: 0,
1240        time_mult: 0,
1241        time_offset: 0,
1242        time_zero: 0,
1243        size: 0,
1244        __reserved_1: 0,
1245        time_cycles: 0,
1246        time_mask: 0,
1247        __reserved: [0; 928usize],
1248        // This first page (metadata) has finished writing. Start data_head at 0.
1249        data_head: 0,
1250        // Start reading from 0; it is the user's responsibility to increment on their end.
1251        data_tail: 0,
1252        // We know the data will start after 1 page size so we can set this now.
1253        data_offset: page_size,
1254        data_size: buffer_size - page_size,
1255        aux_head: 0,
1256        aux_tail: 0,
1257        aux_offset: 0,
1258        aux_size: 0,
1259    };
1260    let vmo = zx::Vmo::from(vmo_handle_ref.duplicate_handle(zx::Rights::SAME_RIGHTS).unwrap());
1261
1262    // Create a SeqLock and safely initialize the `header` and `value` for it.
1263    // SeqLock is formatted thusly:
1264    //   header_struct : any size, params `version` and `compat_version` should not change
1265    //   sequence_counter : u32, this is the lock and should increment
1266    //   value_struct : any size, each param can change
1267    //
1268    // SAFETY: See safety requirements on `create_seq_lock`.
1269    unsafe {
1270        SeqLock::new_from_vmo(metadata_header, metadata_value, vmo)
1271            .expect("failed to create seq_lock for perf metadata")
1272    }
1273}
1274
1275pub fn sys_perf_event_open(
1276    current_task: &CurrentTask,
1277    attr: UserRef<perf_event_attr>,
1278    // Note that this is pid in Linux docs.
1279    tid: tid_t,
1280    cpu: i32,
1281    group_fd: FdNumber,
1282    _flags: u64,
1283) -> Result<SyscallResult, Errno> {
1284    // So far, the implementation only sets the read_data_format according to the "Reading results"
1285    // section of https://man7.org/linux/man-pages/man2/perf_event_open.2.html for a single event.
1286    // Other features will be added in the future (see below track_stubs).
1287    let perf_event_attrs: perf_event_attr = current_task.read_object(attr)?;
1288
1289    if (perf_event_attrs.sample_type & perf_event_sample_format_PERF_SAMPLE_STACK_USER as u64) != 0
1290    {
1291        if perf_event_attrs.sample_stack_user % 8 != 0 {
1292            return error!(EINVAL);
1293        }
1294    }
1295
1296    if tid == -1 && cpu == -1 {
1297        return error!(EINVAL);
1298    }
1299
1300    let target = match tid {
1301        -1 => PerfEventTarget::AllTasks,
1302        0 => PerfEventTarget::CurrentTask,
1303        _ => {
1304            track_stub!(TODO("https://fxbug.dev/409621963"), "[perf_event_open] implement tid > 0");
1305            return error!(ENOSYS);
1306        }
1307    };
1308    let event_type: PerfEventType = perf_event_attrs.type_.try_into()?;
1309
1310    // Check access to open any perf_event first, before any capability check, so that it is
1311    // evaluated and audited first.
1312    security::check_perf_event_open_access(current_task, PerfEventOpenType::Open)?;
1313
1314    // Only require CAP_PERFMON (or CAP_SYS_ADMIN) when `perf_event_paranoid` exceeds the
1315    // threshold for the requested event.
1316    // TODO(https://fxbug.dev/398914921): Starnix currently starts a job-wide profiler session
1317    // regardless of `target`, so apply the CPU-wide threshold (`paranoid > 0`) to all targets
1318    // until sampling is scoped to the target task.
1319    let paranoid = current_task.kernel().perf_event_paranoid.load(Ordering::Relaxed);
1320    let requires_capability = (paranoid > 1 && perf_event_attrs.exclude_kernel() == 0)
1321        || paranoid > 0
1322        || (paranoid > -1 && event_type == PerfEventType::Tracepoint);
1323    // Exceptionally, a tracepoint perf event on the current task that excludes the kernel never
1324    // requires a capability. The kernel and CPU access checks below do not apply to such an event.
1325    let is_exempt = matches!(
1326        (event_type, perf_event_attrs.exclude_kernel(), &target),
1327        (PerfEventType::Tracepoint, 1, PerfEventTarget::CurrentTask)
1328    );
1329    // CAP_SYS_ADMIN is checked without auditing, so that nothing is audit-logged if the caller has
1330    // either capability, and only CAP_PERFMON is audit-logged if it has neither.
1331    if requires_capability
1332        && !is_exempt
1333        && !security::is_task_capable_noaudit(current_task, CAP_SYS_ADMIN)
1334    {
1335        security::check_task_capable(current_task, CAP_PERFMON).map_err(|_| errno!(EACCES))?;
1336    }
1337
1338    // Check access to monitor the kernel when `exclude_kernel` is 0.
1339    if perf_event_attrs.exclude_kernel() == 0 {
1340        security::check_perf_event_open_access(current_task, PerfEventOpenType::Kernel)?;
1341    }
1342
1343    // Check access to monitor CPU-wide activity when
1344    // - type is PERF_TYPE_SOFTWARE or
1345    // - type is PERF_TYPE_HARDWARE or
1346    // - type is in [CACHE, TRACEPOINT, BREAKPOINT, RAW) and pid == -1
1347    // - or when a low `perf_event_paranoid` waived the capability requirement
1348    //   (TODO(https://fxbug.dev/398914921): sampling is currently job-wide regardless of
1349    //   target task or event type).
1350    let check_cpu = !requires_capability
1351        || match event_type {
1352            PerfEventType::Software | PerfEventType::Hardware => true,
1353            PerfEventType::HwCache
1354            | PerfEventType::Tracepoint
1355            | PerfEventType::Breakpoint
1356            | PerfEventType::Raw => matches!(target, PerfEventTarget::AllTasks),
1357        };
1358    if check_cpu {
1359        security::check_perf_event_open_access(current_task, PerfEventOpenType::Cpu)?;
1360    }
1361
1362    // Check access to monitor tracepoints when the type is PERF_TYPE_TRACEPOINT.
1363    if event_type == PerfEventType::Tracepoint {
1364        security::check_perf_event_open_access(current_task, PerfEventOpenType::Tracepoint)?;
1365    }
1366
1367    // Channel used to send info between notifier and spawned task thread.
1368    // We somewhat arbitrarily picked 8 for now in case we get a bunch of ioctls that are in
1369    // quick succession (instead of something lower).
1370    let (sender, mut receiver) = future_mpsc::channel(8);
1371
1372    let mut perf_event_file = PerfEventFileState::new(
1373        perf_event_attrs,
1374        0,
1375        perf_event_attrs.disabled(),
1376        perf_event_attrs.sample_type,
1377        zx::Vmo::create(ESTIMATED_MMAP_BUFFER_SIZE).unwrap(),
1378        sender,
1379    );
1380
1381    let read_format = perf_event_attrs.read_format;
1382
1383    if (read_format & perf_event_read_format_PERF_FORMAT_TOTAL_TIME_ENABLED as u64) != 0
1384        || (read_format & perf_event_read_format_PERF_FORMAT_TOTAL_TIME_RUNNING as u64) != 0
1385    {
1386        // Only keep track of most_recent_enabled_time if we are currently in ENABLED state,
1387        // as otherwise this param shouldn't be used for calculating anything.
1388        if perf_event_file.disabled == 0 {
1389            perf_event_file.most_recent_enabled_time =
1390                zx::MonotonicInstant::get().into_nanos() as u64;
1391        }
1392        // Initialize this to 0 as we will need to return a time duration later during read().
1393        perf_event_file.total_time_running = 0;
1394    }
1395
1396    let event_id = READ_FORMAT_ID_GENERATOR.fetch_add(1, Ordering::Relaxed);
1397    perf_event_file.rf_id = event_id;
1398
1399    if group_fd.raw() == -1 {
1400        perf_event_file.sample_id = event_id;
1401    } else {
1402        let group_file = current_task.files().get(group_fd)?;
1403        let group_file_object_id = group_file.id;
1404        let perf_state = get_perf_state(&current_task.kernel);
1405        let events = perf_state.format_id_lookup_table.lock();
1406        if let Some(rf_id) = events.get(&group_file_object_id) {
1407            perf_event_file.sample_id = *rf_id;
1408        } else {
1409            return error!(EINVAL);
1410        }
1411    }
1412
1413    if (read_format & perf_event_read_format_PERF_FORMAT_GROUP as u64) != 0 {
1414        track_stub!(
1415            TODO("https://fxbug.dev/402238049"),
1416            "[perf_event_open] implement read_format group"
1417        );
1418        return error!(ENOSYS);
1419    }
1420    if (read_format & perf_event_read_format_PERF_FORMAT_LOST as u64) != 0 {
1421        track_stub!(
1422            TODO("https://fxbug.dev/402260383"),
1423            "[perf_event_open] implement read_format lost"
1424        );
1425    }
1426
1427    // Set up notifier for handling ioctl calls to enable/disable sampling.
1428    let mut vmo_handle_copy =
1429        perf_event_file.perf_data_vmo.as_handle_ref().duplicate_handle(zx::Rights::SAME_RIGHTS);
1430
1431    let sample_period = sampling_period(&perf_event_file.attr);
1432
1433    // SeqLock does not get instantiated with metadata values until mmap() is called.
1434    let seq_lock =
1435        Arc::new(OnceLock::<Result<SeqLock<PerfMetadataHeader, PerfMetadataValue>, Errno>>::new());
1436    let cloned_seq_lock = Arc::clone(&seq_lock);
1437    let mut vmo_write_offset = 0;
1438
1439    let closure = async move |kthread_task: &CurrentTask| {
1440        let mut lost_events: u64 = 0;
1441
1442        // Each iteration waits for an Enable and then runs one session.
1443        while let Some((command, profiling_complete_receiver)) = receiver.next().await {
1444            // We only expect Enable when no session is active.
1445            if command != IoctlOp::Enable {
1446                let _ = profiling_complete_receiver.send(());
1447                continue;
1448            }
1449
1450            // Enable is only sent for sampling events, but guard anyway.
1451            let Some(sample_period) = sample_period else {
1452                let _ = profiling_complete_receiver.send(());
1453                continue;
1454            };
1455
1456            let callgraph = callgraph_config(
1457                perf_event_file.sample_type,
1458                perf_event_file.attr.sample_stack_user,
1459            );
1460            let (session_proxy, client) = match set_up_profiler(sample_period, callgraph).await {
1461                Ok(session) => session,
1462                Err(e) => {
1463                    log_warn!("Failed to profile: {}", e);
1464                    let _ = profiling_complete_receiver.send(());
1465                    continue;
1466                }
1467            };
1468
1469            // Record pid/koid mappings before the profiler starts sampling
1470            // so every sampled thread can be resolved. Dropping the session
1471            // at the end of the profiling session ends the recording interest.
1472            let pid_koid_session = kthread_task.kernel().trace_event_manager.open();
1473            let start_request =
1474                profiler::SessionStartRequest { buffer_results: Some(false), ..Default::default() };
1475            if let Err(e) = session_proxy.start(&start_request).await {
1476                log_warn!("Failed to start profiler: {:?}", e);
1477                let _ = profiling_complete_receiver.send(());
1478                continue;
1479            }
1480            let _ = profiling_complete_receiver.send(());
1481
1482            let vmo = zx::Vmo::from(
1483                vmo_handle_copy
1484                    .as_mut()
1485                    .expect("Failed to get VMO handle")
1486                    .as_handle_ref()
1487                    .duplicate_handle(zx::Rights::SAME_RIGHTS)
1488                    .unwrap(),
1489            );
1490            let mut handle_record = |record: TraceRecord| {
1491                // Records can only be written once mmap() has set up the
1492                // ring buffer.
1493                if let Some(Ok(seq_lock_wrapper)) = cloned_seq_lock.get() {
1494                    process_fxt_record(
1495                        record,
1496                        seq_lock_wrapper,
1497                        &vmo,
1498                        perf_event_file.sample_type,
1499                        perf_event_file.sample_id,
1500                        sample_period.into_nanos() as u64,
1501                        perf_event_file.attr.read_format,
1502                        perf_event_file.attr.sample_regs_user,
1503                        perf_event_file.attr.sample_stack_user as u64,
1504                        Some(&pid_koid_session),
1505                        &mut vmo_write_offset,
1506                        &mut lost_events,
1507                    );
1508                }
1509            };
1510
1511            // Pump records from the profiler into the ring buffer until a
1512            // Disable arrives: readers poll the ring during the session, and
1513            // the profiler's socket must be drained continuously.
1514            let (stream, _parser_task) = SessionParser::new_async(client);
1515            let mut stream = stream.fuse();
1516            let mut stream_ended = false;
1517            let disable_ack = loop {
1518                if stream_ended {
1519                    // The profiler closed the socket early; only commands
1520                    // remain.
1521                    match receiver.next().await {
1522                        Some((IoctlOp::Disable, ack)) => break Some(ack),
1523                        Some((IoctlOp::Enable, ack)) => {
1524                            // A session is already active.
1525                            let _ = ack.send(());
1526                        }
1527                        None => break None,
1528                    }
1529                } else {
1530                    futures::select_biased! {
1531                        cmd = receiver.next() => match cmd {
1532                            Some((IoctlOp::Disable, ack)) => break Some(ack),
1533                            Some((IoctlOp::Enable, ack)) => {
1534                                // A session is already active.
1535                                let _ = ack.send(());
1536                            }
1537                            None => break None,
1538                        },
1539                        record = stream.next() => match record {
1540                            Some(Ok(record)) => handle_record(record),
1541                            Some(Err(e)) => {
1542                                // The stream is desynchronized and the parser
1543                                // would keep returning this error, so end the
1544                                // session. The teardown below drops the
1545                                // socket, which unblocks the profiler if it is
1546                                // stalled writing to it.
1547                                log_warn!("[perf_event_open] Error parsing FXT: {:?}", e);
1548                                stream_ended = true;
1549                                break None;
1550                            }
1551                            None => {
1552                                log_warn!("[perf_event_open] Profiler stream ended mid-session");
1553                                stream_ended = true;
1554                            }
1555                        },
1556                    }
1557                }
1558            };
1559
1560            // Tear the session down while still draining: the profiler is a
1561            // separate process whose socket writes block, so if its socket is
1562            // full it is stalled mid-write and cannot service stop() or
1563            // reset() until we keep reading. Awaiting either without pumping
1564            // would deadlock the two.
1565            let stop_and_reset = async {
1566                match session_proxy.stop().await {
1567                    Ok(stats) => log_info!(
1568                        "[perf_event_open] profiler samples_collected: {:?}",
1569                        stats.samples_collected.unwrap_or(0)
1570                    ),
1571                    Err(e) => log_warn!("[perf_event_open] Failed to stop profiler: {:?}", e),
1572                }
1573                // Reset flushes the remaining data and closes the socket,
1574                // which is what ends the drain below.
1575                let _ = session_proxy.reset().await;
1576            };
1577            let drain = async move {
1578                if !stream_ended {
1579                    while let Some(record) = stream.next().await {
1580                        match record {
1581                            Ok(record) => handle_record(record),
1582                            Err(e) => {
1583                                // The stream is desynchronized, so further
1584                                // records cannot be parsed.
1585                                log_warn!("[perf_event_open] Error parsing FXT: {:?}", e);
1586                                break;
1587                            }
1588                        }
1589                    }
1590                }
1591                // Hand the profiler a closed socket so that a write it is
1592                // blocked on fails instead of hanging forever.
1593                drop(stream);
1594                drop(_parser_task);
1595            };
1596            futures::join!(stop_and_reset, drain);
1597
1598            // The Disable ioctl returns only after the drain above, so the
1599            // reader sees every record once the ioctl completes.
1600            if let Some(ack) = disable_ack {
1601                let _ = ack.send(());
1602            }
1603        }
1604    };
1605    let req = SpawnRequestBuilder::new()
1606        .with_debug_name("perf-event-sampler")
1607        .with_async_closure(closure)
1608        .build();
1609    current_task.kernel().kthreads.spawner().spawn_from_request(req);
1610
1611    let file = Box::new(PerfEventFile {
1612        _tid: tid,
1613        _cpu: cpu,
1614        perf_event_file: perf_event_file.into(),
1615        security_state: security::perf_event_alloc(current_task),
1616        seq_lock: seq_lock,
1617    });
1618    // TODO: https://fxbug.dev/404739824 - Confirm whether to handle this as a "private" node.
1619    let file_handle = Anon::new_private_file(current_task, file, OpenFlags::RDWR, "[perf_event]");
1620    let file_object_id = file_handle.id;
1621    let file_descriptor: Result<FdNumber, Errno> =
1622        current_task.add_file(file_handle, FdFlags::empty());
1623
1624    match file_descriptor {
1625        Ok(fd) => {
1626            if group_fd.raw() == -1 {
1627                let perf_state = get_perf_state(&current_task.kernel);
1628                let mut events = perf_state.format_id_lookup_table.lock();
1629                events.insert(file_object_id, event_id);
1630            }
1631            Ok(fd.into())
1632        }
1633        Err(_) => {
1634            track_stub!(
1635                TODO("https://fxbug.dev/402453955"),
1636                "[perf_event_open] implement remaining error handling"
1637            );
1638            error!(EMFILE)
1639        }
1640    }
1641}
1642// Syscalls for arch32 usage
1643#[cfg(target_arch = "aarch64")]
1644mod arch32 {
1645    pub use super::sys_perf_event_open as sys_arch32_perf_event_open;
1646}
1647
1648#[cfg(target_arch = "aarch64")]
1649pub use arch32::*;
1650
1651use crate::mm::memory::MemoryObject;
1652use crate::mm::{MemoryAccessorExt, ProtectionFlags};
1653use crate::task::CurrentTask;
1654use crate::vfs::{
1655    Anon, FdFlags, FdNumber, FileObject, FileObjectId, FileObjectState, FileOps, InputBuffer,
1656    OutputBuffer,
1657};
1658use crate::{fileops_impl_nonseekable, fileops_impl_noop_sync};
1659
1660#[cfg(test)]
1661mod tests {
1662    use super::*;
1663    use crate::task::tracing::{TracePerformanceEventManager, ZirconIdentity};
1664
1665    #[::fuchsia::test]
1666    async fn test_process_fxt_record_resolves_pid_tid() {
1667        let manager = Arc::new(TracePerformanceEventManager::new(std::sync::Weak::new()));
1668        let session = manager.open();
1669        manager.record(
1670            42,
1671            43,
1672            ZirconIdentity { process: zx::Koid::from_raw(1001), thread: zx::Koid::from_raw(1002) },
1673        );
1674
1675        let perf_data_vmo = zx::Vmo::create(ESTIMATED_MMAP_BUFFER_SIZE).unwrap();
1676        let vmo_handle_copy =
1677            perf_data_vmo.as_handle_ref().duplicate_handle(zx::Rights::SAME_RIGHTS).unwrap();
1678        // SAFETY: The test maintains exclusive write access to this VMO.
1679        let seq_lock = unsafe { create_seq_lock(&vmo_handle_copy, ESTIMATED_MMAP_BUFFER_SIZE) };
1680
1681        let sample_type = (perf_event_sample_format_PERF_SAMPLE_IP
1682            | perf_event_sample_format_PERF_SAMPLE_TID) as u64;
1683        let mut vmo_write_offset = 0;
1684        let mut lost_events = 0;
1685
1686        // Mapped sample: process 1001, thread 1002 -> should resolve to pid 42, tid 43.
1687        let mapped_record =
1688            TraceRecord::Profiler(ProfilerRecord::Backtrace(fxt::profiler::BacktraceRecord {
1689                timestamp: 1000,
1690                process: fxt::ProcessKoid(1001),
1691                thread: fxt::ThreadKoid(1002),
1692                num_records: 1,
1693                data: vec![0x12345678],
1694            }));
1695        process_fxt_record(
1696            mapped_record,
1697            &seq_lock,
1698            &perf_data_vmo,
1699            sample_type,
1700            0,
1701            0,
1702            0,
1703            0,
1704            0,
1705            Some(&session),
1706            &mut vmo_write_offset,
1707            &mut lost_events,
1708        );
1709
1710        // Header (8 bytes) + IP (8 bytes) + PID/TID (8 bytes) = 24 bytes.
1711        let expected_record_size: u64 = 24;
1712        assert_eq!(vmo_write_offset, expected_record_size);
1713
1714        let metadata = seq_lock.get();
1715        assert_eq!(metadata.data_head, expected_record_size);
1716
1717        let mut record_bytes = [0u8; 24];
1718        perf_data_vmo.read(&mut record_bytes, metadata.data_offset).unwrap();
1719
1720        let record_type = u32::from_ne_bytes(record_bytes[0..4].try_into().unwrap());
1721        assert_eq!(record_type, perf_event_type_PERF_RECORD_SAMPLE);
1722
1723        let misc = u16::from_ne_bytes(record_bytes[4..6].try_into().unwrap());
1724        assert_eq!(misc, PERF_RECORD_MISC_USER as u16);
1725
1726        let size = u16::from_ne_bytes(record_bytes[6..8].try_into().unwrap());
1727        assert_eq!(size, expected_record_size as u16);
1728
1729        let ip = u64::from_ne_bytes(record_bytes[8..16].try_into().unwrap());
1730        assert_eq!(ip, 0x12345678);
1731
1732        let pid = u32::from_ne_bytes(record_bytes[16..20].try_into().unwrap());
1733        assert_eq!(pid, 42);
1734
1735        let tid = u32::from_ne_bytes(record_bytes[20..24].try_into().unwrap());
1736        assert_eq!(tid, 43);
1737
1738        // Unmapped sample: process 9999, thread 9998 -> should be dropped.
1739        let unmapped_record =
1740            TraceRecord::Profiler(ProfilerRecord::Backtrace(fxt::profiler::BacktraceRecord {
1741                timestamp: 2000,
1742                process: fxt::ProcessKoid(9999),
1743                thread: fxt::ThreadKoid(9998),
1744                num_records: 1,
1745                data: vec![0x87654321],
1746            }));
1747        process_fxt_record(
1748            unmapped_record,
1749            &seq_lock,
1750            &perf_data_vmo,
1751            sample_type,
1752            0,
1753            0,
1754            0,
1755            0,
1756            0,
1757            Some(&session),
1758            &mut vmo_write_offset,
1759            &mut lost_events,
1760        );
1761
1762        // Verify that unmapped sample was dropped and no extra bytes were written.
1763        assert_eq!(vmo_write_offset, expected_record_size);
1764        let metadata = seq_lock.get();
1765        assert_eq!(metadata.data_head, expected_record_size);
1766        let mut trailing_bytes = [0u8; 24];
1767        perf_data_vmo
1768            .read(&mut trailing_bytes, metadata.data_offset + expected_record_size)
1769            .unwrap();
1770        assert_eq!(trailing_bytes, [0u8; 24]);
1771    }
1772
1773    #[::fuchsia::test]
1774    async fn test_write_record_to_vmo_regs_and_stack() {
1775        let perf_data_vmo = zx::Vmo::create(ESTIMATED_MMAP_BUFFER_SIZE).unwrap();
1776        let vmo_handle_copy =
1777            perf_data_vmo.as_handle_ref().duplicate_handle(zx::Rights::SAME_RIGHTS).unwrap();
1778        // SAFETY: The test maintains exclusive write access to this VMO.
1779        let seq_lock = unsafe { create_seq_lock(&vmo_handle_copy, ESTIMATED_MMAP_BUFFER_SIZE) };
1780        let metadata = seq_lock.get();
1781        let mut lost_events = 0;
1782        let regs_data = [0x11u8; 16];
1783        let stack_data = [0x22u8; 16];
1784        let sample = PerfRecordSample {
1785            pid: Some(10),
1786            tid: Some(20),
1787            time: zx::BootInstant::from_nanos(123_456_789),
1788            ips: vec![0xdeadbeef],
1789            regs: &regs_data,
1790            stack: &stack_data,
1791            regs_abi: perf_sample_regs_abi_PERF_SAMPLE_REGS_ABI_32 as u64,
1792            sample_regs_user: 3,
1793            sample_stack_user: 16,
1794        };
1795        let sample_type = (perf_event_sample_format_PERF_SAMPLE_IP
1796            | perf_event_sample_format_PERF_SAMPLE_TID
1797            | perf_event_sample_format_PERF_SAMPLE_TIME
1798            | perf_event_sample_format_PERF_SAMPLE_REGS_USER
1799            | perf_event_sample_format_PERF_SAMPLE_STACK_USER) as u64;
1800        let written = write_record_to_vmo(
1801            sample,
1802            &perf_data_vmo,
1803            sample_type,
1804            0,
1805            0,
1806            0,
1807            0,
1808            &metadata,
1809            &mut lost_events,
1810        );
1811        assert!(written > 0);
1812
1813        let mut record_bytes = vec![0u8; written as usize];
1814        perf_data_vmo.read(&mut record_bytes, metadata.data_offset).unwrap();
1815
1816        let record_type = u32::from_ne_bytes(record_bytes[0..4].try_into().unwrap());
1817        assert_eq!(record_type, perf_event_type_PERF_RECORD_SAMPLE);
1818        let misc = u16::from_ne_bytes(record_bytes[4..6].try_into().unwrap());
1819        assert_eq!(misc, PERF_RECORD_MISC_USER as u16);
1820        let size = u16::from_ne_bytes(record_bytes[6..8].try_into().unwrap());
1821        assert_eq!(size as usize, written as usize);
1822
1823        let ip = u64::from_ne_bytes(record_bytes[8..16].try_into().unwrap());
1824        assert_eq!(ip, 0xdeadbeef);
1825
1826        let pid = u32::from_ne_bytes(record_bytes[16..20].try_into().unwrap());
1827        assert_eq!(pid, 10);
1828        let tid = u32::from_ne_bytes(record_bytes[20..24].try_into().unwrap());
1829        assert_eq!(tid, 20);
1830
1831        let time = u64::from_ne_bytes(record_bytes[24..32].try_into().unwrap());
1832        assert_eq!(time, 123_456_789);
1833
1834        // REGS_USER: abi (8 bytes) + regs (16 bytes)
1835        let abi = u64::from_ne_bytes(record_bytes[32..40].try_into().unwrap());
1836        assert_eq!(abi, perf_sample_regs_abi_PERF_SAMPLE_REGS_ABI_32 as u64);
1837        assert_eq!(&record_bytes[40..56], &regs_data);
1838
1839        // STACK_USER: dest_len (8 bytes) + data (16 bytes) + dyn_size (8 bytes)
1840        let stack_len = u64::from_ne_bytes(record_bytes[56..64].try_into().unwrap());
1841        assert_eq!(stack_len, 16);
1842        assert_eq!(&record_bytes[64..80], &stack_data);
1843        let dyn_size = u64::from_ne_bytes(record_bytes[80..88].try_into().unwrap());
1844        assert_eq!(dyn_size, 16);
1845    }
1846
1847    #[::fuchsia::test]
1848    async fn test_write_record_to_vmo_regs_abi_64() {
1849        let perf_data_vmo = zx::Vmo::create(ESTIMATED_MMAP_BUFFER_SIZE).unwrap();
1850        let vmo_handle_copy =
1851            perf_data_vmo.as_handle_ref().duplicate_handle(zx::Rights::SAME_RIGHTS).unwrap();
1852        // SAFETY: The test maintains exclusive write access to this VMO.
1853        let seq_lock = unsafe { create_seq_lock(&vmo_handle_copy, ESTIMATED_MMAP_BUFFER_SIZE) };
1854        let metadata = seq_lock.get();
1855        let mut lost_events = 0;
1856        let regs_data = [0x44u8; 16];
1857        let stack_data = [0x55u8; 16];
1858        let sample = PerfRecordSample {
1859            pid: Some(10),
1860            tid: Some(20),
1861            time: zx::BootInstant::from_nanos(123_456_789),
1862            ips: vec![0xdeadbeef],
1863            regs: &regs_data,
1864            stack: &stack_data,
1865            regs_abi: perf_sample_regs_abi_PERF_SAMPLE_REGS_ABI_64 as u64,
1866            sample_regs_user: 3,
1867            sample_stack_user: 16,
1868        };
1869        let sample_type = (perf_event_sample_format_PERF_SAMPLE_IP
1870            | perf_event_sample_format_PERF_SAMPLE_TID
1871            | perf_event_sample_format_PERF_SAMPLE_TIME
1872            | perf_event_sample_format_PERF_SAMPLE_REGS_USER
1873            | perf_event_sample_format_PERF_SAMPLE_STACK_USER) as u64;
1874        let written = write_record_to_vmo(
1875            sample,
1876            &perf_data_vmo,
1877            sample_type,
1878            0,
1879            0,
1880            0,
1881            0,
1882            &metadata,
1883            &mut lost_events,
1884        );
1885        assert!(written > 0);
1886
1887        let mut record_bytes = vec![0u8; written as usize];
1888        perf_data_vmo.read(&mut record_bytes, metadata.data_offset).unwrap();
1889
1890        // REGS_USER: abi (8 bytes) + regs (16 bytes)
1891        let abi = u64::from_ne_bytes(record_bytes[32..40].try_into().unwrap());
1892        assert_eq!(abi, perf_sample_regs_abi_PERF_SAMPLE_REGS_ABI_64 as u64);
1893        assert_eq!(&record_bytes[40..56], &regs_data);
1894    }
1895
1896    #[::fuchsia::test]
1897    async fn test_write_record_to_vmo_regs_abi_none_when_empty() {
1898        let perf_data_vmo = zx::Vmo::create(ESTIMATED_MMAP_BUFFER_SIZE).unwrap();
1899        let vmo_handle_copy =
1900            perf_data_vmo.as_handle_ref().duplicate_handle(zx::Rights::SAME_RIGHTS).unwrap();
1901        // SAFETY: The test maintains exclusive write access to this VMO.
1902        let seq_lock = unsafe { create_seq_lock(&vmo_handle_copy, ESTIMATED_MMAP_BUFFER_SIZE) };
1903        let metadata = seq_lock.get();
1904        let mut lost_events = 0;
1905        let stack_data = [0x33u8; 8];
1906        let sample = PerfRecordSample {
1907            pid: Some(10),
1908            tid: Some(20),
1909            time: zx::BootInstant::from_nanos(999),
1910            ips: vec![0x1000],
1911            regs: &[],
1912            stack: &stack_data,
1913            regs_abi: perf_sample_regs_abi_PERF_SAMPLE_REGS_ABI_NONE as u64,
1914            sample_regs_user: 3,
1915            sample_stack_user: 8,
1916        };
1917        let sample_type = (perf_event_sample_format_PERF_SAMPLE_IP
1918            | perf_event_sample_format_PERF_SAMPLE_TID
1919            | perf_event_sample_format_PERF_SAMPLE_TIME
1920            | perf_event_sample_format_PERF_SAMPLE_REGS_USER
1921            | perf_event_sample_format_PERF_SAMPLE_STACK_USER) as u64;
1922        let written = write_record_to_vmo(
1923            sample,
1924            &perf_data_vmo,
1925            sample_type,
1926            0,
1927            0,
1928            0,
1929            0,
1930            &metadata,
1931            &mut lost_events,
1932        );
1933        assert!(written > 0);
1934
1935        let mut record_bytes = vec![0u8; written as usize];
1936        perf_data_vmo.read(&mut record_bytes, metadata.data_offset).unwrap();
1937
1938        // REGS_USER: abi (8 bytes) should be PERF_SAMPLE_REGS_ABI_NONE (0), and no regs follow.
1939        let abi = u64::from_ne_bytes(record_bytes[32..40].try_into().unwrap());
1940        assert_eq!(abi, perf_sample_regs_abi_PERF_SAMPLE_REGS_ABI_NONE as u64);
1941
1942        // STACK_USER immediately follows the ABI tag at offset 40.
1943        let stack_len = u64::from_ne_bytes(record_bytes[40..48].try_into().unwrap());
1944        assert_eq!(stack_len, 8);
1945        assert_eq!(&record_bytes[48..56], &stack_data);
1946        let dyn_size = u64::from_ne_bytes(record_bytes[56..64].try_into().unwrap());
1947        assert_eq!(dyn_size, 8);
1948    }
1949
1950    // Stack-sample (DWARF) blobs, laid out as the profiler's StackSampler
1951    // emits them: [u64 regs_size][thread-state regs][{u64 base, u64 size,
1952    // bytes}...].
1953    const STARNIX_PC: u64 = GUEST_ASPACE_END + 0x2222_0558;
1954    const STARNIX_SP: u64 = GUEST_ASPACE_END + 0x4000_1000;
1955    const RESTRICTED_STATE_ADDR: u64 = GUEST_ASPACE_END + 0x5000_0000;
1956    const GUEST_PC: u64 = 0x7a00_1234;
1957    const GUEST_SP: u64 = 0x7f00_1000;
1958    const ALL_REGS_MASK: u64 = (1 << 33) - 1;
1959    const SAMPLE_STACK_USER: u64 = 256;
1960
1961    fn u64s_to_bytes(values: &[u64]) -> Vec<u8> {
1962        values.iter().flat_map(|value| value.to_ne_bytes()).collect()
1963    }
1964
1965    fn read_u64(bytes: &[u8], pos: &mut usize) -> u64 {
1966        let value = u64::from_ne_bytes(bytes[*pos..*pos + 8].try_into().unwrap());
1967        *pos += 8;
1968        value
1969    }
1970
1971    // An arm64 zx_thread_state_general_regs_t: r0-r29, lr, sp, pc, cpsr,
1972    // tpidr.
1973    fn thread_state_regs(pc: u64, sp: u64, cpsr: u64) -> Vec<u64> {
1974        let mut regs: Vec<u64> = (0..31).map(|i| 0xa000 + i).collect();
1975        regs.extend([sp, pc, cpsr, 0x7777]);
1976        regs
1977    }
1978
1979    fn stack_sample_blob(thread_regs: &[u64], chunks: &[(u64, &[u8])]) -> Vec<u8> {
1980        let regs_bytes = u64s_to_bytes(thread_regs);
1981        let mut blob = (regs_bytes.len() as u64).to_ne_bytes().to_vec();
1982        blob.extend(regs_bytes);
1983        for (base, data) in chunks {
1984            blob.extend(base.to_ne_bytes());
1985            blob.extend((data.len() as u64).to_ne_bytes());
1986            blob.extend_from_slice(data);
1987        }
1988        blob
1989    }
1990
1991    // A zx_restricted_state_t with x0-x30 from `x`, laid out per the
1992    // struct's field offsets.
1993    #[cfg(target_arch = "aarch64")]
1994    fn restricted_state_bytes(x: &[u64; 31], sp: u64, pc: u64, cpsr: u32) -> Vec<u8> {
1995        use zx::sys::zx_restricted_state_t;
1996        let mut state = vec![0u8; std::mem::size_of::<zx_restricted_state_t>()];
1997        state[..31 * 8].copy_from_slice(&u64s_to_bytes(x));
1998        let sp_offset = std::mem::offset_of!(zx_restricted_state_t, sp);
1999        state[sp_offset..sp_offset + 8].copy_from_slice(&sp.to_ne_bytes());
2000        let pc_offset = std::mem::offset_of!(zx_restricted_state_t, pc);
2001        state[pc_offset..pc_offset + 8].copy_from_slice(&pc.to_ne_bytes());
2002        let cpsr_offset = std::mem::offset_of!(zx_restricted_state_t, cpsr);
2003        state[cpsr_offset..cpsr_offset + 4].copy_from_slice(&cpsr.to_ne_bytes());
2004        state
2005    }
2006
2007    fn stack_bytes(seed: u8, len: usize) -> Vec<u8> {
2008        (0..len).map(|i| seed.wrapping_add(i as u8)).collect()
2009    }
2010
2011    struct ParsedStackSample {
2012        ip: u64,
2013        pid: u32,
2014        tid: u32,
2015        callchain: Vec<u64>,
2016        abi: u64,
2017        regs: Vec<u64>,
2018        stack: Vec<u8>,
2019    }
2020
2021    // Runs `blob` through process_fxt_record as a sample of process 1001 /
2022    // thread 1002 (pid 42 / tid 43) and parses the one record it writes.
2023    fn process_stack_sample(blob: Vec<u8>, sample_regs_user: u64) -> ParsedStackSample {
2024        let manager = Arc::new(TracePerformanceEventManager::new(std::sync::Weak::new()));
2025        let session = manager.open();
2026        manager.record(
2027            42,
2028            43,
2029            ZirconIdentity { process: zx::Koid::from_raw(1001), thread: zx::Koid::from_raw(1002) },
2030        );
2031        let perf_data_vmo = zx::Vmo::create(ESTIMATED_MMAP_BUFFER_SIZE).unwrap();
2032        let vmo_handle_copy =
2033            perf_data_vmo.as_handle_ref().duplicate_handle(zx::Rights::SAME_RIGHTS).unwrap();
2034        // SAFETY: The test maintains exclusive write access to this VMO.
2035        let seq_lock = unsafe { create_seq_lock(&vmo_handle_copy, ESTIMATED_MMAP_BUFFER_SIZE) };
2036        let sample_type = (perf_event_sample_format_PERF_SAMPLE_IP
2037            | perf_event_sample_format_PERF_SAMPLE_TID
2038            | perf_event_sample_format_PERF_SAMPLE_CALLCHAIN
2039            | perf_event_sample_format_PERF_SAMPLE_REGS_USER
2040            | perf_event_sample_format_PERF_SAMPLE_STACK_USER) as u64;
2041        let record = TraceRecord::LargeBlob(fxt::LargeBlobRecord {
2042            provider: None,
2043            ty: fxt::BlobType::Data,
2044            category: "cpu_profiler".into(),
2045            name: "stack_sample".into(),
2046            bytes: blob,
2047            metadata: Some(fxt::LargeBlobMetadata {
2048                timestamp: 1000,
2049                process: fxt::ProcessKoid(1001),
2050                thread: fxt::ThreadKoid(1002),
2051                args: vec![],
2052            }),
2053        });
2054        let mut vmo_write_offset = 0;
2055        let mut lost_events = 0;
2056        process_fxt_record(
2057            record,
2058            &seq_lock,
2059            &perf_data_vmo,
2060            sample_type,
2061            0,
2062            0,
2063            0,
2064            sample_regs_user,
2065            SAMPLE_STACK_USER,
2066            Some(&session),
2067            &mut vmo_write_offset,
2068            &mut lost_events,
2069        );
2070        assert!(vmo_write_offset > 0, "no record written");
2071        assert_eq!(lost_events, 0);
2072
2073        let mut record = vec![0u8; vmo_write_offset as usize];
2074        perf_data_vmo.read(&mut record, seq_lock.get().data_offset).unwrap();
2075        assert_eq!(
2076            u32::from_ne_bytes(record[0..4].try_into().unwrap()),
2077            perf_event_type_PERF_RECORD_SAMPLE
2078        );
2079        assert_eq!(u16::from_ne_bytes(record[6..8].try_into().unwrap()) as usize, record.len());
2080
2081        let mut pos = 8;
2082        let ip = read_u64(&record, &mut pos);
2083        let pid = u32::from_ne_bytes(record[pos..pos + 4].try_into().unwrap());
2084        let tid = u32::from_ne_bytes(record[pos + 4..pos + 8].try_into().unwrap());
2085        pos += 8;
2086        let nr = read_u64(&record, &mut pos);
2087        let callchain = (0..nr).map(|_| read_u64(&record, &mut pos)).collect();
2088        let abi = read_u64(&record, &mut pos);
2089        let regs = if abi == perf_sample_regs_abi_PERF_SAMPLE_REGS_ABI_NONE as u64 {
2090            vec![]
2091        } else {
2092            (0..sample_regs_user.count_ones()).map(|_| read_u64(&record, &mut pos)).collect()
2093        };
2094        let stack_size = read_u64(&record, &mut pos) as usize;
2095        assert_eq!(stack_size, SAMPLE_STACK_USER as usize);
2096        let stack_region = record[pos..pos + stack_size].to_vec();
2097        pos += stack_size;
2098        let dyn_size = read_u64(&record, &mut pos) as usize;
2099        assert_eq!(pos, record.len());
2100        ParsedStackSample {
2101            ip,
2102            pid,
2103            tid,
2104            callchain,
2105            abi,
2106            regs,
2107            stack: stack_region[..dyn_size].to_vec(),
2108        }
2109    }
2110
2111    // Checks that `sample` fell back to reporting the Starnix thread registers
2112    // and stack.
2113    fn assert_reports_thread_state(sample: &ParsedStackSample, stack: &[u8]) {
2114        assert_eq!((sample.pid, sample.tid), (42, 43));
2115        assert_eq!(sample.ip, STARNIX_PC);
2116        assert_eq!(
2117            sample.callchain,
2118            vec![
2119                perf_callchain_context_PERF_CONTEXT_KERNEL,
2120                STARNIX_PC,
2121                perf_callchain_context_PERF_CONTEXT_USER,
2122            ]
2123        );
2124        assert_eq!(sample.abi, perf_sample_regs_abi_PERF_SAMPLE_REGS_ABI_64 as u64);
2125        assert_eq!(sample.regs, thread_state_regs(STARNIX_PC, STARNIX_SP, 0)[..33].to_vec());
2126        assert_eq!(sample.stack, stack);
2127    }
2128
2129    #[cfg(target_arch = "aarch64")]
2130    #[::fuchsia::test]
2131    async fn test_stack_sample_in_starnix_reports_guest_regs_and_stack() {
2132        let starnix_stack = stack_bytes(0x10, 64);
2133        let guest_x: [u64; 31] = std::array::from_fn(|i| 0xb000 + i as u64);
2134        let restricted_state = restricted_state_bytes(&guest_x, GUEST_SP, GUEST_PC, 0);
2135        let guest_stack = stack_bytes(0x80, 48);
2136        let blob = stack_sample_blob(
2137            &thread_state_regs(STARNIX_PC, STARNIX_SP, 0),
2138            &[
2139                (STARNIX_SP, &starnix_stack),
2140                (RESTRICTED_STATE_ADDR, &restricted_state),
2141                (GUEST_SP, &guest_stack),
2142            ],
2143        );
2144
2145        let sample = process_stack_sample(blob, ALL_REGS_MASK);
2146
2147        assert_eq!((sample.pid, sample.tid), (42, 43));
2148        // The Starnix PC is only reported as kernel execution; the user
2149        // section starts at the guest PC.
2150        assert_eq!(sample.ip, STARNIX_PC);
2151        assert_eq!(
2152            sample.callchain,
2153            vec![
2154                perf_callchain_context_PERF_CONTEXT_KERNEL,
2155                STARNIX_PC,
2156                perf_callchain_context_PERF_CONTEXT_USER,
2157                GUEST_PC,
2158            ]
2159        );
2160        assert_eq!(sample.abi, perf_sample_regs_abi_PERF_SAMPLE_REGS_ABI_64 as u64);
2161        let mut expected_regs = guest_x.to_vec();
2162        expected_regs.extend([GUEST_SP, GUEST_PC]);
2163        assert_eq!(sample.regs, expected_regs);
2164        assert_eq!(sample.stack, guest_stack);
2165    }
2166
2167    #[cfg(target_arch = "aarch64")]
2168    #[::fuchsia::test]
2169    async fn test_stack_sample_in_starnix_reports_aarch32_guest_regs() {
2170        const GUEST_PC32: u64 = 0x0040_1234;
2171        const GUEST_SP32: u64 = 0xff80_1000;
2172        let starnix_stack = stack_bytes(0x10, 64);
2173        let mut guest_x: [u64; 31] = std::array::from_fn(|i| 0xc000 + i as u64);
2174        guest_x[AARCH32_REG_SP] = GUEST_SP32;
2175        // AArch32 execution does not update the sp field; it holds a stale
2176        // value that no capture covers.
2177        let restricted_state = restricted_state_bytes(
2178            &guest_x,
2179            0xdead_0000,
2180            GUEST_PC32,
2181            zx::sys::ZX_REG_CPSR_ARCH_32_MASK as u32,
2182        );
2183        let guest_stack = stack_bytes(0x40, 32);
2184        let blob = stack_sample_blob(
2185            &thread_state_regs(STARNIX_PC, STARNIX_SP, 0),
2186            &[
2187                (STARNIX_SP, &starnix_stack),
2188                (RESTRICTED_STATE_ADDR, &restricted_state),
2189                (GUEST_SP32, &guest_stack),
2190            ],
2191        );
2192
2193        let sample = process_stack_sample(blob, ALL_REGS_MASK);
2194
2195        assert_eq!(sample.abi, perf_sample_regs_abi_PERF_SAMPLE_REGS_ABI_32 as u64);
2196        assert_eq!(
2197            sample.callchain,
2198            vec![
2199                perf_callchain_context_PERF_CONTEXT_KERNEL,
2200                STARNIX_PC,
2201                perf_callchain_context_PERF_CONTEXT_USER,
2202                GUEST_PC32,
2203            ]
2204        );
2205        assert_eq!(sample.regs.len(), 33);
2206        assert_eq!(sample.regs[AARCH32_REG_SP], GUEST_SP32);
2207        assert_eq!(sample.regs[AARCH32_REG_PC], GUEST_PC32);
2208        assert_eq!(sample.regs[AARCH64_REG_PC], GUEST_PC32);
2209        // The sp and lr slots carry R13 and R14.
2210        assert_eq!(sample.regs[AARCH64_REG_SP], GUEST_SP32);
2211        assert_eq!(sample.regs[AARCH64_REG_LR], guest_x[AARCH32_REG_LR]);
2212        assert_eq!(sample.regs[0], 0xc000);
2213        assert_eq!(sample.stack, guest_stack);
2214    }
2215
2216    #[::fuchsia::test]
2217    async fn test_stack_sample_in_guest_reports_thread_regs() {
2218        // A thread suspended in restricted mode reports the guest registers
2219        // itself; the restricted state then holds stale values and is
2220        // ignored.
2221        let guest_stack = stack_bytes(0x20, 64);
2222        let stale_stack = stack_bytes(0x90, 64);
2223        const STALE_SP: u64 = GUEST_SP + 0x10000;
2224        #[cfg(target_arch = "aarch64")]
2225        let stale_state = restricted_state_bytes(&[0x5a; 31], STALE_SP, GUEST_PC + 0x100, 0);
2226        #[cfg(not(target_arch = "aarch64"))]
2227        let stale_state = vec![0x5au8; 280];
2228        let thread_regs = thread_state_regs(GUEST_PC, GUEST_SP + 16, 0);
2229        let blob = stack_sample_blob(
2230            &thread_regs,
2231            &[
2232                (GUEST_SP, &guest_stack),
2233                (RESTRICTED_STATE_ADDR, &stale_state),
2234                (STALE_SP, &stale_stack),
2235            ],
2236        );
2237
2238        let sample = process_stack_sample(blob, ALL_REGS_MASK);
2239
2240        assert_eq!(sample.ip, GUEST_PC);
2241        assert_eq!(
2242            sample.callchain,
2243            vec![
2244                perf_callchain_context_PERF_CONTEXT_KERNEL,
2245                perf_callchain_context_PERF_CONTEXT_USER,
2246                GUEST_PC,
2247            ]
2248        );
2249        assert_eq!(sample.abi, perf_sample_regs_abi_PERF_SAMPLE_REGS_ABI_64 as u64);
2250        assert_eq!(sample.regs, thread_regs[..33].to_vec());
2251        // The stack starts exactly at the SP, inside the first capture.
2252        assert_eq!(sample.stack, guest_stack[16..].to_vec());
2253    }
2254
2255    #[cfg(target_arch = "aarch64")]
2256    #[::fuchsia::test]
2257    async fn test_stack_sample_in_guest_aarch32_reports_sp_lr_from_r13_r14() {
2258        // A thread suspended in AArch32 restricted mode: Zircon reports its
2259        // stack pointer in r[13] and link register in r[14]; the sp and lr
2260        // slots hold SP_EL0 and X30, which AArch32 execution never updates.
2261        const GUEST_PC32: u64 = 0x0040_1234;
2262        const GUEST_SP32: u64 = 0xff80_1000;
2263        const GUEST_LR32: u64 = 0x0040_5678;
2264        const STALE_SP: u64 = 0xdead_0000;
2265        const STALE_LR: u64 = 0xbeef_0000;
2266        let guest_stack = stack_bytes(0x30, 64);
2267        let mut thread_regs =
2268            thread_state_regs(GUEST_PC32, STALE_SP, zx::sys::ZX_REG_CPSR_ARCH_32_MASK as u64);
2269        thread_regs[AARCH32_REG_SP] = GUEST_SP32;
2270        thread_regs[AARCH32_REG_LR] = GUEST_LR32;
2271        thread_regs[AARCH64_REG_LR] = STALE_LR;
2272        let blob = stack_sample_blob(&thread_regs, &[(GUEST_SP32, &guest_stack)]);
2273
2274        let sample = process_stack_sample(blob, ALL_REGS_MASK);
2275
2276        assert_eq!(sample.ip, GUEST_PC32);
2277        assert_eq!(
2278            sample.callchain,
2279            vec![
2280                perf_callchain_context_PERF_CONTEXT_KERNEL,
2281                perf_callchain_context_PERF_CONTEXT_USER,
2282                GUEST_PC32,
2283            ]
2284        );
2285        assert_eq!(sample.abi, perf_sample_regs_abi_PERF_SAMPLE_REGS_ABI_32 as u64);
2286        assert_eq!(sample.regs.len(), 33);
2287        assert_eq!(sample.regs[AARCH64_REG_SP], GUEST_SP32);
2288        assert_eq!(sample.regs[AARCH64_REG_LR], GUEST_LR32);
2289        assert_eq!(sample.regs[AARCH32_REG_SP], GUEST_SP32);
2290        assert_eq!(sample.regs[AARCH32_REG_LR], GUEST_LR32);
2291        assert_eq!(sample.regs[AARCH32_REG_PC], GUEST_PC32);
2292        assert_eq!(sample.regs[AARCH64_REG_PC], GUEST_PC32);
2293        assert_eq!(sample.regs[0], 0xa000);
2294        // The stack is selected by R13, not by the stale sp slot.
2295        assert_eq!(sample.stack, guest_stack);
2296    }
2297
2298    #[::fuchsia::test]
2299    async fn test_stack_sample_in_starnix_without_restricted_state_reports_thread_regs() {
2300        let starnix_stack = stack_bytes(0x10, 64);
2301        let thread_regs = thread_state_regs(STARNIX_PC, STARNIX_SP, 0);
2302        let blob = stack_sample_blob(&thread_regs, &[(STARNIX_SP, &starnix_stack)]);
2303
2304        let sample = process_stack_sample(blob, ALL_REGS_MASK);
2305
2306        assert_reports_thread_state(&sample, &starnix_stack);
2307    }
2308
2309    #[::fuchsia::test]
2310    async fn test_stack_sample_in_starnix_without_captures_is_reported() {
2311        let thread_regs = thread_state_regs(STARNIX_PC, STARNIX_SP, 0);
2312        let blob = stack_sample_blob(&thread_regs, &[]);
2313
2314        let sample = process_stack_sample(blob, ALL_REGS_MASK);
2315
2316        assert_reports_thread_state(&sample, &[]);
2317    }
2318
2319    #[cfg(target_arch = "aarch64")]
2320    #[::fuchsia::test]
2321    async fn test_stack_sample_in_starnix_ignores_unusable_restricted_state() {
2322        let starnix_stack = stack_bytes(0x10, 64);
2323        let guest_stack = stack_bytes(0x80, 48);
2324        let guest_x: [u64; 31] = std::array::from_fn(|i| 0xb000 + i as u64);
2325        let valid_state = restricted_state_bytes(&guest_x, GUEST_SP, GUEST_PC, 0);
2326        let cases: [(&str, u64, Vec<u8>); 6] = [
2327            (
2328                "pc above the guest address space",
2329                RESTRICTED_STATE_ADDR,
2330                restricted_state_bytes(&guest_x, GUEST_SP, STARNIX_PC, 0),
2331            ),
2332            (
2333                "pc below the guest address space",
2334                RESTRICTED_STATE_ADDR,
2335                restricted_state_bytes(&guest_x, GUEST_SP, 0x1000, 0),
2336            ),
2337            (
2338                "sp above the guest address space",
2339                RESTRICTED_STATE_ADDR,
2340                restricted_state_bytes(&guest_x, STARNIX_SP, GUEST_PC, 0),
2341            ),
2342            ("not yet written", RESTRICTED_STATE_ADDR, restricted_state_bytes(&[0; 31], 0, 0, 0)),
2343            ("not page-aligned", RESTRICTED_STATE_ADDR + 8, valid_state.clone()),
2344            ("short read", RESTRICTED_STATE_ADDR, valid_state[..valid_state.len() - 8].to_vec()),
2345        ];
2346        for (name, state_addr, state) in cases {
2347            let blob = stack_sample_blob(
2348                &thread_state_regs(STARNIX_PC, STARNIX_SP, 0),
2349                &[(STARNIX_SP, &starnix_stack), (state_addr, &state), (GUEST_SP, &guest_stack)],
2350            );
2351
2352            let sample = process_stack_sample(blob, ALL_REGS_MASK);
2353
2354            println!("case: {name}");
2355            assert_reports_thread_state(&sample, &starnix_stack);
2356        }
2357    }
2358
2359    #[cfg(target_arch = "aarch64")]
2360    #[::fuchsia::test]
2361    async fn test_stack_sample_in_starnix_without_guest_stack_reports_guest_regs() {
2362        // Even if the guest stack chunk is missing (e.g. the guest SP was
2363        // unmapped when sampled), guest registers are still reported with an
2364        // empty stack.
2365        let starnix_stack = stack_bytes(0x10, 64);
2366        let guest_x: [u64; 31] = std::array::from_fn(|i| 0xb000 + i as u64);
2367        let restricted_state = restricted_state_bytes(&guest_x, GUEST_SP, GUEST_PC, 0);
2368        let thread_regs = thread_state_regs(STARNIX_PC, STARNIX_SP, 0);
2369        let blob = stack_sample_blob(
2370            &thread_regs,
2371            &[(STARNIX_SP, &starnix_stack), (RESTRICTED_STATE_ADDR, &restricted_state)],
2372        );
2373
2374        let sample = process_stack_sample(blob, ALL_REGS_MASK);
2375
2376        assert_eq!((sample.pid, sample.tid), (42, 43));
2377        assert_eq!(sample.ip, STARNIX_PC);
2378        assert_eq!(
2379            sample.callchain,
2380            vec![
2381                perf_callchain_context_PERF_CONTEXT_KERNEL,
2382                STARNIX_PC,
2383                perf_callchain_context_PERF_CONTEXT_USER,
2384                GUEST_PC,
2385            ]
2386        );
2387        assert_eq!(sample.abi, perf_sample_regs_abi_PERF_SAMPLE_REGS_ABI_64 as u64);
2388        let mut expected_regs = guest_x.to_vec();
2389        expected_regs.extend([GUEST_SP, GUEST_PC]);
2390        assert_eq!(sample.regs, expected_regs);
2391        assert!(sample.stack.is_empty());
2392    }
2393
2394    #[cfg(target_arch = "aarch64")]
2395    #[::fuchsia::test]
2396    async fn test_stack_sample_in_starnix_with_uncovered_guest_sp_reports_guest_regs() {
2397        // If no chunk covers the guest SP, report guest registers with an
2398        // empty stack rather than picking the Starnix stack.
2399        let starnix_stack = stack_bytes(0x10, 64);
2400        let other_stack = stack_bytes(0x80, 48);
2401        let guest_x: [u64; 31] = std::array::from_fn(|i| 0xb000 + i as u64);
2402        let restricted_state = restricted_state_bytes(&guest_x, GUEST_SP + 0x10000, GUEST_PC, 0);
2403        let blob = stack_sample_blob(
2404            &thread_state_regs(STARNIX_PC, STARNIX_SP, 0),
2405            &[
2406                (STARNIX_SP, &starnix_stack),
2407                (RESTRICTED_STATE_ADDR, &restricted_state),
2408                (GUEST_SP, &other_stack),
2409            ],
2410        );
2411
2412        let sample = process_stack_sample(blob, ALL_REGS_MASK);
2413
2414        assert_eq!(sample.abi, perf_sample_regs_abi_PERF_SAMPLE_REGS_ABI_64 as u64);
2415        assert_eq!(sample.regs[AARCH64_REG_SP], GUEST_SP + 0x10000);
2416        assert_eq!(sample.regs[AARCH64_REG_PC], GUEST_PC);
2417        assert!(sample.stack.is_empty());
2418        assert_eq!(
2419            sample.callchain,
2420            vec![
2421                perf_callchain_context_PERF_CONTEXT_KERNEL,
2422                STARNIX_PC,
2423                perf_callchain_context_PERF_CONTEXT_USER,
2424                GUEST_PC,
2425            ]
2426        );
2427    }
2428
2429    #[::fuchsia::test]
2430    async fn test_stack_sample_in_guest_without_stack_capture_is_reported() {
2431        let thread_regs = thread_state_regs(GUEST_PC, GUEST_SP, 0);
2432        let blob = stack_sample_blob(&thread_regs, &[]);
2433
2434        let sample = process_stack_sample(blob, ALL_REGS_MASK);
2435
2436        assert_eq!(
2437            sample.callchain,
2438            vec![
2439                perf_callchain_context_PERF_CONTEXT_KERNEL,
2440                perf_callchain_context_PERF_CONTEXT_USER,
2441                GUEST_PC,
2442            ]
2443        );
2444        assert_eq!(sample.abi, perf_sample_regs_abi_PERF_SAMPLE_REGS_ABI_64 as u64);
2445        assert_eq!(sample.regs, thread_regs[..33].to_vec());
2446        assert!(sample.stack.is_empty());
2447    }
2448
2449    #[::fuchsia::test]
2450    async fn test_callgraph_config_stack_size() {
2451        let callchain_only =
2452            callgraph_config(perf_event_sample_format_PERF_SAMPLE_CALLCHAIN as u64, 65280);
2453        assert_eq!(callchain_only.strategy, Some(profiler::CallgraphStrategy::FramePointer));
2454        assert_eq!(callchain_only.stack_size, None);
2455
2456        let regs_only =
2457            callgraph_config(perf_event_sample_format_PERF_SAMPLE_REGS_USER as u64, 65280);
2458        assert_eq!(regs_only.strategy, Some(profiler::CallgraphStrategy::Dwarf));
2459        assert_eq!(regs_only.stack_size, None);
2460
2461        let zero_stack =
2462            callgraph_config(perf_event_sample_format_PERF_SAMPLE_STACK_USER as u64, 0);
2463        assert_eq!(zero_stack.strategy, Some(profiler::CallgraphStrategy::Dwarf));
2464        assert_eq!(zero_stack.stack_size, Some(0));
2465
2466        let custom_stack =
2467            callgraph_config(perf_event_sample_format_PERF_SAMPLE_STACK_USER as u64, 65280);
2468        assert_eq!(custom_stack.strategy, Some(profiler::CallgraphStrategy::Dwarf));
2469        assert_eq!(custom_stack.stack_size, Some(65280));
2470    }
2471}