blob: 913842e95554bad7a8afbe0aabce96df805108da [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/nouns.h"
#include <inttypes.h>
#include <algorithm>
#include <utility>
#include "garnet/bin/zxdb/client/breakpoint.h"
#include "garnet/bin/zxdb/client/frame.h"
#include "garnet/bin/zxdb/client/job.h"
#include "garnet/bin/zxdb/client/process.h"
#include "garnet/bin/zxdb/client/session.h"
#include "garnet/bin/zxdb/client/system.h"
#include "garnet/bin/zxdb/client/thread.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/console_context.h"
#include "garnet/bin/zxdb/console/format_frame.h"
#include "garnet/bin/zxdb/console/format_table.h"
#include "garnet/bin/zxdb/console/format_value.h"
#include "garnet/bin/zxdb/console/format_value_process_context_impl.h"
#include "garnet/bin/zxdb/console/output_buffer.h"
#include "garnet/bin/zxdb/console/string_util.h"
#include "garnet/bin/zxdb/symbols/location.h"
#include "garnet/public/lib/fxl/logging.h"
#include "garnet/public/lib/fxl/strings/string_printf.h"
namespace zxdb {
namespace {
constexpr int kForceTypes = 1;
constexpr int kVerboseSwitch = 2;
// Frames ----------------------------------------------------------------------
const char kFrameShortHelp[] = "frame / f: Select or list stack frames.";
const char kFrameHelp[] =
R"(frame [ -v ] [ <id> [ <command> ... ] ]
Selects or lists stack frames. Stack frames are only available for threads
that are stopped. Selecting or listing frames for running threads will
fail.
By itself, "frame" will list the stack frames in the current thread.
With an ID following it ("frame 3"), selects that frame as the current
active frame. This frame will apply by default for subsequent commands.
With an ID and another command following it ("frame 3 print"), modifies the
frame for that command only. This allows interrogating stack frames
regardless of which is the active one.
Options
-t
--types
Include all type information for function parameters.
-v
--verbose
Show more information in the frame list. This is valid when listing
frames only.
Examples
f
frame
f -v
frame -v
Lists all stack frames in the current thread.
f 1
frame 1
Selects frame 1 to be the active frame in the current thread.
process 2 thread 1 frame 3
Selects the specified process, thread, and frame.
)";
// Returns true if processing should stop (either a frame command or an error),
// false to continue processing to the nex noun type.
bool HandleFrameNoun(ConsoleContext* context, const Command& cmd, Err* err) {
if (!cmd.HasNoun(Noun::kFrame))
return false;
if (!cmd.thread()) {
*err = Err(ErrType::kInput, "There is no thread to have frames.");
return true;
}
if (cmd.GetNounIndex(Noun::kFrame) == Command::kNoIndex) {
// Just "frame", this lists available frames.
OutputFrameList(cmd.thread(), cmd.HasSwitch(kForceTypes),
cmd.HasSwitch(kVerboseSwitch));
return true;
}
// Explicit index provided, this switches the current context. The thread
// should be already resolved to a valid pointer if it was specified on the
// command line (otherwise the command would have been rejected before here).
FXL_DCHECK(cmd.frame());
context->SetActiveFrameForThread(cmd.frame());
// Setting the active thread also sets the active thread and target.
context->SetActiveThreadForTarget(cmd.thread());
context->SetActiveTarget(cmd.target());
// Schedule asynchronous output of the full frame description.
auto helper = fxl::MakeRefCounted<FormatValue>(
std::make_unique<FormatValueProcessContextImpl>(cmd.target()));
FormatFrameLong(cmd.frame(), cmd.HasSwitch(kForceTypes), helper.get(),
FormatExprValueOptions(),
context->GetActiveFrameIdForThread(cmd.thread()));
helper->Complete(
[helper](OutputBuffer out) { Console::get()->Output(std::move(out)); });
return true;
}
// Threads ---------------------------------------------------------------------
const char kThreadShortHelp[] = "thread / t: Select or list threads.";
const char kThreadHelp[] =
R"(thread [ <id> [ <command> ... ] ]
Selects or lists threads.
By itself, "thread" will list the threads in the current process.
With an ID following it ("thread 3"), selects that thread as the current
active thread. This thread will apply by default for subsequent commands
(like "step").
With an ID and another command following it ("thread 3 step"), modifies the
thread for that command only. This allows stepping or interrogating threads
regardless of which is the active one.
Examples
t
thread
Lists all threads in the current process.
t 1
thread 1
Selects thread 1 to be the active thread in the current process.
process 2 thread 1
Selects process 2 as the active process and thread 1 within it as the
active thread.
process 2 thread
Lists all threads in process 2.
thread 1 step
Steps thread 1 in the current process, regardless of the active thread.
process 2 thread 1 step
Steps thread 1 in process 2, regardless of the active process or thread.
)";
// Prints the thread list for the given process to the console.
void ListThreads(ConsoleContext* context, Process* process) {
std::vector<Thread*> threads = process->GetThreads();
int active_thread_id =
context->GetActiveThreadIdForTarget(process->GetTarget());
// Sort by ID.
std::vector<std::pair<int, Thread*>> id_threads;
for (Thread* thread : threads)
id_threads.push_back(std::make_pair(context->IdForThread(thread), thread));
std::sort(id_threads.begin(), id_threads.end());
std::vector<std::vector<std::string>> rows;
for (const auto& pair : id_threads) {
rows.emplace_back();
std::vector<std::string>& row = rows.back();
// "Current thread" marker.
if (pair.first == active_thread_id)
row.push_back(GetRightArrow());
else
row.emplace_back();
row.push_back(fxl::StringPrintf("%d", pair.first));
row.push_back(ThreadStateToString(pair.second->GetState(),
pair.second->GetBlockedReason()));
row.push_back(fxl::StringPrintf("%" PRIu64, pair.second->GetKoid()));
row.push_back(pair.second->GetName());
}
OutputBuffer out;
FormatTable(
{ColSpec(Align::kLeft),
ColSpec(Align::kRight, 0, "#", 0, Syntax::kSpecial),
ColSpec(Align::kLeft, 0, "State"), ColSpec(Align::kRight, 0, "Koid"),
ColSpec(Align::kLeft, 0, "Name")},
rows, &out);
Console::get()->Output(std::move(out));
}
// Updates the thread list from the debugged process and asynchronously prints
// the result. When the user lists threads, we really don't want to be
// misleading and show out-of-date thread names which the developer might be
// relying on. Therefore, force a sync of the thread list from the target
// (which should be fast) before displaying the thread list.
void ScheduleListThreads(Process* process) {
// Since the Process issues the callback, it's OK to capture the pointer.
process->SyncThreads(
[process]() { ListThreads(&Console::get()->context(), process); });
}
// Returns true if processing should stop (either a thread command or an error),
// false to continue processing to the nex noun type.
bool HandleThreadNoun(ConsoleContext* context, const Command& cmd, Err* err) {
if (!cmd.HasNoun(Noun::kThread))
return false;
Process* process = cmd.target()->GetProcess();
if (!process) {
*err = Err(ErrType::kInput, "Process not running, no threads.");
return true;
}
if (cmd.GetNounIndex(Noun::kThread) == Command::kNoIndex) {
// Just "thread" or "process 2 thread" specified, this lists available
// threads.
ScheduleListThreads(process);
return true;
}
// Explicit index provided, this switches the current context. The thread
// should be already resolved to a valid pointer if it was specified on the
// command line (otherwise the command would have been rejected before here).
FXL_DCHECK(cmd.thread());
context->SetActiveThreadForTarget(cmd.thread());
// Setting the active thread also sets the active target.
context->SetActiveTarget(cmd.target());
Console::get()->Output(DescribeThread(context, cmd.thread()));
return true;
}
// Jobs -------------------------------------------------------------------
const char kJobShortHelp[] = "job / j: Select or list job contexts.";
const char kJobHelp[] =
R"(job [ <id> [ <command> ... ] ]
Alias: "j"
Selects or lists job contexts.
By itself, "job" will list available job contexts with their IDs. New
job contexts can be created with the "new" command. This list of debugger
contexts is different than the list of jobs on the target system (use
"ps" to list all running jobs, and "attach" to attach a context to a
running job).
With an ID following it ("job 3"), selects that job context as the
current active job context. This context will apply by default for subsequent
commands (like "job attach").
With an ID and another command following it ("job 3 attach"), modifies the
job context for that command only. This allows attaching, filtering, etc.
regardless of which is the active one.
Examples
j
job
Lists all job contexts.
j 2
job 2
Sets job context 2 as the active one.
j 2 r
job 2 attach
Attach to job context 2, regardless of the active one.
)";
void ListJobs(ConsoleContext* context) {
auto job_contexts = context->session()->system().GetJobContexts();
int active_job_context_id = context->GetActiveJobContextId();
// Sort by ID.
std::vector<std::pair<int, JobContext*>> id_job_contexts;
for (auto& job_context : job_contexts)
id_job_contexts.push_back(
std::make_pair(context->IdForJobContext(job_context), job_context));
std::sort(id_job_contexts.begin(), id_job_contexts.end());
std::vector<std::vector<std::string>> rows;
for (const auto& pair : id_job_contexts) {
rows.emplace_back();
std::vector<std::string>& row = rows.back();
// "Current process" marker (or nothing).
if (pair.first == active_job_context_id)
row.emplace_back(GetRightArrow());
else
row.emplace_back();
// ID.
row.push_back(fxl::StringPrintf("%d", pair.first));
// State and koid (if running).
row.push_back(JobContextStateToString(pair.second->GetState()));
if (pair.second->GetState() == JobContext::State::kRunning) {
row.push_back(
fxl::StringPrintf("%" PRIu64, pair.second->GetJob()->GetKoid()));
} else {
row.emplace_back();
}
row.push_back(DescribeJobContextName(pair.second));
}
OutputBuffer out;
FormatTable(
{ColSpec(Align::kLeft),
ColSpec(Align::kRight, 0, "#", 0, Syntax::kSpecial),
ColSpec(Align::kLeft, 0, "State"), ColSpec(Align::kRight, 0, "Koid"),
ColSpec(Align::kLeft, 0, "Name")},
rows, &out);
Console::get()->Output(std::move(out));
}
// Returns true if processing should stop (either a thread command or an error),
// false to continue processing to the nex noun type.
bool HandleJobNoun(ConsoleContext* context, const Command& cmd, Err* err) {
if (!cmd.HasNoun(Noun::kJob))
return false;
if (cmd.GetNounIndex(Noun::kJob) == Command::kNoIndex) {
// Just "job", this lists available job.
ListJobs(context);
return true;
}
FXL_DCHECK(cmd.job_context());
context->SetActiveJobContext(cmd.job_context());
Console::get()->Output(DescribeJobContext(context, cmd.job_context()));
return true;
}
// Processes -------------------------------------------------------------------
const char kProcessShortHelp[] =
"process / pr: Select or list process contexts.";
const char kProcessHelp[] =
R"(process [ <id> [ <command> ... ] ]
Alias: "pr"
Selects or lists process contexts.
By itself, "process" will list available process contexts with their IDs. New
process contexts can be created with the "new" command. This list of debugger
contexts is different than the list of processes on the target system (use
"ps" to list all running processes, and "attach" to attach a context to a
running process).
With an ID following it ("process 3"), selects that process context as the
current active context. This context will apply by default for subsequent
commands (like "run").
With an ID and another command following it ("process 3 run"), modifies the
process context for that command only. This allows running, pausing, etc.
processes regardless of which is the active one.
Examples
pr
process
Lists all process contexts.
pr 2
process 2
Sets process context 2 as the active one.
pr 2 r
process 2 run
Runs process context 2, regardless of the active one.
)";
void ListProcesses(ConsoleContext* context) {
auto targets = context->session()->system().GetTargets();
int active_target_id = context->GetActiveTargetId();
// Sort by ID.
std::vector<std::pair<int, Target*>> id_targets;
for (auto& target : targets)
id_targets.push_back(std::make_pair(context->IdForTarget(target), target));
std::sort(id_targets.begin(), id_targets.end());
std::vector<std::vector<std::string>> rows;
for (const auto& pair : id_targets) {
rows.emplace_back();
std::vector<std::string>& row = rows.back();
// "Current process" marker (or nothing).
if (pair.first == active_target_id)
row.emplace_back(GetRightArrow());
else
row.emplace_back();
// ID.
row.push_back(fxl::StringPrintf("%d", pair.first));
// State and koid (if running).
row.push_back(TargetStateToString(pair.second->GetState()));
if (pair.second->GetState() == Target::State::kRunning) {
row.push_back(
fxl::StringPrintf("%" PRIu64, pair.second->GetProcess()->GetKoid()));
} else {
row.emplace_back();
}
row.push_back(DescribeTargetName(pair.second));
}
OutputBuffer out;
FormatTable(
{ColSpec(Align::kLeft),
ColSpec(Align::kRight, 0, "#", 0, Syntax::kSpecial),
ColSpec(Align::kLeft, 0, "State"), ColSpec(Align::kRight, 0, "Koid"),
ColSpec(Align::kLeft, 0, "Name")},
rows, &out);
Console::get()->Output(std::move(out));
}
// Returns true if processing should stop (either a thread command or an error),
// false to continue processing to the nex noun type.
bool HandleProcessNoun(ConsoleContext* context, const Command& cmd, Err* err) {
if (!cmd.HasNoun(Noun::kProcess))
return false;
if (cmd.GetNounIndex(Noun::kProcess) == Command::kNoIndex) {
// Just "process", this lists available processes.
ListProcesses(context);
return true;
}
// Explicit index provided, this switches the current context. The target
// should be already resolved to a valid pointer if it was specified on the
// command line (otherwise the command would have been rejected before here).
FXL_DCHECK(cmd.target());
context->SetActiveTarget(cmd.target());
Console::get()->Output(DescribeTarget(context, cmd.target()));
return true;
}
// Breakpoints -----------------------------------------------------------------
const char kBreakpointShortHelp[] =
"breakpoint / bp: Select or list breakpoints.";
const char kBreakpointHelp[] =
R"(breakpoint [ <id> [ <command> ... ] ]
Alias: "bp"
Selects or lists breakpoints. Not to be confused with the "break" / "b"
command which creates new breakpoints. See "help break" for more.
By itself, "breakpoint" or "bp" will list all breakpoints with their IDs.
With an ID following it ("breakpoint 3"), selects that breakpoint as the
current active breakpoint. This breakpoint will apply by default for
subsequent breakpoint commands (like "clear" or "edit").
With an ID and another command following it ("breakpoint 2 clear"), modifies
the breakpoint context for that command only. This allows modifying
breakpoints regardless of the active one.
Examples
bp
breakpoint
Lists all breakpoints.
bp 2
breakpoint 2
Sets breakpoint 2 as the active one.
bp 2 cl
breakpoint 2 clear
Clears breakpoint 2.
)";
void ListBreakpoints(ConsoleContext* context) {
auto breakpoints = context->session()->system().GetBreakpoints();
if (breakpoints.empty()) {
Console::get()->Output("No breakpoints.\n");
return;
}
int active_breakpoint_id = context->GetActiveBreakpointId();
// Sort by ID.
std::map<int, Breakpoint*> id_bp;
for (auto& bp : breakpoints)
id_bp[context->IdForBreakpoint(bp)] = bp;
std::vector<std::vector<std::string>> rows;
for (const auto& pair : id_bp) {
rows.emplace_back();
std::vector<std::string>& row = rows.back();
// "Current breakpoint" marker.
if (pair.first == active_breakpoint_id)
row.emplace_back(GetRightArrow());
else
row.emplace_back();
BreakpointSettings settings = pair.second->GetSettings();
row.push_back(fxl::StringPrintf("%d", pair.first));
row.push_back(BreakpointScopeToString(context, settings));
row.push_back(BreakpointStopToString(settings.stop_mode));
row.push_back(BreakpointEnabledToString(settings.enabled));
row.push_back(BreakpointTypeToString(settings.type));
row.push_back(DescribeInputLocation(settings.location));
}
OutputBuffer out;
FormatTable(
{ColSpec(Align::kLeft),
ColSpec(Align::kRight, 0, "#", 0, Syntax::kSpecial),
ColSpec(Align::kLeft, 0, "Scope"), ColSpec(Align::kLeft, 0, "Stop"),
ColSpec(Align::kLeft, 0, "Enabled"), ColSpec(Align::kLeft, 0, "Type"),
ColSpec(Align::kLeft, 0, "Location")},
rows, &out);
Console::get()->Output(std::move(out));
}
// Returns true if breakpoint was specified (and therefore nothing else
// should be called. If breakpoint is specified but there was an error, *err
// will be set.
bool HandleBreakpointNoun(ConsoleContext* context, const Command& cmd,
Err* err) {
if (!cmd.HasNoun(Noun::kBreakpoint))
return false;
// With no verb, breakpoint can not be combined with any other noun. Saying
// "process 2 breakpoint" doesn't make any sense.
*err = cmd.ValidateNouns({Noun::kBreakpoint});
if (err->has_error())
return true;
if (cmd.GetNounIndex(Noun::kBreakpoint) == Command::kNoIndex) {
// Just "breakpoint", this lists available breakpoints.
ListBreakpoints(context);
return true;
}
// Explicit index provided, this switches the current context. The breakpoint
// should be already resolved to a valid pointer if it was specified on the
// command line (otherwise the command would have been rejected before here).
FXL_DCHECK(cmd.breakpoint());
context->SetActiveBreakpoint(cmd.breakpoint());
Console::get()->Output(DescribeBreakpoint(context, cmd.breakpoint()));
return true;
}
} // namespace
NounRecord::NounRecord() = default;
NounRecord::NounRecord(std::initializer_list<std::string> aliases,
const char* short_help, const char* help,
CommandGroup command_group)
: aliases(aliases),
short_help(short_help),
help(help),
command_group(command_group) {}
NounRecord::~NounRecord() = default;
const std::map<Noun, NounRecord>& GetNouns() {
static std::map<Noun, NounRecord> all_nouns;
if (all_nouns.empty()) {
AppendNouns(&all_nouns);
// Everything but Noun::kNone (= 0) should be in the map.
FXL_DCHECK(all_nouns.size() == static_cast<size_t>(Noun::kLast) - 1)
<< "You need to update the noun lookup table for additions to Nouns.";
}
return all_nouns;
}
std::string NounToString(Noun n) {
const auto& nouns = GetNouns();
auto found = nouns.find(n);
if (found == nouns.end())
return std::string();
return found->second.aliases[0];
}
const std::map<std::string, Noun>& GetStringNounMap() {
static std::map<std::string, Noun> map;
if (map.empty()) {
// Build up the reverse-mapping from alias to verb enum.
for (const auto& noun_pair : GetNouns()) {
for (const auto& alias : noun_pair.second.aliases)
map[alias] = noun_pair.first;
}
}
return map;
}
Err ExecuteNoun(ConsoleContext* context, const Command& cmd) {
Err result;
if (HandleBreakpointNoun(context, cmd, &result))
return result;
// Work backwards in specificity (frame -> thread -> process).
if (HandleFrameNoun(context, cmd, &result))
return result;
if (HandleThreadNoun(context, cmd, &result))
return result;
if (HandleProcessNoun(context, cmd, &result))
return result;
if (HandleJobNoun(context, cmd, &result))
return result;
return result;
}
void AppendNouns(std::map<Noun, NounRecord>* nouns) {
(*nouns)[Noun::kBreakpoint] =
NounRecord({"breakpoint", "bp"}, kBreakpointShortHelp, kBreakpointHelp,
CommandGroup::kBreakpoint);
(*nouns)[Noun::kFrame] = NounRecord({"frame", "f"}, kFrameShortHelp,
kFrameHelp, CommandGroup::kQuery);
(*nouns)[Noun::kThread] = NounRecord({"thread", "t"}, kThreadShortHelp,
kThreadHelp, CommandGroup::kProcess);
(*nouns)[Noun::kProcess] = NounRecord({"process", "pr"}, kProcessShortHelp,
kProcessHelp, CommandGroup::kProcess);
(*nouns)[Noun::kJob] =
NounRecord({"job", "j"}, kJobShortHelp, kJobHelp, CommandGroup::kJob);
}
const std::vector<SwitchRecord>& GetNounSwitches() {
static std::vector<SwitchRecord> switches;
if (switches.empty()) {
switches.emplace_back(kForceTypes, false, "types", 't');
switches.emplace_back(kVerboseSwitch, false, "verbose", 'v');
}
return switches;
}
} // namespace zxdb