|  | // 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/process_breakpoint.h" | 
|  |  | 
|  | #include <inttypes.h> | 
|  | #include <lib/syslog/cpp/macros.h> | 
|  | #include <zircon/syscalls/exception.h> | 
|  |  | 
|  | #include "src/developer/debug/debug_agent/breakpoint.h" | 
|  | #include "src/developer/debug/debug_agent/debug_agent.h" | 
|  | #include "src/developer/debug/debug_agent/debugged_thread.h" | 
|  | #include "src/developer/debug/debug_agent/hardware_breakpoint.h" | 
|  | #include "src/developer/debug/debug_agent/software_breakpoint.h" | 
|  | #include "src/developer/debug/shared/logging/logging.h" | 
|  | #include "src/lib/fxl/strings/string_printf.h" | 
|  |  | 
|  | namespace debug_agent { | 
|  |  | 
|  | ProcessBreakpoint::ProcessBreakpoint(Breakpoint* breakpoint, DebuggedProcess* process, | 
|  | uint64_t address) | 
|  | : process_(process), address_(address), weak_factory_(this) { | 
|  | breakpoints_.push_back(breakpoint); | 
|  | } | 
|  |  | 
|  | ProcessBreakpoint::~ProcessBreakpoint() = default; | 
|  |  | 
|  | debug::Status ProcessBreakpoint::Init() { return Update(); } | 
|  |  | 
|  | fxl::WeakPtr<ProcessBreakpoint> ProcessBreakpoint::GetWeakPtr() { | 
|  | return weak_factory_.GetWeakPtr(); | 
|  | } | 
|  |  | 
|  | debug::Status ProcessBreakpoint::RegisterBreakpoint(Breakpoint* breakpoint) { | 
|  | // Shouldn't get duplicates. | 
|  | if (std::find(breakpoints_.begin(), breakpoints_.end(), breakpoint) != breakpoints_.end()) | 
|  | return debug::Status("Breakpoint already registered"); | 
|  |  | 
|  | // Should be the same type. | 
|  | if (Type() != breakpoint->settings().type) | 
|  | return debug::Status("Breakpoint should be the same type"); | 
|  |  | 
|  | breakpoints_.push_back(breakpoint); | 
|  | // Check if we need to install/uninstall a breakpoint. | 
|  | return Update(); | 
|  | } | 
|  |  | 
|  | bool ProcessBreakpoint::UnregisterBreakpoint(Breakpoint* breakpoint) { | 
|  | DEBUG_LOG(Breakpoint) << "Unregistering breakpoint " << breakpoint->settings().id << " (" | 
|  | << breakpoint->settings().name << ")."; | 
|  |  | 
|  | auto found = std::find(breakpoints_.begin(), breakpoints_.end(), breakpoint); | 
|  | if (found == breakpoints_.end()) { | 
|  | FX_NOTREACHED();  // Should always be found. | 
|  | } else { | 
|  | breakpoints_.erase(found); | 
|  | } | 
|  | // Check if we need to install/uninstall a breakpoint. | 
|  | Update(); | 
|  | return !breakpoints_.empty(); | 
|  | } | 
|  |  | 
|  | bool ProcessBreakpoint::ShouldHitThread(zx_koid_t thread_koid) const { | 
|  | for (const Breakpoint* bp : breakpoints_) { | 
|  | if (bp->AppliesToThread(process_->koid(), thread_koid)) | 
|  | return true; | 
|  | } | 
|  |  | 
|  | return false; | 
|  | } | 
|  |  | 
|  | void ProcessBreakpoint::OnHit(DebuggedThread* hitting_thread, | 
|  | debug_ipc::BreakpointType exception_type, | 
|  | std::vector<debug_ipc::BreakpointStats>& hit_breakpoints, | 
|  | std::vector<debug_ipc::ThreadRecord>& other_affected_threads) { | 
|  | // This will be filled in with the largest scope to stop. | 
|  | debug_ipc::Stop max_stop = debug_ipc::Stop::kNone; | 
|  |  | 
|  | DebugAgent* agent = process_->debug_agent(); | 
|  |  | 
|  | // How much stack to capture for the suspended threads. | 
|  | constexpr auto kSuspendedStackAmount = debug_ipc::ThreadRecord::StackAmount::kMinimal; | 
|  |  | 
|  | hit_breakpoints.clear(); | 
|  | for (Breakpoint* breakpoint : breakpoints_) { | 
|  | // Only care for breakpoints that match the exception type. | 
|  | if (!Breakpoint::DoesExceptionApply(breakpoint->settings().type, exception_type)) | 
|  | continue; | 
|  |  | 
|  | breakpoint->OnHit(); | 
|  |  | 
|  | // The breakpoint stats are for the client. | 
|  | hit_breakpoints.push_back(breakpoint->stats()); | 
|  |  | 
|  | if (static_cast<uint32_t>(breakpoint->settings().stop) > static_cast<uint32_t>(max_stop)) | 
|  | max_stop = breakpoint->settings().stop; | 
|  | } | 
|  |  | 
|  | // Apply the maximal stop mode. | 
|  | switch (max_stop) { | 
|  | case debug_ipc::Stop::kNone: { | 
|  | // In this case the client will be in charge of resuming the thread because it may need to do | 
|  | // stuff like printing a message. | 
|  | break; | 
|  | } | 
|  | case debug_ipc::Stop::kThread: { | 
|  | // The thread is already stopped, nothing to do. | 
|  | break; | 
|  | } | 
|  | case debug_ipc::Stop::kProcess: { | 
|  | // Suspend each thread in the process except the one that just hit the exception (leave it | 
|  | // suspended in the exception). | 
|  | std::vector<debug_ipc::ProcessThreadId> suspended_ids = | 
|  | process_->ClientSuspendAllThreads(hitting_thread->koid()); | 
|  |  | 
|  | // Save the record for each suspended thread. | 
|  | for (const debug_ipc::ProcessThreadId& id : suspended_ids) { | 
|  | if (DebuggedThread* thread = process_->GetThread(id.thread)) | 
|  | other_affected_threads.push_back(thread->GetThreadRecord(kSuspendedStackAmount)); | 
|  | } | 
|  | break; | 
|  | } | 
|  | case debug_ipc::Stop::kAll: { | 
|  | // Suspend each thread in all processes except the one that just hit the exception (leave it | 
|  | // suspended in the exception). | 
|  | std::vector<debug_ipc::ProcessThreadId> proc_thread_pairs = | 
|  | agent->ClientSuspendAll(process_->koid(), hitting_thread->koid()); | 
|  |  | 
|  | for (const debug_ipc::ProcessThreadId& id : proc_thread_pairs) { | 
|  | if (DebuggedThread* thread = agent->GetDebuggedThread(id)) | 
|  | other_affected_threads.push_back(thread->GetThreadRecord(kSuspendedStackAmount)); | 
|  | } | 
|  | break; | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | void ProcessBreakpoint::BeginStepOver(DebuggedThread* thread) { | 
|  | // Note that this request may get silently dropped in some edge cases (see EnqueueStepOver | 
|  | // comment) so don't keep any state about this request. | 
|  | process_->EnqueueStepOver(this, thread); | 
|  | } | 
|  |  | 
|  | }  // namespace debug_agent |