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 let read_format_data = {
303 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 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 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 Ok(unsafe { create_seq_lock(&vmo_copy, buffer_size) })
468 })
469 .as_ref()
470 .map_err(|e| e.clone())?;
471
472 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 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
515fn 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 let mut sample = Vec::<u8>::new();
559 if (sample_type & perf_event_sample_format_PERF_SAMPLE_IDENTIFIER as u64) != 0 {
561 sample.extend(sample_id.to_ne_bytes());
562 }
563 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 sample.extend(perf_record_sample.pid.unwrap_or(0).to_ne_bytes());
572 sample.extend(perf_record_sample.tid.unwrap_or(0).to_ne_bytes());
574 }
575
576 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 if (sample_type & perf_event_sample_format_PERF_SAMPLE_ID as u64) != 0 {
583 sample.extend(sample_id.to_ne_bytes());
584 }
585
586 if (sample_type & perf_event_sample_format_PERF_SAMPLE_PERIOD as u64) != 0 {
588 sample.extend(sample_period.to_ne_bytes());
589 }
590
591 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 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 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 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 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 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 sample.resize(sample.len() + (dest_len - data_len), 0);
670 sample.extend((data_len as u64).to_ne_bytes());
672 }
673 }
674 let record_len = std::mem::size_of::<perf_event_header>() + sample.len();
680 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 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 type_: perf_event_type_PERF_RECORD_SAMPLE,
703 misc: PERF_RECORD_MISC_USER as u16,
704 size: record_size,
705 };
706
707 let free_space =
712 (metadata.data_tail.wrapping_sub(head).wrapping_sub(1)) & (ring_buffer_size - 1);
713
714 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 *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 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
767fn 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#[derive(Debug, Clone)]
794struct PerfRecordSample<'a> {
795 pid: Option<u32>,
796 tid: Option<u32>,
797 time: zx::BootInstant,
799 ips: Vec<u64>,
801 regs: &'a [u8],
802 stack: &'a [u8],
803 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 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 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 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#[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#[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 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
925fn 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 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 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 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 if regs_size < REGS_BYTES + 8 {
1019 return;
1020 }
1021 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 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 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 let mut blob_regs = Cow::Borrowed(regs);
1062 if is_32bit {
1063 let blob_regs = blob_regs.to_mut();
1064 let r15_offset = AARCH32_REG_PC * 8;
1072 blob_regs.copy_within(pc_offset..pc_offset + 8, r15_offset);
1073 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 read_reg(AARCH32_REG_SP)
1092 } else {
1093 read_reg(AARCH64_REG_SP)
1094 };
1095 let pc = read_reg(AARCH64_REG_PC);
1096
1097 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 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 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
1167fn sampling_period(attr: &perf_event_attr) -> Option<zx::MonotonicDuration> {
1171 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
1185fn 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 let _ = profiling_complete_receiver.recv().unwrap();
1210}
1211
1212unsafe fn create_seq_lock(
1221 vmo_handle_ref: &zx::NullableHandle,
1222 buffer_size: u64,
1223) -> SeqLock<PerfMetadataHeader, PerfMetadataValue> {
1224 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 data_head: 0,
1246 data_tail: 0,
1248 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 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 tid: tid_t,
1276 cpu: i32,
1277 group_fd: FdNumber,
1278 _flags: u64,
1279) -> Result<SyscallResult, Errno> {
1280 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 security::check_perf_event_open_access(current_task, PerfEventOpenType::Open)?;
1309
1310 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 let is_exempt = matches!(
1322 (event_type, perf_event_attrs.exclude_kernel(), &target),
1323 (PerfEventType::Tracepoint, 1, PerfEventTarget::CurrentTask)
1324 );
1325 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 if perf_event_attrs.exclude_kernel() == 0 {
1336 security::check_perf_event_open_access(current_task, PerfEventOpenType::Kernel)?;
1337 }
1338
1339 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 if event_type == PerfEventType::Tracepoint {
1360 security::check_perf_event_open_access(current_task, PerfEventOpenType::Tracepoint)?;
1361 }
1362
1363 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 if perf_event_file.disabled == 0 {
1385 perf_event_file.most_recent_enabled_time =
1386 zx::MonotonicInstant::get().into_nanos() as u64;
1387 }
1388 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(¤t_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 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 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 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 while let Some((command, profiling_complete_receiver)) = receiver.next().await {
1448 if command != IoctlOp::Enable {
1450 let _ = profiling_complete_receiver.send(());
1451 continue;
1452 }
1453
1454 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 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 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 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 match receiver.next().await {
1523 Some((IoctlOp::Disable, ack)) => break Some(ack),
1524 Some((IoctlOp::Enable, ack)) => {
1525 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 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 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 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 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 log_warn!("[perf_event_open] Error parsing FXT: {:?}", e);
1587 break;
1588 }
1589 }
1590 }
1591 }
1592 drop(stream);
1595 drop(_parser_task);
1596 };
1597 futures::join!(stop_and_reset, drain);
1598
1599 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 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(¤t_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#[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 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 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 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 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 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 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: ®s_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 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], ®s_data);
1839
1840 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 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: ®s_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 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], ®s_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 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 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 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 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 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 #[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 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 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 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 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 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 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 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 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 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 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 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 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}