blob: 5ade3791bf54eaf8adb95dba0ab08192fadb98bc [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/step_into_thread_controller.h"
#include <inttypes.h>
#include "src/developer/debug/zxdb/client/finish_thread_controller.h"
#include "src/developer/debug/zxdb/client/frame.h"
#include "src/developer/debug/zxdb/client/process.h"
#include "src/developer/debug/zxdb/client/step_over_thread_controller.h"
#include "src/developer/debug/zxdb/client/step_thread_controller.h"
#include "src/developer/debug/zxdb/client/thread.h"
#include "src/developer/debug/zxdb/common/err.h"
#include "src/developer/debug/zxdb/symbols/input_location.h"
#include "src/developer/debug/zxdb/symbols/line_details.h"
#include "src/developer/debug/zxdb/symbols/location.h"
#include "src/developer/debug/zxdb/symbols/process_symbols.h"
#include "src/developer/debug/zxdb/symbols/resolve_options.h"
namespace zxdb {
// These constructors all pass the function_return callback directly into the step_into_ controller.
// That would only be issued if we end up stepping *out*, which means there's no prologue and we
// wouldn't encounter a return at any other time.
StepIntoThreadController::StepIntoThreadController(StepMode mode,
FunctionReturnCallback function_return)
: step_into_(std::make_unique<StepThreadController>(mode, std::move(function_return))) {}
StepIntoThreadController::StepIntoThreadController(const FileLine& line,
FunctionReturnCallback function_return)
: step_into_(std::make_unique<StepThreadController>(line, std::move(function_return))) {}
StepIntoThreadController::StepIntoThreadController(AddressRanges ranges,
FunctionReturnCallback function_return)
: step_into_(std::make_unique<StepThreadController>(ranges, std::move(function_return))) {}
StepIntoThreadController::~StepIntoThreadController() = default;
void StepIntoThreadController::InitWithThread(Thread* thread, fit::callback<void(const Err&)> cb) {
SetThread(thread);
if (thread->GetStack().empty()) {
cb(Err("Can't step, no frames."));
return;
}
original_frame_fingerprint_ = thread->GetStack().GetFrameFingerprint(0);
step_into_->InitWithThread(thread, std::move(cb));
}
ThreadController::ContinueOp StepIntoThreadController::GetContinueOp() {
if (skip_prologue_)
skip_prologue_->GetContinueOp();
return step_into_->GetContinueOp();
}
ThreadController::StopOp StepIntoThreadController::OnThreadStop(
debug_ipc::ExceptionType stop_type,
const std::vector<fxl::WeakPtr<Breakpoint>>& hit_breakpoints) {
// Once we're doing the skip_prologue operation, it runs until complete and then we're done.
if (skip_prologue_)
return skip_prologue_->OnThreadStop(stop_type, hit_breakpoints);
// Handle normal low-level "step into".
StopOp op = step_into_->OnThreadStop(stop_type, hit_breakpoints);
if (op != kStopDone)
return op;
if (!should_skip_prologue_)
return kStopDone; // Don't need to do anything on top of the normal step.
// If we get here the step controller thinks it's done. If we're not in a prologue now, we're
// done. Otherwise we need to step through the prologue.
// We can only be in a prologue if we've stepped into a new physical frame.
//
// This check is unnecessary as the symbol lookup below should handle all cases since stepping by
// line should never leave you in a function prologue that's not a new frame. But most of the time
// we're stepping in the same frame and a symbol lookup is relatively heavyweight. This is a nice
// filter before doing the full lookup.
const Stack& stack = thread()->GetStack();
if (stack[0]->IsInline())
return kStopDone; // Inline frames don't have prologues.
if (!FrameFingerprint::Newer(stack.GetFrameFingerprint(0), original_frame_fingerprint_))
return kStopDone; // Not in a newer frame, no prologue to skip.
if (stack.empty()) {
FX_NOTREACHED(); // Should always have a current frame on stop.
return kUnexpected;
}
uint64_t current_ip = stack[0]->GetAddress();
// Symbolize the current address and ask to skip the prologue. This will automatically adjust the
// resulting address to be after the prologue if there is one.
ResolveOptions resolve_options;
resolve_options.symbolize = true;
resolve_options.skip_function_prologue = true;
std::vector<Location> symbolized_locs =
thread()->GetProcess()->GetSymbols()->ResolveInputLocation(InputLocation(current_ip),
resolve_options);
FX_DCHECK(symbolized_locs.size() == 1); // Should always return one match.
const Location& after_prologue = symbolized_locs[0];
if (current_ip == after_prologue.address()) {
Log("Not in a function prologue, stopping.");
return kStopDone;
}
// Got to a prologue, now step to the end. This uses a "step over" controller since sometimes
// there can be function calls in the prologue itself. We want to automatically skip these.
// Normally they are bookkeeping functions (for example, asan injects "stack malloc" calls there)
// that the user does not want to stop at.
Log("Stepped to function prologue ending at 0x%" PRIx64 ". Going over it.",
after_prologue.address());
skip_prologue_ = std::make_unique<StepOverThreadController>(
AddressRanges(AddressRange(current_ip, after_prologue.address())));
// Init for this object is guaranteed synchronous so we don't have to wait for the callback.
skip_prologue_->InitWithThread(thread(), [](const Err&) {});
// Don't pass the exception type or breakpoints to the new controller. Depending on how we got
// here, the exception type may not match what the step controller expects. it just needs to know
// that execution stopped.
return skip_prologue_->OnThreadStop(debug_ipc::ExceptionType::kNone, {});
}
} // namespace zxdb