blob: 7ad4a11d977ebd8ab0f89f43b2ee1b93c02004f0 [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_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