use crate::sys::{self as sys, zx_handle_t, zx_time_t, ZX_OBJ_TYPE_UPPER_BOUND};
use crate::{
object_get_info, object_get_info_single, object_get_info_vec, object_get_property,
object_set_property, ok, AsHandleRef, Handle, HandleBased, HandleRef, Koid, MapInfo,
ObjectQuery, Property, PropertyQuery, Rights, Status, Task, Thread, Topic, VmoInfo,
};
use bitflags::bitflags;
use std::mem::MaybeUninit;
bitflags! {
#[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;
}
}
#[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 }
}
}
unsafe impl ObjectQuery for ProcessInfo {
const TOPIC: Topic = Topic::PROCESS;
type InfoTy = ProcessInfo;
}
struct ProcessThreadsInfo;
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,
mem_fractional_scaled_shared_bytes,
}: sys::zx_info_task_stats_t,
) -> TaskStatsInfo {
TaskStatsInfo {
mem_mapped_bytes,
mem_private_bytes,
mem_shared_bytes,
mem_scaled_shared_bytes,
mem_fractional_scaled_shared_bytes,
}
}
}
unsafe impl ObjectQuery for TaskStatsInfo {
const TOPIC: Topic = Topic::TASK_STATS;
type InfoTy = TaskStatsInfo;
}
struct ProcessMapsInfo;
unsafe impl ObjectQuery for ProcessMapsInfo {
const TOPIC: Topic = Topic::PROCESS_MAPS;
type InfoTy = MapInfo;
}
struct ProcessVmoInfo;
unsafe impl ObjectQuery for ProcessVmoInfo {
const TOPIC: Topic = Topic::PROCESS_VMOS;
type InfoTy = VmoInfo;
}
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 {
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) })
}
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))) }
}
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)
}
pub fn read_memory(&self, vaddr: sys::zx_vaddr_t, bytes: &mut [u8]) -> Result<usize, Status> {
let (actually_read, _) = self.read_memory_uninit(vaddr, unsafe {
std::slice::from_raw_parts_mut(
bytes.as_mut_ptr().cast::<MaybeUninit<u8>>(),
bytes.len(),
)
})?;
Ok(actually_read.len())
}
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;
let status = unsafe {
sys::zx_process_read_memory(
self.raw_handle(),
vaddr,
buffer.as_mut_ptr().cast::<u8>(),
buffer.len(),
&mut actually_read,
)
};
ok(status)?;
let (initialized, uninitialized) = buffer.split_at_mut(actually_read);
Ok((
unsafe {
std::slice::from_raw_parts_mut(
initialized.as_mut_ptr().cast::<u8>(),
initialized.len(),
)
},
uninitialized,
))
}
pub fn info(&self) -> Result<ProcessInfo, Status> {
object_get_info_single::<ProcessInfo>(self.as_handle_ref())
}
pub fn threads(&self) -> Result<Vec<Koid>, Status> {
object_get_info_vec::<ProcessThreadsInfo>(self.as_handle_ref())
}
pub fn task_stats(&self) -> Result<TaskStatsInfo, Status> {
object_get_info_single::<TaskStatsInfo>(self.as_handle_ref())
}
pub fn info_maps_vec(&self) -> Result<Vec<MapInfo>, Status> {
object_get_info_vec::<ProcessMapsInfo>(self.as_handle_ref())
}
pub fn exit(retcode: i64) -> ! {
unsafe {
sys::zx_process_exit(retcode);
std::hint::unreachable_unchecked()
}
}
pub fn handle_stats(&self) -> Result<ProcessHandleStats, Status> {
object_get_info_single::<ProcessHandleStats>(self.as_handle_ref())
}
pub fn get_child(&self, koid: &Koid, rights: Rights) -> Result<Thread, Status> {
let mut handle: zx_handle_t = Default::default();
let status = unsafe {
sys::zx_object_get_child(self.raw_handle(), koid.raw_koid(), rights.bits(), &mut handle)
};
ok(status)?;
Ok(Thread::from(unsafe { Handle::from_raw(handle) }))
}
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)
}
pub fn info_maps<'a>(
&self,
info_out: &'a mut [std::mem::MaybeUninit<MapInfo>],
) -> Result<(&'a mut [MapInfo], &'a mut [std::mem::MaybeUninit<MapInfo>], usize), Status> {
object_get_info::<ProcessMapsInfo>(self.as_handle_ref(), info_out)
}
pub fn info_vmos<'a>(
&self,
info_out: &'a mut [std::mem::MaybeUninit<VmoInfo>],
) -> Result<(&'a mut [VmoInfo], &'a mut [std::mem::MaybeUninit<VmoInfo>], usize), Status> {
object_get_info::<ProcessVmoInfo>(self.as_handle_ref(), info_out)
}
}
impl Task for Process {}
#[cfg(test)]
mod tests {
use crate::cprng_draw;
use assert_matches::assert_matches;
use std::ffi::CString;
use std::mem::MaybeUninit;
use zx::{
sys, system_get_page_size, AsHandleRef, Handle, Instant, MapDetails, ProcessInfo,
ProcessInfoFlags, ProcessOptions, Signals, Task, TaskStatsInfo, VmarFlags, Vmo,
};
#[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();
assert!(matches!(task_stats,
TaskStatsInfo {
mem_mapped_bytes,
mem_private_bytes,
mem_shared_bytes,
mem_scaled_shared_bytes,
mem_fractional_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();
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, Instant::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() {
let binpath = CString::new("/pkg/bin/sleep_forever_util").unwrap();
let process = fdio::spawn(
&fuchsia_runtime::job_default(),
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, Instant::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();
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 mut data = vec![MaybeUninit::uninit(); 1];
let (returned, _, available) = process.info_maps(&mut data).unwrap();
assert_eq!(returned.len(), 1);
assert!(available > 0);
let total = available + 10;
let mut data = vec![MaybeUninit::uninit(); total];
let (info, _, available) = process.info_maps(&mut data).unwrap();
assert_eq!(info.len(), available);
let count = info
.iter()
.filter(|info| match info.details() {
MapDetails::Mapping(d) => d.vmo_koid == vmo_koid,
_ => false,
})
.count();
assert_eq!(count, 2);
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();
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();
let count = info
.iter()
.filter(|info| match info.details() {
MapDetails::Mapping(d) => d.vmo_koid == vmo_koid,
_ => false,
})
.count();
assert_eq!(count, 2);
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() {
let process = fuchsia_runtime::process_self();
let vmo = Vmo::create(system_get_page_size() as u64).unwrap();
let vmo_koid = vmo.get_koid().unwrap();
let mut data = vec![MaybeUninit::uninit(); 2048];
let (info, _, available) = process.info_vmos(&mut data).unwrap();
assert_eq!(info.len(), available);
let count = info.iter().filter(|map| map.koid == vmo_koid).count();
assert_eq!(count, 1);
}
#[test]
fn info_vmos_vec() {
let process = fuchsia_runtime::process_self();
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();
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();
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::Rights::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::Rights::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();
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::Rights::NONE).unwrap().get_koid().unwrap(),
threads_koids[0]
);
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::Rights::NONE).unwrap().get_koid().unwrap(),
threads_koids[0]
);
assert_eq!(
process.get_child(&threads_koids[1], zx::Rights::NONE).unwrap().get_koid().unwrap(),
threads_koids[1]
);
process.kill().unwrap();
process.wait_handle(Signals::TASK_TERMINATED, Instant::INFINITE).unwrap();
drop(thread1_suspended);
drop(thread2_suspended);
assert!(process.threads().unwrap().is_empty());
}
}