| // 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/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/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 { | |
| using debug_ipc::Register; | |
| using debug_ipc::RegisterCategory; | |
| using debug_ipc::RegisterID; | |
| namespace { | |
| void FormatCategory(const FormatRegisterOptions& options, RegisterCategory category, | |
| const std::vector<Register>& registers, OutputBuffer* out) { | |
| auto title = fxl::StringPrintf("%s Registers\n", debug_ipc::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_ipc::Arch::kX64) { | |
| if (FormatCategoryX64(options, category, registers, out)) | |
| return; | |
| } else if (options.arch == debug_ipc::Arch::kArm64) { | |
| if (FormatCategoryARM64(options, category, registers, out)) | |
| return; | |
| } | |
| // General formatting. | |
| FormatGeneralRegisters(registers, out); | |
| } | |
| } // namespace | |
| OutputBuffer FormatRegisters(const FormatRegisterOptions& options, | |
| const std::vector<Register>& registers) { | |
| OutputBuffer out; | |
| // Group register by category. | |
| std::map<RegisterCategory, std::vector<Register>> categorized; | |
| for (const Register& reg : registers) | |
| categorized[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 Register& a, const Register& 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<Register>& registers, OutputBuffer* out) { | |
| std::vector<std::vector<OutputBuffer>> rows; | |
| for (const Register& 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_ipc::Register>& 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 = true; | |
| } | |
| // The formatter needs an eval context but we don't need it to have any capabilities. | |
| auto eval_context = fxl::MakeRefCounted<EvalContextImpl>( | |
| 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>(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); // Register 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 Register& reg, TextForegroundColor color) { | |
| std::vector<OutputBuffer> result; | |
| result.emplace_back(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 |