| // 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 |