| // 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 <inttypes.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/filter.h" |
| #include "src/developer/debug/zxdb/client/frame.h" |
| #include "src/developer/debug/zxdb/client/job.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/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_job.h" |
| #include "src/developer/debug/zxdb/console/format_location.h" |
| #include "src/developer/debug/zxdb/console/format_node_console.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/output_buffer.h" |
| #include "src/developer/debug/zxdb/console/string_util.h" |
| #include "src/developer/debug/zxdb/symbols/location.h" |
| #include "src/lib/fxl/strings/string_printf.h" |
| |
| namespace zxdb { |
| |
| namespace { |
| |
| constexpr int kForceTypes = 1; |
| constexpr int kVerboseSwitch = 2; |
| constexpr int kRawOutput = 3; |
| |
| // 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 |
| |
| -r |
| --raw |
| Expands frames that were collapsed by the "pretty" stack formatter. |
| |
| -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 next 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; |
| } |
| |
| FormatStackOptions opts; |
| |
| if (!cmd.HasSwitch(kRawOutput)) |
| opts.pretty_stack = context->pretty_stack_manager(); |
| |
| opts.frame.loc = FormatLocationOptions(cmd.target()); |
| opts.frame.loc.func.name.elide_templates = true; |
| opts.frame.loc.func.name.bold_last = true; |
| opts.frame.loc.func.params = cmd.HasSwitch(kForceTypes) ? FormatFunctionNameOptions::kParamTypes |
| : FormatFunctionNameOptions::kElideParams; |
| |
| opts.frame.variable.verbosity = cmd.HasSwitch(kForceTypes) |
| ? ConsoleFormatOptions::Verbosity::kAllTypes |
| : ConsoleFormatOptions::Verbosity::kMinimal; |
| opts.frame.variable.pointer_expand_depth = 1; |
| opts.frame.variable.max_depth = 4; |
| |
| if (cmd.GetNounIndex(Noun::kFrame) == Command::kNoIndex) { |
| // Just "frame", this lists available frames. |
| opts.frame.detail = FormatFrameOptions::kSimple; |
| if (cmd.HasSwitch(kVerboseSwitch)) { |
| opts.frame.loc.func.name.elide_templates = false; |
| opts.frame.loc.func.params = FormatFunctionNameOptions::kParamTypes; |
| } |
| |
| // Always force update the stack. Various things can have changed and when the user requests |
| // a stack we want to be sure things are correct. |
| Console::get()->Output(FormatStack(cmd.thread(), true, opts)); |
| 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()); |
| context->SetActiveFrameForThread(cmd.frame()); |
| // Setting the active frame also sets the active thread and target. |
| context->SetActiveThreadForTarget(cmd.thread()); |
| context->SetActiveTarget(cmd.target()); |
| |
| Console::get()->Output(FormatFrame(cmd.frame(), opts.frame)); |
| return true; |
| } |
| |
| // Filters ----------------------------------------------------------------------------------------- |
| |
| const char kFilterShortHelp[] = "filter: Select or list process filters."; |
| const char kFilterHelp[] = |
| R"(filter [ <id> [ <command> ... ] ] |
| |
| Selects or lists process filters. Process filters allow you to attach to |
| processes that spawn under a job as soon as they spawn. You can use "attach" |
| to create a new filter. |
| |
| The debugger watches for processes launched from within all jobs currently |
| attached (see "help job") and applies the relevant filters. Filters can either |
| be global (the default, applying to all jobs the debugger is attached to) or |
| apply only to specific jobs. |
| |
| 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. |
| |
| job 3 filter |
| List all filters on job 3. |
| |
| 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* context, const Command& cmd, Err* err) { |
| if (!cmd.HasNoun(Noun::kFilter)) |
| return false; |
| |
| *err = cmd.ValidateNouns({Noun::kJob, Noun::kFilter}); |
| if (err->has_error()) |
| return true; |
| |
| if (cmd.GetNounIndex(Noun::kFilter) == Command::kNoIndex) { |
| // Just "filter", this lists available filters. If a job is given, it lists only filters |
| // for that job. Otherwise it lists all filters. |
| if (cmd.HasNoun(Noun::kJob)) { |
| Console::get()->Output(FormatFilterList(context, cmd.job())); |
| } else { |
| Console::get()->Output(FormatFilterList(context)); |
| } |
| return true; |
| } |
| |
| FX_DCHECK(cmd.filter()); |
| context->SetActiveFilter(cmd.filter()); |
| Console::get()->Output(FormatFilter(context, cmd.filter())); |
| 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(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()); |
| } |
| |
| 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(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). |
| FX_DCHECK(cmd.thread()); |
| context->SetActiveThreadForTarget(cmd.thread()); |
| // Setting the active thread also sets the active target. |
| context->SetActiveTarget(cmd.target()); |
| Console::get()->Output(FormatThread(context, cmd.thread())); |
| return true; |
| } |
| |
| // Jobs -------------------------------------------------------------------------------------------- |
| |
| const char kJobShortHelp[] = "job / j: Select or list jobs."; |
| const char kJobHelp[] = |
| R"(job [ <id> [ <command> ... ] ] |
| |
| Alias: "j" |
| |
| Selects or lists jobs. A job is attached to a Zircon job (a node in the |
| process tree) and watches for processes launched inside of it. |
| See "help attach" on how to automatically attach to these processes. |
| |
| By itself, "job" will list available jobs with their IDs. New jobs 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 as the current active |
| job. 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 for that command only. This allows attaching, filtering, etc. |
| regardless of which is the active one. |
| |
| Examples |
| |
| j |
| job |
| Lists all jobs. |
| |
| j 2 |
| job 2 |
| Sets job 2 as the active one. |
| |
| j 2 r |
| job 2 attach |
| Attach to job 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 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 the jobs. |
| Console::get()->Output(FormatJobList(context)); |
| return true; |
| } |
| |
| FX_DCHECK(cmd.job()); |
| context->SetActiveJob(cmd.job()); |
| Console::get()->Output(FormatJob(context, cmd.job())); |
| 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. |
| )"; |
| |
| // 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* 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. |
| Console::get()->Output(FormatTargetList(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()); |
| context->SetActiveTarget(cmd.target()); |
| Console::get()->Output(FormatTarget(context, cmd.target())); |
| return true; |
| } |
| |
| // Global ------------------------------------------------------------------------------------------ |
| |
| const char kGlobalShortHelp[] = "global / gl: Global override for commands."; |
| const char kGlobalHelp[] = |
| R"("global <command> ... |
| |
| Alias: "gl" |
| |
| The "global" noun allows explicitly scoping a command to the global scope |
| as opposed to a process or thread. |
| )"; |
| |
| bool HandleGlobalNoun(ConsoleContext* context, const Command& cmd, Err* err) { |
| if (!cmd.HasNoun(Noun::kGlobal)) |
| return false; |
| |
| Console::get()->Output( |
| "\"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 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". |
| |
| 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 |
| |
| -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* context, bool include_locations) { |
| auto breakpoints = context->session()->system().GetBreakpoints(); |
| if (breakpoints.empty()) { |
| Console::get()->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; |
| // The hit_mult is normally 1. Hide it unless there's a breakpoint with a different value. |
| bool include_hit_mult = false; |
| for (const auto& bp : breakpoints) { |
| if (BreakpointSettings::TypeHasSize(bp->GetSettings().type)) { |
| include_size = true; |
| } |
| if (bp->GetSettings().hit_mult != 1) { |
| include_hit_mult = true; |
| } |
| } |
| |
| 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<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(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)); |
| |
| 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 (include_hit_mult) { |
| row.emplace_back(std::to_string(settings.hit_mult)); |
| } |
| |
| 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_size) |
| col_specs.emplace_back(Align::kRight, 0, ClientSettings::Breakpoint::kSize); |
| if (include_hit_mult) |
| col_specs.emplace_back(Align::kRight, 0, ClientSettings::Breakpoint::kHitMult); |
| 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); |
| Console::get()->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* 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. The verbose switch expands each |
| // individual breakpoint location. |
| bool include_locations = cmd.HasSwitch(kVerboseSwitch); |
| ListBreakpoints(context, include_locations); |
| 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()); |
| context->SetActiveBreakpoint(cmd.breakpoint()); |
| Console::get()->Output(FormatBreakpoint(context, cmd.breakpoint(), true)); |
| return true; |
| } |
| |
| // Symbol Servers ---------------------------------------------------------------------------------- |
| |
| const char kSymServerShortHelp[] = "sym-server: Select or list symbol servers."; |
| const char kSymServerHelp[] = |
| R"(sym-server [ <id> [ <command> ... ] ] |
| |
| 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* context) { |
| std::vector<SymbolServer*> symbol_servers = context->session()->system().GetSymbolServers(); |
| int active_symbol_server_id = 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(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); |
| Console::get()->Output(out); |
| } |
| |
| bool HandleSymbolServerNoun(ConsoleContext* context, const Command& cmd, Err* err) { |
| if (!cmd.HasNoun(Noun::kSymServer)) |
| return false; |
| |
| // sym-server only makes sense by itself. It doesn't make sense with any other nouns. |
| *err = cmd.ValidateNouns({Noun::kSymServer}); |
| if (err->has_error()) |
| return true; |
| |
| if (cmd.GetNounIndex(Noun::kSymServer) == Command::kNoIndex) { |
| // Just "breakpoint", this lists available breakpoints. |
| ListSymbolServers(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()); |
| 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* 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. |
| 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 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; |
| if (HandleFilterNoun(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; |
| if (HandleSymbolServerNoun(context, cmd, &result)) |
| return result; |
| if (HandleGlobalNoun(context, cmd, &result)) |
| return result; |
| |
| return result; |
| } |
| |
| 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, |
| 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::kGlobal] = |
| NounRecord({"global", "gl"}, kGlobalShortHelp, kGlobalHelp, CommandGroup::kNone); |
| (*nouns)[Noun::kSymServer] = |
| NounRecord({"sym-server"}, kSymServerShortHelp, kSymServerHelp, CommandGroup::kSymbol); |
| (*nouns)[Noun::kJob] = NounRecord({"job", "j"}, kJobShortHelp, kJobHelp, CommandGroup::kJob); |
| (*nouns)[Noun::kFilter] = |
| NounRecord({"filter"}, kFilterShortHelp, kFilterHelp, CommandGroup::kJob); |
| } |
| |
| 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'); |
| } |
| return switches; |
| } |
| |
| } // namespace zxdb |