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