blob: bc54deb363ddeb20cc7a5ad535563083c4b069aa [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/format_context.h"
#include <algorithm>
#include <vector>
#include "garnet/bin/zxdb/client/arch_info.h"
#include "garnet/bin/zxdb/client/disassembler.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/common/file_util.h"
#include "garnet/bin/zxdb/console/console.h"
#include "garnet/bin/zxdb/console/format_table.h"
#include "garnet/bin/zxdb/console/output_buffer.h"
#include "garnet/bin/zxdb/console/source_util.h"
#include "garnet/bin/zxdb/console/string_util.h"
#include "garnet/bin/zxdb/symbols/location.h"
#include "lib/fxl/files/file.h"
#include "lib/fxl/strings/string_printf.h"
namespace zxdb {
namespace {
using LineInfo = std::pair<int, std::string>; // Line #, Line contents.
using LineVector = std::vector<LineInfo>;
// Formats the given line, highlighting from the column to the end of the line.
// The column is 1-based but we also accept 0.
OutputBuffer HighlightLine(std::string str, int column) {
// Convert to 0-based with clamping (since the offsets come from symbols,
// they could be invalid).
int str_size = static_cast<int>(str.size());
int col_index = std::min(std::max(0, column - 1), str_size);
OutputBuffer result;
if (col_index == 0) {
result.Append(Syntax::kHeading, std::move(str));
} else {
result.Append(Syntax::kNormal, str.substr(0, col_index));
if (col_index < str_size)
result.Append(Syntax::kHeading, str.substr(col_index));
}
return result;
}
} // namespace
Err OutputSourceContext(Process* process, const Location& location,
SourceAffinity source_affinity) {
if (source_affinity != SourceAffinity::kAssembly &&
location.file_line().is_valid()) {
// Synchronous source output.
FormatSourceOpts source_opts;
source_opts.active_line = location.file_line().line();
source_opts.highlight_line = source_opts.active_line;
source_opts.highlight_column = location.column();
source_opts.first_line = source_opts.active_line - 2;
source_opts.last_line = source_opts.active_line + 2;
source_opts.dim_others = true;
OutputBuffer out;
Err err = FormatSourceFileContext(
location.file_line().file(),
process->session()->system().GetSymbols()->build_dir(), source_opts,
&out);
if (err.has_error())
return err;
Console::get()->Output(out);
} else {
// Fall back to disassembly.
FormatAsmOpts options;
options.emit_addresses = true;
options.emit_bytes = false;
options.active_address = location.address();
uint64_t start_address;
const ArchInfo* arch_info = process->session()->arch_info();
if (arch_info->is_fixed_instr()) {
// Fixed instruction length, back up 2 instructions to provide context.
start_address = location.address() - 2 * arch_info->max_instr_len();
options.max_instructions = 5;
} else {
// Variable length instructions. Since this code path is triggered when
// there are no symbols, we can't back up reliably. Just disassemble
// starting from the current location.
//
// In the future it might be nice to keep some record of recently stepped
// instructions since usually this address will be the one after the one
// that was just stepped.
start_address = location.address();
options.max_instructions = 4;
}
size_t size = options.max_instructions * arch_info->max_instr_len();
process->ReadMemory(
start_address, size, [ options, weak_process = process->GetWeakPtr() ](
const Err& in_err, MemoryDump dump) {
if (!weak_process)
return; // Give up when the process went away.
Console* console = Console::get();
if (in_err.has_error()) {
console->Output(in_err);
return;
}
OutputBuffer out;
Err err = FormatAsmContext(weak_process->session()->arch_info(), dump,
options, &out);
if (err.has_error())
console->Output(err);
else
console->Output(out);
});
}
return Err();
}
// This doesn't cache the file contents. We may want to add that for
// performance, but we should be careful to always pick the latest version
// since it can get updated.
Err FormatSourceFileContext(const std::string& file_name,
const std::string& build_dir,
const FormatSourceOpts& opts, OutputBuffer* out) {
std::string contents;
Err err = GetFileContents(file_name, build_dir, &contents);
if (err.has_error())
return err;
return FormatSourceContext(file_name, contents, opts, out);
}
Err FormatSourceContext(const std::string& file_name_for_errors,
const std::string& file_contents,
const FormatSourceOpts& opts, OutputBuffer* out) {
FXL_DCHECK(opts.active_line == 0 || !opts.require_active_line ||
(opts.active_line >= opts.first_line &&
opts.active_line <= opts.last_line));
// Allow the beginning to be out-of-range. This mirrors the end handling
// (clamped to end-of-file) so callers can blindly create offsets from
// a current line without clamping.
int first_line = std::max(1, opts.first_line);
std::vector<std::string> context =
ExtractSourceLines(file_contents, first_line, opts.last_line);
if (context.empty()) {
// No source found for this location. If highlight_line exists, assume
// it's the one the user cares about.
int err_line = opts.highlight_line ? opts.highlight_line : first_line;
return Err(fxl::StringPrintf("There is no line %d in the file %s", err_line,
file_name_for_errors.c_str()));
}
if (opts.active_line != 0 && opts.require_active_line &&
first_line + static_cast<int>(context.size()) < opts.active_line) {
return Err(fxl::StringPrintf("There is no line %d in the file %s",
opts.active_line,
file_name_for_errors.c_str()));
}
std::vector<std::vector<OutputBuffer>> rows;
for (size_t i = 0; i < context.size(); i++) {
int line_number = first_line + i;
std::string& line_text = context[i]; // Moved out of.
rows.emplace_back();
std::vector<OutputBuffer>& row = rows.back();
// Compute markers in the left margin.
OutputBuffer margin;
auto found_bp = opts.bp_lines.find(line_number);
if (found_bp != opts.bp_lines.end()) {
std::string breakpoint_marker = found_bp->second
? GetBreakpointMarker()
: GetDisabledBreakpointMarker();
if (line_number == opts.active_line) {
// Active + breakpoint.
margin.Append(Syntax::kError, breakpoint_marker);
margin.Append(Syntax::kHeading, GetRightArrow());
} else {
// Breakpoint.
margin.Append(Syntax::kError, " " + breakpoint_marker);
}
} else {
if (line_number == opts.active_line) {
// Active line.
margin.Append(Syntax::kHeading, " " + GetRightArrow());
} else {
// Inactive line with no breakpoint.
margin.Append(" ");
}
}
row.push_back(std::move(margin));
std::string number = fxl::StringPrintf("%d", line_number);
if (line_number == opts.highlight_line) {
// This is the line to mark.
row.emplace_back(Syntax::kHeading, std::move(number));
row.push_back(HighlightLine(std::move(line_text), opts.highlight_column));
} else {
// Normal context line.
Syntax syntax = opts.dim_others ? Syntax::kComment : Syntax::kNormal;
row.emplace_back(syntax, std::move(number));
row.emplace_back(syntax, std::move(line_text));
}
}
FormatTable({ColSpec(Align::kLeft), ColSpec(Align::kRight),
ColSpec(Align::kLeft, 0, std::string(), 0)},
rows, out);
return Err();
}
Err FormatAsmContext(const ArchInfo* arch_info, const MemoryDump& dump,
const FormatAsmOpts& opts, OutputBuffer* out) {
// Make the disassembler.
Disassembler disassembler;
Err my_err = disassembler.Init(arch_info);
if (my_err.has_error())
return my_err;
Disassembler::Options options;
std::vector<Disassembler::Row> rows;
disassembler.DisassembleDump(dump, dump.address(), options,
opts.max_instructions, &rows);
std::vector<std::vector<OutputBuffer>> table;
for (auto& row : rows) {
table.emplace_back();
std::vector<OutputBuffer>& out_row = table.back();
// Compute markers in the left margin.
OutputBuffer margin;
auto found_bp = opts.bp_addrs.find(row.address);
if (found_bp != opts.bp_addrs.end()) {
std::string breakpoint_marker = found_bp->second
? GetBreakpointMarker()
: GetDisabledBreakpointMarker();
if (row.address == opts.active_address) {
// Active + breakpoint.
margin.Append(Syntax::kError, breakpoint_marker);
margin.Append(Syntax::kHeading, GetRightArrow());
} else {
// Breakpoint.
margin.Append(Syntax::kError, " " + breakpoint_marker);
}
} else {
if (row.address == opts.active_address) {
// Active line.
margin.Append(Syntax::kHeading, " " + GetRightArrow());
} else {
// Inactive line with no breakpoint.
margin.Append(" ");
}
}
out_row.push_back(std::move(margin));
if (opts.emit_addresses) {
out_row.emplace_back(Syntax::kComment,
fxl::StringPrintf("0x%" PRIx64, row.address));
}
if (opts.emit_bytes) {
std::string bytes_str;
for (size_t i = 0; i < row.bytes.size(); i++) {
if (i > 0)
bytes_str.push_back(' ');
bytes_str.append(fxl::StringPrintf("%2.2x", row.bytes[i]));
}
out_row.emplace_back(Syntax::kComment, std::move(bytes_str));
}
Syntax op_param_syntax =
row.address == opts.active_address ? Syntax::kHeading : Syntax::kNormal;
out_row.emplace_back(op_param_syntax, std::move(row.op));
out_row.emplace_back(op_param_syntax, std::move(row.params));
out_row.emplace_back(Syntax::kComment, std::move(row.comment));
}
std::vector<ColSpec> spec;
spec.emplace_back(Align::kLeft); // Margin.
if (opts.emit_addresses)
spec.emplace_back(Align::kRight);
if (opts.emit_bytes) {
// Max out the bytes @ 17 cols (holds 6 bytes) to keep it from pushing
// things too far over in the common case.
spec.emplace_back(Align::kLeft, 17, std::string(), 1);
}
// When there was an address or byte listing, put 1 extra column of space
// to separate the opcode. Otherwise keep it by the left margin.
if (spec.size() > 1)
spec.emplace_back(Align::kLeft, 0, std::string(), 1); // Instructions.
else
spec.emplace_back(Align::kLeft, 0, std::string(), 0); // Instructions.
// Params. Some can be very long so provide a max so the comments don't get
// pushed too far out.
spec.emplace_back(Align::kLeft, 10, std::string(), 1);
spec.emplace_back(Align::kLeft); // Comments.
FormatTable(spec, table, out);
return Err();
}
Err FormatBreakpointContext(const Location& location,
const std::string& build_dir, bool enabled,
OutputBuffer* out) {
if (!location.has_symbols())
return Err("No symbols for this location.");
int line = location.file_line().line();
constexpr int kBreakpointContext = 1;
FormatSourceOpts opts;
opts.first_line = line - kBreakpointContext;
opts.last_line = line + kBreakpointContext;
opts.highlight_line = line;
opts.bp_lines[line] = enabled;
return FormatSourceFileContext(location.file_line().file(), build_dir, opts,
out);
}
} // namespace zxdb