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