| // 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/step_into_thread_controller.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/symbols/input_location.h" |
| #include "src/developer/debug/zxdb/symbols/line_details.h" |
| |
| namespace zxdb { |
| |
| namespace { |
| |
| // This override of MockModuleSymbols allows us to respond with different addresses depending on |
| // whether prologue skipping was requested or not (the normal mock doesn't provide this level of |
| // control). |
| class StepIntoMockModuleSymbols : public MockModuleSymbols { |
| public: |
| // IP of beginning of function where the prologue will be queried. |
| static const uint64_t kNestedBegin; |
| // IP of first non-prologue instruction of the function above. |
| static const uint64_t kNestedPrologueEnd; |
| |
| // ModuleSymbols overrides. |
| std::vector<Location> ResolveInputLocation(const SymbolContext& symbol_context, |
| const InputLocation& input_location, |
| const ResolveOptions& options) const override { |
| if (input_location.type == InputLocation::Type::kAddress && |
| input_location.address == kNestedBegin) { |
| // This is the address in question. |
| if (options.skip_function_prologue) |
| return {Location(Location::State::kSymbolized, kNestedPrologueEnd)}; |
| return {Location(Location::State::kSymbolized, kNestedBegin)}; |
| } |
| return MockModuleSymbols::ResolveInputLocation(symbol_context, input_location, options); |
| } |
| |
| protected: |
| FRIEND_MAKE_REF_COUNTED(StepIntoMockModuleSymbols); |
| FRIEND_REF_COUNTED_THREAD_SAFE(StepIntoMockModuleSymbols); |
| |
| StepIntoMockModuleSymbols() : MockModuleSymbols("file.so") { |
| AddLineDetails( |
| InlineThreadControllerTest::kTopInlineFunctionRange.begin(), |
| LineDetails(InlineThreadControllerTest::kTopInlineFileLine, |
| {LineDetails::LineEntry(InlineThreadControllerTest::kTopInlineFunctionRange)})); |
| } |
| ~StepIntoMockModuleSymbols() override {} |
| }; |
| |
| const uint64_t StepIntoMockModuleSymbols::kNestedBegin = |
| InlineThreadControllerTest::kTopInlineFunctionRange.begin(); |
| const uint64_t StepIntoMockModuleSymbols::kNestedPrologueEnd = kNestedBegin + 4; |
| |
| class StepIntoThreadControllerTest : public InlineThreadControllerTest { |
| public: |
| void DoStepTest(bool skip_prologue) { |
| constexpr uint64_t kBeginAddr = kSymbolizedModuleAddress + 0x1000; |
| constexpr uint64_t kEndAddr = kSymbolizedModuleAddress + 0x1010; |
| |
| constexpr uint64_t kStackFramePrevious = 0x5010; |
| constexpr uint64_t kStackFrameInitial = 0x5000; |
| constexpr uint64_t kStackFrameNested = 0x4090; |
| constexpr uint64_t kStackFrameNestedPrologueCall = 0x4080; |
| |
| // Set up the thread to be stopped at the beginning of our range. |
| debug_ipc::NotifyException exception; |
| exception.type = debug_ipc::ExceptionType::kSingleStep; |
| exception.thread.id = {.process = process()->GetKoid(), .thread = thread()->GetKoid()}; |
| exception.thread.state = debug_ipc::ThreadRecord::State::kBlocked; |
| exception.thread.frames.emplace_back(kBeginAddr, kStackFrameInitial, kStackFrameInitial); |
| exception.thread.frames.emplace_back(kBeginAddr - 10, kStackFramePrevious, kStackFramePrevious); |
| InjectException(exception); |
| |
| // Start the "step into" over that range. |
| auto step_into = std::make_unique<StepIntoThreadController>( |
| AddressRanges(AddressRange(kBeginAddr, kEndAddr))); |
| bool continued = false; |
| step_into->set_should_skip_prologue(skip_prologue); |
| thread()->ContinueWith(std::move(step_into), [&continued](const Err& err) { |
| if (!err.has_error()) |
| continued = true; |
| }); |
| |
| // That should have resumed the thread. |
| EXPECT_TRUE(continued); |
| EXPECT_EQ(1, mock_remote_api()->GetAndResetResumeCount()); |
| |
| // Stop at the beginning of a new stack frame (this adds to the previous stack frame still in th |
| // exception record). |
| exception.thread.frames.emplace(exception.thread.frames.begin(), |
| StepIntoMockModuleSymbols::kNestedBegin, kStackFrameNested, |
| kStackFrameNested); |
| InjectException(exception); |
| |
| if (!skip_prologue) { |
| // When not skipping prologues, the thread should stop since we're in a new frame. |
| EXPECT_EQ(0, mock_remote_api()->GetAndResetResumeCount()); |
| return; |
| } |
| |
| // When skipping prologues, it should continue through the prologue. |
| EXPECT_EQ(1, mock_remote_api()->GetAndResetResumeCount()); |
| |
| // Test a function call from within the prologue. This corresponds to things like asan |
| // bookkeeping functions that should be skipped. Here we generate some random unsymbolized |
| // code address for the prologue call. |
| exception.thread.frames.emplace(exception.thread.frames.begin(), |
| kEndAddr + 0x8, // Call address, arbitrary. |
| kStackFrameNestedPrologueCall, kStackFrameNestedPrologueCall); |
| InjectException(exception); |
| EXPECT_EQ(1, mock_remote_api()->GetAndResetResumeCount()); // Skip prologue call. |
| |
| // Delete the nested prologue call from the stack. |
| exception.thread.frames.erase(exception.thread.frames.begin()); |
| |
| // Report a stop at the end of the prologue. This just updates the same stack frame still in the |
| // exception record. |
| exception.thread.frames.front().ip = StepIntoMockModuleSymbols::kNestedPrologueEnd; |
| InjectException(exception); |
| |
| // That should have stopped. |
| EXPECT_EQ(0, mock_remote_api()->GetAndResetResumeCount()); |
| } |
| |
| protected: |
| // ThreadControllerTest override: |
| fxl::RefPtr<MockModuleSymbols> MakeModuleSymbols() override { |
| return fxl::MakeRefCounted<StepIntoMockModuleSymbols>(); |
| } |
| }; |
| |
| } // namespace |
| |
| TEST_F(StepIntoThreadControllerTest, SkipPrologue) { DoStepTest(true); } |
| |
| TEST_F(StepIntoThreadControllerTest, WithPrologue) { DoStepTest(false); } |
| |
| // Inlines should never have prologues skipped. The prologue finder has a fallback that it will |
| // find a prologue even if one isn't explicitly noted to handle some GCC-generated code. If called |
| // on an inline routine, it will skip the first line. |
| TEST_F(StepIntoThreadControllerTest, Inline) { |
| // Recall the top frame from GetStack() is inline. |
| auto mock_frames = GetStack(); |
| |
| // Stepping into the 0th frame from the first. These are the source locations. |
| FileLine file_line = mock_frames[1]->GetLocation().file_line(); |
| |
| InjectExceptionWithStack(process()->GetKoid(), thread()->GetKoid(), |
| debug_ipc::ExceptionType::kSingleStep, |
| MockFrameVectorToFrameVector(std::move(mock_frames)), true); |
| |
| // Hide the inline frame at the top so we're about to step into it. |
| Stack& stack = thread()->GetStack(); |
| stack.SetHideAmbiguousInlineFrameCount(1); |
| |
| // Do the "step into". |
| auto step_into_controller = std::make_unique<StepIntoThreadController>(StepMode::kSourceLine); |
| bool continued = false; |
| thread()->ContinueWith(std::move(step_into_controller), [&continued](const Err& err) { |
| if (!err.has_error()) |
| continued = true; |
| }); |
| EXPECT_TRUE(continued); |
| |
| // That should have requested a synthetic exception which will be sent out asynchronously. |
| EXPECT_EQ(0, mock_remote_api()->GetAndResetResumeCount()); // Nothing yet. |
| loop().RunUntilNoTasks(); |
| |
| // The operation should have unhidden the inline stack frame rather than actually affecting the |
| // backend. |
| EXPECT_EQ(0, mock_remote_api()->GetAndResetResumeCount()); |
| EXPECT_EQ(0u, stack.hide_ambiguous_inline_frame_count()); |
| } |
| |
| // If the program is killed out from under us, we can get an exception with no stack. This should |
| // stop and not crash. |
| TEST_F(StepIntoThreadControllerTest, NoStack) { |
| InjectExceptionWithStack(process()->GetKoid(), thread()->GetKoid(), |
| debug_ipc::ExceptionType::kSingleStep, {}, true); |
| |
| auto step_into_controller = std::make_unique<StepIntoThreadController>(StepMode::kSourceLine); |
| std::optional<Err> result; |
| thread()->ContinueWith(std::move(step_into_controller), |
| [&result](const Err& err) { result = err; }); |
| |
| loop().RunUntilNoTasks(); |
| EXPECT_EQ(0, mock_remote_api()->GetAndResetResumeCount()); |
| |
| EXPECT_TRUE(result); |
| EXPECT_EQ("Can't step, no frames.", result->msg()); |
| } |
| |
| } // namespace zxdb |