blob: 05eed2e7ff5eadc9050325ffdd9ea0fd69586782 [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/hardware_breakpoint.h"
#include <inttypes.h>
#include "src/developer/debug/debug_agent/breakpoint.h"
#include "src/developer/debug/debug_agent/debugged_thread.h"
#include "src/developer/debug/debug_agent/process_breakpoint.h"
#include "src/developer/debug/shared/logging/logging.h"
#include "src/lib/fxl/strings/string_printf.h"
namespace debug_agent {
namespace {
enum class WarningType {
kInstall,
kUninstall,
};
void Warn(WarningType type, zx_koid_t thread_koid, uint64_t address,
zx_status_t status) {
// This happens normally when we receive a ZX_EXCP_THREAD_EXITING exception,
// making the system ignore our uninstall requests.
if (status == ZX_ERR_NOT_FOUND)
return;
const char* verb = type == WarningType::kInstall ? "install" : "uninstall";
FXL_LOG(WARNING) << fxl::StringPrintf(
"Could not %s HW breakpoint for thread %u at "
"%" PRIX64 ": %s",
verb, static_cast<uint32_t>(thread_koid), address,
zx_status_get_string(status));
}
} // namespace
HardwareBreakpoint::HardwareBreakpoint(ProcessBreakpoint* process_bp)
: process_bp_(process_bp) {}
HardwareBreakpoint::~HardwareBreakpoint() { Uninstall(); }
zx_status_t HardwareBreakpoint::Update(
const std::set<zx_koid_t>& thread_koids) {
// We get a snapshot of which threads are already installed.
auto current_threads = installed_threads_;
// Uninstall pass.
for (zx_koid_t thread_koid : current_threads) {
// The ProcessBreakpoint not longer tracks this. Remove.
if (thread_koids.count(thread_koid) == 0) {
zx_status_t res = Uninstall(thread_koid);
if (res != ZX_OK)
return res;
installed_threads_.erase(thread_koid);
}
}
// Install pass.
for (zx_koid_t thread_koid : thread_koids) {
// If it's already installed, ignore.
if (installed_threads_.count(thread_koid) > 0)
continue;
// If it's new, install.
if (installed_threads_.count(thread_koid) == 0) {
zx_status_t res = Install(thread_koid);
if (res != ZX_OK)
return res;
installed_threads_.insert(thread_koid);
}
}
return ZX_OK;
}
zx_status_t HardwareBreakpoint::Install(zx_koid_t thread_koid) {
uint64_t address = process_bp_->address();
// We need to install this new thread.
DebuggedThread* thread = process_bp_->process()->GetThread(thread_koid);
if (!thread) {
Warn(WarningType::kInstall, thread_koid, address, ZX_ERR_NOT_FOUND);
return ZX_ERR_NOT_FOUND;
}
DEBUG_LOG(Breakpoint) << "Installing HW breakpoint on thread " << thread_koid
<< " on address 0x" << std::hex << address;
// Thread needs to be suspended or on an exception (ZX-3772).
// We make a synchronous (blocking) call.
bool was_running = thread->Suspend(true);
// Do the actual installation.
auto& arch = arch::ArchProvider::Get();
zx_status_t status = arch.InstallHWBreakpoint(&thread->thread(), address);
// TODO: Do we want to remove all other locations when one fails?
if (status != ZX_OK) {
Warn(WarningType::kInstall, thread_koid, address, status);
return status;
}
// If the thread was running, we need to resume it.
if (was_running) {
debug_ipc::ResumeRequest resume;
resume.how = debug_ipc::ResumeRequest::How::kContinue;
thread->Resume(resume);
}
return ZX_OK;
}
zx_status_t HardwareBreakpoint::Uninstall() {
for (zx_koid_t thread_koid : installed_threads_) {
zx_status_t res = Uninstall(thread_koid);
if (res != ZX_OK)
return res;
}
return ZX_OK;
}
zx_status_t HardwareBreakpoint::Uninstall(zx_koid_t thread_koid) {
uint64_t address = process_bp_->address();
DebuggedThread* thread = process_bp_->process()->GetThread(thread_koid);
if (!thread) {
Warn(WarningType::kInstall, thread_koid, address, ZX_ERR_NOT_FOUND);
return ZX_ERR_NOT_FOUND;
}
DEBUG_LOG(Breakpoint) << "Removing HW breakpoint on thread " << thread_koid
<< " on address 0x" << std::hex << address;
// Thread needs to be suspended or on an exception (ZX-3772).
// We make a synchronous (blocking) call.
bool was_running = thread->Suspend(true);
auto& arch = arch::ArchProvider::Get();
zx_status_t status = arch.UninstallHWBreakpoint(&thread->thread(), address);
if (status != ZX_OK) {
Warn(WarningType::kInstall, thread_koid, address, status);
return status;
}
// If the thread was running, we need to resume it.
if (was_running) {
debug_ipc::ResumeRequest resume;
resume.how = debug_ipc::ResumeRequest::How::kContinue;
thread->Resume(resume);
}
return ZX_OK;
}
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->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 debug_agent