| // 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 |