blob: 386eb7cccc5ee51ae833d799d8f5ffeac74ee9ce [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.
#include "src/developer/debug/debug_agent/debugged_thread.h"
#include <inttypes.h>
#include <zircon/status.h>
#include <zircon/syscalls/debug.h>
#include <zircon/syscalls/exception.h>
#include <memory>
#include "src/developer/debug/debug_agent/arch.h"
#include "src/developer/debug/debug_agent/debug_agent.h"
#include "src/developer/debug/debug_agent/debugged_process.h"
#include "src/developer/debug/debug_agent/object_util.h"
#include "src/developer/debug/debug_agent/process_breakpoint.h"
#include "src/developer/debug/debug_agent/process_info.h"
#include "src/developer/debug/debug_agent/process_watchpoint.h"
#include "src/developer/debug/debug_agent/unwind.h"
#include "src/developer/debug/ipc/agent_protocol.h"
#include "src/developer/debug/ipc/message_reader.h"
#include "src/developer/debug/ipc/message_writer.h"
#include "src/developer/debug/shared/logging/logging.h"
#include "src/developer/debug/shared/message_loop_target.h"
#include "src/developer/debug/shared/stream_buffer.h"
#include "src/developer/debug/shared/zx_status.h"
#include "src/lib/fxl/logging.h"
#include "src/lib/fxl/strings/string_printf.h"
namespace debug_agent {
namespace {
bool IsBlockedOnException(const zx::thread& thread) {
zx_info_thread info;
return thread.get_info(ZX_INFO_THREAD, &info, sizeof(info), nullptr,
nullptr) == ZX_OK &&
info.state == ZX_THREAD_STATE_BLOCKED_EXCEPTION;
}
// Used to have better context upon reading the debug logs.
std::string ThreadPreamble(const DebuggedThread* thread) {
return fxl::StringPrintf("[Pr: %lu (%s), T: %lu] ", thread->process()->koid(),
thread->process()->name().c_str(), thread->koid());
}
// TODO(donosoc): Move this to a more generic place (probably shared) where it
// can be used by other code.
const char* ExceptionTypeToString(uint32_t type) {
switch (type) {
case ZX_EXCP_GENERAL:
return "ZX_EXCP_GENERAL";
case ZX_EXCP_FATAL_PAGE_FAULT:
return "ZX_EXCP_FATAL_PAGE_FAULT";
case ZX_EXCP_UNDEFINED_INSTRUCTION:
return "ZX_EXCP_UNDEFINED_INSTRUCTION";
case ZX_EXCP_SW_BREAKPOINT:
return "ZX_EXCP_SW_BREAKPOINT";
case ZX_EXCP_HW_BREAKPOINT:
return "ZX_EXCP_HW_BREAKPOINT";
case ZX_EXCP_UNALIGNED_ACCESS:
return "ZX_EXCP_UNALIGNED_ACCESS";
default:
break;
}
return "<unknown>";
}
} // namespace
DebuggedThread::DebuggedThread(DebuggedProcess* process, zx::thread thread,
zx_koid_t koid, zx::exception exception,
ThreadCreationOption option)
: debug_agent_(process->debug_agent()),
process_(process),
thread_(std::move(thread)),
koid_(koid),
exception_token_(std::move(exception)) {
switch (option) {
case ThreadCreationOption::kRunningKeepRunning:
// do nothing
break;
case ThreadCreationOption::kSuspendedKeepSuspended:
break;
case ThreadCreationOption::kSuspendedShouldRun:
ResumeException();
break;
}
}
DebuggedThread::~DebuggedThread() = default;
void DebuggedThread::OnException(zx::exception exception_token,
zx_exception_info_t exception_info) {
exception_token_ = std::move(exception_token);
debug_ipc::NotifyException exception;
exception.type =
arch::ArchProvider::Get().DecodeExceptionType(*this, exception_info.type);
DEBUG_LOG(Thread) << ThreadPreamble(this) << "Exception: "
<< ExceptionTypeToString(exception_info.type) << " -> "
<< debug_ipc::NotifyException::TypeToString(exception.type);
zx_thread_state_general_regs regs;
thread_.read_state(ZX_THREAD_STATE_GENERAL_REGS, &regs, sizeof(regs));
switch (exception.type) {
case debug_ipc::NotifyException::Type::kSingleStep:
return HandleSingleStep(&exception, &regs);
case debug_ipc::NotifyException::Type::kSoftware:
return HandleSoftwareBreakpoint(&exception, &regs);
case debug_ipc::NotifyException::Type::kHardware:
return HandleHardwareBreakpoint(&exception, &regs);
case debug_ipc::NotifyException::Type::kGeneral:
// TODO(donosoc): Should synthetic be general or invalid?
case debug_ipc::NotifyException::Type::kSynthetic:
return HandleGeneralException(&exception, &regs);
case debug_ipc::NotifyException::Type::kWatchpoint:
return HandleWatchpoint(&exception, &regs);
case debug_ipc::NotifyException::Type::kNone:
case debug_ipc::NotifyException::Type::kLast:
break;
}
FXL_NOTREACHED() << "Invalid exception notification type: "
<< static_cast<uint32_t>(exception.type);
// The exception was unhandled, so we close it so that the system can run its
// course. The destructor would've done it anyway, but being explicit helps
// readability.
exception_token_.reset();
}
void DebuggedThread::HandleSingleStep(debug_ipc::NotifyException* exception,
zx_thread_state_general_regs* regs) {
if (current_breakpoint_) {
// The current breakpoint is set only when stopped at a breakpoint or when
// single-stepping over one. We're not going to get an exception for a
// thread when stopped, so hitting this exception means the breakpoint is
// done being stepped over. The breakpoint will tell us if the exception
// was from a normal completion of the breakpoint step, or whether
// something else went wrong while stepping.
bool completes_bp_step =
current_breakpoint_->EndStepOver(koid_, exception->type);
current_breakpoint_ = nullptr;
if (completes_bp_step &&
run_mode_ == debug_ipc::ResumeRequest::How::kContinue) {
// This step was an internal thing to step over the breakpoint in
// service of continuing from a breakpoint. Transparently resume the
// thread since the client didn't request the step. The step
// (non-continue) cases will be handled below in the normal flow since
// we just finished a step.
ResumeForRunMode();
return;
}
// Something else went wrong while stepping (the instruction with the
// breakpoint could have crashed). Fall through to dispatching the
// exception to the client.
current_breakpoint_ = nullptr;
}
if (run_mode_ == debug_ipc::ResumeRequest::How::kContinue) {
// This could be due to a race where the user was previously single
// stepping and then requested a continue before the single stepping
// completed. It could also be a breakpoint that was deleted while
// in the process of single-stepping over it. In both cases, the
// least confusing thing is to resume automatically.
ResumeForRunMode();
return;
}
// When stepping in a range, automatically continue as long as we're
// still in range.
if (run_mode_ == debug_ipc::ResumeRequest::How::kStepInRange &&
*arch::ArchProvider::Get().IPInRegs(regs) >= step_in_range_begin_ &&
*arch::ArchProvider::Get().IPInRegs(regs) < step_in_range_end_) {
ResumeForRunMode();
return;
}
SendExceptionNotification(exception, regs);
}
void DebuggedThread::HandleGeneralException(
debug_ipc::NotifyException* exception, zx_thread_state_general_regs* regs) {
SendExceptionNotification(exception, regs);
}
void DebuggedThread::HandleSoftwareBreakpoint(
debug_ipc::NotifyException* exception, zx_thread_state_general_regs* regs) {
DEBUG_LOG(Thread) << ThreadPreamble(this) << "Hit SW breakpoint";
auto on_stop = UpdateForSoftwareBreakpoint(regs, &exception->hit_breakpoints);
switch (on_stop) {
case OnStop::kIgnore:
return;
case OnStop::kNotify:
SendExceptionNotification(exception, regs);
return;
case OnStop::kResume: {
// We mark the thread as within an exception
ResumeForRunMode();
return;
}
}
FXL_NOTREACHED() << "Invalid OnStop.";
}
void DebuggedThread::HandleHardwareBreakpoint(
debug_ipc::NotifyException* exception, zx_thread_state_general_regs* regs) {
if (UpdateForHardwareBreakpoint(regs, &exception->hit_breakpoints) ==
OnStop::kIgnore)
return;
SendExceptionNotification(exception, regs);
}
void DebuggedThread::HandleWatchpoint(debug_ipc::NotifyException* exception,
zx_thread_state_general_regs* regs) {
if (UpdateForWatchpoint(regs, &exception->hit_breakpoints) == OnStop::kIgnore)
return;
SendExceptionNotification(exception, regs);
}
void DebuggedThread::SendExceptionNotification(
debug_ipc::NotifyException* exception, zx_thread_state_general_regs* regs) {
FillThreadRecord(debug_ipc::ThreadRecord::StackAmount::kMinimal, regs,
&exception->thread);
// Keep the thread suspended for the client.
// TODO(brettw) suspend other threads in the process and other debugged
// processes as desired.
// Send notification.
debug_ipc::MessageWriter writer;
debug_ipc::WriteNotifyException(*exception, &writer);
debug_agent_->stream()->Write(writer.MessageComplete());
}
void DebuggedThread::Resume(const debug_ipc::ResumeRequest& request) {
DEBUG_LOG(Thread) << ThreadPreamble(this) << "Resuming.";
run_mode_ = request.how;
step_in_range_begin_ = request.range_begin;
step_in_range_end_ = request.range_end;
ResumeForRunMode();
}
void DebuggedThread::ResumeException() {
// We need to mark that this token is correctly handled before closing it.
if (exception_token_.is_valid()) {
DEBUG_LOG(Thread) << "Resuming from exception.";
uint32_t state = ZX_EXCEPTION_STATE_HANDLED;
zx_status_t status = exception_token_.set_property(ZX_PROP_EXCEPTION_STATE,
&state, sizeof(state));
FXL_DCHECK(status == ZX_OK) << "Got: " << zx_status_get_string(status);
}
exception_token_.reset();
}
void DebuggedThread::ResumeSuspension() {
suspend_token_.reset();
}
bool DebuggedThread::Suspend(bool synchronous) {
// Subsequent suspend calls should return immediatelly. Note that this does
// not mean that the thread is in that state, but rather that that operation
// was sent to the kernel.
if (suspended() || in_exception())
return false;
DEBUG_LOG(Thread) << ThreadPreamble(this) << "Suspending thread.";
zx_status_t status;
status = thread_.suspend(&suspend_token_);
if (status != ZX_OK) {
FXL_LOG(WARNING) << ThreadPreamble(this)
<< "Could not suspend: " << zx_status_get_string(status);
return false;
}
if (synchronous)
return WaitForSuspension(DefaultSuspendDeadline());
return true;
}
zx::time DebuggedThread::DefaultSuspendDeadline() {
// Various events and environments can cause suspensions to take a long time,
// so this needs to be a relatively long time. We don't generally expect
// error cases that take infinitely long so there isn't much downside of a
// long timeout.
return zx::deadline_after(zx::sec(1));
}
bool DebuggedThread::WaitForSuspension(zx::time deadline) {
// This function is complex because a thread in an exception state can't be
// suspended (ZX-3772). Delivery of exceptions are queued on the
// exception port so our cached state may be stale, and exceptions can also
// race with our suspend call.
//
// To manually stress-test this code, write a one-line infinite loop:
// volatile bool done = false;
// while (!done) {}
// and step over it with "next". This will cause an infinite flood of
// single-step exceptions as fast as the debugger can process them. Pausing
// after doing the "next" will trigger a suspension and is more likely to
// race with an exception.
// If an exception happens before the suspend does, we'll never get the
// suspend signal and will end up waiting for the entire timeout just to be
// able to tell the difference between suspended and exception. To avoid
// waiting for a long timeout to tell the difference, wait for short timeouts
// multiple times.
auto poll_time = zx::msec(10);
zx_status_t status = ZX_OK;
do {
// Always check the thread state from the kernel because of queue desribed
// above.
if (IsBlockedOnException(thread_))
return true;
zx_signals_t observed;
status = thread_.wait_one(ZX_THREAD_SUSPENDED,
zx::deadline_after(poll_time), &observed);
if (status == ZX_OK && (observed & ZX_THREAD_SUSPENDED))
return true;
} while (status == ZX_ERR_TIMED_OUT && zx::clock::get_monotonic() < deadline);
return false;
}
void DebuggedThread::FillThreadRecord(
debug_ipc::ThreadRecord::StackAmount stack_amount,
const zx_thread_state_general_regs* optional_regs,
debug_ipc::ThreadRecord* record) const {
record->process_koid = process_->koid();
record->thread_koid = koid();
record->name = NameForObject(thread_);
// State (running, blocked, etc.).
zx_info_thread info;
if (thread_.get_info(ZX_INFO_THREAD, &info, sizeof(info), nullptr, nullptr) ==
ZX_OK) {
record->state = ThreadStateToEnums(info.state, &record->blocked_reason);
} else {
FXL_NOTREACHED();
record->state = debug_ipc::ThreadRecord::State::kDead;
}
// The registers are available when suspended or blocked in an exception.
if ((info.state == ZX_THREAD_STATE_SUSPENDED ||
info.state == ZX_THREAD_STATE_BLOCKED_EXCEPTION) &&
stack_amount != debug_ipc::ThreadRecord::StackAmount::kNone) {
// Only record this when we actually attempt to query the stack.
record->stack_amount = stack_amount;
// The registers are required, fetch them if the caller didn't provide.
zx_thread_state_general_regs queried_regs; // Storage for fetched regs.
zx_thread_state_general_regs* regs = nullptr;
if (!optional_regs) {
if (thread_.read_state(ZX_THREAD_STATE_GENERAL_REGS, &queried_regs,
sizeof(queried_regs)) == ZX_OK)
regs = &queried_regs;
} else {
// We don't change the values here but *InRegs below returns mutable
// references so we need a mutable pointer.
regs = const_cast<zx_thread_state_general_regs*>(optional_regs);
}
if (regs) {
// Minimal stacks are 2 (current frame and calling one). Full stacks max
// out at 256 to prevent edge cases, especially around corrupted stacks.
uint32_t max_stack_depth =
stack_amount == debug_ipc::ThreadRecord::StackAmount::kMinimal ? 2
: 256;
UnwindStack(process_->process(), process_->dl_debug_addr(), thread_,
*regs, max_stack_depth, &record->frames);
}
} else {
// Didn't bother querying the stack.
record->stack_amount = debug_ipc::ThreadRecord::StackAmount::kNone;
record->frames.clear();
}
}
void DebuggedThread::ReadRegisters(
const std::vector<debug_ipc::RegisterCategory::Type>& cats_to_get,
std::vector<debug_ipc::RegisterCategory>* out) const {
out->clear();
for (const auto& cat_type : cats_to_get) {
auto& cat = out->emplace_back();
cat.type = cat_type;
zx_status_t status = arch::ArchProvider::Get().ReadRegisters(
cat_type, thread_, &cat.registers);
if (status != ZX_OK) {
out->pop_back();
FXL_LOG(ERROR) << "Could not get register state for category: "
<< debug_ipc::RegisterCategory::TypeToString(cat_type);
}
}
}
zx_status_t DebuggedThread::WriteRegisters(
const std::vector<debug_ipc::Register>& regs) {
// We use a map to keep track of which categories will change.
std::map<debug_ipc::RegisterCategory::Type, debug_ipc::RegisterCategory>
categories;
bool rip_change = false;
debug_ipc::RegisterID rip_id = GetSpecialRegisterID(
arch::ArchProvider::Get().GetArch(), debug_ipc::SpecialRegisterType::kIP);
// We append each register to the correct category to be changed.
for (const debug_ipc::Register& reg : regs) {
auto cat_type = debug_ipc::RegisterCategory::RegisterIDToCategory(reg.id);
if (cat_type == debug_ipc::RegisterCategory::Type::kNone) {
FXL_LOG(WARNING) << "Attempting to change register without category: "
<< RegisterIDToString(reg.id);
continue;
}
// We are changing the RIP, meaning that we're not going to jump over a
// breakpoint.
if (reg.id == rip_id)
rip_change = true;
auto& category = categories[cat_type];
category.type = cat_type;
category.registers.push_back(reg);
}
for (const auto& [cat_type, cat] : categories) {
FXL_DCHECK(cat_type != debug_ipc::RegisterCategory::Type::kNone);
zx_status_t res = arch::ArchProvider::Get().WriteRegisters(cat, &thread_);
if (res != ZX_OK) {
FXL_LOG(WARNING) << "Could not write category "
<< debug_ipc::RegisterCategory::TypeToString(cat_type)
<< ": " << debug_ipc::ZxStatusToString(res);
}
}
// If the debug agent wrote to the thread IP directly, then current state is
// no longer valid. Specifically, if we're currently on a breakpoint, we have
// to now know the fact that we're no longer in a breakpoint.
//
// This is necessary to avoid the single-stepping logic that the thread does
// when resuming from a breakpoint.
current_breakpoint_ = nullptr;
return ZX_OK;
}
void DebuggedThread::SendThreadNotification() const {
debug_ipc::NotifyThread notify;
FillThreadRecord(debug_ipc::ThreadRecord::StackAmount::kMinimal, nullptr,
&notify.record);
debug_ipc::MessageWriter writer;
debug_ipc::WriteNotifyThread(
debug_ipc::MsgHeader::Type::kNotifyThreadStarting, notify, &writer);
debug_agent_->stream()->Write(writer.MessageComplete());
}
void DebuggedThread::WillDeleteProcessBreakpoint(ProcessBreakpoint* bp) {
if (current_breakpoint_ == bp)
current_breakpoint_ = nullptr;
}
DebuggedThread::OnStop DebuggedThread::UpdateForSoftwareBreakpoint(
zx_thread_state_general_regs* regs,
std::vector<debug_ipc::BreakpointStats>* hit_breakpoints) {
// Get the correct address where the CPU is after hitting a breakpoint
// (this is architecture specific).
uint64_t breakpoint_address =
arch::ArchProvider::Get()
.BreakpointInstructionForSoftwareExceptionAddress(
*arch::ArchProvider::Get().IPInRegs(regs));
ProcessBreakpoint* found_bp =
process_->FindProcessBreakpointForAddr(breakpoint_address);
if (found_bp) {
FixSoftwareBreakpointAddress(found_bp, regs);
// When hitting a breakpoint, we need to check if indeed this exception
// should apply to this thread or not.
if (!found_bp->ShouldHitThread(koid())) {
DEBUG_LOG(Thread) << ThreadPreamble(this)
<< "SW Breakpoint not for me. Ignoring.";
// The way to go over is to step over the breakpoint as one would over
// a resume.
current_breakpoint_ = found_bp;
return OnStop::kResume;
}
UpdateForHitProcessBreakpoint(debug_ipc::BreakpointType::kSoftware,
found_bp, regs, hit_breakpoints);
// The found_bp could have been deleted if it was a one-shot, so must
// not be dereferenced below this.
found_bp = nullptr;
} else {
// Hit a software breakpoint that doesn't correspond to any current
// breakpoint.
if (arch::ArchProvider::Get().IsBreakpointInstruction(process_->process(),
breakpoint_address)) {
// The breakpoint is a hardcoded instruction in the program code. In
// this case we want to continue from the following instruction since
// the breakpoint instruction will never go away.
*arch::ArchProvider::Get().IPInRegs(regs) =
arch::ArchProvider::Get().NextInstructionForSoftwareExceptionAddress(
*arch::ArchProvider::Get().IPInRegs(regs));
zx_status_t status =
thread_.write_state(ZX_THREAD_STATE_GENERAL_REGS, regs,
sizeof(zx_thread_state_general_regs));
if (status != ZX_OK) {
fprintf(stderr, "Warning: could not update IP on thread, error = %d.",
static_cast<int>(status));
}
if (!process_->dl_debug_addr() && process_->RegisterDebugState()) {
DEBUG_LOG(Thread) << ThreadPreamble(this)
<< "Found ld.so breakpoint. Sending modules.";
// This breakpoint was the explicit breakpoint ld.so executes to
// notify us that the loader is ready. Send the current module list
// and silently keep this thread stopped. The client will explicitly
// resume this thread when it's ready to continue (it will need to
// load symbols for the modules and may need to set breakpoints based
// on them).
std::vector<uint64_t> paused_threads;
paused_threads.push_back(koid());
process_->SendModuleNotification(std::move(paused_threads));
return OnStop::kIgnore;
}
} else {
// Not a breakpoint instruction. Probably the breakpoint instruction
// used to be ours but its removal raced with the exception handler.
// Resume from the instruction that used to be the breakpoint.
*arch::ArchProvider::Get().IPInRegs(regs) = breakpoint_address;
// Don't automatically continue execution here. A race for this should
// be unusual and maybe something weird happened that caused an
// exception we're not set up to handle. Err on the side of telling the
// user about the exception.
}
}
return OnStop::kNotify;
}
DebuggedThread::OnStop DebuggedThread::UpdateForHardwareBreakpoint(
zx_thread_state_general_regs* regs,
std::vector<debug_ipc::BreakpointStats>* hit_breakpoints) {
uint64_t breakpoint_address =
arch::ArchProvider::Get()
.BreakpointInstructionForHardwareExceptionAddress(
*arch::ArchProvider::Get().IPInRegs(regs));
ProcessBreakpoint* found_bp =
process_->FindProcessBreakpointForAddr(breakpoint_address);
if (!found_bp) {
// Hit a hw debug exception that doesn't belong to any ProcessBreakpoint.
// This is probably a race between the removal and the exception handler.
// Send a notification.
*arch::ArchProvider::Get().IPInRegs(regs) = breakpoint_address;
return OnStop::kNotify;
}
FixSoftwareBreakpointAddress(found_bp, regs);
UpdateForHitProcessBreakpoint(debug_ipc::BreakpointType::kHardware, found_bp,
regs, hit_breakpoints);
// The ProcessBreakpoint could've been deleted if it was a one-shot, so must
// not be derefereced below this.
found_bp = nullptr;
return OnStop::kNotify;
}
DebuggedThread::OnStop DebuggedThread::UpdateForWatchpoint(
zx_thread_state_general_regs* regs,
std::vector<debug_ipc::BreakpointStats>* hit_breakpoints) {
auto& arch = arch::ArchProvider::Get();
uint64_t address = arch.InstructionForWatchpointHit(*this);
ProcessWatchpoint* wp = process_->FindWatchpointByAddress(address);
if (!wp) {
// Hit a hw debug exception that doesn't belong to any ProcessBreakpoint.
// This is probably a race between the removal and the exception handler.
// Send a notification.
*arch::ArchProvider::Get().IPInRegs(regs) = address;
return OnStop::kNotify;
}
FixAddressForWatchpointHit(wp, regs);
UpdateForWatchpointHit(wp, regs, hit_breakpoints);
// If the watchpoint was one-shot, it would've been deleted, so we should not
// rely on it being there.
wp = nullptr;
return OnStop::kNotify;
}
void DebuggedThread::FixSoftwareBreakpointAddress(
ProcessBreakpoint* process_breakpoint, zx_thread_state_general_regs* regs) {
// When the program hits one of our breakpoints, set the IP back to
// the exact address that triggered the breakpoint. When the thread
// resumes, this is the address that it will resume from (after
// putting back the original instruction), and will be what the client
// wants to display to the user.
*arch::ArchProvider::Get().IPInRegs(regs) = process_breakpoint->address();
zx_status_t status = thread_.write_state(
ZX_THREAD_STATE_GENERAL_REGS, regs, sizeof(zx_thread_state_general_regs));
if (status != ZX_OK) {
fprintf(stderr, "Warning: could not update IP on thread, error = %d.",
static_cast<int>(status));
}
}
void DebuggedThread::FixAddressForWatchpointHit(
ProcessWatchpoint* watchpoint, zx_thread_state_general_regs* regs) {
auto& arch_provider = arch::ArchProvider::Get();
*arch_provider.IPInRegs(regs) = arch_provider.NextInstructionForWatchpointHit(
*arch_provider.IPInRegs(regs));
}
void DebuggedThread::UpdateForHitProcessBreakpoint(
debug_ipc::BreakpointType exception_type,
ProcessBreakpoint* process_breakpoint, zx_thread_state_general_regs* regs,
std::vector<debug_ipc::BreakpointStats>* hit_breakpoints) {
current_breakpoint_ = process_breakpoint;
process_breakpoint->OnHit(exception_type, hit_breakpoints);
// Delete any one-shot breakpoints. Since there can be multiple Breakpoints
// (some one-shot, some not) referring to the current ProcessBreakpoint,
// this operation could delete the ProcessBreakpoint or it could not. If it
// does, our observer will be told and current_breakpoint_ will be cleared.
for (const auto& stats : *hit_breakpoints) {
if (stats.should_delete)
process_->debug_agent()->RemoveBreakpoint(stats.id);
}
}
void DebuggedThread::UpdateForWatchpointHit(
ProcessWatchpoint* watchpoint, zx_thread_state_general_regs* regs,
std::vector<debug_ipc::BreakpointStats>* hit_breakpoints) {
auto break_stat = watchpoint->OnHit();
// Delete any one-shot watchpoints. Since there can be multiple Watchpoints
// (some one-shot, some not) referring to the current ProcessBreakpoint,
// this operation could delete the ProcessBreakpoint or it could not. If it
// does, our observer will be told and current_breakpoint_ will be cleared.
if (break_stat.should_delete)
process_->debug_agent()->RemoveWatchpoint(break_stat.id);
*hit_breakpoints = {};
hit_breakpoints->push_back(std::move(break_stat));
}
void DebuggedThread::ResumeForRunMode() {
// If we jumped, once we resume we reset the status.
if (in_exception()) {
// Note: we could have a valid suspend token here in addition to the
// exception if the suspension races with the delivery of the exception.
if (current_breakpoint_) {
// Going over a breakpoint always requires a single-step first. Then we
// continue according to run_mode_.
DEBUG_LOG(Thread) << ThreadPreamble(this) << "Stepping over thread.";
SetSingleStep(true);
current_breakpoint_->BeginStepOver(koid_);
} else {
// All non-continue resumptions require single stepping.
SetSingleStep(run_mode_ != debug_ipc::ResumeRequest::How::kContinue);
}
ResumeException();
} else if (suspended()) {
// A breakpoint should only be current when it was hit which will be
// caused by an exception.
FXL_DCHECK(!current_breakpoint_);
// All non-continue resumptions require single stepping.
SetSingleStep(run_mode_ != debug_ipc::ResumeRequest::How::kContinue);
// The suspend token is holding the thread suspended, releasing it will
// resume (if nobody else has the thread suspended).
ResumeSuspension();
}
}
void DebuggedThread::SetSingleStep(bool single_step) {
zx_thread_state_single_step_t value = single_step ? 1 : 0;
// This could fail for legitimate reasons, like the process could have just
// closed the thread.
thread_.write_state(ZX_THREAD_STATE_SINGLE_STEP, &value, sizeof(value));
}
const char* DebuggedThread::ClientStateToString(ClientState client_state) {
switch (client_state) {
case ClientState::kRunning:
return "Running";
case ClientState::kPaused:
return "Paused";
}
FXL_NOTREACHED();
return "<unknown>";
}
} // namespace debug_agent