| // 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 GARNET_BIN_DEBUG_AGENT_PROCESS_BREAKPOINT_H_ |
| #define GARNET_BIN_DEBUG_AGENT_PROCESS_BREAKPOINT_H_ |
| |
| #include <map> |
| |
| #include "garnet/bin/debug_agent/arch.h" |
| #include "garnet/bin/debug_agent/debugged_process.h" |
| #include "garnet/bin/debug_agent/process_memory_accessor.h" |
| #include "garnet/lib/debug_ipc/records.h" |
| #include "lib/fxl/macros.h" |
| |
| namespace debug_agent { |
| |
| class Breakpoint; |
| |
| // Represents one breakpoint address in a single process. One Breakpoint object |
| // can expand to many ProcessBreakpoints across multiple processes and within a |
| // single one (when a symbolic breakpoint expands to multiple addresses). Also, |
| // multiple Breakpoint objects can refer to the same ProcessBreakpoint when |
| // they refer to the same address. |
| 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, |
| ProcessMemoryAccessor* memory_accessor, |
| uint64_t address); |
| ~ProcessBreakpoint(); |
| |
| // Call immediately after construction. If it returns failure, the breakpoint |
| // will not work. |
| zx_status_t Init(); |
| |
| 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). |
| zx_status_t RegisterBreakpoint(Breakpoint* breakpoint); |
| bool UnregisterBreakpoint(Breakpoint* breakpoint); |
| |
| // Writing debug breakpoints changes memory contents. If an unmodified |
| // virtual picture of memory is needed, this function will replace the |
| // replacement from this breakpoint if it appears in the given block. |
| // Otherwise does nothing. |
| void FixupMemoryBlock(debug_ipc::MemoryBlock* block); |
| |
| // 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. |
| // |
| // 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(debug_ipc::BreakpointType exception_type, |
| std::vector<debug_ipc::BreakpointStats>* hit_breakpoints); |
| |
| // 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. |
| // |
| // The thread must be put into single-step mode by the caller when this |
| // function is called. |
| void BeginStepOver(zx_koid_t thread_koid); |
| |
| // When a thread has a "current breakpoint" its handling, exceptions will be |
| // routed here first. A thread has a current breakpoint when it's either |
| // suspended (can not generate exceptions), or when stepping over the |
| // breakpoint. |
| // |
| // This function will return true if the exception was from successfully |
| // stepping over this breakpoint. Otherwise, the stepped-over instruction |
| // (the one with the breakpoint) caused an exception itself (say, an access |
| // violation). In either case, the breakpoint will clean up after itself from |
| // a single-step. |
| bool BreakpointStepHasException( |
| zx_koid_t thread_koid, debug_ipc::NotifyException::Type exception_type); |
| |
| bool SoftwareBreakpointInstalled() const; |
| bool HardwareBreakpointInstalled() const; |
| |
| private: |
| // A breakpoint could be removed in the middle of single-stepping it. We |
| // need to track this to handle the race between deleting it and the |
| // step actually happening. |
| enum class StepStatus { |
| kCurrent, // Single-step currently valid. |
| kObsolete // Breakpoint was removed while single-stepping over. |
| }; |
| |
| // Returns true if the breakpoint is temporarily disabled as one or more |
| // threads step over it. |
| bool CurrentlySteppingOver() const; |
| |
| // Install or uninstall this breakpoint. |
| zx_status_t Update(); // Will add/remove breakpoints as needed/ |
| void Uninstall(); |
| |
| DebuggedProcess* process_; // Not-owning. |
| ProcessMemoryAccessor* memory_accessor_; // Non-owning. |
| |
| uint64_t address_; |
| |
| // 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 a hardware one. That will define what |
| // kind of installation the ProcessBreakpoint implements. Now, if a software |
| // and a separate hardware breakpoint install to the same memory address, they |
| // will implement the same ProcessBreakpoint, which will have both |
| // |software_breakpoint_| and |hardware_breakpoint_| members instanced. |
| // Null means that that particular installation is not used. |
| class SoftwareBreakpoint; |
| class HardwareBreakpoint; |
| std::unique_ptr<SoftwareBreakpoint> software_breakpoint_; |
| std::unique_ptr<HardwareBreakpoint> hardware_breakpoint_; |
| |
| // Breakpoints that refer to this ProcessBreakpoint. More than one Breakpoint |
| // can refer to the same memory address. |
| std::vector<Breakpoint*> breakpoints_; |
| |
| // Tracks the threads currently single-stepping over this breakpoint. |
| // Normally this will be empty (nobody) or have one thread, but could be more |
| // than one in rare cases. Maps thread koid to status. |
| // |
| // A step is executed by putting back the original instruction, stepping the |
| // thread, and then re-inserting the breakpoint instruction. The breakpoint |
| // instruction can't be put back until there are no more "kCurrent" threads |
| // in this map. |
| // |
| // This could be a simple refcount, but is a set so we can more robustly |
| // check for mistakes. CurrentlySteppingOver() checks this list to see if |
| // the breakpoint is disabled due to stepping. |
| // |
| // TODO(brettw) disabling the breakpoint opens a window where another thread |
| // can execute and miss the breakpoint. To avoid this, we need to implement |
| // something similar to GDB's "displaced step" to execute the instruction |
| // without ever removing the breakpoint instruction. |
| std::map<zx_koid_t, StepStatus> thread_step_over_; |
| |
| FXL_DISALLOW_COPY_AND_ASSIGN(ProcessBreakpoint); |
| }; |
| |
| } // namespace debug_agent |
| |
| #endif // GARNET_BIN_DEBUG_AGENT_PROCESS_BREAKPOINT_H_ |