blob: 06ed1efac15ffa2ec249c044b804998e46385682 [file] [log] [blame]
// 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_disassemble.h"
#include "src/developer/debug/zxdb/client/arch_info.h"
#include "src/developer/debug/zxdb/client/frame.h"
#include "src/developer/debug/zxdb/client/memory_dump.h"
#include "src/developer/debug/zxdb/client/process.h"
#include "src/developer/debug/zxdb/client/session.h"
#include "src/developer/debug/zxdb/client/source_file_provider_impl.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_context.h"
#include "src/developer/debug/zxdb/console/input_location_parser.h"
#include "src/developer/debug/zxdb/console/output_buffer.h"
#include "src/developer/debug/zxdb/console/verbs.h"
#include "src/developer/debug/zxdb/symbols/code_block.h"
#include "src/developer/debug/zxdb/symbols/location.h"
namespace zxdb {
namespace {
constexpr int kNumSwitch = 1;
constexpr int kRawSwitch = 2;
const char kDisassembleShortHelp[] = "disassemble / di: Disassemble machine instructions.";
const char kDisassembleHelp[] =
R"(disassemble [ --num=<lines> ] [ --raw ] [ <location> ]
Alias: "di"
Disassembles machine instructions at the given location. If no location is
given, the instruction pointer of the thread/frame will be used. If the
thread is not stopped, you must specify a start address.
Location arguments
)" LOCATION_ARG_HELP("disassemble") LOCATION_EXPRESSION_HELP("disassemble")
R"( It is the user's responsibility to make sure that the starting address
expression is appropriately aligned on an instruction boundary. For ARM
this will be multiples of 4 bytes. For Intel, you will have to know
some other way.
Arguments
--num=<lines> | -n <lines>
The number of lines/instructions to emit. Defaults to the instructions
in the given function (if the location is a function name), or 16
otherwise.
--raw | -r
Output raw bytes in addition to the decoded instructions.
Examples
di
disassemble
Disassembles starting at the current thread's instruction pointer.
thread 3 disassemble -n 128
Disassembles 128 instructions starting at thread 3's instruction
pointer.
di MyClass::MyFunc
Disassembles the given function.
frame 3 disassemble
thread 2 frame 3 disassemble
Disassembles starting at the thread's "frame 3" instruction pointer
(which will be the call return address).
process 1 disassemble 0x7b851239a0
disassemble *$pc - 0x10
Disassembles instructions in process 1 starting at the given address.
)";
// Completion callback after reading process memory.
void CompleteDisassemble(const Err& err, MemoryDump dump, fxl::WeakPtr<Process> weak_process,
const FormatAsmOpts& options) {
Console* console = Console::get();
if (err.has_error()) {
console->Output(err);
return;
}
if (!weak_process)
return; // Give up if the process went away.
OutputBuffer out;
Err format_err =
FormatAsmContext(weak_process->session()->arch_info(), dump, options, weak_process.get(),
SourceFileProviderImpl(weak_process->GetTarget()->settings()), &out);
if (format_err.has_error()) {
console->Output(err);
return;
}
console->Output(out);
}
Err RunDisassembleVerb(ConsoleContext* context, const Command& cmd) {
// Can take process overrides (to specify which process to read) and thread and frame ones (to
// specify which thread to read the instruction pointer from).
if (Err err = cmd.ValidateNouns({Noun::kProcess, Noun::kThread, Noun::kFrame}); err.has_error())
return err;
if (Err err = AssertRunningTarget(context, "disassemble", cmd.target()); err.has_error())
return err;
FormatAsmOpts options;
options.emit_addresses = true;
if (cmd.frame())
options.active_address = cmd.frame()->GetAddress();
// We may want to add an option for this.
options.include_source = true;
// Num argument (optional).
//
// When there is no known byte size, compute the max bytes requires to get the requested
// instructions. It doesn't matter if we request more memory than necessary so use a high bound.
size_t size = 0;
bool size_is_default = false; // Indicates size may be overridden below.
if (cmd.HasSwitch(kNumSwitch)) {
// Num lines explicitly given.
uint64_t num_instr = 0;
if (Err err = StringToUint64(cmd.GetSwitchValue(kNumSwitch), &num_instr); err.has_error())
return err;
options.max_instructions = num_instr;
size = options.max_instructions * context->session()->arch_info().max_instr_len();
} else {
// Default instruction count when no symbol and no explicit size is given.
options.max_instructions = 16;
size = options.max_instructions * context->session()->arch_info().max_instr_len();
size_is_default = true;
}
// Show bytes.
options.emit_bytes = cmd.HasSwitch(kRawSwitch);
Process* process = cmd.target()->GetProcess();
auto weak_process = process->GetWeakPtr();
if (cmd.args().size() > 1) {
return Err("\"disassemble\" requires exactly one argument specifying a location.");
} else if (cmd.args().empty()) {
// No args: implicitly read the frame's instruction pointer.
//
// TODO(brettw) by default it would be nice if this showed a few lines of disassembly before the
// given address. Going backwards in x86 can be dicey though, the formatter may have to
// guess-and-check about a good starting boundary for the dump.
Frame* frame = cmd.frame();
if (!frame) {
return Err(
"There is no frame to read the instruction pointer from. The thread\n"
"must be stopped to use the implicit current address. Otherwise,\n"
"you must supply an explicit address to disassemble.");
}
Location location = frame->GetLocation();
// Schedule memory request.
process->ReadMemory(
location.address(), size, [options, weak_process](const Err& err, MemoryDump dump) {
CompleteDisassemble(err, std::move(dump), std::move(weak_process), options);
});
} else {
// One arg: parse as an input location.
EvalLocalInputLocation(
GetEvalContextForCommand(cmd), cmd.frame(), cmd.args()[0],
[options, size, size_is_default, weak_process](ErrOr<std::vector<InputLocation>> locs,
std::optional<uint32_t> expr_size) mutable {
Console* console = Console::get();
if (locs.has_error()) {
console->Output(locs.err());
return;
}
if (!weak_process) {
console->Output(Err("Process terminated."));
return;
}
Location location;
if (Err err = ResolveUniqueInputLocation(weak_process->GetSymbols(), locs.value(), true,
&location);
err.has_error()) {
console->Output(err);
return;
}
// Some symbols can give us sizes which we will prefer to use instead of the default size.
// All input locations will have the same type (matching the user input type).
if (size_is_default && locs.value()[0].type == InputLocation::Type::kName) {
if (location.symbol()) {
if (const CodeBlock* block = location.symbol().Get()->As<CodeBlock>()) {
size = block->GetFullRange(location.symbol_context()).size();
options.max_instructions = 0; // No instruction limit.
}
}
}
// Schedule memory request.
weak_process->ReadMemory(
location.address(), size, [options, weak_process](const Err& err, MemoryDump dump) {
CompleteDisassemble(err, std::move(dump), std::move(weak_process), options);
});
});
}
return Err();
}
} // namespace
VerbRecord GetDisassembleVerbRecord() {
VerbRecord disass(&RunDisassembleVerb, &CompleteInputLocation, {"disassemble", "di"},
kDisassembleShortHelp, kDisassembleHelp, CommandGroup::kAssembly,
SourceAffinity::kAssembly);
disass.param_type = VerbRecord::kOneParam; // Don't require quoting for expressions.
disass.switches.emplace_back(kNumSwitch, true, "num", 'n');
disass.switches.emplace_back(kRawSwitch, false, "raw", 'r');
return disass;
}
} // namespace zxdb