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