| // 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/command_utils.h" |
| |
| #include <ctype.h> |
| #include <inttypes.h> |
| #include <lib/syslog/cpp/macros.h> |
| #include <stdio.h> |
| |
| #include <limits> |
| |
| #include "src/developer/debug/zxdb/client/breakpoint.h" |
| #include "src/developer/debug/zxdb/client/breakpoint_location.h" |
| #include "src/developer/debug/zxdb/client/client_eval_context_impl.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/source_file_provider_impl.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/common/string_util.h" |
| #include "src/developer/debug/zxdb/console/command.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_context.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_name.h" |
| #include "src/developer/debug/zxdb/console/format_node_console.h" |
| #include "src/developer/debug/zxdb/console/format_target.h" |
| #include "src/developer/debug/zxdb/console/input_location_parser.h" |
| #include "src/developer/debug/zxdb/console/output_buffer.h" |
| #include "src/developer/debug/zxdb/console/string_util.h" |
| #include "src/developer/debug/zxdb/expr/eval_context_impl.h" |
| #include "src/developer/debug/zxdb/expr/expr.h" |
| #include "src/developer/debug/zxdb/expr/expr_parser.h" |
| #include "src/developer/debug/zxdb/expr/expr_value.h" |
| #include "src/developer/debug/zxdb/expr/format.h" |
| #include "src/developer/debug/zxdb/expr/number_parser.h" |
| #include "src/developer/debug/zxdb/expr/return_value.h" |
| #include "src/developer/debug/zxdb/symbols/base_type.h" |
| #include "src/developer/debug/zxdb/symbols/elf_symbol.h" |
| #include "src/developer/debug/zxdb/symbols/function.h" |
| #include "src/developer/debug/zxdb/symbols/identifier.h" |
| #include "src/developer/debug/zxdb/symbols/location.h" |
| #include "src/developer/debug/zxdb/symbols/modified_type.h" |
| #include "src/developer/debug/zxdb/symbols/process_symbols.h" |
| #include "src/developer/debug/zxdb/symbols/symbol_utils.h" |
| #include "src/developer/debug/zxdb/symbols/target_symbols.h" |
| #include "src/developer/debug/zxdb/symbols/variable.h" |
| #include "src/lib/fxl/strings/string_printf.h" |
| #include "src/lib/fxl/strings/trim.h" |
| |
| namespace zxdb { |
| |
| Err AssertRunningTarget(ConsoleContext* context, const char* command_name, Target* target) { |
| Target::State state = target->GetState(); |
| if (state == Target::State::kRunning) |
| return Err(); |
| return Err(ErrType::kInput, |
| fxl::StringPrintf("%s requires a running process but process %d is %s.", command_name, |
| context->IdForTarget(target), TargetStateToString(state))); |
| } |
| |
| Err AssertStoppedThreadWithFrameCommand(ConsoleContext* context, const Command& cmd, |
| const char* command_name, bool validate_nouns) { |
| if (validate_nouns) { |
| if (Err err = cmd.ValidateNouns({Noun::kProcess, Noun::kThread, Noun::kFrame}); err.has_error()) |
| return err; |
| } |
| |
| if (!cmd.thread()) { |
| return Err("\"%s\" requires a thread but there is no current thread.", command_name); |
| } |
| if (!cmd.thread()->CurrentStopSupportsFrames()) { |
| return Err( |
| "\"%s\" requires a suspended thread but thread %d is %s.\n" |
| "To view and sync thread state with the remote system, type " |
| "\"thread\".\nOr type \"pause\" to pause a running thread.", |
| command_name, context->IdForThread(cmd.thread()), |
| ThreadStateToString(cmd.thread()->GetState(), cmd.thread()->GetBlockedReason()).c_str()); |
| } else if (!cmd.frame()) { |
| // Theoretically this shouldn't happen: if the thread is in a proper blocked state it should |
| // have a frame. But we check this because callers will crash if the frame is not valid after |
| // a successful return. |
| FX_NOTREACHED() << "Thread has no frame but its state is " |
| << ThreadStateToString(cmd.thread()->GetState(), |
| cmd.thread()->GetBlockedReason()); |
| return Err("Thread has no frame."); |
| } |
| |
| return Err(); |
| } |
| |
| size_t CheckHexPrefix(const std::string& s) { |
| if (s.size() >= 2u && s[0] == '0' && (s[1] == 'x' || s[1] == 'X')) |
| return 2u; |
| return 0u; |
| } |
| |
| Err StringToInt(const std::string& s, int* out) { |
| int64_t value64; |
| Err err = StringToInt64(s, &value64); |
| if (err.has_error()) |
| return err; |
| |
| // Range check it can be stored in an int. |
| if (value64 < static_cast<int64_t>(std::numeric_limits<int>::min()) || |
| value64 > static_cast<int64_t>(std::numeric_limits<int>::max())) |
| return Err("This value is too large for an integer."); |
| |
| *out = static_cast<int>(value64); |
| return Err(); |
| } |
| |
| Err StringToInt64(const std::string& s, int64_t* out) { |
| *out = 0; |
| |
| // StringToNumber expects pre-trimmed input. |
| std::string trimmed(fxl::TrimString(s, " ")); |
| |
| ErrOrValue number_value = StringToNumber(ExprLanguage::kC, trimmed); |
| if (number_value.has_error()) |
| return number_value.err(); |
| |
| // Be careful to read the number out in its original sign-edness. |
| if (number_value.value().GetBaseType() == BaseType::kBaseTypeUnsigned) { |
| uint64_t u64; |
| Err err = number_value.value().PromoteTo64(&u64); |
| if (err.has_error()) |
| return err; |
| |
| // Range-check that the unsigned value can be put in a signed. |
| if (u64 > static_cast<uint64_t>(std::numeric_limits<int64_t>::max())) |
| return Err("This value is too large."); |
| *out = static_cast<int64_t>(u64); |
| return Err(); |
| } |
| |
| // Expect everything else to be a signed number. |
| if (number_value.value().GetBaseType() != BaseType::kBaseTypeSigned) |
| return Err("This value is not the correct type."); |
| return number_value.value().PromoteTo64(out); |
| } |
| |
| Err StringToUint32(const std::string& s, uint32_t* out) { |
| // Re-uses StringToUint64's and just size-checks the output. |
| uint64_t value64; |
| Err err = StringToUint64(s, &value64); |
| if (err.has_error()) |
| return err; |
| |
| if (value64 > static_cast<uint64_t>(std::numeric_limits<uint32_t>::max())) { |
| return Err("Expected 32-bit unsigned value, but %s is too large.", s.c_str()); |
| } |
| *out = static_cast<uint32_t>(value64); |
| return Err(); |
| } |
| |
| Err StringToUint64(const std::string& s, uint64_t* out) { |
| *out = 0; |
| |
| // StringToNumber expects pre-trimmed input. |
| std::string trimmed(fxl::TrimString(s, " ")); |
| |
| ErrOrValue number_value = StringToNumber(ExprLanguage::kC, trimmed); |
| if (number_value.has_error()) |
| return number_value.err(); |
| |
| // Be careful to read the number out in its original sign-edness. |
| if (number_value.value().GetBaseType() == BaseType::kBaseTypeSigned) { |
| int64_t s64; |
| Err err = number_value.value().PromoteTo64(&s64); |
| if (err.has_error()) |
| return err; |
| |
| // Range-check that the signed value can be put in an unsigned. |
| if (s64 < 0) |
| return Err("This value can not be negative."); |
| *out = static_cast<uint64_t>(s64); |
| return Err(); |
| } |
| |
| // Expect everything else to be an unsigned number. |
| if (number_value.value().GetBaseType() != BaseType::kBaseTypeUnsigned) |
| return Err("This value is not the correct type."); |
| return number_value.value().PromoteTo64(out); |
| } |
| |
| Err ReadUint64Arg(const Command& cmd, size_t arg_index, const char* param_desc, uint64_t* out) { |
| if (cmd.args().size() <= arg_index) { |
| return Err(ErrType::kInput, |
| fxl::StringPrintf("Not enough arguments when reading the %s.", param_desc)); |
| } |
| Err result = StringToUint64(cmd.args()[arg_index], out); |
| if (result.has_error()) { |
| return Err(ErrType::kInput, fxl::StringPrintf("Invalid number \"%s\" when reading the %s.", |
| cmd.args()[arg_index].c_str(), param_desc)); |
| } |
| return Err(); |
| } |
| |
| std::string ThreadStateToString(debug_ipc::ThreadRecord::State state, |
| debug_ipc::ThreadRecord::BlockedReason blocked_reason) { |
| // Blocked can have many cases, so we handle it separately. |
| if (state != debug_ipc::ThreadRecord::State::kBlocked) |
| return debug_ipc::ThreadRecord::StateToString(state); |
| |
| FX_DCHECK(blocked_reason != debug_ipc::ThreadRecord::BlockedReason::kNotBlocked) |
| << "A blocked thread has to have a valid reason."; |
| return fxl::StringPrintf("Blocked (%s)", |
| debug_ipc::ThreadRecord::BlockedReasonToString(blocked_reason)); |
| } |
| |
| std::string ExecutionScopeToString(const ConsoleContext* context, const ExecutionScope& scope) { |
| switch (scope.type()) { |
| case ExecutionScope::kSystem: |
| return "global"; |
| case ExecutionScope::kTarget: |
| if (scope.target()) |
| return fxl::StringPrintf("pr %d", context->IdForTarget(scope.target())); |
| return "<Deleted process>"; |
| case ExecutionScope::kThread: |
| if (scope.thread()) { |
| return fxl::StringPrintf("pr %d t %d", context->IdForTarget(scope.target()), |
| context->IdForThread(scope.thread())); |
| } |
| return "<Deleted thread>"; |
| } |
| FX_NOTREACHED(); |
| return std::string(); |
| } |
| |
| ExecutionScope ExecutionScopeForCommand(const Command& cmd) { |
| if (cmd.HasNoun(Noun::kThread)) |
| return ExecutionScope(cmd.thread()); // Thread context given explicitly. |
| if (cmd.HasNoun(Noun::kProcess)) |
| return ExecutionScope(cmd.target()); // Target context given explicitly. |
| |
| return ExecutionScope(); // Everything else becomes global scope. |
| } |
| |
| Err ResolveBreakpointsForModification(const Command& cmd, const char* command_name, |
| std::vector<Breakpoint*>* output) { |
| output->clear(); |
| |
| if (cmd.args().size() > 1) { |
| return Err(ErrType::kInput, |
| "Expecting zero or one arg for the location.\n" |
| "Formats: <function>, <file>:<line#>, <line#>, or 0x<address>"); |
| } |
| |
| if (cmd.args().size() == 1) { |
| // "bp <index> clear <location>" is pointless. |
| Err err = cmd.ValidateNouns({}); |
| if (err.has_error()) |
| return err; |
| |
| // No need to resolve the location here because pending breakpoints only have input_locations. |
| // As a result, if a user has breakpoints on both main and $main, "clear main" will only clear |
| // the first breakpoint. |
| std::vector<InputLocation> input_locations; |
| if (Err err = ParseLocalInputLocation(cmd.frame(), cmd.args()[0], &input_locations); |
| err.has_error()) |
| return err; |
| |
| ConsoleContext* context = &Console::get()->context(); |
| std::vector<Breakpoint*> breakpoints = context->session()->system().GetBreakpoints(); |
| |
| for (Breakpoint* breakpoint : breakpoints) { |
| // We compare the input_locations vector directly, in hopes that the same input will |
| // resolve to the same order. |
| if (input_locations == breakpoint->GetSettings().locations) { |
| output->push_back(breakpoint); |
| } |
| } |
| |
| if (output->size() == 0) { |
| auto msg = fxl::StringPrintf("\"%s\" matches zero breakpoints.", cmd.args()[0].c_str()); |
| if (cmd.args()[0].size() && isdigit(cmd.args()[0][0])) |
| msg += fxl::StringPrintf(" Maybe you want to use \"bp %s %s\"?", cmd.args()[0].c_str(), |
| command_name); |
| return Err(msg); |
| } |
| return Err(); |
| } |
| |
| // When cmd.args().size() == 0, use the command's breakpoint context. |
| Err err = cmd.ValidateNouns({Noun::kBreakpoint}); |
| if (err.has_error()) |
| return err; |
| |
| if (!cmd.breakpoint()) { |
| return Err( |
| fxl::StringPrintf("There is no active breakpoint and no breakpoint or location was given.\n" |
| "Use \"bp <index> %s\" or \"%s <location>\" to specify one.\n", |
| command_name, command_name)); |
| } |
| |
| output->push_back(cmd.breakpoint()); |
| return Err(); |
| } |
| |
| OutputBuffer FormatThread(const ConsoleContext* context, const Thread* thread) { |
| OutputBuffer out("Thread "); |
| out.Append(Syntax::kSpecial, std::to_string(context->IdForThread(thread))); |
| |
| out.Append(Syntax::kVariable, " state"); |
| out.Append("=" + FormatConsoleString( |
| ThreadStateToString(thread->GetState(), thread->GetBlockedReason()))); |
| |
| out.Append(Syntax::kVariable, " koid"); |
| out.Append("=" + std::to_string(thread->GetKoid())); |
| |
| out.Append(Syntax::kVariable, " name"); |
| out.Append("=" + FormatConsoleString(thread->GetName())); |
| |
| return out; |
| } |
| |
| OutputBuffer FormatBreakpoint(const ConsoleContext* context, const Breakpoint* breakpoint, |
| bool show_context) { |
| BreakpointSettings settings = breakpoint->GetSettings(); |
| |
| OutputBuffer result("Breakpoint "); |
| result.Append(Syntax::kSpecial, std::to_string(context->IdForBreakpoint(breakpoint)) + " "); |
| |
| // Most breakpoints are simple global software breakpoints. To keep things easier to follow, |
| // only show values that aren't the default. |
| if (settings.scope.type() != ExecutionScope::kSystem) { |
| result.Append(Syntax::kVariable, ClientSettings::Breakpoint::kScope); |
| result.Append(std::string("=\"") + ExecutionScopeToString(context, settings.scope) + "\" "); |
| } |
| |
| if (settings.stop_mode != BreakpointSettings::StopMode::kAll) { |
| result.Append(Syntax::kVariable, ClientSettings::Breakpoint::kStopMode); |
| result.Append(std::string("=") + BreakpointSettings::StopModeToString(settings.stop_mode) + |
| " "); |
| } |
| |
| if (!settings.enabled) { |
| result.Append(Syntax::kVariable, ClientSettings::Breakpoint::kEnabled); |
| result.Append("="); |
| if (settings.enabled) { |
| result.Append("true"); |
| } else { |
| // Highlight disabled breakpoints since that's an unusual condition. |
| result.Append(Syntax::kError, "false"); |
| } |
| result.Append(" "); |
| } |
| |
| // Include type only for non-software (the normal ones) breakpoints. |
| if (settings.type != BreakpointSettings::Type::kSoftware) { |
| result.Append(Syntax::kVariable, ClientSettings::Breakpoint::kType); |
| result.Append(std::string("=") + BreakpointSettings::TypeToString(settings.type) + " "); |
| } |
| |
| if (BreakpointSettings::TypeHasSize(settings.type)) { |
| result.Append(Syntax::kVariable, ClientSettings::Breakpoint::kSize); |
| result.Append("=" + std::to_string(settings.byte_size) + " "); |
| } |
| |
| if (settings.one_shot) { |
| result.Append(Syntax::kVariable, ClientSettings::Breakpoint::kOneShot); |
| result.Append(std::string("=") + BoolToString(settings.one_shot) + " "); |
| } |
| |
| bool show_location_details = !settings.locations.empty() && show_context; |
| |
| size_t matched_locs = breakpoint->GetLocations().size(); |
| if (matched_locs == 0) { |
| // When more details are being shown below, don't duplicate the "pending" warning. |
| if (!show_location_details) |
| result.Append(Syntax::kWarning, "pending "); |
| result.Append("@ "); |
| } else if (matched_locs == 1) { |
| result.Append("@ "); |
| } else { |
| result.Append(fxl::StringPrintf("(%zu addrs) @ ", matched_locs)); |
| } |
| result.Append(FormatInputLocations(settings.locations)); |
| result.Append("\n"); |
| |
| if (show_location_details) { |
| // Append the source code location. |
| // |
| // There is a question of how to show the breakpoint enabled state. The breakpoint has a main |
| // enabled bit and each location (it can apply to more than one address -- think templates and |
| // inlined functions) within that breakpoint has its own. But each location normally resolves to |
| // the same source code location so we can't practically show the individual location's enabled |
| // state separately. |
| // |
| // For simplicity, just base it on the main enabled bit. Most people won't use location-specific |
| // enabling anyway. |
| // |
| // Ignore errors from printing the source, it doesn't matter that much. Since breakpoints are in |
| // the global scope we have to use the global settings for the build dir. We could use the |
| // process build dir for process-specific breakpoints but both process-specific breakpoints and |
| // process-specific build settings are rare. |
| if (auto locs = breakpoint->GetLocations(); !locs.empty()) { |
| FormatBreakpointContext(locs[0]->GetLocation(), |
| SourceFileProviderImpl(breakpoint->session()->system().settings()), |
| settings.enabled, &result); |
| } else { |
| // When the breakpoint resolved to nothing, warn the user, they may have made a typo. |
| result.Append(Syntax::kWarning, "Pending"); |
| result.Append( |
| ": No current matches for location. It will be matched against new\n" |
| " processes and shared libraries.\n"); |
| } |
| } |
| return result; |
| } |
| |
| OutputBuffer FormatInputLocation(const InputLocation& location) { |
| switch (location.type) { |
| case InputLocation::Type::kNone: { |
| return OutputBuffer(Syntax::kComment, "<no location>"); |
| } |
| case InputLocation::Type::kLine: { |
| // Don't pass a TargetSymbols to FormatFileLine because we always want the full file name as |
| // passed-in by the user (as this is an "input" location object). It is surprising if the |
| // debugger deletes some input. |
| return OutputBuffer(FormatFileLine(location.line)); |
| } |
| case InputLocation::Type::kName: { |
| FormatIdentifierOptions opts; |
| opts.show_global_qual = true; // Imporant to disambiguate for InputLocations. |
| opts.bold_last = true; |
| return FormatIdentifier(location.name, opts); |
| } |
| case InputLocation::Type::kAddress: { |
| return OutputBuffer(to_hex_string(location.address)); |
| } |
| } |
| FX_NOTREACHED(); |
| return OutputBuffer(); |
| } |
| |
| OutputBuffer FormatInputLocations(const std::vector<InputLocation>& locations) { |
| if (locations.empty()) |
| return OutputBuffer(Syntax::kComment, "<no location>"); |
| |
| // Comma-separate if there are multiples. |
| bool first_location = true; |
| OutputBuffer result; |
| for (const auto& loc : locations) { |
| if (!first_location) |
| result.Append(", "); |
| else |
| first_location = false; |
| result.Append(FormatInputLocation(loc)); |
| } |
| return result; |
| } |
| |
| fxl::RefPtr<EvalContext> GetEvalContextForCommand(const Command& cmd) { |
| if (cmd.frame()) |
| return cmd.frame()->GetEvalContext(); |
| |
| std::optional<ExprLanguage> language; |
| auto language_setting = |
| cmd.target()->session()->system().settings().GetString(ClientSettings::System::kLanguage); |
| if (language_setting == ClientSettings::System::kLanguage_Rust) { |
| language = ExprLanguage::kRust; |
| } else if (language_setting == ClientSettings::System::kLanguage_Cpp) { |
| language = ExprLanguage::kC; |
| } else { |
| FX_DCHECK(language_setting == ClientSettings::System::kLanguage_Auto); |
| } |
| |
| // Target context only (it may or may not have a process). |
| return fxl::MakeRefCounted<ClientEvalContextImpl>(cmd.target(), language); |
| } |
| |
| Err EvalCommandExpression(const Command& cmd, const char* verb, |
| const fxl::RefPtr<EvalContext>& eval_context, bool follow_references, |
| bool verbose_errors, EvalCallback cb) { |
| if (Err err = cmd.ValidateNouns({Noun::kProcess, Noun::kThread, Noun::kFrame}); err.has_error()) |
| return err; |
| |
| if (cmd.args().size() != 1) |
| return Err("Usage: %s <expression>\nSee \"help %s\" for more.", verb, verb); |
| |
| EvalExpression( |
| cmd.args()[0], std::move(eval_context), follow_references, |
| [verbose_errors, cb = std::move(cb), verb = std::string(verb)](ErrOrValue result) mutable { |
| if (verbose_errors && result.has_error()) { |
| cb(RewriteCommandExpressionError(verb, result.err())); |
| } else { |
| cb(std::move(result)); |
| } |
| }); |
| return Err(); |
| } |
| |
| Err EvalCommandAddressExpression( |
| const Command& cmd, const char* verb, const fxl::RefPtr<EvalContext>& eval_context, |
| fit::callback<void(const Err& err, uint64_t address, std::optional<uint32_t> size)> cb) { |
| return EvalCommandExpression( |
| cmd, verb, eval_context, true, true, |
| [eval_context, verb = std::string(verb), cb = std::move(cb)](ErrOrValue value) mutable { |
| if (value.has_error()) |
| return cb(value.err(), 0, std::nullopt); |
| |
| uint64_t address = 0; |
| std::optional<uint32_t> size; |
| |
| if (Err err = ValueToAddressAndSize(eval_context, value.value(), &address, &size); |
| err.has_error()) |
| return cb(RewriteCommandExpressionError(verb, err), 0, std::nullopt); |
| cb(Err(), address, size); |
| }); |
| } |
| |
| // Errors from the evaluation of expressions of commands oftem don't make sense without context. |
| Err RewriteCommandExpressionError(const std::string& verb, const Err& err) { |
| if (err.type() == ErrType::kOptimizedOut) { |
| // The common error messagess "unavailable" and "optimized out" (both sharing kOptimizedOut) are |
| // very short because they're often included in long dumps of structures and local variables. |
| // But it makes this common class of errors very mysterious here. |
| return Err( |
| "This variable is %s. Nothing has happened.\n" |
| "See \"help expressions\" for more on what this means and what to do.", |
| err.msg().c_str()); |
| } |
| |
| // All other errors. |
| std::string effective_verb = verb.empty() ? std::string("the command") : verb; |
| return Err("Unable to evaluate the expression for " + effective_verb + ". The result was:\n " + |
| err.msg()); |
| } |
| |
| std::string FormatConsoleString(const std::string& input) { |
| // The console parser accepts two forms: |
| // - A C-style string (raw or not) with quotes and C-style escape sequences. |
| // - A whitespace-separated string with no escape character handling. |
| |
| if (input.empty()) |
| return std::string("\"\""); // Empty strings need quotes. |
| |
| // Determine which of the cases is required. |
| bool has_space = false; |
| bool has_special = false; |
| bool has_quote = false; |
| for (unsigned char c : input) { |
| if (isspace(c)) { |
| has_space = true; |
| } else if (c < ' ') { |
| has_special = true; |
| } else if (c == '"') { |
| has_quote = true; |
| } |
| // We assume any high-bit characters are part of UTF-8 sequences. This isn't necessarily the |
| // case. We could validate UTF-8 sequences but currently that effort isn't worth it. |
| } |
| |
| if (!has_space && !has_special && !has_quote) |
| return input; |
| |
| std::string result; |
| if (has_quote && !has_special) { |
| // Raw-encode strings with embedded quotes as long as nothing else needs escaping. |
| |
| // Make sure there's a unique delimiter in case the string has an embedded )". |
| std::string delim; |
| while (input.find(")" + delim + "\"") != std::string::npos) |
| delim += "*"; |
| |
| result = "R\"" + delim + "("; |
| result += input; |
| result += ")" + delim + "\""; |
| } else { |
| // Normal C string. |
| result = "\""; |
| for (char c : input) |
| AppendCEscapedChar(c, &result); |
| result += "\""; |
| } |
| return result; |
| } |
| |
| ErrOr<Target*> GetRunnableTarget(ConsoleContext* context, const Command& cmd) { |
| Target::State state = cmd.target()->GetState(); |
| if (state == Target::State::kNone) |
| return cmd.target(); // Current one is usable. |
| |
| if (cmd.GetNounIndex(Noun::kProcess) != Command::kNoIndex) { |
| // A process was specified explicitly in the command. Since it's not usable, report an error. |
| if (state == Target::State::kStarting || state == Target::State::kAttaching) { |
| return Err( |
| "The specified process is in the process of starting or attaching.\n" |
| "Either \"kill\" it or create a \"new\" process context."); |
| } |
| return Err( |
| "The specified process is already running.\n" |
| "Either \"kill\" it or create a \"new\" process context."); |
| } |
| |
| // Create a new target based on the given one. |
| Target* new_target = context->session()->system().CreateNewTarget(cmd.target()); |
| context->SetActiveTarget(new_target); |
| return new_target; |
| } |
| |
| Err VerifySystemHasRunningProcess(System* system) { |
| for (const Target* target : system->GetTargets()) { |
| if (target->GetProcess()) |
| return Err(); |
| } |
| return Err("No processes are running."); |
| } |
| |
| void ProcessCommandCallback(fxl::WeakPtr<Target> target, bool display_message_on_success, |
| const Err& err, CommandCallback callback) { |
| if (display_message_on_success || err.has_error()) { |
| // Display messaging. |
| Console* console = Console::get(); |
| |
| OutputBuffer out; |
| if (err.has_error()) { |
| out.Append(err); |
| } else if (target) { |
| out.Append(FormatTarget(&console->context(), target.get())); |
| } |
| |
| console->Output(out); |
| } |
| |
| if (callback) |
| callback(err); |
| } |
| |
| void JobCommandCallback(const char* verb, fxl::WeakPtr<Job> job, bool display_message_on_success, |
| const Err& err, CommandCallback callback) { |
| if (!display_message_on_success && !err.has_error()) |
| return; |
| |
| Console* console = Console::get(); |
| |
| OutputBuffer out; |
| if (err.has_error()) { |
| if (job) { |
| out.Append( |
| fxl::StringPrintf("Job %d %s failed.\n", console->context().IdForJob(job.get()), verb)); |
| } |
| out.Append(err); |
| } else if (job) { |
| out.Append(FormatJob(&console->context(), job.get())); |
| } |
| |
| console->Output(out); |
| |
| if (callback) { |
| callback(err); |
| } |
| } |
| |
| void AsyncPrintReturnValue(const FunctionReturnInfo& info, fit::deferred_callback cb) { |
| // This only works for symbolized functions. |
| const Function* func = info.symbol.Get()->As<Function>(); |
| if (!func) |
| return; |
| |
| const Stack& stack = info.thread->GetStack(); |
| if (stack.empty()) |
| return; // Something is messed up. |
| auto eval_context = stack[0]->GetEvalContext(); |
| |
| GetReturnValue(eval_context, func, |
| [eval_context, func = RefPtrTo(func), cb = std::move(cb)](ErrOrValue val) mutable { |
| if (val.has_error() || !val.value().type()) |
| return; // Error or void. |
| |
| auto out = fxl::MakeRefCounted<AsyncOutputBuffer>(); |
| |
| FormatFunctionNameOptions func_name_options; |
| func_name_options.name.elide_templates = true; |
| func_name_options.name.bold_last = true; |
| func_name_options.params = FormatFunctionNameOptions::kNoParams; |
| out->Append(FormatFunctionName(func.get(), func_name_options)); |
| out->Append(Syntax::kOperatorBold, " 🡲 "); |
| |
| ConsoleFormatOptions val_options; |
| val_options.verbosity = ConsoleFormatOptions::Verbosity::kMinimal; |
| val_options.wrapping = ConsoleFormatOptions::Wrapping::kSmart; |
| val_options.max_depth = 3; |
| |
| out->Append(FormatValueForConsole(val.value(), val_options, eval_context)); |
| |
| out->Complete(); |
| if (out->is_complete()) { |
| Console::get()->Output(out->DestructiveFlatten()); |
| } else { |
| out->SetCompletionCallback([out = std::move(out), cb = std::move(cb)]() { |
| Console::get()->Output(out->DestructiveFlatten()); |
| }); |
| } |
| }); |
| } |
| |
| void ScheduleAsyncPrintReturnValue(const FunctionReturnInfo& info) { |
| info.thread->AddPostStopTask( |
| [weak_thread = info.thread->GetWeakPtr(), info](fit::deferred_callback cb) { |
| // The FunctionReturnInfo has a thread pointer in it and we need to be sure it stays in |
| // scope before continuing, even though we don't use the weak pointer directly. |
| if (!weak_thread) |
| return; |
| AsyncPrintReturnValue(info, std::move(cb)); |
| }); |
| } |
| |
| } // namespace zxdb |