| // 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 "src/developer/debug/zxdb/console/format_register.h" |
| |
| #include <lib/syslog/cpp/macros.h> |
| #include <stdlib.h> |
| |
| #include <algorithm> |
| #include <map> |
| |
| #include "src/developer/debug/shared/regex.h" |
| #include "src/developer/debug/shared/register_id.h" |
| #include "src/developer/debug/shared/register_info.h" |
| #include "src/developer/debug/zxdb/client/session.h" |
| #include "src/developer/debug/zxdb/common/err.h" |
| #include "src/developer/debug/zxdb/common/string_util.h" |
| #include "src/developer/debug/zxdb/console/console.h" |
| #include "src/developer/debug/zxdb/console/format_register_arm64.h" |
| #include "src/developer/debug/zxdb/console/format_register_x64.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_formatters.h" |
| #include "src/developer/debug/zxdb/expr/abi_null.h" |
| #include "src/developer/debug/zxdb/expr/eval_context_impl.h" |
| #include "src/developer/debug/zxdb/expr/format.h" |
| #include "src/developer/debug/zxdb/expr/format_options.h" |
| #include "src/lib/fxl/strings/string_printf.h" |
| |
| namespace zxdb { |
| |
| namespace { |
| |
| void FormatCategory(const FormatRegisterOptions& options, debug::RegisterCategory category, |
| const std::vector<debug::RegisterValue>& registers, OutputBuffer* out) { |
| auto title = fxl::StringPrintf("%s Registers\n", debug::RegisterCategoryToString(category)); |
| out->Append(OutputBuffer(Syntax::kHeading, std::move(title))); |
| |
| if (registers.empty()) { |
| out->Append("No registers to show in this category."); |
| return; |
| } |
| |
| // Check for architecture-specific printing. |
| if (options.arch == debug::Arch::kX64) { |
| if (FormatCategoryX64(options, category, registers, out)) |
| return; |
| } else if (options.arch == debug::Arch::kArm64) { |
| if (FormatCategoryARM64(options, category, registers, out)) |
| return; |
| } |
| |
| // General formatting. |
| FormatGeneralRegisters(registers, out); |
| } |
| |
| } // namespace |
| |
| OutputBuffer FormatRegisters(const FormatRegisterOptions& options, |
| const std::vector<debug::RegisterValue>& registers) { |
| OutputBuffer out; |
| |
| // Group register by category. |
| std::map<debug::RegisterCategory, std::vector<debug::RegisterValue>> categorized; |
| for (const debug::RegisterValue& reg : registers) |
| categorized[debug::RegisterIDToCategory(reg.id)].push_back(reg); |
| |
| for (auto& [category, cat_regs] : categorized) { |
| // Ensure the registers appear in a consistent order. |
| std::sort(cat_regs.begin(), cat_regs.end(), |
| [](const debug::RegisterValue& a, const debug::RegisterValue& b) { |
| return static_cast<uint32_t>(a.id) < static_cast<uint32_t>(b.id); |
| }); |
| |
| FormatCategory(options, category, cat_regs, &out); |
| out.Append("\n"); |
| } |
| return out; |
| } |
| |
| void FormatGeneralRegisters(const std::vector<debug::RegisterValue>& registers, OutputBuffer* out) { |
| std::vector<std::vector<OutputBuffer>> rows; |
| for (const debug::RegisterValue& reg : registers) { |
| auto color = |
| rows.size() % 2 == 1 ? TextForegroundColor::kDefault : TextForegroundColor::kLightGray; |
| rows.push_back(DescribeRegister(reg, color)); |
| } |
| |
| // Pad left by two spaces so the headings make more sense. |
| FormatTable({ColSpec(Align::kRight, 0, std::string(), 2), ColSpec(Align::kRight), ColSpec()}, |
| rows, out); |
| } |
| |
| void FormatGeneralVectorRegisters(const FormatRegisterOptions& options, |
| const std::vector<debug::RegisterValue>& registers, |
| OutputBuffer* out) { |
| bool is_float = options.vector_format == VectorRegisterFormat::kFloat || |
| options.vector_format == VectorRegisterFormat::kDouble; |
| |
| FormatOptions format_options; |
| if (!is_float) { |
| // Force padded hex output for all non-floating-point values. |
| format_options.num_format = FormatOptions::NumFormat::kHex; |
| format_options.zero_pad_hex_bin = true; |
| } |
| |
| // The formatter needs an eval context but we don't need it to have any capabilities. |
| auto eval_context = fxl::MakeRefCounted<EvalContextImpl>( |
| std::make_shared<AbiNull>(), fxl::WeakPtr<const ProcessSymbols>(), |
| fxl::RefPtr<SymbolDataProvider>(), Location()); |
| |
| // Largest number of vector elements of all registers. |
| size_t max_children = 0; |
| |
| // Convert each register to a FormatNode. It will have each vector element as the children. |
| std::vector<std::unique_ptr<FormatNode>> formatted; |
| for (const auto& r : registers) { |
| // Use the expression formatter to format the vector members. |
| ExprValue vector_value = VectorRegisterToValue(r.id, options.vector_format, r.data); |
| auto node = |
| std::make_unique<FormatNode>(debug::RegisterIDToString(r.id), std::move(vector_value)); |
| |
| // In general formatting is asynchronous but a vector of numbers should always be completable |
| // synchronously. |
| bool completed = false; |
| FillFormatNodeDescription(node.get(), format_options, eval_context, |
| fit::defer_callback([&completed]() { completed = true; })); |
| FX_DCHECK(completed); |
| |
| max_children = std::max(max_children, node->children().size()); |
| formatted.push_back(std::move(node)); |
| } |
| |
| // Convert the formatted registers to a table. |
| std::vector<std::vector<OutputBuffer>> rows; |
| for (auto& node : formatted) { |
| // Each row is the name + the children elements. |
| std::vector<OutputBuffer>& row = rows.emplace_back(); |
| row.resize(max_children + 1); |
| |
| auto color = |
| rows.size() % 2 == 1 ? TextForegroundColor::kLightGray : TextForegroundColor::kDefault; |
| row[0] = OutputBuffer(node->name(), color); // debug::RegisterValue name. |
| |
| // Add each child to the row. |
| for (size_t i = 0; i < node->children().size(); i++) { |
| bool completed = false; |
| FillFormatNodeDescription(node->children()[i].get(), format_options, eval_context, |
| fit::defer_callback([&completed]() { completed = true; })); |
| FX_DCHECK(completed); |
| |
| // The table is filled with the low index on the right. |
| row[row.size() - i - 1] = OutputBuffer(node->children()[i]->description(), color); |
| } |
| } |
| |
| std::vector<ColSpec> spec; |
| spec.emplace_back(Align::kRight, 0, "Name", 2); |
| for (size_t i = 0; i < max_children; i++) |
| spec.emplace_back(Align::kRight, 0, fxl::StringPrintf("[%zu]", max_children - i - 1)); |
| |
| FormatTable(spec, rows, out); |
| |
| out->Append(Syntax::kComment, |
| fxl::StringPrintf( |
| " (Use \"get/set vector-format\" to control vector register intepretation.\n" |
| " Currently showing vectors of \"%s\".)\n", |
| VectorRegisterFormatToString(options.vector_format))); |
| } |
| |
| std::vector<OutputBuffer> DescribeRegister(const debug::RegisterValue& reg, |
| TextForegroundColor color) { |
| std::vector<OutputBuffer> result; |
| result.emplace_back(debug::RegisterIDToString(reg.id), color); |
| |
| if (reg.data.size() <= 8) { |
| // Treat <= 64 bit registers as numbers. |
| uint64_t value = static_cast<uint64_t>(reg.GetValue()); |
| result.emplace_back(to_hex_string(value), color); |
| |
| // For plausible small integers, show the decimal value also. This size check is intended to |
| // avoid cluttering up the results with large numbers corresponding to pointers. |
| constexpr uint64_t kMaxSmallMagnitude = 0xffff; |
| if (value <= kMaxSmallMagnitude || llabs(static_cast<long long int>(value)) <= |
| static_cast<long long int>(kMaxSmallMagnitude)) { |
| result.emplace_back(fxl::StringPrintf("= %d", static_cast<int>(value)), color); |
| } else { |
| result.emplace_back(); |
| } |
| } else { |
| // Assume anything bigger than 64 bits is a vector and print with grouping. |
| result.emplace_back(GetLittleEndianHexOutput(reg.data)); |
| } |
| |
| return result; |
| } |
| |
| } // namespace zxdb |