| // Copyright 2020 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 <lib/syslog/cpp/macros.h> | |
| #include <zircon/hw/debug/arm64.h> | |
| #include "src/developer/debug/debug_agent/arch.h" | |
| #include "src/developer/debug/debug_agent/debug_registers.h" | |
| #include "src/developer/debug/shared/arch_arm64.h" | |
| #include "src/developer/debug/shared/logging/logging.h" | |
| #include "src/lib/fxl/strings/string_printf.h" | |
| namespace debug_agent { | |
| namespace { | |
| // Validates that the range is properly aligned and masked. Returns 0 on failure. | |
| uint64_t ValidateRange(const debug_ipc::AddressRange& range) { | |
| constexpr uint64_t kMask = 0b11; | |
| if (range.size() == 1) { | |
| return range.begin() & ~kMask; | |
| } else if (range.size() == 2) { | |
| // Should be 2-byte aligned. | |
| if ((range.begin() & 0b1) != 0) | |
| return 0; | |
| return range.begin() & ~kMask; | |
| } else if (range.size() == 4) { | |
| // Should be 4-byte aligned. | |
| if ((range.begin() & 0b11) != 0) | |
| return 0; | |
| return range.begin() & ~kMask; | |
| } else if (range.size() == 8) { | |
| // Should be 8-byte aligned. | |
| if ((range.begin() & 0b111) != 0) | |
| return 0; | |
| return range.begin() & ~kMask; | |
| } else { | |
| return 0; | |
| } | |
| } | |
| // Returns the bit flag to enable each different kind of watchpoint. | |
| uint32_t GetWatchpointWriteFlag(debug_ipc::BreakpointType type) { | |
| // clang-format off | |
| switch (type) { | |
| case debug_ipc::BreakpointType::kReadWrite: return 0b11; | |
| case debug_ipc::BreakpointType::kWrite: return 0b10; | |
| case debug_ipc::BreakpointType::kSoftware: | |
| case debug_ipc::BreakpointType::kHardware: | |
| case debug_ipc::BreakpointType::kLast: | |
| break; | |
| } | |
| // clang-format on | |
| FX_NOTREACHED() << "Invalid breakpoint type: " << static_cast<uint32_t>(type); | |
| return 0; | |
| } | |
| void SetWatchpointFlags(uint32_t* dbgwcr, debug_ipc::BreakpointType type, uint64_t base_address, | |
| const debug_ipc::AddressRange& range) { | |
| if (range.size() == 1) { | |
| uint32_t bas = 1u << (range.begin() - base_address); | |
| ARM64_DBGWCR_BAS_SET(dbgwcr, bas); | |
| } else if (range.size() == 2) { | |
| uint32_t bas = 0b11 << (range.begin() - base_address); | |
| ARM64_DBGWCR_BAS_SET(dbgwcr, bas); | |
| } else if (range.size() == 4) { | |
| uint32_t bas = 0b1111 << (range.begin() - base_address); | |
| ARM64_DBGWCR_BAS_SET(dbgwcr, bas); | |
| } else if (range.size() == 8) { | |
| ARM64_DBGWCR_BAS_SET(dbgwcr, 0xff); | |
| } else { | |
| FX_NOTREACHED() << "Invalid range size: " << range.size(); | |
| } | |
| // Set type. | |
| ARM64_DBGWCR_LSC_SET(dbgwcr, GetWatchpointWriteFlag(type)); | |
| // Set enabled. | |
| ARM64_DBGWCR_E_SET(dbgwcr, 1); | |
| } | |
| uint32_t GetWatchpointLength(uint32_t dbgwcr) { | |
| // Because base range addresses have to be 4 bytes aligned, having a watchpoint for smaller ranges | |
| // (1, 2 or 4 bytes) could have many combinarions of the BAS register (which determines which | |
| // byte will trigger an exception offseted from the base range address. | |
| // clang-format off | |
| uint32_t bas = ARM64_DBGWCR_BAS_GET(dbgwcr); | |
| switch (bas) { | |
| case 0b00000000: return 0; | |
| case 0b00000001: return 1; | |
| case 0b00000010: return 1; | |
| case 0b00000100: return 1; | |
| case 0b00001000: return 1; | |
| case 0b00010000: return 1; | |
| case 0b00100000: return 1; | |
| case 0b01000000: return 1; | |
| case 0b10000000: return 1; | |
| case 0b00000011: return 2; | |
| case 0b00001100: return 2; | |
| case 0b00110000: return 2; | |
| case 0b11000000: return 2; | |
| case 0b00001111: return 4; | |
| case 0b11110000: return 4; | |
| case 0b11111111: return 8; | |
| default: | |
| FX_NOTREACHED() << "Wrong bas value: 0x" << std::hex << bas; | |
| return 0; | |
| } | |
| // clang-format on | |
| } | |
| } // namespace | |
| bool DebugRegisters::SetHWBreakpoint(uint64_t address) { | |
| // We search for an unset register. | |
| int slot = -1; | |
| for (int i = 0; i < regs_.hw_bps_count; i++) { | |
| auto& hw_bp = regs_.hw_bps[i]; | |
| // If we found the same address, we don't care if it's disabled, we simply | |
| // mark this as our slot. | |
| // If the address is 0, this is an empty slot too. | |
| if ((hw_bp.dbgbvr == address) || (hw_bp.dbgbvr == 0)) { | |
| slot = i; | |
| break; | |
| } | |
| // If it's active this is not an empty slot. | |
| if ((hw_bp.dbgbcr & 1u) == 1) | |
| continue; | |
| slot = i; | |
| break; | |
| } | |
| if (slot == -1) | |
| return false; | |
| regs_.hw_bps[slot].dbgbcr |= 1u; | |
| regs_.hw_bps[slot].dbgbvr = address; | |
| return true; | |
| } | |
| bool DebugRegisters::RemoveHWBreakpoint(uint64_t address) { | |
| // Search for an breakpoint with this address. | |
| int slot = -1; | |
| for (int i = 0; i < regs_.hw_bps_count; i++) { | |
| auto& hw_bp = regs_.hw_bps[i]; | |
| if (hw_bp.dbgbvr == address) { | |
| slot = i; | |
| break; | |
| } | |
| } | |
| if (slot == -1) | |
| return false; | |
| regs_.hw_bps[slot].dbgbcr = 0; | |
| regs_.hw_bps[slot].dbgbvr = 0; | |
| return true; | |
| } | |
| std::optional<WatchpointInfo> DebugRegisters::SetWatchpoint(debug_ipc::BreakpointType type, | |
| const debug_ipc::AddressRange& range, | |
| uint32_t watchpoint_count) { | |
| FX_DCHECK(watchpoint_count <= 16); | |
| if (!IsWatchpointType(type)) { | |
| FX_NOTREACHED() << "Requires a watchpoint type, received " | |
| << debug_ipc::BreakpointTypeToString(type); | |
| return std::nullopt; | |
| } | |
| uint64_t base_address = ValidateRange(range); | |
| if (base_address == 0) { | |
| DEBUG_LOG(ArchArm64) << "Range is not valid for added watchpoint: " << range.ToString(); | |
| return std::nullopt; | |
| } | |
| // Search for a free slot. | |
| int slot = -1; | |
| for (uint32_t i = 0; i < watchpoint_count; i++) { | |
| if (regs_.hw_wps[i].dbgwvr == 0) { | |
| slot = i; | |
| break; | |
| } | |
| // If it's the same address, we need to compare length. | |
| uint32_t length = GetWatchpointLength(regs_.hw_wps[i].dbgwcr); | |
| if (regs_.hw_wps[i].dbgwvr == base_address && length == range.size()) { | |
| DEBUG_LOG(ArchArm64) << "Breakpoint range already exists: " << range.ToString(); | |
| return std::nullopt; | |
| } | |
| } | |
| if (slot == -1) { | |
| DEBUG_LOG(ArchArm64) << "No more hardware breakpoints. Not adding new one."; | |
| return std::nullopt; | |
| } | |
| // We found a slot, we bind the watchpoint. | |
| regs_.hw_wps[slot].dbgwvr = base_address; | |
| SetWatchpointFlags(®s_.hw_wps[slot].dbgwcr, type, base_address, range); | |
| return WatchpointInfo(range, slot); | |
| } | |
| bool DebugRegisters::RemoveWatchpoint(const debug_ipc::AddressRange& range, | |
| uint32_t watchpoint_count) { | |
| FX_DCHECK(watchpoint_count <= 16); | |
| uint32_t base_address = ValidateRange(range); | |
| if (base_address == 0) { | |
| DEBUG_LOG(ArchArm64) << "Range is not valid for removed watchpoint: " << range.ToString(); | |
| return ZX_ERR_OUT_OF_RANGE; | |
| } | |
| // Search for a slot that matches. | |
| int slot = -1; | |
| for (uint32_t i = 0; i < watchpoint_count; i++) { | |
| if (regs_.hw_wps[i].dbgwvr == 0) | |
| continue; | |
| // If it's the same address, we need to compare length. | |
| uint32_t length = GetWatchpointLength(regs_.hw_wps[i].dbgwcr); | |
| if (regs_.hw_wps[i].dbgwvr == base_address && length == range.size()) { | |
| slot = i; | |
| break; | |
| } | |
| } | |
| if (slot == -1) { | |
| DEBUG_LOG(ArchArm64) << "Range is not found for removed watchpoint: " << range.ToString(); | |
| return false; | |
| } | |
| // Clear the slot. | |
| regs_.hw_wps[slot].dbgwcr = 0; | |
| regs_.hw_wps[slot].dbgwvr = 0; | |
| return true; | |
| } | |
| std::optional<WatchpointInfo> DebugRegisters::DecodeHitWatchpoint() const { | |
| DEBUG_LOG(ArchArm64) << "Got FAR: 0x" << std::hex << regs_.far; | |
| // Get the closest watchpoint. | |
| uint64_t min_distance = UINT64_MAX; | |
| int closest_index = -1; | |
| debug_ipc::AddressRange closest_range = {}; | |
| for (uint32_t i = 0; i < arch::GetHardwareWatchpointCount(); i++) { | |
| uint64_t dbgwcr = regs_.hw_wps[i].dbgwcr; | |
| uint64_t dbgwvr = regs_.hw_wps[i].dbgwvr; // The actual watchpoint address. | |
| DEBUG_LOG(ArchArm64) << "DBGWCR " << i << ": 0x" << std::hex << dbgwcr; | |
| if (!ARM64_DBGWCR_E_GET(dbgwcr)) | |
| continue; | |
| uint32_t length = GetWatchpointLength(dbgwcr); | |
| if (length == 0) | |
| continue; | |
| const debug_ipc::AddressRange wp_range = {dbgwvr, dbgwvr + length}; | |
| if (wp_range.InRange(regs_.far)) | |
| return WatchpointInfo(wp_range, i); | |
| // Otherwise find the distance and then decide on the closest one. | |
| uint64_t distance = UINT64_MAX; | |
| if (regs_.far < wp_range.begin()) { | |
| distance = wp_range.begin() - regs_.far; | |
| } else if (regs_.far >= wp_range.end()) { | |
| distance = regs_.far - wp_range.end(); | |
| } else { | |
| FX_NOTREACHED() << "Invalid far/range combo. FAR: 0x" << std::hex << regs_.far | |
| << ", range: " << wp_range.begin() << ", " << wp_range.end(); | |
| } | |
| if (distance < min_distance) { | |
| min_distance = distance; | |
| closest_index = i; | |
| closest_range = wp_range; | |
| } | |
| } | |
| return WatchpointInfo(closest_range, closest_index); | |
| } | |
| void DebugRegisters::SetForHitWatchpoint(int slot) { | |
| // ARM64 breakpoint status is nto communicated in registers so there's nothing to do. | |
| } | |
| std::string DebugRegisters::ToString() const { | |
| std::stringstream ss; | |
| ss << "ESR: 0x" << std::hex << regs_.esr << std::endl; | |
| ss << "HW breakpoints: " << std::endl; | |
| for (size_t i = 0; i < std::size(regs_.hw_bps); i++) { | |
| uint32_t dbgbcr = regs_.hw_bps[i].dbgbcr; | |
| uint64_t dbgbvr = regs_.hw_bps[i].dbgbvr; | |
| if (dbgbvr == 0) | |
| continue; | |
| ss << fxl::StringPrintf( | |
| "%02lu. DBGBVR: 0x%lx, DBGBCR: E=%d, PMC=%d, BAS=%d, HMC=%d, SSC=%d, LBN=%d, BT=%d", i, | |
| dbgbvr, ARM64_FLAG_VALUE(dbgbcr, DBGBCR, E), ARM64_FLAG_VALUE(dbgbcr, DBGBCR, PMC), | |
| ARM64_FLAG_VALUE(dbgbcr, DBGBCR, BAS), ARM64_FLAG_VALUE(dbgbcr, DBGBCR, HMC), | |
| ARM64_FLAG_VALUE(dbgbcr, DBGBCR, SSC), ARM64_FLAG_VALUE(dbgbcr, DBGBCR, LBN), | |
| ARM64_FLAG_VALUE(dbgbcr, DBGBCR, BT)); | |
| ss << std::endl; | |
| } | |
| ss << "Watchpoints: " << std::endl; | |
| for (size_t i = 0; i < std::size(regs_.hw_wps); i++) { | |
| uint32_t dbgwcr = regs_.hw_wps[i].dbgwcr; | |
| uint64_t dbgwvr = regs_.hw_wps[i].dbgwvr; | |
| if (dbgwvr == 0) | |
| continue; | |
| ss << fxl::StringPrintf( | |
| "%02lu. DBGWVR: 0x%lx, DBGWCR: " | |
| "E=%d, PAC=%d, LSC=%d, BAS=0x%x, HMC=%d, SSC=%d, LBN=%d, WT=%d, MASK=0x%x", | |
| i, dbgwvr, ARM64_DBGWCR_E_GET(dbgwcr), ARM64_DBGWCR_PAC_GET(dbgwcr), | |
| ARM64_DBGWCR_LSC_GET(dbgwcr), ARM64_DBGWCR_BAS_GET(dbgwcr), | |
| ARM64_DBGWCR_HMC_GET(dbgwcr), ARM64_DBGWCR_SSC_GET(dbgwcr), | |
| ARM64_DBGWCR_LBN_GET(dbgwcr), ARM64_DBGWCR_WT_GET(dbgwcr), | |
| ARM64_DBGWCR_MSK_GET(dbgwcr)) | |
| << std::endl; | |
| } | |
| return ss.str(); | |
| } | |
| } // namespace debug_agent |