1 // Copyright 2015 The Chromium Authors
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/memory/ptr_util.h"
13 #include "base/memory/raw_ptr.h"
14 #include "base/profiler/stack_sampling_profiler_test_util.h"
15 #include "build/build_config.h"
16 #include "testing/gtest/include/gtest/gtest.h"
17
18 namespace base {
19
20 namespace {
21
22 // The image base returned by LookupFunctionEntry starts at this value and is
23 // incremented by the same value with each call.
24 const uintptr_t kImageBaseIncrement = 1 << 20;
25
26 class TestUnwindFunctions : public Win32StackFrameUnwinder::UnwindFunctions {
27 public:
28 TestUnwindFunctions();
29
30 TestUnwindFunctions(const TestUnwindFunctions&) = delete;
31 TestUnwindFunctions& operator=(const TestUnwindFunctions&) = delete;
32
33 PRUNTIME_FUNCTION LookupFunctionEntry(DWORD64 program_counter,
34 PDWORD64 image_base) override;
35 void VirtualUnwind(DWORD64 image_base,
36 DWORD64 program_counter,
37 PRUNTIME_FUNCTION runtime_function,
38 CONTEXT* context) override;
39
40 // These functions set whether the next frame will have a RUNTIME_FUNCTION.
41 void SetHasRuntimeFunction(CONTEXT* context);
42 void SetNoRuntimeFunction(CONTEXT* context);
43
44 private:
45 static RUNTIME_FUNCTION* const kInvalidRuntimeFunction;
46
47 DWORD64 expected_program_counter_;
48 DWORD64 next_image_base_;
49 DWORD64 expected_image_base_;
50 raw_ptr<RUNTIME_FUNCTION> next_runtime_function_;
51 std::vector<RUNTIME_FUNCTION> runtime_functions_;
52 };
53
54 RUNTIME_FUNCTION* const TestUnwindFunctions::kInvalidRuntimeFunction =
55 reinterpret_cast<RUNTIME_FUNCTION*>(static_cast<uintptr_t>(-1));
56
TestUnwindFunctions()57 TestUnwindFunctions::TestUnwindFunctions()
58 : expected_program_counter_(0),
59 next_image_base_(kImageBaseIncrement),
60 expected_image_base_(0),
61 next_runtime_function_(kInvalidRuntimeFunction) {}
62
LookupFunctionEntry(DWORD64 program_counter,PDWORD64 image_base)63 PRUNTIME_FUNCTION TestUnwindFunctions::LookupFunctionEntry(
64 DWORD64 program_counter,
65 PDWORD64 image_base) {
66 EXPECT_EQ(expected_program_counter_, program_counter);
67 *image_base = expected_image_base_ = next_image_base_;
68 next_image_base_ += kImageBaseIncrement;
69 RUNTIME_FUNCTION* return_value = next_runtime_function_;
70 next_runtime_function_ = kInvalidRuntimeFunction;
71 return return_value;
72 }
73
VirtualUnwind(DWORD64 image_base,DWORD64 program_counter,PRUNTIME_FUNCTION runtime_function,CONTEXT * context)74 void TestUnwindFunctions::VirtualUnwind(DWORD64 image_base,
75 DWORD64 program_counter,
76 PRUNTIME_FUNCTION runtime_function,
77 CONTEXT* context) {
78 ASSERT_NE(kInvalidRuntimeFunction, runtime_function)
79 << "expected call to SetHasRuntimeFunction() or SetNoRuntimeFunction() "
80 << "before invoking TryUnwind()";
81 EXPECT_EQ(expected_image_base_, image_base);
82 expected_image_base_ = 0;
83 EXPECT_EQ(expected_program_counter_, program_counter);
84 expected_program_counter_ = 0;
85 // This function should only be called when LookupFunctionEntry returns
86 // a RUNTIME_FUNCTION.
87 EXPECT_EQ(&runtime_functions_.back(), runtime_function);
88 }
89
SetContextPc(CONTEXT * context,DWORD64 val)90 static void SetContextPc(CONTEXT* context, DWORD64 val) {
91 #if defined(ARCH_CPU_ARM64)
92 context->Pc = val;
93 #else
94 context->Rip = val;
95 #endif
96 }
97
SetHasRuntimeFunction(CONTEXT * context)98 void TestUnwindFunctions::SetHasRuntimeFunction(CONTEXT* context) {
99 RUNTIME_FUNCTION runtime_function = {};
100 runtime_function.BeginAddress = 16;
101 #if defined(ARCH_CPU_ARM64)
102 runtime_function.FunctionLength = 256;
103 #else
104 runtime_function.EndAddress = runtime_function.BeginAddress + 256;
105 #endif
106
107 runtime_functions_.push_back(runtime_function);
108 next_runtime_function_ = &runtime_functions_.back();
109 expected_program_counter_ =
110 next_image_base_ + runtime_function.BeginAddress + 8;
111 SetContextPc(context, expected_program_counter_);
112 }
113
SetNoRuntimeFunction(CONTEXT * context)114 void TestUnwindFunctions::SetNoRuntimeFunction(CONTEXT* context) {
115 expected_program_counter_ = 100;
116 SetContextPc(context, expected_program_counter_);
117 next_runtime_function_ = nullptr;
118 }
119
120 } // namespace
121
122 class Win32StackFrameUnwinderTest : public testing::Test {
123 public:
124 Win32StackFrameUnwinderTest(const Win32StackFrameUnwinderTest&) = delete;
125 Win32StackFrameUnwinderTest& operator=(const Win32StackFrameUnwinderTest&) =
126 delete;
127
128 protected:
Win32StackFrameUnwinderTest()129 Win32StackFrameUnwinderTest() {}
130
131 // This exists so that Win32StackFrameUnwinder's constructor can be private
132 // with a single friend declaration of this test fixture.
133 std::unique_ptr<Win32StackFrameUnwinder> CreateUnwinder();
134
135 // Weak pointer to the unwind functions used by last created unwinder.
136 raw_ptr<TestUnwindFunctions, DanglingUntriaged> unwind_functions_;
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
153 TestModule stub_module1(kImageBaseIncrement);
154 unwind_functions_->SetHasRuntimeFunction(&context);
155 EXPECT_TRUE(unwinder->TryUnwind(true, &context, &stub_module1));
156
157 TestModule stub_module2(kImageBaseIncrement * 2);
158 unwind_functions_->SetHasRuntimeFunction(&context);
159 EXPECT_TRUE(unwinder->TryUnwind(false, &context, &stub_module2));
160
161 TestModule stub_module3(kImageBaseIncrement * 3);
162 unwind_functions_->SetHasRuntimeFunction(&context);
163 EXPECT_TRUE(unwinder->TryUnwind(false, &context, &stub_module3));
164 }
165
166 // Checks that the CONTEXT's stack pointer gets popped when the top frame has no
167 // unwind information.
TEST_F(Win32StackFrameUnwinderTest,FrameAtTopWithoutUnwindInfo)168 TEST_F(Win32StackFrameUnwinderTest, FrameAtTopWithoutUnwindInfo) {
169 std::unique_ptr<Win32StackFrameUnwinder> unwinder = CreateUnwinder();
170 CONTEXT context = {0};
171 DWORD64 next_ip = 0x0123456789abcdef;
172 DWORD64 original_rsp = reinterpret_cast<DWORD64>(&next_ip);
173 #if defined(ARCH_CPU_ARM64)
174 context.Sp = original_rsp;
175 context.Lr = next_ip;
176 context.ContextFlags |= CONTEXT_UNWOUND_TO_CALL;
177 #else
178 context.Rsp = original_rsp;
179 #endif
180
181 TestModule stub_module(kImageBaseIncrement);
182 unwind_functions_->SetNoRuntimeFunction(&context);
183 EXPECT_TRUE(unwinder->TryUnwind(true, &context, &stub_module));
184 #if defined(ARCH_CPU_ARM64)
185 EXPECT_EQ(next_ip, context.Pc);
186 #else
187 EXPECT_EQ(next_ip, context.Rip);
188 EXPECT_EQ(original_rsp + 8, context.Rsp);
189 #endif
190 }
191
192 // Checks that a frame below the top of the stack with missing unwind info
193 // terminates the unwinding.
TEST_F(Win32StackFrameUnwinderTest,FrameBelowTopWithoutUnwindInfo)194 TEST_F(Win32StackFrameUnwinderTest, FrameBelowTopWithoutUnwindInfo) {
195 {
196 // First stack, with a bad function below the top of the stack.
197 std::unique_ptr<Win32StackFrameUnwinder> unwinder = CreateUnwinder();
198 CONTEXT context = {0};
199
200 TestModule stub_module(kImageBaseIncrement);
201 unwind_functions_->SetNoRuntimeFunction(&context);
202 EXPECT_FALSE(unwinder->TryUnwind(false, &context, &stub_module));
203 }
204 }
205
206 } // namespace base
207