blob: 651cf963c630a4b7d2b1c54f71770b4397c3ec7b [file] [log] [blame]
// Copyright 2019 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/thread_controller.h"
#include "src/developer/debug/zxdb/client/inline_thread_controller_test.h"
#include "src/developer/debug/zxdb/client/mock_frame.h"
#include "src/developer/debug/zxdb/client/process.h"
#include "src/developer/debug/zxdb/client/stack.h"
#include "src/developer/debug/zxdb/client/thread.h"
#include "src/developer/debug/zxdb/symbols/function.h"
namespace zxdb {
namespace {
// Provide an implementation of the ThreadController's pure virtual functions so we can instantiate
// it.
class DummyThreadController : public ThreadController {
public:
DummyThreadController() = default;
~DummyThreadController() = default;
// ThreadController implementation.
void InitWithThread(Thread* thread, fit::callback<void(const Err&)> cb) override {
SetThread(thread);
cb(Err());
}
ContinueOp GetContinueOp() override { return ContinueOp::StepInstruction(); }
StopOp OnThreadStop(debug_ipc::ExceptionType stop_type,
const std::vector<fxl::WeakPtr<Breakpoint>>& hit_breakpoints) override {
return kStopDone;
}
const char* GetName() const override { return "Dummy"; }
// Make public for the test to use.
using ThreadController::InlineFrameIs;
using ThreadController::SetInlineFrameIfAmbiguous;
};
// Can't be called "ThreadControllerTest" because that's the base class for all
// thread-controller-related tests. We need the inline harness since we want to test the inline
// frame handling.
class ThreadControllerUnitTest : public InlineThreadControllerTest {};
} // namespace
TEST_F(ThreadControllerUnitTest, SetInlineFrameIfAmbiguous) {
// The mock stack has 6 entries, we want to test ambiguous inline frames so lop off the top two.
// This will leave the "middle" function with its two nested inlines starting at the same address
// as being the top.
auto mock_frames = GetStack();
mock_frames.erase(mock_frames.begin(), mock_frames.begin() + 2);
SymbolContext symbol_context = mock_frames[0]->GetLocation().symbol_context();
// Make the now-exposed top two frames have an ambiguous location (address at the beginning of
// their code range). This isn't the case in the default test data (inline frames not at the top
// of the stack can't be ambiguous because the physical call requires some instructions).
uint64_t address = kMiddleInline2FunctionRange.begin();
mock_frames[0]->SetAddress(address);
mock_frames[0]->set_is_ambiguous_inline(true);
mock_frames[1]->SetAddress(address);
mock_frames[1]->set_is_ambiguous_inline(true);
// The top two frames should have the same start address of the function range, and the same code
// address (this is testing that the harness has set things up the way we need). The physical
// frame below them (index 2) should also have the same code address.
ASSERT_EQ(
kMiddleInline2FunctionRange,
mock_frames[0]->GetLocation().symbol().Get()->As<Function>()->GetFullRange(symbol_context));
ASSERT_EQ(
kMiddleInline1FunctionRange,
mock_frames[1]->GetLocation().symbol().Get()->As<Function>()->GetFullRange(symbol_context));
ASSERT_EQ(kMiddleInline1FunctionRange.begin(), address);
ASSERT_EQ(kMiddleInline2FunctionRange.begin(), address);
// Set the stack.
InjectExceptionWithStack(process()->GetKoid(), thread()->GetKoid(),
debug_ipc::ExceptionType::kSingleStep,
MockFrameVectorToFrameVector(std::move(mock_frames)), true);
// Check the initial state of the inline frames on the stack. This is also pre-test validation.
// There should be two inline frames and neither should be hidden.
Stack& stack = thread()->GetStack();
ASSERT_EQ(2u, stack.GetAmbiguousInlineFrameCount());
ASSERT_EQ(0u, stack.hide_ambiguous_inline_frame_count());
FrameFingerprint inline_2_fingerprint = stack.GetFrameFingerprint(0);
FrameFingerprint inline_1_fingerprint = stack.GetFrameFingerprint(1);
FrameFingerprint physical_fingerprint = stack.GetFrameFingerprint(2);
// Supply a frame fingerprint that's not in the stack. This should be ignored.
DummyThreadController controller;
controller.InitWithThread(thread(), [](const Err&) {});
controller.SetInlineFrameIfAmbiguous(DummyThreadController::InlineFrameIs::kEqual,
FrameFingerprint(0x1234567, 0));
EXPECT_EQ(2u, stack.GetAmbiguousInlineFrameCount());
EXPECT_EQ(0u, stack.hide_ambiguous_inline_frame_count());
// Supply the top frame fingerprint, this should also do nothing since it's
// already the top one.
controller.SetInlineFrameIfAmbiguous(DummyThreadController::InlineFrameIs::kEqual,
inline_2_fingerprint);
EXPECT_EQ(2u, stack.GetAmbiguousInlineFrameCount());
EXPECT_EQ(0u, stack.hide_ambiguous_inline_frame_count());
// Set previous to the top frame, it should hide the top frame.
controller.SetInlineFrameIfAmbiguous(DummyThreadController::InlineFrameIs::kOneBefore,
inline_2_fingerprint);
EXPECT_EQ(1u, stack.hide_ambiguous_inline_frame_count());
// The inline frame 1 fingerprint should hide the top inline frame.
controller.SetInlineFrameIfAmbiguous(DummyThreadController::InlineFrameIs::kEqual,
inline_1_fingerprint);
EXPECT_EQ(2u, stack.GetAmbiguousInlineFrameCount());
EXPECT_EQ(1u, stack.hide_ambiguous_inline_frame_count());
// Set previous to inline frame 1, it should hide two frames.
controller.SetInlineFrameIfAmbiguous(DummyThreadController::InlineFrameIs::kOneBefore,
inline_1_fingerprint);
EXPECT_EQ(2u, stack.hide_ambiguous_inline_frame_count());
// Top physical frame should hide both inline frames.
controller.SetInlineFrameIfAmbiguous(DummyThreadController::InlineFrameIs::kEqual,
physical_fingerprint);
EXPECT_EQ(2u, stack.GetAmbiguousInlineFrameCount());
EXPECT_EQ(2u, stack.hide_ambiguous_inline_frame_count());
// Go back to the frame 1 fingerprint. This should work even though its currently hidden.
controller.SetInlineFrameIfAmbiguous(DummyThreadController::InlineFrameIs::kEqual,
inline_1_fingerprint);
EXPECT_EQ(1u, stack.hide_ambiguous_inline_frame_count());
// Set previous to the top physical frame should be invalid because it's not ambiguous (there's a
// physical frame in the way). As a result, the hide count should be unchanged from before.
controller.SetInlineFrameIfAmbiguous(DummyThreadController::InlineFrameIs::kOneBefore,
physical_fingerprint);
EXPECT_EQ(1u, stack.hide_ambiguous_inline_frame_count());
// Make a case that's not ambiguous because the current location isn't at the top of the beginning
// of an inline function range.
mock_frames = GetStack();
mock_frames.erase(mock_frames.begin(), mock_frames.begin() + 2);
mock_frames[0]->SetAddress(mock_frames[0]->GetAddress() + 4);
mock_frames[0]->set_is_ambiguous_inline(false);
InjectExceptionWithStack(process()->GetKoid(), thread()->GetKoid(),
debug_ipc::ExceptionType::kSingleStep,
MockFrameVectorToFrameVector(std::move(mock_frames)), true);
}
} // namespace zxdb