blob: 8a82b62cd29a26ad1bb4253eaf5b145445f95ae3 [file] [log] [blame]
// Copyright 2018 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 "garnet/bin/zxdb/console/verbs.h"
#include <iomanip>
#include <sstream>
#include "garnet/bin/zxdb/client/arch_info.h"
#include "garnet/bin/zxdb/client/disassembler.h"
#include "garnet/bin/zxdb/client/frame.h"
#include "garnet/bin/zxdb/client/memory_dump.h"
#include "garnet/bin/zxdb/client/process.h"
#include "garnet/bin/zxdb/client/session.h"
#include "garnet/bin/zxdb/client/system.h"
#include "garnet/bin/zxdb/client/target.h"
#include "garnet/bin/zxdb/common/err.h"
#include "garnet/bin/zxdb/console/analyze_memory.h"
#include "garnet/bin/zxdb/console/command.h"
#include "garnet/bin/zxdb/console/command_utils.h"
#include "garnet/bin/zxdb/console/console.h"
#include "garnet/bin/zxdb/console/format_context.h"
#include "garnet/bin/zxdb/console/format_memory.h"
#include "garnet/bin/zxdb/console/format_table.h"
#include "garnet/bin/zxdb/console/input_location_parser.h"
#include "garnet/bin/zxdb/console/output_buffer.h"
#include "garnet/bin/zxdb/symbols/code_block.h"
#include "garnet/bin/zxdb/symbols/location.h"
#include "lib/fxl/strings/string_printf.h"
namespace zxdb {
namespace {
constexpr int kSizeSwitch = 1;
constexpr int kNumSwitch = 2;
constexpr int kOffsetSwitch = 3;
constexpr int kRawSwitch = 4;
// Gives 20 lines of output which fits on a terminal without scrolling (plus
// one line of help text, the next prompt, and the command itself).
constexpr uint32_t kDefaultAnalyzeByteSize = 160;
// Shared for commands that take both a num (lines, 8 bytes each), or a byte
// size.
Err ReadNumAndSize(const Command& cmd, uint32_t default_size,
uint32_t* out_size) {
if (cmd.HasSwitch(kNumSwitch) && cmd.HasSwitch(kSizeSwitch))
return Err("Can't specify both --num and --size.");
if (cmd.HasSwitch(kSizeSwitch)) {
// Size argument.
Err err = StringToUint32(cmd.GetSwitchValue(kSizeSwitch), out_size);
if (err.has_error())
return err;
} else if (cmd.HasSwitch(kNumSwitch)) {
// Num lines argument.
Err err = StringToUint32(cmd.GetSwitchValue(kNumSwitch), out_size);
if (err.has_error())
return err;
*out_size *= sizeof(uint64_t); // Convert pointer count to size.
} else {
*out_size = default_size;
}
return Err();
}
// Converts argument 0 (required or it will produce an error) and converts it
// to a unique location (or error). If the input indicates a thing that has
// an intrinsic size like a function name, the size will be placed in
// *location_size. Otherwise, *location_size will be 0.
//
// The command_name is used for writing the current command to error messages.
Err ReadLocation(const Command& cmd, const char* command_name,
Location* location, uint64_t* location_size) {
*location_size = 0;
if (cmd.args().size() != 1) {
return Err("%s requires exactly one argument specifying a location.",
command_name);
}
// We need to check the type of the parsed input location so parse and
// resolve in two steps.
InputLocation input_location;
Err err = ParseInputLocation(cmd.frame(), cmd.args()[0], &input_location);
if (err.has_error())
return err;
err = ResolveUniqueInputLocation(cmd.target()->GetProcess()->GetSymbols(),
input_location, true, location);
if (err.has_error())
return err;
// Some symbols can give us sizes.
if (input_location.type == InputLocation::Type::kSymbol) {
if (location->symbol()) {
if (const CodeBlock* block = location->symbol().Get()->AsCodeBlock()) {
*location_size = block->GetFullRange(location->symbol_context()).size();
}
}
}
return Err();
}
// stack -----------------------------------------------------------------------
const char kStackShortHelp[] = "stack / st: Analyze the stack.";
const char kStackHelp[] =
R"(stack [ --offset=<offset> ] [ --num=<lines> ] [ --size=<bytes> ]
[ <address> ]
Alias: "st"
Prints a stack analysis. This is a special case of "mem-analyze" that
defaults to showing the memory address starting at the current frame's stack
pointer, and annotates the values with the current thread's registers and
stack frames.
An explicit address can optionally be provided to begin dumping to dump at
somewhere other than the current frame's stack pointer, or you can provide an
--offset from the current stack position.
Arguments
--num=<lines> | -n <lines>
The number of output lines. Each line is the size of one pointer, so
the amount of memory displayed on a 64-bit system will be 8 × num_lines.
Mutually exclusive with --size.
--offset=<offset> | -o <offset>
Offset from the stack pointer to begin dumping. Mutually exclusive with
<address>.
--size=<bytes> | -s <bytes>
The number of bytes to analyze. This will be rounded up to the nearest
pointer boundary. Mutually exclusive with --num.
Examples
stack
thread 2 stack
stack --num=128 0x43011a14bfc8
)";
Err DoStack(ConsoleContext* context, const Command& cmd) {
// FIXME(brettw) should be AssertStoppedThreadCommand like "finish".
if (!cmd.frame())
return Err("Can't analyze the stack without a valid frame.");
AnalyzeMemoryOptions opts;
opts.process = cmd.target()->GetProcess();
opts.thread = cmd.thread();
// Begin address.
if (cmd.args().size() == 1) {
// Explicitly provided start address.
Err err = StringToUint64(cmd.args()[0], &opts.begin_address);
if (err.has_error())
return err;
} else if (cmd.args().size() > 1) {
return Err("Too many args to \"stack\", expecting 0 or 1.");
} else {
// Use implicit SP from the frame (with optional --offset).
opts.begin_address = cmd.frame()->GetStackPointer();
if (cmd.HasSwitch(kOffsetSwitch)) {
int offset = 0;
Err err = StringToInt(cmd.GetSwitchValue(kOffsetSwitch), &offset);
if (err.has_error())
return err;
opts.begin_address += offset;
}
}
// Length parameters.
Err err = ReadNumAndSize(cmd, kDefaultAnalyzeByteSize, &opts.bytes_to_read);
if (err.has_error())
return err;
AnalyzeMemory(opts, [bytes_to_read = opts.bytes_to_read](const Err& err,
OutputBuffer output,
uint64_t next_addr) {
if (err.has_error()) {
output.Append(err);
} else {
// Help text for continuation.
output.Append(
Syntax::kComment,
fxl::StringPrintf("↓ For more lines: stack -n %d 0x%" PRIx64,
static_cast<int>(bytes_to_read / sizeof(uint64_t)),
next_addr));
}
Console::get()->Output(std::move(output));
});
return Err();
}
// mem-analyze -----------------------------------------------------------------
const char kMemAnalyzeShortHelp[] =
"mem-analyze / ma: Analyze a memory region.";
const char kMemAnalyzeHelp[] =
R"(mem-analyze [ --num=<lines> ] [ --size=<size> ] <location>
Alias: "ma"
Prints a memory analysis. A memory analysis attempts to find pointers to
code in pointer-aligned locations and annotates those values.
See also "stack" which is specialized more for stacks (it includes the
current thread's registers), and "mem-read" to display a simple hex dump.
Location arguments
)" LOCATION_ARG_HELP("mem-analyze")
R"(
Arguments
--num=<lines> | -n <lines>
The number of output lines. Each line is the size of one pointer, so
the amount of memory displayed on a 64-bit system will be 8 × num_lines.
Mutually exclusive with --size.
--size=<bytes> | -s <bytes>
The number of bytes to analyze. This will be rounded up to the nearest
pointer boundary. Mutually exclusive with --num.
Examples
ma 0x43011a14bfc8
mem-analyze 0x43011a14bfc8
process 3 mem-analyze 0x43011a14bfc8
mem-analyze --num=128 0x43011a14bfc8
)";
Err DoMemAnalyze(ConsoleContext* context, const Command& cmd) {
// Only a process can have its memory read.
Err err = cmd.ValidateNouns({Noun::kProcess});
if (err.has_error())
return err;
err = AssertRunningTarget(context, "mem-analyze", cmd.target());
if (err.has_error())
return err;
AnalyzeMemoryOptions opts;
opts.process = cmd.target()->GetProcess();
// Begin address.
if (cmd.args().size() == 1) {
// Explicitly provided start address.
err = StringToUint64(cmd.args()[0], &opts.begin_address);
if (err.has_error())
return err;
} else if (cmd.args().size() > 1) {
return Err("mam-analyze requires exactly one arg for the start address.");
}
// Length parameters.
err = ReadNumAndSize(cmd, kDefaultAnalyzeByteSize, &opts.bytes_to_read);
if (err.has_error())
return err;
AnalyzeMemory(opts, [bytes_to_read = opts.bytes_to_read](const Err& err,
OutputBuffer output,
uint64_t next_addr) {
if (err.has_error()) {
output.Append(err);
} else {
// Help text for continuation.
output.Append(
Syntax::kComment,
fxl::StringPrintf("↓ For more lines: ma -n %d 0x%" PRIx64,
static_cast<int>(bytes_to_read / sizeof(uint64_t)),
next_addr));
}
Console::get()->Output(std::move(output));
});
return Err();
}
// mem-read --------------------------------------------------------------------
void MemoryReadComplete(const Err& err, MemoryDump dump) {
OutputBuffer out;
if (err.has_error()) {
out.Append(err);
} else {
MemoryFormatOptions opts;
opts.show_addrs = true;
opts.show_ascii = true;
opts.values_per_line = 16;
opts.separator_every = 8;
out.Append(FormatMemory(dump, dump.address(),
static_cast<uint32_t>(dump.size()), opts));
}
Console::get()->Output(std::move(out));
}
const char kMemReadShortHelp[] =
R"(mem-read / x: Read memory from debugged process.)";
const char kMemReadHelp[] =
R"(mem-read [ --size=<bytes> ] <location>
Alias: "x"
Reads memory from the process at the given address and prints it to the
screen. Currently, only a byte-oriented hex dump format is supported.
See also "a-mem" to print a memory analysis and "a-stack" to print a more
useful dump of the raw stack.
Location arguments
)" LOCATION_ARG_HELP("mem-analyze")
R"(
Arguments
--size=<bytes> | -s <bytes>
Bytes to read. This defaults to the size of the function if a function
name is given as the location, or 64 otherwise.
Examples
x --size=128 0x75f19ba
mem-read --size=16 0x8f1763a7
process 3 mem-read 83242384560
process 3 mem-read main
)";
Err DoMemRead(ConsoleContext* context, const Command& cmd) {
// Only a process can have its memory read.
Err err = cmd.ValidateNouns({Noun::kProcess});
if (err.has_error())
return err;
err = AssertRunningTarget(context, "mem-read", cmd.target());
if (err.has_error())
return err;
// Address (required).
Location location;
uint64_t location_size = 0;
err = ReadLocation(cmd, "mem-read", &location, &location_size);
if (err.has_error())
return err;
// Size argument (optional).
uint64_t size = 64;
if (cmd.HasSwitch(kSizeSwitch)) {
err = StringToUint64(cmd.GetSwitchValue(kSizeSwitch), &size);
if (err.has_error())
return err;
} else if (location_size) {
// Default to the size of the symbol.
size = static_cast<uint32_t>(location_size);
}
cmd.target()->GetProcess()->ReadMemory(location.address(), size,
&MemoryReadComplete);
return Err();
}
// disassemble -----------------------------------------------------------------
// 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, &out);
if (format_err.has_error()) {
console->Output(err);
return;
}
console->Output(std::move(out));
}
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("mem-analyze")
R"(
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
Disassembles instructions in process 1 starting at the given address.
)";
Err DoDisassemble(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).
Err err = cmd.ValidateNouns({Noun::kProcess, Noun::kThread, Noun::kFrame});
if (err.has_error())
return err;
err = AssertRunningTarget(context, "disassemble", cmd.target());
if (err.has_error())
return err;
Location location;
uint64_t location_size = 0;
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 = frame->GetLocation();
} else {
err = ReadLocation(cmd, "disassemble", &location, &location_size);
if (err.has_error())
return err;
}
FormatAsmOpts options;
options.emit_addresses = true;
if (cmd.frame())
options.active_address = cmd.frame()->GetAddress();
// 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;
if (cmd.HasSwitch(kNumSwitch)) {
// Num lines explicitly given.
uint64_t num_instr = 0;
err = StringToUint64(cmd.GetSwitchValue(kNumSwitch), &num_instr);
if (err.has_error())
return err;
options.max_instructions = num_instr;
size = options.max_instructions *
context->session()->arch_info()->max_instr_len();
} else if (location_size > 0) {
// Byte size is known.
size = location_size;
} 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();
}
// Show bytes.
options.emit_bytes = cmd.HasSwitch(kRawSwitch);
// Schedule memory request.
Process* process = cmd.target()->GetProcess();
process->ReadMemory(
location.address(), size, [ options, process = process->GetWeakPtr() ](
const Err& err, MemoryDump dump) {
CompleteDisassemble(err, std::move(dump), std::move(process), options);
});
return Err();
}
} // namespace
void AppendMemoryVerbs(std::map<Verb, VerbRecord>* verbs) {
SwitchRecord size_switch(kSizeSwitch, true, "size", 's');
SwitchRecord num_switch(kNumSwitch, true, "num", 'n');
// Disassemble.
VerbRecord disass(&DoDisassemble, {"disassemble", "di"},
kDisassembleShortHelp, kDisassembleHelp,
CommandGroup::kAssembly, SourceAffinity::kAssembly);
disass.switches.push_back(num_switch);
disass.switches.push_back(SwitchRecord(kRawSwitch, false, "raw", 'r'));
(*verbs)[Verb::kDisassemble] = std::move(disass);
// Mem-analyze
VerbRecord mem_analyze(&DoMemAnalyze, {"mem-analyze", "ma"},
kMemAnalyzeShortHelp, kMemAnalyzeHelp,
CommandGroup::kQuery);
mem_analyze.switches.push_back(num_switch);
mem_analyze.switches.push_back(size_switch);
(*verbs)[Verb::kMemAnalyze] = std::move(mem_analyze);
// Mem-read. Note: "x" is the GDB command to read memory.
VerbRecord mem_read(&DoMemRead, {"mem-read", "x"}, kMemReadShortHelp,
kMemReadHelp, CommandGroup::kQuery);
mem_read.switches.push_back(size_switch);
(*verbs)[Verb::kMemRead] = std::move(mem_read);
// Stack.
VerbRecord stack(&DoStack, {"stack", "st"}, kStackShortHelp, kStackHelp,
CommandGroup::kQuery);
stack.switches.push_back(num_switch);
stack.switches.push_back(size_switch);
stack.switches.push_back(SwitchRecord(kOffsetSwitch, true, "offset", 'o'));
(*verbs)[Verb::kStack] = std::move(stack);
}
} // namespace zxdb