blob: 6655a703a5519f59b226ada12531fa86567a81a6 [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 <lib/syslog/cpp/macros.h>
#include <lib/zx/clock.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/hardware_breakpoint.h"
#include "src/developer/debug/debug_agent/object_provider.h"
#include "src/developer/debug/debug_agent/process_breakpoint.h"
#include "src/developer/debug/debug_agent/software_breakpoint.h"
#include "src/developer/debug/debug_agent/unwind.h"
#include "src/developer/debug/debug_agent/watchpoint.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/platform_message_loop.h"
#include "src/developer/debug/shared/stream_buffer.h"
#include "src/developer/debug/shared/zx_status.h"
#include "src/lib/fxl/strings/string_printf.h"
namespace debug_agent {
namespace {
// 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>";
}
void LogHitBreakpoint(debug_ipc::FileLineFunction location, const DebuggedThread* thread,
ProcessBreakpoint* process_breakpoint, uint64_t address) {
if (!debug_ipc::IsDebugModeActive())
return;
std::stringstream ss;
ss << ThreadPreamble(thread) << "Hit SW breakpoint on 0x" << std::hex << address << " for: ";
for (Breakpoint* breakpoint : process_breakpoint->breakpoints()) {
ss << breakpoint->settings().name << ", ";
}
DEBUG_LOG_WITH_LOCATION(Thread, location) << ss.str();
}
void LogExceptionNotification(debug_ipc::FileLineFunction location, const DebuggedThread* thread,
const debug_ipc::NotifyException& exception) {
if (!debug_ipc::IsDebugModeActive())
return;
std::stringstream ss;
ss << ThreadPreamble(thread) << "Notifying exception "
<< debug_ipc::ExceptionTypeToString(exception.type) << ". ";
ss << "Breakpoints hit: ";
int count = 0;
for (auto& bp : exception.hit_breakpoints) {
if (count > 0)
ss << ", ";
ss << bp.id;
if (bp.should_delete)
ss << " (delete)";
}
DEBUG_LOG_WITH_LOCATION(Thread, location) << ss.str();
}
} // namespace
// DebuggedThread::SuspendToken --------------------------------------------------------------------
DebuggedThread::SuspendToken::SuspendToken(DebuggedThread* thread) : thread_(thread->GetWeakPtr()) {
thread->IncreaseSuspend();
}
DebuggedThread::SuspendToken::~SuspendToken() {
if (!thread_)
return;
thread_->DecreaseSuspend();
}
// DebuggedThread ----------------------------------------------------------------------------------
DebuggedThread::DebuggedThread(DebugAgent* debug_agent, CreateInfo&& create_info)
: koid_(create_info.koid),
thread_handle_(std::move(create_info.handle)),
debug_agent_(debug_agent),
process_(create_info.process),
exception_handle_(std::move(create_info.exception)),
arch_provider_(std::move(create_info.arch_provider)),
object_provider_(std::move(create_info.object_provider)),
weak_factory_(this) {
FX_DCHECK(object_provider_);
FX_DCHECK(arch_provider_);
switch (create_info.creation_option) {
case ThreadCreationOption::kRunningKeepRunning:
// do nothing
break;
case ThreadCreationOption::kSuspendedKeepSuspended:
break;
case ThreadCreationOption::kSuspendedShouldRun:
ResumeException();
break;
}
}
DebuggedThread::~DebuggedThread() = default;
fxl::WeakPtr<DebuggedThread> DebuggedThread::GetWeakPtr() { return weak_factory_.GetWeakPtr(); }
void DebuggedThread::OnException(std::unique_ptr<ThreadException> exception_handle,
zx_exception_info_t exception_info) {
exception_handle_ = std::move(exception_handle);
debug_ipc::NotifyException exception;
exception.type = arch_provider_->DecodeExceptionType(*this, exception_info.type);
arch_provider_->FillExceptionRecord(thread_handle_->GetNativeHandle(), &exception.exception);
std::optional<GeneralRegisters> regs = thread_handle_->GetGeneralRegisters();
if (!regs) {
// This can happen, for example, if the thread was killed during the time the exception message
// was waiting to be delivered to us.
FX_LOGS(WARNING) << "Could not read registers from thread.";
return;
}
DEBUG_LOG(Thread) << ThreadPreamble(this) << "Exception @ 0x" << std::hex << regs->ip()
<< std::dec << ": " << ExceptionTypeToString(exception_info.type) << " -> "
<< debug_ipc::ExceptionTypeToString(exception.type);
switch (exception.type) {
case debug_ipc::ExceptionType::kSingleStep:
return HandleSingleStep(&exception, *regs);
case debug_ipc::ExceptionType::kSoftware:
return HandleSoftwareBreakpoint(&exception, *regs);
case debug_ipc::ExceptionType::kHardware:
return HandleHardwareBreakpoint(&exception, *regs);
case debug_ipc::ExceptionType::kWatchpoint:
return HandleWatchpoint(&exception, *regs);
case debug_ipc::ExceptionType::kNone:
case debug_ipc::ExceptionType::kLast:
break;
// TODO(donosoc): Should synthetic be general or invalid?
case debug_ipc::ExceptionType::kSynthetic:
default:
return HandleGeneralException(&exception, *regs);
}
FX_NOTREACHED() << "Invalid exception notification type: "
<< debug_ipc::ExceptionTypeToString(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_handle_ = nullptr;
}
void DebuggedThread::HandleSingleStep(debug_ipc::NotifyException* exception,
const GeneralRegisters& regs) {
if (current_breakpoint_) {
DEBUG_LOG(Thread) << ThreadPreamble(this) << "Ending single stepped over 0x" << std::hex
<< current_breakpoint_->address();
// Getting here means that the thread is done stepping over a breakpoint.
// Depending on whether others threads are stepping over the breakpoints, this thread might be
// suspended (waiting for other threads to step over).
// This means that we cannot resume from suspension here, as the breakpoint is owning the
// thread "run-lifetime".
//
// We can, though, resume from the exception, as effectively we already handled the single-step
// exception, so there is no more need to keep the thread in an excepted state. The suspend
// handle will take care of keeping the thread stopped.
//
// NOTE: It's important to resume the exception *after* telling the breakpoint we are done going
// over it. This is because in the case that there are no other threads queued (the normal
// case), it produces a window between resuming the exception and suspending the thread
// to reinstall the breakpointer, which could make the thread miss the exception. By
// keeping the exception until *after* the breakpoint has been told to step over, we
// ensure that any installs have already occured and thus the thread won't miss any
// breakpoints.
SetSingleStep(run_mode_ != debug_ipc::ResumeRequest::How::kContinue);
current_breakpoint_->EndStepOver(this);
current_breakpoint_ = nullptr;
ResumeException();
return;
}
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.
DEBUG_LOG(Thread) << ThreadPreamble(this) << "Single step without breakpoint. Continuing.";
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 &&
regs.ip() >= step_in_range_begin_ && regs.ip() < step_in_range_end_) {
DEBUG_LOG(Thread) << ThreadPreamble(this) << "Stepping in range. Continuing.";
ResumeForRunMode();
return;
}
DEBUG_LOG(Thread) << ThreadPreamble(this) << "Expected single step. Notifying.";
SendExceptionNotification(exception, regs);
}
void DebuggedThread::HandleGeneralException(debug_ipc::NotifyException* exception,
const GeneralRegisters& regs) {
SendExceptionNotification(exception, regs);
}
void DebuggedThread::HandleSoftwareBreakpoint(debug_ipc::NotifyException* exception,
GeneralRegisters& regs) {
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;
}
}
FX_NOTREACHED() << "Invalid OnStop.";
}
void DebuggedThread::HandleHardwareBreakpoint(debug_ipc::NotifyException* exception,
GeneralRegisters& regs) {
uint64_t breakpoint_address =
arch_provider_->BreakpointInstructionForHardwareExceptionAddress(regs.ip());
HardwareBreakpoint* found_bp = process_->FindHardwareBreakpoint(breakpoint_address);
if (found_bp) {
UpdateForHitProcessBreakpoint(debug_ipc::BreakpointType::kHardware, found_bp,
exception->hit_breakpoints);
} else {
// Hit a hw debug exception that doesn't belong to any ProcessBreakpoint. This is probably a
// race between the removal and the exception handler.
regs.set_ip(breakpoint_address);
}
// The ProcessBreakpoint could've been deleted if it was a one-shot, so must not be derefereced
// below this.
found_bp = nullptr;
SendExceptionNotification(exception, regs);
}
void DebuggedThread::HandleWatchpoint(debug_ipc::NotifyException* exception,
const GeneralRegisters& regs) {
auto [range, slot] = arch_provider_->InstructionForWatchpointHit(*this);
DEBUG_LOG(Thread) << "Found watchpoint hit at 0x" << std::hex << range.ToString() << " on slot "
<< std::dec << slot;
// If no process matches this watchpoint, we send the exception notification and let the client
// handle the exception.
if (slot == -1) {
DEBUG_LOG(Thread) << "Could not find watchpoint.";
SendExceptionNotification(exception, regs);
return;
}
// Comparison is by the base of the address range.
Watchpoint* watchpoint = process_->FindWatchpoint(range);
if (!watchpoint) {
DEBUG_LOG(Thread) << "Could not find watchpoint for range " << range.ToString();
SendExceptionNotification(exception, regs);
return;
}
// TODO(donosoc): Plumb in R/RW types.
UpdateForHitProcessBreakpoint(watchpoint->Type(), watchpoint, exception->hit_breakpoints);
// The ProcessBreakpoint could'be been deleted, so we cannot use it anymore.
watchpoint = nullptr;
SendExceptionNotification(exception, regs);
}
void DebuggedThread::SendExceptionNotification(debug_ipc::NotifyException* exception,
const GeneralRegisters& regs) {
exception->thread = GetThreadRecord(debug_ipc::ThreadRecord::StackAmount::kMinimal, regs);
// Keep the thread suspended for the client.
// TODO(brettw) suspend other threads in the process and other debugged
// processes as desired.
LogExceptionNotification(FROM_HERE, this, *exception);
// 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: " << debug_ipc::ResumeRequest::HowToString(request.how)
<< ", Range: [" << request.range_begin << ", " << request.range_end << ").";
run_mode_ = request.how;
step_in_range_begin_ = request.range_begin;
step_in_range_end_ = request.range_end;
ResumeForRunMode();
}
void DebuggedThread::ResumeException() {
if (!IsInException()) {
return;
}
// We need to mark that this token is correctly handled before closing it.
DEBUG_LOG(Thread) << ThreadPreamble(this) << "Resuming exception handle.";
zx_status_t status = exception_handle_->SetState(ZX_EXCEPTION_STATE_HANDLED);
if (status != ZX_OK) {
DEBUG_LOG(Thread) << ThreadPreamble(this)
<< "Failed to set exception as handled: " << zx_status_get_string(status);
}
exception_handle_ = nullptr;
}
void DebuggedThread::ResumeSuspension() {
if (local_suspend_token_) {
DEBUG_LOG(Thread) << ThreadPreamble(this) << "Resuming suspend token.";
}
local_suspend_token_.reset();
}
bool DebuggedThread::Suspend(bool synchronous) {
if (local_suspend_token_) {
// The thread could have an asynchronous suspend pending from before, but it might not actually
// be suspended yet. If somebody requests a synchronous suspend, make sure we honor that the
// thread is suspended before returning.
if (synchronous)
WaitForSuspension(DefaultSuspendDeadline());
return false;
}
local_suspend_token_ = RefCountedSuspend(synchronous);
// If there is only one count, we know that this was the token that did the suspension.
return suspend_count_ == 1;
}
std::unique_ptr<DebuggedThread::SuspendToken> DebuggedThread::RefCountedSuspend(bool synchronous) {
auto token = std::unique_ptr<SuspendToken>(new SuspendToken(this));
if (synchronous)
WaitForSuspension(DefaultSuspendDeadline());
return token;
}
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::msec(100));
}
bool DebuggedThread::WaitForSuspension(zx::time deadline) {
// The thread could already be suspended. This bypasses a wait cycle in that case.
if (thread_handle_->GetState().state == debug_ipc::ThreadRecord::State::kSuspended)
return true; // Already suspended, success.
// 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 {
// Before waiting, check the thread state from the kernel because of queue described above.
if (thread_handle_->GetState().is_blocked_on_exception())
return true;
zx_signals_t observed;
status = thread_handle_->GetNativeHandle().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;
}
// Note that everything in this function is racy because the thread state can change at any time,
// even while processing an exception (an external program can kill it out from under us).
debug_ipc::ThreadRecord DebuggedThread::GetThreadRecord(
debug_ipc::ThreadRecord::StackAmount stack_amount, std::optional<GeneralRegisters> regs) const {
debug_ipc::ThreadRecord record = thread_handle_->GetThreadRecord();
// Unwind the stack if requested. This requires the registers which are available when suspended
// or blocked in an exception.
if ((record.state == debug_ipc::ThreadRecord::State::kSuspended ||
(record.state == debug_ipc::ThreadRecord::State::kBlocked &&
record.blocked_reason == debug_ipc::ThreadRecord::BlockedReason::kException)) &&
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.
if (!regs)
regs = thread_handle_->GetGeneralRegisters(); // Note this could still fail.
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(arch_provider_.get(), process_->process_handle(), process_->dl_debug_addr(),
thread_handle(), *regs, max_stack_depth, &record.frames);
}
} else {
// Didn't bother querying the stack.
record.stack_amount = debug_ipc::ThreadRecord::StackAmount::kNone;
}
return record;
}
std::vector<debug_ipc::Register> DebuggedThread::ReadRegisters(
const std::vector<debug_ipc::RegisterCategory>& cats_to_get) const {
return thread_handle_->ReadRegisters(cats_to_get);
}
std::vector<debug_ipc::Register> DebuggedThread::WriteRegisters(
const std::vector<debug_ipc::Register>& regs) {
std::vector<debug_ipc::Register> written = thread_handle_->WriteRegisters(regs);
// If we're updating the instruction pointer directly, 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.
bool rip_change = false;
debug_ipc::RegisterID rip_id =
GetSpecialRegisterID(arch_provider_->GetArch(), debug_ipc::SpecialRegisterType::kIP);
for (const debug_ipc::Register& reg : regs) {
if (reg.id == rip_id) {
rip_change = true;
break;
}
}
if (rip_change)
current_breakpoint_ = nullptr;
return written;
}
void DebuggedThread::SendThreadNotification() const {
DEBUG_LOG(Thread) << ThreadPreamble(this) << "Sending starting notification.";
debug_ipc::NotifyThread notify;
notify.record = GetThreadRecord(debug_ipc::ThreadRecord::StackAmount::kMinimal);
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(
GeneralRegisters& 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_provider_->BreakpointInstructionForSoftwareExceptionAddress(regs.ip());
SoftwareBreakpoint* found_bp = process_->FindSoftwareBreakpoint(breakpoint_address);
if (found_bp) {
LogHitBreakpoint(FROM_HERE, this, found_bp, breakpoint_address);
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, 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_provider_->IsBreakpointInstruction(process_->handle(), 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.
regs.set_ip(arch_provider_->NextInstructionForSoftwareExceptionAddress(regs.ip()));
thread_handle_->SetGeneralRegisters(regs);
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 (see DebuggerProcess::RegisterDebugState).
//
// 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 {
DEBUG_LOG(Thread) << ThreadPreamble(this) << "Hit non debugger SW breakpoint on 0x"
<< std::hex << breakpoint_address;
// 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.
regs.set_ip(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;
}
void DebuggedThread::FixSoftwareBreakpointAddress(ProcessBreakpoint* process_breakpoint,
GeneralRegisters& 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.
regs.set_ip(process_breakpoint->address());
thread_handle_->SetGeneralRegisters(regs);
}
void DebuggedThread::UpdateForHitProcessBreakpoint(
debug_ipc::BreakpointType exception_type, ProcessBreakpoint* process_breakpoint,
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::ResumeForRunMode() {
// We check if we're set to currently step over a breakpoint. If so we need to do some special
// handling, as going over a breakpoint is always a single-step operation.
// After that we can continue according to the set run-mode.
if (IsInException() && current_breakpoint_) {
DEBUG_LOG(Thread) << ThreadPreamble(this) << "Stepping over breakpoint: 0x" << std::hex
<< current_breakpoint_->address();
SetSingleStep(true);
current_breakpoint_->BeginStepOver(this);
// In this case, the breakpoint takes control of the thread lifetime and has already set the
// thread to resume.
return;
}
// We're not handling the special "step over a breakpoint case". This is the normal resume case.
// This could've been triggered by an internal resume (eg. triggered by a breakpoint), so we need
// to check if the client actually wants this thread to resume.
if (client_state_ == ClientState::kPaused)
return;
// All non-continue resumptions require single stepping.
SetSingleStep(run_mode_ != debug_ipc::ResumeRequest::How::kContinue);
ResumeException();
ResumeSuspension();
}
void DebuggedThread::SetSingleStep(bool single_step) {
arch_provider_->WriteSingleStep(thread_handle_->GetNativeHandle(), single_step);
}
const char* DebuggedThread::ClientStateToString(ClientState client_state) {
switch (client_state) {
case ClientState::kRunning:
return "Running";
case ClientState::kPaused:
return "Paused";
}
FX_NOTREACHED();
return "<unknown>";
}
void DebuggedThread::IncreaseSuspend() {
suspend_count_++;
// We only need to keep one suspend token around.
if (ref_counted_suspend_token_.is_valid())
return;
zx_status_t status = thread_handle_->GetNativeHandle().suspend(&ref_counted_suspend_token_);
if (status != ZX_OK) {
DEBUG_LOG(Thread) << ThreadPreamble(this)
<< "Could not suspend: " << zx_status_get_string(status);
}
}
void DebuggedThread::DecreaseSuspend() {
suspend_count_--;
FX_DCHECK(suspend_count_ >= 0);
if (suspend_count_ > 0)
return;
ref_counted_suspend_token_.reset();
}
} // namespace debug_agent