blob: df5f15b369786f6fe4a1c0ccd9e2d21cb1b1b03b [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/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_