zx/
info.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
// Copyright 2018 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 object information.

use crate::{ok, sys, HandleRef, Status};
use std::mem::MaybeUninit;
use std::ops::Deref;
use zerocopy::{FromBytes, Immutable};

// Tuning constants for get_info_vec(). pub(crate) to support unit tests.
pub(crate) const INFO_VEC_SIZE_INITIAL: usize = 16;
const INFO_VEC_SIZE_PAD: usize = 2;

#[derive(Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash)]
#[repr(transparent)]
pub struct Topic(sys::zx_object_info_topic_t);

impl Deref for Topic {
    type Target = sys::zx_object_info_topic_t;

    fn deref(&self) -> &Self::Target {
        &self.0
    }
}

/// A query to get info about a zircon object.
///
/// # Safety
///
/// `InfoTy` must be the same size as what the kernel expects to return for the provided topic.
pub(crate) unsafe trait ObjectQuery {
    /// A `Topic` identifying this query.
    const TOPIC: Topic;
    /// The datatype returned by querying for Self::TOPIC.
    type InfoTy: FromBytes + Immutable;
}

assoc_values!(Topic, [
    NONE = sys::ZX_INFO_NONE;
    HANDLE_VALID = sys::ZX_INFO_HANDLE_VALID;
    HANDLE_BASIC = sys::ZX_INFO_HANDLE_BASIC;
    PROCESS = sys::ZX_INFO_PROCESS;
    PROCESS_THREADS = sys::ZX_INFO_PROCESS_THREADS;
    VMAR = sys::ZX_INFO_VMAR;
    VMAR_MAPS = sys::ZX_INFO_VMAR_MAPS;
    JOB_CHILDREN = sys::ZX_INFO_JOB_CHILDREN;
    JOB_PROCESSES = sys::ZX_INFO_JOB_PROCESSES;
    THREAD = sys::ZX_INFO_THREAD;
    THREAD_EXCEPTION_REPORT = sys::ZX_INFO_THREAD_EXCEPTION_REPORT;
    TASK_STATS = sys::ZX_INFO_TASK_STATS;
    TASK_RUNTIME = sys::ZX_INFO_TASK_RUNTIME;
    PROCESS_MAPS = sys::ZX_INFO_PROCESS_MAPS;
    PROCESS_VMOS = sys::ZX_INFO_PROCESS_VMOS;
    THREAD_STATS = sys::ZX_INFO_THREAD_STATS;
    CPU_STATS = sys::ZX_INFO_CPU_STATS;
    KMEM_STATS = sys::ZX_INFO_KMEM_STATS;
    KMEM_STATS_EXTENDED = sys::ZX_INFO_KMEM_STATS_EXTENDED;
    KMEM_STATS_COMPRESSION = sys::ZX_INFO_KMEM_STATS_COMPRESSION;
    RESOURCE = sys::ZX_INFO_RESOURCE;
    HANDLE_COUNT = sys::ZX_INFO_HANDLE_COUNT;
    BTI = sys::ZX_INFO_BTI;
    PROCESS_HANDLE_STATS = sys::ZX_INFO_PROCESS_HANDLE_STATS;
    SOCKET = sys::ZX_INFO_SOCKET;
    VMO = sys::ZX_INFO_VMO;
    JOB = sys::ZX_INFO_JOB;
    IOB = sys::ZX_INFO_IOB;
    IOB_REGIONS = sys::ZX_INFO_IOB_REGIONS;
]);

/// Query information about a zircon object. Returns a valid slice and any remaining capacity on
/// success, along with a count of how many infos the kernel had available.
pub(crate) fn object_get_info<'a, Q: ObjectQuery>(
    handle: HandleRef<'_>,
    out: &'a mut [MaybeUninit<Q::InfoTy>],
) -> Result<(&'a mut [Q::InfoTy], &'a mut [MaybeUninit<Q::InfoTy>], usize), Status>
where
    Q::InfoTy: FromBytes + Immutable,
{
    let mut actual = 0;
    let mut avail = 0;

    // SAFETY: The slice pointer is known valid to write to for `size_of_val` because it came from
    // a mutable reference.
    let status = unsafe {
        sys::zx_object_get_info(
            handle.raw_handle(),
            *Q::TOPIC,
            out.as_mut_ptr().cast::<u8>(),
            std::mem::size_of_val(out),
            &mut actual,
            &mut avail,
        )
    };
    ok(status)?;

    let (initialized, uninit) = out.split_at_mut(actual);

    // TODO(https://fxbug.dev/352398385) switch to MaybeUninit::slice_assume_init_mut
    // SAFETY: these values have been initialized by the kernel and implement the right zerocopy
    // traits to be instantiated from arbitrary bytes.
    let initialized: &mut [Q::InfoTy] = unsafe {
        std::slice::from_raw_parts_mut(
            initialized.as_mut_ptr().cast::<Q::InfoTy>(),
            initialized.len(),
        )
    };

    Ok((initialized, uninit, avail))
}

