| // 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 "src/developer/debug/zxdb/client/breakpoint.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/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); | |
| if (breakpoint_->GetLocations().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.")); | |
| } | |
| } | |
| 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) { | |
| // 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 = true; | |
| 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."); | |
| 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)."); | |
| return kStopDone; | |
| } | |
| System* UntilThreadController::GetSystem() { return &thread()->session()->system(); } | |
| Target* UntilThreadController::GetTarget() { return thread()->GetProcess()->GetTarget(); } | |
| } // namespace zxdb |