blob: 07c1a012a9e80a017f5ad77d4058bb10c3cd0985 [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 "garnet/bin/zxdb/client/step_over_thread_controller.h"
#include "garnet/bin/zxdb/client/inline_thread_controller_test.h"
#include "garnet/bin/zxdb/client/process.h"
#include "garnet/bin/zxdb/client/thread.h"
#include "garnet/bin/zxdb/common/address_ranges.h"
#include "garnet/bin/zxdb/common/err.h"
#include "garnet/bin/zxdb/symbols/function.h"
#include "garnet/lib/debug_ipc/protocol.h"
namespace zxdb {
class StepOverThreadControllerTest : public InlineThreadControllerTest {};
// Tests a "step over" including a function call that's skipped. This generates
// an internal "finish" command to get out of the subroutine.
TEST_F(StepOverThreadControllerTest, InOutFinish) {
// Step as long as we're in this range. Using the "code range" for stepping
// allows us to avoid dependencies on the symbol subsystem.
constexpr uint64_t kBeginAddr = 0x1000;
constexpr uint64_t kEndAddr = 0x1010;
// This is the frame we're starting stepping from.
constexpr uint64_t kInitialBP = 0x2000;
constexpr uint64_t kInitialSP = kInitialBP - 8;
// The previous frame on the call stack.
constexpr uint64_t kPrevSP = kInitialBP + 16;
constexpr uint64_t kPrevBP = kPrevSP + 8;
// Set up the thread to be stopped at the beginning of our range.
debug_ipc::NotifyException exception;
exception.process_koid = process()->GetKoid();
exception.type = debug_ipc::NotifyException::Type::kSingleStep;
exception.thread.koid = thread()->GetKoid();
exception.thread.state = debug_ipc::ThreadRecord::State::kBlocked;
exception.thread.frames.resize(2);
exception.thread.frames[0].ip = kBeginAddr;
exception.thread.frames[0].sp = kInitialSP;
exception.thread.frames[0].bp = kInitialBP;
exception.thread.frames[1].ip = kBeginAddr - 0x100;
exception.thread.frames[1].sp = kPrevSP;
exception.thread.frames[1].bp = kPrevBP;
InjectException(exception);
// Continue the thread with the controller stepping in range.
auto step_over = std::make_unique<StepOverThreadController>(
AddressRanges(AddressRange(kBeginAddr, kEndAddr)));
bool continued = false;
thread()->ContinueWith(std::move(step_over), [&continued](const Err& err) {
if (!err.has_error())
continued = true;
});
// It should have been able to step without doing any further async work.
EXPECT_TRUE(continued);
EXPECT_EQ(1, mock_remote_api()->GetAndResetResumeCount());
// Issue a stop in the range. This should get transparently resumed. In
// general the backend won't issue this since it will continue stepping in
// the given range, but it could, and we should resume anyway.
exception.thread.frames[0].ip += 4;
InjectException(exception);
EXPECT_EQ(1, mock_remote_api()->GetAndResetResumeCount());
// Issue a stop in a new stack frame. The base pointer will be the same as
// the outer function since the prologue hasn't executed yet. The previous
// frame's IP will be the return address.
constexpr uint64_t kInnerSP = kInitialSP - 8;
exception.thread.frames.emplace(exception.thread.frames.begin());
exception.thread.frames[0].ip = 0x3000;
exception.thread.frames[0].sp = kInnerSP;
exception.thread.frames[0].bp = kInitialSP;
exception.thread.frames[1].ip += 4;
InjectException(exception);
// That should have sent a resume + a breakpoint set at the frame 1 IP (this
// breakpoint is implementing the "finish" to step out of the function call).
EXPECT_EQ(1, mock_remote_api()->GetAndResetResumeCount());
EXPECT_EQ(0, mock_remote_api()->breakpoint_remove_count());
EXPECT_EQ(exception.thread.frames[1].ip,
mock_remote_api()->last_breakpoint_address());
// Send a breakpoint completion notification at the previous stack frame.
exception.thread.frames.erase(
exception.thread.frames.begin()); // Erase topmost.
// Breakpoint exceptions are "software".
exception.type = debug_ipc::NotifyException::Type::kSoftware;
exception.hit_breakpoints.resize(1);
exception.hit_breakpoints[0].breakpoint_id =
mock_remote_api()->last_breakpoint_id();
exception.hit_breakpoints[0].hit_count = 1;
InjectException(exception);
// That should have removed the breakpoint and resumed the thread.
EXPECT_EQ(1, mock_remote_api()->breakpoint_remove_count());
EXPECT_EQ(1, mock_remote_api()->GetAndResetResumeCount());
// Last exception is outside the range (the end is non-inclusive).
exception.hit_breakpoints.clear();
exception.thread.frames[0].ip = kEndAddr;
InjectException(exception);
// Should have stopped.
EXPECT_EQ(0, mock_remote_api()->GetAndResetResumeCount()); // Stopped.
EXPECT_EQ(debug_ipc::ThreadRecord::State::kBlocked, thread()->GetState());
}
// Tests "step over" stepping from before an inline function to the call of
// the inline function. This is tricky because that call is actually the
// first instruction of the inline function so needs special handling.
TEST_F(StepOverThreadControllerTest, Inline) {
// The GetStack() function returns a stack in an inline function. Modify
// the initial state so it's right before the inline call (popping the
// inline frame off, exposing the physical frame right before the call).
auto mock_frames = GetStack();
mock_frames.erase(mock_frames.begin());
ASSERT_FALSE(mock_frames[0]->IsInline());
// Step in a range right before the inline call.
AddressRange step_range1(mock_frames[0]->GetAddress() - 4,
mock_frames[0]->GetAddress());
mock_frames[0]->SetAddress(step_range1.begin());
InjectExceptionWithStack(process()->GetKoid(), thread()->GetKoid(),
debug_ipc::NotifyException::Type::kSingleStep,
MockFrameVectorToFrameVector(std::move(mock_frames)),
true);
thread()->ContinueWith(
std::make_unique<StepOverThreadController>(AddressRanges(step_range1)),
[](const Err& err) {});
EXPECT_EQ(1, mock_remote_api()->GetAndResetResumeCount()); // Continued.
// Issue a stop at the beginning of the inline function. We provide the
// full stack (including the inline function) because that's where the
// address resolves to.
InjectExceptionWithStack(process()->GetKoid(), thread()->GetKoid(),
debug_ipc::NotifyException::Type::kSingleStep,
MockFrameVectorToFrameVector(GetStack()), true);
EXPECT_EQ(0, mock_remote_api()->GetAndResetResumeCount()); // Stopped.
// The "step over" controller should have reported "stop" and fixed up the
// stack to be at the call point of the inline function.
EXPECT_EQ(1u, thread()->GetStack().hide_top_inline_frame_count());
// Now step over the inline call.
thread()->ContinueWith(std::make_unique<StepOverThreadController>(
AddressRanges(kTopFunctionRange)),
[](const Err& err) {});
EXPECT_EQ(1, mock_remote_api()->GetAndResetResumeCount()); // Continued.
// Pretend that immediately following that inline call is another inline
// call. This is as if two inline functions are back-to-back in the source
// code, so "returning" from the first immediately pops you in the first
// instruction of the next.
// The function range for inline function immediately following the first.
AddressRange second_inline_range(kTopFunctionRange.end(),
kTopFunctionRange.end() + 4);
auto second_inline_func =
fxl::MakeRefCounted<Function>(Symbol::kTagInlinedSubroutine);
second_inline_func->set_assigned_name("Second");
second_inline_func->set_code_ranges(AddressRanges(second_inline_range));
Location second_inline_loc(
second_inline_range.begin(), FileLine("file.cc", 21), 0,
SymbolContext::ForRelativeAddresses(), LazySymbol(second_inline_func));
// Clear so we can tell in the next step that it was actually changed.
thread()->GetStack().SetHideTopInlineFrameCount(0);
// Replace the inline function at the top with our new one.
mock_frames = GetStack();
mock_frames.erase(mock_frames.begin()); // Erase old top inline function.
mock_frames.insert(
mock_frames.begin(),
std::make_unique<MockFrame>(
nullptr, nullptr,
debug_ipc::StackFrame(second_inline_range.begin(), kTopSP, kTopSP),
second_inline_loc, mock_frames[0].get()));
InjectExceptionWithStack(process()->GetKoid(), thread()->GetKoid(),
debug_ipc::NotifyException::Type::kSingleStep,
MockFrameVectorToFrameVector(std::move(mock_frames)),
true);
// The step controller should have fixed the stack to look like the call
// to the second inline.
EXPECT_EQ(1u, thread()->GetStack().hide_top_inline_frame_count());
}
} // namespace zxdb