blob: 41131495fe28f04cd9d5842bfab8b330b640a01b [file]
// Copyright 2026 The Fuchsia Authors
//
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file or at
// https://opensource.org/licenses/MIT
use crate::object::{
BusTransactionInitiatorDispatcher, Dispatcher, HandleValue, JobDispatcher, MsiDispatcher,
SocketDispatcher, ThreadDispatcher, TimerDispatcher, VmAddressRegionDispatcher,
VmObjectDispatcher,
};
use crate::user_copy::UserOutPtr;
use debug::ltracef;
use syscalls_macro::syscall;
use zerocopy::{FromBytes, Immutable, IntoBytes};
use zx_status::Status;
use zx_types::*;
const LOCAL_TRACE: u32 = 0;
// Specialize the conversion to work for any version that is a subset of zx_info_vmo_t.
// Being a subset the full zx_info_vmo_t can just be copied over.
fn convert_vmo_info_v1(vmo: &zx_info_vmo_t) -> zx_info_vmo_v1_t {
zx_info_vmo_v1_t {
koid: vmo.koid,
name: vmo.name,
size_bytes: vmo.size_bytes,
parent_koid: vmo.parent_koid,
num_children: vmo.num_children,
num_mappings: vmo.num_mappings,
share_count: vmo.share_count,
flags: vmo.flags,
committed_bytes: vmo.committed_bytes,
handle_rights: vmo.handle_rights,
cache_policy: vmo.cache_policy,
..Default::default()
}
}
fn convert_vmo_info_v2(vmo: &zx_info_vmo_t) -> zx_info_vmo_v2_t {
zx_info_vmo_v2_t {
koid: vmo.koid,
name: vmo.name,
size_bytes: vmo.size_bytes,
parent_koid: vmo.parent_koid,
num_children: vmo.num_children,
num_mappings: vmo.num_mappings,
share_count: vmo.share_count,
flags: vmo.flags,
committed_bytes: vmo.committed_bytes,
handle_rights: vmo.handle_rights,
cache_policy: vmo.cache_policy,
metadata_bytes: vmo.metadata_bytes,
committed_change_events: vmo.committed_change_events,
..Default::default()
}
}
fn convert_vmo_info_v3(vmo: &zx_info_vmo_t) -> zx_info_vmo_v3_t {
zx_info_vmo_v3_t {
koid: vmo.koid,
name: vmo.name,
size_bytes: vmo.size_bytes,
parent_koid: vmo.parent_koid,
num_children: vmo.num_children,
num_mappings: vmo.num_mappings,
share_count: vmo.share_count,
flags: vmo.flags,
committed_bytes: vmo.committed_bytes,
handle_rights: vmo.handle_rights,
cache_policy: vmo.cache_policy,
metadata_bytes: vmo.metadata_bytes,
committed_change_events: vmo.committed_change_events,
populated_bytes: vmo.populated_bytes,
..Default::default()
}
}
// Current second version is an extension of v1; simply copy over the
// earlier header and context.arch fields.
fn convert_thread_exception_report_v1(info: &zx_exception_report_t) -> zx_exception_report_v1_t {
// SAFETY: `info` is a valid `zx_exception_report_t` (40 bytes). The 32-byte prefix
// corresponds to `zx_exception_report_v1_t` (8-byte header + 24-byte arch exception data).
let src_bytes: &[u8] = unsafe {
core::slice::from_raw_parts(
(info as *const zx_exception_report_t).cast::<u8>(),
core::mem::size_of::<zx_exception_report_v1_t>(),
)
};
let (v1, _) = zx_exception_report_v1_t::read_from_prefix(src_bytes)
.expect("slice length matches size_of::<zx_exception_report_v1_t>()");
v1
}
// Copies to usermode the actual (number of records written) and the avail (number of records
// available).
fn actual_avail_result(
actual: usize,
avail: usize,
user_actual: UserOutPtr<usize>,
user_avail: UserOutPtr<usize>,
) -> Result<(), Status> {
if !user_actual.is_null() {
user_actual.write(actual)?;
}
if !user_avail.is_null() {
user_avail.write(avail)?;
}
Ok(())
}
fn single_record_bytes(
dst_buffer: UserOutPtr<u8>,
dst_buffer_size: usize,
user_actual: UserOutPtr<usize>,
user_avail: UserOutPtr<usize>,
src_bytes: &[u8],
) -> Result<(), Status> {
let actual = if dst_buffer_size >= src_bytes.len() {
dst_buffer.copy_slice_to_user(src_bytes)?;
1
} else {
0
};
actual_avail_result(actual, 1, user_actual, user_avail)?;
if actual == 0 {
return Err(Status::BUFFER_TOO_SMALL);
}
Ok(())
}
// Copies a single record, |src_record|, into the user buffer |dst_buffer| of size
// |dst_buffer_size|. If the copy succeeds, the value 1 is copied into |user_avail|.
fn single_record_result<T: IntoBytes + Immutable>(
dst_buffer: UserOutPtr<u8>,
dst_buffer_size: usize,
user_actual: UserOutPtr<usize>,
user_avail: UserOutPtr<usize>,
src_record: &T,
) -> Result<(), Status> {
let record_size = core::mem::size_of::<T>();
let actual = if dst_buffer_size >= record_size {
dst_buffer.reinterpret::<T>().copy_to_user(src_record)?;
1
} else {
0
};
actual_avail_result(actual, 1, user_actual, user_avail)?;
if actual == 0 {
return Err(Status::BUFFER_TOO_SMALL);
}
Ok(())
}
unsafe extern "C" {
fn cpp_object_get_info_cpp_types(
handle: zx_handle_t,
topic: u32,
buffer: *mut core::ffi::c_void,
buffer_size: usize,
actual: *mut usize,
avail: *mut usize,
) -> zx_status_t;
}
// actual is an optional return parameter for the number of records returned
// avail is an optional return parameter for the number of records available
//
// Topics which return a fixed number of records will return ZX_ERR_BUFFER_TOO_SMALL
// if there is not enough buffer space provided.
// This allows for zx_object_get_info(handle, topic, &info, sizeof(info), NULL, NULL)
#[syscall]
pub fn sys_object_get_info(
handle: HandleValue,
topic: u32,
buffer: UserOutPtr<u8>,
buffer_size: usize,
actual: UserOutPtr<usize>,
avail: UserOutPtr<usize>,
) -> Result<(), Status> {
ltracef!("handle {:?} topic {}\n", handle, topic);
/// Helper macro for single-record topic queries that obtain a typed dispatcher with
/// `ZX_RIGHT_INSPECT` (or rights) and copy the single record into user buffers using
/// `single_record_result`.
macro_rules! single_record_info {
// Infallible method call on a dispatcher with ZX_RIGHT_INSPECT.
($dispatcher_type:ty, $method:ident) => {{
let dispatcher =
Dispatcher::get_with_rights::<$dispatcher_type>(handle, ZX_RIGHT_INSPECT)?;
let record = dispatcher.$method();
single_record_result(buffer, buffer_size, actual, avail, &record)?;
Ok(())
}};
// Fallible method call on a dispatcher with ZX_RIGHT_INSPECT.
($dispatcher_type:ty, $method:ident?) => {{
let dispatcher =
Dispatcher::get_with_rights::<$dispatcher_type>(handle, ZX_RIGHT_INSPECT)?;
let record = dispatcher.$method()?;
single_record_result(buffer, buffer_size, actual, avail, &record)?;
Ok(())
}};
// Custom closure/expression receiving the dispatcher with ZX_RIGHT_INSPECT.
($dispatcher_type:ty, |$disp:ident| $body:expr) => {{
let $disp = Dispatcher::get_with_rights::<$dispatcher_type>(handle, ZX_RIGHT_INSPECT)?;
let record = $body;
single_record_result(buffer, buffer_size, actual, avail, &record)?;
Ok(())
}};
// Closure/expression with handle rights lookup.
(with_rights: |$disp:ident, $rights:ident| $body:expr) => {{
let ($disp, $rights) = Dispatcher::get_dispatcher_and_rights(handle)?;
let record = $body;
single_record_result(buffer, buffer_size, actual, avail, &record)?;
Ok(())
}};
}
match topic {
ZX_INFO_HANDLE_BASIC => {
single_record_info!(with_rights: |dispatcher, rights| {
dispatcher.get_handle_info(rights)
})
}
ZX_INFO_JOB_CHILDREN | ZX_INFO_JOB_PROCESSES => {
let job = Dispatcher::get_with_rights::<JobDispatcher>(handle, ZX_RIGHT_ENUMERATE)?;
// Don't recurse; we only want the job's direct children.
let max =
if buffer_size == 0 { 0 } else { buffer_size / core::mem::size_of::<zx_koid_t>() };
let is_jobs = topic == ZX_INFO_JOB_CHILDREN;
let (count, avail_count) =
job.enumerate_children(buffer.reinterpret(), max, is_jobs)?;
actual_avail_result(count, avail_count, actual, avail)?;
Ok(())
}
ZX_INFO_THREAD => single_record_info!(ThreadDispatcher, get_info_for_userspace),
ZX_INFO_THREAD_EXCEPTION_REPORT_V1 => {
single_record_info!(ThreadDispatcher, |thread| {
let report = thread.get_exception_report()?;
convert_thread_exception_report_v1(&report)
})
}
ZX_INFO_THREAD_EXCEPTION_REPORT => {
let thread = Dispatcher::get_with_rights::<ThreadDispatcher>(handle, ZX_RIGHT_INSPECT)?;
let report = thread.get_exception_report()?;
// SAFETY: `report` is a valid, fully initialized 40-byte `zx_exception_report_t` struct.
let src_bytes: &[u8] = unsafe {
core::slice::from_raw_parts(
(&report as *const zx_exception_report_t).cast::<u8>(),
core::mem::size_of::<zx_exception_report_t>(),
)
};
single_record_bytes(buffer, buffer_size, actual, avail, src_bytes)?;
Ok(())
}
ZX_INFO_THREAD_STATS => single_record_info!(ThreadDispatcher, get_stats_for_userspace?),
ZX_INFO_TASK_RUNTIME | ZX_INFO_TASK_RUNTIME_V1 => {
let dispatcher = Dispatcher::get_with_rights::<Dispatcher>(handle, ZX_RIGHT_INSPECT)?;
let runtime: zx_info_task_runtime_t =
if let Some(job) = dispatcher.downcast::<JobDispatcher>() {
job.get_runtime_stats()
} else if let Some(thread) = dispatcher.downcast::<ThreadDispatcher>() {
thread.get_runtime_stats()
} else {
// SAFETY: Call C++ FFI helper for topics handled by C++ dispatcher instances (e.g. ProcessDispatcher).
let status = unsafe {
cpp_object_get_info_cpp_types(
handle.raw_value(),
topic,
buffer.as_ptr() as *mut core::ffi::c_void,
buffer_size,
actual.as_ptr(),
avail.as_ptr(),
)
};
return Status::ok(status);
};
if topic == ZX_INFO_TASK_RUNTIME_V1 {
let v1 = zx_info_task_runtime_v1_t {
cpu_time: runtime.cpu_time,
queue_time: runtime.queue_time,
};
single_record_result(buffer, buffer_size, actual, avail, &v1)?;
} else {
single_record_result(buffer, buffer_size, actual, avail, &runtime)?;
}
Ok(())
}
ZX_INFO_VMO | ZX_INFO_VMO_V1 | ZX_INFO_VMO_V2 | ZX_INFO_VMO_V3 => {
let (dispatcher, rights) = Dispatcher::get_dispatcher_and_rights(handle)?;
let vmo = dispatcher.downcast::<VmObjectDispatcher>().ok_or(Status::WRONG_TYPE)?;
let info = vmo.get_vmo_info(rights);
match topic {
ZX_INFO_VMO_V1 => {
let v1 = convert_vmo_info_v1(&info);
single_record_result(buffer, buffer_size, actual, avail, &v1)?;
}
ZX_INFO_VMO_V2 => {
let v2 = convert_vmo_info_v2(&info);
single_record_result(buffer, buffer_size, actual, avail, &v2)?;
}
ZX_INFO_VMO_V3 => {
let v3 = convert_vmo_info_v3(&info);
single_record_result(buffer, buffer_size, actual, avail, &v3)?;
}
_ => {
single_record_result(buffer, buffer_size, actual, avail, &info)?;
}
}
Ok(())
}
ZX_INFO_VMAR => single_record_info!(VmAddressRegionDispatcher, get_vmar_info),
ZX_INFO_HANDLE_COUNT => {
single_record_info!(Dispatcher, |dispatcher| {
zx_info_handle_count_t { handle_count: dispatcher.current_handle_count() }
})
}
ZX_INFO_SOCKET => single_record_info!(SocketDispatcher, get_info),
ZX_INFO_JOB => single_record_info!(JobDispatcher, get_info),
ZX_INFO_TIMER => single_record_info!(TimerDispatcher, get_info),
ZX_INFO_MSI => single_record_info!(MsiDispatcher, get_info),
ZX_INFO_BTI => single_record_info!(BusTransactionInitiatorDispatcher, get_info),
// C++ only topics (and VM map/vmo enumeration, system resources, process info, etc.)
_ => {
// SAFETY: Call C++ FFI helper for topics handled by C++ dispatcher instances or subsystems.
let status = unsafe {
cpp_object_get_info_cpp_types(
handle.raw_value(),
topic,
buffer.as_ptr() as *mut core::ffi::c_void,
buffer_size,
actual.as_ptr(),
avail.as_ptr(),
)
};
Status::ok(status)
}
}
}