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