blob: a887b91b5c2c0937a7e3e1f556bc11d87cb33fef [file] [log] [blame]
// 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_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/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 kInlineSwitch = 1;
constexpr int kInlineTreeSwitch = 2;
constexpr int kLineSwitch = 3;
const char kSymDebugShortHelp[] = "sym-debug: Print debug symbol information.";
const char kSymDebugHelp[] =
R"(sym-debug ( -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.
Options
--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.
Examples
sym-debug -l
sym-debug -i 0x56cfe7b4
)";
// 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) {
// 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()->AsFunction();
if (!func)
return Err("No function at address " + to_hex_string(address) + ".\n");
return func->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 DumpInlineChain(ProcessSymbols* process_symbols, uint64_t address) {
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->AsFunction();
// 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()->AsCodeBlock())
PrintInlineRecursive(context, address, child_block, next_indent, output);
}
}
OutputBuffer DumpInlineTree(ProcessSymbols* process_symbols, uint64_t address) {
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 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}); err.has_error())
return err;
if (Err err = AssertRunningTarget(context, "sym-debug", cmd.target()); err.has_error())
return err;
OutputBuffer (*dumper)(ProcessSymbols*, uint64_t) = nullptr;
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()));
return Err();
}
// Evaluate the expression to get the location.
return EvalCommandAddressExpression(
cmd, "sym-debug", GetEvalContextForCommand(cmd),
[weak_process = cmd.target()->GetProcess()->GetWeakPtr(), dumper](
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));
});
}
} // namespace
VerbRecord GetSymDebugVerbRecord() {
VerbRecord sym_debug(&RunVerbSymDebug, {"sym-debug"}, kSymDebugShortHelp, kSymDebugHelp,
CommandGroup::kSymbol);
sym_debug.param_type = VerbRecord::kOneParam;
SwitchRecord inline_switch(kInlineSwitch, false, "inlines", 'i');
SwitchRecord inline_tree_switch(kInlineTreeSwitch, false, "inline-tree", 't');
SwitchRecord line_switch(kLineSwitch, false, "line", 'l');
sym_debug.switches = {inline_switch, inline_tree_switch, line_switch};
return sym_debug;
}
} // namespace zxdb