blob: 274d6ea4f7b59ce380dedb73ac969a27b92da5a4 [file] [edit]
// Copyright 2026 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "memgraph_internal.h"
#include <inttypes.h>
#include <stdio.h>
#include <stdlib.h>
#include <zircon/status.h>
#include <zircon/syscalls.h>
#include <zircon/syscalls/exception.h>
#include <zircon/syscalls/object.h>
#include <ostream>
#include <print>
#include <vector>
#include "object-utils.h"
#include "vmo-utils.h"
namespace {
// Prints info about VMOs and their relationship to a process.
// Assumes we're in the middle of dumping a process.
// TODO(https://fxbug.dev/575725855): Insert some special entry if count < avail?
void print_vmos(std::ostream& out, const zx_info_vmo_t* vmos, size_t count) {
if (count == 0) {
// Should never happen, but don't print anything in this case.
return;
}
// List of VMOs that this task points to. Should only contain fields that
// are fundamental parts of the VMO and do not change based on how the VMO
// is used or referred to.
std::println(out, ",\n \"vmos\": [");
for (size_t i = 0; i < count; i++) {
const zx_info_vmo_t* vmo = vmos + i;
char delim = i < (count - 1) ? ',' : ' ';
std::println(out,
" {{"
"\"koid\": {}, "
"\"name\": \"{}\", " // TODO(https://fxbug.dev/575725855): Escape quotes
"\"size_bytes\": {}, "
"\"parent_koid\": {}, "
"\"num_children\": {}, "
"\"num_mappings\": {}, "
"\"share_count\": {}, "
// TODO(https://fxbug.dev/575725855): copy-on-write? phys/paged?
"\"committed_bytes\": {}}}{}",
vmo->koid, static_cast<const char*>(vmo->name), vmo->size_bytes, vmo->parent_koid,
vmo->num_children, vmo->num_mappings, vmo->share_count, vmo->committed_bytes,
delim);
}
std::println(out, " ],");
// List of references from this task to the VMOs listed above. May include
// information specific to this particular use of a given VMO.
std::println(out, " \"vmo_refs\": [");
for (size_t i = 0; i < count; i++) {
const zx_info_vmo_t* vmo = vmos + i;
char delim = i < (count - 1) ? ',' : ' ';
std::print(out,
" {{"
"\"vmo_koid\": {}, "
"\"via\": [",
vmo->koid);
bool need_comma = false;
if (vmo->flags & ZX_INFO_VMO_VIA_HANDLE) {
std::print(out, "\"HANDLE\"");
need_comma = true;
}
if (vmo->flags & ZX_INFO_VMO_VIA_MAPPING) {
std::print(out, "{}\"MAPPING\"", need_comma ? ", " : "");
// Future improvement: Could use ZX_INFO_PROCESS_MAPS to include
// specifics of how this VMO is mapped.
}
std::print(out, "]");
if (vmo->flags & ZX_INFO_VMO_VIA_HANDLE) {
need_comma = false;
std::print(out, ", \"handle_rights\": [");
struct RightEntry {
zx_rights_t right;
const char* name;
};
constexpr RightEntry kRights[] = {
{.right = ZX_RIGHT_READ, .name = "READ"},
{.right = ZX_RIGHT_WRITE, .name = "WRITE"},
{.right = ZX_RIGHT_EXECUTE, .name = "EXECUTE"},
{.right = ZX_RIGHT_MAP, .name = "MAP"},
{.right = ZX_RIGHT_DUPLICATE, .name = "DUPLICATE"},
{.right = ZX_RIGHT_TRANSFER, .name = "TRANSFER"},
};
for (const auto& entry : kRights) {
if (vmo->handle_rights & entry.right) {
std::print(out, "{}\"{}\"", need_comma ? ", " : "", entry.name);
need_comma = true;
}
}
std::print(out, "]");
}
std::println(out, "}}{}", delim);
}
std::print(out, " ]");
}
void GetTaskName(zx_handle_t task, zx_koid_t koid, char out_name[ZX_MAX_NAME_LEN]) {
zx_status_t s = zx_object_get_property(task, ZX_PROP_NAME, out_name, ZX_MAX_NAME_LEN);
if (s != ZX_OK) {
std::println(stderr, "WARNING: failed to get name of task {}: {} ({})", koid,
zx_status_get_string(s), s);
snprintf(out_name, ZX_MAX_NAME_LEN, "<UNKNOWN>");
// This is unfortunate, but not worth a partial failure
// since the overall structure of the output is still intact.
}
// TODO(https://fxbug.dev/575725855): Escape quotes in name
}
} // namespace
JsonTaskEnumerator::JsonTaskEnumerator(zx_koid_t self_koid, bool show_threads, bool show_vmos,
bool show_handle_stats, std::ostream& out)
: self_koid_(self_koid),
show_threads_(show_threads),
show_vmos_(show_vmos),
show_handle_stats_(show_handle_stats),
out_(out) {}
const std::vector<zx_status_t>& JsonTaskEnumerator::partial_failures() const {
return partial_failures_;
}
bool JsonTaskEnumerator::has_on_job() const { return true; }
bool JsonTaskEnumerator::has_on_process() const { return true; }
bool JsonTaskEnumerator::has_on_thread() const { return show_threads_; }
zx_status_t JsonTaskEnumerator::OnJob(int depth, zx_handle_t job, zx_koid_t koid,
zx_koid_t parent_koid) {
char name[ZX_MAX_NAME_LEN];
GetTaskName(job, koid, name);
char parent_id[ZX_MAX_NAME_LEN + 16];
if (parent_koid == 0) {
// This is the root job, which we treat as a child of the
// system VMO arena node.
snprintf(parent_id, sizeof(parent_id), "kernel/vmo");
} else {
snprintf(parent_id, sizeof(parent_id), "j/%" PRIu64, parent_koid);
}
std::println(out_,
" {{"
"\"id\": \"j/{}\", "
"\"type\": \"j\", "
"\"koid\": {}, "
"\"parent\": \"{}\", "
"\"name\": \"{}\""
"}},",
koid, koid, static_cast<const char*>(parent_id), static_cast<const char*>(name));
return ZX_OK;
}
zx_status_t JsonTaskEnumerator::OnProcess(int depth, zx_handle_t process, zx_koid_t koid,
zx_koid_t parent_koid) {
char name[ZX_MAX_NAME_LEN];
GetTaskName(process, koid, name);
// Print basic info.
std::print(out_,
" {{"
"\"id\": \"p/{}\", "
"\"type\": \"p\", "
"\"koid\": {}, "
"\"parent\": \"j/{}\", "
"\"name\": \"{}\"",
koid, koid, parent_koid, static_cast<const char*>(name));
// Print memory usage summaries.
zx_info_task_stats_t info;
zx_status_t s =
zx_object_get_info(process, ZX_INFO_TASK_STATS, &info, sizeof(info), nullptr, nullptr);
if (s == ZX_ERR_BAD_STATE) {
// Process has exited, but has not been destroyed.
// Default to zero for all sizes.
info = {};
s = ZX_OK;
}
if (s != ZX_OK) {
std::println(stderr, "WARNING: failed to get mem stats for process {}: {} ({})", koid,
zx_status_get_string(s), s);
partial_failures_.push_back(s);
} else {
std::print(out_,
", "
"\"private_bytes\": {}, "
"\"shared_bytes\": {}, "
"\"pss_bytes\": {}",
info.mem_private_bytes, info.mem_shared_bytes,
info.mem_private_bytes + info.mem_scaled_shared_bytes);
}
// Print the process's VMOs. The same VMO may appear several
// times in this list; it's up to the consumer of this output
// to de-duplicate.
if (show_vmos_ && koid != self_koid_) {
zx_info_vmo_t* vmos;
size_t count = 0;
size_t avail = 0;
s = get_vmos(process, &vmos, &count, &avail);
if (s == ZX_ERR_BAD_STATE) {
// Process has exited, but has not been destroyed.
} else if (s != ZX_OK) {
std::println(stderr, "WARNING: failed to read VMOs for process {}: {} ({})", koid,
zx_status_get_string(s), s);
partial_failures_.push_back(s);
} else {
if (count < avail) {
std::println(stderr, "WARNING: failed to read all VMOs for process {}: count {} < avail {}",
koid, count, avail);
partial_failures_.push_back(ZX_ERR_BUFFER_TOO_SMALL);
// Keep going with the truncated list.
}
print_vmos(out_, vmos, count);
free(vmos);
}
}
if (show_handle_stats_) {
zx_info_process_handle_stats_t info = {};
s = zx_object_get_info(process, ZX_INFO_PROCESS_HANDLE_STATS, &info, sizeof(info), nullptr,
nullptr);
if (s != ZX_OK) {
std::println(stderr, "WARNING: failed to read handle stats for process {}: {} ({})", koid,
zx_status_get_string(s), s);
partial_failures_.push_back(s);
} else {
std::print(out_, ",\n \"handle_stats\": {{");
size_t count = 0;
for (zx_obj_type_t i = 0; i < ZX_OBJ_TYPE_UPPER_BOUND; i++) {
if (!info.handle_count[i]) {
continue;
}
if (count++ > 0) {
std::print(out_, ",");
}
std::print(out_, "\n \"{}\": {}", obj_type_get_name(i), info.handle_count[i]);
}
std::print(out_, "\n }}");
}
}
std::println(out_, "}},");
return ZX_OK;
}
zx_status_t JsonTaskEnumerator::OnThread(int depth, zx_handle_t thread, zx_koid_t koid,
zx_koid_t parent_koid) {
char name[ZX_MAX_NAME_LEN];
GetTaskName(thread, koid, name);
// Print basic info.
std::print(out_,
" {{"
"\"id\": \"t/{}\", "
"\"type\": \"t\", "
"\"koid\": {}, "
"\"parent\": \"p/{}\", "
"\"name\": \"{}\"",
koid, koid, parent_koid, static_cast<const char*>(name));
// Print state.
zx_info_thread_t info;
zx_status_t s = zx_object_get_info(thread, ZX_INFO_THREAD, &info, sizeof(info), nullptr, nullptr);
if (s != ZX_OK) {
std::println(stderr, "WARNING: failed to get info for thread {}: {} ({})", koid,
zx_status_get_string(s), s);
partial_failures_.push_back(s);
} else {
const char* state = "<UNKNOWN>";
if (info.state == ZX_THREAD_STATE_BLOCKED_EXCEPTION &&
info.wait_exception_channel_type != ZX_EXCEPTION_CHANNEL_TYPE_NONE) {
state = "EXCEPTION";
} else {
switch (ZX_THREAD_STATE_BASIC(info.state)) {
case ZX_THREAD_STATE_NEW:
state = "NEW";
break;
case ZX_THREAD_STATE_RUNNING:
state = "RUNNING";
break;
case ZX_THREAD_STATE_SUSPENDED:
state = "SUSPENDED";
break;
case ZX_THREAD_STATE_BLOCKED:
state = "BLOCKED";
break;
case ZX_THREAD_STATE_DYING:
state = "DYING";
break;
case ZX_THREAD_STATE_DEAD:
state = "DEAD";
break;
default:
break;
}
}
std::print(out_, ", \"state\": \"{}\"", state);
}
std::println(out_, "}},");
return ZX_OK;
}