blob: 4e005d87bff2360e75fe88f3ece29e0952f0a97d [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 <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/arch_types.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/process_breakpoint.h"
#include "src/developer/debug/debug_agent/software_breakpoint.h"
#include "src/developer/debug/debug_agent/time.h"
#include "src/developer/debug/debug_agent/unwind.h"
#include "src/developer/debug/debug_agent/watchpoint.h"
#include "src/developer/debug/ipc/message_reader.h"
#include "src/developer/debug/ipc/message_writer.h"
#include "src/developer/debug/ipc/protocol.h"
#include "src/developer/debug/ipc/records.h"
#include "src/developer/debug/shared/logging/logging.h"
#include "src/developer/debug/shared/platform_message_loop.h"
#include "src/developer/debug/shared/register_value.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()->process_handle().GetName().c_str(), thread->koid());
}
void LogHitBreakpoint(debug::FileLineFunction location, const DebuggedThread* thread,
ProcessBreakpoint* process_breakpoint, uint64_t address) {
if (!debug::IsDebugLoggingActive())
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::FileLineFunction location, const DebuggedThread* thread,
const debug_ipc::NotifyException& exception) {
if (!debug::IsDebugLoggingActive())
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::DebuggedThread(DebugAgent* debug_agent, DebuggedProcess* process,
std::unique_ptr<ThreadHandle> handle)
: thread_handle_(std::move(handle)),
debug_agent_(debug_agent),
process_(process),
weak_factory_(this) {}
DebuggedThread::~DebuggedThread() = default;
fxl::WeakPtr<DebuggedThread> DebuggedThread::GetWeakPtr() { return weak_factory_.GetWeakPtr(); }
void DebuggedThread::OnException(std::unique_ptr<ExceptionHandle> exception_handle) {
exception_handle_ = std::move(exception_handle);
debug_ipc::ExceptionType type = exception_handle_->GetType(*thread_handle_);
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.
LOGS(Warn) << "Could not read registers from thread.";
return;
}
DEBUG_LOG(Thread) << ThreadPreamble(this) << "Exception @ 0x" << std::hex << regs->ip()
<< std::dec << ": " << debug_ipc::ExceptionTypeToString(type);
debug_ipc::NotifyException exception{};
exception.type = type;
exception.exception = thread_handle_->GetExceptionRecord();
exception.timestamp = GetNowTimestamp();
// TODO(https://fxbug.dev/42086129) clean up the single-step queue here (currently only handled in the
// HandleSingleStep() function to advance the single-step queue when the stepped-over instruction
// crashes rather than issues a single-step exception.
switch (type) {
case debug_ipc::ExceptionType::kSingleStep:
return HandleSingleStep(&exception, *regs);
case debug_ipc::ExceptionType::kSoftwareBreakpoint:
return HandleSoftwareBreakpoint(&exception, *regs);
case debug_ipc::ExceptionType::kHardwareBreakpoint:
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(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::ResumeFromException() {
if (in_exception() && current_breakpoint_) {
// Resuming from a breakpoint hit. Going over a breakpoint requires removing the breakpoint,
// single-stepping the thread, and putting the breakpoint back.
DEBUG_LOG(Thread) << ThreadPreamble(this) << "Stepping over breakpoint: 0x" << std::hex
<< current_breakpoint_->address();
// BeginStepOver() will takes responsibility for resuming the exception at the proper time.
current_breakpoint_->BeginStepOver(this);
} else {
// Check whether we're resuming from a hardcoded breakpoint exception. If it is, continue from
// the following instruction since the breakpoint instruction will never go away.
if (in_exception() && exception_handle_->GetType(*thread_handle_) ==
debug_ipc::ExceptionType::kSoftwareBreakpoint) {
auto regs = thread_handle_->GetGeneralRegisters();
// It's possible that the software breakpoint we see is newly installed, e.g., when a user
// uninstall and reinstall a breakpoint at the same location. We shouldn't skip the breakpoint
// instruction in this case.
if (regs && process_->FindSoftwareBreakpoint(regs->ip()) == nullptr &&
IsBreakpointInstructionAtAddress(regs->ip())) {
regs->set_ip(regs->ip() + arch::kBreakInstructionSize);
thread_handle_->SetGeneralRegisters(*regs);
}
}
// Normal exception resumption.
InternalResumeException();
}
}
void DebuggedThread::HandleSingleStep(debug_ipc::NotifyException* exception,
const GeneralRegisters& regs) {
if (current_breakpoint_) {
// TODO(https://fxbug.dev/42086129) this single-step cleanup needs to be unconditionally executed for any
// exception on the thread (not just here in the "single step" handler) because the thread could
// crash on the instruction being stepped over and then the step over queue will never be
// cleaned up.
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 occurred and thus the thread won't miss any
// breakpoints.
current_breakpoint_->EndStepOver(this);
current_breakpoint_ = nullptr;
InternalResumeException();
return;
}
if (!debug_ipc::ResumeRequest::MakesStep(run_mode_)) {
// This could be due to a race where the user was previously single stepping and then requested
// a continue or forward 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 (since forwarding the single step exception to the
// debugged program makes no sense).
DEBUG_LOG(Thread) << ThreadPreamble(this) << "Single step without breakpoint. Continuing.";
ResumeFromException();
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.";
ResumeFromException();
return;
}
// When stepping with a count, automatically continue if step_count_ > 1.
if (run_mode_ == debug_ipc::ResumeRequest::How::kStepInstruction && step_count_ > 1) {
DEBUG_LOG(Thread) << ThreadPreamble(this) << "Stepping with count. Continuing.";
step_count_ -= 1;
ResumeFromException();
return;
}
DEBUG_LOG(Thread) << ThreadPreamble(this) << "Expected single step. Notifying.";
SendExceptionNotification(exception, regs);
}
void DebuggedThread::HandleGeneralException(debug_ipc::NotifyException* exception,
const GeneralRegisters& regs) {
auto strategy = exception_handle_->GetStrategy();
if (strategy.is_error()) {
LOGS(Warn) << "Failed to determine current exception strategy: "
<< strategy.error_value().message();
return;
}
debug_ipc::ExceptionStrategy applied = strategy.value();
bool handle_now = true;
// If the strategy is first-chance, then this is the first that we've seen this exception.
// Further, if the applied strategy for this type is second-chance, update and handle it
// accordingly.
auto applicable_strategy = debug_agent_->GetExceptionStrategy(exception->type);
if (strategy.value() == debug_ipc::ExceptionStrategy::kFirstChance &&
applicable_strategy == debug_ipc::ExceptionStrategy::kSecondChance) {
if (auto status = exception_handle_->SetStrategy(applicable_strategy); status.has_error()) {
LOGS(Warn) << "Failed to apply default exception strategy: " << status.message();
return;
}
applied = applicable_strategy;
handle_now = false;
}
DEBUG_LOG(Thread) << ThreadPreamble(this)
<< "Exception strategy: " << debug_ipc::ExceptionStrategyToString(applied);
if (handle_now) {
exception->exception.strategy = applied;
SendExceptionNotification(exception, regs);
} else {
// Reset and close the handle to "forward" the exception back to the
// program to resolve.
exception_handle_.reset();
}
}
void DebuggedThread::HandleSoftwareBreakpoint(debug_ipc::NotifyException* exception,
GeneralRegisters& regs) {
switch (UpdateForSoftwareBreakpoint(regs, exception->hit_breakpoints,
exception->other_affected_threads)) {
case OnStop::kNotify:
SendExceptionNotification(exception, regs);
return;
case OnStop::kResume: {
ResumeFromException();
return;
}
}
FX_NOTREACHED() << "Invalid OnStop.";
}
void DebuggedThread::HandleHardwareBreakpoint(debug_ipc::NotifyException* exception,
GeneralRegisters& regs) {
uint64_t breakpoint_address = arch::BreakpointInstructionForHardwareExceptionAddress(regs.ip());
if (HardwareBreakpoint* found_bp = process_->FindHardwareBreakpoint(breakpoint_address)) {
UpdateForHitProcessBreakpoint(debug_ipc::BreakpointType::kHardware, found_bp,
exception->hit_breakpoints, exception->other_affected_threads);
// Note: may have deleted found_bp.
} 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);
}
SendExceptionNotification(exception, regs);
}
void DebuggedThread::HandleWatchpoint(debug_ipc::NotifyException* exception,
const GeneralRegisters& regs) {
std::optional<DebugRegisters> debug_regs = thread_handle_->GetDebugRegisters();
if (!debug_regs) {
DEBUG_LOG(Thread) << "Could not load debug registers to handle watchpoint.";
return;
}
std::optional<WatchpointInfo> hit = debug_regs->DecodeHitWatchpoint();
if (!hit) {
// When no watchpoint matches this watchpoint, send the exception notification and let the
// debugger frontend handle the exception.
DEBUG_LOG(Thread) << "Could not find watchpoint.";
SendExceptionNotification(exception, regs);
return;
}
DEBUG_LOG(Thread) << "Found watchpoint hit at 0x" << std::hex << hit->range.ToString()
<< " on slot " << std::dec << hit->slot;
// Comparison is by the base of the address range.
Watchpoint* watchpoint = process_->FindWatchpoint(hit->range);
if (!watchpoint) {
DEBUG_LOG(Thread) << "Could not find watchpoint for range " << hit->range.ToString();
SendExceptionNotification(exception, regs);
return;
}
// TODO(donosoc): Plumb in R/RW types.
UpdateForHitProcessBreakpoint(watchpoint->Type(), watchpoint, exception->hit_breakpoints,
exception->other_affected_threads);
// 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.
// The debug agent is able to automatically retrieve memory blocks when a breakpoint is reached
// based on a list of instructions.
// Calls the automation handler which computes the memory blocks and adds them to the exception.
automation_handler_.OnException(exception, regs, process_->process_handle(),
debug_agent_->breakpoints());
LogExceptionNotification(FROM_HERE, this, *exception);
// Send notification.
debug_agent_->SendNotification(*exception);
}
void DebuggedThread::ClientResume(const debug_ipc::ResumeRequest& request) {
DEBUG_LOG(Thread) << ThreadPreamble(this)
<< "Resuming. Run mode: " << debug_ipc::ResumeRequest::HowToString(request.how)
<< ", Count: " << request.count << ", Range: [" << request.range_begin << ", "
<< request.range_end << ").";
run_mode_ = request.how;
step_count_ = request.count;
step_in_range_begin_ = request.range_begin;
step_in_range_end_ = request.range_end;
ResumeFromException();
if (client_suspend_handle_) {
// Normally the single-step flat is set by the exception resumption code, but if we're resuming
// from a pause that will do nothing so set here.
DEBUG_LOG(Thread) << ThreadPreamble(this) << "Resuming from client suspend.";
SetSingleStepForRunMode();
client_suspend_handle_.reset();
}
}
void DebuggedThread::InternalResumeException() {
if (!in_exception()) {
DEBUG_LOG(Thread) << ThreadPreamble(this)
<< "Resuming from exception but there is no exception.";
return;
}
SetSingleStepForRunMode();
if (run_mode_ == debug_ipc::ResumeRequest::How::kForwardAndContinue) {
DEBUG_LOG(Thread) << ThreadPreamble(this) << "Resuming from exception (second chance).";
if (auto status = exception_handle_->SetStrategy(debug_ipc::ExceptionStrategy::kSecondChance);
status.has_error()) {
DEBUG_LOG(Thread) << ThreadPreamble(this)
<< "Failed to set exception as second-chance: " << status.message();
}
} else {
DEBUG_LOG(Thread) << ThreadPreamble(this) << "Resuming from exception (handled).";
if (auto status = exception_handle_->SetResolution(ExceptionHandle::Resolution::kHandled);
status.has_error()) {
DEBUG_LOG(Thread) << ThreadPreamble(this)
<< "Failed to set exception as handled: " << status.message();
}
}
exception_handle_ = nullptr;
}
void DebuggedThread::ClientSuspend(bool synchronous) {
if (!client_suspend_handle_)
client_suspend_handle_ = thread_handle_->Suspend();
// Even if there was already a client_suspend, the previous suspend could have been asynchronous
// and still pending. When a synchronous suspend is requested make sure we honor that the thread
// is suspended before returning. WaitForSuspension() should be relatively inexpensive if the
// thread is already suspended.
if (synchronous)
thread_handle_->WaitForSuspension(DefaultSuspendDeadline());
}
std::unique_ptr<SuspendHandle> DebuggedThread::InternalSuspend(bool synchronous) {
auto suspend_handle = thread_handle_->Suspend();
if (synchronous)
thread_handle_->WaitForSuspension(DefaultSuspendDeadline());
return suspend_handle;
}
TickTimePoint 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 std::chrono::steady_clock::now() + std::chrono::milliseconds(100);
}
// 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(process_->koid());
// 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(process_->process_handle(), process_->module_list(), 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::RegisterValue> DebuggedThread::ReadRegisters(
const std::vector<debug::RegisterCategory>& cats_to_get) const {
return thread_handle_->ReadRegisters(cats_to_get);
}
std::vector<debug::RegisterValue> DebuggedThread::WriteRegisters(
const std::vector<debug::RegisterValue>& regs) {
// If we're updating the instruction pointer to a new value 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::RegisterID rip_id =
GetSpecialRegisterID(arch::GetCurrentArch(), debug::SpecialRegisterType::kIP);
auto ip = std::find_if(regs.begin(), regs.end(),
[rip_id](const debug::RegisterValue& rv) { return rv.id == rip_id; });
if (ip != regs.end()) {
auto current_registers = thread_handle_->GetGeneralRegisters();
if (current_registers && ip->GetValue() != current_registers->ip()) {
rip_change = true;
}
}
if (rip_change)
current_breakpoint_ = nullptr;
std::vector<debug::RegisterValue> written = thread_handle_->WriteRegisters(regs);
return written;
}
void DebuggedThread::SendThreadNotification() const {
DEBUG_LOG(Thread) << ThreadPreamble(this) << "Sending starting notification.";
debug_ipc::NotifyThreadStarting notify;
notify.record = GetThreadRecord(debug_ipc::ThreadRecord::StackAmount::kMinimal);
notify.timestamp = GetNowTimestamp();
debug_agent_->SendNotification(notify);
}
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,
std::vector<debug_ipc::ThreadRecord>& other_affected_threads) {
// Get the correct address where the CPU is after hitting a breakpoint (this is
// architecture-specific).
uint64_t breakpoint_address = regs.ip() - arch::kExceptionOffsetForSoftwareBreakpoint;
// When the program hits a software breakpoint, set the IP back to the exact address that
// triggered the breakpoint, so that
// 1) the backtrace is from the breakpoint instruction.
// 2) if it's a breakpoint that we installed, we need to evaluate the original instruction on
// this address.
if (breakpoint_address != regs.ip()) {
regs.set_ip(breakpoint_address);
thread_handle_->SetGeneralRegisters(regs);
}
if (SoftwareBreakpoint* found_bp = process_->FindSoftwareBreakpoint(breakpoint_address)) {
#if defined(__linux__)
// OnLinux, the loader breakpoint is a software breakpoint we inserted. Special-case this.
if (found_bp->IsInternalLdSoBreakpoint()) {
if (process_->HandleLoaderBreakpoint(breakpoint_address)) {
// |HandleLoaderBreakpoint| may suspend the task and it's safe for us to always resume.
DEBUG_LOG(Thread) << ThreadPreamble(this)
<< "Hardcoded loader breakpoint, internally resuming.";
current_breakpoint_ = found_bp;
return OnStop::kResume;
}
return OnStop::kNotify;
}
#endif
LogHitBreakpoint(FROM_HERE, this, found_bp, breakpoint_address);
// 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,
other_affected_threads);
// Note: may have deleted found_bp!
} else if (IsBreakpointInstructionAtAddress(breakpoint_address)) {
// Hit a software breakpoint that doesn't correspond to any current breakpoint.
if (process_->HandleLoaderBreakpoint(breakpoint_address)) {
// |HandleLoaderBreakpoint| may suspend the task and it's safe for us to always resume.
DEBUG_LOG(Thread) << ThreadPreamble(this)
<< "Hardcoded loader breakpoint, internally resuming.";
return OnStop::kResume;
}
} 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.
DEBUG_LOG(Thread) << ThreadPreamble(this) << "Hit non debugger SW breakpoint on 0x" << std::hex
<< 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::UpdateForHitProcessBreakpoint(
debug_ipc::BreakpointType exception_type, ProcessBreakpoint* process_breakpoint,
std::vector<debug_ipc::BreakpointStats>& hit_breakpoints,
std::vector<debug_ipc::ThreadRecord>& other_affected_threads) {
current_breakpoint_ = process_breakpoint;
process_breakpoint->OnHit(this, exception_type, hit_breakpoints, other_affected_threads);
// 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);
}
}
bool DebuggedThread::IsBreakpointInstructionAtAddress(uint64_t address) const {
arch::BreakInstructionType instruction = 0;
size_t bytes_read = 0;
if (process_->process_handle()
.ReadMemory(address, &instruction, sizeof(instruction), &bytes_read)
.has_error() ||
bytes_read != sizeof(instruction))
return false;
return arch::IsBreakpointInstruction(instruction);
}
void DebuggedThread::SetSingleStepForRunMode() {
// When we're single-stepping over a breakpoint, that overrides the user run mode.
thread_handle_->SetSingleStep(stepping_over_breakpoint_ ||
debug_ipc::ResumeRequest::MakesStep(run_mode_));
}
} // namespace debug_agent