| // 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_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.show_params = cmd.HasSwitch(kForceTypes); | |
| opts.frame.loc.func.name.elide_templates = true; | |
| opts.frame.loc.func.name.bold_last = true; | |
| opts.frame.loc.func.params = 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. | |
| )"; | |
| void ListFilters(ConsoleContext* context, Job* job) { | |
| int active_filter_id = context->GetActiveFilterId(); | |
| auto filters = context->session()->system().GetFilters(); | |
| std::vector<std::vector<std::string>> rows; | |
| for (auto& filter : filters) { | |
| if (job && filter->job() && filter->job() != job) { | |
| continue; | |
| } | |
| auto id = context->IdForFilter(filter); | |
| std::vector<std::string>& row = rows.emplace_back(); | |
| // "Current thread" marker. | |
| if (id == active_filter_id) | |
| row.push_back(GetCurrentRowMarker()); | |
| else | |
| row.emplace_back(); | |
| row.push_back(std::to_string(id)); | |
| row.push_back(filter->pattern()); | |
| if (filter->job()) { | |
| auto job_id = context->IdForJob(filter->job()); | |
| row.push_back(std::to_string(job_id)); | |
| } else { | |
| row.push_back("*"); | |
| } | |
| } | |
| OutputBuffer out; | |
| if (rows.empty()) { | |
| if (job) | |
| out.Append(fxl::StringPrintf("No filters for job %d.\n", context->IdForJob(job))); | |
| else | |
| out.Append("No filters.\n"); | |
| } else { | |
| if (job) | |
| out.Append(fxl::StringPrintf("Filters for job %d only:\n", context->IdForJob(job))); | |
| FormatTable({ColSpec(Align::kLeft), ColSpec(Align::kRight, 0, "#", 0, Syntax::kSpecial), | |
| ColSpec(Align::kLeft, 0, "pattern"), ColSpec(Align::kRight, 0, "job")}, | |
| rows, &out); | |
| } | |
| Console::get()->Output(out); | |
| } | |
| // 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)) | |
| ListFilters(context, cmd.job()); | |
| else | |
| ListFilters(context, nullptr); | |
| 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)); | |
| row.emplace_back(BoolToString(settings.enabled)); | |
| 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 |