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