blob: ba07d49a9cb58b52a01e340dde96e3a2e36748c1 [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 <inttypes.h>
#include <zircon/status.h>
#include <zircon/syscalls/exception.h>
#include <zircon/syscalls/exception.h>
#include "garnet/bin/debug_agent/breakpoint.h"
#include "garnet/bin/debug_agent/debugged_thread.h"
#include "garnet/bin/debug_agent/process_breakpoint.h"
#include "lib/fxl/logging.h"
#include "lib/fxl/strings/string_printf.h"
namespace debug_agent {
// Low-level Declarations ------------------------------------------------------
// Implementations are at the end of the file.
class ProcessBreakpoint::SoftwareBreakpoint {
public:
SoftwareBreakpoint(ProcessBreakpoint*, ProcessMemoryAccessor*);
~SoftwareBreakpoint();
zx_status_t Install();
void Uninstall();
void FixupMemoryBlock(debug_ipc::MemoryBlock* block);
private:
ProcessBreakpoint* process_bp_; // Not-owning.
ProcessMemoryAccessor* memory_accessor_; // Not-owning.
// Set to true when the instruction has been replaced.
bool installed_ = false;
// Previous memory contents before being replaced with the break instruction.
arch::BreakInstructionType previous_data_ = 0;
};
class ProcessBreakpoint::HardwareBreakpoint {
public:
HardwareBreakpoint(ProcessBreakpoint*);
~HardwareBreakpoint();
// Checks if any of the installations need to be added/removed.
zx_status_t Update(const std::set<zx_koid_t>& thread_koids);
// Uninstall all the threads.
zx_status_t Uninstall();
const std::set<zx_koid_t>& installed_threads() const {
return installed_threads_;
}
private:
// Install/Uninstall a particular thread.
zx_status_t Install(zx_koid_t thread_koid);
zx_status_t Uninstall(zx_koid_t thread_koid);
ProcessBreakpoint* process_bp_; // Not-owning.
std::set<zx_koid_t> installed_threads_;
};
namespace {
// A given set of breakpoints have a number of locations, which could target
// different threads. We need to get all the threads that are targeted to
// this particular location.
std::set<zx_koid_t> HWThreadsTargeted(const ProcessBreakpoint& pb) {
std::set<zx_koid_t> ids;
bool all_threads = false;
for (Breakpoint* bp : pb.breakpoints()) {
// We only care about hardware breakpoints.
if (bp->settings().type != debug_ipc::BreakpointType::kHardware)
continue;
for (auto& location : bp->settings().locations) {
// We only install for locations that match this process breakpoint.
if (location.address != pb.address())
continue;
auto thread_id = location.thread_koid;
if (thread_id == 0) {
all_threads = true;
break;
} else {
ids.insert(thread_id);
}
}
// No need to continue searching if a breakpoint wants all threads.
if (all_threads)
break;
}
// If all threads are required, add them all.
if (all_threads) {
for (DebuggedThread* thread : pb.process()->GetThreads())
ids.insert(thread->koid());
}
return ids;
}
} // namespace
// ProcessBreakpoint Implementation --------------------------------------------
ProcessBreakpoint::ProcessBreakpoint(Breakpoint* breakpoint,
DebuggedProcess* process,
ProcessMemoryAccessor* memory_accessor,
uint64_t address)
: process_(process), memory_accessor_(memory_accessor), address_(address) {
breakpoints_.push_back(breakpoint);
}
ProcessBreakpoint::~ProcessBreakpoint() { Uninstall(); }
zx_status_t ProcessBreakpoint::Init() { return Update(); }
zx_status_t ProcessBreakpoint::RegisterBreakpoint(Breakpoint* breakpoint) {
// Shouldn't get duplicates.
FXL_DCHECK(std::find(breakpoints_.begin(), breakpoints_.end(), breakpoint) ==
breakpoints_.end());
breakpoints_.push_back(breakpoint);
// Check if we need to install/uninstall a breakpoint.
return Update();
}
bool ProcessBreakpoint::UnregisterBreakpoint(Breakpoint* breakpoint) {
auto found = std::find(breakpoints_.begin(), breakpoints_.end(), breakpoint);
if (found == breakpoints_.end()) {
FXL_NOTREACHED(); // Should always be found.
} else {
breakpoints_.erase(found);
}
// Check if we need to install/uninstall a breakpoint.
Update();
return !breakpoints_.empty();
}
void ProcessBreakpoint::FixupMemoryBlock(debug_ipc::MemoryBlock* block) {
if (software_breakpoint_)
software_breakpoint_->FixupMemoryBlock(block);
}
void ProcessBreakpoint::OnHit(
debug_ipc::BreakpointType exception_type,
std::vector<debug_ipc::BreakpointStats>* hit_breakpoints) {
hit_breakpoints->clear();
for (Breakpoint* breakpoint : breakpoints_) {
// Only care for breakpoints that match the exception type.
if (breakpoint->settings().type != exception_type)
continue;
breakpoint->OnHit();
hit_breakpoints->push_back(breakpoint->stats());
}
}
void ProcessBreakpoint::BeginStepOver(zx_koid_t thread_koid) {
// Shouldn't be recursively stepping over a breakpoint from the same thread.
FXL_DCHECK(thread_step_over_.find(thread_koid) == thread_step_over_.end());
if (!CurrentlySteppingOver()) {
// This is the first thread to attempt to step over the breakpoint (there
// could theoretically be more than one).
Uninstall();
}
thread_step_over_[thread_koid] = StepStatus::kCurrent;
}
bool ProcessBreakpoint::BreakpointStepHasException(
zx_koid_t thread_koid, debug_ipc::NotifyException::Type exception_type) {
auto found_thread = thread_step_over_.find(thread_koid);
if (found_thread == thread_step_over_.end()) {
// Shouldn't be getting these notifications from a thread not currently
// doing a step-over.
FXL_NOTREACHED();
return false;
}
StepStatus step_status = found_thread->second;
thread_step_over_.erase(found_thread);
// When the last thread is done stepping over, put the breakpoint back.
if (step_status == StepStatus::kCurrent && !CurrentlySteppingOver())
Update();
// Now check if this exception was likely caused by successfully stepping
// over the breakpoint, or something else (the stepped
// instruction crashed or something).
// TODO(donosoc): Handle HW breakpoint case.
return exception_type == debug_ipc::NotifyException::Type::kSingleStep;
}
bool ProcessBreakpoint::CurrentlySteppingOver() const {
for (const auto& pair : thread_step_over_) {
if (pair.second == StepStatus::kCurrent)
return true;
}
return false;
}
zx_status_t ProcessBreakpoint::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 && software_breakpoint_) {
software_breakpoint_.reset();
} else if (sw_bp_count > 0 && !software_breakpoint_) {
software_breakpoint_ =
std::make_unique<SoftwareBreakpoint>(this, memory_accessor_);
zx_status_t status = software_breakpoint_->Install();
if (status != ZX_OK)
return status;
}
// Hardware breakpoints are different. We need to remove for all the threads
// that are not covered anymore.
std::set<zx_koid_t> threads = HWThreadsTargeted(*this);
if (threads.empty()) {
hardware_breakpoint_.reset();
} else {
if (!hardware_breakpoint_) {
hardware_breakpoint_ = std::make_unique<HardwareBreakpoint>(this);
}
return hardware_breakpoint_->Update(threads);
}
return ZX_OK;
}
void ProcessBreakpoint::Uninstall() {
software_breakpoint_.reset();
hardware_breakpoint_.reset();
}
// ProcessBreakpoint::SoftwareBreakpoint Implementation ------------------------
ProcessBreakpoint::SoftwareBreakpoint::SoftwareBreakpoint(
ProcessBreakpoint* process_bp, ProcessMemoryAccessor* memory_accessor)
: process_bp_(process_bp), memory_accessor_(memory_accessor) {}
ProcessBreakpoint::SoftwareBreakpoint::~SoftwareBreakpoint() { Uninstall(); }
zx_status_t ProcessBreakpoint::SoftwareBreakpoint::Install() {
FXL_DCHECK(!installed_);
uint64_t address = process_bp_->address();
// Read previous instruction contents.
size_t actual = 0;
zx_status_t status = memory_accessor_->ReadProcessMemory(
address, &previous_data_, sizeof(arch::BreakInstructionType), &actual);
if (status != ZX_OK)
return status;
if (actual != sizeof(arch::BreakInstructionType))
return ZX_ERR_UNAVAILABLE;
// Replace with breakpoint instruction.
status = memory_accessor_->WriteProcessMemory(
address, &arch::kBreakInstruction, sizeof(arch::BreakInstructionType),
&actual);
if (status != ZX_OK)
return status;
if (actual != sizeof(arch::BreakInstructionType))
return ZX_ERR_UNAVAILABLE;
installed_ = true;
return ZX_OK;
}
void ProcessBreakpoint::SoftwareBreakpoint::Uninstall() {
if (!installed_)
return; // Not installed.
uint64_t address = process_bp_->address();
// 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;
zx_status_t status = memory_accessor_->ReadProcessMemory(
address, &current_contents, sizeof(arch::BreakInstructionType), &actual);
if (status != ZX_OK || actual != sizeof(arch::BreakInstructionType))
return; // Probably unmapped, safe to ignore.
if (current_contents != arch::kBreakInstruction) {
fprintf(stderr,
"Warning: Debug break instruction unexpectedly replaced "
"at %" PRIX64 "\n",
address);
return; // Replaced with something else, ignore.
}
status = memory_accessor_->WriteProcessMemory(
address, &previous_data_, sizeof(arch::BreakInstructionType), &actual);
if (status != ZX_OK || actual != sizeof(arch::BreakInstructionType)) {
fprintf(stderr, "Warning: unable to remove breakpoint at %" PRIX64 ".",
address);
}
installed_ = false;
}
void ProcessBreakpoint::SoftwareBreakpoint::FixupMemoryBlock(
debug_ipc::MemoryBlock* block) {
if (block->data.empty())
return; // Nothing to do.
FXL_DCHECK(static_cast<size_t>(block->size) == block->data.size());
size_t src_size = sizeof(arch::BreakInstructionType);
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 = process_bp_->address() + i;
if (dest_address >= block->address &&
dest_address < block->address + block->size)
block->data[dest_address - block->address] = src[i];
}
}
bool ProcessBreakpoint::SoftwareBreakpointInstalled() const {
return software_breakpoint_ != nullptr;
}
bool ProcessBreakpoint::HardwareBreakpointInstalled() const {
return hardware_breakpoint_ != nullptr &&
!hardware_breakpoint_->installed_threads().empty();
}
// ProcessBreakpoint::HardwareBreakpoint Implementation ------------------------
ProcessBreakpoint::HardwareBreakpoint::HardwareBreakpoint(
ProcessBreakpoint* process_bp)
: process_bp_(process_bp) {}
ProcessBreakpoint::HardwareBreakpoint::~HardwareBreakpoint() {
Uninstall();
}
zx_status_t ProcessBreakpoint::HardwareBreakpoint::Update(
const std::set<zx_koid_t>& thread_ids) {
// We get a snapshot of which threads are already installed.
auto current_threads = installed_threads_;
// Uninstall pass.
for (zx_koid_t thread_id : current_threads) {
// The ProcessBreakpoint not longer tracks this. Remove.
if (thread_ids.count(thread_id) == 0) {
zx_status_t res = Uninstall(thread_id);
if (res != ZX_OK)
return res;
installed_threads_.erase(thread_id);
}
}
// Install pass.
for (zx_koid_t thread_id : thread_ids) {
// If it's already installed, ignore.
if (installed_threads_.count(thread_id) > 0)
continue;
// If it's new, install.
if (installed_threads_.count(thread_id) == 0) {
zx_status_t res = Install(thread_id);
if (res != ZX_OK)
return res;
installed_threads_.insert(thread_id);
}
}
return ZX_OK;
}
zx_status_t ProcessBreakpoint::HardwareBreakpoint::Install(
zx_koid_t thread_id) {
uint64_t address = process_bp_->address();
// We need to install this new thread.
DebuggedThread* thread = process_bp_->process()->GetThread(thread_id);
if (!thread) {
FXL_LOG(WARNING) << fxl::StringPrintf(
"Warning: installing HW breakpoint for nonexistent thread %u at "
"%" PRIX64 "\n",
static_cast<uint32_t>(thread_id), address);
return ZX_OK;
}
zx_status_t res =
arch::ArchProvider::Get().InstallHWBreakpoint(&thread->thread(), address);
// TODO: Do we want to remove all other locations when one fails?
if (res != ZX_OK) {
FXL_LOG(WARNING) << fxl::StringPrintf(
"Warning: Error installing HW breakpoint for thread %u at "
"%" PRIX64 ": %s\n",
static_cast<uint32_t>(thread_id), address, zx_status_get_string(res));
return res;
}
return ZX_OK;
}
zx_status_t ProcessBreakpoint::HardwareBreakpoint::Uninstall() {
for (zx_koid_t thread_id : installed_threads_) {
zx_status_t res = Uninstall(thread_id);
if (res != ZX_OK)
return res;
}
return ZX_OK;
}
zx_status_t ProcessBreakpoint::HardwareBreakpoint::Uninstall(
zx_koid_t thread_id) {
uint64_t address = process_bp_->address();
DebuggedThread* thread = process_bp_->process()->GetThread(thread_id);
if (!thread) {
FXL_LOG(WARNING) << fxl::StringPrintf(
"Warning: removed HW breakpoint for nonexistent thread %u at %" PRIX64
"\n",
static_cast<uint32_t>(thread_id), address);
// TODO: What to do in this case?
return ZX_OK;
}
zx_status_t res = arch::ArchProvider::Get().UninstallHWBreakpoint(
&thread->thread(), address);
if (res != ZX_OK) {
FXL_LOG(WARNING) << fxl::StringPrintf(
"Warning: Error removing HW breakpoint for thread %u at %" PRIX64
": %s\n",
static_cast<uint32_t>(thread_id), address, zx_status_get_string(res));
return res;
}
return ZX_OK;
}
} // namespace debug_agent