xref: /aosp_15_r20/external/libchrome/base/profiler/win32_stack_frame_unwinder_unittest.cc (revision 635a864187cb8b6c713ff48b7e790a6b21769273)
1 // Copyright 2015 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #include "base/profiler/win32_stack_frame_unwinder.h"
6 
7 #include <memory>
8 #include <utility>
9 #include <vector>
10 
11 #include "base/compiler_specific.h"
12 #include "base/macros.h"
13 #include "base/memory/ptr_util.h"
14 #include "testing/gtest/include/gtest/gtest.h"
15 
16 namespace base {
17 
18 namespace {
19 
20 class TestUnwindFunctions : public Win32StackFrameUnwinder::UnwindFunctions {
21  public:
22   TestUnwindFunctions();
23 
24   PRUNTIME_FUNCTION LookupFunctionEntry(DWORD64 program_counter,
25                                         PDWORD64 image_base) override;
26   void VirtualUnwind(DWORD64 image_base,
27                      DWORD64 program_counter,
28                      PRUNTIME_FUNCTION runtime_function,
29                      CONTEXT* context) override;
30   ScopedModuleHandle GetModuleForProgramCounter(
31       DWORD64 program_counter) override;
32 
33   // Instructs GetModuleForProgramCounter to return null on the next call.
34   void SetUnloadedModule();
35 
36   // These functions set whether the next frame will have a RUNTIME_FUNCTION.
37   void SetHasRuntimeFunction(CONTEXT* context);
38   void SetNoRuntimeFunction(CONTEXT* context);
39 
40  private:
41   enum { kImageBaseIncrement = 1 << 20 };
42 
43   static RUNTIME_FUNCTION* const kInvalidRuntimeFunction;
44 
45   bool module_is_loaded_;
46   DWORD64 expected_program_counter_;
47   DWORD64 next_image_base_;
48   DWORD64 expected_image_base_;
49   RUNTIME_FUNCTION* next_runtime_function_;
50   std::vector<RUNTIME_FUNCTION> runtime_functions_;
51 
52   DISALLOW_COPY_AND_ASSIGN(TestUnwindFunctions);
53 };
54 
55 RUNTIME_FUNCTION* const TestUnwindFunctions::kInvalidRuntimeFunction =
56     reinterpret_cast<RUNTIME_FUNCTION*>(static_cast<uintptr_t>(-1));
57 
TestUnwindFunctions()58 TestUnwindFunctions::TestUnwindFunctions()
59     : module_is_loaded_(true),
60       expected_program_counter_(0),
61       next_image_base_(kImageBaseIncrement),
62       expected_image_base_(0),
63       next_runtime_function_(kInvalidRuntimeFunction) {
64 }
65 
LookupFunctionEntry(DWORD64 program_counter,PDWORD64 image_base)66 PRUNTIME_FUNCTION TestUnwindFunctions::LookupFunctionEntry(
67     DWORD64 program_counter,
68     PDWORD64 image_base) {
69   EXPECT_EQ(expected_program_counter_, program_counter);
70   *image_base = expected_image_base_ = next_image_base_;
71   next_image_base_ += kImageBaseIncrement;
72   RUNTIME_FUNCTION* return_value = next_runtime_function_;
73   next_runtime_function_ = kInvalidRuntimeFunction;
74   return return_value;
75 }
76 
VirtualUnwind(DWORD64 image_base,DWORD64 program_counter,PRUNTIME_FUNCTION runtime_function,CONTEXT * context)77 void TestUnwindFunctions::VirtualUnwind(DWORD64 image_base,
78                                         DWORD64 program_counter,
79                                         PRUNTIME_FUNCTION runtime_function,
80                                         CONTEXT* context) {
81   ASSERT_NE(kInvalidRuntimeFunction, runtime_function)
82       << "expected call to SetHasRuntimeFunction() or SetNoRuntimeFunction() "
83       << "before invoking TryUnwind()";
84   EXPECT_EQ(expected_image_base_, image_base);
85   expected_image_base_ = 0;
86   EXPECT_EQ(expected_program_counter_, program_counter);
87   expected_program_counter_ = 0;
88   // This function should only be called when LookupFunctionEntry returns
89   // a RUNTIME_FUNCTION.
90   EXPECT_EQ(&runtime_functions_.back(), runtime_function);
91 }
92 
GetModuleForProgramCounter(DWORD64 program_counter)93 ScopedModuleHandle TestUnwindFunctions::GetModuleForProgramCounter(
94     DWORD64 program_counter) {
95   bool return_non_null_value = module_is_loaded_;
96   module_is_loaded_ = true;
97   return ScopedModuleHandle(return_non_null_value ?
98                             ModuleHandleTraits::kNonNullModuleForTesting :
99                             nullptr);
100 }
101 
SetUnloadedModule()102 void TestUnwindFunctions::SetUnloadedModule() {
103   module_is_loaded_ = false;
104 }
105 
SetHasRuntimeFunction(CONTEXT * context)106 void TestUnwindFunctions::SetHasRuntimeFunction(CONTEXT* context) {
107   RUNTIME_FUNCTION runtime_function = {};
108   runtime_function.BeginAddress = 16;
109   runtime_function.EndAddress = runtime_function.BeginAddress + 256;
110   runtime_functions_.push_back(runtime_function);
111   next_runtime_function_ = &runtime_functions_.back();
112 
113   expected_program_counter_ = context->Rip =
114       next_image_base_ + runtime_function.BeginAddress + 8;
115 }
116 
SetNoRuntimeFunction(CONTEXT * context)117 void TestUnwindFunctions::SetNoRuntimeFunction(CONTEXT* context) {
118   expected_program_counter_ = context->Rip = 100;
119   next_runtime_function_ = nullptr;
120 }
121 
122 }  // namespace
123 
124 class Win32StackFrameUnwinderTest : public testing::Test {
125  protected:
Win32StackFrameUnwinderTest()126   Win32StackFrameUnwinderTest() {}
127 
128   // This exists so that Win32StackFrameUnwinder's constructor can be private
129   // with a single friend declaration of this test fixture.
130   std::unique_ptr<Win32StackFrameUnwinder> CreateUnwinder();
131 
132   // Weak pointer to the unwind functions used by last created unwinder.
133   TestUnwindFunctions* unwind_functions_;
134 
135  private:
136   DISALLOW_COPY_AND_ASSIGN(Win32StackFrameUnwinderTest);
137 };
138 
139 std::unique_ptr<Win32StackFrameUnwinder>
CreateUnwinder()140 Win32StackFrameUnwinderTest::CreateUnwinder() {
141   std::unique_ptr<TestUnwindFunctions> unwind_functions(
142       new TestUnwindFunctions);
143   unwind_functions_ = unwind_functions.get();
144   return WrapUnique(
145       new Win32StackFrameUnwinder(std::move(unwind_functions)));
146 }
147 
148 // Checks the case where all frames have unwind information.
TEST_F(Win32StackFrameUnwinderTest,FramesWithUnwindInfo)149 TEST_F(Win32StackFrameUnwinderTest, FramesWithUnwindInfo) {
150   std::unique_ptr<Win32StackFrameUnwinder> unwinder = CreateUnwinder();
151   CONTEXT context = {0};
152   ScopedModuleHandle module;
153 
154   unwind_functions_->SetHasRuntimeFunction(&context);
155   EXPECT_TRUE(unwinder->TryUnwind(&context, &module));
156   EXPECT_TRUE(module.IsValid());
157 
158   unwind_functions_->SetHasRuntimeFunction(&context);
159   module.Set(nullptr);
160   EXPECT_TRUE(unwinder->TryUnwind(&context, &module));
161   EXPECT_TRUE(module.IsValid());
162 
163   unwind_functions_->SetHasRuntimeFunction(&context);
164   module.Set(nullptr);
165   EXPECT_TRUE(unwinder->TryUnwind(&context, &module));
166   EXPECT_TRUE(module.IsValid());
167 }
168 
169 // Checks that an instruction pointer in an unloaded module fails to unwind.
TEST_F(Win32StackFrameUnwinderTest,UnloadedModule)170 TEST_F(Win32StackFrameUnwinderTest, UnloadedModule) {
171   std::unique_ptr<Win32StackFrameUnwinder> unwinder = CreateUnwinder();
172   CONTEXT context = {0};
173   ScopedModuleHandle module;
174 
175   unwind_functions_->SetUnloadedModule();
176   EXPECT_FALSE(unwinder->TryUnwind(&context, &module));
177 }
178 
179 // Checks that the CONTEXT's stack pointer gets popped when the top frame has no
180 // unwind information.
TEST_F(Win32StackFrameUnwinderTest,FrameAtTopWithoutUnwindInfo)181 TEST_F(Win32StackFrameUnwinderTest, FrameAtTopWithoutUnwindInfo) {
182   std::unique_ptr<Win32StackFrameUnwinder> unwinder = CreateUnwinder();
183   CONTEXT context = {0};
184   ScopedModuleHandle module;
185   DWORD64 next_ip = 0x0123456789abcdef;
186   DWORD64 original_rsp = reinterpret_cast<DWORD64>(&next_ip);
187   context.Rsp = original_rsp;
188 
189   unwind_functions_->SetNoRuntimeFunction(&context);
190   EXPECT_TRUE(unwinder->TryUnwind(&context, &module));
191   EXPECT_EQ(next_ip, context.Rip);
192   EXPECT_EQ(original_rsp + 8, context.Rsp);
193   EXPECT_TRUE(module.IsValid());
194 
195   unwind_functions_->SetHasRuntimeFunction(&context);
196   module.Set(nullptr);
197   EXPECT_TRUE(unwinder->TryUnwind(&context, &module));
198   EXPECT_TRUE(module.IsValid());
199 
200   unwind_functions_->SetHasRuntimeFunction(&context);
201   module.Set(nullptr);
202   EXPECT_TRUE(unwinder->TryUnwind(&context, &module));
203   EXPECT_TRUE(module.IsValid());
204 }
205 
206 // Checks that a frame below the top of the stack with missing unwind info
207 // terminates the unwinding.
TEST_F(Win32StackFrameUnwinderTest,FrameBelowTopWithoutUnwindInfo)208 TEST_F(Win32StackFrameUnwinderTest, FrameBelowTopWithoutUnwindInfo) {
209   {
210     // First stack, with a bad function below the top of the stack.
211     std::unique_ptr<Win32StackFrameUnwinder> unwinder = CreateUnwinder();
212     CONTEXT context = {0};
213     ScopedModuleHandle module;
214     unwind_functions_->SetHasRuntimeFunction(&context);
215     EXPECT_TRUE(unwinder->TryUnwind(&context, &module));
216     EXPECT_TRUE(module.IsValid());
217 
218     unwind_functions_->SetNoRuntimeFunction(&context);
219     EXPECT_FALSE(unwinder->TryUnwind(&context, &module));
220   }
221 }
222 
223 }  // namespace base
224