| // Copyright 2017 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| //! Type-safe bindings for Zircon processes. |
| |
| use crate::ok; |
| use crate::sys::{zx_handle_t, zx_rights_t}; |
| use crate::{ |
| object_get_info, object_get_info_vec, object_get_property, object_set_property, ObjectQuery, |
| Topic, VmoInfo, |
| }; |
| use crate::{AsHandleRef, Handle, HandleBased, HandleRef, Koid, Status, Task, Thread}; |
| use crate::{Property, PropertyQuery}; |
| use bitflags::bitflags; |
| use fuchsia_zircon_sys::{ |
| self as sys, zx_info_maps_type_t, zx_koid_t, zx_time_t, zx_vaddr_t, zx_vm_option_t, |
| InfoMapsTypeUnion, PadByte, ZX_MAX_NAME_LEN, ZX_OBJ_TYPE_UPPER_BOUND, |
| }; |
| use std::mem::MaybeUninit; |
| |
| bitflags! { |
| /// Options that may be used when creating a `Process`. |
| #[repr(transparent)] |
| #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] |
| pub struct ProcessOptions: u32 { |
| const SHARED = sys::ZX_PROCESS_SHARED; |
| } |
| } |
| |
| impl Default for ProcessOptions { |
| fn default() -> Self { |
| ProcessOptions::empty() |
| } |
| } |
| |
| bitflags! { |
| #[repr(transparent)] |
| #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] |
| pub struct ProcessInfoFlags: u32 { |
| const STARTED = sys::ZX_INFO_PROCESS_FLAG_STARTED; |
| const EXITED = sys::ZX_INFO_PROCESS_FLAG_EXITED; |
| const DEBUGGER_ATTACHED = sys::ZX_INFO_PROCESS_FLAG_DEBUGGER_ATTACHED; |
| } |
| } |
| |
| /// An object representing a Zircon process. |
| /// |
| /// As essentially a subtype of `Handle`, it can be freely interconverted. |
| #[derive(Debug, Eq, PartialEq, Ord, PartialOrd, Hash)] |
| #[repr(transparent)] |
| pub struct Process(Handle); |
| impl_handle_based!(Process); |
| unsafe_handle_properties!(object: Process, |
| props: [ |
| {query_ty: PROCESS_DEBUG_ADDR, tag: ProcessDebugAddrTag, prop_ty: u64, get:get_debug_addr, set:set_debug_addr}, |
| {query_ty: PROCESS_BREAK_ON_LOAD, tag: ProcessBreakOnLoadTag, prop_ty: u64, get:get_break_on_load, set:set_break_on_load}, |
| ] |
| ); |
| |
| sys::zx_info_process_t!(ProcessInfo); |
| |
| impl From<sys::zx_info_process_t> for ProcessInfo { |
| fn from(info: sys::zx_info_process_t) -> ProcessInfo { |
| let sys::zx_info_process_t { return_code, start_time, flags } = info; |
| ProcessInfo { return_code, start_time, flags } |
| } |
| } |
| |
| // ProcessInfo is able to be safely replaced with a byte representation and is a PoD type. |
| unsafe impl ObjectQuery for ProcessInfo { |
| const TOPIC: Topic = Topic::PROCESS; |
| type InfoTy = ProcessInfo; |
| } |
| |
| struct ProcessThreadsInfo; |
| |
| // ProcessThreadsInfo is able to be safely replaced with a byte representation and is a PoD type. |
| unsafe impl ObjectQuery for ProcessThreadsInfo { |
| const TOPIC: Topic = Topic::PROCESS_THREADS; |
| type InfoTy = Koid; |
| } |
| |
| sys::zx_info_task_stats_t!(TaskStatsInfo); |
| |
| impl From<sys::zx_info_task_stats_t> for TaskStatsInfo { |
| fn from( |
| sys::zx_info_task_stats_t { |
| mem_mapped_bytes, |
| mem_private_bytes, |
| mem_shared_bytes, |
| mem_scaled_shared_bytes, |
| }: sys::zx_info_task_stats_t, |
| ) -> TaskStatsInfo { |
| TaskStatsInfo { |
| mem_mapped_bytes, |
| mem_private_bytes, |
| mem_shared_bytes, |
| mem_scaled_shared_bytes, |
| } |
| } |
| } |
| |
| // TaskStatsInfo is able to be safely replaced with a byte representation and is a PoD type. |
| unsafe impl ObjectQuery for TaskStatsInfo { |
| const TOPIC: Topic = Topic::TASK_STATS; |
| type InfoTy = TaskStatsInfo; |
| } |
| |
| sys::zx_info_maps_mapping_t!(MapsMappingInfo); |
| |
| impl From<sys::zx_info_maps_mapping_t> for MapsMappingInfo { |
| fn from( |
| sys::zx_info_maps_mapping_t { |
| mmu_flags, |
| padding1, |
| vmo_koid, |
| vmo_offset, |
| committed_pages, |
| populated_pages, |
| }: sys::zx_info_maps_mapping_t, |
| ) -> Self { |
| Self { mmu_flags, padding1, vmo_koid, vmo_offset, committed_pages, populated_pages } |
| } |
| } |
| |
| sys::zx_info_maps_t!(ProcessMapsInfo); |
| |
| impl ProcessMapsInfo { |
| pub fn into_mapping_info(&self) -> Option<MapsMappingInfo> { |
| if self.r#type != sys::ZX_INFO_MAPS_TYPE_MAPPING { |
| return None; |
| } |
| // All the fields of u.mapping are objects that are well defined for any bit |
| // representation, hence it is always safe to read it. |
| Some(unsafe { self.u.mapping }.into()) |
| } |
| } |
| |
| impl Default for ProcessMapsInfo { |
| fn default() -> Self { |
| let mapping = sys::zx_info_maps_mapping_t::default(); |
| Self { |
| u: sys::InfoMapsTypeUnion { mapping }, |
| name: Default::default(), |
| base: Default::default(), |
| size: Default::default(), |
| depth: Default::default(), |
| r#type: Default::default(), |
| } |
| } |
| } |
| |
| unsafe impl ObjectQuery for ProcessMapsInfo { |
| const TOPIC: Topic = Topic::PROCESS_MAPS; |
| type InfoTy = ProcessMapsInfo; |
| } |
| |
| struct ProcessVmoInfo; |
| unsafe impl ObjectQuery for ProcessVmoInfo { |
| const TOPIC: Topic = Topic::PROCESS_VMOS; |
| type InfoTy = sys::zx_info_vmo_t; |
| } |
| |
| sys::zx_info_process_handle_stats_t!(ProcessHandleStats); |
| |
| impl Default for ProcessHandleStats { |
| fn default() -> Self { |
| Self { handle_count: [0; ZX_OBJ_TYPE_UPPER_BOUND] } |
| } |
| } |
| |
| unsafe impl ObjectQuery for ProcessHandleStats { |
| const TOPIC: Topic = Topic::PROCESS_HANDLE_STATS; |
| type InfoTy = ProcessHandleStats; |
| } |
| |
| impl Process { |
| /// Similar to `Thread::start`, but is used to start the first thread in a process. |
| /// |
| /// Wraps the |
| /// [zx_process_start](https://fuchsia.dev/fuchsia-src/reference/syscalls/process_start.md) |
| /// syscall. |
| pub fn start( |
| &self, |
| thread: &Thread, |
| entry: usize, |
| stack: usize, |
| arg1: Handle, |
| arg2: usize, |
| ) -> Result<(), Status> { |
| let process_raw = self.raw_handle(); |
| let thread_raw = thread.raw_handle(); |
| let arg1 = arg1.into_raw(); |
| ok(unsafe { sys::zx_process_start(process_raw, thread_raw, entry, stack, arg1, arg2) }) |
| } |
| |
| /// Create a thread inside a process. |
| /// |
| /// Wraps the |
| /// [zx_thread_create](https://fuchsia.dev/fuchsia-src/reference/syscalls/thread_create.md) |
| /// syscall. |
| pub fn create_thread(&self, name: &[u8]) -> Result<Thread, Status> { |
| let process_raw = self.raw_handle(); |
| let name_ptr = name.as_ptr(); |
| let name_len = name.len(); |
| let options = 0; |
| let mut thread_out = 0; |
| let status = unsafe { |
| sys::zx_thread_create(process_raw, name_ptr, name_len, options, &mut thread_out) |
| }; |
| ok(status)?; |
| unsafe { Ok(Thread::from(Handle::from_raw(thread_out))) } |
| } |
| |
| /// Write memory inside a process. |
| /// |
| /// Wraps the |
| /// [zx_process_write_memory](https://fuchsia.dev/fuchsia-src/reference/syscalls/process_write_memory.md) |
| /// syscall. |
| pub fn write_memory(&self, vaddr: sys::zx_vaddr_t, bytes: &[u8]) -> Result<usize, Status> { |
| let mut actual = 0; |
| let status = unsafe { |
| sys::zx_process_write_memory( |
| self.raw_handle(), |
| vaddr, |
| bytes.as_ptr(), |
| bytes.len(), |
| &mut actual, |
| ) |
| }; |
| ok(status).map(|()| actual) |
| } |
| |
| /// Read memory from inside a process. |
| /// |
| /// Wraps the |
| /// [zx_process_read_memory](https://fuchsia.dev/fuchsia-src/reference/syscalls/process_read_memory.md) |
| /// syscall. |
| pub fn read_memory(&self, vaddr: sys::zx_vaddr_t, bytes: &mut [u8]) -> Result<usize, Status> { |
| // SAFETY: It's OK to interpret &mut [u8] as &mut [MaybeUninit<u8>] as long as we don't |
| // expose the MaybeUninit reference to code that would write uninitialized values to |
| // elements of the slice. Every valid state for a u8 is also a valid state for |
| // MaybeUninit<u8>, although the reverse is not true. |
| let (actually_read, _) = self.read_memory_uninit(vaddr, unsafe { |
| std::slice::from_raw_parts_mut(bytes.as_mut_ptr() as *mut MaybeUninit<u8>, bytes.len()) |
| })?; |
| Ok(actually_read.len()) |
| } |
| |
| /// Read memory from inside a process without requiring the output buffer to be initialized. |
| /// |
| /// Wraps the |
| /// [zx_process_read_memory](https://fuchsia.dev/fuchsia-src/reference/syscalls/process_read_memory.md) |
| /// syscall. |
| pub fn read_memory_uninit<'a>( |
| &self, |
| vaddr: sys::zx_vaddr_t, |
| buffer: &'a mut [MaybeUninit<u8>], |
| ) -> Result<(&'a mut [u8], &'a mut [MaybeUninit<u8>]), Status> { |
| let mut actually_read = 0; |
| // SAFETY: This is a system call that requires the pointers passed are valid to write to. |
| // We get the pointers from a valid mutable slice so we know it's safe to ask the kernel to |
| // write to them. Casting the *mut MaybeUninit<u8> to a *mut u8 is safe because all valid |
| // values for u8 are a subset of the valid values for MaybeUninit<u8> and we know the |
| // kernel won't write uninitialized values to the slice. |
| let status = unsafe { |
| sys::zx_process_read_memory( |
| self.raw_handle(), |
| vaddr, |
| // TODO(https://fxbug.dev/42079723) use MaybeUninit::slice_as_mut_ptr when stable |
| buffer.as_mut_ptr() as *mut u8, |
| buffer.len(), |
| &mut actually_read, |
| ) |
| }; |
| ok(status)?; |
| let (initialized, uninitialized) = buffer.split_at_mut(actually_read); |
| Ok(( |
| // TODO(https://fxbug.dev/42079723) use MaybeUninit::slice_assume_init_mut when stable |
| // SAFETY: We're converting &mut [MaybeUninit<u8>] back to &mut [u8], which is only |
| // valid to do if all elements of `initialized` have actually been initialized. Here we |
| // have to trust that the kernel didn't lie when it said it wrote to the entire buffer, |
| // but as long as that assumption is valid them it's safe to assume this slice is init. |
| unsafe { |
| std::slice::from_raw_parts_mut( |
| initialized.as_mut_ptr() as *mut u8, |
| initialized.len(), |
| ) |
| }, |
| uninitialized, |
| )) |
| } |
| |
| /// Wraps the |
| /// [zx_object_get_info](https://fuchsia.dev/fuchsia-src/reference/syscalls/object_get_info.md) |
| /// syscall for the ZX_INFO_PROCESS topic. |
| pub fn info(&self) -> Result<ProcessInfo, Status> { |
| let mut info = ProcessInfo::default(); |
| object_get_info::<ProcessInfo>(self.as_handle_ref(), std::slice::from_mut(&mut info)) |
| .map(|_| info) |
| } |
| |
| /// Wraps the |
| /// [zx_object_get_info](https://fuchsia.dev/fuchsia-src/reference/syscalls/object_get_info.md) |
| /// syscall for the ZX_INFO_PROCESS_THREADS topic. |
| pub fn threads(&self) -> Result<Vec<Koid>, Status> { |
| object_get_info_vec::<ProcessThreadsInfo>(self.as_handle_ref()) |
| } |
| |
| /// Wraps the |
| /// [zx_object_get_info](https://fuchsia.dev/fuchsia-src/reference/syscalls/object_get_info.md) |
| /// syscall for the ZX_INFO_TASK_STATS topic. |
| pub fn task_stats(&self) -> Result<TaskStatsInfo, Status> { |
| let mut info = TaskStatsInfo::default(); |
| object_get_info::<TaskStatsInfo>(self.as_handle_ref(), std::slice::from_mut(&mut info)) |
| .map(|_| info) |
| } |
| |
| /// Wraps the |
| /// [zx_object_get_info](https://fuchsia.dev/fuchsia-src/reference/syscalls/object_get_info.md) |
| /// syscall for the ZX_INFO_PROCESS_MAPS topic. |
| /// |
| /// Returns `(num_returned, num_remaining)` on success and writes `num_returned`, entries to |
| /// `info_out`. |
| pub fn info_maps(&self, info_out: &mut [ProcessMapsInfo]) -> Result<(usize, usize), Status> { |
| object_get_info::<ProcessMapsInfo>(self.as_handle_ref(), info_out) |
| } |
| |
| pub fn info_maps_vec(&self) -> Result<Vec<ProcessMapsInfo>, Status> { |
| object_get_info_vec::<ProcessMapsInfo>(self.as_handle_ref()) |
| } |
| |
| /// Exit the current process with the given return code. |
| /// |
| /// Wraps the |
| /// [zx_process_exit](https://fuchsia.dev/fuchsia-src/reference/syscalls/process_exit.md) |
| /// syscall. |
| pub fn exit(retcode: i64) -> ! { |
| unsafe { |
| sys::zx_process_exit(retcode); |
| // zither generates the syscall returning a unit value. We know it will not proceed |
| // past this point however. |
| std::hint::unreachable_unchecked() |
| } |
| } |
| |
| /// Wraps the |
| /// [zx_object_get_info](https://fuchsia.dev/fuchsia-src/reference/syscalls/object_get_info.md) |
| /// syscall for the ZX_INFO_PROCESS_HANDLE_STATS topic. |
| pub fn handle_stats(&self) -> Result<ProcessHandleStats, Status> { |
| let mut info = ProcessHandleStats::default(); |
| object_get_info::<ProcessHandleStats>(self.as_handle_ref(), std::slice::from_mut(&mut info)) |
| .map(|_| info) |
| } |
| |
| /// Wraps the |
| /// [zx_object_get_child](https://fuchsia.dev/fuchsia-src/reference/syscalls/object_get_child.md) |
| /// syscall. |
| pub fn get_child(&self, koid: &Koid, rights: zx_rights_t) -> Result<Handle, Status> { |
| let mut handle: zx_handle_t = Default::default(); |
| let status = unsafe { |
| sys::zx_object_get_child( |
| self.raw_handle(), |
| koid.raw_koid(), |
| rights, |
| &mut handle as *mut zx_handle_t, |
| ) |
| }; |
| ok(status)?; |
| Ok(unsafe { Handle::from_raw(handle) }) |
| } |
| |
| /// Wraps the |
| /// [zx_object_get_info](https://fuchsia.dev/fuchsia-src/reference/syscalls/object_get_info.md) |
| /// syscall for the ZX_INFO_PROCESS_VMO topic. |
| pub fn info_vmos_vec(&self) -> Result<Vec<VmoInfo>, Status> { |
| let raw_info = object_get_info_vec::<ProcessVmoInfo>(self.as_handle_ref())?; |
| Ok(raw_info.into_iter().map(|info| info.into()).collect()) |
| } |
| } |
| |
| impl Task for Process {} |
| |
| #[cfg(test)] |
| mod tests { |
| use crate::cprng_draw; |
| // The unit tests are built with a different crate name, but fdio and fuchsia_runtime return a |
| // "real" fuchsia_zircon::Process that we need to use. |
| use assert_matches::assert_matches; |
| use fuchsia_zircon::sys::ZX_RIGHT_NONE; |
| use fuchsia_zircon::{ |
| sys, system_get_page_size, AsHandleRef, Handle, ProcessInfo, ProcessInfoFlags, |
| ProcessMapsInfo, ProcessOptions, Signals, Task, TaskStatsInfo, Time, VmarFlags, Vmo, |
| }; |
| use std::ffi::CString; |
| |
| #[test] |
| fn info_self() { |
| let process = fuchsia_runtime::process_self(); |
| let info = process.info().unwrap(); |
| const STARTED: u32 = ProcessInfoFlags::STARTED.bits(); |
| assert_matches!( |
| info, |
| ProcessInfo { |
| return_code: 0, |
| start_time, |
| flags: STARTED, |
| } if start_time > 0 |
| ); |
| } |
| |
| #[test] |
| fn stats_self() { |
| let process = fuchsia_runtime::process_self(); |
| let task_stats = process.task_stats().unwrap(); |
| |
| // Values greater than zero should be reported back for all memory usage |
| // types. |
| assert!(matches!(task_stats, |
| TaskStatsInfo { |
| mem_mapped_bytes, |
| mem_private_bytes, |
| mem_shared_bytes, |
| mem_scaled_shared_bytes |
| } |
| if mem_mapped_bytes > 0 |
| && mem_private_bytes > 0 |
| && mem_shared_bytes > 0 |
| && mem_scaled_shared_bytes > 0)); |
| } |
| |
| #[test] |
| fn exit_and_info() { |
| let mut randbuf = [0; 8]; |
| cprng_draw(&mut randbuf); |
| let expected_code = i64::from_le_bytes(randbuf); |
| let arg = CString::new(format!("{}", expected_code)).unwrap(); |
| |
| // This test utility will exercise zx::Process::exit, using the provided argument as the |
| // return code. |
| let binpath = CString::new("/pkg/bin/exit_with_code_util").unwrap(); |
| let process = fdio::spawn( |
| &fuchsia_runtime::job_default(), |
| fdio::SpawnOptions::DEFAULT_LOADER, |
| &binpath, |
| &[&arg], |
| ) |
| .expect("Failed to spawn process"); |
| |
| process |
| .wait_handle(Signals::PROCESS_TERMINATED, Time::INFINITE) |
| .expect("Wait for process termination failed"); |
| let info = process.info().unwrap(); |
| const STARTED_AND_EXITED: u32 = |
| ProcessInfoFlags::STARTED.bits() | ProcessInfoFlags::EXITED.bits(); |
| assert_matches!( |
| info, |
| ProcessInfo { |
| return_code, |
| start_time, |
| flags: STARTED_AND_EXITED, |
| } if return_code == expected_code && start_time > 0 |
| ); |
| } |
| |
| #[test] |
| fn kill_and_info() { |
| // This test utility will sleep "forever" without exiting, so that we can kill it.. |
| let binpath = CString::new("/pkg/bin/sleep_forever_util").unwrap(); |
| let process = fdio::spawn( |
| &fuchsia_runtime::job_default(), |
| // Careful not to clone stdio here, or the test runner can hang. |
| fdio::SpawnOptions::DEFAULT_LOADER, |
| &binpath, |
| &[&binpath], |
| ) |
| .expect("Failed to spawn process"); |
| |
| let info = process.info().unwrap(); |
| const STARTED: u32 = ProcessInfoFlags::STARTED.bits(); |
| assert_matches!( |
| info, |
| ProcessInfo { |
| return_code: 0, |
| start_time, |
| flags: STARTED, |
| } if start_time > 0 |
| ); |
| |
| process.kill().expect("Failed to kill process"); |
| process |
| .wait_handle(Signals::PROCESS_TERMINATED, Time::INFINITE) |
| .expect("Wait for process termination failed"); |
| |
| let info = process.info().unwrap(); |
| const STARTED_AND_EXITED: u32 = |
| ProcessInfoFlags::STARTED.bits() | ProcessInfoFlags::EXITED.bits(); |
| assert_matches!( |
| info, |
| ProcessInfo { |
| return_code: sys::ZX_TASK_RETCODE_SYSCALL_KILL, |
| start_time, |
| flags: STARTED_AND_EXITED, |
| } if start_time > 0 |
| ); |
| } |
| |
| #[test] |
| fn maps_info() { |
| let root_vmar = fuchsia_runtime::vmar_root_self(); |
| let process = fuchsia_runtime::process_self(); |
| |
| // Create two mappings so we know what to expect from our test calls. |
| let vmo = Vmo::create(system_get_page_size() as u64).unwrap(); |
| let vmo_koid = vmo.get_koid().unwrap(); |
| |
| let map1 = root_vmar |
| .map(0, &vmo, 0, system_get_page_size() as usize, VmarFlags::PERM_READ) |
| .unwrap(); |
| let map2 = root_vmar |
| .map(0, &vmo, 0, system_get_page_size() as usize, VmarFlags::PERM_READ) |
| .unwrap(); |
| |
| // Querying a single info. As we know there are at least two mappings this is guaranteed to |
| // not return all of them. |
| let mut info = ProcessMapsInfo::default(); |
| let (returned, remaining) = process.info_maps(std::slice::from_mut(&mut info)).unwrap(); |
| assert_eq!(returned, 1); |
| assert!(remaining > 0); |
| |
| // Add some slack to the total to account for mappings created as a result of the heap |
| // allocation in Vec. |
| let total = returned + remaining + 10; |
| |
| // Allocate and retrieve all of the mappings. |
| let mut info = Vec::with_capacity(total); |
| info.resize(total, ProcessMapsInfo::default()); |
| |
| let (_, remaining) = process.info_maps(info.as_mut_slice()).unwrap(); |
| // Don't know exactly how many were returned, but since we are going to search for our |
| // mappings we do need to know that none are remaining. |
| assert_eq!(remaining, 0); |
| |
| // We should find our two mappings in the info. |
| let count = info |
| .iter() |
| .filter_map(ProcessMapsInfo::into_mapping_info) |
| .filter(|map| map.vmo_koid == vmo_koid.raw_koid()) |
| .count(); |
| assert_eq!(count, 2); |
| |
| // We created these mappings and are not letting any references to them escape so unmapping |
| // is safe to do. |
| unsafe { |
| root_vmar.unmap(map1, system_get_page_size() as usize).unwrap(); |
| root_vmar.unmap(map2, system_get_page_size() as usize).unwrap(); |
| } |
| } |
| |
| #[test] |
| fn info_maps_vec() { |
| let root_vmar = fuchsia_runtime::vmar_root_self(); |
| let process = fuchsia_runtime::process_self(); |
| |
| // Create two mappings so we know what to expect from our test calls. |
| let vmo = Vmo::create(system_get_page_size() as u64).unwrap(); |
| let vmo_koid = vmo.get_koid().unwrap(); |
| |
| let map1 = root_vmar |
| .map(0, &vmo, 0, system_get_page_size() as usize, VmarFlags::PERM_READ) |
| .unwrap(); |
| let map2 = root_vmar |
| .map(0, &vmo, 0, system_get_page_size() as usize, VmarFlags::PERM_READ) |
| .unwrap(); |
| |
| let info = process.info_maps_vec().unwrap(); |
| |
| // We should find our two mappings in the info. |
| let count = info |
| .iter() |
| .filter_map(ProcessMapsInfo::into_mapping_info) |
| .filter(|map| map.vmo_koid == vmo_koid.raw_koid()) |
| .count(); |
| assert_eq!(count, 2); |
| |
| // We created these mappings and are not letting any references to them escape so unmapping |
| // is safe to do. |
| unsafe { |
| root_vmar.unmap(map1, system_get_page_size() as usize).unwrap(); |
| root_vmar.unmap(map2, system_get_page_size() as usize).unwrap(); |
| } |
| } |
| |
| #[test] |
| fn info_vmos_vec() { |
| let process = fuchsia_runtime::process_self(); |
| |
| // Create two mappings so we know what to expect from our test calls. |
| let vmo = Vmo::create(system_get_page_size() as u64).unwrap(); |
| let vmo_koid = vmo.get_koid().unwrap(); |
| |
| let info = process.info_vmos_vec().unwrap(); |
| |
| // We should find our two mappings in the info. |
| let count = info.iter().filter(|map| map.koid == vmo_koid).count(); |
| assert_eq!(count, 1); |
| } |
| |
| #[test] |
| fn handle_stats() { |
| let process = fuchsia_runtime::process_self(); |
| let handle_stats = process.handle_stats().unwrap(); |
| |
| // We don't have an opinion about how many handles a typical process has, or what types |
| // they will be (the function returns counts for up to 64 different types), |
| // but a reasonable total should be between 1 and a million. |
| let sum: u32 = handle_stats.handle_count.iter().sum(); |
| |
| assert!(sum > 0); |
| assert!(sum < 1_000_000); |
| } |
| |
| #[test] |
| fn threads_contain_self() { |
| let current_thread_koid = fuchsia_runtime::thread_self().get_koid().unwrap(); |
| let threads_koids = fuchsia_runtime::process_self().threads().unwrap(); |
| assert!(threads_koids.contains(¤t_thread_koid)); |
| let thread_handle = |
| fuchsia_runtime::process_self().get_child(¤t_thread_koid, ZX_RIGHT_NONE).unwrap(); |
| assert_eq!(thread_handle.get_koid().unwrap(), current_thread_koid); |
| } |
| |
| #[test] |
| fn new_process_no_threads() { |
| let job = fuchsia_runtime::job_default().create_child_job().unwrap(); |
| let (process, _) = |
| job.create_child_process(ProcessOptions::empty(), b"test-process").unwrap(); |
| assert!(process.threads().unwrap().is_empty()); |
| } |
| |
| #[test] |
| fn non_started_threads_dont_show_up() { |
| let job = fuchsia_runtime::job_default().create_child_job().unwrap(); |
| let (process, _) = |
| job.create_child_process(ProcessOptions::empty(), b"test-process").unwrap(); |
| |
| let thread = process.create_thread(b"test-thread").unwrap(); |
| let thread_koid = thread.get_koid().unwrap(); |
| |
| assert!(process.threads().unwrap().is_empty()); |
| assert!(process.get_child(&thread_koid, ZX_RIGHT_NONE).is_err()); |
| } |
| |
| #[test] |
| fn started_threads_show_up() { |
| let job = fuchsia_runtime::job_default().create_child_job().unwrap(); |
| let (process, root_vmar) = |
| job.create_child_process(ProcessOptions::empty(), b"test-process").unwrap(); |
| |
| let valid_addr = root_vmar.info().unwrap().base; |
| |
| let thread1 = process.create_thread(b"test-thread-1").unwrap(); |
| let thread2 = process.create_thread(b"test-thread-2").unwrap(); |
| |
| // start with the thread suspended, so we don't care about executing invalid code. |
| let thread1_suspended = thread1.suspend().unwrap(); |
| process.start(&thread1, valid_addr, valid_addr, Handle::invalid(), 0).unwrap(); |
| |
| let threads_koids = process.threads().unwrap(); |
| assert_eq!(threads_koids.len(), 1); |
| assert_eq!(threads_koids[0], thread1.get_koid().unwrap()); |
| assert_eq!( |
| process.get_child(&threads_koids[0], ZX_RIGHT_NONE).unwrap().get_koid().unwrap(), |
| threads_koids[0] |
| ); |
| |
| // Add another thread. |
| let thread2_suspended = thread2.suspend().unwrap(); |
| thread2.start(valid_addr, valid_addr, 0, 0).unwrap(); |
| |
| let threads_koids = process.threads().unwrap(); |
| assert_eq!(threads_koids.len(), 2); |
| assert!(threads_koids.contains(&thread1.get_koid().unwrap())); |
| assert!(threads_koids.contains(&thread2.get_koid().unwrap())); |
| assert_eq!( |
| process.get_child(&threads_koids[0], ZX_RIGHT_NONE).unwrap().get_koid().unwrap(), |
| threads_koids[0] |
| ); |
| assert_eq!( |
| process.get_child(&threads_koids[1], ZX_RIGHT_NONE).unwrap().get_koid().unwrap(), |
| threads_koids[1] |
| ); |
| |
| process.kill().unwrap(); |
| process.wait_handle(Signals::TASK_TERMINATED, Time::INFINITE).unwrap(); |
| |
| drop(thread1_suspended); |
| drop(thread2_suspended); |
| |
| assert!(process.threads().unwrap().is_empty()); |
| } |
| } |