blob: 5a43bf56ef6a83e749c5f7f42df94329803189fe [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 <algorithm>
#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 many commands. Some commands expect just an expression,
most notably "print":
[zxdb] print &object->array_data[i + 4]
(*)71cc72b5310
Other commands such as "break" or "disassemble" accept a location. This is
typically a function name or a line number, but can also be an expression that
evaluates to a memory address. To use expressions for these commands, prefix
it with a "*":
[zxdb] break *foo->some_address
Created Breakpoint 1 @ 0x30ad01a2b80
Most C++ and Rust operators are implemented in a language-compatible way.
Function calls are not currently supported (with exceptions, see "Pretty
printers" below). User-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.
In Rust, use normal "as" syntax for casting.
Arrays
In C/C++, you can specify the size of an array using the syntax "array@size".
This allows printing the array values corresponding to a pointer or for a
flexible array member at the end of a structure.
[zxdb] print argv # Normal pointer interpretation of char**.
(*)0x1e88d07bfc0 "/pkg/bin/foo"
[zxdb] print argv@3 # Declare array of size 3.
{"/pkg/bin/foo", "--bar", "baz"}
Special names
Special names begin with '$'. There are different forms:
Escaped strings: "$(...)"
Sometimes there are symbol names that can't be parsed in the standard
language. This especially applies to compiler-generated symbols. Enclose
such strings in "$(...)". Parens inside the escaped contents can be
literal as long as they're balanced, otherwise, escape them by preceeding
with a backslash. Include a literal backslash with two blackslashes:
$(something with spaces)
$({{impl}})
$(some_closure(data))
$(line\)noise\\)
Register names: "$reg(...)"
To unambiguously refer to register names, use the "$reg" annotation. So on
x64 "$reg(rax)" or "$reg(xmm0)".
Main function: "$main"
Maps to the entrypoint declared in the symbols, and falls back on the
function named "main" if there is no entrypoint defined.
ELF symbols: "$elf(...)" "$plt(...)"
Maps to symbols from the ELF file. "$elf" matches all ELF symbols, "$plt"
matches only symbols in the Procedure Linkage Table (used for jumping to
dynamically linked functions in other shared libraries).
CPU registers
The expression evaluator will check for register names if there is no variable
with a given string. So in most cases you can just use literal register names:
[zxdb] print rax
Unambiguously refer to CPU registers using the form "$reg(rax)" as discussed
above under "Special names".
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 $reg(v6)
  {0.0, 3.14}
  [zxdb] print $reg(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
Common errors
<Optimized out>
Indicates that the program symbols declare a variable with the given name,
but that it has no value or location. This means the compiler has entirely
optimized out the variable and the debugger can not show it. If you need
to see it, use a less-optimized build setting.
<Unavailable>
Indicates that the variable is not valid at the current address, but that
its value is known at other addresses. In optimized code, the compiler
will often re-use registers, clobbering previous values which become
unavailable.
You can see the valid ranges for a variable with the "sym-info" command:
[zxdb] sym-info my_variable
Under "DWARF location" it will give a list of address ranges where the
value of the variable is known (inclusive at the beginning of the range,
non-inclusive at the end). Run to one of these addresses to see the value
of the variable (use "di" to see the current address).
You can ignore the "DWARF expression bytes" which are the internal
instructions for finding the variable.
)*";
constexpr char kJitdName[] = "jitd";
constexpr char kJitdHelp[] = R"(Just In Time Debugging
Just In Time Debugging is a way for the system to suspend processes that have
crashed without any exception handlers. The system will keep those processes
in a place called "Process Limbo". Later, zxdb will automatically connect to
Process Limbo and attach to all processes waiting to be debugged.
Enabling process limbo in the system
To enable catching exceptions in newly crashed processes, use:
ffx debug limbo enable
For full documentation on enabling and configuring Limbo, including enabling
on system startup, see the full documentation at:
https://fuchsia.dev/fuchsia-src/development/debugger/just_in_time_debugging.md
Listing Processes
When zxdb starts up, any process waiting to be debugged within Process Limbo
will be listed like this:
👉 To get started, try "status" or "help".
Processes auto attached from limbo:
4938729: process-that-crashed
4940432: some-other-process-that-crashed
Type "detach <pid>" to send back to Process Limbo (unattached) or type
"process <process context #> kill" to exit the process.
...
[zxdb]
You can also run the "status" command to see the currently attached processes:
[zxdb] status
Connection
Connected to '/tmp/debug_agent_ZdPZdx.socket' on port 0.
Jobs
Attached to 1 job(s) (jobs are nodes in the Zircon process tree). Processes
launched in attached jobs can be caught and debugged via "attach" filters.
See "help job" and "help attach". The debugger has these:
# State Koid Name
1 Attached 1033 root
Process name filters
Filters match the names of processes launched in attached jobs and
automatically attaches to them.
There are no filters. Use "attach <process-name>" to create one.
Processes
Attached to 2 process(es). The debugger has these:
# State Koid Name
1 Running 4938729 process-that-crashed
2 Running 4940432 some-other-process-that-crashed
Processes waiting on exception
No processes waiting on exception.
Attaching/Removing Processes
From the point of view of zxdb, processes in limbo behave very similar to what
a normal running process does. In order to start debugging one, simply start
zxdb like normal and zxdb will retrieve the process from limbo and start
debugging it. Once attached, you can manipulate the process as normal, and
even detach or kill it.
Note that if you detach from a crashing process, the exception will be
re-triggered and it will caught by Process Limbo. Killing it will terminate
the process as usual. A notice will be displayed to communicate when a process
that has previously crashed crashes again. Such a process found to crash
multiple times will not automatically attach, but can still be attached with
the regular attach command i.e. "attach <KOID>".
The only difference comes when attempting to release a process from Process
Limbo, without attaching to it. In that case, you need to instruct the
debugger to "detach" from it by issuing a "detach <KOID>" command. You can
also explicitly kill such a process with "pr <process context #> kill". Using
the example above, use "pr 2 kill" to kill the process named
"some-other-process-that-crashed".
The default setting to automatically attach to all processes in Process Limbo
can be overridden via the -n switch or --no-auto-attach-limbo when invoking
"ffx debug connect" or interactively with "set auto-attach-limbo" at the
[zxdb] prompt.
)";
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.
)";
// Sorted list of strings for other help topics.
const char* kOtherTopics[] = {
"expressions: Information on expressions used in \"print\", etc.",
"jitd: Use \"just-in-time debugging\" to attach after a process crashes.",
};
// 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\n");
for (const auto& line : kOtherTopics)
help.Append(FormatIndexLine(line));
help.Append(Syntax::kHeading, "\nCommand 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 if (on_what == kJitdName) {
help = kJitdHelp;
} 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