| // 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/hardware_breakpoint.h" | |
| #include <inttypes.h> | |
| #include <zircon/status.h> | |
| #include "src/developer/debug/debug_agent/breakpoint.h" | |
| #include "src/developer/debug/debug_agent/debugged_thread.h" | |
| #include "src/developer/debug/debug_agent/process_breakpoint.h" | |
| #include "src/developer/debug/shared/logging/logging.h" | |
| #include "src/lib/fxl/strings/string_printf.h" | |
| namespace debug_agent { | |
| namespace { | |
| std::string LogPreamble(ProcessBreakpoint* b) { | |
| std::stringstream ss; | |
| ss << "[HW BP 0x" << std::hex << b->address(); | |
| bool first = true; | |
| // Add the names of all the breakpoints associated with this process breakpoint. | |
| ss << " ("; | |
| for (Breakpoint* breakpoint : b->breakpoints()) { | |
| if (!first) { | |
| first = false; | |
| ss << ", "; | |
| } | |
| ss << breakpoint->settings().name; | |
| } | |
| ss << ")] "; | |
| return ss.str(); | |
| } | |
| enum class WarningType { | |
| kInstall, | |
| kUninstall, | |
| }; | |
| void Warn(const debug_ipc::FileLineFunction& origin, WarningType type, zx_koid_t thread_koid, | |
| uint64_t address, zx_status_t status) { | |
| // This happens normally when we receive a ZX_EXCP_THREAD_EXITING exception, | |
| // making the system ignore our uninstall requests. | |
| if (status == ZX_ERR_NOT_FOUND) | |
| return; | |
| const char* verb = type == WarningType::kInstall ? "install" : "uninstall"; | |
| DEBUG_LOG_WITH_LOCATION(Breakpoint, origin) << fxl::StringPrintf( | |
| "Could not %s HW breakpoint for thread %u at " | |
| "%" PRIX64 ": %s", | |
| verb, static_cast<uint32_t>(thread_koid), address, zx_status_get_string(status)); | |
| } | |
| std::set<zx_koid_t> HWThreadsTargeted(const ProcessBreakpoint& pb) { | |
| std::set<zx_koid_t> ids; | |
| bool all_threads = false; | |
| for (Breakpoint* bp : pb.breakpoints()) { | |
| // We only care about hardware breakpoints. | |
| if (bp->settings().type != debug_ipc::BreakpointType::kHardware) | |
| continue; | |
| for (auto& location : bp->settings().locations) { | |
| // We only install for locations that match this process breakpoint. | |
| if (location.address != pb.address()) | |
| continue; | |
| auto thread_id = location.thread_koid; | |
| if (thread_id == 0) { | |
| all_threads = true; | |
| break; | |
| } else { | |
| ids.insert(thread_id); | |
| } | |
| } | |
| // No need to continue searching if a breakpoint wants all threads. | |
| if (all_threads) | |
| break; | |
| } | |
| // If all threads are required, add them all. | |
| if (all_threads) { | |
| for (DebuggedThread* thread : pb.process()->GetThreads()) | |
| ids.insert(thread->koid()); | |
| } | |
| return ids; | |
| } | |
| } // namespace | |
| HardwareBreakpoint::HardwareBreakpoint(Breakpoint* breakpoint, DebuggedProcess* process, | |
| uint64_t address) | |
| : ProcessBreakpoint(breakpoint, process, address) {} | |
| HardwareBreakpoint::~HardwareBreakpoint() { Uninstall(); } | |
| bool HardwareBreakpoint::Installed(zx_koid_t thread_koid) const { | |
| return installed_threads_.count(thread_koid) > 0; | |
| } | |
| // ProcessBreakpoint Implementation ---------------------------------------------------------------- | |
| void HardwareBreakpoint::ExecuteStepOver(DebuggedThread* thread) { | |
| FX_DCHECK(current_stepping_over_threads_.count(thread->koid()) == 0); | |
| FX_DCHECK(!thread->stepping_over_breakpoint()); | |
| DEBUG_LOG(Breakpoint) << LogPreamble(this) << "Thread " << thread->koid() << " is stepping over."; | |
| thread->set_stepping_over_breakpoint(true); | |
| current_stepping_over_threads_.insert(thread->koid()); | |
| // HW breakpoints don't need to suspend any threads. | |
| Uninstall(thread); | |
| // The thread now can continue with the step over. | |
| thread->InternalResumeException(); | |
| } | |
| void HardwareBreakpoint::EndStepOver(DebuggedThread* thread) { | |
| FX_DCHECK(thread->stepping_over_breakpoint()); | |
| FX_DCHECK(current_stepping_over_threads_.count(thread->koid()) > 0); | |
| DEBUG_LOG(Breakpoint) << LogPreamble(this) << "Thread " << thread->koid() << " ending step over."; | |
| thread->set_stepping_over_breakpoint(false); | |
| current_stepping_over_threads_.erase(thread->koid()); | |
| // We reinstall this breakpoint for the thread. | |
| Install(thread); | |
| // Tell the process we're done stepping over. | |
| process_->OnBreakpointFinishedSteppingOver(); | |
| } | |
| // Update ------------------------------------------------------------------------------------------ | |
| zx_status_t HardwareBreakpoint::Update() { | |
| // We get a snapshot of which threads are already installed. | |
| auto current_koids = installed_threads_; | |
| auto koids_to_install = HWThreadsTargeted(*this); | |
| // Uninstall pass. | |
| for (zx_koid_t thread_koid : current_koids) { | |
| if (koids_to_install.count(thread_koid) > 0) | |
| continue; | |
| // The ProcessBreakpoint not longer tracks this. Remove. | |
| DebuggedThread* thread = process()->GetThread(thread_koid); | |
| if (thread) { | |
| zx_status_t status = Uninstall(thread); | |
| if (status != ZX_OK) | |
| continue; | |
| } | |
| installed_threads_.erase(thread_koid); | |
| } | |
| // Install pass. | |
| for (zx_koid_t thread_koid : koids_to_install) { | |
| // If it's already installed, ignore. | |
| if (installed_threads_.count(thread_koid) > 0) | |
| continue; | |
| DebuggedThread* thread = process()->GetThread(thread_koid); | |
| if (!thread) | |
| continue; | |
| zx_status_t status = Install(thread); | |
| if (status != ZX_OK) | |
| continue; | |
| installed_threads_.insert(thread_koid); | |
| } | |
| return ZX_OK; | |
| } | |
| // Install ----------------------------------------------------------------------------------------- | |
| zx_status_t HardwareBreakpoint::Install(DebuggedThread* thread) { | |
| if (!thread) { | |
| Warn(FROM_HERE, WarningType::kInstall, thread->koid(), address(), ZX_ERR_NOT_FOUND); | |
| return ZX_ERR_NOT_FOUND; | |
| } | |
| DEBUG_LOG(Breakpoint) << "Installing HW breakpoint on thread " << thread->koid() | |
| << " on address 0x" << std::hex << address(); | |
| auto suspend_token = thread->InternalSuspend(true); | |
| // Do the actual installation. | |
| if (!thread->thread_handle().InstallHWBreakpoint(address())) { | |
| Warn(FROM_HERE, WarningType::kInstall, thread->koid(), address(), ZX_ERR_INTERNAL); | |
| return ZX_ERR_INTERNAL; | |
| } | |
| return ZX_OK; | |
| } | |
| // Uninstall --------------------------------------------------------------------------------------- | |
| zx_status_t HardwareBreakpoint::Uninstall() { | |
| for (zx_koid_t thread_koid : installed_threads_) { | |
| DebuggedThread* thread = process()->GetThread(thread_koid); | |
| if (!thread) | |
| continue; | |
| zx_status_t res = Uninstall(thread); | |
| if (res != ZX_OK) | |
| continue; | |
| } | |
| return ZX_OK; | |
| } | |
| zx_status_t HardwareBreakpoint::Uninstall(DebuggedThread* thread) { | |
| if (!thread) { | |
| Warn(FROM_HERE, WarningType::kUninstall, thread->koid(), address(), ZX_ERR_NOT_FOUND); | |
| return ZX_ERR_NOT_FOUND; | |
| } | |
| DEBUG_LOG(Breakpoint) << "Removing HW breakpoint on thread " << thread->koid() << " on address 0x" | |
| << std::hex << address(); | |
| auto suspend_token = thread->InternalSuspend(true); | |
| if (!thread->thread_handle().UninstallHWBreakpoint(address())) { | |
| Warn(FROM_HERE, WarningType::kUninstall, thread->koid(), address(), ZX_ERR_INTERNAL); | |
| return ZX_ERR_INTERNAL; | |
| } | |
| return ZX_OK; | |
| } | |
| } // namespace debug_agent |