blob: 2c0daad697fb1e7a8214e5ec04adf62d1541ef35 [file] [log] [blame]
// Copyright 2019 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 "src/developer/debug/zxdb/console/commands/verb_aspace.h"
#include "src/developer/debug/zxdb/client/process.h"
#include "src/developer/debug/zxdb/client/session.h"
#include "src/developer/debug/zxdb/client/target.h"
#include "src/developer/debug/zxdb/common/string_util.h"
#include "src/developer/debug/zxdb/console/command.h"
#include "src/developer/debug/zxdb/console/command_utils.h"
#include "src/developer/debug/zxdb/console/console.h"
#include "src/developer/debug/zxdb/console/format_table.h"
#include "src/developer/debug/zxdb/console/output_buffer.h"
#include "src/developer/debug/zxdb/console/verbs.h"
#include "src/developer/debug/zxdb/expr/number_parser.h"
#include "src/lib/fxl/strings/string_printf.h"
namespace zxdb {
namespace {
constexpr int kAddressLimitSwitch = 1;
const char kAspaceShortHelp[] = "aspace / as: Show address space for a process.";
const char kAspaceUsage[] = "aspace [ --limit <address> ] [ <address> ]";
const char kAspaceHelp[] = R"(
Alias: "as"
Shows the address space map for the given process.
With no parameters, it shows the entire process address map.
You can pass a single address and it will show all the regions that
contain it.
In addition to the address range, the output shows the koid of the VMO mapped
to that location, the starting offset it was mapped at, and the number of
committed pages in that region.
Tip: To see more information about a VMO, use "handle -k <koid>".
Arguments
--limit <address>
Only reports mappings at addresses below <address>. This can be useful
when a process has very many mappings that may not fit in a single
terminal buffer.
Committed pages
The "Cmt.Pgs" column shows the number of committed pages (not bytes) in that
memory region in the mapped VMO. This can be surprising for memory mapped
files like blobs and other shared VMOs.
If a VMO is a child (as in the case of mapped blobs), the original data will
be present in the parent VMO but the child VMO that is actually mapped will
indirectly reference this data. The only pages in the child that will count as
committed are those that are duplicated due to copy-on-write. This is why
blobs and other files that are not modified will have a 0 committed page
count.
Examples
aspace
aspace --limit 0x100000000
aspace 0x530b010dc000
process 2 aspace
)";
std::string PrintRegionSize(uint64_t size) {
const uint64_t kOneK = 1024u;
const uint64_t kOneM = kOneK * kOneK;
const uint64_t kOneG = kOneM * kOneK;
const uint64_t kOneT = kOneG * kOneK;
if (size < kOneK)
return fxl::StringPrintf("%" PRIu64 "B", size);
if (size < kOneM)
return fxl::StringPrintf("%" PRIu64 "K", size / kOneK);
if (size < kOneG)
return fxl::StringPrintf("%" PRIu64 "M", size / kOneM);
if (size < kOneT)
return fxl::StringPrintf("%" PRIu64 "G", size / kOneG);
return fxl::StringPrintf("%" PRIu64 "T", size / kOneT);
}
std::string PrintRegionName(uint64_t depth, const std::string& name) {
return std::string(depth * 2, ' ') + name;
}
// Set has_shared if the platform supports this flag.
std::string PrintRegionMmuFlags(const debug_ipc::AddressRegion& region, bool has_shared) {
std::string str;
str += region.read ? 'r' : '-';
str += region.write ? 'w' : '-';
str += region.execute ? 'x' : '-';
if (has_shared) {
str += region.shared ? 's' : 'p';
}
return str;
}
void OnAspaceComplete(fxl::RefPtr<CommandContext> cmd_context, const Err& err,
std::vector<debug_ipc::AddressRegion> map, bool print_totals,
std::optional<uint64_t> addr_limit) {
Console* console = Console::get();
if (err.has_error()) {
console->Output(err);
return;
}
if (map.empty()) {
console->Output("Region not mapped.");
return;
}
uint64_t total_mapped = 0;
uint64_t total_committed = 0;
const uint64_t page_size = console->context().session()->arch_info().page_size();
debug::Platform platform = cmd_context->GetConsoleContext()->session()->platform();
// Only Linux has the shared bit.
bool has_shared = platform == debug::Platform::kLinux;
std::vector<std::vector<std::string>> rows;
for (const auto& region : map) {
std::vector<std::string> row;
if (addr_limit && region.base > addr_limit) break;
row.push_back(to_hex_string(region.base));
row.push_back(to_hex_string(region.base + region.size));
row.push_back(PrintRegionMmuFlags(region, has_shared));
row.push_back(PrintRegionSize(region.size));
if (platform == debug::Platform::kFuchsia) {
// Fuchsia-specific parts.
// Only show VMO information for regions which have a VMO koid. Regions with no VMO will be
// VMARs and showing offset and committed pages is misleading.
bool has_koid = region.vmo_koid != 0;
row.push_back(has_koid ? std::to_string(region.vmo_koid) : std::string());
row.push_back(has_koid ? to_hex_string(region.vmo_offset) : std::string());
row.push_back(has_koid ? std::to_string(region.committed_bytes / page_size) : std::string());
if (has_koid) {
total_mapped += region.size;
total_committed += region.committed_bytes;
}
} else {
// Non-Fuchsia has only the offset which is unconditionally shown.
row.push_back(to_hex_string(region.vmo_offset));
}
row.push_back(PrintRegionName(region.depth, region.name));
rows.push_back(std::move(row));
}
std::vector<ColSpec> colspec;
colspec.push_back(ColSpec(Align::kRight, 0, "Start", 2));
colspec.push_back(ColSpec(Align::kRight, 0, "End", 2));
colspec.push_back(ColSpec(Align::kLeft, 0, "Prot", 1));
colspec.push_back(ColSpec(Align::kRight, 0, "Size", 1));
if (platform == debug::Platform::kFuchsia) {
colspec.push_back(ColSpec(Align::kRight, 0, "Koid", 1));
colspec.push_back(ColSpec(Align::kRight, 0, "Offset", 1));
colspec.push_back(ColSpec(Align::kRight, 0, "Cmt.Pgs", 1));
} else {
colspec.push_back(ColSpec(Align::kRight, 0, "Offset", 1));
}
colspec.push_back(ColSpec(Align::kLeft, 0, "Name", 1));
OutputBuffer out;
FormatTable(colspec, rows, &out);
// Format the section at the bottom showing statistics. These are formatted so the "=" align
// horizontally (hence extra left-spacing on the strings).
out.Append("\n");
out.Append(Syntax::kHeading, " Page size: ");
out.Append(std::to_string(page_size));
out.Append("\n");
if (platform == debug::Platform::kFuchsia && print_totals) {
out.Append(Syntax::kHeading, " Total mapped bytes: ");
out.Append(std::to_string(total_mapped));
out.Append("\n");
out.Append(Syntax::kHeading, " Total committed pages: ");
out.Append(std::to_string(total_committed / page_size));
out.Append(" = " + std::to_string(total_committed) + " bytes\n");
out.Append(" (See \"help aspace\" for what committed pages mean.)\n");
}
console->Output(out);
}
void RunVerbAspace(const Command& cmd, fxl::RefPtr<CommandContext> cmd_context) {
// Only a process can be specified.
if (Err err = cmd.ValidateNouns({Noun::kProcess}); err.has_error())
return cmd_context->ReportError(err);
bool print_totals = true;
uint64_t address = 0;
if (cmd.args().size() == 1) {
if (Err err = ReadUint64Arg(cmd, 0, "address", &address); err.has_error())
return cmd_context->ReportError(err);
print_totals = false; // Adding up totals for a subregion is misleading.
} else if (cmd.args().size() > 1) {
return cmd_context->ReportError(
Err(ErrType::kInput, "\"aspace\" takes zero or one parameter."));
}
std::optional<uint64_t> addr_limit;
if (cmd.HasSwitch(kAddressLimitSwitch)) {
auto err_or = StringToNumber(ExprLanguage::kC, cmd.GetSwitchValue(kAddressLimitSwitch));
if (err_or.has_error()) {
return cmd_context->ReportError(err_or.err());
}
uint64_t limit = 0;
if (auto err = err_or.value().PromoteTo64(&limit); err.has_error()) {
return cmd_context->ReportError(err);
}
addr_limit = limit;
print_totals = false;
}
if (Err err = AssertRunningTarget(cmd_context->GetConsoleContext(), "aspace", cmd.target());
err.has_error())
return cmd_context->ReportError(err);
cmd.target()->GetProcess()->GetAspace(
address, [cmd_context, print_totals, addr_limit](const Err& err,
std::vector<debug_ipc::AddressRegion> map) {
OnAspaceComplete(cmd_context, err, map, print_totals, addr_limit);
});
}
} // namespace
VerbRecord GetAspaceVerbRecord() {
auto record = VerbRecord(&RunVerbAspace, {"aspace", "as"}, kAspaceShortHelp, kAspaceUsage,
kAspaceHelp, CommandGroup::kQuery);
record.switches.push_back(SwitchRecord(kAddressLimitSwitch, true, "limit", 'l'));
return record;
}
} // namespace zxdb