blob: 104b9a61b40656f953f8fa51f58e5f420ee60df0 [file] [log] [blame]
// Copyright 2019 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/commands/verb_help.h"
#include <map>
#include <string>
#include "src/developer/debug/zxdb/console/command.h"
#include "src/developer/debug/zxdb/console/console.h"
#include "src/developer/debug/zxdb/console/nouns.h"
#include "src/developer/debug/zxdb/console/output_buffer.h"
#include "src/developer/debug/zxdb/console/verbs.h"
namespace zxdb {
namespace {
const char kExpressionsName[] = "expressions";
const char kExpressionsHelp[] = R"(Expressions
Expressions appear in some commands, most notably "print":
[zxdb] print &object->array_data[i + 4]
(*)71cc72b5310
Most C++ and Rust operators are implemented in a compatible way. Function
calls are not currently supported (with exceptions, see "Pretty printers"
below). Language-overloaded operators are ignored.
Variable and type names
Names are evaluated in the current context according to C++ rules. This means
that zxdb will search the current frame's local variables, function
parameters, variables on "this" and its base-classes, variables in the current
namespace and enclosing namespace.
Type names are handled similarly, so type names used in casts need not specify
namespaces or class names if the current frame is in that namespace or class.
However, template parameters in type names must match exactly with the names
in the symbol file. This includes all namespaces and, critically for C++ STL,
all optional template parameters like allocator names.
It is not currently possible to refer to types and statics defined locally to
a function when the current scope is outside that function.
Casting
The following casts are supported in a C++-compatible way:
  (Foo*)0x1234567
reinterpret_cast<Foo*>(bar)
  static_cast<int>(foo)
Unlike in C++, const has no effect in the debugger so there is no const_cast.
Rust expressions in zxdb should currently use C++ casts (bug 6001)
CPU registers
Unambiguously refer to CPU registers using the form "$regname", so on x64
"$rax" or "$xmm0". If there is no collision with named values in the debugged
process, the bare register name can also be used, so "rax" and "xmm0".
Vector registers are interpreted according to the current vector-format option
(see "get vector-format" for possibilities, and "set vector-format <new_mode>"
to set). They will be converted to arrays of the extracted values. Array
notation can be used to refer to individual values. Using "double" vector
format on a 128-bit ARM "v6" register would give:
  [zxdb] print $v6
  {0.0, 3.14}
  [zxdb] print $v6[1]
  3.14
  [zxdb] print $v6[0] = 2.71    # Assignment to a vector sub-value.
2.71
Importantly, since they are arrays, vector registers used in expressions print
the 0th element first and increase to the right. This can be surprising
because it's traditional to show vector registers with the high order bits on
the left and indices decreasing to the right. Use the "regs" command for a
vector-specific presentation if you want this format.
Pretty printers
The debugger's pretty-printing system formats objects with complex internal
definitions to be presented in a way that the user expects. This system also
provides pretend data members, array access, and member functions for
expressions so these objects behave as expected.
The pretend functions are implemented internally in the debugger as
expressions rather than executing any code in the debugged process. Only
getters that take no arguments are currently supported.
For example, vector- and string-like objects can be indexed with "[ <index> ]"
and in C++ you can call back(), capacity(), empty(), front(), size(), and in
Rust you can call as_ptr(), as_mut_ptr(), capacity(), is_empty(), len().
[zxdb] print some_std_vector.size()
5
[zxdb] print some_std_vector[2]
42
Smart pointer, optional, and variant object can be dereferenced with "*" and
"-> operators.
[zxdb] print some_optional
std::optional({x = 5, y = 1})
[zxdb] print *some_optional
{x = 5, y = 1}
[zxdb] print some_optional->x
5
)";
const char kHelpShortHelp[] = R"(help / h: Help.)";
const char kHelpHelp[] =
R"(help
Yo dawg, I heard you like help on your help so I put help on the help in
the help.)";
const char kHelpIntro[] =
R"(
Verbs
"step"
Applies the "step" verb to the currently selected thread.
"mem-read --size=16 0x12345678"
Pass a named switch and an argument.
Nouns
"thread"
List available threads
"thread 1"
Select thread with ID 1 to be the default.
Noun-Verb combinations
"thread 4 step"
Steps thread 4 of the current process regardless of the currently
selected thread.
"process 1 thread 4 step"
Steps thread 4 of process 1 regardless of the currently selected
thread or process.
)";
const char kOtherTopics[] =
R"(
expressions: Information on expressions used in "print", etc.
)";
// Format and syntax-highlights a line of the form "<name>: <description>". If there's no colon the
// line will be not syntax highlighted.
OutputBuffer FormatIndexLine(const std::string& line) {
OutputBuffer help(" "); // Indent.
if (size_t colon_index = line.find(':'); colon_index != std::string::npos) {
std::string name = line.substr(0, colon_index);
// Some names have alternate forms, "foo / f". Don't highlight slashes as names so it's more
// clear what things are the name.
while (!name.empty()) {
if (size_t slash = name.find('/'); slash != std::string::npos) {
help.Append(Syntax::kVariable, name.substr(0, slash));
help.Append(Syntax::kComment, "/");
name = name.substr(slash + 1);
} else {
help.Append(Syntax::kVariable, name);
break;
}
}
help.Append(line.substr(colon_index));
} else {
// No syntax formatting for this line.
help.Append(line);
}
help.Append("\n");
return help;
}
OutputBuffer FormatGroupHelp(const char* heading, std::vector<std::string>* items) {
std::sort(items->begin(), items->end());
OutputBuffer help("\n");
help.Append(Syntax::kHeading, heading);
help.Append("\n");
for (const auto& line : *items)
help.Append(FormatIndexLine(line));
return help;
}
OutputBuffer GetReference() {
OutputBuffer help(Syntax::kHeading, "Help!");
help.Append("\n\n Type \"help <command>\" for command-specific help.\n\n");
help.Append(Syntax::kHeading, "Other help topics");
help.Append(" (see \"help <topic>\")\n");
help.Append(kOtherTopics);
help.Append(Syntax::kHeading, "Command syntax\n");
help.Append(kHelpIntro);
// Group all verbs by their CommandGroup. Add nouns to this since people will expect, for example,
// "breakpoint" to be in the breakpoints section.
std::map<CommandGroup, std::vector<std::string>> groups;
// Get the separate noun reference and add to the groups.
help.Append(Syntax::kHeading, "\nNouns\n");
std::vector<std::string> noun_lines;
for (const auto& pair : GetNouns()) {
noun_lines.push_back(pair.second.short_help);
groups[pair.second.command_group].push_back(pair.second.short_help);
}
std::sort(noun_lines.begin(), noun_lines.end());
for (const auto& line : noun_lines)
help.Append(FormatIndexLine(line));
// Add in verbs.
for (const auto& pair : GetVerbs())
groups[pair.second.command_group].push_back(pair.second.short_help);
help.Append(FormatGroupHelp("General", &groups[CommandGroup::kGeneral]));
help.Append(FormatGroupHelp("Process", &groups[CommandGroup::kProcess]));
help.Append(FormatGroupHelp("Symbol", &groups[CommandGroup::kSymbol]));
help.Append(FormatGroupHelp("Assembly", &groups[CommandGroup::kAssembly]));
help.Append(FormatGroupHelp("Breakpoint", &groups[CommandGroup::kBreakpoint]));
help.Append(FormatGroupHelp("Query", &groups[CommandGroup::kQuery]));
help.Append(FormatGroupHelp("Step", &groups[CommandGroup::kStep]));
return help;
}
Err RunVerbHelp(ConsoleContext* context, const Command& cmd) {
OutputBuffer out;
if (cmd.args().empty()) {
// Generic help, list topics and quick reference.
Console::get()->Output(GetReference());
return Err();
}
const std::string& on_what = cmd.args()[0];
const char* help = nullptr;
// Check for a noun.
const auto& string_noun = GetStringNounMap();
auto found_string_noun = string_noun.find(on_what);
if (found_string_noun != string_noun.end()) {
// Find the noun record to get the help. This is guaranteed to exist.
const auto& nouns = GetNouns();
help = nouns.find(found_string_noun->second)->second.help;
} else {
// Check for a verb
const auto& string_verb = GetStringVerbMap();
auto found_string_verb = string_verb.find(on_what);
if (found_string_verb != string_verb.end()) {
// Find the verb record to get the help. This is guaranteed to exist.
const auto& verbs = GetVerbs();
help = verbs.find(found_string_verb->second)->second.help;
} else {
// Check for standalone topic.
if (on_what == kExpressionsName) {
help = kExpressionsHelp;
} else {
// Not a valid command.
out.Append(Err("\"" + on_what +
"\" is not a valid command.\n"
"Try just \"help\" to get a list."));
Console::get()->Output(out);
return Err();
}
}
}
out.FormatHelp(help);
Console::get()->Output(out);
return Err();
}
} // namespace
VerbRecord GetHelpVerbRecord() {
return VerbRecord(&RunVerbHelp, {"help", "h"}, kHelpShortHelp, kHelpHelp, CommandGroup::kGeneral);
}
} // namespace zxdb