blob: 73c1477d6670d98bb528b987a1c848f1cf304bb4 [file] [log] [blame]
// Copyright 2018 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 "garnet/bin/zxdb/console/verbs.h"
#include <inttypes.h>
#include <algorithm>
#include <vector>
#include "garnet/bin/zxdb/client/process.h"
#include "garnet/bin/zxdb/client/session.h"
#include "garnet/bin/zxdb/client/target.h"
#include "garnet/bin/zxdb/common/err.h"
#include "garnet/bin/zxdb/console/command.h"
#include "garnet/bin/zxdb/console/command_utils.h"
#include "garnet/bin/zxdb/console/console.h"
#include "garnet/bin/zxdb/console/format_table.h"
#include "garnet/bin/zxdb/console/output_buffer.h"
#include "garnet/public/lib/fxl/strings/string_printf.h"
namespace zxdb {
namespace {
// Verifies that the given target can be run or attached.
Err AssertRunnableTarget(Target* target) {
Target::State state = target->GetState();
if (state == Target::State::kStarting || state == Target::State::kAttaching) {
return Err(
"The current process is in the process of starting or attaching.\n"
"Either \"kill\" it or create a \"new\" process context.");
}
if (state == Target::State::kRunning) {
return Err(
"The current process is already running.\n"
"Either \"kill\" it or create a \"new\" process context.");
}
return Err();
}
// Verifies that the given job_context can be run or attached.
Err AssertRunnableJobContext(JobContext* job_context) {
JobContext::State state = job_context->GetState();
if (state == JobContext::State::kStarting ||
state == JobContext::State::kAttaching) {
return Err(
"The current job is in the job of starting or attaching.\n"
"Either \"kill\" it or create a \"new\" job context.");
}
if (state == JobContext::State::kRunning) {
return Err(
"The current job is already running.\n"
"Either \"kill\" it or create a \"new\" job context.");
}
return Err();
}
// Callback for "attach", "detach". The verb affects the
// message printed to the screen.
void JobCommandCallback(const char* verb, fxl::WeakPtr<JobContext> job_context,
bool display_message_on_success, const Err& err,
CommandCallback callback = nullptr) {
if (!display_message_on_success && !err.has_error())
return;
Console* console = Console::get();
OutputBuffer out;
if (err.has_error()) {
if (job_context) {
out.Append(fxl::StringPrintf(
"Job %d %s failed.\n",
console->context().IdForJobContext(job_context.get()), verb));
}
out.Append(err);
} else if (job_context) {
out.Append(DescribeJobContext(&console->context(), job_context.get()));
}
console->Output(std::move(out));
if (callback) {
callback(err);
}
}
// Callback for "run", "attach", "detach" and "stop". The verb affects the
// message printed to the screen.
// Since now Verbs commands can take in a callback and Process commands call
// this callback, we optionally pass that callback here to be called in a long
// merry string of callbacks.
void ProcessCommandCallback(const char* verb, fxl::WeakPtr<Target> target,
bool display_message_on_success, const Err& err,
CommandCallback callback = nullptr) {
if (!display_message_on_success && !err.has_error())
return;
Console* console = Console::get();
OutputBuffer out;
if (err.has_error()) {
if (target) {
out.Append(fxl::StringPrintf("Process %d %s failed.\n",
console->context().IdForTarget(target.get()),
verb));
}
out.Append(err);
} else if (target) {
out.Append(DescribeTarget(&console->context(), target.get()));
}
console->Output(std::move(out));
if (callback) {
callback(err);
}
}
// run -------------------------------------------------------------------------
const char kRunShortHelp[] = "run / r: Run the program.";
const char kRunHelp[] =
R"(run [ <program name> <program args>* ]
Alias: "r"
Runs the program. With no arguments, "run" will run the binary stored in the
process context, if any. With an argument, the binary name will be set and
that binary will be run.
Hints
By default "run" will run the active process context (create a new one with
"new" to run multiple programs at once). To run an explicit process context,
specify it explicitly: "process 2 run".
To see a list of available process contexts, type "process".
Examples
run
process 2 run
Runs a process that's already been configured with a binary name.
run /boot/bin/ps
run chrome --no-sandbox http://www.google.com/
Runs the given process.
)";
Err DoRun(ConsoleContext* context, const Command& cmd,
CommandCallback callback = nullptr) {
// Only a process can be run.
Err err = cmd.ValidateNouns({Noun::kProcess});
if (err.has_error())
return err;
err = AssertRunnableTarget(cmd.target());
if (err.has_error())
return err;
if (cmd.args().empty()) {
// Use the args already set on the target.
if (cmd.target()->GetArgs().empty())
return Err("No program to run. Try \"run <program name>\".");
} else {
cmd.target()->SetArgs(cmd.args());
}
cmd.target()->Launch([callback](fxl::WeakPtr<Target> target, const Err& err) {
ProcessCommandCallback("launch", target, true, err, callback);
});
return Err();
}
// kill ----------------------------------------------------------------------
const char kKillShortHelp[] = "kill / k: terminate a process";
const char kKillHelp[] =
R"(kill
Terminates a process from the debugger.
Hints
By default the current process is detached.
To detach a different process prefix with "process N"
Examples
kill
Kills the current process.
process 4 kill
Kills process 4.
)";
Err DoKill(ConsoleContext* context, const Command& cmd,
CommandCallback callback = nullptr) {
// Only a process can be detached.
Err err = cmd.ValidateNouns({Noun::kProcess});
if (err.has_error())
return err;
if (!cmd.args().empty())
return Err("The 'kill' command doesn't take any parameters.");
cmd.target()->Detach([callback](fxl::WeakPtr<Target> target, const Err& err) {
// The ConsoleContext displays messages for stopped processes, so don't
// display messages when successfully killing.
ProcessCommandCallback("kill", target, false, err, callback);
});
return Err();
}
// attach ----------------------------------------------------------------------
const char kAttachShortHelp[] = "attach: Attach to a running process/job.";
const char kAttachHelp[] =
R"(attach <process/job koid>
Hints
Use the "ps" command to view the active process and job tree.
To debug more than one process/job at a time, use "new" to create a new
process/job context.
Examples
attach 2371
Attaches to the process with koid 2371.
job attach 2323
Attaches to job with koid 2323.
process 4 attach 2371
Attaches process context 4 to the process with koid 2371.
job 3 attach 2323
Attaches job context 3 to the job with koid 2323.
)";
Err DoAttach(ConsoleContext* context, const Command& cmd,
CommandCallback callback = nullptr) {
// Only a process can be attached.
Err err = cmd.ValidateNouns({Noun::kProcess, Noun::kJob});
if (err.has_error())
return err;
if (cmd.HasNoun(Noun::kJob)) {
err = AssertRunnableJobContext(cmd.job_context());
if (err.has_error())
return err;
// Should have one arg which is the koid.
uint64_t koid = 0;
err = ReadUint64Arg(cmd, 0, "job koid", &koid);
if (err.has_error())
return err;
cmd.job_context()->Attach(
koid, [callback](fxl::WeakPtr<JobContext> job_context, const Err& err) {
JobCommandCallback("attach", job_context, true, err, callback);
});
} else {
err = AssertRunnableTarget(cmd.target());
if (err.has_error())
return err;
// Should have one arg which is the koid.
uint64_t koid = 0;
err = ReadUint64Arg(cmd, 0, "process koid", &koid);
if (err.has_error())
return err;
cmd.target()->Attach(
koid, [callback](fxl::WeakPtr<Target> target, const Err& err) {
ProcessCommandCallback("attach", target, true, err, callback);
});
}
return Err();
}
// detach ----------------------------------------------------------------------
const char kDetachShortHelp[] = "detach: Detach from a process/job.";
const char kDetachHelp[] =
R"(detach
Detaches the debugger from a running process/job. The process will continue
running.
Hints
By default the current process/job is detached.
To detach a different process/job prefix with "process N" or "job N"
Examples
detach
Detaches from the current process.
job detach
Detaches from the current job.
process 4 detach
Detaches from process context 4.
job 3 detach
Detaches from job context 3.
)";
Err DoDetach(ConsoleContext* context, const Command& cmd,
CommandCallback callback = nullptr) {
// Only a process can be detached.
Err err = cmd.ValidateNouns({Noun::kProcess, Noun::kJob});
if (err.has_error())
return err;
if (!cmd.args().empty())
return Err(ErrType::kInput, "\"detach\" takes no parameters.");
if (cmd.HasNoun(Noun::kJob)) {
cmd.job_context()->Detach(
[callback](fxl::WeakPtr<JobContext> job_context, const Err& err) {
JobCommandCallback("detach", job_context, false, err, callback);
});
} else {
// Only print something when there was an error detaching. The console
// context will watch for Process destruction and print messages for each
// one in the success case.
cmd.target()->Detach(
[callback](fxl::WeakPtr<Target> target, const Err& err) {
// The ConsoleContext displays messages for stopped processes, so
// don't display messages when successfully detaching.
ProcessCommandCallback("detach", target, false, err, callback);
});
}
return Err();
}
// libs ------------------------------------------------------------------------
const char kLibsShortHelp[] = "libs: Show loaded libraries for a process.";
const char kLibsHelp[] =
R"(libs
Shows the loaded library information for the given process.
Examples
libs
process 2 libs
)";
// Completion callback for DoLibs().
void OnLibsComplete(const Err& err, std::vector<debug_ipc::Module> modules) {
Console* console = Console::get();
if (err.has_error()) {
console->Output(err);
return;
}
// Sort by load address.
std::sort(modules.begin(), modules.end(),
[](const debug_ipc::Module& a, const debug_ipc::Module& b) {
return a.base < b.base;
});
std::vector<std::vector<std::string>> rows;
for (const auto& module : modules) {
rows.push_back(std::vector<std::string>{
fxl::StringPrintf("0x%" PRIx64, module.base), module.name});
}
OutputBuffer out;
FormatTable({ColSpec(Align::kRight, 0, "Load address", 2),
ColSpec(Align::kLeft, 0, "Name", 1)},
rows, &out);
console->Output(std::move(out));
}
Err DoLibs(ConsoleContext* context, const Command& cmd) {
// Only a process can be specified.
Err err = cmd.ValidateNouns({Noun::kProcess});
if (err.has_error())
return err;
if (!cmd.args().empty())
return Err(ErrType::kInput, "\"libs\" takes no parameters.");
err = AssertRunningTarget(context, "libs", cmd.target());
if (err.has_error())
return err;
cmd.target()->GetProcess()->GetModules(&OnLibsComplete);
return Err();
}
// libs ------------------------------------------------------------------------
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;
}
const char kAspaceShortHelp[] =
"aspace / as: Show address space for a process.";
const char kAspaceHelp[] =
R"(aspace [ <address> ]
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.
Examples
aspace
aspace 0x530b010dc000
process 2 aspace
)";
void OnAspaceComplete(const Err& err,
std::vector<debug_ipc::AddressRegion> map) {
Console* console = Console::get();
if (err.has_error()) {
console->Output(err);
return;
}
if (map.empty()) {
console->Output("Region not mapped.");
return;
}
std::vector<std::vector<std::string>> rows;
for (const auto& region : map) {
rows.push_back(std::vector<std::string>{
fxl::StringPrintf("0x%" PRIx64, region.base),
fxl::StringPrintf("0x%" PRIx64, region.base + region.size),
PrintRegionSize(region.size),
PrintRegionName(region.depth, region.name)});
}
OutputBuffer out;
FormatTable({ColSpec(Align::kRight, 0, "Start", 2),
ColSpec(Align::kRight, 0, "End", 2),
ColSpec(Align::kRight, 0, "Size", 2),
ColSpec(Align::kLeft, 0, "Name", 1)},
rows, &out);
console->Output(std::move(out));
}
Err DoAspace(ConsoleContext* context, const Command& cmd) {
// Only a process can be specified.
Err err = cmd.ValidateNouns({Noun::kProcess});
if (err.has_error())
return err;
uint64_t address = 0;
if (cmd.args().size() == 1) {
err = ReadUint64Arg(cmd, 0, "address", &address);
if (err.has_error())
return err;
} else if (cmd.args().size() > 1) {
return Err(ErrType::kInput, "\"aspace\" takes zero or one parameter.");
}
err = AssertRunningTarget(context, "aspace", cmd.target());
if (err.has_error())
return err;
cmd.target()->GetProcess()->GetAspace(address, &OnAspaceComplete);
return Err();
}
} // namespace
void AppendProcessVerbs(std::map<Verb, VerbRecord>* verbs) {
// TODO(anmittal): Add one for job when we fix verbs.
(*verbs)[Verb::kRun] = VerbRecord(&DoRun, {"run", "r"}, kRunShortHelp,
kRunHelp, CommandGroup::kProcess);
(*verbs)[Verb::kKill] = VerbRecord(&DoKill, {"kill", "k"}, kKillShortHelp,
kKillHelp, CommandGroup::kProcess);
(*verbs)[Verb::kAttach] = VerbRecord(&DoAttach, {"attach"}, kAttachShortHelp,
kAttachHelp, CommandGroup::kProcess);
(*verbs)[Verb::kDetach] = VerbRecord(&DoDetach, {"detach"}, kDetachShortHelp,
kDetachHelp, CommandGroup::kProcess);
(*verbs)[Verb::kLibs] = VerbRecord(&DoLibs, {"libs"}, kLibsShortHelp,
kLibsHelp, CommandGroup::kQuery);
(*verbs)[Verb::kAspace] =
VerbRecord(&DoAspace, {"aspace", "as"}, kAspaceShortHelp, kAspaceHelp,
CommandGroup::kQuery);
}
} // namespace zxdb