| // 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 |
| |
| #include <object/diagnostics.h> |
| |
| #include <inttypes.h> |
| #include <stdio.h> |
| #include <string.h> |
| |
| #include <lib/console.h> |
| #include <lib/ktrace.h> |
| #include <fbl/auto_lock.h> |
| #include <object/handle.h> |
| #include <object/job_dispatcher.h> |
| #include <object/port_dispatcher.h> |
| #include <object/process_dispatcher.h> |
| #include <object/vm_object_dispatcher.h> |
| #include <pretty/sizes.h> |
| #include <zircon/types.h> |
| |
| // Machinery to walk over a job tree and run a callback on each process. |
| template <typename ProcessCallbackType> |
| class ProcessWalker final : public JobEnumerator { |
| public: |
| ProcessWalker(ProcessCallbackType cb) : cb_(cb) {} |
| ProcessWalker(const ProcessWalker&) = delete; |
| ProcessWalker(ProcessWalker&& other) : cb_(other.cb_) {} |
| |
| private: |
| bool OnProcess(ProcessDispatcher* process) final { |
| cb_(process); |
| return true; |
| } |
| |
| const ProcessCallbackType cb_; |
| }; |
| |
| template <typename ProcessCallbackType> |
| static ProcessWalker<ProcessCallbackType> MakeProcessWalker(ProcessCallbackType cb) { |
| return ProcessWalker<ProcessCallbackType>(cb); |
| } |
| |
| static void DumpProcessListKeyMap() { |
| printf("id : process id number\n"); |
| printf("#h : total number of handles\n"); |
| printf("#jb : number of job handles\n"); |
| printf("#pr : number of process handles\n"); |
| printf("#th : number of thread handles\n"); |
| printf("#vo : number of vmo handles\n"); |
| printf("#vm : number of virtual memory address region handles\n"); |
| printf("#ch : number of channel handles\n"); |
| printf("#ev : number of event and event pair handles\n"); |
| printf("#po : number of port handles\n"); |
| printf("#so: number of sockets\n"); |
| printf("#tm : number of timers\n"); |
| printf("#fi : number of fifos\n"); |
| } |
| |
| static const char* ObjectTypeToString(zx_obj_type_t type) { |
| static_assert(ZX_OBJ_TYPE_LAST == 25, "need to update switch below"); |
| |
| switch (type) { |
| case ZX_OBJ_TYPE_PROCESS: return "process"; |
| case ZX_OBJ_TYPE_THREAD: return "thread"; |
| case ZX_OBJ_TYPE_VMO: return "vmo"; |
| case ZX_OBJ_TYPE_CHANNEL: return "channel"; |
| case ZX_OBJ_TYPE_EVENT: return "event"; |
| case ZX_OBJ_TYPE_PORT: return "port"; |
| case ZX_OBJ_TYPE_INTERRUPT: return "interrupt"; |
| case ZX_OBJ_TYPE_PCI_DEVICE: return "pci-device"; |
| case ZX_OBJ_TYPE_LOG: return "log"; |
| case ZX_OBJ_TYPE_SOCKET: return "socket"; |
| case ZX_OBJ_TYPE_RESOURCE: return "resource"; |
| case ZX_OBJ_TYPE_EVENT_PAIR: return "event-pair"; |
| case ZX_OBJ_TYPE_JOB: return "job"; |
| case ZX_OBJ_TYPE_VMAR: return "vmar"; |
| case ZX_OBJ_TYPE_FIFO: return "fifo"; |
| case ZX_OBJ_TYPE_GUEST: return "guest"; |
| case ZX_OBJ_TYPE_VCPU: return "vcpu"; |
| case ZX_OBJ_TYPE_TIMER: return "timer"; |
| case ZX_OBJ_TYPE_IOMMU: return "iommu"; |
| case ZX_OBJ_TYPE_BTI: return "bti"; |
| default: return "???"; |
| } |
| } |
| |
| // Returns the count of a process's handles. If |handle_type| is non-NULL, |
| // it should point to |size| elements. For each handle, the corresponding |
| // zx_obj_type_t-indexed element of |handle_type| is incremented. |
| static uint32_t BuildHandleStats(const ProcessDispatcher& pd, |
| uint32_t* handle_type, size_t size) { |
| uint32_t total = 0; |
| pd.ForEachHandle([&](zx_handle_t handle, zx_rights_t rights, |
| const Dispatcher* disp) { |
| if (handle_type) { |
| uint32_t type = static_cast<uint32_t>(disp->get_type()); |
| if (size > type) { |
| ++handle_type[type]; |
| } |
| } |
| ++total; |
| return ZX_OK; |
| }); |
| return total; |
| } |
| |
| // Counts the process's handles by type and formats them into the provided |
| // buffer as strings. |
| static void FormatHandleTypeCount(const ProcessDispatcher& pd, |
| char *buf, size_t buf_len) { |
| uint32_t types[ZX_OBJ_TYPE_LAST] = {0}; |
| uint32_t handle_count = BuildHandleStats(pd, types, sizeof(types)); |
| |
| snprintf(buf, buf_len, "%4u: %3u %3u %3u %3u %3u %3u %3u %3u %3u %3u %3u", |
| handle_count, |
| types[ZX_OBJ_TYPE_JOB], |
| types[ZX_OBJ_TYPE_PROCESS], |
| types[ZX_OBJ_TYPE_THREAD], |
| types[ZX_OBJ_TYPE_VMO], |
| types[ZX_OBJ_TYPE_VMAR], |
| types[ZX_OBJ_TYPE_CHANNEL], |
| types[ZX_OBJ_TYPE_EVENT] + types[ZX_OBJ_TYPE_EVENT_PAIR], |
| types[ZX_OBJ_TYPE_PORT], |
| types[ZX_OBJ_TYPE_SOCKET], |
| types[ZX_OBJ_TYPE_TIMER], |
| types[ZX_OBJ_TYPE_FIFO] |
| ); |
| } |
| |
| void DumpProcessList() { |
| printf("%7s #h: #jb #pr #th #vo #vm #ch #ev #po #so #tm #fi [name]\n", "id"); |
| |
| auto walker = MakeProcessWalker([](ProcessDispatcher* process) { |
| char handle_counts[(ZX_OBJ_TYPE_LAST * 4) + 1 + /*slop*/ 16]; |
| FormatHandleTypeCount(*process, handle_counts, sizeof(handle_counts)); |
| |
| char pname[ZX_MAX_NAME_LEN]; |
| process->get_name(pname); |
| printf("%7" PRIu64 "%s [%s]\n", |
| process->get_koid(), |
| handle_counts, |
| pname); |
| }); |
| GetRootJobDispatcher()->EnumerateChildren(&walker, /* recurse */ true); |
| } |
| |
| void DumpJobList() { |
| printf("All jobs from least to most important:\n"); |
| printf("%7s %s\n", "koid", "name"); |
| JobDispatcher::ForEachJobByImportance([&](JobDispatcher* job) { |
| char name[ZX_MAX_NAME_LEN]; |
| job->get_name(name); |
| printf("%7" PRIu64 " '%s'\n", job->get_koid(), name); |
| return ZX_OK; |
| }); |
| } |
| |
| void DumpProcessHandles(zx_koid_t id) { |
| auto pd = ProcessDispatcher::LookupProcessById(id); |
| if (!pd) { |
| printf("process %" PRIu64 " not found!\n", id); |
| return; |
| } |
| |
| printf("process [%" PRIu64 "] handles :\n", id); |
| printf("handle koid : type\n"); |
| |
| uint32_t total = 0; |
| pd->ForEachHandle([&](zx_handle_t handle, zx_rights_t rights, |
| const Dispatcher* disp) { |
| printf("%9x %7" PRIu64 " : %s\n", |
| handle, disp->get_koid(), ObjectTypeToString(disp->get_type())); |
| ++total; |
| return ZX_OK; |
| }); |
| printf("total: %u handles\n", total); |
| } |
| |
| void ktrace_report_live_processes() { |
| auto walker = MakeProcessWalker([](ProcessDispatcher* process) { |
| char name[ZX_MAX_NAME_LEN]; |
| process->get_name(name); |
| ktrace_name(TAG_PROC_NAME, (uint32_t)process->get_koid(), 0, name); |
| }); |
| GetRootJobDispatcher()->EnumerateChildren(&walker, /* recurse */ true); |
| } |
| |
| // Returns a string representation of VMO-related rights. |
| static constexpr size_t kRightsStrLen = 8; |
| static const char* VmoRightsToString(uint32_t rights, char str[kRightsStrLen]) { |
| char* c = str; |
| *c++ = (rights & ZX_RIGHT_READ) ? 'r' : '-'; |
| *c++ = (rights & ZX_RIGHT_WRITE) ? 'w' : '-'; |
| *c++ = (rights & ZX_RIGHT_EXECUTE) ? 'x' : '-'; |
| *c++ = (rights & ZX_RIGHT_MAP) ? 'm' : '-'; |
| *c++ = (rights & ZX_RIGHT_DUPLICATE) ? 'd' : '-'; |
| *c++ = (rights & ZX_RIGHT_TRANSFER) ? 't' : '-'; |
| *c = '\0'; |
| return str; |
| } |
| |
| // Prints a header for the columns printed by DumpVmObject. |
| // If |handles| is true, the dumped objects are expected to have handle info. |
| static void PrintVmoDumpHeader(bool handles) { |
| printf( |
| "%s koid parent #chld #map #shr size alloc name\n", |
| handles ? " handle rights " : " - - "); |
| } |
| |
| static void DumpVmObject( |
| const VmObject& vmo, char format_unit, |
| zx_handle_t handle, uint32_t rights, zx_koid_t koid) { |
| |
| char handle_str[11]; |
| if (handle != ZX_HANDLE_INVALID) { |
| snprintf(handle_str, sizeof(handle_str), |
| "%u", static_cast<uint32_t>(handle)); |
| } else { |
| handle_str[0] = '-'; |
| handle_str[1] = '\0'; |
| } |
| |
| char rights_str[kRightsStrLen]; |
| if (rights != 0) { |
| VmoRightsToString(rights, rights_str); |
| } else { |
| rights_str[0] = '-'; |
| rights_str[1] = '\0'; |
| } |
| |
| char size_str[MAX_FORMAT_SIZE_LEN]; |
| format_size_fixed(size_str, sizeof(size_str), vmo.size(), format_unit); |
| |
| char alloc_str[MAX_FORMAT_SIZE_LEN]; |
| if (vmo.is_paged()) { |
| format_size_fixed(alloc_str, sizeof(alloc_str), |
| vmo.AllocatedPages() * PAGE_SIZE, format_unit); |
| } else { |
| strlcpy(alloc_str, "phys", sizeof(alloc_str)); |
| } |
| |
| char clone_str[21]; |
| if (vmo.is_cow_clone()) { |
| snprintf(clone_str, sizeof(clone_str), |
| "%" PRIu64, vmo.parent_user_id()); |
| } else { |
| clone_str[0] = '-'; |
| clone_str[1] = '\0'; |
| } |
| |
| char name[ZX_MAX_NAME_LEN]; |
| vmo.get_name(name, sizeof(name)); |
| if (name[0] == '\0') { |
| name[0] = '-'; |
| name[1] = '\0'; |
| } |
| |
| printf(" %10s " // handle |
| "%6s " // rights |
| "%5" PRIu64 " " // koid |
| "%6s " // clone parent koid |
| "%5" PRIu32 " " // number of children |
| "%4" PRIu32 " " // map count |
| "%4" PRIu32 " " // share count |
| "%7s " // size in bytes |
| "%7s " // allocated bytes |
| "%s\n", // name |
| handle_str, |
| rights_str, |
| koid, |
| clone_str, |
| vmo.num_children(), |
| vmo.num_mappings(), |
| vmo.share_count(), |
| size_str, |
| alloc_str, |
| name); |
| } |
| |
| // If |hidden_only| is set, will only dump VMOs that are not mapped |
| // into any process: |
| // - VMOs that userspace has handles to but does not map |
| // - VMOs that are mapped only into kernel space |
| // - Kernel-only, unmapped VMOs that have no handles |
| static void DumpAllVmObjects(bool hidden_only, char format_unit) { |
| if (hidden_only) { |
| printf("\"Hidden\" VMOs, oldest to newest:\n"); |
| } else { |
| printf("All VMOs, oldest to newest:\n"); |
| } |
| PrintVmoDumpHeader(/* handles */ false); |
| VmObject::ForEach([=](const VmObject& vmo) { |
| if (hidden_only && vmo.IsMappedByUser()) { |
| return ZX_OK; |
| } |
| DumpVmObject( |
| vmo, |
| format_unit, |
| ZX_HANDLE_INVALID, |
| /* rights */ 0u, |
| /* koid */ vmo.user_id()); |
| // TODO(dbort): Dump the VmAspaces (processes) that map the VMO. |
| // TODO(dbort): Dump the processes that hold handles to the VMO. |
| // This will be a lot harder to gather. |
| return ZX_OK; |
| }); |
| PrintVmoDumpHeader(/* handles */ false); |
| } |
| |
| namespace { |
| // Dumps VMOs under a VmAspace. |
| class AspaceVmoDumper final : public VmEnumerator { |
| public: |
| AspaceVmoDumper(char format_unit) : format_unit_(format_unit) {} |
| bool OnVmMapping(const VmMapping* map, const VmAddressRegion* vmar, |
| uint depth) final { |
| auto vmo = map->vmo(); |
| DumpVmObject( |
| *vmo, |
| format_unit_, |
| ZX_HANDLE_INVALID, |
| /* rights */ 0u, |
| /* koid */ vmo->user_id()); |
| return true; |
| } |
| private: |
| const char format_unit_; |
| }; |
| } // namespace |
| |
| // Dumps all VMOs associated with a process. |
| static void DumpProcessVmObjects(zx_koid_t id, char format_unit) { |
| auto pd = ProcessDispatcher::LookupProcessById(id); |
| if (!pd) { |
| printf("process not found!\n"); |
| return; |
| } |
| |
| printf("process [%" PRIu64 "]:\n", id); |
| printf("Handles to VMOs:\n"); |
| PrintVmoDumpHeader(/* handles */ true); |
| int count = 0; |
| uint64_t total_size = 0; |
| uint64_t total_alloc = 0; |
| pd->ForEachHandle([&](zx_handle_t handle, zx_rights_t rights, |
| const Dispatcher* disp) { |
| auto vmod = DownCastDispatcher<const VmObjectDispatcher>(disp); |
| if (vmod == nullptr) { |
| return ZX_OK; |
| } |
| auto vmo = vmod->vmo(); |
| DumpVmObject(*vmo, format_unit, handle, rights, vmod->get_koid()); |
| |
| // TODO: Doesn't handle the case where a process has multiple |
| // handles to the same VMO; will double-count all of these totals. |
| count++; |
| total_size += vmo->size(); |
| // TODO: Doing this twice (here and in DumpVmObject) is a waste of |
| // work, and can get out of sync. |
| total_alloc += vmo->AllocatedPages() * PAGE_SIZE; |
| return ZX_OK; |
| }); |
| char size_str[MAX_FORMAT_SIZE_LEN]; |
| char alloc_str[MAX_FORMAT_SIZE_LEN]; |
| printf(" total: %d VMOs, size %s, alloc %s\n", |
| count, |
| format_size_fixed(size_str, sizeof(size_str), |
| total_size, format_unit), |
| format_size_fixed(alloc_str, sizeof(alloc_str), |
| total_alloc, format_unit)); |
| |
| // Call DumpVmObject() on all VMOs under the process's VmAspace. |
| printf("Mapped VMOs:\n"); |
| PrintVmoDumpHeader(/* handles */ false); |
| AspaceVmoDumper avd(format_unit); |
| pd->aspace()->EnumerateChildren(&avd); |
| PrintVmoDumpHeader(/* handles */ false); |
| } |
| |
| void KillProcess(zx_koid_t id) { |
| // search the process list and send a kill if found |
| auto pd = ProcessDispatcher::LookupProcessById(id); |
| if (!pd) { |
| printf("process not found!\n"); |
| return; |
| } |
| // if found, outside of the lock hit it with kill |
| printf("killing process %" PRIu64 "\n", id); |
| pd->Kill(); |
| } |
| |
| namespace { |
| // Counts memory usage under a VmAspace. |
| class VmCounter final : public VmEnumerator { |
| public: |
| bool OnVmMapping(const VmMapping* map, const VmAddressRegion* vmar, |
| uint depth) override { |
| usage.mapped_pages += map->size() / PAGE_SIZE; |
| |
| size_t committed_pages = map->vmo()->AllocatedPagesInRange( |
| map->object_offset(), map->size()); |
| uint32_t share_count = map->vmo()->share_count(); |
| if (share_count == 1) { |
| usage.private_pages += committed_pages; |
| } else { |
| usage.shared_pages += committed_pages; |
| usage.scaled_shared_bytes += |
| committed_pages * PAGE_SIZE / share_count; |
| } |
| return true; |
| } |
| |
| VmAspace::vm_usage_t usage = {}; |
| }; |
| } // namespace |
| |
| zx_status_t VmAspace::GetMemoryUsage(vm_usage_t* usage) { |
| VmCounter vc; |
| if (!EnumerateChildren(&vc)) { |
| *usage = {}; |
| return ZX_ERR_INTERNAL; |
| } |
| *usage = vc.usage; |
| return ZX_OK; |
| } |
| |
| namespace { |
| unsigned int arch_mmu_flags_to_vm_flags(unsigned int arch_mmu_flags) { |
| if (arch_mmu_flags & ARCH_MMU_FLAG_INVALID) { |
| return 0; |
| } |
| unsigned int ret = 0; |
| if (arch_mmu_flags & ARCH_MMU_FLAG_PERM_READ) { |
| ret |= ZX_VM_FLAG_PERM_READ; |
| } |
| if (arch_mmu_flags & ARCH_MMU_FLAG_PERM_WRITE) { |
| ret |= ZX_VM_FLAG_PERM_WRITE; |
| } |
| if (arch_mmu_flags & ARCH_MMU_FLAG_PERM_EXECUTE) { |
| ret |= ZX_VM_FLAG_PERM_EXECUTE; |
| } |
| return ret; |
| } |
| |
| // Builds a description of an apsace/vmar/mapping hierarchy. |
| class VmMapBuilder final : public VmEnumerator { |
| public: |
| // NOTE: Code outside of the syscall layer should not typically know about |
| // user_ptrs; do not use this pattern as an example. |
| VmMapBuilder(user_out_ptr<zx_info_maps_t> maps, size_t max) |
| : maps_(maps), max_(max) {} |
| |
| bool OnVmAddressRegion(const VmAddressRegion* vmar, uint depth) override { |
| available_++; |
| if (nelem_ < max_) { |
| zx_info_maps_t entry = {}; |
| strlcpy(entry.name, vmar->name(), sizeof(entry.name)); |
| entry.base = vmar->base(); |
| entry.size = vmar->size(); |
| entry.depth = depth + 1; // The root aspace is depth 0. |
| entry.type = ZX_INFO_MAPS_TYPE_VMAR; |
| if (maps_.copy_array_to_user(&entry, 1, nelem_) != ZX_OK) { |
| return false; |
| } |
| nelem_++; |
| } |
| return true; |
| } |
| |
| bool OnVmMapping(const VmMapping* map, const VmAddressRegion* vmar, |
| uint depth) override { |
| available_++; |
| if (nelem_ < max_) { |
| zx_info_maps_t entry = {}; |
| auto vmo = map->vmo(); |
| vmo->get_name(entry.name, sizeof(entry.name)); |
| entry.base = map->base(); |
| entry.size = map->size(); |
| entry.depth = depth + 1; // The root aspace is depth 0. |
| entry.type = ZX_INFO_MAPS_TYPE_MAPPING; |
| zx_info_maps_mapping_t* u = &entry.u.mapping; |
| u->mmu_flags = |
| arch_mmu_flags_to_vm_flags(map->arch_mmu_flags()); |
| u->vmo_koid = vmo->user_id(); |
| u->committed_pages = vmo->AllocatedPagesInRange( |
| map->object_offset(), map->size()); |
| if (maps_.copy_array_to_user(&entry, 1, nelem_) != ZX_OK) { |
| return false; |
| } |
| nelem_++; |
| } |
| return true; |
| } |
| |
| size_t nelem() const { return nelem_; } |
| size_t available() const { return available_; } |
| |
| private: |
| // The caller must write an entry for the root VmAspace at index 0. |
| size_t nelem_ = 1; |
| size_t available_ = 1; |
| user_out_ptr<zx_info_maps_t> maps_; |
| size_t max_; |
| }; |
| } // namespace |
| |
| // NOTE: Code outside of the syscall layer should not typically know about |
| // user_ptrs; do not use this pattern as an example. |
| zx_status_t GetVmAspaceMaps(fbl::RefPtr<VmAspace> aspace, |
| user_out_ptr<zx_info_maps_t> maps, size_t max, |
| size_t* actual, size_t* available) { |
| DEBUG_ASSERT(aspace != nullptr); |
| *actual = 0; |
| *available = 0; |
| if (aspace->is_destroyed()) { |
| return ZX_ERR_BAD_STATE; |
| } |
| if (max > 0) { |
| zx_info_maps_t entry = {}; |
| strlcpy(entry.name, aspace->name(), sizeof(entry.name)); |
| entry.base = aspace->base(); |
| entry.size = aspace->size(); |
| entry.depth = 0; |
| entry.type = ZX_INFO_MAPS_TYPE_ASPACE; |
| if (maps.copy_array_to_user(&entry, 1, 0) != ZX_OK) { |
| return ZX_ERR_INVALID_ARGS; |
| } |
| } |
| |
| VmMapBuilder b(maps, max); |
| if (!aspace->EnumerateChildren(&b)) { |
| // VmMapBuilder only returns false |
| // when it can't copy to the user pointer. |
| return ZX_ERR_INVALID_ARGS; |
| } |
| *actual = max > 0 ? b.nelem() : 0; |
| *available = b.available(); |
| return ZX_OK; |
| } |
| |
| namespace { |
| zx_info_vmo_t VmoToInfoEntry(const VmObject* vmo, |
| bool is_handle, zx_rights_t handle_rights) { |
| zx_info_vmo_t entry = {}; |
| entry.koid = vmo->user_id(); |
| vmo->get_name(entry.name, sizeof(entry.name)); |
| entry.size_bytes = vmo->size(); |
| entry.parent_koid = vmo->parent_user_id(); |
| entry.num_children = vmo->num_children(); |
| entry.num_mappings = vmo->num_mappings(); |
| entry.share_count = vmo->share_count(); |
| entry.flags = |
| (vmo->is_paged() ? ZX_INFO_VMO_TYPE_PAGED : ZX_INFO_VMO_TYPE_PHYSICAL) | |
| (vmo->is_cow_clone() ? ZX_INFO_VMO_IS_COW_CLONE : 0); |
| entry.committed_bytes = vmo->AllocatedPages() * PAGE_SIZE; |
| if (is_handle) { |
| entry.flags |= ZX_INFO_VMO_VIA_HANDLE; |
| entry.handle_rights = handle_rights; |
| } else { |
| entry.flags |= ZX_INFO_VMO_VIA_MAPPING; |
| } |
| return entry; |
| } |
| |
| // Builds a list of all VMOs mapped into a VmAspace. |
| class AspaceVmoEnumerator final : public VmEnumerator { |
| public: |
| // NOTE: Code outside of the syscall layer should not typically know about |
| // user_ptrs; do not use this pattern as an example. |
| AspaceVmoEnumerator(user_out_ptr<zx_info_vmo_t> vmos, size_t max) |
| : vmos_(vmos), max_(max) {} |
| |
| bool OnVmMapping(const VmMapping* map, const VmAddressRegion* vmar, |
| uint depth) override { |
| available_++; |
| if (nelem_ < max_) { |
| // We're likely to see the same VMO a couple times in a given |
| // address space (e.g., somelib.so mapped as r--, r-x), but leave it |
| // to userspace to do deduping. |
| zx_info_vmo_t entry = VmoToInfoEntry(map->vmo().get(), |
| /*is_handle=*/false, |
| /*handle_rights=*/0); |
| if (vmos_.copy_array_to_user(&entry, 1, nelem_) != ZX_OK) { |
| return false; |
| } |
| nelem_++; |
| } |
| return true; |
| } |
| |
| size_t nelem() const { return nelem_; } |
| size_t available() const { return available_; } |
| |
| private: |
| const user_out_ptr<zx_info_vmo_t> vmos_; |
| const size_t max_; |
| |
| size_t nelem_ = 0; |
| size_t available_ = 0; |
| }; |
| } // namespace |
| |
| // NOTE: Code outside of the syscall layer should not typically know about |
| // user_ptrs; do not use this pattern as an example. |
| zx_status_t GetVmAspaceVmos(fbl::RefPtr<VmAspace> aspace, |
| user_out_ptr<zx_info_vmo_t> vmos, size_t max, |
| size_t* actual, size_t* available) { |
| DEBUG_ASSERT(aspace != nullptr); |
| DEBUG_ASSERT(actual != nullptr); |
| DEBUG_ASSERT(available != nullptr); |
| *actual = 0; |
| *available = 0; |
| if (aspace->is_destroyed()) { |
| return ZX_ERR_BAD_STATE; |
| } |
| |
| AspaceVmoEnumerator ave(vmos, max); |
| if (!aspace->EnumerateChildren(&ave)) { |
| // AspaceVmoEnumerator only returns false |
| // when it can't copy to the user pointer. |
| return ZX_ERR_INVALID_ARGS; |
| } |
| *actual = ave.nelem(); |
| *available = ave.available(); |
| return ZX_OK; |
| } |
| |
| // NOTE: Code outside of the syscall layer should not typically know about |
| // user_ptrs; do not use this pattern as an example. |
| zx_status_t GetProcessVmosViaHandles(ProcessDispatcher* process, |
| user_out_ptr<zx_info_vmo_t> vmos, size_t max, |
| size_t* actual_out, size_t* available_out) { |
| DEBUG_ASSERT(process != nullptr); |
| DEBUG_ASSERT(actual_out != nullptr); |
| DEBUG_ASSERT(available_out != nullptr); |
| size_t actual = 0; |
| size_t available = 0; |
| // We may see multiple handles to the same VMO, but leave it to userspace to |
| // do deduping. |
| zx_status_t s = process->ForEachHandle([&](zx_handle_t handle, |
| zx_rights_t rights, |
| const Dispatcher* disp) { |
| auto vmod = DownCastDispatcher<const VmObjectDispatcher>(disp); |
| if (vmod == nullptr) { |
| // This handle isn't a VMO; skip it. |
| return ZX_OK; |
| } |
| available++; |
| if (actual < max) { |
| zx_info_vmo_t entry = VmoToInfoEntry(vmod->vmo().get(), |
| /*is_handle=*/true, |
| rights); |
| if (vmos.copy_array_to_user(&entry, 1, actual) != ZX_OK) { |
| return ZX_ERR_INVALID_ARGS; |
| } |
| actual++; |
| } |
| return ZX_OK; |
| }); |
| if (s != ZX_OK) { |
| return s; |
| } |
| *actual_out = actual; |
| *available_out = available; |
| return ZX_OK; |
| } |
| |
| void DumpProcessAddressSpace(zx_koid_t id) { |
| auto pd = ProcessDispatcher::LookupProcessById(id); |
| if (!pd) { |
| printf("process %" PRIu64 " not found!\n", id); |
| return; |
| } |
| |
| pd->aspace()->Dump(true); |
| } |
| |
| // Dumps an address space based on the arg. |
| static void DumpAddressSpace(const cmd_args* arg) { |
| if (strncmp(arg->str, "kernel", strlen(arg->str)) == 0) { |
| // The arg is a prefix of "kernel". |
| VmAspace::kernel_aspace()->Dump(true); |
| } else { |
| DumpProcessAddressSpace(arg->u); |
| } |
| } |
| |
| static void DumpHandleTable() { |
| printf("outstanding handles: %zu\n", |
| Handle::diagnostics::OutstandingHandles()); |
| Handle::diagnostics::DumpTableInfo(); |
| } |
| |
| static size_t mwd_limit = 32 * 256; |
| static bool mwd_running; |
| |
| static size_t hwd_limit = 1024; |
| static bool hwd_running; |
| |
| static int hwd_thread(void* arg) { |
| static size_t previous_handle_count = 0u; |
| |
| for (;;) { |
| auto handle_count = Handle::diagnostics::OutstandingHandles(); |
| if (handle_count != previous_handle_count) { |
| if (handle_count > hwd_limit) { |
| printf("HandleWatchdog! %zu handles outstanding (greater than limit %zu)\n", |
| handle_count, hwd_limit); |
| } else if (previous_handle_count > hwd_limit) { |
| printf("HandleWatchdog! %zu handles outstanding (dropping below limit %zu)\n", |
| handle_count, hwd_limit); |
| } |
| } |
| |
| previous_handle_count = handle_count; |
| |
| thread_sleep_relative(ZX_SEC(1)); |
| } |
| } |
| |
| void DumpProcessMemoryUsage(const char* prefix, size_t min_pages) { |
| auto walker = MakeProcessWalker([&](ProcessDispatcher* process) { |
| size_t pages = process->PageCount(); |
| if (pages >= min_pages) { |
| char pname[ZX_MAX_NAME_LEN]; |
| process->get_name(pname); |
| printf("%sproc %5" PRIu64 " %4zuM '%s'\n", |
| prefix, process->get_koid(), pages / 256, pname); |
| } |
| }); |
| GetRootJobDispatcher()->EnumerateChildren(&walker, /* recurse */ true); |
| } |
| |
| static void DumpPortPacketInfo() { |
| const size_t count = PortPacket::DiagnosticAllocationCount(); |
| printf("port packet allocation count: %zu\n", count); |
| } |
| |
| static int mwd_thread(void* arg) { |
| for (;;) { |
| thread_sleep_relative(ZX_SEC(1)); |
| DumpProcessMemoryUsage("MemoryHog! ", mwd_limit); |
| } |
| } |
| |
| static int cmd_diagnostics(int argc, const cmd_args* argv, uint32_t flags) { |
| int rc = 0; |
| |
| if (argc < 2) { |
| printf("not enough arguments:\n"); |
| usage: |
| printf("%s ps : list processes\n", argv[0].str); |
| printf("%s jobs : list jobs\n", argv[0].str); |
| printf("%s mwd <mb> : memory watchdog\n", argv[0].str); |
| printf("%s ht <pid> : dump process handles\n", argv[0].str); |
| printf("%s hwd <count> : handle watchdog\n", argv[0].str); |
| printf("%s vmos <pid>|all|hidden [-u?]\n", argv[0].str); |
| printf(" : dump process/all/hidden VMOs\n"); |
| printf(" -u? : fix all sizes to the named unit\n"); |
| printf(" where ? is one of [BkMGTPE]\n"); |
| printf("%s ppinfo : port packet arena info\n", argv[0].str); |
| printf("%s kill <pid> : kill process\n", argv[0].str); |
| printf("%s asd <pid>|kernel : dump process/kernel address space\n", |
| argv[0].str); |
| printf("%s htinfo : handle table info\n", argv[0].str); |
| return -1; |
| } |
| |
| if (strcmp(argv[1].str, "mwd") == 0) { |
| if (argc == 3) { |
| mwd_limit = argv[2].u * 256; |
| } |
| if (!mwd_running) { |
| thread_t* t = thread_create("mwd", mwd_thread, nullptr, DEFAULT_PRIORITY, DEFAULT_STACK_SIZE); |
| if (t) { |
| mwd_running = true; |
| thread_resume(t); |
| } |
| } |
| } else if (strcmp(argv[1].str, "ps") == 0) { |
| if ((argc == 3) && (strcmp(argv[2].str, "help") == 0)) { |
| DumpProcessListKeyMap(); |
| } else { |
| DumpProcessList(); |
| } |
| } else if (strcmp(argv[1].str, "jobs") == 0) { |
| DumpJobList(); |
| } else if (strcmp(argv[1].str, "hwd") == 0) { |
| if (argc == 3) { |
| hwd_limit = argv[2].u; |
| } |
| if (!hwd_running) { |
| thread_t* t = thread_create("hwd", hwd_thread, nullptr, DEFAULT_PRIORITY, DEFAULT_STACK_SIZE); |
| if (t) { |
| hwd_running = true; |
| thread_resume(t); |
| } |
| } |
| } else if (strcmp(argv[1].str, "ht") == 0) { |
| if (argc < 3) |
| goto usage; |
| DumpProcessHandles(argv[2].u); |
| } else if (strcmp(argv[1].str, "vmos") == 0) { |
| if (argc < 3) |
| goto usage; |
| char format_unit = 0; |
| if (argc >= 4) { |
| if (!strncmp(argv[3].str, "-u", sizeof("-u") - 1)) { |
| format_unit = argv[3].str[sizeof("-u") - 1]; |
| } else { |
| printf("dunno '%s'\n", argv[3].str); |
| goto usage; |
| } |
| } |
| if (strcmp(argv[2].str, "all") == 0) { |
| DumpAllVmObjects(/*hidden_only=*/false, format_unit); |
| } else if (strcmp(argv[2].str, "hidden") == 0) { |
| DumpAllVmObjects(/*hidden_only=*/true, format_unit); |
| } else { |
| DumpProcessVmObjects(argv[2].u, format_unit); |
| } |
| } else if (strcmp(argv[1].str, "ppinfo") == 0) { |
| if (argc != 2) |
| goto usage; |
| DumpPortPacketInfo(); |
| } else if (strcmp(argv[1].str, "kill") == 0) { |
| if (argc < 3) |
| goto usage; |
| KillProcess(argv[2].u); |
| } else if (strcmp(argv[1].str, "asd") == 0) { |
| if (argc < 3) |
| goto usage; |
| DumpAddressSpace(&argv[2]); |
| } else if (strcmp(argv[1].str, "htinfo") == 0) { |
| if (argc != 2) |
| goto usage; |
| DumpHandleTable(); |
| } else { |
| printf("unrecognized subcommand '%s'\n", argv[1].str); |
| goto usage; |
| } |
| return rc; |
| } |
| |
| STATIC_COMMAND_START |
| STATIC_COMMAND("zx", "kernel object diagnostics", &cmd_diagnostics) |
| STATIC_COMMAND_END(zx); |