| // 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/software_breakpoint.h" |
| |
| #include <inttypes.h> |
| #include <lib/syslog/cpp/macros.h> |
| |
| #include <algorithm> |
| |
| #include "src/developer/debug/debug_agent/breakpoint.h" |
| #include "src/developer/debug/debug_agent/process_breakpoint.h" |
| #include "src/developer/debug/debug_agent/process_handle.h" |
| #include "src/developer/debug/shared/logging/logging.h" |
| |
| namespace debug_agent { |
| |
| namespace { |
| |
| std::string LogPreamble(ProcessBreakpoint* b) { |
| std::stringstream ss; |
| |
| ss << "[SW BP 0x" << std::hex << b->address(); |
| bool first = true; |
| |
| // Add the names of all the breakpoints associated with this process breakpoint. |
| ss << " ("; |
| for (Breakpoint* breakpoint : b->breakpoints()) { |
| if (!first) { |
| first = false; |
| ss << ", "; |
| } |
| ss << breakpoint->settings().name; |
| } |
| |
| ss << ")] "; |
| return ss.str(); |
| } |
| |
| } // namespace |
| |
| SoftwareBreakpoint::SoftwareBreakpoint(Breakpoint* breakpoint, DebuggedProcess* process, |
| uint64_t address) |
| : ProcessBreakpoint(breakpoint, process, address) {} |
| |
| SoftwareBreakpoint::~SoftwareBreakpoint() { Uninstall(); } |
| |
| debug::Status SoftwareBreakpoint::Update() { |
| // Software breakpoints remain installed as long as even one remains active, regardless of which |
| // threads are targeted. |
| int sw_bp_count = 0; |
| for (Breakpoint* bp : breakpoints()) { |
| if (bp->settings().type == debug_ipc::BreakpointType::kSoftware) |
| sw_bp_count++; |
| } |
| |
| if (sw_bp_count == 0 && installed_) { |
| Uninstall(); |
| } else if (sw_bp_count > 0 && !installed_) { |
| return Install(); |
| } |
| |
| return debug::Status(); |
| } |
| |
| debug::Status SoftwareBreakpoint::Install() { |
| FX_DCHECK(!installed_); |
| |
| // Read previous instruction contents. |
| size_t actual = 0; |
| if (debug::Status status = process_->process_handle().ReadMemory( |
| address(), &previous_data_, arch::kBreakInstructionSize, &actual); |
| status.has_error()) |
| return status; |
| if (actual != arch::kBreakInstructionSize) |
| return debug::Status("Could not read breakpoint memory."); |
| |
| // Replace with breakpoint instruction. |
| if (debug::Status status = process_->process_handle().WriteMemory( |
| address(), &arch::kBreakInstruction, arch::kBreakInstructionSize, &actual); |
| status.has_error()) |
| return status; |
| if (actual != arch::kBreakInstructionSize) |
| return debug::Status("Could not write breakpoint memory."); |
| |
| installed_ = true; |
| return debug::Status(); |
| } |
| |
| debug::Status SoftwareBreakpoint::Uninstall() { |
| if (!installed_) |
| return debug::Status(); // Not installed. |
| |
| // If the breakpoint was previously installed it means the memory address was valid and writable, |
| // so we generally expect to be able to do the same write to uninstall it. But it could have been |
| // unmapped during execution or even remapped with something else. So verify that it's still a |
| // breakpoint instruction before doing any writes. |
| arch::BreakInstructionType current_contents = 0; |
| size_t actual = 0; |
| debug::Status status = process_->process_handle().ReadMemory( |
| address(), ¤t_contents, arch::kBreakInstructionSize, &actual); |
| if (status.has_error() || actual != arch::kBreakInstructionSize) |
| return debug::Status(); // Probably unmapped, safe to ignore. |
| |
| if (current_contents != arch::kBreakInstruction) { |
| LOGS(Warn) << "Debug break instruction unexpectedly replaced at 0x" << std::hex << address(); |
| return debug::Status(); // Replaced with something else, ignore. |
| } |
| |
| status = process_->process_handle().WriteMemory(address(), &previous_data_, |
| arch::kBreakInstructionSize, &actual); |
| if (status.has_error() || actual != arch::kBreakInstructionSize) { |
| LOGS(Warn) << "Unable to remove breakpoint at 0x" << std::hex << address(); |
| } |
| |
| installed_ = false; |
| return debug::Status(); |
| } |
| |
| void SoftwareBreakpoint::FixupMemoryBlock(debug_ipc::MemoryBlock* block) { |
| if (block->data.empty()) |
| return; // Nothing to do. |
| FX_DCHECK(static_cast<size_t>(block->size) == block->data.size()); |
| |
| size_t src_size = arch::kBreakInstructionSize; |
| const uint8_t* src = reinterpret_cast<uint8_t*>(&previous_data_); |
| |
| // Simple implementation to prevent boundary errors (ARM instructions are 32-bits and could be |
| // hanging partially off either end of the requested buffer). |
| for (size_t i = 0; i < src_size; i++) { |
| uint64_t dest_address = address() + i; |
| if (dest_address >= block->address && dest_address < block->address + block->size) |
| block->data[dest_address - block->address] = src[i]; |
| } |
| } |
| |
| void SoftwareBreakpoint::ExecuteStepOver(DebuggedThread* thread) { |
| DEBUG_LOG(Breakpoint) << LogPreamble(this) << "Thread " << thread->koid() << " is stepping over."; |
| currently_stepping_over_thread_ = thread->GetWeakPtr(); |
| thread->set_stepping_over_breakpoint(true); |
| |
| SuspendAllOtherThreads(thread->koid()); |
| |
| Uninstall(thread); |
| |
| // This thread now has to continue running. |
| thread->InternalResumeException(); |
| } |
| |
| void SoftwareBreakpoint::EndStepOver(DebuggedThread* thread) { |
| FX_DCHECK(thread->stepping_over_breakpoint()); |
| FX_DCHECK(currently_stepping_over_thread_); |
| FX_DCHECK(currently_stepping_over_thread_->koid() == thread->koid()) |
| << " Expected " << currently_stepping_over_thread_->koid() << ", Got " << thread->koid(); |
| |
| DEBUG_LOG(Breakpoint) << LogPreamble(this) << "Thread " << thread->koid() << " ending step over."; |
| thread->set_stepping_over_breakpoint(false); |
| currently_stepping_over_thread_.reset(); |
| |
| // Install the breakpoint again. |
| // NOTE(donosoc): For multiple threads stepping over (queue), this is inefficient as threads are |
| // suspended and there is no need to reinstall them every time, expect for |
| // implementation simplicity. If performance becomes an issue, we could create a |
| // notification that the process calls when the complete step queue has been done |
| // that tells the breakpoints to reinstall themselves. |
| Update(); |
| |
| // Tell the process we're done stepping over. |
| process_->OnBreakpointFinishedSteppingOver(); |
| } |
| |
| void SoftwareBreakpoint::StepOverCleanup(DebuggedThread* thread) { |
| DEBUG_LOG(Breakpoint) << LogPreamble(this) << "Finishing step over for thread " << thread->koid(); |
| |
| // We are done stepping over this thread, so we can remove the suspend tokens. Normally this means |
| // cleaning all the suspend tokens, if there is only one thread in the stepping over queue or the |
| // next step over is another breakpoint. |
| // |
| // But in the case that another thread is stepping over *the same* breakpoint, cleaning all the |
| // tokens would resume all the threads that have just been suspended by the next instance of the |
| // step over. |
| // |
| // For this case we need the ability to maintain more than one suspend tokens per thread: one for |
| // the first step over and another for the second, as they coincide between the process callind |
| // |ExecuteStepOver| on the second instance and callind |StepOverCleanup| on the first one. |
| auto it = suspend_tokens_.begin(); |
| while (it != suspend_tokens_.end()) { |
| // Calculate the upper bound (so we skip over repeated keys). |
| auto cur_it = it; |
| it = suspend_tokens_.upper_bound(it->first); |
| |
| // We do not erase a token for the thread we just stepped over, because it will be the only |
| // thread that will not have 2 suspend tokens: It will have the one taken by the next step over, |
| // as the first one didn't get one. |
| if (cur_it->first == thread->koid()) |
| continue; |
| |
| // All other threads would have 2 suspend tokens (one for the first step over and one for the |
| // second), meaning that we can safely remove the first one. |
| suspend_tokens_.erase(cur_it); |
| } |
| |
| // Remote the thread from the exception. |
| thread->InternalResumeException(); |
| } |
| |
| void SoftwareBreakpoint::SuspendAllOtherThreads(zx_koid_t stepping_over_koid) { |
| std::vector<DebuggedThread*> suspended_threads; |
| for (DebuggedThread* thread : process_->GetThreads()) { |
| // We do not suspend the stepping over thread. |
| if (thread->koid() == stepping_over_koid) |
| continue; |
| |
| // Only one thread should be stepping over at a time. |
| if (thread->stepping_over_breakpoint() && thread->koid() != stepping_over_koid) { |
| FX_NOTREACHED() << "Thread " << thread->koid() << " is stepping over. Only thread " |
| << stepping_over_koid << " should be stepping over."; |
| } |
| |
| // We keep every other thread suspended. |
| // If this is a re-entrant breakpoint (two threads in a row are stepping over the same |
| // breakpoint), we could have more than one token for each thread. |
| suspend_tokens_.insert({thread->koid(), thread->InternalSuspend(false)}); |
| } |
| |
| // We wait on all the suspend signals to trigger. |
| for (DebuggedThread* thread : suspended_threads) { |
| bool suspended = |
| thread->thread_handle().WaitForSuspension(DebuggedThread::DefaultSuspendDeadline()); |
| FX_DCHECK(suspended) << "Thread " << thread->koid(); |
| } |
| } |
| |
| std::vector<zx_koid_t> SoftwareBreakpoint::CurrentlySuspendedThreads() const { |
| std::vector<zx_koid_t> koids; |
| koids.reserve(suspend_tokens_.size()); |
| for (auto& [thread_koid, _] : suspend_tokens_) { |
| koids.emplace_back(thread_koid); |
| } |
| |
| std::sort(koids.begin(), koids.end()); |
| return koids; |
| } |
| |
| } // namespace debug_agent |