1use 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);
65const ESTIMATED_MMAP_BUFFER_SIZE: u64 = 16 * 1024 * 1024;
75const LOST_RECORD_SIZE: u64 = 24;
78const AARCH64_REG_PC: usize = 32;
80const AARCH64_REG_SP: usize = 31;
81const AARCH64_REG_LR: usize = 30;
82const AARCH32_REG_PC: usize = 15; const AARCH32_REG_LR: usize = 14; const AARCH32_REG_SP: usize = 13; const 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
151unsafe 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 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_, 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, most_recent_enabled_time: u64,
219 total_time_running: u64,
223 rf_id: u64,
224 sample_id: u64,
225 _rf_lost: u64,
226 disabled: u64,
227 sample_type: u64,
228 perf_data_vmo: zx::Vmo,
231 ioctl_sender: future_mpsc::Sender<(IoctlOp, sync_mpsc::Sender<()>)>,
233}
234
235impl 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 pub security_state: security::PerfEventState,
268 seq_lock: Arc<OnceLock<Result<SeqLock<PerfMetadataHeader, PerfMetadataValue>, Errno>>>,
269}
270
271impl FileOps for PerfEventFile {
276 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 perf_event_file.disabled = 1;
285 ping_receiver(perf_event_file.ioctl_sender.clone(), IoctlOp::Disable);
286 }
287 let perf_state = get_perf_state(¤t_task.kernel);
288 let mut events = perf_state.format_id_lookup_table.lock();
289 events.remove(&file.id);
290 }
291
292 fn read(
294 &self,
295 _file: &FileObject,
296 current_task: &CurrentTask,
297 _offset: usize,
298 data: &mut dyn OutputBuffer,
299 ) -> Result<usize, Errno> {
300 security::check_perf_event_read_access(current_task, self)?;
301
302 let read_format_data = {
305 let mut perf_event_file = self.perf_event_file.write();
308
309 let mut total_time_running_including_curr = perf_event_file.total_time_running;
310
311 if perf_event_file.disabled == 0 {
313 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 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 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 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 output.extend(perf_event_file.rf_id.to_ne_bytes());
346 }
347
348 output
349 };
350
351 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; perf_event_file.most_recent_enabled_time =
383 zx::MonotonicInstant::get().into_nanos() as u64;
384 }
385
386 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; 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 fn get_memory(
439 &self,
440 _file: &FileObject,
441 current_task: &CurrentTask,
442 length: Option<usize>,
443 prot: ProtectionFlags,
444 ) -> Result<Arc<MemoryObject>, Errno> {
445 if prot.contains(ProtectionFlags::EXEC) {
446 return error!(EPERM);
447 }
448 let buffer_size: u64 = length.unwrap_or(0) as u64;
449 let page_size = zx::system_get_page_size() as u64;
450 if buffer_size <= page_size || buffer_size > ESTIMATED_MMAP_BUFFER_SIZE {
451 return error!(EINVAL);
452 }
453 let data_size = buffer_size - page_size;
454 if !data_size.is_power_of_two() {
455 return error!(EINVAL);
456 }
457
458 security::check_perf_event_read_access(current_task, self)?;
459
460 self.seq_lock
461 .get_or_init(|| {
462 let perf_event_file = self.perf_event_file.read();
463 let vmo_copy = perf_event_file
464 .perf_data_vmo
465 .as_handle_ref()
466 .duplicate_handle(zx::Rights::SAME_RIGHTS)
467 .map_err(|status| from_status_like_fdio!(status))?;
468 Ok(unsafe { create_seq_lock(&vmo_copy, buffer_size) })
470 })
471 .as_ref()
472 .map_err(|e| e.clone())?;
473
474 let perf_event_file = self.perf_event_file.read();
476 match perf_event_file
477 .perf_data_vmo
478 .as_handle_ref()
479 .duplicate_handle(zx::Rights::SAME_RIGHTS)
480 {
481 Ok(vmo) => {
482 let vmo: zx::Vmo = vmo.into();
483 let memory = MemoryObject::from(vmo);
484 return Ok(Arc::new(memory));
485 }
486 Err(_) => {
487 track_stub!(
488 TODO("https://fxbug.dev/416323134"),
489 "[perf_event_open] handle get_memory() errors"
490 );
491 return error!(EINVAL);
492 }
493 };
494 }
495
496 fn can_map_executable(&self) -> bool {
498 false
499 }
500
501 fn write(
502 &self,
503 _file: &FileObject,
504 _current_task: &CurrentTask,
505 _offset: usize,
506 _data: &mut dyn InputBuffer,
507 ) -> Result<usize, Errno> {
508 track_stub!(
509 TODO("https://fxbug.dev/394960158"),
510 "[perf_event_open] implement perf event functions"
511 );
512 error!(ENOSYS)
513 }
514}
515
516fn write_record_to_vmo(
544 perf_record_sample: PerfRecordSample<'_>,
545 perf_data_vmo: &zx::Vmo,
546 sample_type: u64,
547 sample_id: u64,
548 sample_period: u64,
549 read_format: u64,
550 head: u64,
551 metadata: &PerfMetadataValue,
552 lost_events: &mut u64,
553) -> u64 {
554 let ring_buffer_size = metadata.data_size;
555 if ring_buffer_size == 0 {
556 return 0;
557 }
558 let mut sample = Vec::<u8>::new();
560 if (sample_type & perf_event_sample_format_PERF_SAMPLE_IDENTIFIER as u64) != 0 {
562 sample.extend(sample_id.to_ne_bytes());
563 }
564 if (sample_type & perf_event_sample_format_PERF_SAMPLE_IP as u64) != 0 {
566 let ip = perf_record_sample.ips.first().copied().unwrap_or(0);
567 sample.extend(ip.to_ne_bytes());
568 }
569
570 if (sample_type & perf_event_sample_format_PERF_SAMPLE_TID as u64) != 0 {
571 sample.extend(perf_record_sample.pid.unwrap_or(0).to_ne_bytes());
573 sample.extend(perf_record_sample.tid.unwrap_or(0).to_ne_bytes());
575 }
576
577 if (sample_type & perf_event_sample_format_PERF_SAMPLE_TIME as u64) != 0 {
579 sample.extend((perf_record_sample.time.into_nanos() as u64).to_ne_bytes());
580 }
581
582 if (sample_type & perf_event_sample_format_PERF_SAMPLE_ID as u64) != 0 {
584 sample.extend(sample_id.to_ne_bytes());
585 }
586
587 if (sample_type & perf_event_sample_format_PERF_SAMPLE_PERIOD as u64) != 0 {
589 sample.extend(sample_period.to_ne_bytes());
590 }
591
592 if (sample_type & perf_event_sample_format_PERF_SAMPLE_READ as u64) != 0 {
594 if (read_format & perf_event_read_format_PERF_FORMAT_GROUP as u64) != 0 {
595 sample.extend(1u64.to_ne_bytes());
598 sample.extend(0u64.to_ne_bytes());
599 } else {
600 sample.extend(0u64.to_ne_bytes());
601 }
602 }
603
604 if (sample_type & perf_event_sample_format_PERF_SAMPLE_CALLCHAIN as u64) != 0 {
605 let nr_offset = sample.len();
618 sample.extend(0u64.to_ne_bytes());
619 let callchain_start = sample.len();
620
621 sample.extend(perf_callchain_context_PERF_CONTEXT_KERNEL.to_ne_bytes());
622 for ip in perf_record_sample.ips.iter().filter(|&&ip| ip >= GUEST_ASPACE_END) {
623 sample.extend(ip.to_ne_bytes());
624 }
625 sample.extend(perf_callchain_context_PERF_CONTEXT_USER.to_ne_bytes());
626 for ip in perf_record_sample.ips.iter().filter(|&&ip| ip < GUEST_ASPACE_END) {
627 sample.extend(ip.to_ne_bytes());
628 }
629
630 let nr = ((sample.len() - callchain_start) / std::mem::size_of::<u64>()) as u64;
632 sample[nr_offset..callchain_start].copy_from_slice(&nr.to_ne_bytes());
633 }
634
635 if (sample_type & perf_event_sample_format_PERF_SAMPLE_REGS_USER as u64) != 0 {
644 if perf_record_sample.regs_abi == perf_sample_regs_abi_PERF_SAMPLE_REGS_ABI_NONE as u64
645 || perf_record_sample.sample_regs_user == 0
646 || perf_record_sample.regs.is_empty()
647 {
648 sample.extend((perf_sample_regs_abi_PERF_SAMPLE_REGS_ABI_NONE as u64).to_ne_bytes());
649 } else {
650 sample.extend(perf_record_sample.regs_abi.to_ne_bytes());
651 sample.extend_from_slice(perf_record_sample.regs);
652 }
653 }
654
655 if (sample_type & perf_event_sample_format_PERF_SAMPLE_STACK_USER as u64) != 0 {
660 let fixed_len = std::mem::size_of::<perf_event_header>() + sample.len() + 16;
661 let header_space = (u16::MAX as usize).saturating_sub(fixed_len) & !7;
662 let requested = (perf_record_sample.sample_stack_user as usize) & !7;
663 let dest_len = requested.min(header_space);
664
665 sample.extend((dest_len as u64).to_ne_bytes());
666 if dest_len > 0 {
667 let data_len = perf_record_sample.stack.len().min(dest_len) & !7;
668 sample.extend_from_slice(&perf_record_sample.stack[..data_len]);
669 sample.resize(sample.len() + (dest_len - data_len), 0);
671 sample.extend((data_len as u64).to_ne_bytes());
673 }
674 }
675 let record_len = std::mem::size_of::<perf_event_header>() + sample.len();
681 if record_len % 8 != 0 {
687 log_error!("Record length {} is not 8-byte aligned, dropping", record_len);
688 *lost_events += 1;
689 return 0;
690 }
691 let Ok(record_size) = u16::try_from(record_len) else {
695 log_warn!("Dropping {} byte perf sample record: exceeds u16 record size", record_len);
696 *lost_events += 1;
697 return 0;
698 };
699
700 let perf_event_header = perf_event_header {
701 type_: perf_event_type_PERF_RECORD_SAMPLE,
704 misc: PERF_RECORD_MISC_USER as u16,
705 size: record_size,
706 };
707
708 let free_space =
713 (metadata.data_tail.wrapping_sub(head).wrapping_sub(1)) & (ring_buffer_size - 1);
714
715 if free_space < record_len as u64 {
719 *lost_events += 1;
720 return 0;
721 }
722
723 let mut bytes_written: u64 = 0;
724
725 if *lost_events > 0 && free_space >= record_len as u64 + LOST_RECORD_SIZE {
728 let lost_header = perf_event_header {
729 type_: perf_event_type_PERF_RECORD_LOST,
730 misc: 0,
731 size: LOST_RECORD_SIZE as u16,
732 };
733 let lost_record = LostRecord { header: lost_header, sample_id, lost_events: *lost_events };
734 if write_circular(
735 perf_data_vmo,
736 metadata.data_offset,
737 ring_buffer_size,
738 head,
739 lost_record.as_bytes(),
740 )
741 .is_ok()
742 {
743 *lost_events = 0;
744 bytes_written += LOST_RECORD_SIZE;
745 }
746 }
747
748 let mut record = Vec::with_capacity(record_len);
749 record.extend_from_slice(perf_event_header.as_bytes());
750 record.extend_from_slice(&sample);
751
752 match write_circular(
753 perf_data_vmo,
754 metadata.data_offset,
755 ring_buffer_size,
756 head + bytes_written,
757 &record,
758 ) {
759 Ok(()) => bytes_written + record_len as u64,
761 Err(e) => {
762 log_warn!("Failed to write PerfRecordSample to VMO due to: {}", e);
763 bytes_written
764 }
765 }
766}
767
768fn write_circular(
774 vmo: &zx::Vmo,
775 data_offset: u64,
776 ring_buffer_size: u64,
777 head: u64,
778 data: &[u8],
779) -> Result<(), zx::Status> {
780 let position = head % ring_buffer_size;
781 let vmo_offset = data_offset + position;
782 if position + data.len() as u64 <= ring_buffer_size {
783 vmo.write(data, vmo_offset)?;
784 } else {
785 let first_len = (ring_buffer_size - position) as usize;
786 vmo.write(&data[..first_len], vmo_offset)?;
787 vmo.write(&data[first_len..], data_offset)?;
788 }
789 Ok(())
790}
791
792#[derive(Debug, Clone)]
795struct PerfRecordSample<'a> {
796 pid: Option<u32>,
797 tid: Option<u32>,
798 time: zx::BootInstant,
800 ips: Vec<u64>,
802 regs: &'a [u8],
803 stack: &'a [u8],
804 regs_abi: u64,
806 sample_regs_user: u64,
807 sample_stack_user: u64,
808}
809
810fn callgraph_config(sample_type: u64, sample_stack_user: u32) -> profiler::CallgraphConfig {
811 let want_stack_samples = (sample_type
817 & (perf_event_sample_format_PERF_SAMPLE_REGS_USER as u64
818 | perf_event_sample_format_PERF_SAMPLE_STACK_USER as u64))
819 != 0;
820 let strategy = if want_stack_samples {
821 profiler::CallgraphStrategy::Dwarf
822 } else {
823 profiler::CallgraphStrategy::FramePointer
824 };
825 let stack_size = ((sample_type & perf_event_sample_format_PERF_SAMPLE_STACK_USER as u64) != 0)
826 .then_some(u64::from(sample_stack_user));
827 profiler::CallgraphConfig { strategy: Some(strategy), stack_size, ..Default::default() }
828}
829
830async fn set_up_profiler(
831 sample_period: zx::MonotonicDuration,
832 callgraph: profiler::CallgraphConfig,
833) -> Result<(profiler::SessionProxy, fidl::AsyncSocket), Errno> {
834 let sample = profiler::Sample { callgraph: Some(callgraph), ..Default::default() };
835
836 let sampling_config = profiler::SamplingConfig {
837 period: Some(sample_period.into_nanos() as u64),
838 timebase: Some(profiler::Counter::PlatformIndependent(profiler::CounterId::Nanoseconds)),
839 sample: Some(sample),
840 ..Default::default()
841 };
842
843 track_stub!(
844 TODO("https://fxbug.dev/398914921"),
845 "[perf_event_open] allow for profiling system-wide not during tests"
846 );
847 let job = fuchsia_runtime::job_default();
848 let koid = job.koid().map_err(|e| errno!(EINVAL, e.to_string()))?;
849 let tasks = vec![
850 profiler::Task::Job(koid.raw_koid()),
852 ];
853 let targets = profiler::TargetConfig::Tasks(tasks);
854 let config = profiler::Config {
855 configs: Some(vec![sampling_config]),
856 target: Some(targets),
857 ..Default::default()
858 };
859 let (client, server) = fidl::Socket::create_stream();
860 let configure = profiler::SessionConfigureRequest {
861 output: Some(server),
862 config: Some(config),
863 ..Default::default()
864 };
865
866 let proxy = connect_to_protocol::<profiler::SessionMarker>()
867 .context("Error connecting to Profiler protocol");
868 let session_proxy: profiler::SessionProxy = match proxy {
869 Ok(p) => p.clone(),
870 Err(e) => return error!(EINVAL, e),
871 };
872
873 let config_request = session_proxy.configure(configure).await;
875 match config_request {
876 Ok(_) => Ok((session_proxy, fidl::AsyncSocket::from_socket(client))),
877 Err(e) => return error!(EINVAL, e),
878 }
879}
880
881#[cfg(target_arch = "aarch64")]
883fn cpsr_is_aarch32(cpsr: u64) -> bool {
884 (cpsr & zx::sys::ZX_REG_CPSR_ARCH_32_MASK) == zx::sys::ZX_REG_CPSR_ARCH_32_MASK
885}
886
887#[cfg(not(target_arch = "aarch64"))]
888fn cpsr_is_aarch32(_cpsr: u64) -> bool {
889 false
890}
891
892#[cfg(target_arch = "aarch64")]
899fn guest_regs_from_restricted_state<'a>(chunks: &[(u64, &'a [u8])]) -> Option<(&'a [u8], u64)> {
900 chunks.iter().find_map(|&(base, data)| {
901 if base < GUEST_ASPACE_END || base % *PAGE_SIZE != 0 || data.len() != RESTRICTED_STATE_SIZE
902 {
903 return None;
904 }
905 let read_reg =
906 |index: usize| u64::from_ne_bytes(data[index * 8..index * 8 + 8].try_into().unwrap());
907 let cpsr = u32::from_ne_bytes(
908 data[RESTRICTED_STATE_CPSR_OFFSET..RESTRICTED_STATE_CPSR_OFFSET + 4]
909 .try_into()
910 .unwrap(),
911 );
912 let cpsr = u64::from(cpsr);
913 let sp =
915 if cpsr_is_aarch32(cpsr) { read_reg(AARCH32_REG_SP) } else { read_reg(AARCH64_REG_SP) };
916 let pc = read_reg(AARCH64_REG_PC);
917 if !(GUEST_ASPACE_BASE..GUEST_ASPACE_END).contains(&pc) || sp >= GUEST_ASPACE_END {
918 return None;
919 }
920 Some((&data[..REGS_BYTES], cpsr))
921 })
922}
923
924#[cfg(not(target_arch = "aarch64"))]
925fn guest_regs_from_restricted_state<'a>(_chunks: &[(u64, &'a [u8])]) -> Option<(&'a [u8], u64)> {
926 None
927}
928
929fn process_fxt_record(
933 record: TraceRecord,
934 seq_lock_wrapper: &SeqLock<PerfMetadataHeader, PerfMetadataValue>,
935 perf_data_vmo: &zx::Vmo,
936 sample_type: u64,
937 sample_id: u64,
938 sample_period: u64,
939 read_format: u64,
940 sample_regs_user: u64,
941 sample_stack_user: u64,
942 koid_session: Option<&PidKoidSession>,
943 vmo_write_offset: &mut u64,
944 lost_events: &mut u64,
945) {
946 match record {
947 TraceRecord::Profiler(ProfilerRecord::Backtrace(backtrace)) => {
948 let ips: Vec<u64> = backtrace.data;
949 let Some(LinuxIdentity::Thread { pid, tid }) = koid_session.and_then(|s| {
955 s.resolve_koids(
956 zx::Koid::from_raw(backtrace.process.0),
957 zx::Koid::from_raw(backtrace.thread.0),
958 )
959 }) else {
960 return;
961 };
962 let time = zx::BootInstant::from_nanos(backtrace.timestamp.max(0));
963 let perf_record_sample = PerfRecordSample {
964 pid: Some(pid as u32),
965 tid: Some(tid as u32),
966 time,
967 ips,
968 regs: &[],
969 stack: &[],
970 regs_abi: perf_sample_regs_abi_PERF_SAMPLE_REGS_ABI_NONE as u64,
971 sample_regs_user,
972 sample_stack_user,
973 };
974 let metadata = seq_lock_wrapper.get();
975 let bytes_written = write_record_to_vmo(
976 perf_record_sample,
977 perf_data_vmo,
978 sample_type,
979 sample_id,
980 sample_period,
981 read_format,
982 *vmo_write_offset,
983 &metadata,
984 lost_events,
985 );
986 if bytes_written > 0 {
990 *vmo_write_offset += bytes_written;
991 let mut metadata = seq_lock_wrapper.get();
992 metadata.data_head = *vmo_write_offset;
993 seq_lock_wrapper.set_value(metadata);
994 }
995 }
996 TraceRecord::LargeBlob(large_blob) => {
997 if large_blob.name != "stack_sample" {
1002 return;
1003 }
1004 let Some(blob_metadata) = large_blob.metadata else {
1005 return;
1006 };
1007 let bytes = &large_blob.bytes;
1008 if bytes.len() < 8 {
1009 return;
1010 }
1011 let regs_size = u64::from_ne_bytes(bytes[0..8].try_into().unwrap()) as usize;
1012 let mut offset = 8;
1013 if regs_size == 0 || bytes.len() < offset + regs_size {
1014 return;
1015 }
1016
1017 if regs_size < REGS_BYTES + 8 {
1023 return;
1024 }
1025 let thread_cpsr = u64::from_ne_bytes(
1027 bytes[offset + REGS_BYTES..offset + REGS_BYTES + 8].try_into().unwrap(),
1028 );
1029 let thread_regs = &bytes[offset..offset + REGS_BYTES];
1030 offset += regs_size;
1031
1032 let mut chunks = Vec::<(u64, &[u8])>::new();
1037 while offset + 16 <= bytes.len() {
1038 let chunk_base = u64::from_ne_bytes(bytes[offset..offset + 8].try_into().unwrap());
1039 offset += 8;
1040 let chunk_size = u64::from_ne_bytes(bytes[offset..offset + 8].try_into().unwrap());
1041 offset += 8;
1042 if offset + chunk_size as usize > bytes.len() {
1043 break;
1044 }
1045 chunks.push((chunk_base, &bytes[offset..offset + chunk_size as usize]));
1046 offset += chunk_size as usize;
1047 }
1048
1049 let pc_offset = AARCH64_REG_PC * 8;
1054 let thread_pc =
1055 u64::from_ne_bytes(thread_regs[pc_offset..pc_offset + 8].try_into().unwrap());
1056 let in_starnix = thread_pc >= GUEST_ASPACE_END;
1057 let guest_state =
1058 if in_starnix { guest_regs_from_restricted_state(&chunks) } else { None };
1059 let reports_guest_state = guest_state.is_some();
1060 let (regs, cpsr) = guest_state.unwrap_or((thread_regs, thread_cpsr));
1061 let is_32bit = cpsr_is_aarch32(cpsr);
1062
1063 let mut blob_regs = Cow::Borrowed(regs);
1066 if is_32bit {
1067 let blob_regs = blob_regs.to_mut();
1068 let r15_offset = AARCH32_REG_PC * 8;
1076 blob_regs.copy_within(pc_offset..pc_offset + 8, r15_offset);
1077 let r13_offset = AARCH32_REG_SP * 8;
1083 let sp_offset = AARCH64_REG_SP * 8;
1084 blob_regs.copy_within(r13_offset..r13_offset + 8, sp_offset);
1085 let r14_offset = AARCH32_REG_LR * 8;
1086 let lr_offset = AARCH64_REG_LR * 8;
1087 blob_regs.copy_within(r14_offset..r14_offset + 8, lr_offset);
1088 }
1089
1090 let read_reg = |index: usize| {
1091 u64::from_ne_bytes(blob_regs[index * 8..index * 8 + 8].try_into().unwrap())
1092 };
1093 let sp = if is_32bit {
1094 read_reg(AARCH32_REG_SP)
1096 } else {
1097 read_reg(AARCH64_REG_SP)
1098 };
1099 let pc = read_reg(AARCH64_REG_PC);
1100
1101 let stack: &[u8] = chunks
1111 .iter()
1112 .find_map(|&(chunk_base, data)| {
1113 (chunk_base <= sp && sp - chunk_base < data.len() as u64)
1114 .then(|| &data[(sp - chunk_base) as usize..])
1115 })
1116 .unwrap_or(&[]);
1117
1118 let Some(LinuxIdentity::Thread { pid, tid }) = koid_session.and_then(|s| {
1119 s.resolve_koids(
1120 zx::Koid::from_raw(blob_metadata.process.0),
1121 zx::Koid::from_raw(blob_metadata.thread.0),
1122 )
1123 }) else {
1124 return;
1125 };
1126 let time = zx::BootInstant::from_nanos(blob_metadata.timestamp.max(0));
1127 let regs_abi = if is_32bit {
1128 perf_sample_regs_abi_PERF_SAMPLE_REGS_ABI_32 as u64
1129 } else {
1130 perf_sample_regs_abi_PERF_SAMPLE_REGS_ABI_64 as u64
1131 };
1132 let perf_record_sample = PerfRecordSample {
1133 pid: Some(pid as u32),
1134 tid: Some(tid as u32),
1135 time,
1136 ips: if reports_guest_state { vec![thread_pc, pc] } else { vec![pc] },
1139 regs: &blob_regs,
1140 stack,
1141 regs_abi,
1142 sample_regs_user,
1143 sample_stack_user,
1144 };
1145 let metadata = seq_lock_wrapper.get();
1146 let bytes_written = write_record_to_vmo(
1147 perf_record_sample,
1148 perf_data_vmo,
1149 sample_type,
1150 sample_id,
1151 sample_period,
1152 read_format,
1153 *vmo_write_offset,
1154 &metadata,
1155 lost_events,
1156 );
1157 if bytes_written > 0 {
1161 *vmo_write_offset += bytes_written;
1162 let mut metadata = seq_lock_wrapper.get();
1163 metadata.data_head = *vmo_write_offset;
1164 seq_lock_wrapper.set_value(metadata);
1165 }
1166 }
1167 _ => {}
1168 }
1169}
1170
1171fn sampling_period(attr: &perf_event_attr) -> Option<zx::MonotonicDuration> {
1175 let value = unsafe { attr.__bindgen_anon_1.sample_period };
1182 if value == 0 {
1183 return None;
1184 }
1185 let nanos = if attr.freq() != 0 { (1_000_000_000 / value).max(1) } else { value };
1186 Some(zx::MonotonicDuration::from_nanos(nanos as i64))
1187}
1188
1189fn ping_receiver(
1194 mut ioctl_sender: future_mpsc::Sender<(IoctlOp, sync_mpsc::Sender<()>)>,
1195 command: IoctlOp,
1196) {
1197 log_info!("[perf_event_open] Received sampling command: {:?}", command);
1198 let (profiling_complete_sender, profiling_complete_receiver) = sync_mpsc::channel::<()>();
1199 match ioctl_sender.try_send((command, profiling_complete_sender)) {
1200 Ok(_) => (),
1201 Err(e) => {
1202 if e.is_full() {
1203 log_warn!("[perf_event_open] Failed to send {:?}: Channel full", command);
1204 } else if e.is_disconnected() {
1205 log_warn!("[perf_event_open] Failed to send {:?}: Receiver disconnected", command);
1206 } else {
1207 log_warn!("[perf_event_open] Failed to send {:?} due to {:?}", command, e.source());
1208 }
1209 }
1210 };
1211 let _ = profiling_complete_receiver.recv().unwrap();
1214}
1215
1216unsafe fn create_seq_lock(
1225 vmo_handle_ref: &zx::NullableHandle,
1226 buffer_size: u64,
1227) -> SeqLock<PerfMetadataHeader, PerfMetadataValue> {
1228 let metadata_header = PerfMetadataHeader { version: 1, compat_version: 2 };
1230 let page_size = *PAGE_SIZE;
1231 let metadata_value = PerfMetadataValue {
1232 lock: 0,
1233 index: 3,
1234 offset: 19337,
1235 time_enabled: 0,
1236 time_running: 0,
1237 __bindgen_anon_1: perf_event_mmap_page__bindgen_ty_1 { capabilities: 30 },
1238 pmc_width: 0,
1239 time_shift: 0,
1240 time_mult: 0,
1241 time_offset: 0,
1242 time_zero: 0,
1243 size: 0,
1244 __reserved_1: 0,
1245 time_cycles: 0,
1246 time_mask: 0,
1247 __reserved: [0; 928usize],
1248 data_head: 0,
1250 data_tail: 0,
1252 data_offset: page_size,
1254 data_size: buffer_size - page_size,
1255 aux_head: 0,
1256 aux_tail: 0,
1257 aux_offset: 0,
1258 aux_size: 0,
1259 };
1260 let vmo = zx::Vmo::from(vmo_handle_ref.duplicate_handle(zx::Rights::SAME_RIGHTS).unwrap());
1261
1262 unsafe {
1270 SeqLock::new_from_vmo(metadata_header, metadata_value, vmo)
1271 .expect("failed to create seq_lock for perf metadata")
1272 }
1273}
1274
1275pub fn sys_perf_event_open(
1276 current_task: &CurrentTask,
1277 attr: UserRef<perf_event_attr>,
1278 tid: tid_t,
1280 cpu: i32,
1281 group_fd: FdNumber,
1282 _flags: u64,
1283) -> Result<SyscallResult, Errno> {
1284 let perf_event_attrs: perf_event_attr = current_task.read_object(attr)?;
1288
1289 if (perf_event_attrs.sample_type & perf_event_sample_format_PERF_SAMPLE_STACK_USER as u64) != 0
1290 {
1291 if perf_event_attrs.sample_stack_user % 8 != 0 {
1292 return error!(EINVAL);
1293 }
1294 }
1295
1296 if tid == -1 && cpu == -1 {
1297 return error!(EINVAL);
1298 }
1299
1300 let target = match tid {
1301 -1 => PerfEventTarget::AllTasks,
1302 0 => PerfEventTarget::CurrentTask,
1303 _ => {
1304 track_stub!(TODO("https://fxbug.dev/409621963"), "[perf_event_open] implement tid > 0");
1305 return error!(ENOSYS);
1306 }
1307 };
1308 let event_type: PerfEventType = perf_event_attrs.type_.try_into()?;
1309
1310 security::check_perf_event_open_access(current_task, PerfEventOpenType::Open)?;
1313
1314 let paranoid = current_task.kernel().perf_event_paranoid.load(Ordering::Relaxed);
1320 let requires_capability = (paranoid > 1 && perf_event_attrs.exclude_kernel() == 0)
1321 || paranoid > 0
1322 || (paranoid > -1 && event_type == PerfEventType::Tracepoint);
1323 let is_exempt = matches!(
1326 (event_type, perf_event_attrs.exclude_kernel(), &target),
1327 (PerfEventType::Tracepoint, 1, PerfEventTarget::CurrentTask)
1328 );
1329 if requires_capability
1332 && !is_exempt
1333 && !security::is_task_capable_noaudit(current_task, CAP_SYS_ADMIN)
1334 {
1335 security::check_task_capable(current_task, CAP_PERFMON).map_err(|_| errno!(EACCES))?;
1336 }
1337
1338 if perf_event_attrs.exclude_kernel() == 0 {
1340 security::check_perf_event_open_access(current_task, PerfEventOpenType::Kernel)?;
1341 }
1342
1343 let check_cpu = !requires_capability
1351 || match event_type {
1352 PerfEventType::Software | PerfEventType::Hardware => true,
1353 PerfEventType::HwCache
1354 | PerfEventType::Tracepoint
1355 | PerfEventType::Breakpoint
1356 | PerfEventType::Raw => matches!(target, PerfEventTarget::AllTasks),
1357 };
1358 if check_cpu {
1359 security::check_perf_event_open_access(current_task, PerfEventOpenType::Cpu)?;
1360 }
1361
1362 if event_type == PerfEventType::Tracepoint {
1364 security::check_perf_event_open_access(current_task, PerfEventOpenType::Tracepoint)?;
1365 }
1366
1367 let (sender, mut receiver) = future_mpsc::channel(8);
1371
1372 let mut perf_event_file = PerfEventFileState::new(
1373 perf_event_attrs,
1374 0,
1375 perf_event_attrs.disabled(),
1376 perf_event_attrs.sample_type,
1377 zx::Vmo::create(ESTIMATED_MMAP_BUFFER_SIZE).unwrap(),
1378 sender,
1379 );
1380
1381 let read_format = perf_event_attrs.read_format;
1382
1383 if (read_format & perf_event_read_format_PERF_FORMAT_TOTAL_TIME_ENABLED as u64) != 0
1384 || (read_format & perf_event_read_format_PERF_FORMAT_TOTAL_TIME_RUNNING as u64) != 0
1385 {
1386 if perf_event_file.disabled == 0 {
1389 perf_event_file.most_recent_enabled_time =
1390 zx::MonotonicInstant::get().into_nanos() as u64;
1391 }
1392 perf_event_file.total_time_running = 0;
1394 }
1395
1396 let event_id = READ_FORMAT_ID_GENERATOR.fetch_add(1, Ordering::Relaxed);
1397 perf_event_file.rf_id = event_id;
1398
1399 if group_fd.raw() == -1 {
1400 perf_event_file.sample_id = event_id;
1401 } else {
1402 let group_file = current_task.files().get(group_fd)?;
1403 let group_file_object_id = group_file.id;
1404 let perf_state = get_perf_state(¤t_task.kernel);
1405 let events = perf_state.format_id_lookup_table.lock();
1406 if let Some(rf_id) = events.get(&group_file_object_id) {
1407 perf_event_file.sample_id = *rf_id;
1408 } else {
1409 return error!(EINVAL);
1410 }
1411 }
1412
1413 if (read_format & perf_event_read_format_PERF_FORMAT_GROUP as u64) != 0 {
1414 track_stub!(
1415 TODO("https://fxbug.dev/402238049"),
1416 "[perf_event_open] implement read_format group"
1417 );
1418 return error!(ENOSYS);
1419 }
1420 if (read_format & perf_event_read_format_PERF_FORMAT_LOST as u64) != 0 {
1421 track_stub!(
1422 TODO("https://fxbug.dev/402260383"),
1423 "[perf_event_open] implement read_format lost"
1424 );
1425 }
1426
1427 let mut vmo_handle_copy =
1429 perf_event_file.perf_data_vmo.as_handle_ref().duplicate_handle(zx::Rights::SAME_RIGHTS);
1430
1431 let sample_period = sampling_period(&perf_event_file.attr);
1432
1433 let seq_lock =
1435 Arc::new(OnceLock::<Result<SeqLock<PerfMetadataHeader, PerfMetadataValue>, Errno>>::new());
1436 let cloned_seq_lock = Arc::clone(&seq_lock);
1437 let mut vmo_write_offset = 0;
1438
1439 let closure = async move |kthread_task: &CurrentTask| {
1440 let mut lost_events: u64 = 0;
1441
1442 while let Some((command, profiling_complete_receiver)) = receiver.next().await {
1444 if command != IoctlOp::Enable {
1446 let _ = profiling_complete_receiver.send(());
1447 continue;
1448 }
1449
1450 let Some(sample_period) = sample_period else {
1452 let _ = profiling_complete_receiver.send(());
1453 continue;
1454 };
1455
1456 let callgraph = callgraph_config(
1457 perf_event_file.sample_type,
1458 perf_event_file.attr.sample_stack_user,
1459 );
1460 let (session_proxy, client) = match set_up_profiler(sample_period, callgraph).await {
1461 Ok(session) => session,
1462 Err(e) => {
1463 log_warn!("Failed to profile: {}", e);
1464 let _ = profiling_complete_receiver.send(());
1465 continue;
1466 }
1467 };
1468
1469 let pid_koid_session = kthread_task.kernel().trace_event_manager.open();
1473 let start_request =
1474 profiler::SessionStartRequest { buffer_results: Some(false), ..Default::default() };
1475 if let Err(e) = session_proxy.start(&start_request).await {
1476 log_warn!("Failed to start profiler: {:?}", e);
1477 let _ = profiling_complete_receiver.send(());
1478 continue;
1479 }
1480 let _ = profiling_complete_receiver.send(());
1481
1482 let vmo = zx::Vmo::from(
1483 vmo_handle_copy
1484 .as_mut()
1485 .expect("Failed to get VMO handle")
1486 .as_handle_ref()
1487 .duplicate_handle(zx::Rights::SAME_RIGHTS)
1488 .unwrap(),
1489 );
1490 let mut handle_record = |record: TraceRecord| {
1491 if let Some(Ok(seq_lock_wrapper)) = cloned_seq_lock.get() {
1494 process_fxt_record(
1495 record,
1496 seq_lock_wrapper,
1497 &vmo,
1498 perf_event_file.sample_type,
1499 perf_event_file.sample_id,
1500 sample_period.into_nanos() as u64,
1501 perf_event_file.attr.read_format,
1502 perf_event_file.attr.sample_regs_user,
1503 perf_event_file.attr.sample_stack_user as u64,
1504 Some(&pid_koid_session),
1505 &mut vmo_write_offset,
1506 &mut lost_events,
1507 );
1508 }
1509 };
1510
1511 let (stream, _parser_task) = SessionParser::new_async(client);
1515 let mut stream = stream.fuse();
1516 let mut stream_ended = false;
1517 let disable_ack = loop {
1518 if stream_ended {
1519 match receiver.next().await {
1522 Some((IoctlOp::Disable, ack)) => break Some(ack),
1523 Some((IoctlOp::Enable, ack)) => {
1524 let _ = ack.send(());
1526 }
1527 None => break None,
1528 }
1529 } else {
1530 futures::select_biased! {
1531 cmd = receiver.next() => match cmd {
1532 Some((IoctlOp::Disable, ack)) => break Some(ack),
1533 Some((IoctlOp::Enable, ack)) => {
1534 let _ = ack.send(());
1536 }
1537 None => break None,
1538 },
1539 record = stream.next() => match record {
1540 Some(Ok(record)) => handle_record(record),
1541 Some(Err(e)) => {
1542 log_warn!("[perf_event_open] Error parsing FXT: {:?}", e);
1548 stream_ended = true;
1549 break None;
1550 }
1551 None => {
1552 log_warn!("[perf_event_open] Profiler stream ended mid-session");
1553 stream_ended = true;
1554 }
1555 },
1556 }
1557 }
1558 };
1559
1560 let stop_and_reset = async {
1566 match session_proxy.stop().await {
1567 Ok(stats) => log_info!(
1568 "[perf_event_open] profiler samples_collected: {:?}",
1569 stats.samples_collected.unwrap_or(0)
1570 ),
1571 Err(e) => log_warn!("[perf_event_open] Failed to stop profiler: {:?}", e),
1572 }
1573 let _ = session_proxy.reset().await;
1576 };
1577 let drain = async move {
1578 if !stream_ended {
1579 while let Some(record) = stream.next().await {
1580 match record {
1581 Ok(record) => handle_record(record),
1582 Err(e) => {
1583 log_warn!("[perf_event_open] Error parsing FXT: {:?}", e);
1586 break;
1587 }
1588 }
1589 }
1590 }
1591 drop(stream);
1594 drop(_parser_task);
1595 };
1596 futures::join!(stop_and_reset, drain);
1597
1598 if let Some(ack) = disable_ack {
1601 let _ = ack.send(());
1602 }
1603 }
1604 };
1605 let req = SpawnRequestBuilder::new()
1606 .with_debug_name("perf-event-sampler")
1607 .with_async_closure(closure)
1608 .build();
1609 current_task.kernel().kthreads.spawner().spawn_from_request(req);
1610
1611 let file = Box::new(PerfEventFile {
1612 _tid: tid,
1613 _cpu: cpu,
1614 perf_event_file: perf_event_file.into(),
1615 security_state: security::perf_event_alloc(current_task),
1616 seq_lock: seq_lock,
1617 });
1618 let file_handle = Anon::new_private_file(current_task, file, OpenFlags::RDWR, "[perf_event]");
1620 let file_object_id = file_handle.id;
1621 let file_descriptor: Result<FdNumber, Errno> =
1622 current_task.add_file(file_handle, FdFlags::empty());
1623
1624 match file_descriptor {
1625 Ok(fd) => {
1626 if group_fd.raw() == -1 {
1627 let perf_state = get_perf_state(¤t_task.kernel);
1628 let mut events = perf_state.format_id_lookup_table.lock();
1629 events.insert(file_object_id, event_id);
1630 }
1631 Ok(fd.into())
1632 }
1633 Err(_) => {
1634 track_stub!(
1635 TODO("https://fxbug.dev/402453955"),
1636 "[perf_event_open] implement remaining error handling"
1637 );
1638 error!(EMFILE)
1639 }
1640 }
1641}
1642#[cfg(target_arch = "aarch64")]
1644mod arch32 {
1645 pub use super::sys_perf_event_open as sys_arch32_perf_event_open;
1646}
1647
1648#[cfg(target_arch = "aarch64")]
1649pub use arch32::*;
1650
1651use crate::mm::memory::MemoryObject;
1652use crate::mm::{MemoryAccessorExt, ProtectionFlags};
1653use crate::task::CurrentTask;
1654use crate::vfs::{
1655 Anon, FdFlags, FdNumber, FileObject, FileObjectId, FileObjectState, FileOps, InputBuffer,
1656 OutputBuffer,
1657};
1658use crate::{fileops_impl_nonseekable, fileops_impl_noop_sync};
1659
1660#[cfg(test)]
1661mod tests {
1662 use super::*;
1663 use crate::task::tracing::{TracePerformanceEventManager, ZirconIdentity};
1664
1665 #[::fuchsia::test]
1666 async fn test_process_fxt_record_resolves_pid_tid() {
1667 let manager = Arc::new(TracePerformanceEventManager::new(std::sync::Weak::new()));
1668 let session = manager.open();
1669 manager.record(
1670 42,
1671 43,
1672 ZirconIdentity { process: zx::Koid::from_raw(1001), thread: zx::Koid::from_raw(1002) },
1673 );
1674
1675 let perf_data_vmo = zx::Vmo::create(ESTIMATED_MMAP_BUFFER_SIZE).unwrap();
1676 let vmo_handle_copy =
1677 perf_data_vmo.as_handle_ref().duplicate_handle(zx::Rights::SAME_RIGHTS).unwrap();
1678 let seq_lock = unsafe { create_seq_lock(&vmo_handle_copy, ESTIMATED_MMAP_BUFFER_SIZE) };
1680
1681 let sample_type = (perf_event_sample_format_PERF_SAMPLE_IP
1682 | perf_event_sample_format_PERF_SAMPLE_TID) as u64;
1683 let mut vmo_write_offset = 0;
1684 let mut lost_events = 0;
1685
1686 let mapped_record =
1688 TraceRecord::Profiler(ProfilerRecord::Backtrace(fxt::profiler::BacktraceRecord {
1689 timestamp: 1000,
1690 process: fxt::ProcessKoid(1001),
1691 thread: fxt::ThreadKoid(1002),
1692 num_records: 1,
1693 data: vec![0x12345678],
1694 }));
1695 process_fxt_record(
1696 mapped_record,
1697 &seq_lock,
1698 &perf_data_vmo,
1699 sample_type,
1700 0,
1701 0,
1702 0,
1703 0,
1704 0,
1705 Some(&session),
1706 &mut vmo_write_offset,
1707 &mut lost_events,
1708 );
1709
1710 let expected_record_size: u64 = 24;
1712 assert_eq!(vmo_write_offset, expected_record_size);
1713
1714 let metadata = seq_lock.get();
1715 assert_eq!(metadata.data_head, expected_record_size);
1716
1717 let mut record_bytes = [0u8; 24];
1718 perf_data_vmo.read(&mut record_bytes, metadata.data_offset).unwrap();
1719
1720 let record_type = u32::from_ne_bytes(record_bytes[0..4].try_into().unwrap());
1721 assert_eq!(record_type, perf_event_type_PERF_RECORD_SAMPLE);
1722
1723 let misc = u16::from_ne_bytes(record_bytes[4..6].try_into().unwrap());
1724 assert_eq!(misc, PERF_RECORD_MISC_USER as u16);
1725
1726 let size = u16::from_ne_bytes(record_bytes[6..8].try_into().unwrap());
1727 assert_eq!(size, expected_record_size as u16);
1728
1729 let ip = u64::from_ne_bytes(record_bytes[8..16].try_into().unwrap());
1730 assert_eq!(ip, 0x12345678);
1731
1732 let pid = u32::from_ne_bytes(record_bytes[16..20].try_into().unwrap());
1733 assert_eq!(pid, 42);
1734
1735 let tid = u32::from_ne_bytes(record_bytes[20..24].try_into().unwrap());
1736 assert_eq!(tid, 43);
1737
1738 let unmapped_record =
1740 TraceRecord::Profiler(ProfilerRecord::Backtrace(fxt::profiler::BacktraceRecord {
1741 timestamp: 2000,
1742 process: fxt::ProcessKoid(9999),
1743 thread: fxt::ThreadKoid(9998),
1744 num_records: 1,
1745 data: vec![0x87654321],
1746 }));
1747 process_fxt_record(
1748 unmapped_record,
1749 &seq_lock,
1750 &perf_data_vmo,
1751 sample_type,
1752 0,
1753 0,
1754 0,
1755 0,
1756 0,
1757 Some(&session),
1758 &mut vmo_write_offset,
1759 &mut lost_events,
1760 );
1761
1762 assert_eq!(vmo_write_offset, expected_record_size);
1764 let metadata = seq_lock.get();
1765 assert_eq!(metadata.data_head, expected_record_size);
1766 let mut trailing_bytes = [0u8; 24];
1767 perf_data_vmo
1768 .read(&mut trailing_bytes, metadata.data_offset + expected_record_size)
1769 .unwrap();
1770 assert_eq!(trailing_bytes, [0u8; 24]);
1771 }
1772
1773 #[::fuchsia::test]
1774 async fn test_write_record_to_vmo_regs_and_stack() {
1775 let perf_data_vmo = zx::Vmo::create(ESTIMATED_MMAP_BUFFER_SIZE).unwrap();
1776 let vmo_handle_copy =
1777 perf_data_vmo.as_handle_ref().duplicate_handle(zx::Rights::SAME_RIGHTS).unwrap();
1778 let seq_lock = unsafe { create_seq_lock(&vmo_handle_copy, ESTIMATED_MMAP_BUFFER_SIZE) };
1780 let metadata = seq_lock.get();
1781 let mut lost_events = 0;
1782 let regs_data = [0x11u8; 16];
1783 let stack_data = [0x22u8; 16];
1784 let sample = PerfRecordSample {
1785 pid: Some(10),
1786 tid: Some(20),
1787 time: zx::BootInstant::from_nanos(123_456_789),
1788 ips: vec![0xdeadbeef],
1789 regs: ®s_data,
1790 stack: &stack_data,
1791 regs_abi: perf_sample_regs_abi_PERF_SAMPLE_REGS_ABI_32 as u64,
1792 sample_regs_user: 3,
1793 sample_stack_user: 16,
1794 };
1795 let sample_type = (perf_event_sample_format_PERF_SAMPLE_IP
1796 | perf_event_sample_format_PERF_SAMPLE_TID
1797 | perf_event_sample_format_PERF_SAMPLE_TIME
1798 | perf_event_sample_format_PERF_SAMPLE_REGS_USER
1799 | perf_event_sample_format_PERF_SAMPLE_STACK_USER) as u64;
1800 let written = write_record_to_vmo(
1801 sample,
1802 &perf_data_vmo,
1803 sample_type,
1804 0,
1805 0,
1806 0,
1807 0,
1808 &metadata,
1809 &mut lost_events,
1810 );
1811 assert!(written > 0);
1812
1813 let mut record_bytes = vec![0u8; written as usize];
1814 perf_data_vmo.read(&mut record_bytes, metadata.data_offset).unwrap();
1815
1816 let record_type = u32::from_ne_bytes(record_bytes[0..4].try_into().unwrap());
1817 assert_eq!(record_type, perf_event_type_PERF_RECORD_SAMPLE);
1818 let misc = u16::from_ne_bytes(record_bytes[4..6].try_into().unwrap());
1819 assert_eq!(misc, PERF_RECORD_MISC_USER as u16);
1820 let size = u16::from_ne_bytes(record_bytes[6..8].try_into().unwrap());
1821 assert_eq!(size as usize, written as usize);
1822
1823 let ip = u64::from_ne_bytes(record_bytes[8..16].try_into().unwrap());
1824 assert_eq!(ip, 0xdeadbeef);
1825
1826 let pid = u32::from_ne_bytes(record_bytes[16..20].try_into().unwrap());
1827 assert_eq!(pid, 10);
1828 let tid = u32::from_ne_bytes(record_bytes[20..24].try_into().unwrap());
1829 assert_eq!(tid, 20);
1830
1831 let time = u64::from_ne_bytes(record_bytes[24..32].try_into().unwrap());
1832 assert_eq!(time, 123_456_789);
1833
1834 let abi = u64::from_ne_bytes(record_bytes[32..40].try_into().unwrap());
1836 assert_eq!(abi, perf_sample_regs_abi_PERF_SAMPLE_REGS_ABI_32 as u64);
1837 assert_eq!(&record_bytes[40..56], ®s_data);
1838
1839 let stack_len = u64::from_ne_bytes(record_bytes[56..64].try_into().unwrap());
1841 assert_eq!(stack_len, 16);
1842 assert_eq!(&record_bytes[64..80], &stack_data);
1843 let dyn_size = u64::from_ne_bytes(record_bytes[80..88].try_into().unwrap());
1844 assert_eq!(dyn_size, 16);
1845 }
1846
1847 #[::fuchsia::test]
1848 async fn test_write_record_to_vmo_regs_abi_64() {
1849 let perf_data_vmo = zx::Vmo::create(ESTIMATED_MMAP_BUFFER_SIZE).unwrap();
1850 let vmo_handle_copy =
1851 perf_data_vmo.as_handle_ref().duplicate_handle(zx::Rights::SAME_RIGHTS).unwrap();
1852 let seq_lock = unsafe { create_seq_lock(&vmo_handle_copy, ESTIMATED_MMAP_BUFFER_SIZE) };
1854 let metadata = seq_lock.get();
1855 let mut lost_events = 0;
1856 let regs_data = [0x44u8; 16];
1857 let stack_data = [0x55u8; 16];
1858 let sample = PerfRecordSample {
1859 pid: Some(10),
1860 tid: Some(20),
1861 time: zx::BootInstant::from_nanos(123_456_789),
1862 ips: vec![0xdeadbeef],
1863 regs: ®s_data,
1864 stack: &stack_data,
1865 regs_abi: perf_sample_regs_abi_PERF_SAMPLE_REGS_ABI_64 as u64,
1866 sample_regs_user: 3,
1867 sample_stack_user: 16,
1868 };
1869 let sample_type = (perf_event_sample_format_PERF_SAMPLE_IP
1870 | perf_event_sample_format_PERF_SAMPLE_TID
1871 | perf_event_sample_format_PERF_SAMPLE_TIME
1872 | perf_event_sample_format_PERF_SAMPLE_REGS_USER
1873 | perf_event_sample_format_PERF_SAMPLE_STACK_USER) as u64;
1874 let written = write_record_to_vmo(
1875 sample,
1876 &perf_data_vmo,
1877 sample_type,
1878 0,
1879 0,
1880 0,
1881 0,
1882 &metadata,
1883 &mut lost_events,
1884 );
1885 assert!(written > 0);
1886
1887 let mut record_bytes = vec![0u8; written as usize];
1888 perf_data_vmo.read(&mut record_bytes, metadata.data_offset).unwrap();
1889
1890 let abi = u64::from_ne_bytes(record_bytes[32..40].try_into().unwrap());
1892 assert_eq!(abi, perf_sample_regs_abi_PERF_SAMPLE_REGS_ABI_64 as u64);
1893 assert_eq!(&record_bytes[40..56], ®s_data);
1894 }
1895
1896 #[::fuchsia::test]
1897 async fn test_write_record_to_vmo_regs_abi_none_when_empty() {
1898 let perf_data_vmo = zx::Vmo::create(ESTIMATED_MMAP_BUFFER_SIZE).unwrap();
1899 let vmo_handle_copy =
1900 perf_data_vmo.as_handle_ref().duplicate_handle(zx::Rights::SAME_RIGHTS).unwrap();
1901 let seq_lock = unsafe { create_seq_lock(&vmo_handle_copy, ESTIMATED_MMAP_BUFFER_SIZE) };
1903 let metadata = seq_lock.get();
1904 let mut lost_events = 0;
1905 let stack_data = [0x33u8; 8];
1906 let sample = PerfRecordSample {
1907 pid: Some(10),
1908 tid: Some(20),
1909 time: zx::BootInstant::from_nanos(999),
1910 ips: vec![0x1000],
1911 regs: &[],
1912 stack: &stack_data,
1913 regs_abi: perf_sample_regs_abi_PERF_SAMPLE_REGS_ABI_NONE as u64,
1914 sample_regs_user: 3,
1915 sample_stack_user: 8,
1916 };
1917 let sample_type = (perf_event_sample_format_PERF_SAMPLE_IP
1918 | perf_event_sample_format_PERF_SAMPLE_TID
1919 | perf_event_sample_format_PERF_SAMPLE_TIME
1920 | perf_event_sample_format_PERF_SAMPLE_REGS_USER
1921 | perf_event_sample_format_PERF_SAMPLE_STACK_USER) as u64;
1922 let written = write_record_to_vmo(
1923 sample,
1924 &perf_data_vmo,
1925 sample_type,
1926 0,
1927 0,
1928 0,
1929 0,
1930 &metadata,
1931 &mut lost_events,
1932 );
1933 assert!(written > 0);
1934
1935 let mut record_bytes = vec![0u8; written as usize];
1936 perf_data_vmo.read(&mut record_bytes, metadata.data_offset).unwrap();
1937
1938 let abi = u64::from_ne_bytes(record_bytes[32..40].try_into().unwrap());
1940 assert_eq!(abi, perf_sample_regs_abi_PERF_SAMPLE_REGS_ABI_NONE as u64);
1941
1942 let stack_len = u64::from_ne_bytes(record_bytes[40..48].try_into().unwrap());
1944 assert_eq!(stack_len, 8);
1945 assert_eq!(&record_bytes[48..56], &stack_data);
1946 let dyn_size = u64::from_ne_bytes(record_bytes[56..64].try_into().unwrap());
1947 assert_eq!(dyn_size, 8);
1948 }
1949
1950 const STARNIX_PC: u64 = GUEST_ASPACE_END + 0x2222_0558;
1954 const STARNIX_SP: u64 = GUEST_ASPACE_END + 0x4000_1000;
1955 const RESTRICTED_STATE_ADDR: u64 = GUEST_ASPACE_END + 0x5000_0000;
1956 const GUEST_PC: u64 = 0x7a00_1234;
1957 const GUEST_SP: u64 = 0x7f00_1000;
1958 const ALL_REGS_MASK: u64 = (1 << 33) - 1;
1959 const SAMPLE_STACK_USER: u64 = 256;
1960
1961 fn u64s_to_bytes(values: &[u64]) -> Vec<u8> {
1962 values.iter().flat_map(|value| value.to_ne_bytes()).collect()
1963 }
1964
1965 fn read_u64(bytes: &[u8], pos: &mut usize) -> u64 {
1966 let value = u64::from_ne_bytes(bytes[*pos..*pos + 8].try_into().unwrap());
1967 *pos += 8;
1968 value
1969 }
1970
1971 fn thread_state_regs(pc: u64, sp: u64, cpsr: u64) -> Vec<u64> {
1974 let mut regs: Vec<u64> = (0..31).map(|i| 0xa000 + i).collect();
1975 regs.extend([sp, pc, cpsr, 0x7777]);
1976 regs
1977 }
1978
1979 fn stack_sample_blob(thread_regs: &[u64], chunks: &[(u64, &[u8])]) -> Vec<u8> {
1980 let regs_bytes = u64s_to_bytes(thread_regs);
1981 let mut blob = (regs_bytes.len() as u64).to_ne_bytes().to_vec();
1982 blob.extend(regs_bytes);
1983 for (base, data) in chunks {
1984 blob.extend(base.to_ne_bytes());
1985 blob.extend((data.len() as u64).to_ne_bytes());
1986 blob.extend_from_slice(data);
1987 }
1988 blob
1989 }
1990
1991 #[cfg(target_arch = "aarch64")]
1994 fn restricted_state_bytes(x: &[u64; 31], sp: u64, pc: u64, cpsr: u32) -> Vec<u8> {
1995 use zx::sys::zx_restricted_state_t;
1996 let mut state = vec![0u8; std::mem::size_of::<zx_restricted_state_t>()];
1997 state[..31 * 8].copy_from_slice(&u64s_to_bytes(x));
1998 let sp_offset = std::mem::offset_of!(zx_restricted_state_t, sp);
1999 state[sp_offset..sp_offset + 8].copy_from_slice(&sp.to_ne_bytes());
2000 let pc_offset = std::mem::offset_of!(zx_restricted_state_t, pc);
2001 state[pc_offset..pc_offset + 8].copy_from_slice(&pc.to_ne_bytes());
2002 let cpsr_offset = std::mem::offset_of!(zx_restricted_state_t, cpsr);
2003 state[cpsr_offset..cpsr_offset + 4].copy_from_slice(&cpsr.to_ne_bytes());
2004 state
2005 }
2006
2007 fn stack_bytes(seed: u8, len: usize) -> Vec<u8> {
2008 (0..len).map(|i| seed.wrapping_add(i as u8)).collect()
2009 }
2010
2011 struct ParsedStackSample {
2012 ip: u64,
2013 pid: u32,
2014 tid: u32,
2015 callchain: Vec<u64>,
2016 abi: u64,
2017 regs: Vec<u64>,
2018 stack: Vec<u8>,
2019 }
2020
2021 fn process_stack_sample(blob: Vec<u8>, sample_regs_user: u64) -> ParsedStackSample {
2024 let manager = Arc::new(TracePerformanceEventManager::new(std::sync::Weak::new()));
2025 let session = manager.open();
2026 manager.record(
2027 42,
2028 43,
2029 ZirconIdentity { process: zx::Koid::from_raw(1001), thread: zx::Koid::from_raw(1002) },
2030 );
2031 let perf_data_vmo = zx::Vmo::create(ESTIMATED_MMAP_BUFFER_SIZE).unwrap();
2032 let vmo_handle_copy =
2033 perf_data_vmo.as_handle_ref().duplicate_handle(zx::Rights::SAME_RIGHTS).unwrap();
2034 let seq_lock = unsafe { create_seq_lock(&vmo_handle_copy, ESTIMATED_MMAP_BUFFER_SIZE) };
2036 let sample_type = (perf_event_sample_format_PERF_SAMPLE_IP
2037 | perf_event_sample_format_PERF_SAMPLE_TID
2038 | perf_event_sample_format_PERF_SAMPLE_CALLCHAIN
2039 | perf_event_sample_format_PERF_SAMPLE_REGS_USER
2040 | perf_event_sample_format_PERF_SAMPLE_STACK_USER) as u64;
2041 let record = TraceRecord::LargeBlob(fxt::LargeBlobRecord {
2042 provider: None,
2043 ty: fxt::BlobType::Data,
2044 category: "cpu_profiler".into(),
2045 name: "stack_sample".into(),
2046 bytes: blob,
2047 metadata: Some(fxt::LargeBlobMetadata {
2048 timestamp: 1000,
2049 process: fxt::ProcessKoid(1001),
2050 thread: fxt::ThreadKoid(1002),
2051 args: vec![],
2052 }),
2053 });
2054 let mut vmo_write_offset = 0;
2055 let mut lost_events = 0;
2056 process_fxt_record(
2057 record,
2058 &seq_lock,
2059 &perf_data_vmo,
2060 sample_type,
2061 0,
2062 0,
2063 0,
2064 sample_regs_user,
2065 SAMPLE_STACK_USER,
2066 Some(&session),
2067 &mut vmo_write_offset,
2068 &mut lost_events,
2069 );
2070 assert!(vmo_write_offset > 0, "no record written");
2071 assert_eq!(lost_events, 0);
2072
2073 let mut record = vec![0u8; vmo_write_offset as usize];
2074 perf_data_vmo.read(&mut record, seq_lock.get().data_offset).unwrap();
2075 assert_eq!(
2076 u32::from_ne_bytes(record[0..4].try_into().unwrap()),
2077 perf_event_type_PERF_RECORD_SAMPLE
2078 );
2079 assert_eq!(u16::from_ne_bytes(record[6..8].try_into().unwrap()) as usize, record.len());
2080
2081 let mut pos = 8;
2082 let ip = read_u64(&record, &mut pos);
2083 let pid = u32::from_ne_bytes(record[pos..pos + 4].try_into().unwrap());
2084 let tid = u32::from_ne_bytes(record[pos + 4..pos + 8].try_into().unwrap());
2085 pos += 8;
2086 let nr = read_u64(&record, &mut pos);
2087 let callchain = (0..nr).map(|_| read_u64(&record, &mut pos)).collect();
2088 let abi = read_u64(&record, &mut pos);
2089 let regs = if abi == perf_sample_regs_abi_PERF_SAMPLE_REGS_ABI_NONE as u64 {
2090 vec![]
2091 } else {
2092 (0..sample_regs_user.count_ones()).map(|_| read_u64(&record, &mut pos)).collect()
2093 };
2094 let stack_size = read_u64(&record, &mut pos) as usize;
2095 assert_eq!(stack_size, SAMPLE_STACK_USER as usize);
2096 let stack_region = record[pos..pos + stack_size].to_vec();
2097 pos += stack_size;
2098 let dyn_size = read_u64(&record, &mut pos) as usize;
2099 assert_eq!(pos, record.len());
2100 ParsedStackSample {
2101 ip,
2102 pid,
2103 tid,
2104 callchain,
2105 abi,
2106 regs,
2107 stack: stack_region[..dyn_size].to_vec(),
2108 }
2109 }
2110
2111 fn assert_reports_thread_state(sample: &ParsedStackSample, stack: &[u8]) {
2114 assert_eq!((sample.pid, sample.tid), (42, 43));
2115 assert_eq!(sample.ip, STARNIX_PC);
2116 assert_eq!(
2117 sample.callchain,
2118 vec![
2119 perf_callchain_context_PERF_CONTEXT_KERNEL,
2120 STARNIX_PC,
2121 perf_callchain_context_PERF_CONTEXT_USER,
2122 ]
2123 );
2124 assert_eq!(sample.abi, perf_sample_regs_abi_PERF_SAMPLE_REGS_ABI_64 as u64);
2125 assert_eq!(sample.regs, thread_state_regs(STARNIX_PC, STARNIX_SP, 0)[..33].to_vec());
2126 assert_eq!(sample.stack, stack);
2127 }
2128
2129 #[cfg(target_arch = "aarch64")]
2130 #[::fuchsia::test]
2131 async fn test_stack_sample_in_starnix_reports_guest_regs_and_stack() {
2132 let starnix_stack = stack_bytes(0x10, 64);
2133 let guest_x: [u64; 31] = std::array::from_fn(|i| 0xb000 + i as u64);
2134 let restricted_state = restricted_state_bytes(&guest_x, GUEST_SP, GUEST_PC, 0);
2135 let guest_stack = stack_bytes(0x80, 48);
2136 let blob = stack_sample_blob(
2137 &thread_state_regs(STARNIX_PC, STARNIX_SP, 0),
2138 &[
2139 (STARNIX_SP, &starnix_stack),
2140 (RESTRICTED_STATE_ADDR, &restricted_state),
2141 (GUEST_SP, &guest_stack),
2142 ],
2143 );
2144
2145 let sample = process_stack_sample(blob, ALL_REGS_MASK);
2146
2147 assert_eq!((sample.pid, sample.tid), (42, 43));
2148 assert_eq!(sample.ip, STARNIX_PC);
2151 assert_eq!(
2152 sample.callchain,
2153 vec![
2154 perf_callchain_context_PERF_CONTEXT_KERNEL,
2155 STARNIX_PC,
2156 perf_callchain_context_PERF_CONTEXT_USER,
2157 GUEST_PC,
2158 ]
2159 );
2160 assert_eq!(sample.abi, perf_sample_regs_abi_PERF_SAMPLE_REGS_ABI_64 as u64);
2161 let mut expected_regs = guest_x.to_vec();
2162 expected_regs.extend([GUEST_SP, GUEST_PC]);
2163 assert_eq!(sample.regs, expected_regs);
2164 assert_eq!(sample.stack, guest_stack);
2165 }
2166
2167 #[cfg(target_arch = "aarch64")]
2168 #[::fuchsia::test]
2169 async fn test_stack_sample_in_starnix_reports_aarch32_guest_regs() {
2170 const GUEST_PC32: u64 = 0x0040_1234;
2171 const GUEST_SP32: u64 = 0xff80_1000;
2172 let starnix_stack = stack_bytes(0x10, 64);
2173 let mut guest_x: [u64; 31] = std::array::from_fn(|i| 0xc000 + i as u64);
2174 guest_x[AARCH32_REG_SP] = GUEST_SP32;
2175 let restricted_state = restricted_state_bytes(
2178 &guest_x,
2179 0xdead_0000,
2180 GUEST_PC32,
2181 zx::sys::ZX_REG_CPSR_ARCH_32_MASK as u32,
2182 );
2183 let guest_stack = stack_bytes(0x40, 32);
2184 let blob = stack_sample_blob(
2185 &thread_state_regs(STARNIX_PC, STARNIX_SP, 0),
2186 &[
2187 (STARNIX_SP, &starnix_stack),
2188 (RESTRICTED_STATE_ADDR, &restricted_state),
2189 (GUEST_SP32, &guest_stack),
2190 ],
2191 );
2192
2193 let sample = process_stack_sample(blob, ALL_REGS_MASK);
2194
2195 assert_eq!(sample.abi, perf_sample_regs_abi_PERF_SAMPLE_REGS_ABI_32 as u64);
2196 assert_eq!(
2197 sample.callchain,
2198 vec![
2199 perf_callchain_context_PERF_CONTEXT_KERNEL,
2200 STARNIX_PC,
2201 perf_callchain_context_PERF_CONTEXT_USER,
2202 GUEST_PC32,
2203 ]
2204 );
2205 assert_eq!(sample.regs.len(), 33);
2206 assert_eq!(sample.regs[AARCH32_REG_SP], GUEST_SP32);
2207 assert_eq!(sample.regs[AARCH32_REG_PC], GUEST_PC32);
2208 assert_eq!(sample.regs[AARCH64_REG_PC], GUEST_PC32);
2209 assert_eq!(sample.regs[AARCH64_REG_SP], GUEST_SP32);
2211 assert_eq!(sample.regs[AARCH64_REG_LR], guest_x[AARCH32_REG_LR]);
2212 assert_eq!(sample.regs[0], 0xc000);
2213 assert_eq!(sample.stack, guest_stack);
2214 }
2215
2216 #[::fuchsia::test]
2217 async fn test_stack_sample_in_guest_reports_thread_regs() {
2218 let guest_stack = stack_bytes(0x20, 64);
2222 let stale_stack = stack_bytes(0x90, 64);
2223 const STALE_SP: u64 = GUEST_SP + 0x10000;
2224 #[cfg(target_arch = "aarch64")]
2225 let stale_state = restricted_state_bytes(&[0x5a; 31], STALE_SP, GUEST_PC + 0x100, 0);
2226 #[cfg(not(target_arch = "aarch64"))]
2227 let stale_state = vec![0x5au8; 280];
2228 let thread_regs = thread_state_regs(GUEST_PC, GUEST_SP + 16, 0);
2229 let blob = stack_sample_blob(
2230 &thread_regs,
2231 &[
2232 (GUEST_SP, &guest_stack),
2233 (RESTRICTED_STATE_ADDR, &stale_state),
2234 (STALE_SP, &stale_stack),
2235 ],
2236 );
2237
2238 let sample = process_stack_sample(blob, ALL_REGS_MASK);
2239
2240 assert_eq!(sample.ip, GUEST_PC);
2241 assert_eq!(
2242 sample.callchain,
2243 vec![
2244 perf_callchain_context_PERF_CONTEXT_KERNEL,
2245 perf_callchain_context_PERF_CONTEXT_USER,
2246 GUEST_PC,
2247 ]
2248 );
2249 assert_eq!(sample.abi, perf_sample_regs_abi_PERF_SAMPLE_REGS_ABI_64 as u64);
2250 assert_eq!(sample.regs, thread_regs[..33].to_vec());
2251 assert_eq!(sample.stack, guest_stack[16..].to_vec());
2253 }
2254
2255 #[cfg(target_arch = "aarch64")]
2256 #[::fuchsia::test]
2257 async fn test_stack_sample_in_guest_aarch32_reports_sp_lr_from_r13_r14() {
2258 const GUEST_PC32: u64 = 0x0040_1234;
2262 const GUEST_SP32: u64 = 0xff80_1000;
2263 const GUEST_LR32: u64 = 0x0040_5678;
2264 const STALE_SP: u64 = 0xdead_0000;
2265 const STALE_LR: u64 = 0xbeef_0000;
2266 let guest_stack = stack_bytes(0x30, 64);
2267 let mut thread_regs =
2268 thread_state_regs(GUEST_PC32, STALE_SP, zx::sys::ZX_REG_CPSR_ARCH_32_MASK as u64);
2269 thread_regs[AARCH32_REG_SP] = GUEST_SP32;
2270 thread_regs[AARCH32_REG_LR] = GUEST_LR32;
2271 thread_regs[AARCH64_REG_LR] = STALE_LR;
2272 let blob = stack_sample_blob(&thread_regs, &[(GUEST_SP32, &guest_stack)]);
2273
2274 let sample = process_stack_sample(blob, ALL_REGS_MASK);
2275
2276 assert_eq!(sample.ip, GUEST_PC32);
2277 assert_eq!(
2278 sample.callchain,
2279 vec![
2280 perf_callchain_context_PERF_CONTEXT_KERNEL,
2281 perf_callchain_context_PERF_CONTEXT_USER,
2282 GUEST_PC32,
2283 ]
2284 );
2285 assert_eq!(sample.abi, perf_sample_regs_abi_PERF_SAMPLE_REGS_ABI_32 as u64);
2286 assert_eq!(sample.regs.len(), 33);
2287 assert_eq!(sample.regs[AARCH64_REG_SP], GUEST_SP32);
2288 assert_eq!(sample.regs[AARCH64_REG_LR], GUEST_LR32);
2289 assert_eq!(sample.regs[AARCH32_REG_SP], GUEST_SP32);
2290 assert_eq!(sample.regs[AARCH32_REG_LR], GUEST_LR32);
2291 assert_eq!(sample.regs[AARCH32_REG_PC], GUEST_PC32);
2292 assert_eq!(sample.regs[AARCH64_REG_PC], GUEST_PC32);
2293 assert_eq!(sample.regs[0], 0xa000);
2294 assert_eq!(sample.stack, guest_stack);
2296 }
2297
2298 #[::fuchsia::test]
2299 async fn test_stack_sample_in_starnix_without_restricted_state_reports_thread_regs() {
2300 let starnix_stack = stack_bytes(0x10, 64);
2301 let thread_regs = thread_state_regs(STARNIX_PC, STARNIX_SP, 0);
2302 let blob = stack_sample_blob(&thread_regs, &[(STARNIX_SP, &starnix_stack)]);
2303
2304 let sample = process_stack_sample(blob, ALL_REGS_MASK);
2305
2306 assert_reports_thread_state(&sample, &starnix_stack);
2307 }
2308
2309 #[::fuchsia::test]
2310 async fn test_stack_sample_in_starnix_without_captures_is_reported() {
2311 let thread_regs = thread_state_regs(STARNIX_PC, STARNIX_SP, 0);
2312 let blob = stack_sample_blob(&thread_regs, &[]);
2313
2314 let sample = process_stack_sample(blob, ALL_REGS_MASK);
2315
2316 assert_reports_thread_state(&sample, &[]);
2317 }
2318
2319 #[cfg(target_arch = "aarch64")]
2320 #[::fuchsia::test]
2321 async fn test_stack_sample_in_starnix_ignores_unusable_restricted_state() {
2322 let starnix_stack = stack_bytes(0x10, 64);
2323 let guest_stack = stack_bytes(0x80, 48);
2324 let guest_x: [u64; 31] = std::array::from_fn(|i| 0xb000 + i as u64);
2325 let valid_state = restricted_state_bytes(&guest_x, GUEST_SP, GUEST_PC, 0);
2326 let cases: [(&str, u64, Vec<u8>); 6] = [
2327 (
2328 "pc above the guest address space",
2329 RESTRICTED_STATE_ADDR,
2330 restricted_state_bytes(&guest_x, GUEST_SP, STARNIX_PC, 0),
2331 ),
2332 (
2333 "pc below the guest address space",
2334 RESTRICTED_STATE_ADDR,
2335 restricted_state_bytes(&guest_x, GUEST_SP, 0x1000, 0),
2336 ),
2337 (
2338 "sp above the guest address space",
2339 RESTRICTED_STATE_ADDR,
2340 restricted_state_bytes(&guest_x, STARNIX_SP, GUEST_PC, 0),
2341 ),
2342 ("not yet written", RESTRICTED_STATE_ADDR, restricted_state_bytes(&[0; 31], 0, 0, 0)),
2343 ("not page-aligned", RESTRICTED_STATE_ADDR + 8, valid_state.clone()),
2344 ("short read", RESTRICTED_STATE_ADDR, valid_state[..valid_state.len() - 8].to_vec()),
2345 ];
2346 for (name, state_addr, state) in cases {
2347 let blob = stack_sample_blob(
2348 &thread_state_regs(STARNIX_PC, STARNIX_SP, 0),
2349 &[(STARNIX_SP, &starnix_stack), (state_addr, &state), (GUEST_SP, &guest_stack)],
2350 );
2351
2352 let sample = process_stack_sample(blob, ALL_REGS_MASK);
2353
2354 println!("case: {name}");
2355 assert_reports_thread_state(&sample, &starnix_stack);
2356 }
2357 }
2358
2359 #[cfg(target_arch = "aarch64")]
2360 #[::fuchsia::test]
2361 async fn test_stack_sample_in_starnix_without_guest_stack_reports_guest_regs() {
2362 let starnix_stack = stack_bytes(0x10, 64);
2366 let guest_x: [u64; 31] = std::array::from_fn(|i| 0xb000 + i as u64);
2367 let restricted_state = restricted_state_bytes(&guest_x, GUEST_SP, GUEST_PC, 0);
2368 let thread_regs = thread_state_regs(STARNIX_PC, STARNIX_SP, 0);
2369 let blob = stack_sample_blob(
2370 &thread_regs,
2371 &[(STARNIX_SP, &starnix_stack), (RESTRICTED_STATE_ADDR, &restricted_state)],
2372 );
2373
2374 let sample = process_stack_sample(blob, ALL_REGS_MASK);
2375
2376 assert_eq!((sample.pid, sample.tid), (42, 43));
2377 assert_eq!(sample.ip, STARNIX_PC);
2378 assert_eq!(
2379 sample.callchain,
2380 vec![
2381 perf_callchain_context_PERF_CONTEXT_KERNEL,
2382 STARNIX_PC,
2383 perf_callchain_context_PERF_CONTEXT_USER,
2384 GUEST_PC,
2385 ]
2386 );
2387 assert_eq!(sample.abi, perf_sample_regs_abi_PERF_SAMPLE_REGS_ABI_64 as u64);
2388 let mut expected_regs = guest_x.to_vec();
2389 expected_regs.extend([GUEST_SP, GUEST_PC]);
2390 assert_eq!(sample.regs, expected_regs);
2391 assert!(sample.stack.is_empty());
2392 }
2393
2394 #[cfg(target_arch = "aarch64")]
2395 #[::fuchsia::test]
2396 async fn test_stack_sample_in_starnix_with_uncovered_guest_sp_reports_guest_regs() {
2397 let starnix_stack = stack_bytes(0x10, 64);
2400 let other_stack = stack_bytes(0x80, 48);
2401 let guest_x: [u64; 31] = std::array::from_fn(|i| 0xb000 + i as u64);
2402 let restricted_state = restricted_state_bytes(&guest_x, GUEST_SP + 0x10000, GUEST_PC, 0);
2403 let blob = stack_sample_blob(
2404 &thread_state_regs(STARNIX_PC, STARNIX_SP, 0),
2405 &[
2406 (STARNIX_SP, &starnix_stack),
2407 (RESTRICTED_STATE_ADDR, &restricted_state),
2408 (GUEST_SP, &other_stack),
2409 ],
2410 );
2411
2412 let sample = process_stack_sample(blob, ALL_REGS_MASK);
2413
2414 assert_eq!(sample.abi, perf_sample_regs_abi_PERF_SAMPLE_REGS_ABI_64 as u64);
2415 assert_eq!(sample.regs[AARCH64_REG_SP], GUEST_SP + 0x10000);
2416 assert_eq!(sample.regs[AARCH64_REG_PC], GUEST_PC);
2417 assert!(sample.stack.is_empty());
2418 assert_eq!(
2419 sample.callchain,
2420 vec![
2421 perf_callchain_context_PERF_CONTEXT_KERNEL,
2422 STARNIX_PC,
2423 perf_callchain_context_PERF_CONTEXT_USER,
2424 GUEST_PC,
2425 ]
2426 );
2427 }
2428
2429 #[::fuchsia::test]
2430 async fn test_stack_sample_in_guest_without_stack_capture_is_reported() {
2431 let thread_regs = thread_state_regs(GUEST_PC, GUEST_SP, 0);
2432 let blob = stack_sample_blob(&thread_regs, &[]);
2433
2434 let sample = process_stack_sample(blob, ALL_REGS_MASK);
2435
2436 assert_eq!(
2437 sample.callchain,
2438 vec![
2439 perf_callchain_context_PERF_CONTEXT_KERNEL,
2440 perf_callchain_context_PERF_CONTEXT_USER,
2441 GUEST_PC,
2442 ]
2443 );
2444 assert_eq!(sample.abi, perf_sample_regs_abi_PERF_SAMPLE_REGS_ABI_64 as u64);
2445 assert_eq!(sample.regs, thread_regs[..33].to_vec());
2446 assert!(sample.stack.is_empty());
2447 }
2448
2449 #[::fuchsia::test]
2450 async fn test_callgraph_config_stack_size() {
2451 let callchain_only =
2452 callgraph_config(perf_event_sample_format_PERF_SAMPLE_CALLCHAIN as u64, 65280);
2453 assert_eq!(callchain_only.strategy, Some(profiler::CallgraphStrategy::FramePointer));
2454 assert_eq!(callchain_only.stack_size, None);
2455
2456 let regs_only =
2457 callgraph_config(perf_event_sample_format_PERF_SAMPLE_REGS_USER as u64, 65280);
2458 assert_eq!(regs_only.strategy, Some(profiler::CallgraphStrategy::Dwarf));
2459 assert_eq!(regs_only.stack_size, None);
2460
2461 let zero_stack =
2462 callgraph_config(perf_event_sample_format_PERF_SAMPLE_STACK_USER as u64, 0);
2463 assert_eq!(zero_stack.strategy, Some(profiler::CallgraphStrategy::Dwarf));
2464 assert_eq!(zero_stack.stack_size, Some(0));
2465
2466 let custom_stack =
2467 callgraph_config(perf_event_sample_format_PERF_SAMPLE_STACK_USER as u64, 65280);
2468 assert_eq!(custom_stack.strategy, Some(profiler::CallgraphStrategy::Dwarf));
2469 assert_eq!(custom_stack.stack_size, Some(65280));
2470 }
2471}