blob: dd3dfe80ed42aae3be9c597e154e1047889ae26d [file] [log] [blame]
// Copyright 2018 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "src/developer/debug/zxdb/console/command_utils.h"
#include <inttypes.h>
#include <stdio.h>
#include <limits>
#include "src/developer/debug/zxdb/client/breakpoint.h"
#include "src/developer/debug/zxdb/client/frame.h"
#include "src/developer/debug/zxdb/client/job.h"
#include "src/developer/debug/zxdb/client/job_context.h"
#include "src/developer/debug/zxdb/client/process.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/console_context.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/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/number_parser.h"
#include "src/developer/debug/zxdb/expr/symbol_eval_context.h"
#include "src/developer/debug/zxdb/symbols/base_type.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/logging.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).c_str()));
}
Err AssertStoppedThreadCommand(ConsoleContext* context, const Command& cmd,
bool validate_nouns, const char* command_name) {
Err err;
if (validate_nouns) {
err = cmd.ValidateNouns({Noun::kProcess, Noun::kThread});
if (err.has_error())
return err;
}
if (!cmd.thread()) {
return Err(fxl::StringPrintf(
"\"%s\" requires a thread but there is no current thread.",
command_name));
}
if (cmd.thread()->GetState() != debug_ipc::ThreadRecord::State::kBlocked &&
cmd.thread()->GetState() != debug_ipc::ThreadRecord::State::kCoreDump &&
cmd.thread()->GetState() != debug_ipc::ThreadRecord::State::kSuspended) {
return Err(fxl::StringPrintf(
"\"%s\" requires a suspended thread but thread %d is %s.\n"
"To view and sync thread state with the remote system, type "
"\"thread\".",
command_name, context->IdForThread(cmd.thread()),
ThreadStateToString(cmd.thread()->GetState(),
cmd.thread()->GetBlockedReason())
.c_str()));
}
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, " ").ToString();
ExprValue number_value;
Err err = StringToNumber(trimmed, &number_value);
if (err.has_error())
return err;
// Be careful to read the number out in its original sign-edness.
if (number_value.GetBaseType() == BaseType::kBaseTypeUnsigned) {
uint64_t u64;
err = number_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.GetBaseType() != BaseType::kBaseTypeSigned)
return Err("This value is not the correct type.");
return number_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(fxl::StringPrintf(
"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, " ").ToString();
ExprValue number_value;
Err err = StringToNumber(trimmed, &number_value);
if (err.has_error())
return err;
// Be careful to read the number out in its original sign-edness.
if (number_value.GetBaseType() == BaseType::kBaseTypeSigned) {
int64_t s64;
err = number_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.GetBaseType() != BaseType::kBaseTypeUnsigned)
return Err("This value is not the correct type.");
return number_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();
}
Err ParseHostPort(const std::string& in_host, const std::string& in_port,
std::string* out_host, uint16_t* out_port) {
if (in_host.empty())
return Err(ErrType::kInput, "No host component specified.");
if (in_port.empty())
return Err(ErrType::kInput, "No port component specified.");
// Trim brackets from the host name for IPv6 addresses.
if (in_host.front() == '[' && in_host.back() == ']')
*out_host = in_host.substr(1, in_host.size() - 2);
else
*out_host = in_host;
// Re-use paranoid int64 parsing.
uint64_t port64;
Err err = StringToUint64(in_port, &port64);
if (err.has_error())
return err;
if (port64 == 0 || port64 > std::numeric_limits<uint16_t>::max())
return Err(ErrType::kInput, "Port value out of range.");
*out_port = static_cast<uint16_t>(port64);
return Err();
}
Err ParseHostPort(const std::string& input, std::string* out_host,
uint16_t* out_port) {
// Separate based on the last colon.
size_t colon = input.rfind(':');
if (colon == std::string::npos)
return Err(ErrType::kInput, "Expected colon to separate host/port.");
// If the host has a colon in it, it could be an IPv6 address. In this case,
// require brackets around it to differentiate the case where people
// supplied an IPv6 address and we just picked out the last component above.
std::string host = input.substr(0, colon);
if (host.empty())
return Err(ErrType::kInput, "No host component specified.");
if (host.find(':') != std::string::npos) {
if (host.front() != '[' || host.back() != ']') {
return Err(ErrType::kInput,
"For IPv6 addresses use either: \"[::1]:1234\"\n"
"or the two-parameter form: \"::1 1234");
}
}
std::string port = input.substr(colon + 1);
return ParseHostPort(host, port, out_host, out_port);
}
std::string TargetStateToString(Target::State state) {
switch (state) {
case Target::State::kNone:
return "Not running";
case Target::State::kStarting:
return "Starting";
case Target::State::kAttaching:
return "Attaching";
case Target::State::kRunning:
return "Running";
}
FXL_NOTREACHED();
return std::string();
}
std::string JobContextStateToString(JobContext::State state) {
switch (state) {
case JobContext::State::kNone:
return "Not running";
case JobContext::State::kStarting:
return "Starting";
case JobContext::State::kAttaching:
return "Attaching";
case JobContext::State::kRunning:
return "Running";
}
FXL_NOTREACHED();
return std::string();
}
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);
FXL_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 BreakpointScopeToString(const ConsoleContext* context,
const BreakpointSettings& settings) {
switch (settings.scope) {
case BreakpointSettings::Scope::kSystem:
return "Global";
case BreakpointSettings::Scope::kTarget:
return fxl::StringPrintf("pr %d",
context->IdForTarget(settings.scope_target));
case BreakpointSettings::Scope::kThread:
return fxl::StringPrintf(
"pr %d t %d",
context->IdForTarget(
settings.scope_thread->GetProcess()->GetTarget()),
context->IdForThread(settings.scope_thread));
}
FXL_NOTREACHED();
return std::string();
}
std::string BreakpointStopToString(BreakpointSettings::StopMode mode) {
switch (mode) {
case BreakpointSettings::StopMode::kNone:
return "None";
case BreakpointSettings::StopMode::kThread:
return "Thread";
case BreakpointSettings::StopMode::kProcess:
return "Process";
case BreakpointSettings::StopMode::kAll:
return "All";
}
FXL_NOTREACHED();
return std::string();
}
const char* BreakpointEnabledToString(bool enabled) {
return enabled ? "Enabled" : "Disabled";
}
const char* BreakpointTypeToString(debug_ipc::BreakpointType type) {
switch (type) {
case debug_ipc::BreakpointType::kSoftware:
return "Software";
case debug_ipc::BreakpointType::kHardware:
return "Hardware";
}
}
std::string DescribeJobContext(const ConsoleContext* context,
const JobContext* job_context) {
int id = context->IdForJobContext(job_context);
std::string state = JobContextStateToString(job_context->GetState());
// Koid string. This includes a trailing space when present so it can be
// concat'd even when not present and things look nice.
std::string koid_str;
if (job_context->GetState() == JobContext::State::kRunning) {
koid_str = fxl::StringPrintf("koid=%" PRIu64 " ",
job_context->GetJob()->GetKoid());
}
std::string result =
fxl::StringPrintf("Job %d [%s] %s", id, state.c_str(), koid_str.c_str());
result += DescribeJobContextName(job_context);
return result;
}
std::string DescribeTarget(const ConsoleContext* context,
const Target* target) {
int id = context->IdForTarget(target);
std::string state = TargetStateToString(target->GetState());
// Koid string. This includes a trailing space when present so it can be
// concat'd even when not present and things look nice.
std::string koid_str;
if (target->GetState() == Target::State::kRunning) {
koid_str =
fxl::StringPrintf("koid=%" PRIu64 " ", target->GetProcess()->GetKoid());
}
std::string result = fxl::StringPrintf("Process %d [%s] %s", id,
state.c_str(), koid_str.c_str());
result += DescribeTargetName(target);
return result;
}
std::string DescribeTargetName(const Target* target) {
// When running, use the object name if any.
std::string name;
if (target->GetState() == Target::State::kRunning)
name = target->GetProcess()->GetName();
// Otherwise fall back to the program name which is the first arg.
if (name.empty()) {
const std::vector<std::string>& args = target->GetArgs();
if (!args.empty())
name += args[0];
}
return name;
}
std::string DescribeJobContextName(const JobContext* job_context) {
// When running, use the object name if any.
std::string name;
if (job_context->GetState() == JobContext::State::kRunning)
name = job_context->GetJob()->GetName();
return name;
}
std::string DescribeThread(const ConsoleContext* context,
const Thread* thread) {
return fxl::StringPrintf(
"Thread %d [%s] koid=%" PRIu64 " %s", context->IdForThread(thread),
ThreadStateToString(thread->GetState(), thread->GetBlockedReason())
.c_str(),
thread->GetKoid(), thread->GetName().c_str());
}
OutputBuffer FormatBreakpoint(const ConsoleContext* context,
const Breakpoint* breakpoint) {
BreakpointSettings settings = breakpoint->GetSettings();
std::string scope = BreakpointScopeToString(context, settings);
std::string stop = BreakpointStopToString(settings.stop_mode);
const char* enabled = BreakpointEnabledToString(settings.enabled);
const char* type = BreakpointTypeToString(settings.type);
OutputBuffer location = FormatInputLocation(settings.location);
OutputBuffer result("Breakpoint ");
result.Append(Syntax::kSpecial,
fxl::StringPrintf("%d", context->IdForBreakpoint(breakpoint)));
result.Append(fxl::StringPrintf(" (%s) on %s, %s, stop=%s, @ ", type,
scope.c_str(), enabled, stop.c_str()));
result.Append(std::move(location));
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 DescribeFileLine 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(DescribeFileLine(nullptr, location.line));
case InputLocation::Type::kSymbol:
return FormatIdentifier(location.symbol, true);
case InputLocation::Type::kAddress:
return OutputBuffer(fxl::StringPrintf("0x%" PRIx64, location.address));
}
FXL_NOTREACHED();
return OutputBuffer();
}
// This annoyingly duplicates Identifier::GetName but is required to get
// syntax highlighting for all the components.
OutputBuffer FormatIdentifier(const Identifier& identifier, bool bold_last) {
OutputBuffer result;
if (identifier.qualification() == Identifier::kGlobal)
result.Append(identifier.GetSeparator());
const auto& comps = identifier.components();
for (size_t i = 0; i < comps.size(); i++) {
const auto& comp = comps[i];
if (i > 0)
result.Append(identifier.GetSeparator());
// Name.
if (bold_last && i == comps.size() - 1)
result.Append(Syntax::kHeading, comp.name());
else
result.Append(Syntax::kNormal, comp.name());
// Template.
if (comp.has_template()) {
std::string t_string("<");
for (size_t t_i = 0; t_i < comp.template_contents().size(); t_i++) {
if (t_i > 0)
t_string += ", ";
t_string += comp.template_contents()[t_i];
}
t_string.push_back('>');
result.Append(Syntax::kComment, std::move(t_string));
}
}
return result;
}
OutputBuffer FormatFunctionName(const Function* function, bool show_params) {
auto [err, ident] = ExprParser::ParseIdentifier(function->GetFullName());
OutputBuffer result;
if (err.has_error()) {
// Can't parse the name, just pass through.
result = OutputBuffer(function->GetFullName());
} else {
result = FormatIdentifier(ident, true);
}
const auto& params = function->parameters();
std::string params_str;
if (show_params) {
params_str.push_back('(');
for (size_t i = 0; i < params.size(); i++) {
if (i > 0)
params_str += ", ";
if (const Variable* var = params[i].Get()->AsVariable())
params_str += var->type().Get()->GetFullName();
}
params_str.push_back(')');
} else {
if (params.empty())
params_str += "()";
else
params_str += "(…)";
}
result.Append(Syntax::kComment, std::move(params_str));
return result;
}
OutputBuffer FormatLocation(const TargetSymbols* optional_target_symbols,
const Location& loc, bool always_show_address,
bool always_show_types) {
if (!loc.is_valid())
return OutputBuffer("<invalid address>");
if (!loc.has_symbols())
return OutputBuffer(fxl::StringPrintf("0x%" PRIx64, loc.address()));
OutputBuffer result;
if (always_show_address) {
result = OutputBuffer(Syntax::kComment,
fxl::StringPrintf("0x%" PRIx64 ", ", loc.address()));
}
const Function* func = loc.symbol().Get()->AsFunction();
if (func) {
OutputBuffer func_output = FormatFunctionName(func, always_show_types);
if (!func_output.empty()) {
result.Append(std::move(func_output));
if (loc.file_line().is_valid()) {
// Separator between function and file/line.
result.Append(" " + GetBullet() + " ");
} else {
// Check if the address is inside a function and show the offset.
AddressRange function_range = func->GetFullRange(loc.symbol_context());
if (function_range.InRange(loc.address())) {
// Inside a function but no file/line known. Show the offset.
result.Append(fxl::StringPrintf(
" + 0x%" PRIx64, loc.address() - function_range.begin()));
result.Append(Syntax::kComment, " (no line info)");
}
}
}
}
if (loc.file_line().is_valid())
result.Append(DescribeFileLine(optional_target_symbols, loc.file_line()));
return result;
}
std::string DescribeFileLine(const TargetSymbols* optional_target_symbols,
const FileLine& file_line) {
std::string result;
// Name.
if (file_line.file().empty()) {
result = "?";
} else if (!optional_target_symbols) {
result = file_line.file();
} else {
result =
optional_target_symbols->GetShortestUniqueFileName(file_line.file());
}
result.push_back(':');
// Line.
if (file_line.line() == 0)
result.push_back('?');
else
result.append(fxl::StringPrintf("%d", file_line.line()));
return result;
}
Err SetElementsToAdd(const std::vector<std::string>& args,
AssignType* assign_type,
std::vector<std::string>* elements_to_set) {
if (args.size() < 2u)
return Err("Expected at least two arguments.");
elements_to_set->clear();
// Validation.
auto& token = args[1];
if (token == "=" || token == "+=" || token == "-=") {
if (args.size() < 3)
return Err("Expected a value after \"=\"");
elements_to_set->insert(elements_to_set->end(), args.begin() + 2,
args.end());
if (token == "=") {
*assign_type = AssignType::kAssign;
} else if (token == "+=") {
*assign_type = AssignType::kAppend;
}
if (token == "-=") {
*assign_type = AssignType::kRemove;
}
} else {
*assign_type = AssignType::kAssign;
// We just append everything after the setting name.
elements_to_set->insert(elements_to_set->end(), args.begin() + 1,
args.end());
}
return Err();
}
const char* AssignTypeToString(AssignType assign_type) {
switch (assign_type) {
case AssignType::kAssign:
return "Assign";
case AssignType::kAppend:
return "Append";
case AssignType::kRemove:
return "Remove";
}
FXL_NOTREACHED();
return "";
}
fxl::RefPtr<ExprEvalContext> GetEvalContextForCommand(const Command& cmd) {
if (cmd.frame())
return cmd.frame()->GetExprEvalContext();
if (Process* process = cmd.target()->GetProcess()) {
// Process context only.
return fxl::MakeRefCounted<SymbolEvalContext>(
process->GetSymbols()->GetWeakPtr(), process->GetSymbolDataProvider(),
Location());
}
// No context.
return fxl::MakeRefCounted<SymbolEvalContext>(
fxl::WeakPtr<ProcessSymbols>(), fxl::MakeRefCounted<SymbolDataProvider>(),
Location());
}
Err EvalCommandExpression(
const Command& cmd, const char* verb,
fxl::RefPtr<ExprEvalContext> eval_context, bool follow_references,
std::function<void(const Err& err, ExprValue value)> cb) {
Err err = cmd.ValidateNouns({Noun::kProcess, Noun::kThread, Noun::kFrame});
if (err.has_error())
return err;
// This takes one expression that may have spaces, so concatenate everything
// the command parser has split apart back into one thing.
//
// If we run into limitations of this, we should add a "don't parse the args"
// flag to the command record.
std::string expr;
for (const auto& cur : cmd.args()) {
if (!expr.empty())
expr.push_back(' ');
expr += cur;
}
if (expr.empty())
return Err("Usage: %s <expression>\nSee \"help %s\" for more.", verb, verb);
EvalExpression(expr, std::move(eval_context), follow_references,
std::move(cb));
return Err();
}
Err EvalCommandAddressExpression(
const Command& cmd, const char* verb,
fxl::RefPtr<ExprEvalContext> eval_context,
std::function<void(const Err& err, uint64_t address,
std::optional<uint32_t> size)>
cb) {
return EvalCommandExpression(
cmd, verb, std::move(eval_context), true,
[cb = std::move(cb)](const Err& err, ExprValue value) {
if (err.has_error()) {
cb(err, 0, std::nullopt);
return;
}
const Type* concrete_type = value.type()->GetConcreteType();
if (concrete_type->AsCollection()) {
// Don't allow structs and classes that are <= 64 bits to be converted
// to addresses.
cb(Err("Can't convert '%s' to an address.",
concrete_type->GetFullName().c_str()),
0, std::nullopt);
return;
}
// See if there's an intrinsic size to the object being pointed to. This
// is true for pointers. References should have been followed and
// stripped before here.
std::optional<uint32_t> size;
if (auto modified = concrete_type->AsModifiedType();
modified && modified->tag() == DwarfTag::kPointerType) {
if (auto modified_type = modified->modified().Get()->AsType())
size = modified_type->byte_size();
}
// Convert anything else <= 64 bits to a number.
uint64_t address = 0;
Err conversion_err = value.PromoteTo64(&address);
cb(conversion_err, address, size);
});
}
} // namespace zxdb