blob: e55c4e5a4cec37f8c4d55b1f1b94a25ae965c415 [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/ipc/protocol.h"
#include "src/lib/fxl/macros.h"
struct zx_thread_state_general_regs;
namespace debug_agent {
class DebugAgent;
class DebuggedProcess;
class ProcessBreakpoint;
class ProcessWatchpoint;
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 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);
// When a thread is first created and we get a notification about it, it
// will be suspended, but when we attach to a process with existing threads
// it won't in in this state. The |starting| flag indicates that this is
// a thread discovered via a debug notification.
DebuggedThread(DebuggedProcess* process, zx::thread thread,
zx_koid_t thread_koid, zx::exception exception,
ThreadCreationOption option);
virtual ~DebuggedThread();
const DebuggedProcess* process() const { return process_; }
zx::thread& thread() { return thread_; }
const zx::thread& thread() const { return thread_; }
zx_koid_t koid() const { return koid_; }
void OnException(zx::exception exception_token,
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);
// Resume the thread from an exception.
// If |exception_token_| is not valid, this will no-op.
void ResumeException();
// Resume the thread from a suspension.
// if |suspend_token_| is not valid, this will no-op.
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).
//
// A nullopt means an error ocurred while suspending.
bool Suspend(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.
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).
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.
void ReadRegisters(
const std::vector<debug_ipc::RegisterCategory::Type>& cats_to_get,
std::vector<debug_ipc::RegisterCategory>* out) const;
zx_status_t 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 !suspended() && !in_exception(); }
bool suspended() const { return suspend_token_.is_valid(); }
bool in_exception() const { return exception_token_.is_valid(); }
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);
OnStop UpdateForHardwareBreakpoint(
zx_thread_state_general_regs* regs,
std::vector<debug_ipc::BreakpointStats>* hit_breakpoints);
OnStop UpdateForWatchpoint(
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);
void FixAddressForWatchpointHit(ProcessWatchpoint* watchpoint,
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.
void UpdateForHitProcessBreakpoint(
debug_ipc::BreakpointType exception_type,
ProcessBreakpoint* process_breakpoint, zx_thread_state_general_regs* regs,
std::vector<debug_ipc::BreakpointStats>* hit_breakpoints);
// WARNING: The ProcessWatchpoint argument could be deleted in this call
// if it was a one-shot breakpoint.
void UpdateForWatchpointHit(
ProcessWatchpoint*, zx_thread_state_general_regs* regs,
std::vector<debug_ipc::BreakpointStats>* hit_breakpoints);
// Resumes the thread according to the current run mode.
void ResumeForRunMode();
// Sets or clears the single step bit on the thread.
void SetSingleStep(bool single_step);
DebugAgent* debug_agent_; // Non-owning.
DebuggedProcess* process_; // Non-owning.
zx::thread thread_;
zx_koid_t koid_;
// 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;
zx::suspend_token suspend_token_;
zx::exception exception_token_;
// 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;
FXL_DISALLOW_COPY_AND_ASSIGN(DebuggedThread);
};
} // namespace debug_agent
#endif // SRC_DEVELOPER_DEBUG_DEBUG_AGENT_DEBUGGED_THREAD_H_