| // 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 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. | |
| Rust expressions in zxdb should currently use C++ casts (bug 6001) | |
| 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 is able to connect to Process | |
| Limbo and attach to process waiting to be debugged. | |
| Enabling process limbo in the system | |
| To enable catching exceptions in newly crashed processes, type in a Fuchsia | |
| shell: | |
| run limbo.cmx 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 waiting on exception: | |
| 2780309: process-that-crashed | |
| 2783544: some-other-process-that-crashed | |
| Type "attach <pid>" to reconnect. | |
| [zxdb] | |
| You call also run the "status" command and get the same information: | |
| [zxdb] status | |
| ... | |
| Processes waiting on exception | |
| 2 process(es) waiting on exception. | |
| Run "attach <KOID>" to load them into zxdb or "detach <KOID>" to | |
| terminate them. See "help jitd" for more information on Just-In-Time | |
| Debugging. | |
| 2780309 process-that-crashed | |
| 2783544 some-other-process-that-crashed | |
| Attaching/Removing Processes | |
| From the point of view of zxdb, the processes within the limbo behave very | |
| similar to what a normal running process does. In order to start debugging | |
| one, simply do "attach <KOID>" 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 the Process Limbo. Killing it will | |
| terminate the process as usual. | |
| The only difference comes when attempting to release a process from the | |
| Process Limbo, without attaching from it. In that case, you need to instruct | |
| the debugger to "detach" from it by issuing a "detach <KOID>" command. | |
| )"; | |
| 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 |