| // 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/debug_agent/arch_x64_helpers.h" |
| |
| #include <zircon/hw/debug/x86.h> |
| |
| #include <vector> |
| |
| #include "src/developer/debug/debug_agent/arch.h" |
| #include "src/developer/debug/debug_agent/arch_helpers.h" |
| #include "src/developer/debug/shared/arch_x86.h" |
| #include "src/lib/fxl/logging.h" |
| #include "src/lib/fxl/strings/string_printf.h" |
| |
| using namespace debug_ipc; |
| |
| namespace debug_agent { |
| namespace arch { |
| |
| namespace { |
| |
| // Implements a case statement for calling WriteRegisterValue assuming the Zircon register |
| // field matches the enum name. This avoids implementation typos where the names don't match. |
| #define IMPLEMENT_CASE_WRITE_REGISTER_VALUE(name) \ |
| case RegisterID::kX64_##name: \ |
| status = WriteRegisterValue(reg, ®s->name); \ |
| break; |
| |
| uint64_t HWDebugResourceEnabled(uint64_t dr7, size_t index) { |
| FXL_DCHECK(index < 4); |
| |
| // clang-format off |
| static uint64_t masks[4] = { X86_FLAG_MASK(DR7L0), |
| X86_FLAG_MASK(DR7L1), |
| X86_FLAG_MASK(DR7L2), |
| X86_FLAG_MASK(DR7L3)}; |
| // clang-format on |
| |
| return (dr7 & masks[index]) != 0; |
| } |
| |
| // A watchpoint is configured by DR7RW<i> = 0b10 (write) or 0b11 (read/write). |
| bool IsWatchpoint(uint64_t dr7, size_t index) { |
| // clang-format off |
| switch (index) { |
| case 0: return (X86_FLAG_VALUE(dr7, DR7RW0) & 1) > 0; |
| case 1: return (X86_FLAG_VALUE(dr7, DR7RW1) & 1) > 0; |
| case 2: return (X86_FLAG_VALUE(dr7, DR7RW2) & 1) > 0; |
| case 3: return (X86_FLAG_VALUE(dr7, DR7RW3) & 1) > 0; |
| default: |
| break; |
| } |
| // clang-format on |
| |
| FXL_NOTREACHED(); |
| return false; |
| } |
| |
| } // namespace |
| |
| zx_status_t WriteGeneralRegisters(const std::vector<Register>& updates, |
| zx_thread_state_general_regs_t* regs) { |
| uint32_t begin = static_cast<uint32_t>(RegisterID::kX64_rax); |
| uint32_t last = static_cast<uint32_t>(RegisterID::kX64_rflags); |
| |
| uint64_t* output_array = reinterpret_cast<uint64_t*>(regs); |
| |
| for (const Register& reg : updates) { |
| if (reg.data.size() != 8) |
| return ZX_ERR_INVALID_ARGS; |
| |
| // zx_thread_state_general_regs has the same layout as the RegisterID enum for x64 general |
| // registers. |
| uint32_t id = static_cast<uint32_t>(reg.id); |
| if (id < begin || id > last) |
| return ZX_ERR_INVALID_ARGS; |
| |
| // Insert the value to the correct offset. |
| output_array[id - begin] = *reinterpret_cast<const uint64_t*>(reg.data.data()); |
| } |
| |
| return ZX_OK; |
| } |
| |
| zx_status_t WriteFloatingPointRegisters(const std::vector<Register>& updates, |
| zx_thread_state_fp_regs_t* regs) { |
| for (const auto& reg : updates) { |
| zx_status_t status = ZX_OK; |
| if (reg.id >= RegisterID::kX64_st0 && reg.id <= RegisterID::kX64_st7) { |
| // FP stack value. |
| uint32_t stack_index = |
| static_cast<uint32_t>(reg.id) - static_cast<uint32_t>(RegisterID::kX64_st0); |
| status = WriteRegisterValue(reg, ®s->st[stack_index]); |
| } else { |
| // FP control registers. |
| switch (reg.id) { |
| IMPLEMENT_CASE_WRITE_REGISTER_VALUE(fcw); |
| IMPLEMENT_CASE_WRITE_REGISTER_VALUE(fsw); |
| IMPLEMENT_CASE_WRITE_REGISTER_VALUE(ftw); |
| IMPLEMENT_CASE_WRITE_REGISTER_VALUE(fop); |
| IMPLEMENT_CASE_WRITE_REGISTER_VALUE(fip); |
| IMPLEMENT_CASE_WRITE_REGISTER_VALUE(fdp); |
| default: |
| status = ZX_ERR_INVALID_ARGS; |
| break; |
| } |
| } |
| |
| if (status != ZX_OK) |
| return status; |
| } |
| return ZX_OK; |
| } |
| |
| zx_status_t WriteVectorRegisters(const std::vector<Register>& updates, |
| zx_thread_state_vector_regs_t* regs) { |
| for (const auto& reg : updates) { |
| zx_status_t status = ZX_OK; |
| if (static_cast<uint32_t>(reg.id) >= static_cast<uint32_t>(RegisterID::kX64_zmm0) && |
| static_cast<uint32_t>(reg.id) <= static_cast<uint32_t>(RegisterID::kX64_zmm31)) { |
| uint32_t stack_index = |
| static_cast<uint32_t>(reg.id) - static_cast<uint32_t>(RegisterID::kX64_zmm0); |
| status = WriteRegisterValue(reg, ®s->zmm[stack_index]); |
| } else { |
| switch (reg.id) { |
| IMPLEMENT_CASE_WRITE_REGISTER_VALUE(mxcsr); |
| default: |
| status = ZX_ERR_INVALID_ARGS; |
| break; |
| } |
| if (status != ZX_OK) |
| return status; |
| } |
| } |
| return ZX_OK; |
| } |
| |
| zx_status_t WriteDebugRegisters(const std::vector<Register>& updates, |
| zx_thread_state_debug_regs_t* regs) { |
| for (const auto& reg : updates) { |
| zx_status_t status = ZX_OK; |
| switch (reg.id) { |
| case RegisterID::kX64_dr0: |
| status = WriteRegisterValue(reg, ®s->dr[0]); |
| break; |
| case RegisterID::kX64_dr1: |
| status = WriteRegisterValue(reg, ®s->dr[1]); |
| break; |
| case RegisterID::kX64_dr2: |
| status = WriteRegisterValue(reg, ®s->dr[2]); |
| break; |
| case RegisterID::kX64_dr3: |
| status = WriteRegisterValue(reg, ®s->dr[3]); |
| break; |
| case RegisterID::kX64_dr6: |
| status = WriteRegisterValue(reg, ®s->dr6); |
| break; |
| case RegisterID::kX64_dr7: |
| status = WriteRegisterValue(reg, ®s->dr7); |
| break; |
| default: |
| status = ZX_ERR_INVALID_ARGS; |
| break; |
| } |
| |
| if (status != ZX_OK) |
| return status; |
| } |
| return ZX_OK; |
| } |
| |
| // HW Breakpoints ---------------------------------------------------------------------------------- |
| |
| namespace { |
| |
| void SetHWBreakpointFlags(uint64_t* dr7, int slot, bool active) { |
| switch (slot) { |
| case 0: { |
| X86_DBG_CONTROL_L0_SET(dr7, active ? 1 : 0); |
| X86_DBG_CONTROL_RW0_SET(dr7, 0); |
| X86_DBG_CONTROL_LEN0_SET(dr7, 0); |
| return; |
| } |
| case 1: { |
| X86_DBG_CONTROL_L1_SET(dr7, active ? 1 : 0); |
| X86_DBG_CONTROL_RW1_SET(dr7, 0); |
| X86_DBG_CONTROL_LEN1_SET(dr7, 0); |
| return; |
| } |
| case 2: { |
| X86_DBG_CONTROL_L2_SET(dr7, active ? 1 : 0); |
| X86_DBG_CONTROL_RW2_SET(dr7, 0); |
| X86_DBG_CONTROL_LEN2_SET(dr7, 0); |
| return; |
| } |
| case 3: { |
| X86_DBG_CONTROL_L3_SET(dr7, active ? 1 : 0); |
| X86_DBG_CONTROL_RW3_SET(dr7, 0); |
| X86_DBG_CONTROL_LEN3_SET(dr7, 0); |
| return; |
| } |
| } |
| |
| FXL_NOTREACHED() << "Invalid slot: " << slot; |
| } |
| |
| } // namespace |
| |
| zx_status_t SetupHWBreakpoint(uint64_t address, zx_thread_state_debug_regs_t* debug_regs) { |
| // Search for a free slot. |
| int slot = -1; |
| for (size_t i = 0; i < 4; i++) { |
| if (HWDebugResourceEnabled(debug_regs->dr7, i)) { |
| // If it's already bound there, we don't need to do anything. |
| if (debug_regs->dr[i] == address) |
| return ZX_ERR_ALREADY_BOUND; |
| } else { |
| slot = i; |
| break; |
| } |
| } |
| |
| if (slot == -1) |
| return ZX_ERR_NO_RESOURCES; |
| |
| // We found a slot, we bind the address. |
| debug_regs->dr[slot] = address; |
| SetHWBreakpointFlags(&debug_regs->dr7, slot, true); |
| return ZX_OK; |
| } |
| |
| zx_status_t RemoveHWBreakpoint(uint64_t address, zx_thread_state_debug_regs_t* debug_regs) { |
| // Search for the slot. |
| for (int i = 0; i < 4; i++) { |
| if (!HWDebugResourceEnabled(debug_regs->dr7, i) || IsWatchpoint(debug_regs->dr7, i)) { |
| continue; |
| } |
| |
| if (debug_regs->dr[i] != address) |
| continue; |
| |
| // Clear this breakpoint. |
| debug_regs->dr[i] = 0; |
| SetHWBreakpointFlags(&debug_regs->dr7, i, false); |
| |
| return ZX_OK; |
| } |
| |
| // We didn't find the address. |
| return ZX_ERR_OUT_OF_RANGE; |
| } |
| |
| // HW Watchpoints -------------------------------------------------------------- |
| |
| namespace { |
| |
| // x86 uses the following bits to represent watchpoint lenghts: |
| // 00: 1 byte. |
| // 10: 2 bytes. |
| // 11: 8 bytes. |
| // 10: 4 bytes. |
| // |
| // The following functions translate between the both. |
| |
| inline uint64_t x86LenToLength(uint64_t len) { |
| // clang-format off |
| switch (len) { |
| case 0: return 1; |
| case 1: return 2; |
| case 2: return 8; |
| case 3: return 4; |
| } |
| // clang-format on |
| |
| FXL_NOTREACHED() << "Invalid len: " << len; |
| return 0; |
| } |
| |
| inline uint64_t LengthTox86Length(uint64_t len) { |
| // clang-format off |
| switch (len) { |
| case 1: return 0; |
| case 2: return 1; |
| case 8: return 2; |
| case 4: return 3; |
| } |
| // clang-format on |
| |
| FXL_NOTREACHED() << "Invalid len: " << len; |
| return 0; |
| } |
| |
| void SetWatchpointFlags(uint64_t* dr7, int slot, bool active, uint64_t size, |
| debug_ipc::BreakpointType type) { |
| uint32_t rw_value = 0; |
| if (type == debug_ipc::BreakpointType::kWrite) { |
| rw_value = 0b1; |
| } else if (type == debug_ipc::BreakpointType::kReadWrite) { |
| rw_value = 0b11; |
| } |
| |
| // clang-format off |
| switch (slot) { |
| case 0: { |
| X86_DBG_CONTROL_L0_SET(dr7, active ? 1 : 0); |
| X86_DBG_CONTROL_RW0_SET(dr7, rw_value); |
| X86_DBG_CONTROL_LEN0_SET(dr7, size ? LengthTox86Length(size) : 0); |
| return; |
| } |
| case 1: { |
| X86_DBG_CONTROL_L1_SET(dr7, active ? 1 : 0); |
| X86_DBG_CONTROL_RW1_SET(dr7, rw_value); |
| X86_DBG_CONTROL_LEN1_SET(dr7, size ? LengthTox86Length(size) : 0); |
| return; |
| } |
| case 2: { |
| X86_DBG_CONTROL_L2_SET(dr7, active ? 1 : 0); |
| X86_DBG_CONTROL_RW2_SET(dr7, rw_value); |
| X86_DBG_CONTROL_LEN2_SET(dr7, size ? LengthTox86Length(size) : 0); |
| return; |
| } |
| case 3: { |
| X86_DBG_CONTROL_L3_SET(dr7, active ? 1 : 0); |
| X86_DBG_CONTROL_RW3_SET(dr7, rw_value); |
| X86_DBG_CONTROL_LEN3_SET(dr7, size ? LengthTox86Length(size) : 0); |
| return; |
| } |
| } |
| // clang-format on |
| |
| FXL_NOTREACHED() << "Invalid slot: " << slot; |
| } |
| |
| } // namespace |
| |
| uint64_t GetWatchpointLength(uint64_t dr7, int slot) { |
| // clang-format off |
| switch (slot) { |
| case 0: return x86LenToLength(X86_DBG_CONTROL_LEN0_GET(dr7)); |
| case 1: return x86LenToLength(X86_DBG_CONTROL_LEN1_GET(dr7)); |
| case 2: return x86LenToLength(X86_DBG_CONTROL_LEN2_GET(dr7)); |
| case 3: return x86LenToLength(X86_DBG_CONTROL_LEN3_GET(dr7)); |
| } |
| // clang-format on |
| |
| FXL_NOTREACHED() << "Invalid slot: " << slot; |
| return -1; |
| } |
| |
| uint32_t GetWatchpointRW(uint64_t dr7, int slot) { |
| // clang-format off |
| switch (slot) { |
| case 0: return X86_DBG_CONTROL_RW0_GET(dr7); |
| case 1: return X86_DBG_CONTROL_RW1_GET(dr7); |
| case 2: return X86_DBG_CONTROL_RW2_GET(dr7); |
| case 3: return X86_DBG_CONTROL_RW3_GET(dr7); |
| } |
| // clang-format on |
| |
| FXL_NOTREACHED() << "Invalid slot: " << slot; |
| return -1; |
| } |
| |
| uint64_t WatchpointAddressAlign(const debug_ipc::AddressRange& range) { |
| // clang-format off |
| switch (range.size()) { |
| case 1: return range.begin(); |
| case 2: return range.begin() & (uint64_t)(~0b1); |
| case 4: return range.begin() & (uint64_t)(~0b11); |
| case 8: return range.begin() & (uint64_t)(~0b111); |
| } |
| // clang-format on |
| |
| return 0; |
| } |
| // clang-format on |
| |
| WatchpointInstallationResult SetupWatchpoint(zx_thread_state_debug_regs_t* debug_regs, |
| const debug_ipc::AddressRange& range, |
| debug_ipc::BreakpointType type) { |
| if (!debug_ipc::IsWatchpointType(type)) |
| return CreateResult(ZX_ERR_INVALID_ARGS); |
| |
| // Create an aligned range that will cover the watchpoint. |
| auto aligned_range = AlignRange(range); |
| if (!aligned_range.has_value()) |
| return CreateResult(ZX_ERR_OUT_OF_RANGE); |
| |
| uint64_t address = aligned_range->begin(); |
| uint64_t size = aligned_range->end() - aligned_range->begin(); |
| |
| // Search for a free slot. |
| int slot = -1; |
| for (int i = 0; i < 4; i++) { |
| if (HWDebugResourceEnabled(debug_regs->dr7, i)) { |
| // If it's the same address, we don't need to do anything. |
| // For watchpoints, we need to compare against the range. |
| if ((debug_regs->dr[i] == address) && GetWatchpointLength(debug_regs->dr7, i) == size) { |
| return CreateResult(ZX_ERR_ALREADY_BOUND); |
| } |
| } else { |
| slot = i; |
| break; |
| } |
| } |
| |
| if (slot == -1) |
| return WatchpointInstallationResult(ZX_ERR_NO_RESOURCES); |
| |
| // We found a slot, we bind the watchpoint. |
| debug_regs->dr[slot] = address; |
| SetWatchpointFlags(&debug_regs->dr7, slot, true, size, type); |
| |
| return CreateResult(ZX_OK, aligned_range.value(), slot); |
| } |
| |
| zx_status_t RemoveWatchpoint(zx_thread_state_debug_regs_t* debug_regs, |
| const debug_ipc::AddressRange& range) { |
| uint64_t aligned_address = WatchpointAddressAlign(range); |
| if (!aligned_address) |
| return ZX_ERR_INVALID_ARGS; |
| |
| for (int slot = 0; slot < 4; slot++) { |
| if (!IsWatchpoint(debug_regs->dr7, slot)) |
| continue; |
| |
| // Both address and length should match. |
| if ((debug_regs->dr[slot] != aligned_address) || |
| GetWatchpointLength(debug_regs->dr7, slot) != range.size()) { |
| continue; |
| } |
| |
| // Clear this breakpoint. |
| debug_regs->dr[slot] = 0; |
| SetWatchpointFlags(&debug_regs->dr7, slot, false, 0, debug_ipc::BreakpointType::kLast); |
| return ZX_OK; |
| } |
| |
| // We didn't find the address. |
| return ZX_ERR_NOT_FOUND; |
| } |
| |
| // Debug functions ------------------------------------------------------------- |
| |
| std::string GeneralRegistersToString(const zx_thread_state_general_regs& regs) { |
| std::stringstream ss; |
| ss << "General regs: " << std::endl |
| << "rax: 0x" << std::hex << regs.rax << std::endl |
| << "rbx: 0x" << std::hex << regs.rbx << std::endl |
| << "rcx: 0x" << std::hex << regs.rcx << std::endl |
| << "rdx: 0x" << std::hex << regs.rdx << std::endl |
| << "rsi: 0x" << std::hex << regs.rsi << std::endl |
| << "rdi: 0x" << std::hex << regs.rdi << std::endl |
| << "rbp: 0x" << std::hex << regs.rbp << std::endl |
| << "rsp: 0x" << std::hex << regs.rsp << std::endl |
| << "r8: 0x" << std::hex << regs.r8 << std::endl |
| << "r9: 0x" << std::hex << regs.r9 << std::endl |
| << "r10: 0x" << std::hex << regs.r10 << std::endl |
| << "r11: 0x" << std::hex << regs.r11 << std::endl |
| << "r12: 0x" << std::hex << regs.r12 << std::endl |
| << "r13: 0x" << std::hex << regs.r13 << std::endl |
| << "r14: 0x" << std::hex << regs.r14 << std::endl |
| << "r15: 0x" << std::hex << regs.r15 << std::endl |
| << "rip: 0x" << std::hex << regs.rip << std::endl |
| << "rflags: 0x" << std::hex << regs.rflags; |
| |
| return ss.str(); |
| } |
| |
| std::string DebugRegistersToString(const zx_thread_state_debug_regs_t& regs) { |
| std::stringstream ss; |
| ss << "Regs: " << std::endl |
| << "DR0: 0x" << std::hex << regs.dr[0] << std::endl |
| << "DR1: 0x" << std::hex << regs.dr[1] << std::endl |
| << "DR2: 0x" << std::hex << regs.dr[2] << std::endl |
| << "DR3: 0x" << std::hex << regs.dr[3] << std::endl |
| << "DR6: " << DR6ToString(regs.dr6) << std::endl |
| << "DR7: " << DR7ToString(regs.dr7) << std::endl; |
| |
| return ss.str(); |
| } |
| |
| std::string DR6ToString(uint64_t dr6) { |
| return fxl::StringPrintf( |
| "0x%lx: B0=%d, B1=%d, B2=%d, B3=%d, BD=%d, BS=%d, BT=%d", dr6, X86_FLAG_VALUE(dr6, DR6B0), |
| X86_FLAG_VALUE(dr6, DR6B1), X86_FLAG_VALUE(dr6, DR6B2), X86_FLAG_VALUE(dr6, DR6B3), |
| X86_FLAG_VALUE(dr6, DR6BD), X86_FLAG_VALUE(dr6, DR6BS), X86_FLAG_VALUE(dr6, DR6BT)); |
| } |
| |
| std::string DR7ToString(uint64_t dr7) { |
| return fxl::StringPrintf( |
| "0x%lx: L0=%d, G0=%d, L1=%d, G1=%d, L2=%d, G2=%d, L3=%d, G4=%d, LE=%d, " |
| "GE=%d, GD=%d, R/W0=%d, LEN0=%d, R/W1=%d, LEN1=%d, R/W2=%d, LEN2=%d, " |
| "R/W3=%d, LEN3=%d", |
| dr7, X86_FLAG_VALUE(dr7, DR7L0), X86_FLAG_VALUE(dr7, DR7G0), X86_FLAG_VALUE(dr7, DR7L1), |
| X86_FLAG_VALUE(dr7, DR7G1), X86_FLAG_VALUE(dr7, DR7L2), X86_FLAG_VALUE(dr7, DR7G2), |
| X86_FLAG_VALUE(dr7, DR7L3), X86_FLAG_VALUE(dr7, DR7G3), X86_FLAG_VALUE(dr7, DR7LE), |
| X86_FLAG_VALUE(dr7, DR7GE), X86_FLAG_VALUE(dr7, DR7GD), X86_FLAG_VALUE(dr7, DR7RW0), |
| X86_FLAG_VALUE(dr7, DR7LEN0), X86_FLAG_VALUE(dr7, DR7RW1), X86_FLAG_VALUE(dr7, DR7LEN1), |
| X86_FLAG_VALUE(dr7, DR7RW2), X86_FLAG_VALUE(dr7, DR7LEN2), X86_FLAG_VALUE(dr7, DR7RW3), |
| X86_FLAG_VALUE(dr7, DR7LEN3)); |
| } |
| |
| } // namespace arch |
| } // namespace debug_agent |