|  | // 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 <lib/console.h> | 
|  | #include <lib/ktrace.h> | 
|  | #include <stdio.h> | 
|  | #include <string.h> | 
|  | #include <zircon/syscalls/object.h> | 
|  | #include <zircon/types.h> | 
|  |  | 
|  | #include <fbl/auto_lock.h> | 
|  | #include <ktl/span.h> | 
|  | #include <object/handle.h> | 
|  | #include <object/job_dispatcher.h> | 
|  | #include <object/process_dispatcher.h> | 
|  | #include <object/vm_object_dispatcher.h> | 
|  | #include <pretty/cpp/sizes.h> | 
|  | #include <vm/fault.h> | 
|  |  | 
|  | #include <ktl/enforce.h> | 
|  |  | 
|  | namespace { | 
|  |  | 
|  | using pretty::FormattedBytes; | 
|  |  | 
|  | // 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> | 
|  | ProcessWalker<ProcessCallbackType> MakeProcessWalker(ProcessCallbackType cb) { | 
|  | return ProcessWalker<ProcessCallbackType>(cb); | 
|  | } | 
|  |  | 
|  | // Machinery to walk over a job tree and run a callback on each job. | 
|  | template <typename JobCallbackType> | 
|  | class JobWalker final : public JobEnumerator { | 
|  | public: | 
|  | JobWalker(JobCallbackType cb) : cb_(cb) {} | 
|  | JobWalker(const JobWalker&) = delete; | 
|  | JobWalker(JobWalker&& other) : cb_(other.cb_) {} | 
|  |  | 
|  | private: | 
|  | bool OnJob(JobDispatcher* job) final { | 
|  | cb_(job); | 
|  | return true; | 
|  | } | 
|  |  | 
|  | const JobCallbackType cb_; | 
|  | }; | 
|  |  | 
|  | template <typename JobCallbackType> | 
|  | JobWalker<JobCallbackType> MakeJobWalker(JobCallbackType cb) { | 
|  | return JobWalker<JobCallbackType>(cb); | 
|  | } | 
|  |  | 
|  | 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"); | 
|  | printf("#?? : number of all other handle types\n"); | 
|  | } | 
|  |  | 
|  | const char* ObjectTypeToString(zx_obj_type_t type) { | 
|  | 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_EVENTPAIR: | 
|  | 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"; | 
|  | case ZX_OBJ_TYPE_PROFILE: | 
|  | return "profile"; | 
|  | case ZX_OBJ_TYPE_PMT: | 
|  | return "pmt"; | 
|  | case ZX_OBJ_TYPE_SUSPEND_TOKEN: | 
|  | return "suspend-token"; | 
|  | case ZX_OBJ_TYPE_PAGER: | 
|  | return "pager"; | 
|  | case ZX_OBJ_TYPE_EXCEPTION: | 
|  | return "exception"; | 
|  | default: | 
|  | return "???"; | 
|  | } | 
|  | } | 
|  |  | 
|  | // Returns the count of a process's handles. For each handle, the corresponding | 
|  | // zx_obj_type_t-indexed element of |handle_types| is incremented. | 
|  | using HandleTypeCounts = ktl::span<uint32_t, ZX_OBJ_TYPE_UPPER_BOUND>; | 
|  | uint32_t BuildHandleStats(const ProcessDispatcher& pd, HandleTypeCounts handle_types) { | 
|  | uint32_t total = 0; | 
|  | pd.handle_table().ForEachHandle( | 
|  | [&](zx_handle_t handle, zx_rights_t rights, const Dispatcher* disp) { | 
|  | uint32_t type = static_cast<uint32_t>(disp->get_type()); | 
|  | ++handle_types[type]; | 
|  | ++total; | 
|  | return ZX_OK; | 
|  | }); | 
|  | return total; | 
|  | } | 
|  |  | 
|  | // Counts the process's handles by type and formats them into the provided | 
|  | // buffer as strings. | 
|  | void FormatHandleTypeCount(const ProcessDispatcher& pd, char* buf, size_t buf_len) { | 
|  | uint32_t types[ZX_OBJ_TYPE_UPPER_BOUND] = {0}; | 
|  | uint32_t handle_count = BuildHandleStats(pd, types); | 
|  |  | 
|  | snprintf(buf, buf_len, "%4u: %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_EVENTPAIR], types[ZX_OBJ_TYPE_PORT], | 
|  | types[ZX_OBJ_TYPE_SOCKET], types[ZX_OBJ_TYPE_TIMER], types[ZX_OBJ_TYPE_FIFO], | 
|  | types[ZX_OBJ_TYPE_INTERRUPT] + types[ZX_OBJ_TYPE_PCI_DEVICE] + types[ZX_OBJ_TYPE_LOG] + | 
|  | types[ZX_OBJ_TYPE_RESOURCE] + types[ZX_OBJ_TYPE_GUEST] + types[ZX_OBJ_TYPE_VCPU] + | 
|  | types[ZX_OBJ_TYPE_IOMMU] + types[ZX_OBJ_TYPE_BTI] + types[ZX_OBJ_TYPE_PROFILE] + | 
|  | types[ZX_OBJ_TYPE_PMT] + types[ZX_OBJ_TYPE_SUSPEND_TOKEN] + | 
|  | types[ZX_OBJ_TYPE_PAGER] + types[ZX_OBJ_TYPE_EXCEPTION]); | 
|  | } | 
|  |  | 
|  | 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_UPPER_BOUND * 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()->EnumerateChildrenRecursive(&walker); | 
|  | } | 
|  |  | 
|  | void DumpJobList() { | 
|  | printf("All jobs:\n"); | 
|  | printf("%7s %s\n", "koid", "name"); | 
|  | auto walker = MakeJobWalker([](JobDispatcher* job) { | 
|  | char name[ZX_MAX_NAME_LEN]; | 
|  | job->get_name(name); | 
|  | printf("%7" PRIu64 " '%s'\n", job->get_koid(), name); | 
|  | }); | 
|  | GetRootJobDispatcher()->EnumerateChildrenRecursive(&walker); | 
|  | } | 
|  |  | 
|  | void DumpProcessChannels(fbl::RefPtr<ProcessDispatcher> process, | 
|  | zx_koid_t koid_filter = ZX_KOID_INVALID) { | 
|  | char pname[ZX_MAX_NAME_LEN]; | 
|  | bool printed_header = false; | 
|  |  | 
|  | process->handle_table().ForEachHandle( | 
|  | [&](zx_handle_t handle, zx_rights_t rights, const Dispatcher* disp) { | 
|  | if (disp->get_type() == ZX_OBJ_TYPE_CHANNEL) { | 
|  | auto chan = DownCastDispatcher<const ChannelDispatcher>(disp); | 
|  | const uint64_t koid = chan->get_koid(); | 
|  | const uint64_t peer_koid = chan->get_related_koid(); | 
|  | const ChannelDispatcher::MessageCounts counts = chan->get_message_counts(); | 
|  |  | 
|  | if (koid_filter != ZX_KOID_INVALID && koid_filter != koid && koid_filter != peer_koid) | 
|  | return ZX_OK; | 
|  | if (!printed_header) { | 
|  | process->get_name(pname); | 
|  | printf("%7" PRIu64 " [%s]\n", process->get_koid(), pname); | 
|  | printed_header = true; | 
|  | } | 
|  | printf("    chan %7" PRIu64 " %7" PRIu64 " count %" PRIu64 " max %" PRIu64 "\n", koid, | 
|  | peer_koid, counts.current, counts.max); | 
|  | } | 
|  | return ZX_OK; | 
|  | }); | 
|  | } | 
|  |  | 
|  | void DumpChannelsByKoid(zx_koid_t id) { | 
|  | auto pd = ProcessDispatcher::LookupProcessById(id); | 
|  | if (pd) { | 
|  | DumpProcessChannels(pd); | 
|  | } else { | 
|  | auto walker = MakeProcessWalker( | 
|  | [id](ProcessDispatcher* process) { DumpProcessChannels(fbl::RefPtr(process), id); }); | 
|  | GetRootJobDispatcher()->EnumerateChildrenRecursive(&walker); | 
|  | } | 
|  | } | 
|  |  | 
|  | void DumpAllChannels() { | 
|  | auto walker = MakeProcessWalker( | 
|  | [](ProcessDispatcher* process) { DumpProcessChannels(fbl::RefPtr(process)); }); | 
|  | GetRootJobDispatcher()->EnumerateChildrenRecursive(&walker); | 
|  | } | 
|  |  | 
|  | const char kRightsHeader[] = | 
|  | "dup tr r w x map gpr spr enm des spo gpo sig sigp wt ins mj mp mt ap ms"; | 
|  | void DumpHandleRightsKeyMap() { | 
|  | printf("dup : ZX_RIGHT_DUPLICATE\n"); | 
|  | printf("tr  : ZX_RIGHT_TRANSFER\n"); | 
|  | printf("r   : ZX_RIGHT_READ\n"); | 
|  | printf("w   : ZX_RIGHT_WRITE\n"); | 
|  | printf("x   : ZX_RIGHT_EXECUTE\n"); | 
|  | printf("map : ZX_RIGHT_MAP\n"); | 
|  | printf("gpr : ZX_RIGHT_GET_PROPERTY\n"); | 
|  | printf("spr : ZX_RIGHT_SET_PROPERTY\n"); | 
|  | printf("enm : ZX_RIGHT_ENUMERATE\n"); | 
|  | printf("des : ZX_RIGHT_DESTROY\n"); | 
|  | printf("spo : ZX_RIGHT_SET_POLICY\n"); | 
|  | printf("gpo : ZX_RIGHT_GET_POLICY\n"); | 
|  | printf("sig : ZX_RIGHT_SIGNAL\n"); | 
|  | printf("sigp: ZX_RIGHT_SIGNAL_PEER\n"); | 
|  | printf("wt  : ZX_RIGHT_WAIT\n"); | 
|  | printf("ins : ZX_RIGHT_INSPECT\n"); | 
|  | printf("mj  : ZX_RIGHT_MANAGE_JOB\n"); | 
|  | printf("mp  : ZX_RIGHT_MANAGE_PROCESS\n"); | 
|  | printf("mt  : ZX_RIGHT_MANAGE_THREAD\n"); | 
|  | printf("ap  : ZX_RIGHT_APPLY_PROFILE\n"); | 
|  | printf("ms  : ZX_RIGHT_MANAGE_SOCKET\n"); | 
|  | } | 
|  |  | 
|  | bool HasRights(zx_rights_t rights, zx_rights_t desired) { return (rights & desired) == desired; } | 
|  |  | 
|  | void FormatHandleRightsMask(zx_rights_t rights, char* buf, size_t buf_len) { | 
|  | snprintf(buf, buf_len, | 
|  | "%3d %2d %1d %1d %1d %3d %3d %3d %3d %3d %3d %3d %3d %4d %2d %3d %2d %2d %2d %2d %2d", | 
|  | HasRights(rights, ZX_RIGHT_DUPLICATE), HasRights(rights, ZX_RIGHT_TRANSFER), | 
|  | HasRights(rights, ZX_RIGHT_READ), HasRights(rights, ZX_RIGHT_WRITE), | 
|  | HasRights(rights, ZX_RIGHT_EXECUTE), HasRights(rights, ZX_RIGHT_MAP), | 
|  | HasRights(rights, ZX_RIGHT_GET_PROPERTY), HasRights(rights, ZX_RIGHT_SET_PROPERTY), | 
|  | HasRights(rights, ZX_RIGHT_ENUMERATE), HasRights(rights, ZX_RIGHT_DESTROY), | 
|  | HasRights(rights, ZX_RIGHT_SET_POLICY), HasRights(rights, ZX_RIGHT_GET_POLICY), | 
|  | HasRights(rights, ZX_RIGHT_SIGNAL), HasRights(rights, ZX_RIGHT_SIGNAL_PEER), | 
|  | HasRights(rights, ZX_RIGHT_WAIT), HasRights(rights, ZX_RIGHT_INSPECT), | 
|  | HasRights(rights, ZX_RIGHT_MANAGE_JOB), HasRights(rights, ZX_RIGHT_MANAGE_PROCESS), | 
|  | HasRights(rights, ZX_RIGHT_MANAGE_THREAD), HasRights(rights, ZX_RIGHT_APPLY_PROFILE), | 
|  | HasRights(rights, ZX_RIGHT_MANAGE_SOCKET)); | 
|  | } | 
|  |  | 
|  | void DumpProcessHandles(zx_koid_t id) { | 
|  | auto pd = ProcessDispatcher::LookupProcessById(id); | 
|  | if (!pd) { | 
|  | printf("process %" PRIu64 " not found!\n", id); | 
|  | return; | 
|  | } | 
|  |  | 
|  | char pname[ZX_MAX_NAME_LEN]; | 
|  | pd->get_name(pname); | 
|  | printf("process %" PRIu64 " ('%s') handles:\n", id, pname); | 
|  | printf("%7s %10s %10s: {%s} [type]\n", "koid", "handle", "rights", kRightsHeader); | 
|  |  | 
|  | uint32_t total = 0; | 
|  | pd->handle_table().ForEachHandle( | 
|  | [&](zx_handle_t handle, zx_rights_t rights, const Dispatcher* disp) { | 
|  | char rights_mask[sizeof(kRightsHeader)]; | 
|  | FormatHandleRightsMask(rights, rights_mask, sizeof(rights_mask)); | 
|  | printf("%7" PRIu64 " %#10x %#10x: {%s} [%s]\n", disp->get_koid(), handle, rights, | 
|  | rights_mask, ObjectTypeToString(disp->get_type())); | 
|  | ++total; | 
|  | return ZX_OK; | 
|  | }); | 
|  | printf("total: %u handles\n", total); | 
|  | } | 
|  |  | 
|  | void DumpHandlesForKoid(zx_koid_t id) { | 
|  | if (id < ZX_KOID_FIRST) { | 
|  | printf("invalid koid, non-reserved koids start at %" PRIu64 "\n", ZX_KOID_FIRST); | 
|  | return; | 
|  | } | 
|  |  | 
|  | uint32_t total_proc = 0; | 
|  | uint32_t total_handles = 0; | 
|  | auto walker = MakeProcessWalker([&](ProcessDispatcher* process) { | 
|  | bool found_handle = false; | 
|  | process->handle_table().ForEachHandle([&](zx_handle_t handle, zx_rights_t rights, | 
|  | const Dispatcher* disp) { | 
|  | if (disp->get_koid() != id) { | 
|  | return ZX_OK; | 
|  | } | 
|  |  | 
|  | if (total_handles == 0) { | 
|  | printf("handles for koid %" PRIu64 " (%s):\n", id, ObjectTypeToString(disp->get_type())); | 
|  | printf("%7s %10s: {%s} [name]\n", "pid", "rights", kRightsHeader); | 
|  | } | 
|  |  | 
|  | char pname[ZX_MAX_NAME_LEN]; | 
|  | char rights_mask[sizeof(kRightsHeader)]; | 
|  | process->get_name(pname); | 
|  | FormatHandleRightsMask(rights, rights_mask, sizeof(rights_mask)); | 
|  | printf("%7" PRIu64 " %#10x: {%s} [%s]\n", process->get_koid(), rights, rights_mask, pname); | 
|  |  | 
|  | ++total_handles; | 
|  | found_handle = true; | 
|  | return ZX_OK; | 
|  | }); | 
|  | total_proc += found_handle; | 
|  | }); | 
|  | GetRootJobDispatcher()->EnumerateChildrenRecursive(&walker); | 
|  |  | 
|  | if (total_handles > 0) { | 
|  | printf("total: %u handles in %u processes\n", total_handles, total_proc); | 
|  | } else { | 
|  | printf("no handles found for koid %" PRIu64 "\n", id); | 
|  | } | 
|  | } | 
|  |  | 
|  | }  // namespace | 
|  |  | 
|  | 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, /*always*/ true); | 
|  | }); | 
|  | GetRootJobDispatcher()->EnumerateChildrenRecursive(&walker); | 
|  | } | 
|  |  | 
|  | namespace { | 
|  |  | 
|  | // Returns a string representation of VMO-related rights. | 
|  | constexpr size_t kRightsStrLen = 8; | 
|  | 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. | 
|  | void PrintVmoDumpHeader(bool handles) { | 
|  | printf("%s koid obj                parent #chld #map #shr    size   alloc name\n", | 
|  | handles ? "      handle rights " : "           -      - "); | 
|  | } | 
|  |  | 
|  | void DumpVmObject(const VmObject& vmo, pretty::SizeUnit 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'; | 
|  | } | 
|  |  | 
|  | FormattedBytes size_str(vmo.size(), format_unit); | 
|  |  | 
|  | FormattedBytes alloc_size; | 
|  | const char* alloc_str = "phys"; | 
|  | if (vmo.is_paged()) { | 
|  | alloc_size.SetSize(vmo.AttributedPages() * PAGE_SIZE, format_unit); | 
|  | alloc_str = alloc_size.c_str(); | 
|  | } | 
|  |  | 
|  | char child_str[21]; | 
|  | if (vmo.child_type() != VmObject::kNotChild) { | 
|  | snprintf(child_str, sizeof(child_str), "%" PRIu64, vmo.parent_user_id()); | 
|  | } else { | 
|  | child_str[0] = '-'; | 
|  | child_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 | 
|  | "%p "   // vm_object | 
|  | "%6s "  // child 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, &vmo, child_str, vmo.num_children(), vmo.num_mappings(), | 
|  | vmo.share_count(), size_str.c_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 | 
|  | void DumpAllVmObjects(bool hidden_only, pretty::SizeUnit 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); | 
|  | } | 
|  |  | 
|  | // Dumps VMOs under a VmAspace. | 
|  | class AspaceVmoDumper final : public VmEnumerator { | 
|  | public: | 
|  | explicit AspaceVmoDumper(pretty::SizeUnit format_unit) : format_unit_(format_unit) {} | 
|  | bool OnVmMapping(const VmMapping* map, const VmAddressRegion* vmar, uint depth) final | 
|  | TA_REQ(map->lock()) TA_REQ(vmar->lock()) { | 
|  | auto vmo = map->vmo_locked(); | 
|  | DumpVmObject(*vmo, format_unit_, ZX_HANDLE_INVALID, | 
|  | /* rights */ 0u, | 
|  | /* koid */ vmo->user_id()); | 
|  | return true; | 
|  | } | 
|  |  | 
|  | private: | 
|  | pretty::SizeUnit format_unit_; | 
|  | }; | 
|  |  | 
|  | // Dumps all VMOs associated with a process. | 
|  | void DumpProcessVmObjects(zx_koid_t id, pretty::SizeUnit 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->handle_table().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->AttributedPages() * PAGE_SIZE; | 
|  | return ZX_OK; | 
|  | }); | 
|  | printf("  total: %d VMOs, size %s, alloc %s\n", count, | 
|  | FormattedBytes(total_size, format_unit).c_str(), | 
|  | FormattedBytes(total_alloc, format_unit).c_str()); | 
|  |  | 
|  | // 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(ZX_TASK_RETCODE_SYSCALL_KILL); | 
|  | } | 
|  |  | 
|  | // Counts memory usage under a VmAspace. | 
|  | class VmCounter final : public VmEnumerator { | 
|  | public: | 
|  | bool OnVmMapping(const VmMapping* map, const VmAddressRegion* vmar, uint depth) | 
|  | TA_REQ(map->lock()) TA_REQ(vmar->lock()) override { | 
|  | usage.mapped_pages += map->size() / PAGE_SIZE; | 
|  |  | 
|  | auto vmo = map->vmo_locked(); | 
|  | size_t committed_pages = vmo->AttributedPagesInRange(map->object_offset_locked(), map->size()); | 
|  | uint32_t share_count = 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) != ZX_OK) { | 
|  | *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_PERM_READ; | 
|  | } | 
|  | if (arch_mmu_flags & ARCH_MMU_FLAG_PERM_WRITE) { | 
|  | ret |= ZX_VM_PERM_WRITE; | 
|  | } | 
|  | if (arch_mmu_flags & ARCH_MMU_FLAG_PERM_EXECUTE) { | 
|  | ret |= ZX_VM_PERM_EXECUTE; | 
|  | } | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | // This provides a generic way to perform VmAspace::EnumerateChildren in scenarios where the | 
|  | // enumeration may need to be retried due to page faults for user copies needing to be handled. | 
|  | // Mostly it serves to reduce the duplication in logic between the VmMapBuilder and the | 
|  | // AspaceVmoEnumerator and so the template options exist to handle precisely those two cases. | 
|  | // IMPL is the object type that the Make* methods will be called on if the respective Enumerate* | 
|  | // options are true. | 
|  | enum class MappingEnumeration { | 
|  | // Skip mappings. | 
|  | None, | 
|  | // Only care about virtual ranges and can enumerate entire mappings even if they have different | 
|  | // protection types. | 
|  | Mapping, | 
|  | // Enumerate every different protection block. | 
|  | Protection, | 
|  | }; | 
|  | template <typename ENTRY, typename IMPL, bool EnumerateVmar, MappingEnumeration EnumerateMapping, | 
|  | size_t FirstEntry> | 
|  | class RestartableVmEnumerator { | 
|  | public: | 
|  | // max is the total number of elements that entries can support, with FirstEntry being the first | 
|  | // of these entries that this enumerator will store to. This means we can write at most | 
|  | // max-FirstEntry entries. | 
|  | RestartableVmEnumerator(size_t max) : max_(max) {} | 
|  |  | 
|  | zx_status_t Enumerate(VmAspace* target) { | 
|  | nelem_ = FirstEntry; | 
|  | available_ = FirstEntry; | 
|  | start_ = 0; | 
|  | faults_ = 0; | 
|  | visited_ = 0; | 
|  |  | 
|  | // EnumerateChildren only fails if copying to the user hit a fault, or the aspace itself has | 
|  | // been destroyed. If the aspace got destroyed this is fatal and we propagate that error, | 
|  | // otherwise as long as the user copy failed (i.e. we got ZX_ERR_CANCELED) we redo the copy | 
|  | // outside of the enumeration so that we're not holding the aspace lock. If it still fails then | 
|  | // we consider it an error, otherwise we restart the enumeration skipping any entries with a | 
|  | // virtual address in the segment we already processed. A segment is represented by an address | 
|  | // and a depth pair, as vmars/mappings can exist at the same base address due to them being | 
|  | // hierarchical, but they will have a higher depth. | 
|  | Enumerator enumerator{this}; | 
|  | zx_status_t result; | 
|  | while ((result = target->EnumerateChildren(&enumerator)) == ZX_ERR_CANCELED) { | 
|  | DEBUG_ASSERT(nelem_ < max_); | 
|  | result = WriteEntry(entry_, nelem_); | 
|  | if (result != ZX_OK) { | 
|  | return result; | 
|  | } | 
|  | nelem_++; | 
|  | } | 
|  |  | 
|  | // This aims to ensure that the logic of skipping already processed segments does not cause us | 
|  | // to miss any segments. Guards against the VmAspace failing to correctly enumerate in depth | 
|  | // first order. | 
|  | DEBUG_ASSERT(faults_ > 0 || visited_ + FirstEntry == available_); | 
|  |  | 
|  | return result; | 
|  | } | 
|  |  | 
|  | size_t nelem() const { return nelem_; } | 
|  | size_t available() const { return available_; } | 
|  |  | 
|  | protected: | 
|  | virtual zx_status_t WriteEntry(const ENTRY& entry, size_t offset) = 0; | 
|  | virtual UserCopyCaptureFaultsResult WriteEntryCaptureFaults(const ENTRY& entry, | 
|  | size_t offset) = 0; | 
|  |  | 
|  | private: | 
|  | class Enumerator : public VmEnumerator { | 
|  | public: | 
|  | Enumerator(RestartableVmEnumerator* parent) : parent_(parent) {} | 
|  |  | 
|  | bool OnVmAddressRegion([[maybe_unused]] const VmAddressRegion* vmar, | 
|  | [[maybe_unused]] uint depth) override { | 
|  | if constexpr (EnumerateVmar) { | 
|  | return parent_->DoEntry( | 
|  | [vmar, depth, this] { IMPL::MakeVmarEntry(vmar, depth, &parent_->entry_); }, | 
|  | vmar->base(), depth); | 
|  | } | 
|  | return true; | 
|  | } | 
|  |  | 
|  | bool OnVmMapping([[maybe_unused]] const VmMapping* map, | 
|  | [[maybe_unused]] const VmAddressRegion* vmar, [[maybe_unused]] uint depth) | 
|  | TA_REQ(map->lock()) TA_REQ(vmar->lock()) override { | 
|  | if constexpr (EnumerateMapping == MappingEnumeration::Mapping) { | 
|  | return parent_->DoEntry( | 
|  | [map, vmar, depth, this]() { | 
|  | // These are true as they are required for calling OnVmMapping, but we cannot pass | 
|  | // the capabilities easily via DoEntry to this callback. | 
|  | AssertHeld(map->lock_ref()); | 
|  | AssertHeld(vmar->lock_ref()); | 
|  | IMPL::MakeMappingEntry(map, vmar, depth, &parent_->entry_); | 
|  | }, | 
|  | map->base(), depth); | 
|  | } else if constexpr (EnumerateMapping == MappingEnumeration::Protection) { | 
|  | struct { | 
|  | const VmMapping* map; | 
|  | const VmAddressRegion* vmar; | 
|  | uint depth; | 
|  | } state{map, vmar, depth}; | 
|  | zx_status_t result = map->EnumerateProtectionRangesLocked( | 
|  | map->base(), map->size(), [&state, this](vaddr_t base, size_t size, uint flags) { | 
|  | return parent_->DoEntry( | 
|  | [&state, base, size, flags, this] { | 
|  | // These are true as they are required for calling OnVmMapping, but we | 
|  | // cannot pass the capabilities easily via DoEntry to this callback. | 
|  | AssertHeld(state.map->lock_ref()); | 
|  | AssertHeld(state.vmar->lock_ref()); | 
|  | const uint64_t object_offset = | 
|  | state.map->object_offset_locked() + (base - state.map->base()); | 
|  | IMPL::MakeMappingProtectionEntry(state.map, state.vmar, state.depth, | 
|  | base, size, flags, object_offset, | 
|  | &parent_->entry_); | 
|  | }, | 
|  | base, state.depth) | 
|  | ? ZX_ERR_NEXT | 
|  | : ZX_ERR_BUFFER_TOO_SMALL; | 
|  | }); | 
|  | // We expect to either complete enumeration or have an explicit early termination request | 
|  | // due to needing to fault in more of the user buffer. | 
|  | DEBUG_ASSERT_MSG(result == ZX_OK || result == ZX_ERR_BUFFER_TOO_SMALL, | 
|  | "Unexpected status %d\n", result); | 
|  | return result == ZX_OK ? true : false; | 
|  | } else { | 
|  | static_assert(EnumerateMapping == MappingEnumeration::None); | 
|  | } | 
|  | return true; | 
|  | } | 
|  |  | 
|  | private: | 
|  | RestartableVmEnumerator* parent_; | 
|  | }; | 
|  | friend Enumerator; | 
|  |  | 
|  | // This helper is templated to allow the maximum code sharing between the two On* callbacks. | 
|  | template <typename F> | 
|  | bool DoEntry(F&& make_entry, zx_vaddr_t base, uint depth) { | 
|  | visited_++; | 
|  | // Skip anything that is at an earlier address or depth to prevent us double processing any | 
|  | // segments. | 
|  | if (base < start_ || (base == start_ && depth < start_depth_)) { | 
|  | return true; | 
|  | } | 
|  | // Whatever happens we never want to process this again. We set this *always*, and not just on | 
|  | // faults so that the logic of skipping above is consistently applied helping catch any bugs in | 
|  | // changes to enumeration order. | 
|  | start_ = base; | 
|  | start_depth_ = depth + 1; | 
|  |  | 
|  | available_++; | 
|  | if (nelem_ >= max_) { | 
|  | return true; | 
|  | } | 
|  | ktl::forward<F>(make_entry)(); | 
|  |  | 
|  | UserCopyCaptureFaultsResult res = WriteEntryCaptureFaults(entry_, nelem_); | 
|  | if (res.status != ZX_OK) { | 
|  | // This entry will get written out by the main loop, so return false to break all the way out. | 
|  | faults_++; | 
|  | return false; | 
|  | } | 
|  |  | 
|  | nelem_++; | 
|  | return true; | 
|  | } | 
|  |  | 
|  | const size_t max_; | 
|  |  | 
|  | // Use a single ENTRY stashed here and pass it by reference anywhere as this can be a large | 
|  | // structure and we want to avoid multiple stack allocations from occurring. | 
|  | ENTRY entry_{}; | 
|  | static_assert(sizeof(ENTRY) < 512); | 
|  | size_t nelem_ = 0; | 
|  | size_t available_ = 0; | 
|  |  | 
|  | zx_vaddr_t start_ = 0; | 
|  | uint start_depth_ = 0; | 
|  |  | 
|  | // Count some statistics so we can do some lightweight sanity checking that we correctly process | 
|  | // everything. | 
|  | size_t faults_ = 0; | 
|  | size_t visited_ = 0; | 
|  | }; | 
|  |  | 
|  | // Builds a description of an apsace/vmar/mapping hierarchy. Entries start at 1 as the user must | 
|  | // write an entry for the root VmAspace at index 0. | 
|  | class VmMapBuilder final : public RestartableVmEnumerator<zx_info_maps_t, VmMapBuilder, true, | 
|  | MappingEnumeration::Protection, 1> { | 
|  | 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) | 
|  | : RestartableVmEnumerator(max), entries_(maps) {} | 
|  |  | 
|  | static void MakeVmarEntry(const VmAddressRegion* vmar, uint depth, zx_info_maps_t* entry) { | 
|  | *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; | 
|  | } | 
|  |  | 
|  | static void MakeMappingProtectionEntry(const VmMapping* map, const VmAddressRegion* vmar, | 
|  | uint depth, vaddr_t region_base, size_t region_size, | 
|  | uint region_mmu_flags, uint64_t region_object_offset, | 
|  | zx_info_maps_t* entry) TA_REQ(map->lock_ref()) | 
|  | TA_REQ(vmar->lock_ref()) { | 
|  | *entry = {}; | 
|  | auto vmo = map->vmo_locked(); | 
|  | vmo->get_name(entry->name, sizeof(entry->name)); | 
|  | entry->base = region_base; | 
|  | entry->size = region_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(region_mmu_flags), u->vmo_koid = vmo->user_id(); | 
|  | u->committed_pages = vmo->AttributedPagesInRange(region_object_offset, region_size); | 
|  | u->vmo_offset = region_object_offset; | 
|  | } | 
|  |  | 
|  | protected: | 
|  | zx_status_t WriteEntry(const zx_info_maps_t& entry, size_t offset) { | 
|  | return entries_.element_offset(offset).copy_to_user(entry); | 
|  | } | 
|  | UserCopyCaptureFaultsResult WriteEntryCaptureFaults(const zx_info_maps_t& entry, size_t offset) { | 
|  | return entries_.element_offset(offset).copy_to_user_capture_faults(entry); | 
|  | } | 
|  | const user_out_ptr<zx_info_maps_t> entries_; | 
|  | }; | 
|  |  | 
|  | }  // 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(VmAspace* current_aspace, fbl::RefPtr<VmAspace> target_aspace, | 
|  | user_out_ptr<zx_info_maps_t> maps, size_t max, size_t* actual, | 
|  | size_t* available) { | 
|  | DEBUG_ASSERT(target_aspace != nullptr); | 
|  | *actual = 0; | 
|  | *available = 0; | 
|  | if (target_aspace->is_destroyed()) { | 
|  | return ZX_ERR_BAD_STATE; | 
|  | } | 
|  | if (max > 0) { | 
|  | zx_info_maps_t entry = {}; | 
|  | strlcpy(entry.name, target_aspace->name(), sizeof(entry.name)); | 
|  | entry.base = target_aspace->base(); | 
|  | entry.size = target_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); | 
|  |  | 
|  | zx_status_t status = b.Enumerate(target_aspace.get()); | 
|  | if (status != ZX_OK) { | 
|  | return status; | 
|  | } | 
|  |  | 
|  | *actual = max > 0 ? b.nelem() : 0; | 
|  | *available = b.available(); | 
|  | return ZX_OK; | 
|  | } | 
|  |  | 
|  | namespace { | 
|  |  | 
|  | // Builds a list of all VMOs mapped into a VmAspace. | 
|  | class AspaceVmoEnumerator final | 
|  | : public RestartableVmEnumerator<zx_info_vmo_t, AspaceVmoEnumerator, false, | 
|  | MappingEnumeration::Mapping, 0> { | 
|  | public: | 
|  | // NOTE: Code outside of the syscall layer should not typically know about | 
|  | // user_ptrs; do not use this pattern as an example. | 
|  | AspaceVmoEnumerator(VmoInfoWriter& vmos, size_t max) | 
|  | : RestartableVmEnumerator(max), vmos_(vmos) {} | 
|  |  | 
|  | static void MakeMappingEntry(const VmMapping* map, const VmAddressRegion* vmar, uint depth, | 
|  | zx_info_vmo_t* entry) TA_REQ(map->lock()) { | 
|  | // 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. | 
|  | *entry = VmoToInfoEntry(map->vmo_locked().get(), | 
|  | /*is_handle=*/false, | 
|  | /*handle_rights=*/0); | 
|  | } | 
|  |  | 
|  | protected: | 
|  | zx_status_t WriteEntry(const zx_info_vmo_t& entry, size_t offset) { | 
|  | return vmos_.Write(entry, offset); | 
|  | } | 
|  | UserCopyCaptureFaultsResult WriteEntryCaptureFaults(const zx_info_vmo_t& entry, size_t offset) { | 
|  | return vmos_.WriteCaptureFaults(entry, offset); | 
|  | } | 
|  | VmoInfoWriter& vmos_; | 
|  | }; | 
|  |  | 
|  | }  // 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(VmAspace* current_aspace, fbl::RefPtr<VmAspace> target_aspace, | 
|  | VmoInfoWriter& vmos, size_t max, size_t* actual, size_t* available) { | 
|  | DEBUG_ASSERT(target_aspace != nullptr); | 
|  | DEBUG_ASSERT(actual != nullptr); | 
|  | DEBUG_ASSERT(available != nullptr); | 
|  | *actual = 0; | 
|  | *available = 0; | 
|  | if (target_aspace->is_destroyed()) { | 
|  | return ZX_ERR_BAD_STATE; | 
|  | } | 
|  |  | 
|  | AspaceVmoEnumerator ave(vmos, max); | 
|  |  | 
|  | zx_status_t status = ave.Enumerate(target_aspace.get()); | 
|  | if (status != ZX_OK) { | 
|  | return status; | 
|  | } | 
|  |  | 
|  | *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 GetProcessVmos(ProcessDispatcher* process, VmoInfoWriter& 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->handle_table().ForEachHandleBatched( | 
|  | [&](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.Write(entry, 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; | 
|  | } | 
|  |  | 
|  | namespace { | 
|  |  | 
|  | 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. | 
|  | 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); | 
|  | } | 
|  | } | 
|  |  | 
|  | void DumpHandleTable() { | 
|  | printf("outstanding handles: %zu\n", Handle::diagnostics::OutstandingHandles()); | 
|  | Handle::diagnostics::DumpTableInfo(); | 
|  | } | 
|  |  | 
|  | size_t mwd_limit = 32 * 256; | 
|  | bool mwd_running; | 
|  |  | 
|  | size_t hwd_limit = 1024; | 
|  | bool hwd_running; | 
|  |  | 
|  | 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::Current::SleepRelative(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()->EnumerateChildrenRecursive(&walker); | 
|  | } | 
|  |  | 
|  | int mwd_thread(void* arg) { | 
|  | for (;;) { | 
|  | Thread::Current::SleepRelative(ZX_SEC(1)); | 
|  | DumpProcessMemoryUsage("MemoryHog! ", mwd_limit); | 
|  | } | 
|  | } | 
|  |  | 
|  | 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 ps help           : print header label descriptions for 'ps'\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 ch   <koid>       : dump channels for pid or for all processes,\n", argv[0].str); | 
|  | printf("                       or processes for channel koid\n"); | 
|  | 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 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); | 
|  | printf("%s koid <koid>       : list all handles for a koid\n", argv[0].str); | 
|  | printf("%s koid help         : print header label descriptions for 'koid'\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 = Thread::Create("mwd", mwd_thread, nullptr, DEFAULT_PRIORITY); | 
|  | if (t) { | 
|  | mwd_running = true; | 
|  | t->Resume(); | 
|  | } | 
|  | } | 
|  | } 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 = Thread::Create("hwd", hwd_thread, nullptr, DEFAULT_PRIORITY); | 
|  | if (t) { | 
|  | hwd_running = true; | 
|  | t->Resume(); | 
|  | } | 
|  | } | 
|  | } else if (strcmp(argv[1].str, "ht") == 0) { | 
|  | if (argc < 3) | 
|  | goto usage; | 
|  | DumpProcessHandles(argv[2].u); | 
|  | } else if (strcmp(argv[1].str, "ch") == 0) { | 
|  | if (argc == 3) { | 
|  | DumpChannelsByKoid(argv[2].u); | 
|  | } else { | 
|  | DumpAllChannels(); | 
|  | } | 
|  | } else if (strcmp(argv[1].str, "vmos") == 0) { | 
|  | if (argc < 3) | 
|  | goto usage; | 
|  | pretty::SizeUnit format_unit = pretty::SizeUnit::kAuto; | 
|  | if (argc >= 4) { | 
|  | if (!strncmp(argv[3].str, "-u", sizeof("-u") - 1)) { | 
|  | format_unit = static_cast<pretty::SizeUnit>(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, "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 if (strcmp(argv[1].str, "koid") == 0) { | 
|  | if (argc < 3) | 
|  | goto usage; | 
|  |  | 
|  | if (strcmp(argv[2].str, "help") == 0) { | 
|  | DumpHandleRightsKeyMap(); | 
|  | } else { | 
|  | DumpHandlesForKoid(argv[2].u); | 
|  | } | 
|  | } else { | 
|  | printf("unrecognized subcommand '%s'\n", argv[1].str); | 
|  | goto usage; | 
|  | } | 
|  | return rc; | 
|  | } | 
|  |  | 
|  | }  // namespace | 
|  |  | 
|  | STATIC_COMMAND_START | 
|  | STATIC_COMMAND("zx", "kernel object diagnostics", &cmd_diagnostics) | 
|  | STATIC_COMMAND_END(zx) |