blob: 14910b379103e9fd2dbe0ef2d39dc5b7322a4961 [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/finish_thread_controller.h"
#include "gtest/gtest.h"
#include "src/developer/debug/zxdb/client/inline_thread_controller_test.h"
#include "src/developer/debug/zxdb/client/process.h"
#include "src/developer/debug/zxdb/client/thread.h"
#include "src/developer/debug/zxdb/client/thread_impl_test_support.h"
#include "src/developer/debug/zxdb/common/err.h"
namespace zxdb {
namespace {
class FinishThreadControllerTest : public InlineThreadControllerTest {};
} // namespace
// See also the FinishPhysicalFrameThreadController tests.
// Tests finishing a single inline frame. This finishes the top frame of the
// stack which is an inline function (see InlineThreadControllerTest for what
// the returned stack layout is).
TEST_F(FinishThreadControllerTest, FinishInline) {
auto mock_frames = GetStack();
InjectExceptionWithStack(process()->GetKoid(), thread()->GetKoid(),
debug_ipc::NotifyException::Type::kSingleStep,
MockFrameVectorToFrameVector(std::move(mock_frames)),
true);
// "Finish" from the top stack frame, which is an inline one.
auto finish_controller =
std::make_unique<FinishThreadController>(thread()->GetStack(), 0);
bool continued = false;
thread()->ContinueWith(std::move(finish_controller),
[&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());
// Do one step inside the inline function (add 4 to the address).
mock_frames = GetStack();
mock_frames[0]->SetAddress(mock_frames[0]->GetAddress() + 4);
InjectExceptionWithStack(process()->GetKoid(), thread()->GetKoid(),
debug_ipc::NotifyException::Type::kSingleStep,
MockFrameVectorToFrameVector(std::move(mock_frames)),
true);
// That's still inside the frame's range, so it should continue.
EXPECT_EQ(1, mock_remote_api()->GetAndResetResumeCount());
// Set exception at the first instruction after the inline frame.
mock_frames = GetStack();
uint64_t after_inline = kTopInlineFunctionRange.end();
mock_frames.erase(mock_frames.begin()); // Remove the inline function.
mock_frames[0]->SetAddress(after_inline);
InjectExceptionWithStack(process()->GetKoid(), thread()->GetKoid(),
debug_ipc::NotifyException::Type::kSingleStep,
MockFrameVectorToFrameVector(std::move(mock_frames)),
true);
// Should not have resumed.
EXPECT_EQ(0, mock_remote_api()->GetAndResetResumeCount());
EXPECT_EQ(debug_ipc::ThreadRecord::State::kBlocked, thread()->GetState());
}
// Finishes multiple frames, consisting of one physical frame finish followed
// by two inline frame finishes. This finishes to frame 4 (see
// InlineThreadControllerTest) which is the "middle" physical frame. It
// requires doing a "finish" of the top physical frame, then stepping through
// both middle inline frames.
TEST_F(FinishThreadControllerTest, FinishPhysicalAndInline) {
auto mock_frames = GetStack();
uint64_t frame_2_ip = mock_frames[2]->GetAddress();
InjectExceptionWithStack(process()->GetKoid(), thread()->GetKoid(),
debug_ipc::NotifyException::Type::kSingleStep,
MockFrameVectorToFrameVector(std::move(mock_frames)),
true);
// "Finish" frame 3,
auto finish_controller =
std::make_unique<FinishThreadController>(thread()->GetStack(), 3);
bool continued = false;
thread()->ContinueWith(std::move(finish_controller),
[&continued](const Err& err) {
if (!err.has_error())
continued = true;
});
// That should have sent a resume + a breakpoint set at the frame 2 IP (this
// breakpoint is implementing the "finish" to step out of the frame 1
// physical frame).
EXPECT_EQ(1, mock_remote_api()->GetAndResetResumeCount());
EXPECT_EQ(0, mock_remote_api()->breakpoint_remove_count());
EXPECT_EQ(frame_2_ip, mock_remote_api()->last_breakpoint_address());
// Simulate a breakpoint hit of that breakpoint (breakpoint exceptions are
// "software").
debug_ipc::NotifyException exception;
exception.type = debug_ipc::NotifyException::Type::kSoftware;
exception.thread.process_koid = process()->GetKoid();
exception.thread.thread_koid = thread()->GetKoid();
exception.thread.state = debug_ipc::ThreadRecord::State::kBlocked;
exception.hit_breakpoints.emplace_back();
exception.hit_breakpoints[0].id =
mock_remote_api()->last_breakpoint_id();
// Create a stack now showing frame 2 as the top (new frame 0).
mock_frames = GetStack();
mock_frames.erase(mock_frames.begin(), mock_frames.begin() + 3);
InjectExceptionWithStack(
exception, MockFrameVectorToFrameVector(std::move(mock_frames)), true);
// The breakpoint should have been cleared and the thread should have been
// resumed.
EXPECT_EQ(1, mock_remote_api()->GetAndResetResumeCount());
EXPECT_EQ(1, mock_remote_api()->breakpoint_remove_count());
// Do another stop 4 bytes later in the inline frame 2 which should get
// continued.
mock_frames = GetStack();
mock_frames.erase(mock_frames.begin(), mock_frames.begin() + 3);
mock_frames[0]->SetAddress(mock_frames[0]->GetAddress() + 4);
InjectExceptionWithStack(process()->GetKoid(), thread()->GetKoid(),
debug_ipc::NotifyException::Type::kSingleStep,
MockFrameVectorToFrameVector(std::move(mock_frames)),
true);
EXPECT_EQ(1, mock_remote_api()->GetAndResetResumeCount());
// Stop in inline frame 1. This leaves inline frame 2 (right after its
// address range) but should still continue since we haven't reached the
// target.
mock_frames = GetStack();
mock_frames.erase(mock_frames.begin(), mock_frames.begin() + 4);
mock_frames[0]->SetAddress(kMiddleInline2FunctionRange.end());
InjectExceptionWithStack(process()->GetKoid(), thread()->GetKoid(),
debug_ipc::NotifyException::Type::kSingleStep,
MockFrameVectorToFrameVector(std::move(mock_frames)),
true);
EXPECT_EQ(1, mock_remote_api()->GetAndResetResumeCount());
// Stop in middle frame which is the target (right after the inline 1 range).
mock_frames = GetStack();
mock_frames.erase(mock_frames.begin(), mock_frames.begin() + 5);
mock_frames[0]->SetAddress(kMiddleInline1FunctionRange.end());
InjectExceptionWithStack(process()->GetKoid(), thread()->GetKoid(),
debug_ipc::NotifyException::Type::kSingleStep,
MockFrameVectorToFrameVector(std::move(mock_frames)),
true);
EXPECT_EQ(0, mock_remote_api()->GetAndResetResumeCount()); // Stopped.
}
} // namespace zxdb