| // Copyright 2019 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/watchpoint.h" | 
 |  | 
 | #include <zircon/status.h> | 
 |  | 
 | #include "src/developer/debug/debug_agent/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 << "[WP 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, | 
 | }; | 
 |  | 
 | // Passing OK as the status omits printing it. | 
 | void Warn(debug_ipc::FileLineFunction origin, WarningType type, zx_koid_t thread_koid, | 
 |           uint64_t address, zx_status_t status = ZX_OK) { | 
 |   // 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"; | 
 |   printf("[%s:%d][%s] Could not %s HW breakpoint for thread %u at %" PRIX64, origin.file().c_str(), | 
 |          origin.line(), origin.function().c_str(), verb, static_cast<uint32_t>(thread_koid), | 
 |          address); | 
 |   if (status == ZX_OK) | 
 |     printf("\n"); | 
 |   else | 
 |     printf(": %s\n", zx_status_get_string(status)); | 
 |  | 
 |   fflush(stdout); | 
 | } | 
 |  | 
 | std::set<zx_koid_t> ThreadsTargeted(const Watchpoint& watchpoint) { | 
 |   std::set<zx_koid_t> ids; | 
 |   bool all_threads = false; | 
 |   for (Breakpoint* bp : watchpoint.breakpoints()) { | 
 |     // We only care about breakpoint that cover our case. | 
 |     if (!Breakpoint::DoesExceptionApply(watchpoint.Type(), bp->settings().type)) | 
 |       continue; | 
 |  | 
 |     for (auto& location : bp->settings().locations) { | 
 |       // We only install for locations that match this process breakpoint. | 
 |       if (location.address_range != watchpoint.range()) | 
 |         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 : watchpoint.process()->GetThreads()) | 
 |       ids.insert(thread->koid()); | 
 |   } | 
 |  | 
 |   return ids; | 
 | } | 
 |  | 
 | }  // namespace | 
 |  | 
 | Watchpoint::Watchpoint(debug_ipc::BreakpointType type, Breakpoint* breakpoint, | 
 |                        DebuggedProcess* process, const debug_ipc::AddressRange& range) | 
 |     : ProcessBreakpoint(breakpoint, process, range.begin()), type_(type), range_(range) { | 
 |   FX_DCHECK(IsWatchpointType(type)) | 
 |       << "Wrong breakpoint type: " << debug_ipc::BreakpointTypeToString(type); | 
 | } | 
 |  | 
 | Watchpoint::~Watchpoint() { Uninstall(); } | 
 |  | 
 | bool Watchpoint::Installed(zx_koid_t thread_koid) const { | 
 |   return installed_threads_.count(thread_koid) > 0; | 
 | } | 
 |  | 
 | bool Watchpoint::MatchesException(zx_koid_t thread_koid, uint64_t watchpoint_address, int slot) { | 
 |   for (auto& [tid, installation] : installed_threads_) { | 
 |     if (tid != thread_koid) | 
 |       continue; | 
 |  | 
 |     if (installation.slot != slot) | 
 |       continue; | 
 |  | 
 |     if (installation.range.InRange(watchpoint_address)) | 
 |       return true; | 
 |   } | 
 |  | 
 |   return false; | 
 | } | 
 |  | 
 | // ProcessBreakpoint Implementation ---------------------------------------------------------------- | 
 |  | 
 | void Watchpoint::ExecuteStepOver(DebuggedThread* thread) { | 
 |   FX_DCHECK(current_stepping_over_threads_.count(thread->koid()) == 0); | 
 |   FX_DCHECK(!thread->stepping_over_breakpoint()); | 
 |  | 
 |   DEBUG_LOG(Watchpoint) << 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 Watchpoint::EndStepOver(DebuggedThread* thread) { | 
 |   FX_DCHECK(thread->stepping_over_breakpoint()); | 
 |   FX_DCHECK(current_stepping_over_threads_.count(thread->koid()) > 0); | 
 |  | 
 |   DEBUG_LOG(Watchpoint) << 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 Watchpoint::Update() { | 
 |   // We get a snapshot of which threads are already installed. | 
 |   auto current_installs = installed_threads_; | 
 |   auto koids_to_install = ThreadsTargeted(*this); | 
 |  | 
 |   // Uninstall pass. | 
 |   for (auto& [thread_koid, installation] : current_installs) { | 
 |     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; | 
 |   } | 
 |  | 
 |   return ZX_OK; | 
 | } | 
 |  | 
 | // Install ----------------------------------------------------------------------------------------- | 
 |  | 
 | bool Watchpoint::Install(DebuggedThread* thread) { | 
 |   if (!thread) { | 
 |     Warn(FROM_HERE, WarningType::kInstall, thread->koid(), address(), ZX_ERR_NOT_FOUND); | 
 |     return false; | 
 |   } | 
 |  | 
 |   DEBUG_LOG(Watchpoint) << "Installing watchpoint on thread " << thread->koid() << " on address 0x" | 
 |                         << std::hex << address(); | 
 |  | 
 |   auto suspend_token = thread->InternalSuspend(true); | 
 |  | 
 |   // Do the actual installation. | 
 |   auto result = thread->thread_handle().InstallWatchpoint(type_, range_); | 
 |   if (!result) { | 
 |     Warn(FROM_HERE, WarningType::kInstall, thread->koid(), address()); | 
 |     return false; | 
 |   } | 
 |  | 
 |   installed_threads_[thread->koid()] = *result; | 
 |   return true; | 
 | } | 
 |  | 
 | // Uninstall --------------------------------------------------------------------------------------- | 
 |  | 
 | zx_status_t Watchpoint::Uninstall() { | 
 |   std::vector<zx_koid_t> uninstalled_threads; | 
 |   for (auto& [thread_koid, installation] : installed_threads_) { | 
 |     DebuggedThread* thread = process()->GetThread(thread_koid); | 
 |     if (!thread) | 
 |       continue; | 
 |  | 
 |     if (Uninstall(thread) != ZX_OK) | 
 |       continue; | 
 |  | 
 |     uninstalled_threads.push_back(thread_koid); | 
 |   } | 
 |  | 
 |   // Remove them from the list. | 
 |   for (zx_koid_t thread_koid : uninstalled_threads) { | 
 |     installed_threads_.erase(thread_koid); | 
 |   } | 
 |  | 
 |   return ZX_OK; | 
 | } | 
 |  | 
 | zx_status_t Watchpoint::Uninstall(DebuggedThread* thread) { | 
 |   if (!thread) { | 
 |     Warn(FROM_HERE, WarningType::kUninstall, thread->koid(), address(), ZX_ERR_NOT_FOUND); | 
 |     return ZX_ERR_NOT_FOUND; | 
 |   } | 
 |  | 
 |   DEBUG_LOG(Watchpoint) << "Removing watchpoint on thread " << thread->koid() << " on address 0x" | 
 |                         << std::hex << address(); | 
 |  | 
 |   auto suspend_token = thread->InternalSuspend(true); | 
 |  | 
 |   if (!thread->thread_handle().UninstallWatchpoint(range_)) { | 
 |     Warn(FROM_HERE, WarningType::kUninstall, thread->koid(), address()); | 
 |     return ZX_ERR_INTERNAL; | 
 |   } | 
 |  | 
 |   return ZX_OK; | 
 | } | 
 |  | 
 | }  // namespace debug_agent |