| // Copyright 2016 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 super::io_buffer_dispatcher::IoBufferDispatcher; | |
| use super::process_dispatcher::ProcessDispatcher; | |
| use super::vm_object_dispatcher::{VmObjectDispatcher, VmoOwnership, vmo_to_info_entry}; | |
| use crate::vm::arch_vm_aspace::{ | |
| ARCH_MMU_FLAG_INVALID, ARCH_MMU_FLAG_PERM_EXECUTE, ARCH_MMU_FLAG_PERM_READ, | |
| ARCH_MMU_FLAG_PERM_WRITE, ArchMmuFlags, | |
| }; | |
| use crate::vm::attribution::{AttributionCounts, FractionalBytes}; | |
| use crate::vm::vm_address_region::{VmAddressRegion, VmAspaceGuard, VmEnumerator}; | |
| use crate::vm::vm_aspace::VmAspace; | |
| use crate::vm::vm_mapping::{FlagsRange, VmMapping}; | |
| use crate::vm::vm_object::VmObject; | |
| use core::ffi::CStr; | |
| use core::pin::Pin; | |
| use fbl::RefPtr; | |
| use zx_status::Status; | |
| use zx_types::{ | |
| ZX_INFO_MAPS_TYPE_ASPACE, ZX_INFO_MAPS_TYPE_MAPPING, ZX_INFO_MAPS_TYPE_VMAR, ZX_MAX_NAME_LEN, | |
| ZX_VM_PERM_EXECUTE, ZX_VM_PERM_READ, ZX_VM_PERM_WRITE, zx_info_maps_mapping_t, zx_info_maps_t, | |
| zx_info_vmo_t, | |
| }; | |
| #[cfg(console_enabled)] | |
| use super::diagnostics_ffi::{ | |
| dump_all_peer_dispatchers, dump_all_vm_objects, dump_handles_for_koid, | |
| dump_orphaned_mapped_vmos, dump_orphaned_slices, dump_peer_dispatchers_by_koid, | |
| dump_process_address_space, dump_process_handles, dump_process_memory_usage, | |
| dump_process_vm_objects, dump_vm_object_cow_tree, | |
| }; | |
| #[cfg(console_enabled)] | |
| use super::handle::HandleTableArena; | |
| #[cfg(console_enabled)] | |
| use super::job_dispatcher::{JobDispatcher, JobEnumerator}; | |
| #[cfg(console_enabled)] | |
| use super::singleton::get_root_job_dispatcher; | |
| #[cfg(console_enabled)] | |
| use crate::console::{CMD_AVAIL_NORMAL, CmdArgs, static_command}; | |
| #[cfg(console_enabled)] | |
| use crate::kernel::deadline::SlackMode; | |
| #[cfg(console_enabled)] | |
| use crate::kernel::thread; | |
| #[cfg(console_enabled)] | |
| use crate::platform_rs::timer::DurationMono; | |
| #[cfg(console_enabled)] | |
| use core::ffi::{c_char, c_int, c_void}; | |
| #[cfg(console_enabled)] | |
| use core::ptr; | |
| #[cfg(console_enabled)] | |
| use core::sync::atomic::{AtomicBool, AtomicUsize, Ordering}; | |
| #[cfg(console_enabled)] | |
| use kprint::{kformat, kprintln}; | |
| #[cfg(console_enabled)] | |
| use zx_types::{ | |
| ZX_OBJ_TYPE_BTI, ZX_OBJ_TYPE_CHANNEL, ZX_OBJ_TYPE_DEBUGLOG, ZX_OBJ_TYPE_EVENT, | |
| ZX_OBJ_TYPE_EVENTPAIR, ZX_OBJ_TYPE_EXCEPTION, ZX_OBJ_TYPE_FIFO, ZX_OBJ_TYPE_GUEST, | |
| ZX_OBJ_TYPE_INTERRUPT, ZX_OBJ_TYPE_IOB, ZX_OBJ_TYPE_IOMMU, ZX_OBJ_TYPE_JOB, ZX_OBJ_TYPE_PAGER, | |
| ZX_OBJ_TYPE_PMT, ZX_OBJ_TYPE_PORT, ZX_OBJ_TYPE_PROCESS, ZX_OBJ_TYPE_PROFILE, | |
| ZX_OBJ_TYPE_RESOURCE, ZX_OBJ_TYPE_SOCKET, ZX_OBJ_TYPE_SUSPEND_TOKEN, ZX_OBJ_TYPE_THREAD, | |
| ZX_OBJ_TYPE_TIMER, ZX_OBJ_TYPE_UPPER_BOUND, ZX_OBJ_TYPE_VCPU, ZX_OBJ_TYPE_VMAR, | |
| ZX_OBJ_TYPE_VMO, ZX_POL_ACTION_ALLOW, ZX_POL_ACTION_ALLOW_EXCEPTION, ZX_POL_ACTION_DENY, | |
| ZX_POL_ACTION_DENY_EXCEPTION, ZX_POL_ACTION_KILL, ZX_POL_ACTION_MAX, | |
| ZX_POL_AMBIENT_MARK_VMO_EXEC, ZX_POL_BAD_HANDLE, ZX_POL_MAX, ZX_POL_NEW_ANY, | |
| ZX_POL_NEW_CHANNEL, ZX_POL_NEW_EVENT, ZX_POL_NEW_EVENTPAIR, ZX_POL_NEW_FIFO, ZX_POL_NEW_IOB, | |
| ZX_POL_NEW_PAGER, ZX_POL_NEW_PORT, ZX_POL_NEW_PROCESS, ZX_POL_NEW_PROFILE, ZX_POL_NEW_SAMPLER, | |
| ZX_POL_NEW_SOCKET, ZX_POL_NEW_TIMER, ZX_POL_NEW_VMO, ZX_POL_OVERRIDE_ALLOW, | |
| ZX_POL_OVERRIDE_DENY, ZX_POL_VMAR_WX, ZX_POL_WRONG_OBJECT, ZX_TASK_RETCODE_SYSCALL_KILL, | |
| zx_koid_t, | |
| }; | |
| /// Abstraction around writing `zx_info_vmo_t`s to user space. As there are multiple versions of the | |
| /// `zx_info_vmo_t` struct and we generally just want to produce the most recent one, | |
| /// implementations of this trait deal with converting from the most recent representation to the | |
| /// version requested by the user. | |
| /// Since conversion may need to occur element by element this provides no multi-element array | |
| /// writes like the regular user_copy interface. | |
| pub trait VmoInfoWriter { | |
| /// Writes a single `zx_info_vmo_t` at the given element offset. Return values are same as | |
| /// `user_ptr::copy_to_user`. | |
| fn write(&mut self, vmo: &zx_info_vmo_t, offset: usize) -> Result<(), Status>; | |
| /// Increases the base offset such that writes to offset 0 write to this offset. | |
| fn add_offset(&mut self, offset: usize); | |
| } | |
| /// Similar to [`VmoInfoWriter`], but for `zx_info_maps_t`. | |
| pub trait VmarMapsInfoWriter { | |
| /// Writes a single `zx_info_maps_t` at the given element offset. Return values are same as | |
| /// `user_ptr::copy_to_user`. | |
| fn write(&mut self, maps: &zx_info_maps_t, offset: usize) -> Result<(), Status>; | |
| /// Increases the base offset such that writes to offset 0 write to this offset. | |
| fn add_offset(&mut self, offset: usize); | |
| } | |
| fn copy_cstr_to_name(dst: &mut [u8; ZX_MAX_NAME_LEN], src: &CStr) { | |
| let bytes = src.to_bytes(); | |
| let len = bytes.len().min(dst.len() - 1); | |
| dst[..len].copy_from_slice(&bytes[..len]); | |
| dst[len..].fill(0); | |
| } | |
| /// Returns the NUL-terminated prefix of a `ZX_MAX_NAME_LEN` name buffer for printing. | |
| #[cfg(console_enabled)] | |
| fn c_name(name: &[u8]) -> &CStr { | |
| CStr::from_bytes_until_nul(name).unwrap_or(c"") | |
| } | |
| fn arch_mmu_flags_to_vm_flags(arch_mmu_flags: ArchMmuFlags) -> u32 { | |
| if (arch_mmu_flags & ARCH_MMU_FLAG_INVALID) != 0 { | |
| return 0; | |
| } | |
| let mut ret = 0; | |
| if (arch_mmu_flags & ARCH_MMU_FLAG_PERM_READ) != 0 { | |
| ret |= ZX_VM_PERM_READ; | |
| } | |
| if (arch_mmu_flags & ARCH_MMU_FLAG_PERM_WRITE) != 0 { | |
| ret |= ZX_VM_PERM_WRITE; | |
| } | |
| if (arch_mmu_flags & ARCH_MMU_FLAG_PERM_EXECUTE) != 0 { | |
| ret |= ZX_VM_PERM_EXECUTE; | |
| } | |
| ret | |
| } | |
| /// Number of entries that are counted, but not written because the user buffer is already full, | |
| /// between checks for contention on the aspace lock. | |
| /// | |
| /// Writing an entry always releases and re-acquires the aspace lock (the entry is built and copied | |
| /// out via `call_unlocked`), which lets other threads of the target process make progress. | |
| /// Counting an entry does not, so once the user buffer is full the remainder of the walk would | |
| /// otherwise hold the aspace lock continuously, stalling page faults and VMAR operations of the | |
| /// target process for the whole duration of the walk (several milliseconds for address spaces with | |
| /// many thousands of mappings). To bound this, the lock is yielded if it is found to be contested | |
| /// every this many counted entries. | |
| const COUNT_ONLY_ENTRIES_BETWEEN_CONTENTION_CHECKS: usize = 32; | |
| /// Called by the enumerators below for every entry that is counted but not written, with `count` | |
| /// being the number of entries enumerated so far. Every | |
| /// `COUNT_ONLY_ENTRIES_BETWEEN_CONTENTION_CHECKS` entries, if the aspace lock held by `guard` is | |
| /// contested, briefly releases it so that the waiting threads can acquire it. | |
| /// | |
| /// Dropping the lock is permitted at any point of a `VmEnumerator` callback, since | |
| /// `enumerate_children` pauses its enumerator across each callback, with the usual caveat that | |
| /// objects observed before the lock was dropped may have been unmapped meanwhile. | |
| #[inline] | |
| fn yield_aspace_lock_if_contested( | |
| count: usize, | |
| vmar: &VmAddressRegion, | |
| guard: Pin<&mut VmAspaceGuard<'_>>, | |
| ) { | |
| if count.is_multiple_of(COUNT_ONLY_ENTRIES_BETWEEN_CONTENTION_CHECKS) | |
| && vmar.lock().raw_mutex().is_contested() | |
| { | |
| guard.call_unlocked(|| {}); | |
| } | |
| } | |
| /// Builds a list of all VMARs and mappings in a `VmAspace` or `VmAddressRegion`. | |
| struct AspaceEnumerator<'a, W: VmarMapsInfoWriter> { | |
| writer: &'a mut W, | |
| depth_offset: usize, | |
| max: usize, | |
| available: usize, | |
| } | |
| impl<'a, W: VmarMapsInfoWriter> AspaceEnumerator<'a, W> { | |
| fn new(writer: &'a mut W, depth_offset: usize, max: usize, avail_start: usize) -> Self { | |
| Self { writer, depth_offset, max, available: avail_start } | |
| } | |
| fn available(&self) -> usize { | |
| self.available | |
| } | |
| } | |
| impl<W: VmarMapsInfoWriter> VmEnumerator for AspaceEnumerator<'_, W> { | |
| fn on_vm_mapping( | |
| &mut self, | |
| map: &VmMapping, | |
| vmar: &VmAddressRegion, | |
| depth: u32, | |
| mut guard: Pin<&mut VmAspaceGuard<'_>>, | |
| ) -> Status { | |
| let map_base = map.base().0; | |
| let map_size = map.size(); | |
| let map_object_offset = map.object_offset(); | |
| let vmo: RefPtr<VmObject> = | |
| map.vmo_locked(guard.token()).expect("mapping in aspace must have a VMO"); | |
| let mut region_offset = 0; | |
| let mut entry = zx_info_maps_t::default(); | |
| while region_offset < map_size && map.is_alive_locked(guard.token()) { | |
| let region_base = map_base + region_offset; | |
| let protect_range: FlagsRange = | |
| map.arch_mmu_flags_range_locked(guard.token(), region_base); | |
| let region_len = (protect_range.region_top as usize) - region_base; | |
| if self.available < self.max { | |
| let mmu_flags: ArchMmuFlags = protect_range.mmu_flags; | |
| let available = self.available; | |
| let depth_offset = self.depth_offset; | |
| let writer = &mut *self.writer; | |
| let mut status = Ok(()); | |
| guard.as_mut().call_unlocked(|| { | |
| vmo.get_name(&mut entry.name); | |
| entry.base = region_base; | |
| entry.size = region_len; | |
| entry.depth = (depth as usize) + depth_offset; | |
| entry.r#type = ZX_INFO_MAPS_TYPE_MAPPING; | |
| let object_offset = map_object_offset + (region_base - map_base) as u64; | |
| let counts: AttributionCounts = | |
| vmo.get_attributed_memory_in_range(object_offset, region_len as u64); | |
| let total_scaled_bytes: FractionalBytes = counts.total_scaled_bytes(); | |
| entry.u.mapping = zx_info_maps_mapping_t { | |
| mmu_flags: arch_mmu_flags_to_vm_flags(mmu_flags), | |
| padding1: Default::default(), | |
| vmo_koid: vmo.user_id(), | |
| vmo_offset: object_offset, | |
| committed_bytes: counts.uncompressed_bytes, | |
| populated_bytes: counts.total_bytes(), | |
| committed_private_bytes: counts.private_uncompressed_bytes, | |
| populated_private_bytes: counts.total_private_bytes(), | |
| committed_scaled_bytes: counts.scaled_uncompressed_bytes.integral, | |
| populated_scaled_bytes: total_scaled_bytes.integral, | |
| committed_fractional_scaled_bytes: counts | |
| .scaled_uncompressed_bytes | |
| .fractional, | |
| populated_fractional_scaled_bytes: total_scaled_bytes.fractional, | |
| }; | |
| status = writer.write(&entry, available); | |
| }); | |
| if status.is_err() { | |
| return Status::INVALID_ARGS; | |
| } | |
| } else { | |
| yield_aspace_lock_if_contested(self.available, vmar, guard.as_mut()); | |
| } | |
| self.available += 1; | |
| region_offset += region_len; | |
| } | |
| Status::NEXT | |
| } | |
| fn on_vm_address_region( | |
| &mut self, | |
| vmar: &VmAddressRegion, | |
| depth: u32, | |
| mut guard: Pin<&mut VmAspaceGuard<'_>>, | |
| ) -> Status { | |
| if self.available < self.max { | |
| let mut entry = zx_info_maps_t::default(); | |
| copy_cstr_to_name(&mut entry.name, vmar.name()); | |
| entry.base = vmar.base().0; | |
| entry.size = vmar.size(); | |
| entry.depth = (depth as usize) + self.depth_offset; | |
| entry.r#type = ZX_INFO_MAPS_TYPE_VMAR; | |
| let available = self.available; | |
| let writer = &mut *self.writer; | |
| let mut status = Ok(()); | |
| guard.as_mut().call_unlocked(|| { | |
| status = writer.write(&entry, available); | |
| }); | |
| if status.is_err() { | |
| return Status::INVALID_ARGS; | |
| } | |
| } else { | |
| yield_aspace_lock_if_contested(self.available, vmar, guard.as_mut()); | |
| } | |
| self.available += 1; | |
| Status::NEXT | |
| } | |
| } | |
| /// Walks `target_aspace` and writes entries that describe it into `maps`, up to `max` entries. | |
| /// Returns `(actual, available)`, the number of entries written and the number of entries that | |
| /// could have been written. | |
| pub fn get_vm_aspace_maps( | |
| target_aspace: &VmAspace, | |
| maps: &mut impl VmarMapsInfoWriter, | |
| max: usize, | |
| ) -> Result<(usize, usize), Status> { | |
| if target_aspace.is_destroyed() { | |
| return Err(Status::BAD_STATE); | |
| } | |
| if max > 0 { | |
| let mut entry = zx_info_maps_t::default(); | |
| // SAFETY: `target_aspace` is a live `VmAspace`, and `VmAspace::rename` is only called | |
| // during `VmAspace` initialization before the object is published, so no concurrent | |
| // `rename` can occur. | |
| let name = unsafe { target_aspace.name_ptr() }; | |
| copy_cstr_to_name(&mut entry.name, name); | |
| entry.base = target_aspace.base(); | |
| entry.size = target_aspace.size(); | |
| entry.depth = 0; | |
| entry.r#type = ZX_INFO_MAPS_TYPE_ASPACE; | |
| if maps.write(&entry, 0).is_err() { | |
| return Err(Status::INVALID_ARGS); | |
| } | |
| } | |
| let root_vmar = target_aspace.root_vmar().ok_or(Status::BAD_STATE)?; | |
| let mut ae = AspaceEnumerator::new(maps, 1, max, 1); | |
| root_vmar.enumerate_children(&mut ae)?; | |
| let available = ae.available(); | |
| let actual = max.min(available); | |
| Ok((actual, available)) | |
| } | |
| /// Walks `target_vmar` and writes entries that describe it into `maps`, up to `max` entries. | |
| /// Returns `(actual, available)`, the number of entries written and the number of entries that | |
| /// could have been written. | |
| pub fn get_vmar_maps( | |
| target_vmar: &VmAddressRegion, | |
| maps: &mut impl VmarMapsInfoWriter, | |
| max: usize, | |
| ) -> Result<(usize, usize), Status> { | |
| let mut ae = AspaceEnumerator::new(maps, 0, max, 0); | |
| target_vmar.enumerate_children(&mut ae)?; | |
| let available = ae.available(); | |
| let actual = max.min(available); | |
| Ok((actual, available)) | |
| } | |
| /// Builds a list of all VMOs mapped into a `VmAspace`. | |
| struct AspaceVmoEnumerator<'a, W: VmoInfoWriter> { | |
| vmos: &'a mut W, | |
| max: usize, | |
| available: usize, | |
| } | |
| impl<'a, W: VmoInfoWriter> AspaceVmoEnumerator<'a, W> { | |
| fn new(vmos: &'a mut W, max: usize) -> Self { | |
| Self { vmos, max, available: 0 } | |
| } | |
| fn available(&self) -> usize { | |
| self.available | |
| } | |
| } | |
| impl<W: VmoInfoWriter> VmEnumerator for AspaceVmoEnumerator<'_, W> { | |
| fn on_vm_mapping( | |
| &mut self, | |
| map: &VmMapping, | |
| vmar: &VmAddressRegion, | |
| _depth: u32, | |
| mut guard: Pin<&mut VmAspaceGuard<'_>>, | |
| ) -> Status { | |
| if self.available < self.max { | |
| let vmo: RefPtr<VmObject> = | |
| map.vmo_locked(guard.token()).expect("mapping in aspace must have a VMO"); | |
| let available = self.available; | |
| let vmos = &mut *self.vmos; | |
| let mut status = Ok(()); | |
| guard.as_mut().call_unlocked(|| { | |
| let entry: zx_info_vmo_t = | |
| vmo_to_info_entry(&vmo, VmoOwnership::Mapping, /*handle_rights=*/ 0); | |
| status = vmos.write(&entry, available); | |
| }); | |
| if let Err(err) = status { | |
| return err; | |
| } | |
| } else { | |
| yield_aspace_lock_if_contested(self.available, vmar, guard.as_mut()); | |
| } | |
| self.available += 1; | |
| Status::NEXT | |
| } | |
| } | |
| /// Walks `target_aspace` and writes entries that describe its mapped VMOs into `vmos`, up to `max` | |
| /// entries. Returns `(actual, available)`, the number of entries written and the number of entries | |
| /// that could have been written. | |
| pub fn get_vm_aspace_vmos( | |
| target_aspace: &VmAspace, | |
| vmos: &mut impl VmoInfoWriter, | |
| max: usize, | |
| ) -> Result<(usize, usize), Status> { | |
| if target_aspace.is_destroyed() { | |
| return Err(Status::BAD_STATE); | |
| } | |
| let root_vmar = target_aspace.root_vmar().ok_or(Status::BAD_STATE)?; | |
| let mut ave = AspaceVmoEnumerator::new(vmos, max); | |
| root_vmar.enumerate_children(&mut ave)?; | |
| let available = ave.available(); | |
| let actual = available.min(max); | |
| Ok((actual, available)) | |
| } | |
| /// For every VMO in `process`'s handle table, writes an entry into `vmos`, up to `max` entries. | |
| /// Returns `(actual, available)`, the number of entries written and the number of entries that | |
| /// could have been written. | |
| pub fn get_process_vmos( | |
| process: &ProcessDispatcher, | |
| vmos: &mut impl VmoInfoWriter, | |
| max: usize, | |
| ) -> Result<(usize, usize), Status> { | |
| let mut actual = 0; | |
| let mut available = 0; | |
| // We may see multiple handles to the same VMO, but leave it to userspace to | |
| // do deduping. | |
| process.handle_table().for_each_handle_batched(|_handle, rights, disp| { | |
| if let Some(vmod) = disp.downcast::<VmObjectDispatcher>() { | |
| available += 1; | |
| if actual < max { | |
| let entry = vmo_to_info_entry(vmod.vmo(), VmoOwnership::Handle, rights); | |
| if vmos.write(&entry, actual).is_err() { | |
| return Err(Status::INVALID_ARGS); | |
| } | |
| actual += 1; | |
| } | |
| return Ok(()); | |
| } | |
| if let Some(iobd) = disp.downcast::<IoBufferDispatcher>() { | |
| available += iobd.region_count(); | |
| for i in 0..iobd.region_count() { | |
| if actual >= max { | |
| break; | |
| } | |
| let vmo = iobd.get_vmo(i).expect("region index in range"); | |
| let region_map_rights = iobd.get_map_rights(rights, i); | |
| let entry = vmo_to_info_entry(&vmo, VmoOwnership::IoBuffer, region_map_rights); | |
| if vmos.write(&entry, actual).is_err() { | |
| return Err(Status::INVALID_ARGS); | |
| } | |
| actual += 1; | |
| } | |
| } | |
| // We can skip this if it isn't a VMO or IOB | |
| Ok(()) | |
| })?; | |
| Ok((actual, available)) | |
| } | |
| /// Machinery to walk over a job tree and run a callback on each process. | |
| #[cfg(console_enabled)] | |
| struct ProcessWalker<F: FnMut(&ProcessDispatcher)> { | |
| cb: F, | |
| } | |
| #[cfg(console_enabled)] | |
| impl<F: FnMut(&ProcessDispatcher)> JobEnumerator for ProcessWalker<F> { | |
| fn on_process(&mut self, process: &ProcessDispatcher) -> bool { | |
| (self.cb)(process); | |
| true | |
| } | |
| } | |
| #[cfg(console_enabled)] | |
| fn make_process_walker<F: FnMut(&ProcessDispatcher)>(cb: F) -> ProcessWalker<F> { | |
| ProcessWalker { cb } | |
| } | |
| /// Machinery to walk over a job tree and run a callback on each job. | |
| #[cfg(console_enabled)] | |
| struct JobWalker<F: FnMut(&JobDispatcher)> { | |
| cb: F, | |
| } | |
| #[cfg(console_enabled)] | |
| impl<F: FnMut(&JobDispatcher)> JobEnumerator for JobWalker<F> { | |
| fn on_job(&mut self, job: &JobDispatcher) -> bool { | |
| (self.cb)(job); | |
| true | |
| } | |
| } | |
| #[cfg(console_enabled)] | |
| fn make_job_walker<F: FnMut(&JobDispatcher)>(cb: F) -> JobWalker<F> { | |
| JobWalker { cb } | |
| } | |
| /// Per-`zx_obj_type_t` handle counts, indexed by object type. | |
| #[cfg(console_enabled)] | |
| type HandleTypeCounts<'a> = &'a mut [u32; ZX_OBJ_TYPE_UPPER_BOUND]; | |
| /// Returns the count of a process's handles. For each handle, the corresponding | |
| /// `zx_obj_type_t`-indexed element of `handle_types` is incremented. | |
| #[cfg(console_enabled)] | |
| fn build_handle_stats(pd: &ProcessDispatcher, handle_types: HandleTypeCounts<'_>) -> u32 { | |
| let mut total = 0; | |
| let _ = pd.handle_table().for_each_handle(|_handle, _rights, disp| { | |
| let obj_type = disp.get_type() as usize; | |
| handle_types[obj_type] += 1; | |
| total += 1; | |
| Ok(()) | |
| }); | |
| total | |
| } | |
| /// Counts the process's handles by type and formats them into the provided | |
| /// buffer as strings. | |
| #[cfg(console_enabled)] | |
| fn format_handle_type_count<'a>(pd: &ProcessDispatcher, buf: &'a mut [u8]) -> &'a [u8] { | |
| let mut types = [0u32; ZX_OBJ_TYPE_UPPER_BOUND]; | |
| let handle_count = build_handle_stats(pd, &mut types); | |
| kformat!( | |
| buf, | |
| "{:4u}: {:4u} {:3u} {:3u} {:3u} {:3u} {:3u} {:3u} {:3u} {:3u} {:3u} {:3u} {:3u}", | |
| handle_count, | |
| types[ZX_OBJ_TYPE_JOB as usize], | |
| types[ZX_OBJ_TYPE_PROCESS as usize], | |
| types[ZX_OBJ_TYPE_THREAD as usize], | |
| types[ZX_OBJ_TYPE_VMO as usize], | |
| types[ZX_OBJ_TYPE_VMAR as usize], | |
| types[ZX_OBJ_TYPE_CHANNEL as usize], | |
| types[ZX_OBJ_TYPE_EVENT as usize] + types[ZX_OBJ_TYPE_EVENTPAIR as usize], | |
| types[ZX_OBJ_TYPE_PORT as usize], | |
| types[ZX_OBJ_TYPE_SOCKET as usize], | |
| types[ZX_OBJ_TYPE_TIMER as usize], | |
| types[ZX_OBJ_TYPE_FIFO as usize], | |
| types[ZX_OBJ_TYPE_INTERRUPT as usize] | |
| + types[ZX_OBJ_TYPE_DEBUGLOG as usize] | |
| + types[ZX_OBJ_TYPE_RESOURCE as usize] | |
| + types[ZX_OBJ_TYPE_GUEST as usize] | |
| + types[ZX_OBJ_TYPE_VCPU as usize] | |
| + types[ZX_OBJ_TYPE_IOMMU as usize] | |
| + types[ZX_OBJ_TYPE_BTI as usize] | |
| + types[ZX_OBJ_TYPE_PROFILE as usize] | |
| + types[ZX_OBJ_TYPE_PMT as usize] | |
| + types[ZX_OBJ_TYPE_SUSPEND_TOKEN as usize] | |
| + types[ZX_OBJ_TYPE_PAGER as usize] | |
| + types[ZX_OBJ_TYPE_EXCEPTION as usize] | |
| ) | |
| } | |
| #[cfg(console_enabled)] | |
| fn dump_process_list() { | |
| kprintln!("{:>7s} #h: #jb #pr #th #vo #vm #ch #ev #po #so #tm #fi #?? [name]", "id"); | |
| let mut walker = make_process_walker(|process: &ProcessDispatcher| { | |
| let mut handle_counts = [0u8; (ZX_OBJ_TYPE_UPPER_BOUND * 4) + 1 + /*slop*/ 16]; | |
| let handle_counts_str = format_handle_type_count(process, &mut handle_counts); | |
| let mut pname = [0u8; ZX_MAX_NAME_LEN]; | |
| let status = process.get_name(&mut pname); | |
| debug_assert!(status.is_ok()); | |
| kprintln!("{:7u} {:s} [{:s}]", process.get_koid(), handle_counts_str, c_name(&pname)); | |
| }); | |
| get_root_job_dispatcher().enumerate_children_recursive(&mut walker); | |
| } | |
| #[cfg(console_enabled)] | |
| fn dump_job_list() { | |
| kprintln!("All jobs:"); | |
| kprintln!("{:>7s} {:s}", "koid", "name"); | |
| let mut walker = make_job_walker(|job: &JobDispatcher| { | |
| let mut name = [0u8; ZX_MAX_NAME_LEN]; | |
| let status = job.get_name(&mut name); | |
| debug_assert!(status.is_ok()); | |
| kprintln!("{:7u} '{:s}'", job.get_koid(), c_name(&name)); | |
| }); | |
| get_root_job_dispatcher().enumerate_children_recursive(&mut walker); | |
| } | |
| #[cfg(console_enabled)] | |
| struct JobPolicyNameValue { | |
| name: &'static str, | |
| value: u32, | |
| } | |
| #[cfg(console_enabled)] | |
| macro_rules! entry { | |
| ($x:ident) => { | |
| JobPolicyNameValue { name: stringify!($x), value: $x } | |
| }; | |
| } | |
| #[cfg(console_enabled)] | |
| const JOB_POLICIES: [JobPolicyNameValue; ZX_POL_MAX as usize] = [ | |
| entry!(ZX_POL_BAD_HANDLE), | |
| entry!(ZX_POL_WRONG_OBJECT), | |
| entry!(ZX_POL_VMAR_WX), | |
| entry!(ZX_POL_NEW_ANY), | |
| entry!(ZX_POL_NEW_VMO), | |
| entry!(ZX_POL_NEW_CHANNEL), | |
| entry!(ZX_POL_NEW_EVENT), | |
| entry!(ZX_POL_NEW_EVENTPAIR), | |
| entry!(ZX_POL_NEW_PORT), | |
| entry!(ZX_POL_NEW_SOCKET), | |
| entry!(ZX_POL_NEW_FIFO), | |
| entry!(ZX_POL_NEW_TIMER), | |
| entry!(ZX_POL_NEW_PROCESS), | |
| entry!(ZX_POL_NEW_PROFILE), | |
| entry!(ZX_POL_NEW_PAGER), | |
| entry!(ZX_POL_AMBIENT_MARK_VMO_EXEC), | |
| entry!(ZX_POL_NEW_IOB), | |
| entry!(ZX_POL_NEW_SAMPLER), | |
| ]; | |
| // Missing Policy Id. | |
| #[cfg(console_enabled)] | |
| zr::static_assert!(JOB_POLICIES.len() == ZX_POL_MAX as usize); | |
| #[cfg(console_enabled)] | |
| fn policy_action_to_string(action: u32) -> &'static str { | |
| if action >= ZX_POL_ACTION_MAX { | |
| return "INVALID ACTION"; | |
| } | |
| match action { | |
| ZX_POL_ACTION_ALLOW => "Allow", | |
| ZX_POL_ACTION_DENY => "Deny", | |
| ZX_POL_ACTION_ALLOW_EXCEPTION => "Allow+Exception", | |
| ZX_POL_ACTION_DENY_EXCEPTION => "Deny+Exception", | |
| ZX_POL_ACTION_KILL => "Kill", | |
| _ => "Unknown", | |
| } | |
| } | |
| #[cfg(console_enabled)] | |
| fn policy_override_to_string(override_val: u32) -> &'static str { | |
| match override_val { | |
| ZX_POL_OVERRIDE_ALLOW => "Allow override", | |
| ZX_POL_OVERRIDE_DENY => "Deny override", | |
| _ => "Unknown", | |
| } | |
| } | |
| #[cfg(console_enabled)] | |
| fn slack_mode_to_string(mode: SlackMode) -> &'static str { | |
| match mode { | |
| SlackMode::Center => "TIMER_SLACK_CENTER", | |
| SlackMode::Early => "TIMER_SLACK_EARLY", | |
| SlackMode::Late => "TIMER_SLACK_LATE", | |
| } | |
| } | |
| #[cfg(console_enabled)] | |
| fn dump_job_policies(job: &JobDispatcher) { | |
| let mut jname = [0u8; ZX_MAX_NAME_LEN]; | |
| let status = job.get_name(&mut jname); | |
| debug_assert!(status.is_ok()); | |
| kprintln!("job {:u} ('{:s}') Basic Policies:", job.get_koid(), c_name(&jname)); | |
| kprintln!("{:<30s}\t{:<15s}\t{:<15s}", "Policy", "Action", "Override"); | |
| let policy = job.get_policy(); | |
| for job_policy in &JOB_POLICIES { | |
| let action = policy.query_basic_policy(job_policy.value); | |
| let policy_override = policy.query_basic_policy_override(job_policy.value); | |
| kprintln!( | |
| "{:<30s}\t{:<15s}\t{:<15s}", | |
| job_policy.name, | |
| policy_action_to_string(action), | |
| policy_override_to_string(policy_override) | |
| ); | |
| } | |
| kprintln!("Slack Policy:"); | |
| let slack = policy.get_timer_slack(); | |
| kprintln!("mode: {:s}", slack_mode_to_string(slack.mode())); | |
| kprintln!("duration: {}ns", slack.amount().0); | |
| } | |
| #[cfg(console_enabled)] | |
| fn dump_job_policies_by_koid(id: zx_koid_t) { | |
| let mut walker = make_job_walker(|job: &JobDispatcher| { | |
| if job.get_koid() != id { | |
| return; | |
| } | |
| dump_job_policies(job); | |
| }); | |
| get_root_job_dispatcher().enumerate_children_recursive(&mut walker); | |
| } | |
| #[cfg(console_enabled)] | |
| fn kill_process(id: zx_koid_t) { | |
| // search the process list and send a kill if found | |
| let pd = ProcessDispatcher::lookup_process_by_id(id); | |
| let Some(pd) = pd else { | |
| kprintln!("process not found!"); | |
| return; | |
| }; | |
| // if found, outside of the lock hit it with kill | |
| kprintln!("killing process {:u}", id); | |
| pd.kill(ZX_TASK_RETCODE_SYSCALL_KILL); | |
| } | |
| #[cfg(console_enabled)] | |
| const MB: usize = 1024 * 1024; | |
| #[cfg(console_enabled)] | |
| static MWD_LIMIT_BYTES: AtomicUsize = AtomicUsize::new(32 * MB); | |
| #[cfg(console_enabled)] | |
| static MWD_RUNNING: AtomicBool = AtomicBool::new(false); | |
| #[cfg(console_enabled)] | |
| static HWD_LIMIT: AtomicUsize = AtomicUsize::new(1024); | |
| #[cfg(console_enabled)] | |
| static HWD_RUNNING: AtomicBool = AtomicBool::new(false); | |
| #[cfg(console_enabled)] | |
| fn dump_process_list_key_map() { | |
| kprintln!("id : process id number"); | |
| kprintln!("#h : total number of handles"); | |
| kprintln!("#jb : number of job handles"); | |
| kprintln!("#pr : number of process handles"); | |
| kprintln!("#th : number of thread handles"); | |
| kprintln!("#vo : number of vmo handles"); | |
| kprintln!("#vm : number of virtual memory address region handles"); | |
| kprintln!("#ch : number of channel handles"); | |
| kprintln!("#ev : number of event and event pair handles"); | |
| kprintln!("#po : number of port handles"); | |
| kprintln!("#so: number of sockets"); | |
| kprintln!("#tm : number of timers"); | |
| kprintln!("#fi : number of fifos"); | |
| kprintln!("#?? : number of all other handle types"); | |
| } | |
| #[cfg(console_enabled)] | |
| fn dump_handle_rights_key_map() { | |
| kprintln!("dup : ZX_RIGHT_DUPLICATE"); | |
| kprintln!("tr : ZX_RIGHT_TRANSFER"); | |
| kprintln!("r : ZX_RIGHT_READ"); | |
| kprintln!("w : ZX_RIGHT_WRITE"); | |
| kprintln!("x : ZX_RIGHT_EXECUTE"); | |
| kprintln!("map : ZX_RIGHT_MAP"); | |
| kprintln!("gpr : ZX_RIGHT_GET_PROPERTY"); | |
| kprintln!("spr : ZX_RIGHT_SET_PROPERTY"); | |
| kprintln!("enm : ZX_RIGHT_ENUMERATE"); | |
| kprintln!("des : ZX_RIGHT_DESTROY"); | |
| kprintln!("spo : ZX_RIGHT_SET_POLICY"); | |
| kprintln!("gpo : ZX_RIGHT_GET_POLICY"); | |
| kprintln!("sig : ZX_RIGHT_SIGNAL"); | |
| kprintln!("sigp: ZX_RIGHT_SIGNAL_PEER"); | |
| kprintln!("wt : ZX_RIGHT_WAIT"); | |
| kprintln!("ins : ZX_RIGHT_INSPECT"); | |
| kprintln!("mj : ZX_RIGHT_MANAGE_JOB"); | |
| kprintln!("mp : ZX_RIGHT_MANAGE_PROCESS"); | |
| kprintln!("mt : ZX_RIGHT_MANAGE_THREAD"); | |
| kprintln!("ap : ZX_RIGHT_APPLY_PROFILE"); | |
| kprintln!("ms : ZX_RIGHT_MANAGE_SOCKET"); | |
| } | |
| /// Dumps an address space based on the arg. | |
| #[cfg(console_enabled)] | |
| fn dump_address_space(arg: &CmdArgs) { | |
| if "kernel".starts_with(arg.as_str()) { | |
| // The arg is a prefix of "kernel". | |
| VmAspace::kernel_aspace().dump(true); | |
| } else { | |
| dump_process_address_space(arg.arg_uint); | |
| } | |
| } | |
| #[cfg(console_enabled)] | |
| fn dump_handle_table() { | |
| kprintln!("outstanding handles: {}", HandleTableArena::diagnostic_count()); | |
| HandleTableArena::dump(); | |
| } | |
| #[cfg(console_enabled)] | |
| extern "C" fn hwd_thread(_arg: *mut c_void) -> i32 { | |
| static PREVIOUS_HANDLE_COUNT: AtomicUsize = AtomicUsize::new(0); | |
| loop { | |
| let handle_count = HandleTableArena::diagnostic_count(); | |
| let prev = PREVIOUS_HANDLE_COUNT.load(Ordering::Relaxed); | |
| if handle_count != prev { | |
| let limit = HWD_LIMIT.load(Ordering::Relaxed); | |
| if handle_count > limit { | |
| kprintln!( | |
| "HandleWatchdog! {} handles outstanding (greater than limit {})", | |
| handle_count, | |
| limit | |
| ); | |
| } else if prev > limit { | |
| kprintln!( | |
| "HandleWatchdog! {} handles outstanding (dropping below limit {})", | |
| handle_count, | |
| limit | |
| ); | |
| } | |
| } | |
| PREVIOUS_HANDLE_COUNT.store(handle_count, Ordering::Relaxed); | |
| let _ = thread::sleep_relative(DurationMono::from_seconds(1)); | |
| } | |
| } | |
| #[cfg(console_enabled)] | |
| extern "C" fn mwd_thread(_arg: *mut c_void) -> i32 { | |
| loop { | |
| let _ = thread::sleep_relative(DurationMono::from_seconds(1)); | |
| dump_process_memory_usage(c"MemoryHog! ", MWD_LIMIT_BYTES.load(Ordering::Relaxed)); | |
| } | |
| } | |
| /// # Safety | |
| /// | |
| /// `argv` must point to a valid array of `CmdArgs` of length `argc`, with `argc >= 1`. | |
| #[cfg(console_enabled)] | |
| unsafe extern "C" fn cmd_diagnostics(argc: c_int, argv: *const CmdArgs, _flags: u32) -> c_int { | |
| let rc: c_int = 0; | |
| // SAFETY: The console framework guarantees `argv` points to `argc` valid `CmdArgs` elements. | |
| let argv = unsafe { core::slice::from_raw_parts(argv, argc as usize) }; | |
| 'usage: { | |
| if argc < 2 { | |
| kprintln!("not enough arguments:"); | |
| break 'usage; | |
| } | |
| if argv[1].as_str() == "mwd" { | |
| if argc == 3 { | |
| MWD_LIMIT_BYTES | |
| .store((argv[2].arg_uint as usize).wrapping_mul(MB), Ordering::Relaxed); | |
| } | |
| if !MWD_RUNNING.load(Ordering::Relaxed) { | |
| // SAFETY: `c"mwd".as_ptr()` is a valid NUL-terminated C string, and `mwd_thread` | |
| // does not dereference its null `arg`. | |
| if let Ok(t) = | |
| unsafe { thread::create(c"mwd".as_ptr(), mwd_thread, ptr::null_mut()) } | |
| { | |
| MWD_RUNNING.store(true, Ordering::Relaxed); | |
| // SAFETY: `t` was just created and has not been resumed or joined. | |
| unsafe { t.resume() }; | |
| } | |
| } | |
| } else if argv[1].as_str() == "ps" { | |
| if argc == 3 && argv[2].as_str() == "help" { | |
| dump_process_list_key_map(); | |
| } else { | |
| dump_process_list(); | |
| } | |
| } else if argv[1].as_str() == "jobs" { | |
| dump_job_list(); | |
| } else if argv[1].as_str() == "jobpol" { | |
| if argc < 3 { | |
| break 'usage; | |
| } | |
| dump_job_policies_by_koid(argv[2].arg_uint); | |
| } else if argv[1].as_str() == "hwd" { | |
| if argc == 3 { | |
| HWD_LIMIT.store(argv[2].arg_uint as usize, Ordering::Relaxed); | |
| } | |
| if !HWD_RUNNING.load(Ordering::Relaxed) { | |
| // SAFETY: `c"hwd".as_ptr()` is a valid NUL-terminated C string, and `hwd_thread` | |
| // does not dereference its null `arg`. | |
| if let Ok(t) = | |
| unsafe { thread::create(c"hwd".as_ptr(), hwd_thread, ptr::null_mut()) } | |
| { | |
| HWD_RUNNING.store(true, Ordering::Relaxed); | |
| // SAFETY: `t` was just created and has not been resumed or joined. | |
| unsafe { t.resume() }; | |
| } | |
| } | |
| } else if argv[1].as_str() == "ht" { | |
| if argc < 3 { | |
| break 'usage; | |
| } | |
| dump_process_handles(argv[2].arg_uint); | |
| } else if argv[1].as_str() == "ch" { | |
| if argc == 3 { | |
| dump_peer_dispatchers_by_koid(ZX_OBJ_TYPE_CHANNEL, argv[2].arg_uint); | |
| } else { | |
| dump_all_peer_dispatchers(ZX_OBJ_TYPE_CHANNEL); | |
| } | |
| } else if argv[1].as_str() == "sock" { | |
| if argc == 3 { | |
| dump_peer_dispatchers_by_koid(ZX_OBJ_TYPE_SOCKET, argv[2].arg_uint); | |
| } else { | |
| dump_all_peer_dispatchers(ZX_OBJ_TYPE_SOCKET); | |
| } | |
| } else if argv[1].as_str() == "fifo" { | |
| if argc == 3 { | |
| dump_peer_dispatchers_by_koid(ZX_OBJ_TYPE_FIFO, argv[2].arg_uint); | |
| } else { | |
| dump_all_peer_dispatchers(ZX_OBJ_TYPE_FIFO); | |
| } | |
| } else if argv[1].as_str() == "eventpair" { | |
| if argc == 3 { | |
| dump_peer_dispatchers_by_koid(ZX_OBJ_TYPE_EVENTPAIR, argv[2].arg_uint); | |
| } else { | |
| dump_all_peer_dispatchers(ZX_OBJ_TYPE_EVENTPAIR); | |
| } | |
| } else if argv[1].as_str() == "iob" { | |
| if argc == 3 { | |
| dump_peer_dispatchers_by_koid(ZX_OBJ_TYPE_IOB, argv[2].arg_uint); | |
| } else { | |
| dump_all_peer_dispatchers(ZX_OBJ_TYPE_IOB); | |
| } | |
| } else if argv[1].as_str() == "vmos" { | |
| if argc < 3 { | |
| break 'usage; | |
| } | |
| let mut format_unit: c_char = 0; | |
| if argc >= 4 { | |
| if let Some(rest) = argv[3].as_str().strip_prefix("-u") { | |
| format_unit = if rest.is_empty() { 0 } else { rest.as_bytes()[0] as c_char }; | |
| } else { | |
| kprintln!("dunno '{:cs}'", argv[3].arg_str); | |
| break 'usage; | |
| } | |
| } | |
| if argv[2].as_str() == "all" { | |
| dump_all_vm_objects(false, format_unit); | |
| } else if argv[2].as_str() == "hidden" { | |
| dump_all_vm_objects(true, format_unit); | |
| } else if argv[2].as_str() == "orphaned" { | |
| dump_orphaned_slices(format_unit); | |
| dump_orphaned_mapped_vmos(format_unit); | |
| } else { | |
| dump_process_vm_objects(argv[2].arg_uint, format_unit); | |
| } | |
| } else if argv[1].as_str() == "cow-tree" { | |
| if argc < 3 { | |
| break 'usage; | |
| } | |
| dump_vm_object_cow_tree(argv[2].arg_uint); | |
| } else if argv[1].as_str() == "kill" { | |
| if argc < 3 { | |
| break 'usage; | |
| } | |
| kill_process(argv[2].arg_uint); | |
| } else if argv[1].as_str() == "asd" { | |
| if argc < 3 { | |
| break 'usage; | |
| } | |
| dump_address_space(&argv[2]); | |
| } else if argv[1].as_str() == "htinfo" { | |
| if argc != 2 { | |
| break 'usage; | |
| } | |
| dump_handle_table(); | |
| } else if argv[1].as_str() == "koid" { | |
| if argc < 3 { | |
| break 'usage; | |
| } | |
| if argv[2].as_str() == "help" { | |
| dump_handle_rights_key_map(); | |
| } else { | |
| dump_handles_for_koid(argv[2].arg_uint); | |
| } | |
| } else { | |
| kprintln!("unrecognized subcommand '{:cs}'", argv[1].arg_str); | |
| break 'usage; | |
| } | |
| return rc; | |
| } | |
| kprintln!("{:cs} ps : list processes", argv[0].arg_str); | |
| kprintln!( | |
| "{:cs} ps help : print header label descriptions for 'ps'", | |
| argv[0].arg_str | |
| ); | |
| kprintln!("{:cs} jobs : list jobs", argv[0].arg_str); | |
| kprintln!("{:cs} jobpol <koid> : print policies for given job", argv[0].arg_str); | |
| kprintln!("{:cs} mwd <mb> : memory watchdog", argv[0].arg_str); | |
| kprintln!("{:cs} ht <pid> : dump process handles", argv[0].arg_str); | |
| kprintln!("{:cs} hwd <count> : handle watchdog", argv[0].arg_str); | |
| kprintln!("{:cs} vmos <pid>|all|hidden|orphaned [-u?]", argv[0].arg_str); | |
| kprintln!(" : dump process/all/hidden/orphaned VMOs"); | |
| kprintln!(" -u? : fix all sizes to the named unit"); | |
| kprintln!(" where ? is one of [BkMGTPE]"); | |
| kprintln!( | |
| "{:cs} cow-tree <vmo> : dump the copy-on-write tree for a vmo koid", | |
| argv[0].arg_str | |
| ); | |
| kprintln!("{:cs} kill <pid> : kill process", argv[0].arg_str); | |
| kprintln!("{:cs} asd <pid>|kernel : dump process/kernel address space", argv[0].arg_str); | |
| kprintln!("{:cs} htinfo : handle table info", argv[0].arg_str); | |
| kprintln!("{:cs} koid <koid> : list all handles for a koid", argv[0].arg_str); | |
| kprintln!( | |
| "{:cs} koid help : print header label descriptions for 'koid'", | |
| argv[0].arg_str | |
| ); | |
| kprintln!( | |
| "{:cs} ch <koid> : dump channels for pid or for all processes,", | |
| argv[0].arg_str | |
| ); | |
| kprintln!(" or processes for channel koid"); | |
| kprintln!( | |
| "{:cs} sock <koid> : dump sockets for pid or for all processes,", | |
| argv[0].arg_str | |
| ); | |
| kprintln!(" or processes for socket koid"); | |
| kprintln!( | |
| "{:cs} fifo <koid> : dump fifos for pid or for all processes,", | |
| argv[0].arg_str | |
| ); | |
| kprintln!(" or processes for fifo koid"); | |
| kprintln!( | |
| "{:cs} eventpair <koid> : dump event pairs for pid or for all processes,", | |
| argv[0].arg_str | |
| ); | |
| kprintln!(" or processes for eventpair koid"); | |
| kprintln!( | |
| "{:cs} iob <koid> : dump io buffers for pid or for all processes,", | |
| argv[0].arg_str | |
| ); | |
| kprintln!(" or processes for io buffer koid"); | |
| -1 | |
| } | |
| #[cfg(console_enabled)] | |
| static_command!( | |
| CMD_ZX, | |
| c"zx".as_ptr(), | |
| c"kernel object diagnostics".as_ptr(), | |
| cmd_diagnostics, | |
| CMD_AVAIL_NORMAL | |
| ); |