| // 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 "garnet/bin/zxdb/client/finish_thread_controller.h" |
| |
| #include "garnet/bin/zxdb/client/finish_physical_frame_thread_controller.h" |
| #include "garnet/bin/zxdb/client/frame.h" |
| #include "garnet/bin/zxdb/client/step_over_thread_controller.h" |
| #include "garnet/bin/zxdb/client/thread.h" |
| #include "garnet/bin/zxdb/common/err.h" |
| #include "garnet/bin/zxdb/symbols/function.h" |
| #include "garnet/bin/zxdb/symbols/location.h" |
| #include "lib/fxl/logging.h" |
| |
| namespace zxdb { |
| |
| FinishThreadController::FinishThreadController(Stack& stack, |
| size_t frame_to_finish) |
| : frame_to_finish_(frame_to_finish), weak_factory_(this) { |
| FXL_DCHECK(frame_to_finish < stack.size()); |
| |
| if (!stack[frame_to_finish]->IsInline()) { |
| // Finishing a physical frame, don't need to do anything except forward |
| // to the physical version. |
| finish_physical_controller_ = |
| std::make_unique<FinishPhysicalFrameThreadController>(stack, |
| frame_to_finish); |
| return; |
| } |
| |
| #ifndef NDEBUG |
| // Stash for validation later. |
| frame_ip_ = stack[frame_to_finish]->GetAddress(); |
| #endif |
| } |
| |
| FinishThreadController::~FinishThreadController() = default; |
| |
| FinishThreadController::StopOp FinishThreadController::OnThreadStop( |
| debug_ipc::NotifyException::Type stop_type, |
| const std::vector<fxl::WeakPtr<Breakpoint>>& hit_breakpoints) { |
| if (finish_physical_controller_) { |
| Log("Dispatching to physical frame finisher."); |
| if (finish_physical_controller_->OnThreadStop(stop_type, hit_breakpoints) == |
| kContinue) |
| return kContinue; // Still stepping out of the physical frame. |
| |
| // Physical frame controller said stop. |
| finish_physical_controller_.reset(); |
| |
| // May need to step out of some inline frames now. |
| if (!from_inline_frame_fingerprint_.is_valid()) { |
| Log("No inline frames to step out of, 'finish' is done."); |
| return kStop; // No inline frames to step out of, we're done. |
| } |
| } |
| |
| if (step_over_controller_) { |
| // Have an existing step controller for an inline frame. |
| Log("Dispatching to inline frame step over."); |
| if (step_over_controller_->OnThreadStop(stop_type, hit_breakpoints) == |
| kContinue) |
| return kContinue; |
| |
| // Current step controller said stop so it's done. |
| step_over_controller_.reset(); |
| } |
| |
| // See if there's an inline frame that needs stepping out of. |
| Stack& stack = thread()->GetStack(); |
| FrameFingerprint current_fingerprint = *stack.GetFrameFingerprint(0); |
| if (!FrameFingerprint::NewerOrEqual(current_fingerprint, |
| from_inline_frame_fingerprint_)) { |
| Log("Not in a newer frame than the target, stopping."); |
| return kStop; |
| } |
| |
| // The top frame is newer than the desired destination so we need to |
| // step out of it. If the stack hasn't changed in a surprising way all |
| // frames above the desired destination will be inline ones that we can |
| // step out of with the "step over" controller. |
| Log("Newer stack frame needs stepping out of."); |
| if (!CreateInlineStepOverController([](const Err&) {})) |
| return kStop; // Something unexpected happened. |
| return step_over_controller_->OnThreadStop(stop_type, hit_breakpoints); |
| } |
| |
| void FinishThreadController::InitWithThread( |
| Thread* thread, std::function<void(const Err&)> cb) { |
| set_thread(thread); |
| |
| if (finish_physical_controller_) { |
| // Simple case where only a physical frame is being finished. The physical |
| // frame controller can do everything. |
| finish_physical_controller_->InitWithThread(thread, std::move(cb)); |
| return; |
| } |
| |
| Stack& stack = thread->GetStack(); |
| |
| #ifndef NDEBUG |
| // The stack must not have changed from construction to this call. |
| FXL_DCHECK(stack.size() > frame_to_finish_); |
| FXL_DCHECK(stack[frame_to_finish_]->GetAddress() == frame_ip_); |
| #endif |
| |
| auto found_fingerprint = stack.GetFrameFingerprint(frame_to_finish_); |
| if (!found_fingerprint) { |
| // This can happen if the creator of this class requested that we finish |
| // the bottom-most stack frame available, without having all stack frames |
| // available. That's not allowed and any code doing that should be fixed. |
| FXL_NOTREACHED(); |
| cb( |
| Err("Trying to step out of an inline frame with insufficient context.\n" |
| "Please file a bug with a repro.")); |
| return; |
| } |
| from_inline_frame_fingerprint_ = *found_fingerprint; |
| |
| // Find the next physical frame above the one being stepped out of. |
| std::optional<size_t> found_physical_index; |
| for (int i = static_cast<int>(frame_to_finish_) - 1; i >= 0; i--) { |
| if (!stack[i]->IsInline()) { |
| found_physical_index = i; |
| break; |
| } |
| } |
| if (found_physical_index) { |
| // There is a physical frame above the one being stepped out of. Set up |
| // the physical frame stepper to get out of it. |
| finish_physical_controller_ = |
| std::make_unique<FinishPhysicalFrameThreadController>( |
| stack, *found_physical_index); |
| finish_physical_controller_->InitWithThread(thread, std::move(cb)); |
| return; |
| } |
| |
| // There is no physical frame above the one being stepped out of, go to |
| // inline stepping to get out of it. |
| CreateInlineStepOverController(std::move(cb)); |
| } |
| |
| ThreadController::ContinueOp FinishThreadController::GetContinueOp() { |
| if (finish_physical_controller_) |
| return finish_physical_controller_->GetContinueOp(); |
| return step_over_controller_->GetContinueOp(); |
| } |
| |
| bool FinishThreadController::CreateInlineStepOverController( |
| std::function<void(const Err&)> cb) { |
| Stack& stack = thread()->GetStack(); |
| if (!stack[0]->IsInline()) { |
| // The stack changed in an unexpected way and a newer physical frame |
| // appeared that we weren't expecting. For now, report stop since |
| // something weird is going on. If this happens in practice, the best |
| // thing to do is restart the step-out process with the physical frame |
| // step out, followed by any inline ones. |
| const char kMsg[] = |
| "Unexpected non-inline frame when stepping out, giving up."; |
| Log(kMsg); |
| cb(Err(kMsg)); |
| return false; |
| } |
| |
| const Location& location = stack[0]->GetLocation(); |
| const Function* func = location.symbol().Get()->AsFunction(); |
| if (!func) { |
| const char kMsg[] = "No function symbol for inline frame, giving up."; |
| Log(kMsg); |
| cb(Err(kMsg)); |
| return false; |
| } |
| |
| // Make a step over controller with the range of the inline function at |
| // the top of the stack. |
| Log("Creating a new step over controller to get out of inline frame."); |
| step_over_controller_ = std::make_unique<StepOverThreadController>( |
| func->GetAbsoluteCodeRanges(location.symbol_context())); |
| step_over_controller_->InitWithThread(thread(), std::move(cb)); |
| return true; |
| } |
| |
| } // namespace zxdb |