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