blob: 0eafa2319755ea1ef64b55567fa1c98566318c39 [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/until_thread_controller.h"
#include <lib/syslog/cpp/macros.h>
#include <sstream>
#include "src/developer/debug/zxdb/client/breakpoint.h"
#include "src/developer/debug/zxdb/client/breakpoint_location.h"
#include "src/developer/debug/zxdb/client/breakpoint_settings.h"
#include "src/developer/debug/zxdb/client/frame.h"
#include "src/developer/debug/zxdb/client/process.h"
#include "src/developer/debug/zxdb/client/session.h"
#include "src/developer/debug/zxdb/client/system.h"
#include "src/developer/debug/zxdb/client/thread.h"
#include "src/developer/debug/zxdb/common/string_util.h"
#include "src/developer/debug/zxdb/symbols/input_location.h"
namespace zxdb {
UntilThreadController::UntilThreadController(std::vector<InputLocation> locations)
: ThreadController(), locations_(std::move(locations)), weak_factory_(this) {}
UntilThreadController::UntilThreadController(std::vector<InputLocation> locations,
FrameFingerprint newest_frame, FrameComparison cmp)
: ThreadController(),
locations_(std::move(locations)),
threshold_frame_(newest_frame),
comparison_(cmp),
weak_factory_(this) {}
UntilThreadController::~UntilThreadController() {
if (breakpoint_)
GetSystem()->DeleteBreakpoint(breakpoint_.get());
}
void UntilThreadController::InitWithThread(Thread* thread, fit::callback<void(const Err&)> cb) {
SetThread(thread);
BreakpointSettings settings;
settings.scope = ExecutionScope(thread);
settings.locations = std::move(locations_);
// Frame-tied triggers can't be one-shot because we need to check the stack every time it
// triggers. In the non-frame case the one-shot breakpoint will be slightly more efficient.
settings.one_shot = !threshold_frame_.is_valid();
breakpoint_ = GetSystem()->CreateNewInternalBreakpoint()->GetWeakPtr();
breakpoint_->SetSettings(settings, [weak_controller = weak_factory_.GetWeakPtr(),
cb = std::move(cb)](const Err& err) mutable {
if (weak_controller)
weak_controller->OnBreakpointSetComplete(err, std::move(cb));
});
}
void UntilThreadController::OnBreakpointSetComplete(const Err& err,
fit::callback<void(const Err&)> cb) {
if (err.has_error())
return cb(err); // Error updating breakpoint.
// Validate that the breakpoint matched some locations that look reasonable. Note that this
// information is available synchronously after Breakpoint::SetSettings() since it's just doing
// symbol matching, but we defer checking to here to simplify error checking and issuing the
// callback from one place.
const std::vector<const BreakpointLocation*>& locs = GetLocations();
if (locs.empty()) {
// Setting the breakpoint may have resolved to no locations and the breakpoint is now pending.
// For "until" this is not good because if the user does "until SomethingNonexistant" they would
// like to see the error rather than have the thread transparently continue without stopping.
cb(Err("Destination to run until matched no location."));
} else {
if (enable_debug_logging()) {
// Log the addresses we resolved.
std::ostringstream log;
log << "Matched addr(s): ";
for (size_t i = 0; i < locs.size(); i++) {
log << to_hex_string(locs[i]->GetLocation().address());
if (i + 1 < locs.size())
log << ", ";
}
std::string log_str = log.str();
Log(log_str.c_str());
}
cb(Err());
}
}
ThreadController::ContinueOp UntilThreadController::GetContinueOp() {
// Stopping the thread is done via a breakpoint, so the thread can always be resumed with no
// qualifications.
return ContinueOp::Continue();
}
ThreadController::StopOp UntilThreadController::OnThreadStop(
debug_ipc::ExceptionType stop_type,
const std::vector<fxl::WeakPtr<Breakpoint>>& hit_breakpoints) {
if (stop_type == debug_ipc::ExceptionType::kNone) {
// A "none" exception type will be passed in to us to see if we apply to the current location
// when being initialized in nested controller context.
//
// Since the "until" controller only triggers on breakpoints, we always want to continue in
// these cases. Even if the breakpoint is at the current address, continuing at this address
// will hit it again.
return kContinue;
}
// Other controllers such as the StepOverRangeThreadController can use this as a sub-controller.
// If the controllers don't care about breakpoint set failures, they may start using the thread
// right away without waiting for the callback in InitWithThread() to asynchronously complete
// (indicating the breakpoint was set successful).
//
// This is generally fine, we just need to be careful not to do anything in OnBreakpointSet() that
// the code in this function depends on.
if (!breakpoint_) {
// Our internal breakpoint shouldn't be deleted out from under ourselves.
FX_NOTREACHED();
return kUnexpected;
}
// Only care about stops if one of the breakpoints hit was ours. Don't check the stop_type since
// as long as the breakpoint was hit, we don't care how the program got there (it could have
// single-stepped to the breakpoint).
Breakpoint* our_breakpoint = breakpoint_.get();
bool is_our_breakpoint = false;
for (auto& hit : hit_breakpoints) {
if (hit && hit.get() == our_breakpoint) {
is_our_breakpoint = true;
break;
}
}
if (!is_our_breakpoint) {
Log("Not our breakpoint.");
return kUnexpected;
}
if (!threshold_frame_.is_valid()) {
Log("No frame check required, we're done.");
return kStopDone;
}
const Stack& stack = thread()->GetStack();
if (stack.empty()) {
FX_NOTREACHED(); // Should always have a current frame on stop.
return kUnexpected;
}
// If inline frames are ambiguous and the one we want is one of the ambiguous ones, use it.
if (comparison_ == kRunUntilEqualOrOlderFrame)
SetInlineFrameIfAmbiguous(InlineFrameIs::kEqual, threshold_frame_);
else
SetInlineFrameIfAmbiguous(InlineFrameIs::kOneBefore, threshold_frame_);
// Check frames.
FrameFingerprint current_frame = stack.GetFrameFingerprint(0);
if (FrameFingerprint::Newer(current_frame, threshold_frame_)) {
Log("In newer frame, ignoring.");
return kContinue;
}
if (comparison_ == kRunUntilOlderFrame && current_frame == threshold_frame_) {
// In kRunUntilOlderFrame mode, the threshold frame fingerprint itself is one that should
// continue running.
Log("In threshold frame, ignoring.");
return kContinue;
}
Log("Found target frame (or older), 'until' operation complete.");
return kStopDone;
}
std::vector<const BreakpointLocation*> UntilThreadController::GetLocations() const {
if (!breakpoint_) {
FX_NOTREACHED();
return {};
}
return const_cast<const Breakpoint*>(breakpoint_.get())->GetLocations();
}
System* UntilThreadController::GetSystem() { return &thread()->session()->system(); }
Target* UntilThreadController::GetTarget() { return thread()->GetProcess()->GetTarget(); }
} // namespace zxdb