| // Copyright 2020 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_sym_debug.h" |
| |
| #include "src/developer/debug/zxdb/client/frame.h" |
| #include "src/developer/debug/zxdb/client/process.h" |
| #include "src/developer/debug/zxdb/client/target.h" |
| #include "src/developer/debug/zxdb/common/string_util.h" |
| #include "src/developer/debug/zxdb/console/command.h" |
| #include "src/developer/debug/zxdb/console/command_utils.h" |
| #include "src/developer/debug/zxdb/console/console.h" |
| #include "src/developer/debug/zxdb/console/format_location.h" |
| #include "src/developer/debug/zxdb/console/format_name.h" |
| #include "src/developer/debug/zxdb/console/format_symbol.h" |
| #include "src/developer/debug/zxdb/console/format_table.h" |
| #include "src/developer/debug/zxdb/console/output_buffer.h" |
| #include "src/developer/debug/zxdb/console/string_util.h" |
| #include "src/developer/debug/zxdb/console/verbs.h" |
| #include "src/developer/debug/zxdb/symbols/call_site.h" |
| #include "src/developer/debug/zxdb/symbols/dwarf_unit.h" |
| #include "src/developer/debug/zxdb/symbols/function.h" |
| #include "src/developer/debug/zxdb/symbols/line_table.h" |
| #include "src/developer/debug/zxdb/symbols/loaded_module_symbols.h" |
| #include "src/developer/debug/zxdb/symbols/module_symbols.h" |
| #include "src/developer/debug/zxdb/symbols/process_symbols.h" |
| #include "src/developer/debug/zxdb/symbols/target_symbols.h" |
| |
| namespace zxdb { |
| |
| namespace { |
| |
| constexpr int kCallReturnSwitch = 1; |
| constexpr int kFunctionSwitch = 2; |
| constexpr int kInlineSwitch = 3; |
| constexpr int kInlineTreeSwitch = 4; |
| constexpr int kLineSwitch = 5; |
| constexpr int kDwarfExprSwitch = 6; |
| |
| const char kSymDebugShortHelp[] = "sym-debug: Print debug symbol information."; |
| const char kSymDebugHelp[] = |
| R"(sym-debug ( -f | -i | -t | -l ) [ <address-expression> ] |
| |
| This command takes a flag for what to print and an optional address. If no |
| address-expression is given, the current frame's instruction pointer will be |
| used. |
| |
| Printing modes |
| |
| --call-return | -r |
| Prints the call site information for the call returning to the given |
| address. |
| |
| --function | -f |
| Prints the information about the function symbol covering the address. |
| This is equivalent to "sym-info <current-function-name>". |
| |
| --inlines | -i |
| Prints the chain of inline functions covering the address. The plysical |
| (non-inlined) function will be at the bottom. The address ranges for the |
| inline will be shows after the name. |
| |
| --inline-tree | -t |
| Dumps the inline tree for the function covering the address. This will |
| show each inline function indented according to its nesting. Each inline |
| will also contain the set of address ranges |
| |
| --line | -l |
| Dumps the DWARF line table sequence containing the address. |
| |
| Options |
| |
| )" DWARF_EXPR_COMMAND_SWTICH_HELP |
| R"( |
| Examples |
| |
| sym-debug -l |
| sym-debug -i 0x56cfe7b4 |
| sym-debug -f --dwarf-expr=raw |
| )"; |
| |
| ErrOr<fxl::RefPtr<Function>> GetFunctionAtAddress(ProcessSymbols* process_symbols, |
| uint64_t address) { |
| // With an address, we should get exactly one location back. |
| std::vector<Location> locs = process_symbols->ResolveInputLocation(InputLocation(address)); |
| if (locs.size() != 1) |
| return Err("Error: more than one location matched."); |
| const Location& loc = locs[0]; |
| |
| if (!loc.symbol()) |
| return Err("No symbol at address " + to_hex_string(address) + ".\n"); |
| |
| const Function* func = loc.symbol().Get()->As<Function>(); |
| if (!func) |
| return Err("No function at address " + to_hex_string(address) + ".\n"); |
| |
| return RefPtrTo(func); |
| } |
| |
| // Returns the inline chain of functions corresponding to the given address. Guaranteed to return |
| // nonempty if there's no error. |
| ErrOr<std::vector<fxl::RefPtr<Function>>> GetInlineChainAtAddress(ProcessSymbols* process_symbols, |
| uint64_t address) { |
| auto func_or = GetFunctionAtAddress(process_symbols, address); |
| if (!func_or) |
| return func_or.err(); |
| return func_or.value()->GetInlineChain(); |
| } |
| |
| // Appends the function name and its code ranges to the given output buffer. |
| void AppendFunctionAndRanges(const SymbolContext& context, const Function* function, |
| OutputBuffer& out) { |
| FormatFunctionNameOptions options; |
| options.name.bold_last = true; |
| // Lots of inline functions are long templates and they make things much harder to follow. For the |
| // typical debug tasks, this information is not needed, so omit the templates. |
| options.name.elide_templates = true; |
| options.params = FormatFunctionNameOptions::kNoParams; |
| |
| out.Append(FormatFunctionName(function, options)); |
| out.Append(": "); |
| out.Append(Syntax::kComment, function->GetAbsoluteCodeRanges(context).ToString()); |
| out.Append("\n"); |
| } |
| |
| OutputBuffer DumpCallReturn(ProcessSymbols* process_symbols, uint64_t address, |
| const FormatSymbolOptions& opts) { |
| auto func_or = GetFunctionAtAddress(process_symbols, address); |
| if (!func_or) { |
| OutputBuffer out; |
| out.Append(func_or.err()); |
| return out; |
| } |
| const Function* func = func_or.value().get(); |
| |
| SymbolContext symbol_context = func->GetSymbolContext(process_symbols); |
| |
| fxl::RefPtr<CallSite> call_site = func->GetCallSiteForReturnTo(symbol_context, address); |
| if (call_site) |
| return FormatSymbol(process_symbols, call_site.get(), opts); |
| |
| return OutputBuffer("No call site information for return to " + to_hex_string(address)); |
| } |
| |
| OutputBuffer DumpFunction(ProcessSymbols* process_symbols, uint64_t address, |
| const FormatSymbolOptions& opts) { |
| auto func_or = GetFunctionAtAddress(process_symbols, address); |
| if (func_or.has_error()) { |
| OutputBuffer out; |
| out.Append(func_or.err()); |
| return out; |
| } |
| |
| return FormatSymbol(process_symbols, func_or.value().get(), opts); |
| } |
| |
| OutputBuffer DumpInlineChain(ProcessSymbols* process_symbols, uint64_t address, |
| const FormatSymbolOptions& opts) { |
| OutputBuffer out; |
| |
| auto chain = GetInlineChainAtAddress(process_symbols, address); |
| if (chain.has_error()) { |
| out.Append(chain.err()); |
| return out; |
| } |
| |
| // Expect all functions in an inline chain to have the same context (because they share the same |
| // physical function). |
| SymbolContext context = chain.value()[0]->GetSymbolContext(process_symbols); |
| |
| // Pring each inline with the inline depth (going back in time to the non-inlined function). |
| int index = static_cast<int>(chain.value().size()) - 1; |
| for (const auto& func : chain.value()) { |
| out.Append(Syntax::kSpecial, " " + std::to_string(index) + " "); |
| index--; |
| AppendFunctionAndRanges(context, func.get(), out); |
| } |
| return out; |
| } |
| |
| // Recursive function to print the inlines. |
| void PrintInlineRecursive(const SymbolContext& context, uint64_t address, const CodeBlock* block, |
| int indent, OutputBuffer& output) { |
| const Function* function = block->As<Function>(); |
| |
| // This block could be a lexical block (takes no indents and produces no output), or a function. |
| int next_indent = indent; |
| if (function) { |
| // Mark the inlines that contain the given address. |
| AddressRanges ranges = function->GetAbsoluteCodeRanges(context); |
| if (ranges.InRange(address)) |
| output.Append(GetCurrentRowMarker() + " "); |
| else |
| output.Append(" "); // Spacer since there's no marker. |
| |
| output.Append(std::string(indent, ' ')); // Indentation. |
| AppendFunctionAndRanges(context, function, output); |
| |
| next_indent += 2; // When there's a function, indent the children. |
| } |
| |
| // Print child blocks. |
| for (auto& child : block->inner_blocks()) { |
| if (const CodeBlock* child_block = child.Get()->As<CodeBlock>()) |
| PrintInlineRecursive(context, address, child_block, next_indent, output); |
| } |
| } |
| |
| OutputBuffer DumpInlineTree(ProcessSymbols* process_symbols, uint64_t address, |
| const FormatSymbolOptions& opts) { |
| OutputBuffer out; |
| |
| auto chain = GetInlineChainAtAddress(process_symbols, address); |
| if (chain.has_error()) { |
| out.Append(chain.err()); |
| return out; |
| } |
| |
| const Function* function = chain.value().back().get(); |
| SymbolContext context = function->GetSymbolContext(process_symbols); |
| |
| // The containing function will be the first element of the inline chain. |
| PrintInlineRecursive(context, address, function, 0, out); |
| return out; |
| } |
| |
| OutputBuffer DumpLineTable(ProcessSymbols* process_symbols, uint64_t address, |
| const FormatSymbolOptions& opts) { |
| const LoadedModuleSymbols* loaded_module = process_symbols->GetModuleForAddress(address); |
| if (!loaded_module) |
| return OutputBuffer("The address " + to_hex_string(address) + " is not covered by a module.\n"); |
| SymbolContext symbol_context = loaded_module->symbol_context(); |
| |
| fxl::RefPtr<DwarfUnit> unit = |
| loaded_module->module_symbols()->GetDwarfUnit(symbol_context, address); |
| if (!unit) { |
| return OutputBuffer("This address " + to_hex_string(address) + |
| " is not covered by a compilation unit.\n"); |
| } |
| |
| const LineTable& line_table = unit->GetLineTable(); |
| containers::array_view<LineTable::Row> sequence = |
| line_table.GetRowSequenceForAddress(symbol_context, address); |
| if (sequence.empty()) |
| return OutputBuffer("No row sequence covers " + to_hex_string(address) + ".\n"); |
| |
| std::vector<std::vector<OutputBuffer>> table; |
| bool seen_address = false; |
| for (const auto& row : sequence) { |
| auto& line = table.emplace_back(); |
| |
| // Line marker and address. |
| Syntax syntax; |
| uint64_t absolute_line_addr = symbol_context.RelativeToAbsolute(row.Address.Address); |
| if (!seen_address && absolute_line_addr >= address) { |
| // Since the sequence is in order and contains the address, the first row that contains the |
| // address is the one in question. |
| seen_address = true; |
| syntax = Syntax::kHeading; |
| line.emplace_back(syntax, GetCurrentRowMarker()); |
| } else { |
| // No current row marker. |
| syntax = Syntax::kNormal; |
| line.emplace_back(); |
| } |
| |
| // Basic info. |
| line.emplace_back(syntax, to_hex_string(absolute_line_addr)); |
| if (auto file_name = line_table.GetFileNameForRow(row)) { |
| line.emplace_back(syntax, |
| process_symbols->target_symbols()->GetShortestUniqueFileName(*file_name)); |
| } else { |
| line.emplace_back(); |
| } |
| line.emplace_back(syntax, std::to_string(row.Line)); |
| |
| // Accumulate the flags. |
| std::string flags; |
| auto append_flag_if = [&flags](bool condition, const char* text) { |
| if (condition) { |
| if (!flags.empty()) |
| flags += " | "; |
| flags += text; |
| } |
| }; |
| append_flag_if(row.IsStmt, "IsStmt"); |
| append_flag_if(row.BasicBlock, "BasicBlock"); |
| append_flag_if(row.EndSequence, "EndSequence"); |
| append_flag_if(row.PrologueEnd, "PrologueEnd"); |
| append_flag_if(row.EpilogueBegin, "EpilogueBegin"); |
| line.emplace_back(syntax, flags); |
| } |
| |
| OutputBuffer result; |
| FormatTable({ColSpec(Align::kLeft, 0, "", 1), ColSpec(Align::kRight, 0, "Address"), |
| ColSpec(Align::kLeft, 0, "File"), ColSpec(Align::kRight, 0, "Line"), |
| ColSpec(Align::kLeft, 0, "Flags")}, |
| table, &result); |
| return result; |
| } |
| |
| Err RunVerbSymDebug(ConsoleContext* context, const Command& cmd) { |
| if (Err err = cmd.ValidateNouns({Noun::kProcess, Noun::kFrame}); err.has_error()) |
| return err; |
| if (Err err = AssertRunningTarget(context, "sym-debug", cmd.target()); err.has_error()) |
| return err; |
| |
| ErrOr<FormatSymbolOptions> opts = GetFormatSymbolOptionsFromCommand(cmd, kDwarfExprSwitch); |
| if (opts.has_error()) |
| return opts.err(); |
| |
| OutputBuffer (*dumper)(ProcessSymbols*, uint64_t, const FormatSymbolOptions&) = nullptr; |
| if (cmd.HasSwitch(kCallReturnSwitch)) { |
| dumper = &DumpCallReturn; |
| } else if (cmd.HasSwitch(kFunctionSwitch)) { |
| if (cmd.args().empty()) { |
| // When no address is given, use the function symbol from the current frame. This will handle |
| // the case of ambiguous inlines where just looking up the address won't get what the user |
| // sees as the current function. |
| if (const Frame* frame = cmd.frame()) { |
| if (!frame->GetLocation().symbol()) |
| return Err("No function symbol for current location."); |
| Console::get()->Output(FormatSymbol(cmd.target()->GetProcess()->GetSymbols(), |
| frame->GetLocation().symbol().Get(), opts.value())); |
| } else { |
| return Err("No current frame, please specify an address."); |
| } |
| return Err(); |
| } |
| |
| // Fall back on address lookup. when a parameter is given. |
| dumper = &DumpFunction; |
| } else if (cmd.HasSwitch(kInlineSwitch)) { |
| dumper = &DumpInlineChain; |
| } else if (cmd.HasSwitch(kInlineTreeSwitch)) { |
| dumper = &DumpInlineTree; |
| } else if (cmd.HasSwitch(kLineSwitch)) { |
| dumper = &DumpLineTable; |
| } else { |
| return Err( |
| "Missing a switch to indicate what to print.\n" |
| "See \"help sym-debug\" for available options."); |
| } |
| |
| if (cmd.args().empty()) { |
| // Use the current location. |
| if (!cmd.frame()) |
| return Err("No current frame, please specify an address."); |
| |
| Console::get()->Output( |
| dumper(cmd.target()->GetProcess()->GetSymbols(), cmd.frame()->GetAddress(), opts.value())); |
| return Err(); |
| } |
| |
| // Evaluate the expression to get the location. |
| return EvalCommandAddressExpression( |
| cmd, "sym-debug", GetEvalContextForCommand(cmd), |
| [weak_process = cmd.target()->GetProcess()->GetWeakPtr(), dumper, opts = opts.value()]( |
| const Err& err, uint64_t address, std::optional<uint64_t> size) { |
| Console* console = Console::get(); |
| if (err.has_error()) { |
| console->Output(err); // Evaluation error. |
| return; |
| } |
| if (!weak_process) { |
| // Process has been destroyed during evaluation. Normally a message will be printed when |
| // that happens so we can skip reporting the error. |
| return; |
| } |
| |
| console->Output(dumper(weak_process->GetSymbols(), address, opts)); |
| }); |
| } |
| |
| } // namespace |
| |
| VerbRecord GetSymDebugVerbRecord() { |
| VerbRecord sym_debug(&RunVerbSymDebug, {"sym-debug"}, kSymDebugShortHelp, kSymDebugHelp, |
| CommandGroup::kSymbol); |
| sym_debug.param_type = VerbRecord::kOneParam; |
| |
| SwitchRecord call_return_switch(kCallReturnSwitch, false, "call-return", 'r'); |
| SwitchRecord func_switch(kFunctionSwitch, false, "function", 'f'); |
| SwitchRecord inline_switch(kInlineSwitch, false, "inlines", 'i'); |
| SwitchRecord inline_tree_switch(kInlineTreeSwitch, false, "inline-tree", 't'); |
| SwitchRecord line_switch(kLineSwitch, false, "line", 'l'); |
| SwitchRecord dwarf_expr_switch(kDwarfExprSwitch, true, "dwarf-expr"); |
| sym_debug.switches = {call_return_switch, func_switch, inline_switch, |
| inline_tree_switch, line_switch, dwarf_expr_switch}; |
| |
| return sym_debug; |
| } |
| |
| } // namespace zxdb |