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