blob: 546edc60fba15e9402b69e017ba35c0eec51b699 [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/zxdb/client/breakpoint_impl.h"
#include <inttypes.h>
#include <algorithm>
#include <map>
#include <sstream>
#include "src/developer/debug/shared/message_loop.h"
#include "src/developer/debug/shared/zx_status.h"
#include "src/developer/debug/zxdb/client/breakpoint_location_impl.h"
#include "src/developer/debug/zxdb/client/breakpoint_observer.h"
#include "src/developer/debug/zxdb/client/process.h"
#include "src/developer/debug/zxdb/client/remote_api.h"
#include "src/developer/debug/zxdb/client/session.h"
#include "src/developer/debug/zxdb/client/system.h"
#include "src/developer/debug/zxdb/client/target.h"
#include "src/developer/debug/zxdb/client/thread.h"
#include "src/developer/debug/zxdb/common/err.h"
#include "src/developer/debug/zxdb/expr/permissive_input_location.h"
#include "src/developer/debug/zxdb/symbols/loaded_module_symbols.h"
#include "src/developer/debug/zxdb/symbols/module_symbols.h"
#include "src/developer/debug/zxdb/symbols/process_symbols.h"
#include "src/developer/debug/zxdb/symbols/resolve_options.h"
#include "src/developer/debug/zxdb/symbols/target_symbols.h"
namespace zxdb {
namespace {
uint32_t next_breakpoint_id = 1;
debug_ipc::Stop SettingsStopToIpcStop(BreakpointSettings::StopMode mode) {
switch (mode) {
case BreakpointSettings::StopMode::kNone:
return debug_ipc::Stop::kNone;
case BreakpointSettings::StopMode::kThread:
return debug_ipc::Stop::kThread;
case BreakpointSettings::StopMode::kProcess:
return debug_ipc::Stop::kProcess;
case BreakpointSettings::StopMode::kAll:
return debug_ipc::Stop::kAll;
}
}
} // namespace
struct BreakpointImpl::ProcessRecord {
// Helper to return whether there are any enabled locations for this process.
bool HasEnabledLocation() const {
for (const auto& loc : locs) {
if (loc.second.IsEnabled())
return true;
}
return false;
}
// Helper to add a list of locations to the locs array. Returns true if anything was added (this
// makes the call site cleaner).
bool AddLocations(BreakpointImpl* bp, Process* process, const std::vector<Location>& locations) {
for (const auto& loc : locations) {
locs.emplace(std::piecewise_construct, std::forward_as_tuple(loc.address()),
std::forward_as_tuple(bp, process, loc.address()));
}
return !locations.empty();
}
// Set when we're registered as an observer for this process.
bool observing = false;
// All resolved locations indexed by address.
std::map<uint64_t, BreakpointLocationImpl> locs;
};
BreakpointImpl::BreakpointImpl(Session* session, bool is_internal)
: Breakpoint(session),
is_internal_(is_internal),
backend_id_(next_breakpoint_id++),
impl_weak_factory_(this) {
session->process_observers().AddObserver(this);
session->target_observers().AddObserver(this);
}
BreakpointImpl::~BreakpointImpl() {
if (backend_installed_ && settings_.enabled) {
// Breakpoint was installed and the process still exists.
settings_.enabled = false;
SendBackendRemove();
}
session()->target_observers().RemoveObserver(this);
session()->process_observers().RemoveObserver(this);
if (registered_as_thread_observer_)
session()->thread_observers().RemoveObserver(this);
}
BreakpointSettings BreakpointImpl::GetSettings() const { return settings_; }
void BreakpointImpl::SetSettings(const BreakpointSettings& settings, SetCallback cb) {
settings_ = settings;
bool changed = false;
for (Target* target : session()->system().GetTargets()) {
Process* process = target->GetProcess();
if (process && CouldApplyToProcess(process))
changed |= RegisterProcess(process);
}
// Add or remove thread notifications as required.
if (settings_.scope.thread() && !registered_as_thread_observer_) {
session()->thread_observers().AddObserver(this);
registered_as_thread_observer_ = true;
} else if (!settings_.scope.thread() && registered_as_thread_observer_) {
session()->thread_observers().RemoveObserver(this);
registered_as_thread_observer_ = false;
}
SyncBackend(std::move(cb));
if (changed && !IsInternal()) {
for (auto& observer : session()->breakpoint_observers())
observer.OnBreakpointMatched(this, true);
}
}
bool BreakpointImpl::IsInternal() const { return is_internal_; }
std::vector<const BreakpointLocation*> BreakpointImpl::GetLocations() const {
std::vector<const BreakpointLocation*> result;
for (auto& proc : procs_) {
for (auto& pair : proc.second.locs)
result.push_back(&pair.second);
}
return result;
}
std::vector<BreakpointLocation*> BreakpointImpl::GetLocations() {
std::vector<BreakpointLocation*> result;
for (auto& proc : procs_) {
for (auto& pair : proc.second.locs)
result.push_back(&pair.second);
}
return result;
}
debug_ipc::BreakpointStats BreakpointImpl::GetStats() { return stats_; }
void BreakpointImpl::UpdateStats(const debug_ipc::BreakpointStats& stats) { stats_ = stats; }
void BreakpointImpl::BackendBreakpointRemoved() { backend_installed_ = false; }
void BreakpointImpl::WillDestroyTarget(Target* target) {
if (target == settings_.scope.target()) {
// As with threads going away, when the target goes away for a target-scoped breakpoint, convert
// to a disabled system-wide breakpoint.
settings_.scope = ExecutionScope();
settings_.enabled = false;
}
}
void BreakpointImpl::DidCreateProcess(Process* process, bool autoattached, uint64_t timestamp) {
if (CouldApplyToProcess(process)) {
if (RegisterProcess(process)) {
SyncBackend();
if (!IsInternal()) {
for (auto& observer : session()->breakpoint_observers())
observer.OnBreakpointMatched(this, false);
}
}
}
}
void BreakpointImpl::WillDestroyProcess(Process* process, ProcessObserver::DestroyReason,
int exit_code, uint64_t timestamp) {
auto found = procs_.find(process);
if (found == procs_.end())
return;
// Only need to update the backend if there was an enabled address associated with this process.
bool send_update = found->second.HasEnabledLocation();
// When the process exits, disable breakpoints that are entirely address-based since the addresses
// will normally change when a process is loaded.
if (AllLocationsAddresses())
settings_.enabled = false;
procs_.erase(found);
// Needs to be done after the ProcessRecord is removed.
if (send_update)
SyncBackend();
}
void BreakpointImpl::DidLoadModuleSymbols(Process* process, LoadedModuleSymbols* module) {
if (!CouldApplyToProcess(process))
return; // Irrelevant process.
FindNameContext find_context(process->GetSymbols());
ResolveOptions options = GetResolveOptions();
bool needs_sync = false;
for (const auto& loc : ExpandPermissiveInputLocationNames(find_context, settings_.locations)) {
needs_sync |=
procs_[process].AddLocations(this, process, module->ResolveInputLocation(loc, options));
}
if (needs_sync) {
SyncBackend();
if (!IsInternal()) {
for (auto& observer : session()->breakpoint_observers())
observer.OnBreakpointMatched(this, false);
}
}
}
void BreakpointImpl::WillUnloadModuleSymbols(Process* process, LoadedModuleSymbols* module) {
// TODO(bug 61248) need to get the address range of this module and then remove all breakpoints in
// that range.
}
void BreakpointImpl::WillDestroyThread(Thread* thread) {
if (settings_.scope.thread() == thread) {
// When the thread is destroyed that the breakpoint is associated with, disable the breakpoint
// and convert to a target-scoped breakpoint. This will preserve its state without us having to
// maintain some "defunct thread" association. The user can associate it with a new thread and
// re-enable as desired.
settings_.scope = ExecutionScope(thread->GetProcess()->GetTarget());
settings_.enabled = false;
// Don't need more thread notifications.
FX_DCHECK(registered_as_thread_observer_);
session()->thread_observers().RemoveObserver(this);
registered_as_thread_observer_ = false;
}
}
void BreakpointImpl::SyncBackend(SetCallback cb) {
bool has_locations = HasEnabledLocation();
if (backend_installed_ && !has_locations) {
SendBackendRemove(std::move(cb));
} else if (has_locations) {
SendBackendAddOrChange(std::move(cb));
} else if (cb) {
// The backend doesn't know about it and we don't require anything, but we still need to issue
// the callback (non-reentrantly).
debug::MessageLoop::Current()->PostTask(FROM_HERE,
[cb = std::move(cb)]() mutable { cb(Err()); });
}
}
void BreakpointImpl::SendBackendAddOrChange(SetCallback cb) {
backend_installed_ = true;
debug_ipc::AddOrChangeBreakpointRequest request;
request.breakpoint.id = backend_id_;
request.breakpoint.type = settings_.type;
request.breakpoint.name = settings_.name;
request.breakpoint.stop = SettingsStopToIpcStop(settings_.stop_mode);
request.breakpoint.one_shot = settings_.one_shot;
request.breakpoint.has_automation = settings_.has_automation;
request.breakpoint.instructions = settings_.instructions;
for (const auto& proc : procs_) {
for (const auto& pair : proc.second.locs) {
if (!pair.second.IsEnabled())
continue;
debug_ipc::ProcessBreakpointSettings addition;
addition.id.process = proc.first->GetKoid();
if (settings_.scope.type() == ExecutionScope::kThread) {
if (Thread* thread = settings_.scope.thread())
addition.id.thread = thread->GetKoid();
}
if (BreakpointSettings::TypeHasSize(settings_.type)) {
uint64_t address = pair.second.address();
addition.address_range = {address, address + settings_.byte_size};
} else {
addition.address = pair.second.address();
}
request.breakpoint.locations.push_back(addition);
}
}
session()->remote_api()->AddOrChangeBreakpoint(
request, [breakpoint = impl_weak_factory_.GetWeakPtr(), cb = std::move(cb)](
const Err& err, debug_ipc::AddOrChangeBreakpointReply reply) mutable {
if (breakpoint) {
breakpoint->OnAddOrChangeComplete(err, std::move(reply), std::move(cb));
} else if (cb) {
cb(Err("Breakpoint deleted."));
}
});
}
void BreakpointImpl::SendBackendRemove(SetCallback cb) {
debug_ipc::RemoveBreakpointRequest request;
request.breakpoint_id = backend_id_;
session()->remote_api()->RemoveBreakpoint(
request, [breakpoint = impl_weak_factory_.GetWeakPtr(), cb = std::move(cb)](
const Err& err, debug_ipc::RemoveBreakpointReply reply) mutable {
if (breakpoint) {
breakpoint->OnRemoveComplete(err, std::move(reply), std::move(cb));
} else if (cb) {
cb(Err("Breakpoint deleted."));
}
});
backend_installed_ = false;
}
void BreakpointImpl::OnAddOrChangeComplete(const Err& input_err,
debug_ipc::AddOrChangeBreakpointReply reply,
SetCallback cb) {
// Map transport errors and remote errors to a single error.
Err err = input_err;
if (err.ok())
err = Err(reply.status);
if (err.has_error()) {
// Provide a better explanation for some common failures.
if (err.type() == ErrType::kNoResources) {
err = Err(err.type(),
"Could not set the breakpoint.\n\n"
"Is this a hardware breakpoint? Check \"sys-info\" to verify the number\n"
"available within the system.");
} else if (err.type() == ErrType::kNotSupported) {
err = Err(err.type(),
"Could not set the breakpoint.\n\n"
"This kernel command-line flag \"kernel.enable-debugging-syscalls\" is\n"
"likely not set.");
}
}
if (cb) {
cb(err);
} else if (err.has_error()) {
// There was no callback given, issue the global notification to show the error.
for (auto& observer : session()->breakpoint_observers())
observer.OnBreakpointUpdateFailure(this, err);
}
}
void BreakpointImpl::OnRemoveComplete(const Err& err, debug_ipc::RemoveBreakpointReply reply,
SetCallback cb) {
if (cb) {
cb(err);
} else if (err.has_error()) {
// There was no callback given, issue the global notification to show the error.
for (auto& observer : session()->breakpoint_observers())
observer.OnBreakpointUpdateFailure(this, err);
}
}
void BreakpointImpl::DidChangeLocation() { SyncBackend(); }
bool BreakpointImpl::CouldApplyToProcess(Process* process) const {
// When applied to all processes, we need all notifications.
if (settings_.scope.type() == ExecutionScope::kSystem)
return true;
// Target- and thread-specific breakpoints only watch their process.
return settings_.scope.target() == process->GetTarget();
}
bool BreakpointImpl::HasEnabledLocation() const {
if (!settings_.enabled)
return false;
for (const auto& proc : procs_) {
if (proc.second.HasEnabledLocation())
return true;
}
return false;
}
bool BreakpointImpl::RegisterProcess(Process* process) {
// Clear existing locations for this process.
ProcessRecord& record = procs_[process];
bool changed = !record.locs.empty();
record.locs.clear();
// Resolve addresses.
ResolveOptions options = GetResolveOptions();
FindNameContext find_context(process->GetSymbols());
changed |=
record.AddLocations(this, process,
ResolvePermissiveInputLocations(process->GetSymbols(), options,
find_context, settings_.locations));
return changed;
}
ResolveOptions BreakpointImpl::GetResolveOptions() const {
ResolveOptions options;
// We don't need the result to be symbolized unless required by skip_function_prologue.
if (AllLocationsAddresses()) {
// Only need addresses. Don't try to skip function prologues when the user gives an address or
// the address might move.
options.symbolize = false;
options.skip_function_prologue = false;
} else {
// When breaking on symbols or lines, skip function prologues so the function parameters
// can be displayed properly (they're not always correct in the prologue) as well as backtraces
// (on ARM, the link register is saved in the prologue so things may look funny before that).
// Function prologues require symbolization so we ask for both.
//
// TODO(bug 45309) we will need an option to control this like other debuggers. LLDB has a
// per-breakpoint setting and a global default preference. In GDB you can do "break *Foo" to
// skip the prologue.
options.symbolize = true;
options.skip_function_prologue = true;
}
return options;
}
bool BreakpointImpl::AllLocationsAddresses() const {
return !settings_.locations.empty() &&
std::all_of(settings_.locations.begin(), settings_.locations.end(),
[](const auto& loc) { return loc.type == InputLocation::Type::kAddress; });
}
} // namespace zxdb