| // 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_DEBUGGED_THREAD_H_ |
| #define SRC_DEVELOPER_DEBUG_DEBUG_AGENT_DEBUGGED_THREAD_H_ |
| |
| #include <lib/zx/exception.h> |
| #include <lib/zx/thread.h> |
| #include <zircon/syscalls/exception.h> |
| |
| #include "src/developer/debug/debug_agent/arch.h" |
| #include "src/developer/debug/debug_agent/general_registers.h" |
| #include "src/developer/debug/debug_agent/object_provider.h" |
| #include "src/developer/debug/debug_agent/thread_exception.h" |
| #include "src/developer/debug/debug_agent/thread_handle.h" |
| #include "src/developer/debug/ipc/protocol.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_agent { |
| |
| class DebugAgent; |
| class DebuggedProcess; |
| class ObjectProvider; |
| class ProcessBreakpoint; |
| class Watchpoint; |
| |
| enum class ThreadCreationOption { |
| // Already running, don't do anything |
| kRunningKeepRunning, |
| |
| // Already suspended, keep it suspended |
| kSuspendedKeepSuspended, |
| |
| // Already suspended, run it |
| kSuspendedShouldRun |
| }; |
| |
| class DebuggedThread { |
| public: |
| // Represents a ref-counted suspend token to the debugged thread. |
| // As long as one of these token is valid, the thread will maintain suspended. |
| class SuspendToken { |
| public: |
| ~SuspendToken(); |
| |
| private: |
| SuspendToken(DebuggedThread*); |
| |
| fxl::WeakPtr<DebuggedThread> thread_; |
| |
| FXL_DISALLOW_COPY_ASSIGN_AND_MOVE(SuspendToken); |
| friend class ::debug_agent::DebuggedThread; |
| }; |
| |
| // Represents the state the client thinks this thread is in. Certain |
| // operations can suspend all the threads of a process and the debugger needs |
| // to know which threads should remain suspended after that operation is done. |
| enum class ClientState { |
| kRunning, |
| kPaused, |
| }; |
| const char* ClientStateToString(ClientState); |
| |
| struct CreateInfo { |
| DebuggedProcess* process = nullptr; |
| zx_koid_t koid = 0; |
| std::unique_ptr<ThreadHandle> handle; |
| ThreadCreationOption creation_option = ThreadCreationOption::kRunningKeepRunning; |
| |
| std::unique_ptr<ThreadException> exception; // Optional. |
| |
| std::shared_ptr<arch::ArchProvider> arch_provider; |
| std::shared_ptr<ObjectProvider> object_provider; |
| }; |
| DebuggedThread(DebugAgent*, CreateInfo&&); |
| virtual ~DebuggedThread(); |
| |
| const DebuggedProcess* process() const { return process_; } |
| |
| zx_koid_t koid() const { return koid_; } |
| |
| const ThreadHandle& thread_handle() const { return *thread_handle_; } |
| ThreadHandle& thread_handle() { return *thread_handle_; } |
| |
| // TODO(brettw) remove this and have all callers use thread_handle(). |
| zx::thread& handle() { return thread_handle_->GetNativeHandle(); } |
| const zx::thread& handle() const { return thread_handle_->GetNativeHandle(); } |
| |
| ThreadException* exception_handle() { return exception_handle_.get(); } |
| const ThreadException* exception_handle() const { return exception_handle_.get(); } |
| void set_exception_handle(std::unique_ptr<ThreadException> exception) { |
| exception_handle_ = std::move(exception); |
| } |
| |
| fxl::WeakPtr<DebuggedThread> GetWeakPtr(); |
| |
| void OnException(std::unique_ptr<ThreadException> exception_handle, |
| zx_exception_info_t exception_info); |
| |
| // Resumes execution of the thread. The thread should currently be in a |
| // stopped state. If it's not stopped, this will be ignored. |
| void Resume(const debug_ipc::ResumeRequest& request); |
| |
| // Resumes the thread according to the current run mode. |
| void ResumeForRunMode(); |
| |
| // Resume the thread from an exception. |
| // If |exception_handle_| is not valid, this will no-op. |
| virtual void ResumeException(); |
| |
| // Resume the thread from a suspension. |
| // if |suspend_token_| is not valid, this will no-op. |
| virtual void ResumeSuspension(); |
| |
| // Pauses execution of the thread. Pausing happens asynchronously so the |
| // thread will not necessarily have stopped when this returns. Set the |
| // |synchronous| flag for blocking on the suspended signal and make this call |
| // block until the thread is suspended. |
| // |
| // |new_state| represents what the new state of the client should be. If no |
| // change is wanted, you can use the overload that doesn't receives that. |
| // |
| // Returns true if the thread was running at the moment of this call being |
| // made. Returns false if it was on a suspension condition (suspended or on an |
| // exception). |
| virtual bool Suspend(bool synchronous = false); |
| |
| // Pauses execution of the thread. Pausing happens asynchronously so the thread will not |
| // necessarily have stopped when this returns. Set the |synchronous| flag for blocking on the |
| // suspended signal and make this call block. |
| // |
| // Suspension is ref-counted on the thread. This is done by returning a suspend token that will |
| // keep track of how many suspensions this thread has. As long as there is a valid one, the |
| // thread will remain suspended. |
| [[nodiscard]] virtual std::unique_ptr<SuspendToken> RefCountedSuspend(bool synchronous = false); |
| |
| // The typical suspend deadline users should use when suspending. |
| static zx::time DefaultSuspendDeadline(); |
| |
| // Waits on a suspension token. |
| // Returns true if we could find a valid suspension condition (either |
| // suspended or on an exception). False if timeout or error. |
| virtual bool WaitForSuspension(zx::time deadline = DefaultSuspendDeadline()); |
| |
| // Fills the thread status record. If full_stack is set, a full backtrace will be generated, |
| // otherwise a minimal one will be generated. |
| // |
| // If the optional registers is set, will contain the current registers of the thread. If null, |
| // these will be fetched automatically (this is an optimization for cases where the caller has |
| // already requested registers). |
| debug_ipc::ThreadRecord GetThreadRecord( |
| debug_ipc::ThreadRecord::StackAmount stack_amount, |
| std::optional<GeneralRegisters> regs = std::nullopt) const; |
| |
| // Register reading and writing. The "write" command also returns the contents of the register |
| // categories written do. |
| std::vector<debug_ipc::Register> ReadRegisters( |
| const std::vector<debug_ipc::RegisterCategory>& cats_to_get) const; |
| std::vector<debug_ipc::Register> WriteRegisters(const std::vector<debug_ipc::Register>& regs); |
| |
| // Sends a notification to the client about the state of this thread. |
| void SendThreadNotification() const; |
| |
| // Notification that a ProcessBreakpoint is about to be deleted. |
| void WillDeleteProcessBreakpoint(ProcessBreakpoint* bp); |
| |
| ClientState client_state() const { return client_state_; } |
| void set_client_state(ClientState cs) { client_state_ = cs; } |
| |
| bool running() const { return !IsSuspended() && !IsInException(); } |
| |
| virtual bool IsSuspended() const { return ref_counted_suspend_token_.is_valid(); } |
| virtual bool IsInException() const { return !!exception_handle_; } |
| |
| int ref_counted_suspend_count() const { return suspend_count_; } |
| |
| bool stepping_over_breakpoint() const { return stepping_over_breakpoint_; } |
| void set_stepping_over_breakpoint(bool so) { stepping_over_breakpoint_ = so; } |
| |
| protected: |
| virtual void IncreaseSuspend(); |
| virtual void DecreaseSuspend(); |
| |
| zx_koid_t koid_; |
| std::unique_ptr<ThreadHandle> thread_handle_; |
| |
| private: |
| enum class OnStop { |
| kIgnore, // Don't do anything, keep the thread stopped and don't notify. |
| kNotify, // Send client notification like normal. |
| kResume, // The thread should be resumed from this exception. |
| }; |
| |
| // Some of these need to update the general registers in response to handling the exception. These |
| // ones take a non-const GeneralRegisters reference. |
| void HandleSingleStep(debug_ipc::NotifyException*, const GeneralRegisters& regs); |
| void HandleGeneralException(debug_ipc::NotifyException*, const GeneralRegisters& regs); |
| void HandleSoftwareBreakpoint(debug_ipc::NotifyException*, GeneralRegisters& regs); |
| void HandleHardwareBreakpoint(debug_ipc::NotifyException*, GeneralRegisters& regs); |
| void HandleWatchpoint(debug_ipc::NotifyException*, const GeneralRegisters& regs); |
| |
| void SendExceptionNotification(debug_ipc::NotifyException*, const GeneralRegisters& regs); |
| |
| // Updates the registers and the thread state for hitting the breakpoint, and fills in the |
| // given breakpoint array for all matches. |
| OnStop UpdateForSoftwareBreakpoint(GeneralRegisters& regs, |
| std::vector<debug_ipc::BreakpointStats>& hit_breakpoints); |
| |
| // When hitting a SW breakpoint, the PC needs to be correctly re-set depending on where the CPU |
| // leaves the PC after a SW exception. This updates both the given register record and syncs it |
| // to the actual thread. |
| void FixSoftwareBreakpointAddress(ProcessBreakpoint* process_breakpoint, GeneralRegisters& regs); |
| |
| // Handles an exception corresponding to a ProcessBreakpoint. All Breakpoints affected will have |
| // their updated stats added to *hit_breakpoints. |
| // |
| // WARNING: The ProcessBreakpoint argument could be deleted in this call if it was a one-shot |
| // breakpoint, so it must not be used after this call. |
| void UpdateForHitProcessBreakpoint(debug_ipc::BreakpointType exception_type, |
| ProcessBreakpoint* process_breakpoint, |
| std::vector<debug_ipc::BreakpointStats>& hit_breakpoints); |
| |
| // Sets or clears the single step bit on the thread. |
| void SetSingleStep(bool single_step); |
| |
| DebugAgent* debug_agent_; // Non-owning. |
| DebuggedProcess* process_; // Non-owning. |
| |
| // The main thing we're doing. When automatically resuming, this will be |
| // what happens. |
| debug_ipc::ResumeRequest::How run_mode_ = debug_ipc::ResumeRequest::How::kContinue; |
| |
| // When run_mode_ == kStepInRange, this defines the range (end non-inclusive). |
| uint64_t step_in_range_begin_ = 0; |
| uint64_t step_in_range_end_ = 0; |
| |
| // This is the state the client is considering this thread to be. This is used |
| // for internal suspension the agent can do. |
| ClientState client_state_ = ClientState::kRunning; |
| |
| int suspend_count_ = 0; |
| // This permits users to simply call Suspend/Resume without having to worry about having to |
| // track a suspend token. They could if they so wanted. |
| std::unique_ptr<SuspendToken> local_suspend_token_; |
| zx::suspend_token ref_counted_suspend_token_; |
| |
| // Active if the thread is currently on an exception. |
| std::unique_ptr<ThreadException> exception_handle_; |
| |
| // Whether this thread is currently stepping over. |
| bool stepping_over_breakpoint_ = false; |
| |
| // This can be set in two cases: |
| // - When suspended after hitting a breakpoint, this will be the breakpoint |
| // that was hit. |
| // - When single-stepping over a breakpoint, this will be the breakpoint |
| // being stepped over. |
| ProcessBreakpoint* current_breakpoint_ = nullptr; |
| |
| std::shared_ptr<arch::ArchProvider> arch_provider_ = nullptr; |
| std::shared_ptr<ObjectProvider> object_provider_ = nullptr; |
| |
| fxl::WeakPtrFactory<DebuggedThread> weak_factory_; |
| |
| FXL_DISALLOW_COPY_AND_ASSIGN(DebuggedThread); |
| |
| friend class ::debug_agent::DebuggedThread::SuspendToken; |
| }; |
| |
| } // namespace debug_agent |
| |
| #endif // SRC_DEVELOPER_DEBUG_DEBUG_AGENT_DEBUGGED_THREAD_H_ |