| // 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 |