| // 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_x64.h" | |
| #include <inttypes.h> | |
| #include <lib/syslog/cpp/macros.h> | |
| #include <set> | |
| #include "src/developer/debug/ipc/records.h" | |
| #include "src/developer/debug/shared/arch_x86.h" | |
| #include "src/developer/debug/zxdb/common/err.h" | |
| #include "src/developer/debug/zxdb/console/format_register.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/lib/fxl/strings/string_printf.h" | |
| using debug_ipc::RegisterCategory; | |
| using debug_ipc::RegisterID; | |
| namespace zxdb { | |
| namespace { | |
| using debug_ipc::Register; | |
| using debug_ipc::RegisterCategory; | |
| void PushName(const Register& reg, TextForegroundColor color, std::vector<OutputBuffer>* row) { | |
| row->emplace_back(RegisterIDToString(reg.id), color); | |
| } | |
| // A nonzero length will case that number of bytes to be printed. | |
| void PushHex(const Register& reg, TextForegroundColor color, int length, | |
| std::vector<OutputBuffer>* row) { | |
| containers::array_view<uint8_t> view = reg.data; | |
| if (length > 0) | |
| view = view.subview(0, length); | |
| row->emplace_back(GetLittleEndianHexOutput(view), color); | |
| } | |
| void PushFP(const Register& reg, TextForegroundColor color, std::vector<OutputBuffer>* row) { | |
| row->emplace_back(GetFPString(reg.data), color); | |
| } | |
| // Function used for interleaving color, for easier reading of a table. | |
| TextForegroundColor GetRowColor(size_t table_len) { | |
| return table_len % 2 == 0 ? TextForegroundColor::kDefault : TextForegroundColor::kLightGray; | |
| } | |
| // Format General Registers ------------------------------------------------------------------------ | |
| std::vector<OutputBuffer> DescribeRflags(const Register& rflags, TextForegroundColor color) { | |
| std::vector<OutputBuffer> result; | |
| result.emplace_back(RegisterIDToString(rflags.id), color); | |
| uint32_t value = static_cast<uint32_t>(rflags.GetValue()); | |
| // Hex value: rflags is a 32 bit value. | |
| result.emplace_back(fxl::StringPrintf("0x%08x", value), color); | |
| // Decode individual flags. | |
| result.emplace_back( | |
| fxl::StringPrintf("CF=%d, PF=%d, AF=%d, ZF=%d, SF=%d, TF=%d, IF=%d, DF=%d, OF=%d", | |
| X86_FLAG_VALUE(value, RflagsCF), X86_FLAG_VALUE(value, RflagsPF), | |
| X86_FLAG_VALUE(value, RflagsAF), X86_FLAG_VALUE(value, RflagsZF), | |
| X86_FLAG_VALUE(value, RflagsSF), X86_FLAG_VALUE(value, RflagsTF), | |
| X86_FLAG_VALUE(value, RflagsIF), X86_FLAG_VALUE(value, RflagsDF), | |
| X86_FLAG_VALUE(value, RflagsOF)), | |
| color); | |
| return result; | |
| } | |
| std::vector<OutputBuffer> DescribeRflagsExtended(const Register& rflags, | |
| TextForegroundColor color) { | |
| std::vector<OutputBuffer> result; | |
| result.reserve(3); | |
| result.emplace_back(OutputBuffer()); | |
| result.emplace_back(OutputBuffer()); | |
| uint32_t value = rflags.GetValue(); | |
| // Decode individual flags. | |
| result.emplace_back( | |
| fxl::StringPrintf("IOPL=%d, NT=%d, RF=%d, VM=%d, AC=%d, VIF=%d, VIP=%d, ID=%d", | |
| X86_FLAG_VALUE(value, RflagsIOPL), X86_FLAG_VALUE(value, RflagsNT), | |
| X86_FLAG_VALUE(value, RflagsRF), X86_FLAG_VALUE(value, RflagsVM), | |
| X86_FLAG_VALUE(value, RflagsAC), X86_FLAG_VALUE(value, RflagsVIF), | |
| X86_FLAG_VALUE(value, RflagsVIP), X86_FLAG_VALUE(value, RflagsID)), | |
| color); | |
| return result; | |
| } | |
| void FormatGeneralRegisters(const FormatRegisterOptions& options, | |
| const std::vector<Register>& registers, OutputBuffer* out) { | |
| std::vector<std::vector<OutputBuffer>> rows; | |
| for (const Register& reg : registers) { | |
| auto color = GetRowColor(rows.size()); | |
| if (reg.id == RegisterID::kX64_rflags) { | |
| rows.push_back(DescribeRflags(reg, color)); | |
| if (options.extended) | |
| rows.push_back(DescribeRflagsExtended(reg, color)); | |
| } else { | |
| rows.push_back(DescribeRegister(reg, color)); | |
| } | |
| } | |
| // Output the tables. | |
| if (!rows.empty()) { | |
| std::vector<ColSpec> colspecs({ColSpec(Align::kRight, 0, std::string(), 2), | |
| ColSpec(Align::kRight, 0, std::string(), 1), ColSpec()}); | |
| FormatTable(colspecs, rows, out); | |
| } | |
| } | |
| // Format Floating Point (x87) --------------------------------------------------------------------- | |
| inline const std::set<RegisterID>& GetFPControlRegistersSet() { | |
| static std::set<RegisterID> regs = {RegisterID::kX64_fcw, RegisterID::kX64_fsw, | |
| RegisterID::kX64_ftw, RegisterID::kX64_fop, | |
| RegisterID::kX64_fip, RegisterID::kX64_fdp}; | |
| return regs; | |
| } | |
| inline const std::set<RegisterID>& GetFPValueRegistersSet() { | |
| static std::set<RegisterID> regs = { | |
| RegisterID::kX64_st0, RegisterID::kX64_st1, RegisterID::kX64_st2, RegisterID::kX64_st3, | |
| RegisterID::kX64_st4, RegisterID::kX64_st5, RegisterID::kX64_st6, RegisterID::kX64_st7}; | |
| return regs; | |
| } | |
| void FormatFPRegisters(const std::vector<Register>& registers, OutputBuffer* out) { | |
| // We want to look up the registers in two sets: control & values, and display them differently. | |
| // There is no memory movement the input, so taking pointers is fine. | |
| const auto& control_set = GetFPControlRegistersSet(); | |
| std::vector<const Register*> control_registers; | |
| const auto& value_set = GetFPValueRegistersSet(); | |
| std::vector<const Register*> value_registers; | |
| for (const Register& reg : registers) { | |
| if (control_set.find(reg.id) != control_set.end()) { | |
| control_registers.push_back(®); | |
| } else if (value_set.find(reg.id) != value_set.end()) { | |
| value_registers.push_back(®); | |
| } else { | |
| FX_NOTREACHED() << "UNCATEGORIZED FP REGISTER: " << RegisterIDToString(reg.id); | |
| } | |
| } | |
| // Format the control register first. | |
| if (!control_registers.empty()) { | |
| std::vector<std::vector<OutputBuffer>> rows; | |
| rows.reserve(control_registers.size()); | |
| for (size_t i = 0; i < control_registers.size(); i++) { | |
| const auto& reg = *control_registers[i]; | |
| rows.emplace_back(); | |
| auto& row = rows[i]; | |
| auto color = GetRowColor(rows.size()); | |
| switch (reg.id) { | |
| default: { | |
| // TODO: Placeholder. Remove when all control registers have their | |
| // custom output implemented. | |
| PushName(reg, color, &row); | |
| PushHex(reg, color, 4, &row); | |
| row.emplace_back(); | |
| } | |
| } | |
| } | |
| // Output the control table. | |
| OutputBuffer control_out; | |
| auto colspecs = | |
| std::vector<ColSpec>({ColSpec(Align::kRight, 0, "", 2), ColSpec(Align::kLeft, 0, "", 1), | |
| ColSpec(Align::kLeft, 0, "", 1)}); | |
| FormatTable(colspecs, rows, &control_out); | |
| out->Append(std::move(control_out)); | |
| } | |
| // Format the value registers. | |
| if (!value_registers.empty()) { | |
| std::vector<std::vector<OutputBuffer>> rows; | |
| rows.reserve(value_registers.size()); | |
| for (size_t i = 0; i < value_registers.size(); i++) { | |
| const auto& reg = *value_registers[i]; | |
| rows.emplace_back(); | |
| auto color = GetRowColor(rows.size()); | |
| auto& row = rows[i]; | |
| row.reserve(3); | |
| PushName(reg, color, &row); | |
| PushFP(reg, color, &row); | |
| PushHex(reg, color, 16, &row); | |
| } | |
| // The "value" for the floating point registers is left-aligned here rather | |
| // than right-aligned like the normal numeric registers because the | |
| // right-hand digits don't correspond to each other, and usually this will | |
| // end up aligning the decimal point which is nice. | |
| OutputBuffer value_out; | |
| auto colspecs = std::vector<ColSpec>({ColSpec(Align::kRight, 0, std::string(), 2), | |
| ColSpec(Align::kLeft, 0, std::string(), 1), | |
| ColSpec(Align::kLeft, 0, std::string(), 1)}); | |
| FormatTable(std::move(colspecs), rows, &value_out); | |
| out->Append(std::move(value_out)); | |
| } | |
| } | |
| // Vector Registers -------------------------------------------------------------------------------- | |
| void FormatVectorRegistersX64(const FormatRegisterOptions& options, | |
| const std::vector<Register>& registers, OutputBuffer* out) { | |
| // This uses the standard vector register formatting, but converts from AVX-512 to AVX. Zircon | |
| // doesn't currently support AVX-512 but our canonical registers use this format. Unnecessarily | |
| // displaying all those 0's makes things more difficult to follow. If AVX-512 is supported in | |
| // the future we can show the zmm and xmm/ymm registers >= 16 when the target CPU has them. | |
| std::vector<Register> non_vect; // Control registers. | |
| std::vector<Register> filtered; | |
| filtered.reserve(registers.size()); | |
| for (auto& r : registers) { | |
| uint32_t id = static_cast<uint32_t>(r.id); | |
| // Filter out all vector registers >= 16 (these are additions in AVX-512). | |
| if (id >= static_cast<uint32_t>(RegisterID::kX64_zmm16) && | |
| id <= static_cast<uint32_t>(RegisterID::kX64_zmm31)) | |
| continue; | |
| if (id >= static_cast<uint32_t>(RegisterID::kX64_zmm0) && | |
| id <= static_cast<uint32_t>(RegisterID::kX64_zmm15) && r.data.size() == 64) { | |
| // Convert 512-bit zmm0-15 to 256-bit "ymm" registers. | |
| RegisterID ymm_id = | |
| static_cast<RegisterID>(id - static_cast<uint32_t>(RegisterID::kX64_zmm0) + | |
| static_cast<uint32_t>(RegisterID::kX64_ymm0)); | |
| filtered.emplace_back(ymm_id, std::vector<uint8_t>(r.data.begin(), r.data.begin() + 32)); | |
| } else if ((id >= static_cast<uint32_t>(RegisterID::kX64_xmm0) && | |
| id <= static_cast<uint32_t>(RegisterID::kX64_xmm15)) || | |
| (id >= static_cast<uint32_t>(RegisterID::kX64_ymm0) && | |
| id <= static_cast<uint32_t>(RegisterID::kX64_ymm15))) { | |
| // All other vector registers that happen to be in the list. We don't expect to have other | |
| // vector registers here, but pass the rest through unchanged if they appear. | |
| filtered.push_back(r); | |
| } else { | |
| // Control registers get a separate section. | |
| non_vect.push_back(r); | |
| } | |
| } | |
| // Start with any control registers. | |
| if (!non_vect.empty()) { | |
| FormatGeneralRegisters(non_vect, out); | |
| // Blank line separating sections. | |
| if (!filtered.empty()) | |
| out->Append("\n"); | |
| } | |
| if (!filtered.empty()) | |
| FormatGeneralVectorRegisters(options, filtered, out); | |
| } | |
| // Debug Registers --------------------------------------------------------------------------------- | |
| std::vector<OutputBuffer> FormatDr6(const Register& dr6, TextForegroundColor color) { | |
| std::vector<OutputBuffer> result; | |
| result.emplace_back(RegisterIDToString(dr6.id), color); | |
| // Write as padded 32-bit value. | |
| uint32_t value = static_cast<uint32_t>(dr6.GetValue()); | |
| result.emplace_back(fxl::StringPrintf("0x%08x", value), color); | |
| result.emplace_back(fxl::StringPrintf("B0=%d, B1=%d, B2=%d, B3=%d, BD=%d, BS=%d, BT=%d", | |
| X86_FLAG_VALUE(value, DR6B0), X86_FLAG_VALUE(value, DR6B1), | |
| X86_FLAG_VALUE(value, DR6B2), X86_FLAG_VALUE(value, DR6B3), | |
| X86_FLAG_VALUE(value, DR6BD), X86_FLAG_VALUE(value, DR6BS), | |
| X86_FLAG_VALUE(value, DR6BT)), | |
| color); | |
| return result; | |
| } | |
| // NOTE: This function receives the table because it will append another row. | |
| void FormatDr7(const Register& dr7, TextForegroundColor color, | |
| std::vector<std::vector<OutputBuffer>>* rows) { | |
| rows->emplace_back(); | |
| auto& first_row = rows->back(); | |
| // First row gets the name and raw value (padded 32 bits). | |
| first_row.emplace_back(RegisterIDToString(dr7.id), color); | |
| uint32_t value = static_cast<uint32_t>(dr7.GetValue()); | |
| first_row.emplace_back(fxl::StringPrintf("0x%08x", value), color); | |
| // First row decoded values. | |
| first_row.emplace_back( | |
| fxl::StringPrintf( | |
| "L0=%d, G0=%d, L1=%d, G1=%d, L2=%d, G2=%d, L3=%d, G4=%d, LE=%d, " | |
| "GE=%d, " | |
| "GD=%d", | |
| X86_FLAG_VALUE(value, DR7L0), X86_FLAG_VALUE(value, DR7G0), X86_FLAG_VALUE(value, DR7L1), | |
| X86_FLAG_VALUE(value, DR7G1), X86_FLAG_VALUE(value, DR7L2), X86_FLAG_VALUE(value, DR7G2), | |
| X86_FLAG_VALUE(value, DR7L3), X86_FLAG_VALUE(value, DR7G3), X86_FLAG_VALUE(value, DR7LE), | |
| X86_FLAG_VALUE(value, DR7GE), X86_FLAG_VALUE(value, DR7GD)), | |
| color); | |
| // Second row only gets decoded values in the 3rd column. | |
| rows->emplace_back(); | |
| auto& second_row = rows->back(); | |
| second_row.resize(2); // Default-construct two empty cols. | |
| second_row.emplace_back( | |
| fxl::StringPrintf("R/W0=%d, LEN0=%d, R/W1=%d, LEN1=%d, R/W2=%d, " | |
| "LEN2=%d, R/W3=%d, LEN3=%d", | |
| X86_FLAG_VALUE(value, DR7RW0), X86_FLAG_VALUE(value, DR7LEN0), | |
| X86_FLAG_VALUE(value, DR7RW1), X86_FLAG_VALUE(value, DR7LEN1), | |
| X86_FLAG_VALUE(value, DR7RW2), X86_FLAG_VALUE(value, DR7LEN2), | |
| X86_FLAG_VALUE(value, DR7RW3), X86_FLAG_VALUE(value, DR7LEN3)), | |
| color); | |
| } | |
| void FormatDebugRegisters(const std::vector<Register>& registers, OutputBuffer* out) { | |
| // dr[0-3] and dr[6-7] have different formats, so get separate tables. | |
| std::vector<std::vector<OutputBuffer>> rows; | |
| std::vector<std::vector<OutputBuffer>> dr67_rows; | |
| for (const Register& reg : registers) { | |
| auto color = GetRowColor(rows.size() + 1); | |
| // We do special formatting for dr6/dr7 | |
| if (reg.id == RegisterID::kX64_dr6) { | |
| rows.push_back(FormatDr6(reg, color)); | |
| } else if (reg.id == RegisterID::kX64_dr7) { | |
| FormatDr7(reg, color, &rows); | |
| } else { | |
| // Generic formatting for now. | |
| rows.push_back(DescribeRegister(reg, color)); | |
| } | |
| } | |
| // Output each table if needed. | |
| auto colspecs = | |
| std::vector<ColSpec>({ColSpec(Align::kRight, 0, std::string(), 2), | |
| ColSpec(Align::kRight, 0, std::string(), 1), ColSpec(Align::kLeft)}); | |
| FormatTable(colspecs, rows, out); | |
| } | |
| } // namespace | |
| bool FormatCategoryX64(const FormatRegisterOptions& options, RegisterCategory category, | |
| const std::vector<Register>& registers, OutputBuffer* out) { | |
| switch (category) { | |
| case RegisterCategory::kGeneral: | |
| FormatGeneralRegisters(options, registers, out); | |
| return true; | |
| case RegisterCategory::kFloatingPoint: | |
| FormatFPRegisters(registers, out); | |
| return true; | |
| case RegisterCategory::kVector: | |
| FormatVectorRegistersX64(options, registers, out); | |
| return true; | |
| case RegisterCategory::kDebug: | |
| FormatDebugRegisters(registers, out); | |
| return true; | |
| default: | |
| return false; | |
| } | |
| } | |
| } // namespace zxdb |