blob: f5df8988d5b918dd65a6b6040e9a3af14ae20206 [file] [log] [blame]
// 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/object_provider.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"
struct zx_thread_state_general_regs;
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;
zx::thread handle;
ThreadCreationOption creation_option = ThreadCreationOption::kRunningKeepRunning;
zx::exception 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_; }
zx::thread& handle() { return handle_; }
const zx::thread& handle() const { return handle_; }
zx::exception& exception_handle() { return exception_handle_; }
const zx::exception& exception_handle() const { return exception_handle_; }
void set_exception_handle(zx::exception exception) { exception_handle_ = std::move(exception); }
fxl::WeakPtr<DebuggedThread> GetWeakPtr();
void OnException(zx::exception 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 optional_regs is non-null, it should point to 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).
virtual void FillThreadRecord(debug_ipc::ThreadRecord::StackAmount stack_amount,
const zx_thread_state_general_regs* optional_regs,
debug_ipc::ThreadRecord* record) const;
// Register reading and writing. The "write" command also has an output parameter which includes a
// validated version of all registers re-read (and possibly others if they're known) from the
// kernel after the write.
void ReadRegisters(const std::vector<debug_ipc::RegisterCategory>& cats_to_get,
std::vector<debug_ipc::Register>* out) const;
zx_status_t WriteRegisters(const std::vector<debug_ipc::Register>& regs,
std::vector<debug_ipc::Register>* written);
// 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_.is_valid(); }
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_;
zx::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.
};
void HandleSingleStep(debug_ipc::NotifyException*, zx_thread_state_general_regs*);
void HandleGeneralException(debug_ipc::NotifyException*, zx_thread_state_general_regs*);
void HandleSoftwareBreakpoint(debug_ipc::NotifyException*, zx_thread_state_general_regs*);
void HandleHardwareBreakpoint(debug_ipc::NotifyException*, zx_thread_state_general_regs*);
void HandleWatchpoint(debug_ipc::NotifyException*, zx_thread_state_general_regs*);
void SendExceptionNotification(debug_ipc::NotifyException*, zx_thread_state_general_regs*);
OnStop UpdateForSoftwareBreakpoint(zx_thread_state_general_regs* 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.
void FixSoftwareBreakpointAddress(ProcessBreakpoint* process_breakpoint,
zx_thread_state_general_regs* 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,
zx_thread_state_general_regs* regs,
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.
zx::exception 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_