/// Query information about a zircon object, expecting only a single info in the return.
pub(crate) fn object_get_info_single<Q: ObjectQuery>(
    handle: HandleRef<'_>,
) -> Result<Q::InfoTy, Status>
where
    Q::InfoTy: Copy + FromBytes + Immutable,
{
    let mut info = MaybeUninit::<Q::InfoTy>::uninit();
    let (info, _, _) = object_get_info::<Q>(handle, std::slice::from_mut(&mut info))?;
    Ok(info[0])
}

/// Query multiple records of information about a zircon object.
/// Returns a vec of Q::InfoTy on success.
/// Intended for calls that return multiple small objects.
pub(crate) fn object_get_info_vec<Q: ObjectQuery>(
    handle: HandleRef<'_>,
) -> Result<Vec<Q::InfoTy>, Status> {
    // Start with a few slots
    let mut out = Vec::<Q::InfoTy>::with_capacity(INFO_VEC_SIZE_INITIAL);
    loop {
        let (init, _uninit, avail) =
            object_get_info::<Q>(handle.clone(), out.spare_capacity_mut())?;
        let num_initialized = init.len();
        if num_initialized == avail {
            // SAFETY: the kernel has initialized all of these values.
            unsafe { out.set_len(num_initialized) };
            return Ok(out);
        } else {
            // The number of records may increase between retries; reserve space for that.
            // TODO(https://fxbug.dev/384531846) grow more conservatively
            let needed_space = avail * INFO_VEC_SIZE_PAD;
            if let Some(to_grow) = needed_space.checked_sub(out.capacity()) {
                out.reserve_exact(to_grow);
            }

            // We may ask the kernel to copy more than a page in the next iteration of this loop, so
            // prefault each of the pages in the region.
            //
            // Currently Zircon has a very slow path when performing large usercopies into freshly
            // allocated buffers. In practice our large heap allocated buffers are typically newly
            // mapped and do not yet have any pages faulted in. Unlike a copy performed by
            // userspace, Zircon itself has to handle any page faults and in order to avoid
            // deadlocking with user pagers (among other issues), it will drop locks it holds,
            // service the page fault, and then reacquire the locks (see linked bug below).
            //
            // In order to avoid hitting this slow path, we want to ensure that all of the pages of
            // our Vec have already been faulted in before we hand off to the kernel to write data
            // into them. Ideally the kernel would handle this for us but in the meantime we can
            // shave a lot of time from the heaviest zx_object_get_info calls by faulting the pages
            // ourselves.
            //
            // We only want to do this when the bindings themselves are responsible for creating the
            // vector, because callers who manage their own buffer for the syscall may be doing
            // their own page management for the buffer and we don't want to penalize them.
            // TODO(https://fxbug.dev/383401884) remove once zircon prefaults get_info usercopies
            // TODO(https://fxbug.dev/384941113) consider zx_vmar_op_range on root vmar instead
            let maybe_unfaulted = out.spare_capacity_mut();

            // SAFETY: zerocopy::FromBytes means it's OK to write arbitrary bytes to the slice.
            // TODO(https://github.com/rust-lang/rust/issues/93092) use MaybeUninit::slice_as_bytes_mut
            let maybe_unfaulted_bytes: &mut [MaybeUninit<u8>] = unsafe {
                std::slice::from_raw_parts_mut(
                    maybe_unfaulted.as_mut_ptr().cast::<MaybeUninit<u8>>(),
                    std::mem::size_of_val(maybe_unfaulted),
                )
            };

            // chunks_mut doesn't give us page alignment but that doesn't matter for pre-faulting.
            for page in maybe_unfaulted_bytes.chunks_mut(crate::system_get_page_size() as usize) {
                // This writes a single byte to each page to avoid unnecessary memory traffic. A
                // non-zero byte is written so that these pages won't get picked up by Zircon's
                // zero page scanner.
                page[0] = MaybeUninit::new(1u8);
            }
        }
    }
}