blob: 27cf6b92206f22f51228e3f0e93cfa7af1664a9f [file] [log] [blame]
// 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(&current_thread_koid));
let thread_handle =
fuchsia_runtime::process_self().get_child(&current_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());
}
}