| // 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, |
| std::function<void(const Err&)> cb) override { |
| set_thread(thread); |
| cb(Err()); |
| } |
| ContinueOp GetContinueOp() override { return ContinueOp::StepInstruction(); } |
| StopOp OnThreadStop( |
| debug_ipc::NotifyException::Type 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::NotifyException::Type::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::NotifyException::Type::kSingleStep, |
| MockFrameVectorToFrameVector(std::move(mock_frames)), |
| true); |
| } |
| |
| } // namespace zxdb |