blob: 2645f77cb7258e575063b770cea4d85d8ed85fb5 [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 "src/developer/debug/zxdb/console/nouns.h"
#include <lib/syslog/cpp/macros.h>
#include <algorithm>
#include <utility>
#include "src/developer/debug/zxdb/client/breakpoint.h"
#include "src/developer/debug/zxdb/client/breakpoint_location.h"
#include "src/developer/debug/zxdb/client/frame.h"
#include "src/developer/debug/zxdb/client/process.h"
#include "src/developer/debug/zxdb/client/session.h"
#include "src/developer/debug/zxdb/client/setting_schema_definition.h"
#include "src/developer/debug/zxdb/client/stack.h"
#include "src/developer/debug/zxdb/client/system.h"
#include "src/developer/debug/zxdb/client/target.h"
#include "src/developer/debug/zxdb/client/thread.h"
#include "src/developer/debug/zxdb/common/err.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/console_context.h"
#include "src/developer/debug/zxdb/console/format_filter.h"
#include "src/developer/debug/zxdb/console/format_frame.h"
#include "src/developer/debug/zxdb/console/format_location.h"
#include "src/developer/debug/zxdb/console/format_table.h"
#include "src/developer/debug/zxdb/console/format_target.h"
#include "src/developer/debug/zxdb/console/format_thread.h"
#include "src/developer/debug/zxdb/console/output_buffer.h"
#include "src/developer/debug/zxdb/console/string_util.h"
namespace zxdb {
namespace {
constexpr int kForceTypes = 1;
constexpr int kVerboseSwitch = 2;
constexpr int kRawOutput = 3;
constexpr int kForceRefresh = 4;
constexpr int kInternalSwitch = 5;
constexpr int kForceRemoteUnwind = 6;
constexpr int kForceFrameAddresses = 7;
constexpr int kTrust = 8;
// Sets formatting options commonly used for outputting stack frames for the "frame" noun.
FormatStackOptions GetFormatStackOptions(const Command& cmd, ConsoleContext* console_context) {
auto opts =
FormatStackOptions::GetFrameOptions(cmd.target(), cmd.HasSwitch(kVerboseSwitch),
cmd.HasSwitch(kForceTypes), cmd.HasSwitch(kTrust), 4);
if (!cmd.HasSwitch(kRawOutput))
opts.pretty_stack = console_context->pretty_stack_manager();
if (cmd.HasSwitch(kForceRefresh)) {
opts.sync_options.force_update = true;
}
if (cmd.HasSwitch(kForceRemoteUnwind)) {
opts.sync_options.force_update = true;
opts.sync_options.remote_unwind = true;
}
if (cmd.HasSwitch(kForceFrameAddresses)) {
opts.frame.loc.always_show_addresses = true;
}
return opts;
}
// Frames ------------------------------------------------------------------------------------------
const char kFrameShortHelp[] = "frame / f: Select or list stack frames.";
const char kFrameUsage[] = "frame [ <id> [ <command> ... ] ]";
const char kFrameHelp[] = R"(
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
-a
--address
Includes the address of each frame.
-f
--force
Force an update to the stack when listing stack frames. This will always
reevaluate the complete call stack based on the current stop location. Use
--force-remote-unwind to unwind completely on the target.
--force-remote-unwind
Force the unwinding operation to happen from the target backend. Note that
this may result in different results than the default due to less metadata
availability on the target compared to the default option. Implies
--force.
-r
--raw
Expands frames that were collapsed by the "pretty" stack formatter.
-t
--types
Include all type information for function parameters.
--trust
Include the frame's Trust as reported by the unwinder, indicating which
unwinder implementation retrieved this frame.
-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.
pr 2 t * frame
Lists all stack frames of all threads in process 2.
)";
// Returns true if processing should stop (either a frame command or an error), false to continue
// processing to the next noun type.
bool HandleFrameNoun(ConsoleContext* console_context, const Command& cmd,
fxl::RefPtr<CommandContext>& cmd_context) {
if (!cmd.HasNoun(Noun::kFrame))
return false;
if (cmd.all_threads().empty()) {
cmd_context->ReportError(Err(ErrType::kInput, "There is no thread to have frames."));
return true;
} else if (cmd.GetNounIndex(Noun::kThread) == Command::kWildcard) {
// Let |HandleThreadNoun| handle the case where we're printing the call stack for all threads.
return false;
}
FormatStackOptions opts = GetFormatStackOptions(cmd, console_context);
if (cmd.GetNounIndex(Noun::kFrame) == Command::kNoIndex) {
// Just "frame", this lists available frames. Simple format is the default.
cmd_context->Output(FormatStack(cmd.thread(), opts));
return true;
} else if (cmd.GetNounIndex(Noun::kFrame) == Command::kWildcard) {
cmd_context->ReportError(
Err(ErrType::kInput, "frame * is not supported, did you mean thread * frame?"));
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).
FX_DCHECK(cmd.frame());
console_context->SetActiveFrameForThread(cmd.frame());
// Setting the active frame also sets the active thread and target.
console_context->SetActiveThreadForTarget(cmd.thread());
console_context->SetActiveTarget(cmd.target());
cmd_context->Output(FormatFrame(cmd.frame(), opts.frame, -1));
return true;
}
// Filters -----------------------------------------------------------------------------------------
const char kFilterShortHelp[] = "filter: Select or list process filters.";
const char kFilterUsage[] = "filter [ <id> [ <command> ... ] ]";
const char kFilterHelp[] = R"(
Selects or lists process filters. Process filters allow you to attach to
processes that spawn in the system as soon as they spawn. You can use "attach"
to create a new filter.
More info
• Create a filter with "attach <pattern>". See "help attach" for more.
• Change a filter's pattern with "filter [X] set pattern = <newvalue>"
(where [X] is the index of the filter from the "filter" command).
• Delete a filter with "filter [X] rm".
Examples
filter
Lists all filters.
filter 1
Selects filter 1 to be the active filter.
filter 3 set pattern = foo
Update filter 3 to attach to processes named "foo".
filter 4 rm
Removes filter 4.
)";
// Returns true if processing should stop (either a filter command or an error), false to continue
// processing to the next noun type.
bool HandleFilterNoun(ConsoleContext* console_context, const Command& cmd,
fxl::RefPtr<CommandContext>& cmd_context) {
if (!cmd.HasNoun(Noun::kFilter))
return false;
if (Err err = cmd.ValidateNouns({Noun::kFilter}); err.has_error()) {
cmd_context->ReportError(err);
return true;
}
if (cmd.GetNounIndex(Noun::kFilter) == Command::kNoIndex) {
// Just "filter", this lists available filters.
cmd_context->Output(FormatFilterList(console_context));
return true;
} else if (cmd.GetNounIndex(Noun::kFilter) == Command::kWildcard) {
// "filter *" doesn't make sense without a verb, which will end up calling the verb executor,
// and should never get here. Return an error.
cmd_context->ReportError(
Err(ErrType::kInput, "filter * can only be used with a corresponding verb."));
return true;
}
FX_DCHECK(cmd.filter());
console_context->SetActiveFilter(cmd.filter());
cmd_context->Output(FormatFilter(console_context, cmd.filter()));
return true;
}
// Threads -----------------------------------------------------------------------------------------
const char kThreadShortHelp[] = "thread / t: Select or list threads.";
const char kThreadUsage[] = "thread [ <id> [ <command> ... ] ]";
const char kThreadHelp[] = R"(
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. "*" is a special ID ("thread * ...")
that can be used with certain actions and "frame" to apply the operation to
all threads. Using the "*" ID does not change the active thread.
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.
thread * frame
Prints the stack frames for all threads. See also help backtrace.
)";
// Prints the thread list for the given process to the console.
void ListThreads(fxl::RefPtr<CommandContext> cmd_context, Process* process) {
if (!cmd_context->console())
return;
ConsoleContext* console_context = &cmd_context->console()->context();
std::vector<Thread*> threads = process->GetThreads();
int active_thread_id = console_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(console_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(GetCurrentRowMarker());
else
row.emplace_back();
row.push_back(std::to_string(pair.first));
row.push_back(ThreadStateToString(pair.second->GetState(), pair.second->GetBlockedReason()));
row.push_back(std::to_string(pair.second->GetKoid()));
row.push_back(pair.second->GetName());
}
const char* id_name = debug::PlatformThreadIdName(console_context->session()->platform(), false);
OutputBuffer out;
FormatTable({ColSpec(Align::kLeft), ColSpec(Align::kRight, 0, "#", 0, Syntax::kSpecial),
ColSpec(Align::kLeft, 0, "state"), ColSpec(Align::kRight, 0, id_name),
ColSpec(Align::kLeft, 0, "name")},
rows, &out);
cmd_context->Output(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(fxl::RefPtr<CommandContext>& cmd_context, Process* process) {
// Since the Process issues the callback, it's OK to capture the pointer.
process->SyncThreads([cmd_context, process]() { ListThreads(cmd_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* console_context, const Command& cmd,
fxl::RefPtr<CommandContext>& cmd_context) {
if (!cmd.HasNoun(Noun::kThread))
return false;
Process* process = cmd.target()->GetProcess();
if (!process) {
cmd_context->ReportError(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(cmd_context, process);
return true;
} else if (cmd.GetNounIndex(Noun::kThread) == Command::kWildcard) {
// If we get here, it means thread was specified with a wildcard, but no verb. No nouns
// other than "frame" makes sense in this context.
if (!cmd.HasNoun(Noun::kFrame)) {
cmd_context->ReportError(
Err(ErrType::kInput,
"thread * must be used with a verb or frame. See help thread for more."));
return true;
}
cmd_context->Output(FormatAllThreadStacks(
cmd.all_threads(), GetFormatStackOptions(cmd, console_context), cmd_context));
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).
FX_DCHECK(cmd.thread());
console_context->SetActiveThreadForTarget(cmd.thread());
// Setting the active thread also sets the active target.
console_context->SetActiveTarget(cmd.target());
cmd_context->Output(FormatThreadConcise(console_context, cmd.thread()));
return true;
}
// Processes ---------------------------------------------------------------------------------------
const char kProcessShortHelp[] = "process / pr: Select or list process contexts.";
const char kProcessUsage[] = "process [ <id> [ <command> ... ] ]";
const char kProcessHelp[] = R"(
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 "pause").
With an ID and another command following it ("process 3 pause"), 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 pa
process 2 pause
Pauses process context 2, regardless of the active one.
)";
// Returns true if processing should stop (either a thread command or an error), false to continue
// processing to the next noun type.
bool HandleProcessNoun(ConsoleContext* console_context, const Command& cmd,
fxl::RefPtr<CommandContext>& cmd_context) {
if (!cmd.HasNoun(Noun::kProcess))
return false;
if (cmd.GetNounIndex(Noun::kProcess) == Command::kNoIndex) {
// Just "process", this lists available processes.
cmd_context->Output(FormatTargetList(console_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).
FX_DCHECK(cmd.target());
console_context->SetActiveTarget(cmd.target());
cmd_context->Output(FormatTarget(console_context, cmd.target()));
return true;
}
// Global ------------------------------------------------------------------------------------------
const char kGlobalShortHelp[] = "global / gl: Global override for commands.";
const char kGlobalUsage[] = "global <command> ...";
const char kGlobalHelp[] = R"(
Alias: "gl"
The "global" noun allows explicitly scoping a command to the global scope
as opposed to a process or thread.
)";
bool HandleGlobalNoun(ConsoleContext* console_context, const Command& cmd,
fxl::RefPtr<CommandContext>& cmd_context) {
if (!cmd.HasNoun(Noun::kGlobal))
return false;
cmd_context->ReportError(
Err("\"global\" only makes sense when applied to a verb, for example \"global get\"."));
return true;
}
// Breakpoints -------------------------------------------------------------------------------------
const char kBreakpointShortHelp[] = "breakpoint / bp: Select or list breakpoints.";
const char kBreakpointUsage[] = "breakpoint [ <id> [ <command> ... ] ]";
const char kBreakpointHelp[] = R"(
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".
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.
Options
--internal
Lists the internal breakpoints. These are breakpoints set by the debugger
for executing step operations. You can not modify these, this is primarily
for debugging the debugger.
-v
--verbose
When listing breakpoints, show information on each address that the
breakpoint applies to. A symbolic breakpoint can apply to many processes
and can expand to more than one address in a process.
Other breakpoint commands
"break": Create a breakpoint.
"clear": Delete a breakpoint.
"disable": Disable a breakpoint off without deleting it.
"enable": Enable a previously-disabled breakpoint.
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* console_context, fxl::RefPtr<CommandContext>& cmd_context,
bool include_locations) {
auto breakpoints = console_context->session()->system().GetBreakpoints();
if (breakpoints.empty()) {
cmd_context->Output("No breakpoints.\n");
return;
}
// The size is normally not applicable since most breakpoints are software. Hide the size for
// clarity unless there is a hardware breakpoint.
bool include_size = false;
// Conditions are an opt-in feature, don't show this column unless the user has installed a
// conditional breakpoint.
bool include_condition = false;
for (const auto& bp : breakpoints) {
if (BreakpointSettings::TypeHasSize(bp->GetSettings().type)) {
include_size = true;
}
if (!bp->GetSettings().condition.empty()) {
include_condition = true;
}
}
int active_breakpoint_id = console_context->GetActiveBreakpointId();
// Sort by ID.
std::map<int, Breakpoint*> id_bp;
for (auto& bp : breakpoints)
id_bp[console_context->IdForBreakpoint(bp)] = bp;
std::vector<std::vector<OutputBuffer>> rows;
for (const auto& pair : id_bp) {
std::vector<OutputBuffer>& row = rows.emplace_back();
// "Current breakpoint" marker.
if (pair.first == active_breakpoint_id)
row.emplace_back(GetCurrentRowMarker());
else
row.emplace_back();
BreakpointSettings settings = pair.second->GetSettings();
auto matched_locs = pair.second->GetLocations();
row.push_back(OutputBuffer(Syntax::kSpecial, std::to_string(pair.first)));
row.emplace_back(ExecutionScopeToString(console_context, settings.scope));
row.emplace_back(BreakpointSettings::StopModeToString(settings.stop_mode));
if (settings.enabled) {
row.emplace_back("true");
} else {
row.emplace_back(Syntax::kError, "false");
}
row.emplace_back(BreakpointSettings::TypeToString(settings.type));
// This breakpoint has a conditional.
if (include_condition) {
if (!settings.condition.empty()) {
row.emplace_back(settings.condition);
} else {
row.emplace_back("");
}
}
if (include_size) {
if (BreakpointSettings::TypeHasSize(settings.type))
row.emplace_back(std::to_string(settings.byte_size));
else
row.emplace_back(Syntax::kComment, "n/a");
}
if (matched_locs.empty()) {
row.emplace_back(Syntax::kWarning, "pending");
// It's confusing to show a hit_count for pending breakpoints, which happens
// when a process is killed and locations are cleared.
row.emplace_back();
} else {
row.emplace_back(std::to_string(matched_locs.size()));
row.emplace_back(std::to_string(pair.second->GetStats().hit_count));
}
row.push_back(FormatInputLocations(settings.locations));
if (include_locations) {
for (const auto& loc : matched_locs) {
std::vector<OutputBuffer>& loc_row = rows.emplace_back();
loc_row.resize(2); // Empty columns.
Process* process = loc->GetProcess();
FormatLocationOptions opts(process->GetTarget());
opts.always_show_addresses = true; // So the disambiguation is always unique.
OutputBuffer out(GetBullet() + " ");
out.Append(FormatLocation(loc->GetLocation(), opts));
loc_row.push_back(out);
}
}
}
std::vector<ColSpec> col_specs{ColSpec(Align::kLeft),
ColSpec(Align::kRight, 0, "#", 0, Syntax::kSpecial),
ColSpec(Align::kLeft, 0, ClientSettings::Breakpoint::kScope),
ColSpec(Align::kLeft, 0, ClientSettings::Breakpoint::kStopMode),
ColSpec(Align::kLeft, 0, ClientSettings::Breakpoint::kEnabled),
ColSpec(Align::kLeft, 0, ClientSettings::Breakpoint::kType)};
if (include_condition)
col_specs.emplace_back(Align::kLeft, 0, "Condition");
if (include_size)
col_specs.emplace_back(Align::kRight, 0, ClientSettings::Breakpoint::kSize);
col_specs.emplace_back(Align::kRight, 0, "#addrs");
col_specs.emplace_back(Align::kRight, 0, "hit-count");
col_specs.emplace_back(Align::kLeft, 0, ClientSettings::Breakpoint::kLocation);
OutputBuffer out;
FormatTable(col_specs, rows, &out);
cmd_context->Output(out);
}
void ListInternalBreakpoints(ConsoleContext* console_context,
fxl::RefPtr<CommandContext>& cmd_context) {
std::vector<Breakpoint*> breakpoints =
console_context->session()->system().GetInternalBreakpoints();
if (breakpoints.empty()) {
cmd_context->Output("No internal breakpoints.\n");
return;
}
OutputBuffer out;
for (const Breakpoint* bp : breakpoints) {
BreakpointSettings settings = bp->GetSettings();
out.Append(FormatInputLocations(settings.locations));
out.Append("\n");
auto matched_locs = bp->GetLocations();
for (const auto& loc : matched_locs) {
Process* process = loc->GetProcess();
FormatLocationOptions opts(process->GetTarget());
opts.always_show_addresses = true;
out.Append(" " + GetBullet() + " ");
out.Append(FormatLocation(loc->GetLocation(), opts));
out.Append("\n");
}
}
cmd_context->Output(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* console_context, const Command& cmd,
fxl::RefPtr<CommandContext>& cmd_context) {
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.
if (Err err = cmd.ValidateNouns({Noun::kBreakpoint}); err.has_error()) {
cmd_context->ReportError(err);
return true;
}
if (cmd.GetNounIndex(Noun::kBreakpoint) == Command::kNoIndex) {
// Just "breakpoint", this lists available breakpoints. The verbose switch expands each
// individual breakpoint location.
if (cmd.HasSwitch(kInternalSwitch)) {
ListInternalBreakpoints(console_context, cmd_context);
} else {
bool include_locations = cmd.HasSwitch(kVerboseSwitch);
ListBreakpoints(console_context, cmd_context, include_locations);
}
return true;
} else if (cmd.GetNounIndex(Noun::kBreakpoint) == Command::kWildcard) {
// "breakpoint *" doesn't make sense without a verb, which will end up calling the verb
// executor, and should never get here. Return an error.
cmd_context->ReportError(
Err(ErrType::kInput, "breakpoint * can only be used with a corresponding verb."));
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).
FX_DCHECK(cmd.breakpoint());
console_context->SetActiveBreakpoint(cmd.breakpoint());
cmd_context->Output(FormatBreakpoint(console_context, cmd.breakpoint(), true));
return true;
}
// Symbol Servers ----------------------------------------------------------------------------------
const char kSymServerShortHelp[] = "sym-server: Select or list symbol servers.";
const char kSymServerUsage[] = "sym-server [ <id> [ <command> ... ] ]";
const char kSymServerHelp[] = R"(
Selects or lists symbol servers.
By itself, "sym-server" will list all symbol servers with their IDs.
With an ID following it ("sym-server 3"), selects that symbol server as the
current active symbol server. This symbol server will apply by default for
subsequent symbol server commands (like "auth" or "rm").
With an ID and another command following it ("sym-server 2 auth"), applys the
command to that symbol server.
Examples
sym-server
Lists all symbol servers.
sym-server 2
Sets symbol server 2 as the active one.
sym-server 2 auth
Authenticates with symbol server 2.
)";
OutputBuffer SymbolServerStateToColorString(SymbolServer::State state) {
switch (state) {
case SymbolServer::State::kInitializing:
return OutputBuffer(Syntax::kComment, "Initializing");
case SymbolServer::State::kAuth:
return OutputBuffer(Syntax::kHeading, "Authenticating");
case SymbolServer::State::kBusy:
return OutputBuffer(Syntax::kComment, "Busy");
case SymbolServer::State::kReady:
return OutputBuffer(Syntax::kHeading, "Ready");
case SymbolServer::State::kUnreachable:
return OutputBuffer(Syntax::kError, "Unreachable");
}
}
void ListSymbolServers(ConsoleContext* console_context, fxl::RefPtr<CommandContext>& cmd_context) {
std::vector<SymbolServer*> symbol_servers =
console_context->session()->system().GetSymbolServers();
int active_symbol_server_id = console_context->GetActiveSymbolServerId();
// Sort by ID.
std::vector<std::pair<int, SymbolServer*>> id_symbol_servers;
for (SymbolServer* symbol_server : symbol_servers) {
id_symbol_servers.push_back(
std::make_pair(console_context->IdForSymbolServer(symbol_server), symbol_server));
}
std::sort(id_symbol_servers.begin(), id_symbol_servers.end());
std::vector<std::vector<OutputBuffer>> rows;
for (const auto& [id, server] : id_symbol_servers) {
rows.emplace_back();
std::vector<OutputBuffer>& row = rows.back();
// "Current symbol_server" marker.
if (id == active_symbol_server_id)
row.emplace_back(GetCurrentRowMarker());
else
row.emplace_back();
row.emplace_back(std::to_string(id));
row.emplace_back(server->name());
row.emplace_back(SymbolServerStateToColorString(server->state()));
if (server->error_log().empty()) {
continue;
}
rows.emplace_back();
std::vector<OutputBuffer>& line = rows.back();
line.emplace_back("");
line.emplace_back("");
line.emplace_back(Syntax::kError, server->error_log().back());
}
OutputBuffer out;
FormatTable({ColSpec(Align::kLeft), ColSpec(Align::kRight, 0, "#", 0, Syntax::kSpecial),
ColSpec(Align::kLeft, 0, "URL"), ColSpec(Align::kLeft, 0, "State")},
rows, &out);
cmd_context->Output(out);
}
bool HandleSymbolServerNoun(ConsoleContext* console_context, const Command& cmd,
fxl::RefPtr<CommandContext>& cmd_context) {
if (!cmd.HasNoun(Noun::kSymServer))
return false;
// sym-server only makes sense by itself. It doesn't make sense with any other nouns.
if (Err err = cmd.ValidateNouns({Noun::kSymServer}); err.has_error()) {
cmd_context->ReportError(err);
return true;
}
if (cmd.GetNounIndex(Noun::kSymServer) == Command::kNoIndex) {
// Just "breakpoint", this lists available breakpoints.
ListSymbolServers(console_context, cmd_context);
return true;
}
// Explicit index provided, this switches the current context. The symbol server 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).
FX_DCHECK(cmd.sym_server());
console_context->SetActiveSymbolServer(cmd.sym_server());
OutputBuffer out;
out.Append(cmd.sym_server()->name() + " - ");
out.Append(SymbolServerStateToColorString(cmd.sym_server()->state()));
out.Append("\n");
auto& error_log = cmd.sym_server()->error_log();
auto iter = error_log.begin();
if (error_log.size() > 10) {
iter += error_log.size() - 10;
out.Append(" ... " + std::to_string(error_log.size() - 10) + " more ...\n");
}
for (; iter != error_log.end(); iter++) {
out.Append(" " + *iter + "\n", TextForegroundColor::kRed);
}
Console::get()->Output(out);
return true;
}
} // namespace
NounRecord::NounRecord() = default;
NounRecord::NounRecord(std::initializer_list<std::string> aliases, const char* short_help,
const char* usage, const char* help, CommandGroup command_group)
: aliases(aliases),
short_help(short_help),
usage(usage),
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.
FX_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 NounRecord* NounToRecord(Noun n) {
const auto& nouns = GetNouns();
auto found = nouns.find(n);
if (found == nouns.end())
return nullptr;
return &found->second;
}
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;
}
void ExecuteNoun(const Command& cmd, fxl::RefPtr<CommandContext> cmd_context) {
// Extract a non-weak ConsoleContext pointer. The noun implementations all deal with the console
// context (because they're listing and modifying the default item) and are almost all
// synchronous. This prevents those implementations from having to deal with the weak pointer in
// the CommandContext themselves.
if (!cmd_context->console())
return;
ConsoleContext* console_context = &cmd_context->console()->context();
if (HandleBreakpointNoun(console_context, cmd, cmd_context))
return;
if (HandleFilterNoun(console_context, cmd, cmd_context))
return;
// Work backwards in specificity (frame -> thread -> process).
if (HandleFrameNoun(console_context, cmd, cmd_context))
return;
if (HandleThreadNoun(console_context, cmd, cmd_context))
return;
if (HandleProcessNoun(console_context, cmd, cmd_context))
return;
if (HandleSymbolServerNoun(console_context, cmd, cmd_context))
return;
if (HandleGlobalNoun(console_context, cmd, cmd_context))
return;
}
void AppendNouns(std::map<Noun, NounRecord>* nouns) {
// If non-kNone, the "command groups" on the noun will cause the help for that noun to additionall
// appear under that section (people expect the "thread" command to appear in the process
// section).
(*nouns)[Noun::kBreakpoint] =
NounRecord({"breakpoint", "bp"}, kBreakpointShortHelp, kBreakpointUsage, kBreakpointHelp,
CommandGroup::kBreakpoint);
(*nouns)[Noun::kFrame] =
NounRecord({"frame", "f"}, kFrameShortHelp, kFrameUsage, kFrameHelp, CommandGroup::kQuery);
(*nouns)[Noun::kThread] = NounRecord({"thread", "t"}, kThreadShortHelp, kThreadUsage, kThreadHelp,
CommandGroup::kProcess);
(*nouns)[Noun::kProcess] = NounRecord({"process", "pr"}, kProcessShortHelp, kProcessUsage,
kProcessHelp, CommandGroup::kProcess);
(*nouns)[Noun::kGlobal] = NounRecord({"global", "gl"}, kGlobalShortHelp, kGlobalUsage,
kGlobalHelp, CommandGroup::kNone);
(*nouns)[Noun::kSymServer] = NounRecord({"sym-server"}, kSymServerShortHelp, kSymServerUsage,
kSymServerHelp, CommandGroup::kSymbol);
(*nouns)[Noun::kFilter] =
NounRecord({"filter"}, kFilterShortHelp, kFilterUsage, kFilterHelp, CommandGroup::kProcess);
}
const std::vector<SwitchRecord>& GetNounSwitches() {
static std::vector<SwitchRecord> switches;
if (switches.empty()) {
switches.emplace_back(kRawOutput, false, "raw", 'r');
switches.emplace_back(kForceTypes, false, "types", 't');
switches.emplace_back(kVerboseSwitch, false, "verbose", 'v');
switches.emplace_back(kForceRefresh, false, "force", 'f');
switches.emplace_back(kInternalSwitch, false, "internal");
switches.emplace_back(kForceRemoteUnwind, false, "force-remote-unwind");
switches.emplace_back(kForceFrameAddresses, false, "address", 'a');
switches.emplace_back(kTrust, false, "trust");
}
return switches;
}
} // namespace zxdb