| // 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::arch_rs::UserCopyCaptureFaultsError; |
| 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_job_list, |
| dump_job_policies, dump_orphaned_mapped_vmos, dump_orphaned_slices, |
| dump_peer_dispatchers_by_koid, dump_process_address_space, dump_process_handles, |
| dump_process_list, dump_process_memory_usage, dump_process_vm_objects, dump_vm_object_cow_tree, |
| handle_diagnostics_dump_table_info, handle_diagnostics_outstanding_handles, kill_process, |
| }; |
| #[cfg(console_enabled)] |
| use crate::console::{CMD_AVAIL_NORMAL, CmdArgs, static_command}; |
| #[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::kprintln; |
| #[cfg(console_enabled)] |
| use zx_types::{ |
| ZX_OBJ_TYPE_CHANNEL, ZX_OBJ_TYPE_EVENTPAIR, ZX_OBJ_TYPE_FIFO, ZX_OBJ_TYPE_IOB, |
| ZX_OBJ_TYPE_SOCKET, |
| }; |
| |
| /// 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>; |
| /// Same as `write`, except is the no faulting variant and returns the complete capture result |
| /// type. |
| fn write_capture_faults( |
| &mut self, |
| vmo: &zx_info_vmo_t, |
| offset: usize, |
| ) -> Result<(), UserCopyCaptureFaultsError>; |
| /// 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>; |
| /// Same as `write`, except is the no faulting variant and returns the complete capture result |
| /// type. |
| fn write_capture_faults( |
| &mut self, |
| maps: &zx_info_maps_t, |
| offset: usize, |
| ) -> Result<(), UserCopyCaptureFaultsError>; |
| /// 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); |
| } |
| |
| 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 |
| } |
| |
| /// 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; |
| } |
| } |
| 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; |
| } |
| } |
| 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; |
| } |
| } |
| 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)) |
| } |
| |
| #[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: {}", handle_diagnostics_outstanding_handles()); |
| handle_diagnostics_dump_table_info(); |
| } |
| |
| #[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 = handle_diagnostics_outstanding_handles(); |
| 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(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 |
| ); |