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