| // 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/stack.h" | |
| #include <lib/syslog/cpp/macros.h> | |
| #include <map> | |
| #include <gtest/gtest.h> | |
| #include "llvm/BinaryFormat/Dwarf.h" | |
| #include "src/developer/debug/shared/message_loop.h" | |
| #include "src/developer/debug/zxdb/client/frame_fingerprint.h" | |
| #include "src/developer/debug/zxdb/client/mock_frame.h" | |
| #include "src/developer/debug/zxdb/client/mock_stack_delegate.h" | |
| #include "src/developer/debug/zxdb/common/err.h" | |
| #include "src/developer/debug/zxdb/common/test_with_loop.h" | |
| #include "src/developer/debug/zxdb/expr/eval_context.h" | |
| #include "src/developer/debug/zxdb/expr/expr.h" | |
| #include "src/developer/debug/zxdb/symbols/function.h" | |
| #include "src/developer/debug/zxdb/symbols/type_test_support.h" | |
| #include "src/developer/debug/zxdb/symbols/variable.h" | |
| namespace zxdb { | |
| namespace { | |
| class StackTest : public TestWithLoop {}; | |
| // Stack pointers used by MakeInlineStackFrames. | |
| constexpr uint64_t kTopSP = 0x2000; | |
| constexpr uint64_t kMiddleSP = 0x2020; | |
| constexpr uint64_t kBottomSP = 0x2040; | |
| // Returns a set of stack frames: | |
| // [0] = inline #2 from frame 2 | |
| // [1] = inline #1 from frame 2 | |
| // [2] = physical frame at kTopSP | |
| // [3] = inline from frame 4 | |
| // [4] = physical frame at kMiddleSP | |
| // [5] = physical frame at kBottomSP | |
| std::vector<std::unique_ptr<Frame>> MakeInlineStackFrames() { | |
| // Create three physical frames. | |
| Location top_location(Location::State::kSymbolized, 0x1000); | |
| Location middle_location(Location::State::kSymbolized, 0x1010); | |
| Location bottom_location(Location::State::kSymbolized, 0x1020); | |
| auto phys_top = std::make_unique<MockFrame>(nullptr, nullptr, top_location, kTopSP, kMiddleSP); | |
| auto phys_middle = | |
| std::make_unique<MockFrame>(nullptr, nullptr, middle_location, kMiddleSP, kBottomSP); | |
| auto phys_bottom = std::make_unique<MockFrame>(nullptr, nullptr, bottom_location, kBottomSP, 0); | |
| std::vector<std::unique_ptr<Frame>> frames; | |
| // Top frame has two inline functions expanded on top of it. This uses the same Location object | |
| // for simplicity, in real life these will be different. | |
| frames.push_back(std::make_unique<MockFrame>(nullptr, nullptr, top_location, kTopSP, kMiddleSP, | |
| std::vector<debug_ipc::Register>(), kTopSP, | |
| phys_top.get())); | |
| frames.push_back(std::make_unique<MockFrame>(nullptr, nullptr, top_location, kTopSP, kMiddleSP, | |
| std::vector<debug_ipc::Register>(), kTopSP, | |
| phys_top.get())); | |
| // Physical top frame below those. | |
| frames.push_back(std::move(phys_top)); | |
| // Middle frame has one inline function expanded on top of it. | |
| frames.push_back(std::make_unique<MockFrame>(nullptr, nullptr, middle_location, kMiddleSP, | |
| kBottomSP, std::vector<debug_ipc::Register>(), | |
| kMiddleSP, phys_middle.get())); | |
| frames.push_back(std::move(phys_middle)); | |
| // Bottom frame has no inline frame. | |
| frames.push_back(std::move(phys_bottom)); | |
| return frames; | |
| } | |
| } // namespace | |
| // IndexForFrame is trivial when there's no inline frame, but when there is, the returned index | |
| // must take this into account. | |
| TEST_F(StackTest, IndexForFrame) { | |
| MockStackDelegate delegate; | |
| Stack stack(&delegate); | |
| delegate.set_stack(&stack); | |
| // Set some stack frames with inline frames. Nothing should start out as hidden. | |
| auto frames = MakeInlineStackFrames(); | |
| // Make a function for the top stack frame. It needs this to get the ranges for the ambiguity | |
| // comuptation. | |
| auto func = fxl::MakeRefCounted<Function>(DwarfTag::kInlinedSubroutine); | |
| func->set_assigned_name("Inline"); | |
| // Must start exactly at kBottomAddr for the location to be ambiguous. | |
| uint64_t ambiguous_address = frames[0]->GetLocation().address(); | |
| func->set_code_ranges(AddressRanges(AddressRange(ambiguous_address, ambiguous_address + 8))); | |
| // Force the top frame to be ambiguous. For this it needs an inline function that starts at the | |
| // current address. | |
| Location ambig_loc(ambiguous_address, FileLine("file", 10), 0, | |
| SymbolContext::ForRelativeAddresses(), func); | |
| auto new_top_frame = std::make_unique<MockFrame>(nullptr, nullptr, ambig_loc, kTopSP, kMiddleSP, | |
| std::vector<debug_ipc::Register>(), kTopSP, | |
| frames[0]->GetPhysicalFrame()); | |
| new_top_frame->set_is_ambiguous_inline(true); | |
| frames[0] = std::move(new_top_frame); | |
| stack.SetFramesForTest(std::move(frames), true); | |
| ASSERT_EQ(1u, stack.GetAmbiguousInlineFrameCount()); | |
| EXPECT_EQ(0u, stack.hide_ambiguous_inline_frame_count()); | |
| // The operator[] and the IndexForFrame results should match. | |
| for (size_t i = 0; i < stack.size(); i++) | |
| EXPECT_EQ(i, stack.IndexForFrame(stack[i])) << i; | |
| // Hide some inline frames, the indices should still match. | |
| stack.SetHideAmbiguousInlineFrameCount(1); | |
| for (size_t i = 0; i < stack.size(); i++) | |
| EXPECT_EQ(i, stack.IndexForFrame(stack[i])) << i; | |
| } | |
| // Tests fingerprint computations involving inline frames. | |
| TEST_F(StackTest, InlineFingerprint) { | |
| MockStackDelegate delegate; | |
| Stack stack(&delegate); | |
| delegate.set_stack(&stack); | |
| stack.SetFramesForTest(MakeInlineStackFrames(), true); | |
| // The top frames (physical and inline) have the middle frame's SP as their fingerprint, along | |
| // with the inline count. | |
| EXPECT_EQ(FrameFingerprint(kMiddleSP, 2), stack.GetFrameFingerprint(0)); | |
| EXPECT_EQ(2u, stack.InlineDepthForIndex(0)); | |
| EXPECT_EQ(FrameFingerprint(kMiddleSP, 1), stack.GetFrameFingerprint(1)); | |
| EXPECT_EQ(1u, stack.InlineDepthForIndex(1)); | |
| EXPECT_EQ(FrameFingerprint(kMiddleSP, 0), stack.GetFrameFingerprint(2)); | |
| EXPECT_EQ(0u, stack.InlineDepthForIndex(2)); | |
| // Middle frames have the bottom frame's SP. | |
| EXPECT_EQ(FrameFingerprint(kBottomSP, 1), stack.GetFrameFingerprint(3)); | |
| EXPECT_EQ(1u, stack.InlineDepthForIndex(3)); | |
| EXPECT_EQ(FrameFingerprint(kBottomSP, 0), stack.GetFrameFingerprint(4)); | |
| EXPECT_EQ(0u, stack.InlineDepthForIndex(4)); | |
| // Bottom frame reports the 0 CFA. | |
| EXPECT_EQ(FrameFingerprint(0, 0), stack.GetFrameFingerprint(5)); | |
| EXPECT_EQ(0u, stack.InlineDepthForIndex(5)); | |
| } | |
| // Tests that stack frames inside inline functions are expanded so that the inline functions have | |
| // their own "inline" frames. | |
| // | |
| // This tests a bottom function calling an inline function which calls a top function. The tricky | |
| // part is the IP of the bottom frame is actually in a different inline function (the "ambiguous" | |
| // one) because the address in the bottom frame is immediately following the TopFunc() call and this | |
| // happens to fall in range of an inlined function. This should be omitted from the stack. | |
| // | |
| // void TopFunc() { | |
| // ... // <- top_line | |
| // } | |
| // | |
| // // Not actually on the stack but looks like it. | |
| // inline void bottom_ambig_inline_func() { | |
| // ... // <- inline_exec_line | |
| // } | |
| // | |
| // inline void bottom_inline_func() { | |
| // ... | |
| // TopFunc(); // Non-inline | |
| // bottom_ambig_inline_func(); // <- inline_ambig_call_line | |
| // } | |
| // | |
| // void bottom() { | |
| // ... | |
| // bottom_inline_func(); // <- inline_call_line | |
| // ... | |
| // } | |
| TEST_F(StackTest, InlineExpansion) { | |
| constexpr uint64_t kBottomAddr = 0x127365; // IP for bottom stack frame. | |
| constexpr uint64_t kTopAddr = 0x893746123; // IP for top stack frale. | |
| const char kFileName[] = "file.cc"; | |
| FileLine inline_ambig_call_line(kFileName, 5); | |
| FileLine inline_call_line(kFileName, 10); | |
| FileLine inline_exec_line(kFileName, 20); | |
| FileLine top_line(kFileName, 30); | |
| MockStackDelegate delegate; | |
| SymbolContext symbol_context = SymbolContext::ForRelativeAddresses(); | |
| // Non-inline location for the top stack frame. | |
| auto top_func = fxl::MakeRefCounted<Function>(DwarfTag::kSubprogram); | |
| top_func->set_assigned_name("Top"); | |
| Location top_location(kTopAddr, top_line, 0, symbol_context, top_func); | |
| delegate.AddLocation(top_location); | |
| // Bottom stack frame has a real function, an inline function, and an ambiguous inline location | |
| // (at the start of an inline range). | |
| auto bottom_ambig_inline_func = fxl::MakeRefCounted<Function>(DwarfTag::kInlinedSubroutine); | |
| bottom_ambig_inline_func->set_assigned_name("Inline"); | |
| // Must start exactly at kBottomAddr for the location to be ambiguous. | |
| bottom_ambig_inline_func->set_code_ranges( | |
| AddressRanges(AddressRange(kBottomAddr, kBottomAddr + 8))); | |
| bottom_ambig_inline_func->set_call_line(inline_ambig_call_line); | |
| auto bottom_inline_func = fxl::MakeRefCounted<Function>(DwarfTag::kInlinedSubroutine); | |
| bottom_inline_func->set_assigned_name("Inline"); | |
| // Must start before at kBottomAddr for the location to not be ambiguous. | |
| bottom_inline_func->set_code_ranges( | |
| AddressRanges(AddressRange(kBottomAddr - 8, kBottomAddr + 8))); | |
| bottom_inline_func->set_call_line(inline_call_line); | |
| auto bottom_func = fxl::MakeRefCounted<Function>(DwarfTag::kSubprogram); | |
| bottom_func->set_assigned_name("Bottom"); | |
| bottom_func->set_code_ranges(AddressRanges(AddressRange(kBottomAddr - 8, kBottomAddr + 16))); | |
| // Our containing functions don't have references to the contained functions so we don't have to | |
| // worry about a reference cycle here (hence "unsafe"). | |
| bottom_ambig_inline_func->set_containing_block( | |
| UncachedLazySymbol::MakeUnsafe(bottom_inline_func)); | |
| bottom_inline_func->set_containing_block(UncachedLazySymbol::MakeUnsafe(bottom_func)); | |
| // The location returned by the symbol function will have the file/line inside the inline | |
| // function. | |
| Location bottom_location(kBottomAddr, inline_exec_line, 0, symbol_context, | |
| bottom_ambig_inline_func); | |
| delegate.AddLocation(bottom_location); | |
| Stack stack(&delegate); | |
| delegate.set_stack(&stack); | |
| // Send IPs that will map to the bottom and top addresses. | |
| constexpr uint64_t kTopSP = 0x100; | |
| constexpr uint64_t kBottomSP = 0x200; | |
| stack.SetFrames(debug_ipc::ThreadRecord::StackAmount::kFull, | |
| {debug_ipc::StackFrame(kTopAddr, kTopSP, kBottomSP), | |
| debug_ipc::StackFrame(kBottomAddr, kBottomSP, 0)}); | |
| // This should expand to tree stack entries, the one in the middle should be the inline function | |
| // expanded from the "bottom". | |
| EXPECT_EQ(3u, stack.size()); | |
| // Bottom stack frame should be the non-inline bottom function. | |
| EXPECT_FALSE(stack[2]->IsInline()); | |
| EXPECT_EQ(stack[2], stack[2]->GetPhysicalFrame()); | |
| EXPECT_EQ(kBottomAddr, stack[2]->GetAddress()); | |
| Location loc = stack[2]->GetLocation(); | |
| EXPECT_EQ(kBottomAddr, loc.address()); | |
| EXPECT_EQ(inline_call_line, loc.file_line()); | |
| EXPECT_EQ(bottom_func.get(), loc.symbol().Get()->AsFunction()); | |
| // Middle stack frame should be the inline bottom function, referencing the bottom one as the | |
| // physical frame. The location should be the call line of the ambiguous inline function because | |
| // it's next, even though that function was omitted from the stack. | |
| EXPECT_TRUE(stack[1]->IsInline()); | |
| EXPECT_EQ(stack[2], stack[1]->GetPhysicalFrame()); | |
| EXPECT_EQ(kBottomAddr, stack[1]->GetAddress()); | |
| loc = stack[1]->GetLocation(); | |
| EXPECT_EQ(kBottomAddr, loc.address()); | |
| EXPECT_EQ(inline_ambig_call_line, loc.file_line()); | |
| EXPECT_EQ(bottom_inline_func.get(), loc.symbol().Get()->AsFunction()); | |
| // The bottom_ambig_inline_func should be skipped because it's at the beginning of an inline call | |
| // and it's not at the top physical frame of the stack. | |
| // Top stack frame. | |
| EXPECT_FALSE(stack[0]->IsInline()); | |
| EXPECT_EQ(stack[0], stack[0]->GetPhysicalFrame()); | |
| EXPECT_EQ(kTopAddr, stack[0]->GetAddress()); | |
| loc = stack[0]->GetLocation(); | |
| EXPECT_EQ(kTopAddr, loc.address()); | |
| EXPECT_EQ(top_line, loc.file_line()); | |
| EXPECT_EQ(top_func.get(), loc.symbol().Get()->AsFunction()); | |
| } | |
| TEST_F(StackTest, InlineHiding) { | |
| constexpr uint64_t kTopSP = 0x2000; | |
| constexpr uint64_t kBottomSP = 0x2020; | |
| // Create two physical frames. | |
| Location top_location(Location::State::kSymbolized, 0x1000); | |
| Location bottom_location(Location::State::kSymbolized, 0x1020); | |
| auto phys_top = std::make_unique<MockFrame>(nullptr, nullptr, top_location, kTopSP); | |
| auto phys_bottom = std::make_unique<MockFrame>(nullptr, nullptr, bottom_location, kBottomSP); | |
| std::vector<std::unique_ptr<Frame>> frames; | |
| // Top frame has two inline functions expanded on top of it. | |
| frames.push_back(std::make_unique<MockFrame>(nullptr, nullptr, top_location, kTopSP, 0, | |
| std::vector<debug_ipc::Register>(), kTopSP, | |
| phys_top.get(), true)); | |
| frames.push_back(std::make_unique<MockFrame>(nullptr, nullptr, top_location, kTopSP, 0, | |
| std::vector<debug_ipc::Register>(), kTopSP, | |
| phys_top.get(), true)); | |
| // Physical top frame below those. | |
| frames.push_back(std::move(phys_top)); | |
| // Bottom frame has no inline frame. | |
| frames.push_back(std::move(phys_bottom)); | |
| MockStackDelegate delegate; | |
| Stack stack(&delegate); | |
| delegate.set_stack(&stack); | |
| // With no frames, there should be no inline frames. | |
| EXPECT_EQ(0u, stack.GetAmbiguousInlineFrameCount()); | |
| // Setting the frames should give the two inline ones, followed by two physical ones. | |
| stack.SetFramesForTest(std::move(frames), true); | |
| EXPECT_EQ(4u, stack.size()); | |
| EXPECT_EQ(2u, stack.GetAmbiguousInlineFrameCount()); | |
| // Hide both inline frames, the top frame should now be the physical one. | |
| stack.SetHideAmbiguousInlineFrameCount(2); | |
| EXPECT_EQ(2u, stack.size()); | |
| EXPECT_EQ(2u, stack.GetAmbiguousInlineFrameCount()); | |
| } | |
| // Appends stack items to an already existing stack via SetFrames(). The existing frames and the | |
| // inline hide count should be unchanged. | |
| TEST_F(StackTest, UpdateExisting) { | |
| MockStackDelegate delegate; | |
| Stack stack(&delegate); | |
| delegate.set_stack(&stack); | |
| // Make a stack with one physial frame and one inline frame above it. | |
| Location top_location(Location::State::kSymbolized, 0x1000); | |
| auto phys_top = std::make_unique<MockFrame>(nullptr, nullptr, top_location, kTopSP, 0, | |
| std::vector<debug_ipc::Register>(), kTopSP); | |
| auto inline_top = | |
| std::make_unique<MockFrame>(nullptr, nullptr, top_location, kTopSP, 0, | |
| std::vector<debug_ipc::Register>(), kTopSP, phys_top.get(), true); | |
| inline_top->set_is_ambiguous_inline(true); | |
| // Save for verification later. | |
| const Frame* frame0 = inline_top.get(); | |
| const Frame* frame1 = phys_top.get(); | |
| std::vector<std::unique_ptr<Frame>> input_frames; | |
| input_frames.push_back(std::move(inline_top)); | |
| input_frames.push_back(std::move(phys_top)); | |
| stack.SetFramesForTest(std::move(input_frames), true); | |
| // The ambiguous inline frame is hidden so we can check later this is preserved across updates. | |
| ASSERT_EQ(2u, stack.size()); | |
| ASSERT_EQ(1u, stack.GetAmbiguousInlineFrameCount()); | |
| stack.SetHideAmbiguousInlineFrameCount(1); | |
| // Synthesize a frame update. The first physical frame matches the first physical frame from | |
| // above. This uses the non-test update flow which should preserve the frame objects that haven't | |
| // changed. | |
| std::vector<debug_ipc::StackFrame> raw_frames; | |
| debug_ipc::StackFrame phys_top_record(0x1000, kTopSP, kBottomSP); | |
| raw_frames.push_back(phys_top_record); | |
| debug_ipc::StackFrame phys_bottom_record(0x1020, kBottomSP); | |
| raw_frames.push_back(phys_bottom_record); | |
| stack.SetFrames(debug_ipc::ThreadRecord::StackAmount::kFull, raw_frames); | |
| // The update should have left the existing top physical frame and the inline frame expanded on | |
| // top of it, and add the additional physical frame below it. | |
| EXPECT_EQ(1u, stack.GetAmbiguousInlineFrameCount()); | |
| // Now that we checked it, reset the hidden frame count so we can see them. | |
| stack.SetHideAmbiguousInlineFrameCount(0); | |
| ASSERT_EQ(3u, stack.size()); | |
| EXPECT_EQ(frame0, stack[0]); | |
| EXPECT_EQ(frame1, stack[1]); | |
| EXPECT_EQ(phys_bottom_record.ip, stack[2]->GetAddress()); | |
| // Now supply a slightly different stack, it should be replaced and the hidden inline frame count | |
| // reset. | |
| stack.SetHideAmbiguousInlineFrameCount(0); // So we can test for reset. | |
| raw_frames[0].sp++; // Modify frame. | |
| stack.SetFrames(debug_ipc::ThreadRecord::StackAmount::kFull, raw_frames); | |
| // The inline frame at the top should have gone away because we didn't provide any inline | |
| // information for the Stack to expand it. | |
| ASSERT_EQ(2u, stack.size()); | |
| EXPECT_EQ(0u, stack.GetAmbiguousInlineFrameCount()); | |
| EXPECT_EQ(raw_frames[0].ip, stack[0]->GetAddress()); | |
| EXPECT_EQ(raw_frames[0].sp, stack[0]->GetStackPointer()); | |
| EXPECT_EQ(raw_frames[1].ip, stack[1]->GetAddress()); | |
| EXPECT_EQ(raw_frames[1].sp, stack[1]->GetStackPointer()); | |
| } | |
| // Tests that variables in inline functions are found during evaluation. | |
| // | |
| // This sets up an inline frame and makes sure we can read a local variable out of it. | |
| TEST_F(StackTest, InlineVars) { | |
| constexpr uint64_t kInlineAddr = 0x1002; | |
| constexpr uint64_t kPhysAddr = 0x1000; | |
| MockStackDelegate delegate; | |
| SymbolContext symbol_context = SymbolContext::ForRelativeAddresses(); | |
| // Make inline function. | |
| auto inline_func = fxl::MakeRefCounted<Function>(DwarfTag::kInlinedSubroutine); | |
| inline_func->set_code_ranges(AddressRanges(AddressRange(kInlineAddr, kInlineAddr + 8))); | |
| // The inline function has a local variable ("var") that always evaluates to 3. | |
| VariableLocation::Entry loc_entry; | |
| loc_entry.expression = {llvm::dwarf::DW_OP_lit3, llvm::dwarf::DW_OP_stack_value}; | |
| VariableLocation var_loc({loc_entry}); | |
| auto int32_type = MakeInt32Type(); | |
| auto inline_var = | |
| fxl::MakeRefCounted<Variable>(DwarfTag::kVariable, "var", LazySymbol(int32_type), var_loc); | |
| inline_func->set_variables({LazySymbol(inline_var)}); | |
| // Make physical fuction. | |
| auto phys_func = fxl::MakeRefCounted<Function>(DwarfTag::kSubprogram); | |
| phys_func->set_code_ranges(AddressRanges(AddressRange(kPhysAddr, kPhysAddr + 16))); | |
| inline_func->set_containing_block(UncachedLazySymbol::MakeUnsafe(phys_func)); | |
| // Physical stack frame. | |
| Location phys_location(kPhysAddr, FileLine("file.cc", 200), 0, symbol_context, phys_func); | |
| delegate.AddLocation(phys_location); | |
| // Inline frame on top of that. | |
| Location inline_location(kInlineAddr, FileLine("file.cc", 100), 0, symbol_context, inline_func); | |
| delegate.AddLocation(inline_location); | |
| Stack stack(&delegate); | |
| delegate.set_stack(&stack); | |
| stack.SetFrames(debug_ipc::ThreadRecord::StackAmount::kFull, | |
| {debug_ipc::StackFrame(kInlineAddr, kTopSP, kBottomSP)}); | |
| ASSERT_EQ(2u, stack.size()); // Should have expanded the inline frame. | |
| // ACTUAL TEST. | |
| // Evaluate "var + 1" which should be "4" given var evaluates to 3. | |
| auto eval_context = stack[0]->GetEvalContext(); | |
| bool called = false; | |
| EvalExpression("var + 1", eval_context, true, [&called](ErrOrValue value) mutable { | |
| called = true; | |
| EXPECT_FALSE(value.has_error()); | |
| int64_t result = 0; | |
| Err e = value.value().PromoteTo64(&result); | |
| EXPECT_FALSE(e.has_error()); | |
| EXPECT_EQ(4, result); | |
| }); | |
| EXPECT_TRUE(called); // Callback should have been issued. | |
| } | |
| } // namespace zxdb |