| // 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. | |
| #ifndef SRC_DEVELOPER_DEBUG_DEBUG_AGENT_PROCESS_BREAKPOINT_H_ | |
| #define SRC_DEVELOPER_DEBUG_DEBUG_AGENT_PROCESS_BREAKPOINT_H_ | |
| #include <set> | |
| #include "src/developer/debug/debug_agent/arch.h" | |
| #include "src/developer/debug/debug_agent/debugged_process.h" | |
| #include "src/developer/debug/ipc/records.h" | |
| #include "src/developer/debug/shared/status.h" | |
| #include "src/lib/fxl/macros.h" | |
| #include "src/lib/fxl/memory/ref_ptr.h" | |
| #include "src/lib/fxl/memory/weak_ptr.h" | |
| namespace debug_ipc { | |
| struct ThreadRecord; | |
| } | |
| namespace debug_agent { | |
| class Breakpoint; | |
| class HardwareBreakpoint; | |
| class SoftwareBreakpoint; | |
| // Low-level implementations of the breakpoints. A ProcessBreakpoint represents the actual | |
| // "installation" of a Breakpoint in a particular location (address). A Breakpoint can have many | |
| // locations: | |
| // | |
| // b Foo() -> If Foo() is inlined, you can get 2+ locations. | |
| // | |
| // In that base, that Breakpoint will have two locations, which means two "installations", or | |
| // ProcessBreakpoint. | |
| // | |
| // A Breakpoint can be a software or hardware one. That will define what kind of specialization the | |
| // ProcessBreakpoint implements. | |
| class ProcessBreakpoint { | |
| public: | |
| // Given the initial Breakpoint object this corresponds to. Breakpoints can be added or removed | |
| // later. | |
| // | |
| // Call Init() immediately after construction to initialize the parts that can report errors. | |
| explicit ProcessBreakpoint(Breakpoint* breakpoint, DebuggedProcess* debugged_process, | |
| uint64_t address); | |
| virtual ~ProcessBreakpoint(); | |
| virtual debug_ipc::BreakpointType Type() const = 0; | |
| // Call immediately after construction. If it returns failure, the breakpoint will not work. | |
| debug::Status Init(); | |
| virtual bool Installed(zx_koid_t thread_koid) const = 0; | |
| fxl::WeakPtr<ProcessBreakpoint> GetWeakPtr(); | |
| zx_koid_t process_koid() const { return process_->koid(); } | |
| DebuggedProcess* process() const { return process_; } | |
| uint64_t address() const { return address_; } | |
| const std::vector<Breakpoint*>& breakpoints() const { return breakpoints_; } | |
| // Adds or removes breakpoints associated with this process/address. Unregister returns whether | |
| // there are still any breakpoints referring to this address (false means this is unused and | |
| // should be deleted). | |
| debug::Status RegisterBreakpoint(Breakpoint* breakpoint); | |
| bool UnregisterBreakpoint(Breakpoint* breakpoint); | |
| // When a thread receives a breakpoint exception installed by a process breakpoint, it must check | |
| // if the breakpoint was indeed intended to apply to it (we can have thread-specific breakpoints). | |
| bool ShouldHitThread(zx_koid_t thread_koid) const; | |
| // Notification that this breakpoint was just hit. All affected Breakpoints will have their stats | |
| // updated and placed in the *stats param. This makes a difference whether the exceptions was | |
| // software or hardware (debug registers) triggered. | |
| // | |
| // All threads requested to be suspended (in any process) by this breakpoint's settings will be | |
| // filled into |other_affected_threads|. | |
| // | |
| // IMPORTANT: The caller should check the stats and for any breakpoint with "should_delete" set, | |
| // remove the breakpoints. This can't conveniently be done within this call because it will cause | |
| // this ProcessBreakpoint object to be deleted from within itself. | |
| void OnHit(DebuggedThread* hitting_thread, debug_ipc::BreakpointType exception_type, | |
| std::vector<debug_ipc::BreakpointStats>& hit_breakpoints, | |
| std::vector<debug_ipc::ThreadRecord>& other_affected_threads); | |
| // Call before single-stepping over a breakpoint. This will remove the breakpoint such that it | |
| // will be put back when the exception is hit and BreakpointStepHasException() is called. | |
| // | |
| // This will not execute the stepping over directly, but rather enqueue it within the process so | |
| // that each stepping over is done one at a time. | |
| // | |
| // The actual stepping over logic is done by |ExecuteStepOver|, which is called by the process. | |
| // | |
| // NOTE: From this moment, the breakpoint "takes over" the "run-lifetime" of the thread. This | |
| // means that it will suspend and resume it according to what threads are stepping over it. | |
| virtual void BeginStepOver(DebuggedThread* thread); | |
| // When a thread has a "current breakpoint" its handling and gets a single step exception, it | |
| // means that it's done stepping over it and calls this in order to resolve the stepping. | |
| // | |
| // This will tell the process that this stepping over instance is done and will call | |
| // |OnBreakpointFinishedSteppingOver|, which will advance the queue so that the other queued step | |
| // overs can occur. | |
| // | |
| // NOTE: Even though the thread is done stepping over, this will *not* resume the suspended | |
| // threads nor the excepted (stepping over) thread. This is done on |StepOverCleanup|. | |
| // This is because there might be another breakpoint queued up and that breakpoint needs a | |
| // chance to suspend the threads before these are unsuspended from the previous breakpoint. | |
| // | |
| // Otherwise we introduce a race between the current step over breakpoint resuming the | |
| // threads and the next one suspending them. | |
| // | |
| // With the new order, the process will first call the next process |ExecuteStepOver|, which | |
| // will suspend the corresponding threads and then |StepOverCleanup| will free the | |
| // threads suspended by the current one. | |
| virtual void EndStepOver(DebuggedThread* thread) = 0; | |
| // Called by the queue-owning process. | |
| // | |
| // This function actually sets up the stepping over and suspend *all* other threads. When the | |
| // thread is done stepping over, it will call the process |OnBreakpointFinishedSteppingOver| | |
| // function. | |
| virtual void ExecuteStepOver(DebuggedThread* thread) = 0; | |
| // Frees all the suspension and exception resources held by the breakpoint. This is called by the | |
| // process. | |
| // | |
| // See the comments of |EndStepOver| for more details. | |
| virtual void StepOverCleanup(DebuggedThread* thread) = 0; | |
| virtual debug::Status Update() = 0; | |
| protected: | |
| DebuggedProcess* process_; // Not-owning. | |
| uint64_t address_; | |
| private: | |
| virtual debug::Status Uninstall(DebuggedThread* thread) = 0; | |
| virtual debug::Status Uninstall() = 0; // Uninstall for all the threads. | |
| // Breakpoints that refer to this ProcessBreakpoint. More than one Breakpoint can refer to the | |
| // same memory address. | |
| std::vector<Breakpoint*> breakpoints_; | |
| fxl::WeakPtrFactory<ProcessBreakpoint> weak_factory_; | |
| FXL_DISALLOW_COPY_AND_ASSIGN(ProcessBreakpoint); | |
| }; | |
| } // namespace debug_agent | |
| #endif // SRC_DEVELOPER_DEBUG_DEBUG_AGENT_PROCESS_BREAKPOINT_H_ |