| // 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/shared/register_info.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" |
| |
| namespace zxdb { |
| |
| namespace { |
| |
| using debug::RegisterCategory; |
| using debug::RegisterID; |
| |
| void PushName(const debug::RegisterValue& reg, TextForegroundColor color, |
| std::vector<OutputBuffer>* row) { |
| row->emplace_back(debug::RegisterIDToString(reg.id), color); |
| } |
| |
| // A nonzero length will case that number of bytes to be printed. |
| void PushHex(const debug::RegisterValue& 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 debug::RegisterValue& 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 debug::RegisterValue& rflags, |
| TextForegroundColor color) { |
| std::vector<OutputBuffer> result; |
| result.emplace_back(debug::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 debug::RegisterValue& 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<debug::RegisterValue>& registers, OutputBuffer* out) { |
| std::vector<std::vector<OutputBuffer>> rows; |
| |
| for (const debug::RegisterValue& 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<debug::RegisterValue>& 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 debug::RegisterValue*> control_registers; |
| const auto& value_set = GetFPValueRegistersSet(); |
| std::vector<const debug::RegisterValue*> value_registers; |
| for (const debug::RegisterValue& 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: " << debug::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<debug::RegisterValue>& 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<debug::RegisterValue> non_vect; // Control registers. |
| std::vector<debug::RegisterValue> 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 debug::RegisterValue& dr6, TextForegroundColor color) { |
| std::vector<OutputBuffer> result; |
| result.emplace_back(debug::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 debug::RegisterValue& 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(debug::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<debug::RegisterValue>& 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 debug::RegisterValue& 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<debug::RegisterValue>& 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 |