| // 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()->AsFunction()->GetFullRange(symbol_context)); | |
| ASSERT_EQ( | |
| kMiddleInline1FunctionRange, | |
| mock_frames[1]->GetLocation().symbol().Get()->AsFunction()->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 |