1// Copyright 2016 The Fuchsia Authors
2//
3// Use of this source code is governed by a MIT-style
4// license that can be found in the LICENSE file or at
5// https://opensource.org/licenses/MIT
6
7#include <object/diagnostics.h>
8
9#include <inttypes.h>
10#include <stdio.h>
11#include <string.h>
12
13#include <lib/console.h>
14#include <lib/ktrace.h>
15#include <fbl/auto_lock.h>
16#include <object/handle.h>
17#include <object/job_dispatcher.h>
18#include <object/process_dispatcher.h>
19#include <object/vm_object_dispatcher.h>
20#include <pretty/sizes.h>
21#include <zircon/types.h>
22
23// Machinery to walk over a job tree and run a callback on each process.
24template <typename ProcessCallbackType>
25class ProcessWalker final : public JobEnumerator {
26public:
27    ProcessWalker(ProcessCallbackType cb) : cb_(cb) {}
28    ProcessWalker(const ProcessWalker&) = delete;
29    ProcessWalker(ProcessWalker&& other) : cb_(other.cb_) {}
30
31private:
32    bool OnProcess(ProcessDispatcher* process) final {
33        cb_(process);
34        return true;
35    }
36
37    const ProcessCallbackType cb_;
38};
39
40template <typename ProcessCallbackType>
41static ProcessWalker<ProcessCallbackType> MakeProcessWalker(ProcessCallbackType cb) {
42    return ProcessWalker<ProcessCallbackType>(cb);
43}
44
45static void DumpProcessListKeyMap() {
46    printf("id  : process id number\n");
47    printf("#h  : total number of handles\n");
48    printf("#jb : number of job handles\n");
49    printf("#pr : number of process handles\n");
50    printf("#th : number of thread handles\n");
51    printf("#vo : number of vmo handles\n");
52    printf("#vm : number of virtual memory address region handles\n");
53    printf("#ch : number of channel handles\n");
54    printf("#ev : number of event and event pair handles\n");
55    printf("#po : number of port handles\n");
56    printf("#so: number of sockets\n");
57    printf("#tm : number of timers\n");
58    printf("#fi : number of fifos\n");
59    printf("#?? : number of all other handle types\n");
60}
61
62static const char* ObjectTypeToString(zx_obj_type_t type) {
63    static_assert(ZX_OBJ_TYPE_LAST == 28, "need to update switch below");
64
65    switch (type) {
66        case ZX_OBJ_TYPE_PROCESS: return "process";
67        case ZX_OBJ_TYPE_THREAD: return "thread";
68        case ZX_OBJ_TYPE_VMO: return "vmo";
69        case ZX_OBJ_TYPE_CHANNEL: return "channel";
70        case ZX_OBJ_TYPE_EVENT: return "event";
71        case ZX_OBJ_TYPE_PORT: return "port";
72        case ZX_OBJ_TYPE_INTERRUPT: return "interrupt";
73        case ZX_OBJ_TYPE_PCI_DEVICE: return "pci-device";
74        case ZX_OBJ_TYPE_LOG: return "log";
75        case ZX_OBJ_TYPE_SOCKET: return "socket";
76        case ZX_OBJ_TYPE_RESOURCE: return "resource";
77        case ZX_OBJ_TYPE_EVENTPAIR: return "event-pair";
78        case ZX_OBJ_TYPE_JOB: return "job";
79        case ZX_OBJ_TYPE_VMAR: return "vmar";
80        case ZX_OBJ_TYPE_FIFO: return "fifo";
81        case ZX_OBJ_TYPE_GUEST: return "guest";
82        case ZX_OBJ_TYPE_VCPU: return "vcpu";
83        case ZX_OBJ_TYPE_TIMER: return "timer";
84        case ZX_OBJ_TYPE_IOMMU: return "iommu";
85        case ZX_OBJ_TYPE_BTI: return "bti";
86        case ZX_OBJ_TYPE_PROFILE: return "profile";
87        case ZX_OBJ_TYPE_PMT: return "pmt";
88        case ZX_OBJ_TYPE_SUSPEND_TOKEN: return "suspend-token";
89        default: return "???";
90    }
91}
92
93// Returns the count of a process's handles. If |handle_type| is non-NULL,
94// it should point to |size| elements. For each handle, the corresponding
95// zx_obj_type_t-indexed element of |handle_type| is incremented.
96static uint32_t BuildHandleStats(const ProcessDispatcher& pd,
97                                 uint32_t* handle_type, size_t size) {
98    uint32_t total = 0;
99    pd.ForEachHandle([&](zx_handle_t handle, zx_rights_t rights,
100                         const Dispatcher* disp) {
101        if (handle_type) {
102            uint32_t type = static_cast<uint32_t>(disp->get_type());
103            if (size > type) {
104                ++handle_type[type];
105            }
106        }
107        ++total;
108        return ZX_OK;
109    });
110    return total;
111}
112
113// Counts the process's handles by type and formats them into the provided
114// buffer as strings.
115static void FormatHandleTypeCount(const ProcessDispatcher& pd,
116                                  char *buf, size_t buf_len) {
117    static_assert(ZX_OBJ_TYPE_LAST == 28, "need to update table below");
118
119    uint32_t types[ZX_OBJ_TYPE_LAST] = {0};
120    uint32_t handle_count = BuildHandleStats(pd, types, sizeof(types));
121
122    snprintf(buf, buf_len, "%4u: %4u %3u %3u %3u %3u %3u %3u %3u %3u %3u %3u %3u",
123             handle_count,
124             types[ZX_OBJ_TYPE_JOB],
125             types[ZX_OBJ_TYPE_PROCESS],
126             types[ZX_OBJ_TYPE_THREAD],
127             types[ZX_OBJ_TYPE_VMO],
128             types[ZX_OBJ_TYPE_VMAR],
129             types[ZX_OBJ_TYPE_CHANNEL],
130             types[ZX_OBJ_TYPE_EVENT] + types[ZX_OBJ_TYPE_EVENTPAIR],
131             types[ZX_OBJ_TYPE_PORT],
132             types[ZX_OBJ_TYPE_SOCKET],
133             types[ZX_OBJ_TYPE_TIMER],
134             types[ZX_OBJ_TYPE_FIFO],
135             types[ZX_OBJ_TYPE_INTERRUPT] + types[ZX_OBJ_TYPE_PCI_DEVICE] +
136             types[ZX_OBJ_TYPE_LOG] + types[ZX_OBJ_TYPE_RESOURCE] +
137             types[ZX_OBJ_TYPE_GUEST] + types[ZX_OBJ_TYPE_VCPU] +
138             types[ZX_OBJ_TYPE_IOMMU] + types[ZX_OBJ_TYPE_BTI] +
139             types[ZX_OBJ_TYPE_PROFILE] + types[ZX_OBJ_TYPE_PMT] +
140             types[ZX_OBJ_TYPE_SUSPEND_TOKEN]
141             );
142}
143
144void DumpProcessList() {
145    printf("%7s  #h:  #jb #pr #th #vo #vm #ch #ev #po #so #tm #fi #?? [name]\n", "id");
146
147    auto walker = MakeProcessWalker([](ProcessDispatcher* process) {
148        char handle_counts[(ZX_OBJ_TYPE_LAST * 4) + 1 + /*slop*/ 16];
149        FormatHandleTypeCount(*process, handle_counts, sizeof(handle_counts));
150
151        char pname[ZX_MAX_NAME_LEN];
152        process->get_name(pname);
153        printf("%7" PRIu64 "%s [%s]\n",
154               process->get_koid(),
155               handle_counts,
156               pname);
157    });
158    GetRootJobDispatcher()->EnumerateChildren(&walker, /* recurse */ true);
159}
160
161void DumpJobList() {
162    printf("All jobs:\n");
163    printf("%7s %s\n", "koid", "name");
164    JobDispatcher::ForEachJob([&](JobDispatcher* job) {
165        char name[ZX_MAX_NAME_LEN];
166        job->get_name(name);
167        printf("%7" PRIu64 " '%s'\n", job->get_koid(), name);
168        return ZX_OK;
169    });
170}
171
172void DumpProcessHandles(zx_koid_t id) {
173    auto pd = ProcessDispatcher::LookupProcessById(id);
174    if (!pd) {
175        printf("process %" PRIu64 " not found!\n", id);
176        return;
177    }
178
179    printf("process [%" PRIu64 "] handles :\n", id);
180    printf("handle       koid : type\n");
181
182    uint32_t total = 0;
183    pd->ForEachHandle([&](zx_handle_t handle, zx_rights_t rights,
184                          const Dispatcher* disp) {
185        printf("%9x %7" PRIu64 " : %s\n",
186            handle, disp->get_koid(), ObjectTypeToString(disp->get_type()));
187        ++total;
188        return ZX_OK;
189    });
190    printf("total: %u handles\n", total);
191}
192
193void ktrace_report_live_processes() {
194    auto walker = MakeProcessWalker([](ProcessDispatcher* process) {
195        char name[ZX_MAX_NAME_LEN];
196        process->get_name(name);
197        ktrace_name(TAG_PROC_NAME, (uint32_t)process->get_koid(), 0, name);
198    });
199    GetRootJobDispatcher()->EnumerateChildren(&walker, /* recurse */ true);
200}
201
202// Returns a string representation of VMO-related rights.
203static constexpr size_t kRightsStrLen = 8;
204static const char* VmoRightsToString(uint32_t rights, char str[kRightsStrLen]) {
205    char* c = str;
206    *c++ = (rights & ZX_RIGHT_READ) ? 'r' : '-';
207    *c++ = (rights & ZX_RIGHT_WRITE) ? 'w' : '-';
208    *c++ = (rights & ZX_RIGHT_EXECUTE) ? 'x' : '-';
209    *c++ = (rights & ZX_RIGHT_MAP) ? 'm' : '-';
210    *c++ = (rights & ZX_RIGHT_DUPLICATE) ? 'd' : '-';
211    *c++ = (rights & ZX_RIGHT_TRANSFER) ? 't' : '-';
212    *c = '\0';
213    return str;
214}
215
216// Prints a header for the columns printed by DumpVmObject.
217// If |handles| is true, the dumped objects are expected to have handle info.
218static void PrintVmoDumpHeader(bool handles) {
219    printf(
220        "%s koid parent #chld #map #shr    size   alloc name\n",
221        handles ? "      handle rights " : "           -      - ");
222}
223
224static void DumpVmObject(
225    const VmObject& vmo, char format_unit,
226    zx_handle_t handle, uint32_t rights, zx_koid_t koid) {
227
228    char handle_str[11];
229    if (handle != ZX_HANDLE_INVALID) {
230        snprintf(handle_str, sizeof(handle_str),
231                 "%u", static_cast<uint32_t>(handle));
232    } else {
233        handle_str[0] = '-';
234        handle_str[1] = '\0';
235    }
236
237    char rights_str[kRightsStrLen];
238    if (rights != 0) {
239        VmoRightsToString(rights, rights_str);
240    } else {
241        rights_str[0] = '-';
242        rights_str[1] = '\0';
243    }
244
245    char size_str[MAX_FORMAT_SIZE_LEN];
246    format_size_fixed(size_str, sizeof(size_str), vmo.size(), format_unit);
247
248    char alloc_str[MAX_FORMAT_SIZE_LEN];
249    if (vmo.is_paged()) {
250        format_size_fixed(alloc_str, sizeof(alloc_str),
251                          vmo.AllocatedPages() * PAGE_SIZE, format_unit);
252    } else {
253        strlcpy(alloc_str, "phys", sizeof(alloc_str));
254    }
255
256    char clone_str[21];
257    if (vmo.is_cow_clone()) {
258        snprintf(clone_str, sizeof(clone_str),
259                 "%" PRIu64, vmo.parent_user_id());
260    } else {
261        clone_str[0] = '-';
262        clone_str[1] = '\0';
263    }
264
265    char name[ZX_MAX_NAME_LEN];
266    vmo.get_name(name, sizeof(name));
267    if (name[0] == '\0') {
268        name[0] = '-';
269        name[1] = '\0';
270    }
271
272    printf("  %10s " // handle
273           "%6s " // rights
274           "%5" PRIu64 " " // koid
275           "%6s " // clone parent koid
276           "%5" PRIu32 " " // number of children
277           "%4" PRIu32 " " // map count
278           "%4" PRIu32 " " // share count
279           "%7s " // size in bytes
280           "%7s " // allocated bytes
281           "%s\n", // name
282           handle_str,
283           rights_str,
284           koid,
285           clone_str,
286           vmo.num_children(),
287           vmo.num_mappings(),
288           vmo.share_count(),
289           size_str,
290           alloc_str,
291           name);
292}
293
294// If |hidden_only| is set, will only dump VMOs that are not mapped
295// into any process:
296// - VMOs that userspace has handles to but does not map
297// - VMOs that are mapped only into kernel space
298// - Kernel-only, unmapped VMOs that have no handles
299static void DumpAllVmObjects(bool hidden_only, char format_unit) {
300    if (hidden_only) {
301        printf("\"Hidden\" VMOs, oldest to newest:\n");
302    } else {
303        printf("All VMOs, oldest to newest:\n");
304    }
305    PrintVmoDumpHeader(/* handles */ false);
306    VmObject::ForEach([=](const VmObject& vmo) {
307        if (hidden_only && vmo.IsMappedByUser()) {
308            return ZX_OK;
309        }
310        DumpVmObject(
311            vmo,
312            format_unit,
313            ZX_HANDLE_INVALID,
314            /* rights */ 0u,
315            /* koid */ vmo.user_id());
316        // TODO(dbort): Dump the VmAspaces (processes) that map the VMO.
317        // TODO(dbort): Dump the processes that hold handles to the VMO.
318        //     This will be a lot harder to gather.
319        return ZX_OK;
320    });
321    PrintVmoDumpHeader(/* handles */ false);
322}
323
324namespace {
325// Dumps VMOs under a VmAspace.
326class AspaceVmoDumper final : public VmEnumerator {
327public:
328    AspaceVmoDumper(char format_unit) : format_unit_(format_unit) {}
329    bool OnVmMapping(const VmMapping* map, const VmAddressRegion* vmar,
330                     uint depth) final {
331        auto vmo = map->vmo();
332        DumpVmObject(
333            *vmo,
334            format_unit_,
335            ZX_HANDLE_INVALID,
336            /* rights */ 0u,
337            /* koid */ vmo->user_id());
338        return true;
339    }
340private:
341    const char format_unit_;
342};
343} // namespace
344
345// Dumps all VMOs associated with a process.
346static void DumpProcessVmObjects(zx_koid_t id, char format_unit) {
347    auto pd = ProcessDispatcher::LookupProcessById(id);
348    if (!pd) {
349        printf("process not found!\n");
350        return;
351    }
352
353    printf("process [%" PRIu64 "]:\n", id);
354    printf("Handles to VMOs:\n");
355    PrintVmoDumpHeader(/* handles */ true);
356    int count = 0;
357    uint64_t total_size = 0;
358    uint64_t total_alloc = 0;
359    pd->ForEachHandle([&](zx_handle_t handle, zx_rights_t rights,
360                          const Dispatcher* disp) {
361        auto vmod = DownCastDispatcher<const VmObjectDispatcher>(disp);
362        if (vmod == nullptr) {
363            return ZX_OK;
364        }
365        auto vmo = vmod->vmo();
366        DumpVmObject(*vmo, format_unit, handle, rights, vmod->get_koid());
367
368        // TODO: Doesn't handle the case where a process has multiple
369        // handles to the same VMO; will double-count all of these totals.
370        count++;
371        total_size += vmo->size();
372        // TODO: Doing this twice (here and in DumpVmObject) is a waste of
373        // work, and can get out of sync.
374        total_alloc += vmo->AllocatedPages() * PAGE_SIZE;
375        return ZX_OK;
376    });
377    char size_str[MAX_FORMAT_SIZE_LEN];
378    char alloc_str[MAX_FORMAT_SIZE_LEN];
379    printf("  total: %d VMOs, size %s, alloc %s\n",
380           count,
381           format_size_fixed(size_str, sizeof(size_str),
382                             total_size, format_unit),
383           format_size_fixed(alloc_str, sizeof(alloc_str),
384                             total_alloc, format_unit));
385
386    // Call DumpVmObject() on all VMOs under the process's VmAspace.
387    printf("Mapped VMOs:\n");
388    PrintVmoDumpHeader(/* handles */ false);
389    AspaceVmoDumper avd(format_unit);
390    pd->aspace()->EnumerateChildren(&avd);
391    PrintVmoDumpHeader(/* handles */ false);
392}
393
394void KillProcess(zx_koid_t id) {
395    // search the process list and send a kill if found
396    auto pd = ProcessDispatcher::LookupProcessById(id);
397    if (!pd) {
398        printf("process not found!\n");
399        return;
400    }
401    // if found, outside of the lock hit it with kill
402    printf("killing process %" PRIu64 "\n", id);
403    pd->Kill();
404}
405
406namespace {
407// Counts memory usage under a VmAspace.
408class VmCounter final : public VmEnumerator {
409public:
410    bool OnVmMapping(const VmMapping* map, const VmAddressRegion* vmar,
411                     uint depth) override {
412        usage.mapped_pages += map->size() / PAGE_SIZE;
413
414        size_t committed_pages = map->vmo()->AllocatedPagesInRange(
415            map->object_offset(), map->size());
416        uint32_t share_count = map->vmo()->share_count();
417        if (share_count == 1) {
418            usage.private_pages += committed_pages;
419        } else {
420            usage.shared_pages += committed_pages;
421            usage.scaled_shared_bytes +=
422                committed_pages * PAGE_SIZE / share_count;
423        }
424        return true;
425    }
426
427    VmAspace::vm_usage_t usage = {};
428};
429} // namespace
430
431zx_status_t VmAspace::GetMemoryUsage(vm_usage_t* usage) {
432    VmCounter vc;
433    if (!EnumerateChildren(&vc)) {
434        *usage = {};
435        return ZX_ERR_INTERNAL;
436    }
437    *usage = vc.usage;
438    return ZX_OK;
439}
440
441namespace {
442unsigned int arch_mmu_flags_to_vm_flags(unsigned int arch_mmu_flags) {
443    if (arch_mmu_flags & ARCH_MMU_FLAG_INVALID) {
444        return 0;
445    }
446    unsigned int ret = 0;
447    if (arch_mmu_flags & ARCH_MMU_FLAG_PERM_READ) {
448        ret |= ZX_VM_PERM_READ;
449    }
450    if (arch_mmu_flags & ARCH_MMU_FLAG_PERM_WRITE) {
451        ret |= ZX_VM_PERM_WRITE;
452    }
453    if (arch_mmu_flags & ARCH_MMU_FLAG_PERM_EXECUTE) {
454        ret |= ZX_VM_PERM_EXECUTE;
455    }
456    return ret;
457}
458
459// Builds a description of an apsace/vmar/mapping hierarchy.
460class VmMapBuilder final : public VmEnumerator {
461public:
462    // NOTE: Code outside of the syscall layer should not typically know about
463    // user_ptrs; do not use this pattern as an example.
464    VmMapBuilder(user_out_ptr<zx_info_maps_t> maps, size_t max)
465        : maps_(maps), max_(max) {}
466
467    bool OnVmAddressRegion(const VmAddressRegion* vmar, uint depth) override {
468        available_++;
469        if (nelem_ < max_) {
470            zx_info_maps_t entry = {};
471            strlcpy(entry.name, vmar->name(), sizeof(entry.name));
472            entry.base = vmar->base();
473            entry.size = vmar->size();
474            entry.depth = depth + 1; // The root aspace is depth 0.
475            entry.type = ZX_INFO_MAPS_TYPE_VMAR;
476            if (maps_.copy_array_to_user(&entry, 1, nelem_) != ZX_OK) {
477                return false;
478            }
479            nelem_++;
480        }
481        return true;
482    }
483
484    bool OnVmMapping(const VmMapping* map, const VmAddressRegion* vmar,
485                     uint depth) override {
486        available_++;
487        if (nelem_ < max_) {
488            zx_info_maps_t entry = {};
489            auto vmo = map->vmo();
490            vmo->get_name(entry.name, sizeof(entry.name));
491            entry.base = map->base();
492            entry.size = map->size();
493            entry.depth = depth + 1; // The root aspace is depth 0.
494            entry.type = ZX_INFO_MAPS_TYPE_MAPPING;
495            zx_info_maps_mapping_t* u = &entry.u.mapping;
496            u->mmu_flags =
497                arch_mmu_flags_to_vm_flags(map->arch_mmu_flags());
498            u->vmo_koid = vmo->user_id();
499            u->committed_pages = vmo->AllocatedPagesInRange(
500                map->object_offset(), map->size());
501            if (maps_.copy_array_to_user(&entry, 1, nelem_) != ZX_OK) {
502                return false;
503            }
504            nelem_++;
505        }
506        return true;
507    }
508
509    size_t nelem() const { return nelem_; }
510    size_t available() const { return available_; }
511
512private:
513    // The caller must write an entry for the root VmAspace at index 0.
514    size_t nelem_ = 1;
515    size_t available_ = 1;
516    user_out_ptr<zx_info_maps_t> maps_;
517    size_t max_;
518};
519} // namespace
520
521// NOTE: Code outside of the syscall layer should not typically know about
522// user_ptrs; do not use this pattern as an example.
523zx_status_t GetVmAspaceMaps(fbl::RefPtr<VmAspace> aspace,
524                            user_out_ptr<zx_info_maps_t> maps, size_t max,
525                            size_t* actual, size_t* available) {
526    DEBUG_ASSERT(aspace != nullptr);
527    *actual = 0;
528    *available = 0;
529    if (aspace->is_destroyed()) {
530        return ZX_ERR_BAD_STATE;
531    }
532    if (max > 0) {
533        zx_info_maps_t entry = {};
534        strlcpy(entry.name, aspace->name(), sizeof(entry.name));
535        entry.base = aspace->base();
536        entry.size = aspace->size();
537        entry.depth = 0;
538        entry.type = ZX_INFO_MAPS_TYPE_ASPACE;
539        if (maps.copy_array_to_user(&entry, 1, 0) != ZX_OK) {
540            return ZX_ERR_INVALID_ARGS;
541        }
542    }
543
544    VmMapBuilder b(maps, max);
545    if (!aspace->EnumerateChildren(&b)) {
546        // VmMapBuilder only returns false
547        // when it can't copy to the user pointer.
548        return ZX_ERR_INVALID_ARGS;
549    }
550    *actual = max > 0 ? b.nelem() : 0;
551    *available = b.available();
552    return ZX_OK;
553}
554
555namespace {
556// Builds a list of all VMOs mapped into a VmAspace.
557class AspaceVmoEnumerator final : public VmEnumerator {
558public:
559    // NOTE: Code outside of the syscall layer should not typically know about
560    // user_ptrs; do not use this pattern as an example.
561    AspaceVmoEnumerator(user_out_ptr<zx_info_vmo_t> vmos, size_t max)
562        : vmos_(vmos), max_(max) {}
563
564    bool OnVmMapping(const VmMapping* map, const VmAddressRegion* vmar,
565                     uint depth) override {
566        available_++;
567        if (nelem_ < max_) {
568            // We're likely to see the same VMO a couple times in a given
569            // address space (e.g., somelib.so mapped as r--, r-x), but leave it
570            // to userspace to do deduping.
571            zx_info_vmo_t entry = VmoToInfoEntry(map->vmo().get(),
572                                                 /*is_handle=*/false,
573                                                 /*handle_rights=*/0);
574            if (vmos_.copy_array_to_user(&entry, 1, nelem_) != ZX_OK) {
575                return false;
576            }
577            nelem_++;
578        }
579        return true;
580    }
581
582    size_t nelem() const { return nelem_; }
583    size_t available() const { return available_; }
584
585private:
586    const user_out_ptr<zx_info_vmo_t> vmos_;
587    const size_t max_;
588
589    size_t nelem_ = 0;
590    size_t available_ = 0;
591};
592} // namespace
593
594// NOTE: Code outside of the syscall layer should not typically know about
595// user_ptrs; do not use this pattern as an example.
596zx_status_t GetVmAspaceVmos(fbl::RefPtr<VmAspace> aspace,
597                            user_out_ptr<zx_info_vmo_t> vmos, size_t max,
598                            size_t* actual, size_t* available) {
599    DEBUG_ASSERT(aspace != nullptr);
600    DEBUG_ASSERT(actual != nullptr);
601    DEBUG_ASSERT(available != nullptr);
602    *actual = 0;
603    *available = 0;
604    if (aspace->is_destroyed()) {
605        return ZX_ERR_BAD_STATE;
606    }
607
608    AspaceVmoEnumerator ave(vmos, max);
609    if (!aspace->EnumerateChildren(&ave)) {
610        // AspaceVmoEnumerator only returns false
611        // when it can't copy to the user pointer.
612        return ZX_ERR_INVALID_ARGS;
613    }
614    *actual = ave.nelem();
615    *available = ave.available();
616    return ZX_OK;
617}
618
619// NOTE: Code outside of the syscall layer should not typically know about
620// user_ptrs; do not use this pattern as an example.
621zx_status_t GetProcessVmosViaHandles(ProcessDispatcher* process,
622                                     user_out_ptr<zx_info_vmo_t> vmos, size_t max,
623                                     size_t* actual_out, size_t* available_out) {
624    DEBUG_ASSERT(process != nullptr);
625    DEBUG_ASSERT(actual_out != nullptr);
626    DEBUG_ASSERT(available_out != nullptr);
627    size_t actual = 0;
628    size_t available = 0;
629    // We may see multiple handles to the same VMO, but leave it to userspace to
630    // do deduping.
631    zx_status_t s = process->ForEachHandle([&](zx_handle_t handle,
632                                               zx_rights_t rights,
633                                               const Dispatcher* disp) {
634        auto vmod = DownCastDispatcher<const VmObjectDispatcher>(disp);
635        if (vmod == nullptr) {
636            // This handle isn't a VMO; skip it.
637            return ZX_OK;
638        }
639        available++;
640        if (actual < max) {
641            zx_info_vmo_t entry = VmoToInfoEntry(vmod->vmo().get(),
642                                                 /*is_handle=*/true,
643                                                 rights);
644            if (vmos.copy_array_to_user(&entry, 1, actual) != ZX_OK) {
645                return ZX_ERR_INVALID_ARGS;
646            }
647            actual++;
648        }
649        return ZX_OK;
650    });
651    if (s != ZX_OK) {
652        return s;
653    }
654    *actual_out = actual;
655    *available_out = available;
656    return ZX_OK;
657}
658
659void DumpProcessAddressSpace(zx_koid_t id) {
660    auto pd = ProcessDispatcher::LookupProcessById(id);
661    if (!pd) {
662        printf("process %" PRIu64 " not found!\n", id);
663        return;
664    }
665
666    pd->aspace()->Dump(true);
667}
668
669// Dumps an address space based on the arg.
670static void DumpAddressSpace(const cmd_args* arg) {
671    if (strncmp(arg->str, "kernel", strlen(arg->str)) == 0) {
672        // The arg is a prefix of "kernel".
673        VmAspace::kernel_aspace()->Dump(true);
674    } else {
675        DumpProcessAddressSpace(arg->u);
676    }
677}
678
679static void DumpHandleTable() {
680    printf("outstanding handles: %zu\n",
681           Handle::diagnostics::OutstandingHandles());
682    Handle::diagnostics::DumpTableInfo();
683}
684
685static size_t mwd_limit = 32 * 256;
686static bool mwd_running;
687
688static size_t hwd_limit = 1024;
689static bool hwd_running;
690
691static int hwd_thread(void* arg) {
692    static size_t previous_handle_count = 0u;
693
694    for (;;) {
695        auto handle_count = Handle::diagnostics::OutstandingHandles();
696        if (handle_count != previous_handle_count) {
697            if (handle_count > hwd_limit) {
698                printf("HandleWatchdog! %zu handles outstanding (greater than limit %zu)\n",
699                       handle_count, hwd_limit);
700            } else if (previous_handle_count > hwd_limit) {
701                printf("HandleWatchdog! %zu handles outstanding (dropping below limit %zu)\n",
702                       handle_count, hwd_limit);
703            }
704        }
705
706        previous_handle_count = handle_count;
707
708        thread_sleep_relative(ZX_SEC(1));
709    }
710}
711
712void DumpProcessMemoryUsage(const char* prefix, size_t min_pages) {
713    auto walker = MakeProcessWalker([&](ProcessDispatcher* process) {
714        size_t pages = process->PageCount();
715        if (pages >= min_pages) {
716            char pname[ZX_MAX_NAME_LEN];
717            process->get_name(pname);
718            printf("%sproc %5" PRIu64 " %4zuM '%s'\n",
719                   prefix, process->get_koid(), pages / 256, pname);
720        }
721    });
722    GetRootJobDispatcher()->EnumerateChildren(&walker, /* recurse */ true);
723}
724
725static int mwd_thread(void* arg) {
726    for (;;) {
727        thread_sleep_relative(ZX_SEC(1));
728        DumpProcessMemoryUsage("MemoryHog! ", mwd_limit);
729    }
730}
731
732static int cmd_diagnostics(int argc, const cmd_args* argv, uint32_t flags) {
733    int rc = 0;
734
735    if (argc < 2) {
736        printf("not enough arguments:\n");
737    usage:
738        printf("%s ps                : list processes\n", argv[0].str);
739        printf("%s jobs              : list jobs\n", argv[0].str);
740        printf("%s mwd  <mb>         : memory watchdog\n", argv[0].str);
741        printf("%s ht   <pid>        : dump process handles\n", argv[0].str);
742        printf("%s hwd  <count>      : handle watchdog\n", argv[0].str);
743        printf("%s vmos <pid>|all|hidden [-u?]\n", argv[0].str);
744        printf("                     : dump process/all/hidden VMOs\n");
745        printf("                 -u? : fix all sizes to the named unit\n");
746        printf("                       where ? is one of [BkMGTPE]\n");
747        printf("%s kill <pid>        : kill process\n", argv[0].str);
748        printf("%s asd  <pid>|kernel : dump process/kernel address space\n",
749               argv[0].str);
750        printf("%s htinfo            : handle table info\n", argv[0].str);
751        return -1;
752    }
753
754    if (strcmp(argv[1].str, "mwd") == 0) {
755        if (argc == 3) {
756            mwd_limit = argv[2].u * 256;
757        }
758        if (!mwd_running) {
759            thread_t* t = thread_create("mwd", mwd_thread, nullptr, DEFAULT_PRIORITY);
760            if (t) {
761                mwd_running = true;
762                thread_resume(t);
763            }
764        }
765    } else if (strcmp(argv[1].str, "ps") == 0) {
766        if ((argc == 3) && (strcmp(argv[2].str, "help") == 0)) {
767            DumpProcessListKeyMap();
768        } else {
769            DumpProcessList();
770        }
771    } else if (strcmp(argv[1].str, "jobs") == 0) {
772        DumpJobList();
773    } else if (strcmp(argv[1].str, "hwd") == 0) {
774        if (argc == 3) {
775            hwd_limit = argv[2].u;
776        }
777        if (!hwd_running) {
778            thread_t* t = thread_create("hwd", hwd_thread, nullptr, DEFAULT_PRIORITY);
779            if (t) {
780                hwd_running = true;
781                thread_resume(t);
782            }
783        }
784    } else if (strcmp(argv[1].str, "ht") == 0) {
785        if (argc < 3)
786            goto usage;
787        DumpProcessHandles(argv[2].u);
788    } else if (strcmp(argv[1].str, "vmos") == 0) {
789        if (argc < 3)
790            goto usage;
791        char format_unit = 0;
792        if (argc >= 4) {
793            if (!strncmp(argv[3].str, "-u", sizeof("-u") - 1)) {
794                format_unit = argv[3].str[sizeof("-u") - 1];
795            } else {
796                printf("dunno '%s'\n", argv[3].str);
797                goto usage;
798            }
799        }
800        if (strcmp(argv[2].str, "all") == 0) {
801            DumpAllVmObjects(/*hidden_only=*/false, format_unit);
802        } else if (strcmp(argv[2].str, "hidden") == 0) {
803            DumpAllVmObjects(/*hidden_only=*/true, format_unit);
804        } else {
805            DumpProcessVmObjects(argv[2].u, format_unit);
806        }
807    } else if (strcmp(argv[1].str, "kill") == 0) {
808        if (argc < 3)
809            goto usage;
810        KillProcess(argv[2].u);
811    } else if (strcmp(argv[1].str, "asd") == 0) {
812        if (argc < 3)
813            goto usage;
814        DumpAddressSpace(&argv[2]);
815    } else if (strcmp(argv[1].str, "htinfo") == 0) {
816        if (argc != 2)
817            goto usage;
818        DumpHandleTable();
819    } else {
820        printf("unrecognized subcommand '%s'\n", argv[1].str);
821        goto usage;
822    }
823    return rc;
824}
825
826STATIC_COMMAND_START
827STATIC_COMMAND("zx", "kernel object diagnostics", &cmd_diagnostics)
828STATIC_COMMAND_END(zx);
829