1use crate::sys::{self as sys, PadByte, ZX_OBJ_TYPE_UPPER_BOUND, zx_handle_t};
8use crate::{
9 AsHandleRef, HandleBased, HandleRef, Job, Koid, MapInfo, MonotonicInstant, Name,
10 NullableHandle, ObjectQuery, Property, PropertyQuery, Rights, Status, Task, Thread, Topic,
11 Vmar, VmoInfo, ok,
12};
13use bitflags::bitflags;
14use static_assertions::const_assert_eq;
15use std::mem::MaybeUninit;
16use zerocopy::{FromBytes, Immutable, KnownLayout};
17
18bitflags! {
19 #[repr(transparent)]
21 #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
22 pub struct ProcessOptions: u32 {
23 const SHARED = sys::ZX_PROCESS_SHARED;
24 }
25}
26
27impl Default for ProcessOptions {
28 fn default() -> Self {
29 ProcessOptions::empty()
30 }
31}
32
33#[repr(transparent)]
34#[derive(
35 Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, FromBytes, Immutable, KnownLayout,
36)]
37pub struct ProcessInfoFlags(u32);
38
39bitflags! {
40 impl ProcessInfoFlags: u32 {
41 const STARTED = sys::ZX_INFO_PROCESS_FLAG_STARTED;
42 const EXITED = sys::ZX_INFO_PROCESS_FLAG_EXITED;
43 const DEBUGGER_ATTACHED = sys::ZX_INFO_PROCESS_FLAG_DEBUGGER_ATTACHED;
44 }
45}
46
47#[derive(Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
51#[repr(transparent)]
52pub struct Process(NullableHandle);
53impl_handle_based!(Process);
54unsafe_handle_properties!(object: Process,
55 props: [
56 {query_ty: PROCESS_DEBUG_ADDR, tag: ProcessDebugAddrTag, prop_ty: u64, get:get_debug_addr, set:set_debug_addr},
57 {query_ty: PROCESS_BREAK_ON_LOAD, tag: ProcessBreakOnLoadTag, prop_ty: u64, get:get_break_on_load, set:set_break_on_load},
58 ]
59);
60
61#[repr(C)]
62#[derive(Clone, Copy, Debug, PartialEq, Eq, FromBytes, Immutable, KnownLayout)]
63pub struct ProcessInfo {
64 pub return_code: i64,
65 pub start_time: MonotonicInstant,
66 pub flags: ProcessInfoFlags,
67
68 _pad: [PadByte; 4],
70}
71
72impl ProcessInfo {
73 pub fn new(return_code: i64, start_time: MonotonicInstant, flags: ProcessInfoFlags) -> Self {
74 Self { return_code, start_time, flags, _pad: [PadByte::default(); 4] }
75 }
76}
77
78const_assert_eq!(std::mem::size_of::<ProcessInfo>(), std::mem::size_of::<sys::zx_info_process_t>());
80const_assert_eq!(
81 std::mem::offset_of!(ProcessInfo, return_code),
82 std::mem::offset_of!(sys::zx_info_process_t, return_code)
83);
84const_assert_eq!(
85 std::mem::offset_of!(ProcessInfo, start_time),
86 std::mem::offset_of!(sys::zx_info_process_t, start_time)
87);
88const_assert_eq!(
89 std::mem::offset_of!(ProcessInfo, flags),
90 std::mem::offset_of!(sys::zx_info_process_t, flags)
91);
92
93unsafe impl ObjectQuery for ProcessInfo {
95 const TOPIC: Topic = Topic::PROCESS;
96 type InfoTy = ProcessInfo;
97}
98
99struct ProcessThreadsInfo;
100
101unsafe impl ObjectQuery for ProcessThreadsInfo {
103 const TOPIC: Topic = Topic::PROCESS_THREADS;
104 type InfoTy = Koid;
105}
106
107sys::zx_info_task_stats_t!(TaskStatsInfo);
108
109impl From<sys::zx_info_task_stats_t> for TaskStatsInfo {
110 fn from(
111 sys::zx_info_task_stats_t {
112 mem_mapped_bytes,
113 mem_private_bytes,
114 mem_shared_bytes,
115 mem_scaled_shared_bytes,
116 mem_fractional_scaled_shared_bytes,
117 }: sys::zx_info_task_stats_t,
118 ) -> TaskStatsInfo {
119 TaskStatsInfo {
120 mem_mapped_bytes,
121 mem_private_bytes,
122 mem_shared_bytes,
123 mem_scaled_shared_bytes,
124 mem_fractional_scaled_shared_bytes,
125 }
126 }
127}
128
129unsafe impl ObjectQuery for TaskStatsInfo {
131 const TOPIC: Topic = Topic::TASK_STATS;
132 type InfoTy = TaskStatsInfo;
133}
134
135struct ProcessMapsInfo;
136unsafe impl ObjectQuery for ProcessMapsInfo {
137 const TOPIC: Topic = Topic::PROCESS_MAPS;
138 type InfoTy = MapInfo;
139}
140
141struct ProcessVmoInfo;
142unsafe impl ObjectQuery for ProcessVmoInfo {
143 const TOPIC: Topic = Topic::PROCESS_VMOS;
144 type InfoTy = VmoInfo;
145}
146
147sys::zx_info_process_handle_stats_t!(ProcessHandleStats);
148
149impl Default for ProcessHandleStats {
150 fn default() -> Self {
151 Self { handle_count: [0; ZX_OBJ_TYPE_UPPER_BOUND] }
152 }
153}
154
155unsafe impl ObjectQuery for ProcessHandleStats {
156 const TOPIC: Topic = Topic::PROCESS_HANDLE_STATS;
157 type InfoTy = ProcessHandleStats;
158}
159
160impl Process {
161 pub fn create(job: &Job, name: Name, options: ProcessOptions) -> Result<(Self, Vmar), Status> {
167 let mut this = 0;
168 let mut vmar = 0;
169
170 ok(unsafe {
173 crate::sys::zx_process_create(
174 job.raw_handle(),
175 name.as_raw(),
176 name.len(),
177 options.bits(),
178 &mut this,
179 &mut vmar,
180 )
181 })?;
182
183 let this = Self(unsafe { NullableHandle::from_raw(this) });
185
186 let vmar = Vmar::from(unsafe { NullableHandle::from_raw(vmar) });
188
189 Ok((this, vmar))
190 }
191
192 pub fn start(
198 &self,
199 thread: &Thread,
200 entry: usize,
201 stack: usize,
202 arg1: NullableHandle,
203 arg2: usize,
204 ) -> Result<(), Status> {
205 let process_raw = self.raw_handle();
206 let thread_raw = thread.raw_handle();
207 let arg1 = arg1.into_raw();
208 ok(unsafe { sys::zx_process_start(process_raw, thread_raw, entry, stack, arg1, arg2) })
209 }
210
211 pub fn create_thread(&self, name: &[u8]) -> Result<Thread, Status> {
217 let process_raw = self.raw_handle();
218 let name_ptr = name.as_ptr();
219 let name_len = name.len();
220 let options = 0;
221 let mut thread_out = 0;
222 let status = unsafe {
223 sys::zx_thread_create(process_raw, name_ptr, name_len, options, &mut thread_out)
224 };
225 ok(status)?;
226 unsafe { Ok(Thread::from(NullableHandle::from_raw(thread_out))) }
227 }
228
229 pub fn write_memory(&self, vaddr: sys::zx_vaddr_t, bytes: &[u8]) -> Result<usize, Status> {
235 let mut actual = 0;
236 let status = unsafe {
237 sys::zx_process_write_memory(
238 self.raw_handle(),
239 vaddr,
240 bytes.as_ptr(),
241 bytes.len(),
242 &mut actual,
243 )
244 };
245 ok(status).map(|()| actual)
246 }
247
248 pub fn read_memory(&self, vaddr: sys::zx_vaddr_t, bytes: &mut [u8]) -> Result<usize, Status> {
254 let (actually_read, _) = self.read_memory_uninit(vaddr, unsafe {
259 std::slice::from_raw_parts_mut(
260 bytes.as_mut_ptr().cast::<MaybeUninit<u8>>(),
261 bytes.len(),
262 )
263 })?;
264 Ok(actually_read.len())
265 }
266
267 pub fn read_memory_uninit<'a>(
273 &self,
274 vaddr: sys::zx_vaddr_t,
275 buffer: &'a mut [MaybeUninit<u8>],
276 ) -> Result<(&'a mut [u8], &'a mut [MaybeUninit<u8>]), Status> {
277 let mut actually_read = 0;
278 let status = unsafe {
284 sys::zx_process_read_memory(
285 self.raw_handle(),
286 vaddr,
287 buffer.as_mut_ptr().cast::<u8>(),
289 buffer.len(),
290 &mut actually_read,
291 )
292 };
293 ok(status)?;
294 let (initialized, uninitialized) = buffer.split_at_mut(actually_read);
295 Ok((
296 unsafe {
302 std::slice::from_raw_parts_mut(
303 initialized.as_mut_ptr().cast::<u8>(),
304 initialized.len(),
305 )
306 },
307 uninitialized,
308 ))
309 }
310
311 pub fn info(&self) -> Result<ProcessInfo, Status> {
315 self.0.get_info_single::<ProcessInfo>()
316 }
317
318 pub fn threads(&self) -> Result<Vec<Koid>, Status> {
322 self.0.get_info_vec::<ProcessThreadsInfo>()
323 }
324
325 pub fn task_stats(&self) -> Result<TaskStatsInfo, Status> {
329 self.0.get_info_single::<TaskStatsInfo>()
330 }
331
332 pub fn info_maps_vec(&self) -> Result<Vec<MapInfo>, Status> {
336 self.0.get_info_vec::<ProcessMapsInfo>()
337 }
338
339 pub fn exit(retcode: i64) -> ! {
345 unsafe {
346 sys::zx_process_exit(retcode);
347 std::hint::unreachable_unchecked()
350 }
351 }
352
353 pub fn handle_stats(&self) -> Result<ProcessHandleStats, Status> {
357 self.0.get_info_single::<ProcessHandleStats>()
358 }
359
360 pub fn get_child(&self, koid: &Koid, rights: Rights) -> Result<Thread, Status> {
364 let mut handle: zx_handle_t = Default::default();
365 let status = unsafe {
366 sys::zx_object_get_child(self.raw_handle(), koid.raw_koid(), rights.bits(), &mut handle)
367 };
368 ok(status)?;
369 Ok(Thread::from(unsafe { NullableHandle::from_raw(handle) }))
370 }
371
372 pub fn info_vmos_vec(&self) -> Result<Vec<VmoInfo>, Status> {
376 self.0.get_info_vec::<ProcessVmoInfo>()
377 }
378
379 pub fn info_maps<'a>(
385 &self,
386 info_out: &'a mut [std::mem::MaybeUninit<MapInfo>],
387 ) -> Result<(&'a mut [MapInfo], &'a mut [std::mem::MaybeUninit<MapInfo>], usize), Status> {
388 self.0.get_info::<ProcessMapsInfo>(info_out)
389 }
390
391 pub fn info_vmos<'a>(
397 &self,
398 info_out: &'a mut [std::mem::MaybeUninit<VmoInfo>],
399 ) -> Result<(&'a mut [VmoInfo], &'a mut [std::mem::MaybeUninit<VmoInfo>], usize), Status> {
400 self.0.get_info::<ProcessVmoInfo>(info_out)
401 }
402}
403
404impl Task for Process {}
405
406#[cfg(test)]
407mod tests {
408 use crate::cprng_draw;
409 use assert_matches::assert_matches;
412 use std::ffi::CString;
413 use std::mem::MaybeUninit;
414 use zx::{
415 AsHandleRef, Instant, MapDetails, ProcessInfo, ProcessInfoFlags, Signals, Task,
416 TaskStatsInfo, VmarFlags, Vmo, sys, system_get_page_size,
417 };
418
419 const STARTED: ProcessInfoFlags = ProcessInfoFlags::STARTED;
420 const STARTED_AND_EXITED: ProcessInfoFlags = STARTED.union(ProcessInfoFlags::EXITED);
421
422 #[test]
423 fn info_self() {
424 let process = fuchsia_runtime::process_self();
425 let info = process.info().unwrap();
426 assert_matches!(
427 info,
428 ProcessInfo {
429 return_code: 0,
430 start_time,
431 flags: STARTED,
432 ..
433 } if start_time.into_nanos() > 0
434 );
435 }
436
437 #[test]
438 fn stats_self() {
439 let process = fuchsia_runtime::process_self();
440 let task_stats = process.task_stats().unwrap();
441
442 assert!(matches!(task_stats,
445 TaskStatsInfo {
446 mem_mapped_bytes,
447 mem_private_bytes,
448 mem_shared_bytes,
449 mem_scaled_shared_bytes,
450 mem_fractional_scaled_shared_bytes: _
451 }
452 if mem_mapped_bytes > 0
453 && mem_private_bytes > 0
454 && mem_shared_bytes > 0
455 && mem_scaled_shared_bytes > 0));
456 }
457
458 #[test]
459 fn exit_and_info() {
460 let mut randbuf = [0; 8];
461 cprng_draw(&mut randbuf);
462 let expected_code = i64::from_le_bytes(randbuf);
463 let arg = CString::new(format!("{}", expected_code)).unwrap();
464
465 let binpath = CString::new("/pkg/bin/exit_with_code_util").unwrap();
468 let process = fdio::spawn(
469 &fuchsia_runtime::job_default(),
470 fdio::SpawnOptions::DEFAULT_LOADER,
471 &binpath,
472 &[&arg],
473 )
474 .expect("Failed to spawn process");
475
476 process
477 .wait_handle(Signals::PROCESS_TERMINATED, Instant::INFINITE)
478 .expect("Wait for process termination failed");
479 let info = process.info().unwrap();
480 assert_matches!(
481 info,
482 ProcessInfo {
483 return_code,
484 start_time,
485 flags: STARTED_AND_EXITED,
486 ..
487 } if return_code == expected_code && start_time.into_nanos() > 0
488 );
489 }
490
491 #[test]
492 fn kill_and_info() {
493 let binpath = CString::new("/pkg/bin/sleep_forever_util").unwrap();
495 let process = fdio::spawn(
496 &fuchsia_runtime::job_default(),
497 fdio::SpawnOptions::DEFAULT_LOADER,
499 &binpath,
500 &[&binpath],
501 )
502 .expect("Failed to spawn process");
503
504 let info = process.info().unwrap();
505 assert_matches!(
506 info,
507 ProcessInfo {
508 return_code: 0,
509 start_time,
510 flags: STARTED,
511 ..
512 } if start_time.into_nanos() > 0
513 );
514
515 process.kill().expect("Failed to kill process");
516 process
517 .wait_handle(Signals::PROCESS_TERMINATED, Instant::INFINITE)
518 .expect("Wait for process termination failed");
519
520 let info = process.info().unwrap();
521 assert_matches!(
522 info,
523 ProcessInfo {
524 return_code: sys::ZX_TASK_RETCODE_SYSCALL_KILL,
525 start_time,
526 flags: STARTED_AND_EXITED,
527 ..
528 } if start_time.into_nanos() > 0
529 );
530 }
531
532 #[test]
533 fn maps_info() {
534 let root_vmar = fuchsia_runtime::vmar_root_self();
535 let process = fuchsia_runtime::process_self();
536
537 let vmo = Vmo::create(system_get_page_size() as u64).unwrap();
539 let vmo_koid = vmo.get_koid().unwrap();
540
541 let map1 = root_vmar
542 .map(0, &vmo, 0, system_get_page_size() as usize, VmarFlags::PERM_READ)
543 .unwrap();
544 let map2 = root_vmar
545 .map(0, &vmo, 0, system_get_page_size() as usize, VmarFlags::PERM_READ)
546 .unwrap();
547
548 let mut data = vec![MaybeUninit::uninit(); 1];
551 let (returned, _, available) = process.info_maps(&mut data).unwrap();
552 assert_eq!(returned.len(), 1);
553 assert!(available > 0);
554
555 let total = available + 10;
558
559 let mut data = vec![MaybeUninit::uninit(); total];
561
562 let (info, _, available) = process.info_maps(&mut data).unwrap();
563
564 assert_eq!(info.len(), available);
566
567 let count = info
569 .iter()
570 .filter(|info| match info.details() {
571 MapDetails::Mapping(d) => d.vmo_koid == vmo_koid,
572 _ => false,
573 })
574 .count();
575 assert_eq!(count, 2);
576
577 unsafe {
580 root_vmar.unmap(map1, system_get_page_size() as usize).unwrap();
581 root_vmar.unmap(map2, system_get_page_size() as usize).unwrap();
582 }
583 }
584
585 #[test]
586 fn info_maps_vec() {
587 let root_vmar = fuchsia_runtime::vmar_root_self();
588 let process = fuchsia_runtime::process_self();
589
590 let vmo = Vmo::create(system_get_page_size() as u64).unwrap();
592 let vmo_koid = vmo.get_koid().unwrap();
593
594 let map1 = root_vmar
595 .map(0, &vmo, 0, system_get_page_size() as usize, VmarFlags::PERM_READ)
596 .unwrap();
597 let map2 = root_vmar
598 .map(0, &vmo, 0, system_get_page_size() as usize, VmarFlags::PERM_READ)
599 .unwrap();
600
601 let info = process.info_maps_vec().unwrap();
602
603 let count = info
605 .iter()
606 .filter(|info| match info.details() {
607 MapDetails::Mapping(d) => d.vmo_koid == vmo_koid,
608 _ => false,
609 })
610 .count();
611 assert_eq!(count, 2);
612
613 unsafe {
616 root_vmar.unmap(map1, system_get_page_size() as usize).unwrap();
617 root_vmar.unmap(map2, system_get_page_size() as usize).unwrap();
618 }
619 }
620
621 #[test]
622 fn info_vmos() {
623 let process = fuchsia_runtime::process_self();
624
625 let vmo = Vmo::create(system_get_page_size() as u64).unwrap();
627 let vmo_koid = vmo.get_koid().unwrap();
628
629 let mut data = vec![MaybeUninit::uninit(); 2048];
630 let (info, _, available) = process.info_vmos(&mut data).unwrap();
631
632 assert_eq!(info.len(), available);
634
635 let count = info.iter().filter(|map| map.koid == vmo_koid).count();
637 assert_eq!(count, 1);
638 }
639
640 #[test]
641 fn info_vmos_vec() {
642 let process = fuchsia_runtime::process_self();
643
644 let vmo = Vmo::create(system_get_page_size() as u64).unwrap();
646 let vmo_koid = vmo.get_koid().unwrap();
647
648 let info = process.info_vmos_vec().unwrap();
649
650 let count = info.iter().filter(|map| map.koid == vmo_koid).count();
652 assert_eq!(count, 1);
653 }
654
655 #[test]
656 fn handle_stats() {
657 let process = fuchsia_runtime::process_self();
658 let handle_stats = process.handle_stats().unwrap();
659
660 let sum: u32 = handle_stats.handle_count.iter().sum();
664
665 assert!(sum > 0);
666 assert!(sum < 1_000_000);
667 }
668
669 #[test]
670 fn threads_contain_self() {
671 let current_thread_koid =
672 fuchsia_runtime::with_thread_self(|thread| thread.get_koid().unwrap());
673 let threads_koids = fuchsia_runtime::process_self().threads().unwrap();
674 assert!(threads_koids.contains(¤t_thread_koid));
675 let thread_handle = fuchsia_runtime::process_self()
676 .get_child(¤t_thread_koid, zx::Rights::NONE)
677 .unwrap();
678 assert_eq!(thread_handle.get_koid().unwrap(), current_thread_koid);
679 }
680
681 #[cfg(not(feature = "vdso_next"))]
683 #[test]
684 fn new_process_no_threads() {
685 let job = fuchsia_runtime::job_default().create_child_job().unwrap();
686 let (process, _) =
687 job.create_child_process(zx::ProcessOptions::empty(), b"test-process").unwrap();
688 assert!(process.threads().unwrap().is_empty());
689 }
690
691 #[cfg(not(feature = "vdso_next"))]
693 #[test]
694 fn non_started_threads_dont_show_up() {
695 let job = fuchsia_runtime::job_default().create_child_job().unwrap();
696 let (process, _) =
697 job.create_child_process(zx::ProcessOptions::empty(), b"test-process").unwrap();
698
699 let thread = process.create_thread(b"test-thread").unwrap();
700 let thread_koid = thread.get_koid().unwrap();
701
702 assert!(process.threads().unwrap().is_empty());
703 assert!(process.get_child(&thread_koid, zx::Rights::NONE).is_err());
704 }
705
706 #[cfg(not(feature = "vdso_next"))]
708 #[test]
709 fn started_threads_show_up() {
710 let job = fuchsia_runtime::job_default().create_child_job().unwrap();
711 let (process, root_vmar) =
712 job.create_child_process(zx::ProcessOptions::empty(), b"test-process").unwrap();
713
714 let valid_addr = root_vmar.info().unwrap().base;
715
716 let thread1 = process.create_thread(b"test-thread-1").unwrap();
717 let thread2 = process.create_thread(b"test-thread-2").unwrap();
718
719 let thread1_suspended = thread1.suspend().unwrap();
721 process.start(&thread1, valid_addr, valid_addr, zx::NullableHandle::invalid(), 0).unwrap();
722
723 let threads_koids = process.threads().unwrap();
724 assert_eq!(threads_koids.len(), 1);
725 assert_eq!(threads_koids[0], thread1.get_koid().unwrap());
726 assert_eq!(
727 process.get_child(&threads_koids[0], zx::Rights::NONE).unwrap().get_koid().unwrap(),
728 threads_koids[0]
729 );
730
731 let thread2_suspended = thread2.suspend().unwrap();
733 thread2.start(valid_addr, valid_addr, 0, 0).unwrap();
734
735 let threads_koids = process.threads().unwrap();
736 assert_eq!(threads_koids.len(), 2);
737 assert!(threads_koids.contains(&thread1.get_koid().unwrap()));
738 assert!(threads_koids.contains(&thread2.get_koid().unwrap()));
739 assert_eq!(
740 process.get_child(&threads_koids[0], zx::Rights::NONE).unwrap().get_koid().unwrap(),
741 threads_koids[0]
742 );
743 assert_eq!(
744 process.get_child(&threads_koids[1], zx::Rights::NONE).unwrap().get_koid().unwrap(),
745 threads_koids[1]
746 );
747
748 process.kill().unwrap();
749 process.wait_handle(Signals::TASK_TERMINATED, Instant::INFINITE).unwrap();
750
751 drop(thread1_suspended);
752 drop(thread2_suspended);
753
754 assert!(process.threads().unwrap().is_empty());
755 }
756}