1 // Copyright 2022 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/frame_pointer_unwinder.h"
6
7 #include <memory>
8
9 #include "base/profiler/module_cache.h"
10 #include "base/profiler/stack_sampling_profiler_test_util.h"
11 #include "base/profiler/unwinder.h"
12 #include "build/buildflag.h"
13 #include "testing/gtest/include/gtest/gtest.h"
14
15 #if BUILDFLAG(IS_APPLE)
16 #include "base/mac/mac_util.h"
17 #endif
18
19 namespace base {
20
21 namespace {
22
23 constexpr uintptr_t kModuleStart = 0x1000;
24 constexpr size_t kModuleSize = 0x1000;
25 constexpr uintptr_t kNonNativeModuleStart = 0x4000;
26
27 // Used to construct test stacks. If `relative` is true, the value should be the
28 // address `offset` positions from the bottom of the stack (at 8-byte alignment)
29 // Otherwise, `offset` is added to the stack as an absolute address/value.
30 // For example, when creating a stack with bottom 0x2000, {false, 0xf00d} will
31 // become 0xf00d, and {true, 0x3} will become 0x2018.
32 struct StackEntrySpec {
33 bool relative;
34 uintptr_t offset;
35 };
36
37 // Enables constructing a stack buffer that has pointers to itself
38 // and provides convenience methods for calling the unwinder.
39 struct InputStack {
InputStackbase::__anon65a6472c0111::InputStack40 explicit InputStack(const std::vector<StackEntrySpec>& offsets)
41 : buffer(offsets.size()) {
42 size_t size = offsets.size();
43 for (size_t i = 0; i < size; ++i) {
44 auto spec = offsets[i];
45 if (spec.relative) {
46 buffer[i] = bottom() + (spec.offset * sizeof(uintptr_t));
47 } else {
48 buffer[i] = spec.offset;
49 }
50 }
51 }
bottombase::__anon65a6472c0111::InputStack52 uintptr_t bottom() const {
53 return reinterpret_cast<uintptr_t>(buffer.data());
54 }
topbase::__anon65a6472c0111::InputStack55 uintptr_t top() const { return bottom() + buffer.size() * sizeof(uintptr_t); }
56
57 private:
58 std::vector<uintptr_t> buffer;
59 };
60
61 } // namespace
62
63 class FramePointerUnwinderTest : public testing::Test {
64 protected:
FramePointerUnwinderTest()65 FramePointerUnwinderTest() {
66 #if BUILDFLAG(IS_APPLE)
67 if (__builtin_available(iOS 12, *)) {
68 #else
69 {
70 #endif
71 unwinder_ = std::make_unique<FramePointerUnwinder>();
72
73 auto test_module =
74 std::make_unique<TestModule>(kModuleStart, kModuleSize);
75 module_ = test_module.get();
76 module_cache_.AddCustomNativeModule(std::move(test_module));
77 auto non_native_module = std::make_unique<TestModule>(
78 kNonNativeModuleStart, kModuleSize, false);
79 non_native_module_ = non_native_module.get();
80 std::vector<std::unique_ptr<const ModuleCache::Module>> wrapper;
81 wrapper.push_back(std::move(non_native_module));
82 module_cache()->UpdateNonNativeModules({}, std::move(wrapper));
83
84 unwinder_->Initialize(&module_cache_);
85 }
86 }
87
88 ModuleCache* module_cache() { return &module_cache_; }
89 ModuleCache::Module* module() { return module_; }
90 ModuleCache::Module* non_native_module() { return non_native_module_; }
91 Unwinder* unwinder() { return unwinder_.get(); }
92
93 private:
94 std::unique_ptr<Unwinder> unwinder_;
95 base::ModuleCache module_cache_;
96 raw_ptr<ModuleCache::Module> module_;
97 raw_ptr<ModuleCache::Module> non_native_module_;
98 };
99
TEST_F(FramePointerUnwinderTest,CanUnwindFromNonDelegated)100 TEST_F(FramePointerUnwinderTest, CanUnwindFromNonDelegated) {
101 EXPECT_FALSE(unwinder()->CanUnwindFrom(Frame(0, nullptr)));
102 EXPECT_TRUE(unwinder()->CanUnwindFrom(Frame(0, module())));
103 EXPECT_FALSE(unwinder()->CanUnwindFrom(Frame(0, non_native_module())));
104 }
105
TEST_F(FramePointerUnwinderTest,CanUnwindFromDelegated)106 TEST_F(FramePointerUnwinderTest, CanUnwindFromDelegated) {
107 auto unwinder_with_delegate = std::make_unique<FramePointerUnwinder>(
108 BindRepeating([](const Frame& frame) {
109 return frame.instruction_pointer == 0x10;
110 }));
111
112 EXPECT_FALSE(unwinder_with_delegate->CanUnwindFrom(Frame(0xa, nullptr)));
113 EXPECT_FALSE(unwinder_with_delegate->CanUnwindFrom(Frame(0xa, module())));
114 EXPECT_FALSE(
115 unwinder_with_delegate->CanUnwindFrom(Frame(0xa, non_native_module())));
116 EXPECT_TRUE(unwinder_with_delegate->CanUnwindFrom(Frame(0x10, nullptr)));
117 EXPECT_TRUE(unwinder_with_delegate->CanUnwindFrom(Frame(0x10, module())));
118 EXPECT_TRUE(
119 unwinder_with_delegate->CanUnwindFrom(Frame(0x10, non_native_module())));
120 }
121
TEST_F(FramePointerUnwinderTest,FPPointsOutsideOfStack)122 TEST_F(FramePointerUnwinderTest, FPPointsOutsideOfStack) {
123 InputStack input({
124 {false, 0x1000},
125 {false, 0x1000},
126 {false, 0x1000},
127 {false, 0x1000},
128 {false, 0x1000},
129 });
130
131 RegisterContext context;
132 RegisterContextStackPointer(&context) = input.bottom();
133 RegisterContextInstructionPointer(&context) = kModuleStart;
134 RegisterContextFramePointer(&context) = 0x1;
135 std::vector<Frame> stack = {
136 Frame(RegisterContextInstructionPointer(&context), module())};
137
138 EXPECT_EQ(UnwindResult::kAborted,
139 unwinder()->TryUnwind(/*state_capture=*/nullptr, &context,
140 input.top(), &stack));
141 EXPECT_EQ(std::vector<Frame>({{kModuleStart, module()}}), stack);
142
143 RegisterContextFramePointer(&context) = input.bottom() - sizeof(uintptr_t);
144 EXPECT_EQ(UnwindResult::kAborted,
145 unwinder()->TryUnwind(/*state_capture=*/nullptr, &context,
146 input.top(), &stack));
147 EXPECT_EQ(std::vector<Frame>({{kModuleStart, module()}}), stack);
148
149 RegisterContextFramePointer(&context) = input.top();
150 EXPECT_EQ(UnwindResult::kAborted,
151 unwinder()->TryUnwind(/*state_capture=*/nullptr, &context,
152 input.top(), &stack));
153 EXPECT_EQ(std::vector<Frame>({{kModuleStart, module()}}), stack);
154 }
155
TEST_F(FramePointerUnwinderTest,FPPointsToSelf)156 TEST_F(FramePointerUnwinderTest, FPPointsToSelf) {
157 InputStack input({
158 {true, 0},
159 {false, kModuleStart + 0x10},
160 {true, 4},
161 {false, kModuleStart + 0x20},
162 {false, 0},
163 {false, 0},
164 });
165
166 RegisterContext context;
167 RegisterContextStackPointer(&context) = input.bottom();
168 RegisterContextInstructionPointer(&context) = kModuleStart;
169 RegisterContextFramePointer(&context) = input.bottom();
170 std::vector<Frame> stack = {
171 Frame(RegisterContextInstructionPointer(&context), module())};
172
173 EXPECT_EQ(UnwindResult::kAborted,
174 unwinder()->TryUnwind(/*state_capture=*/nullptr, &context,
175 input.top(), &stack));
176 EXPECT_EQ(std::vector<Frame>({
177 {kModuleStart, module()},
178 }),
179 stack);
180 }
181
182 // Tests that two frame pointers that point to each other can't create an
183 // infinite loop
TEST_F(FramePointerUnwinderTest,FPCycle)184 TEST_F(FramePointerUnwinderTest, FPCycle) {
185 InputStack input({
186 {true, 2},
187 {false, kModuleStart + 0x10},
188 {true, 0},
189 {false, kModuleStart + 0x20},
190 {true, 4},
191 {false, kModuleStart + 0x30},
192 {false, 0},
193 {false, 0},
194 });
195
196 RegisterContext context;
197 RegisterContextStackPointer(&context) = input.bottom();
198 RegisterContextInstructionPointer(&context) = kModuleStart;
199 RegisterContextFramePointer(&context) = input.bottom();
200 std::vector<Frame> stack = {
201 Frame(RegisterContextInstructionPointer(&context), module())};
202
203 EXPECT_EQ(UnwindResult::kAborted,
204 unwinder()->TryUnwind(/*state_capture=*/nullptr, &context,
205 input.top(), &stack));
206 EXPECT_EQ(std::vector<Frame>({
207 {kModuleStart, module()},
208 {kModuleStart + 0x10, module()},
209 }),
210 stack);
211 }
212
TEST_F(FramePointerUnwinderTest,NoModuleForIP)213 TEST_F(FramePointerUnwinderTest, NoModuleForIP) {
214 uintptr_t not_in_module = kModuleStart - 0x10;
215 InputStack input({
216 {true, 2},
217 {false, not_in_module},
218 {true, 4},
219 {true, kModuleStart + 0x10},
220 {false, 0},
221 {false, 0},
222 });
223
224 RegisterContext context;
225 RegisterContextStackPointer(&context) = input.bottom();
226 RegisterContextInstructionPointer(&context) = kModuleStart;
227 RegisterContextFramePointer(&context) = input.bottom();
228 std::vector<Frame> stack = {
229 Frame(RegisterContextInstructionPointer(&context), module())};
230
231 EXPECT_EQ(UnwindResult::kAborted,
232 unwinder()->TryUnwind(/*state_capture=*/nullptr, &context,
233 input.top(), &stack));
234 EXPECT_EQ(
235 std::vector<Frame>({{kModuleStart, module()}, {not_in_module, nullptr}}),
236 stack);
237 }
238
239 // Tests that testing that checking if there's space to read two values from the
240 // stack doesn't overflow.
TEST_F(FramePointerUnwinderTest,FPAdditionOverflows)241 TEST_F(FramePointerUnwinderTest, FPAdditionOverflows) {
242 uintptr_t will_overflow = std::numeric_limits<uintptr_t>::max() - 1;
243 InputStack input({
244 {true, 2},
245 {false, kModuleStart + 0x10},
246 {false, 0},
247 {false, 0},
248 });
249
250 RegisterContext context;
251 RegisterContextStackPointer(&context) = input.bottom();
252 RegisterContextInstructionPointer(&context) = kModuleStart;
253 RegisterContextFramePointer(&context) = will_overflow;
254 std::vector<Frame> stack = {
255 Frame(RegisterContextInstructionPointer(&context), module())};
256
257 EXPECT_EQ(UnwindResult::kAborted,
258 unwinder()->TryUnwind(/*state_capture=*/nullptr, &context,
259 input.top(), &stack));
260 EXPECT_EQ(std::vector<Frame>({
261 {kModuleStart, module()},
262 }),
263 stack);
264 }
265
266 // Tests the happy path: a successful unwind with no non-native modules.
TEST_F(FramePointerUnwinderTest,RegularUnwind)267 TEST_F(FramePointerUnwinderTest, RegularUnwind) {
268 InputStack input({
269 {true, 4}, // fp of frame 1
270 {false, kModuleStart + 0x20}, // ip of frame 1
271 {false, 0xaaaa},
272 {false, 0xaaaa},
273 {true, 8}, // fp of frame 2
274 {false, kModuleStart + 0x42}, // ip of frame 2
275 {false, 0xaaaa},
276 {false, 0xaaaa},
277 {false, 0},
278 {false, 1},
279 });
280
281 RegisterContext context;
282 RegisterContextStackPointer(&context) = input.bottom();
283 RegisterContextInstructionPointer(&context) = kModuleStart;
284 RegisterContextFramePointer(&context) = input.bottom();
285 std::vector<Frame> stack = {
286 Frame(RegisterContextInstructionPointer(&context), module())};
287
288 EXPECT_EQ(UnwindResult::kCompleted,
289 unwinder()->TryUnwind(/*state_capture=*/nullptr, &context,
290 input.top(), &stack));
291 EXPECT_EQ(std::vector<Frame>({
292 {kModuleStart, module()},
293 {kModuleStart + 0x20, module()},
294 {kModuleStart + 0x42, module()},
295 }),
296 stack);
297 }
298
299 // Tests that if a V8 frame is encountered, unwinding stops and
300 // kUnrecognizedFrame is returned to facilitate continuing with the V8 unwinder.
TEST_F(FramePointerUnwinderTest,NonNativeFrame)301 TEST_F(FramePointerUnwinderTest, NonNativeFrame) {
302 InputStack input({
303 {true, 4}, // fp of frame 1
304 {false, kModuleStart + 0x20}, // ip of frame 1
305 {false, 0xaaaa},
306 {false, 0xaaaa},
307 {true, 8}, // fp of frame 2
308 {false, kNonNativeModuleStart + 0x42}, // ip of frame 2
309 {false, 0xaaaa},
310 {false, 0xaaaa},
311 {true, 12}, // fp of frame 3
312 {false, kModuleStart + 0x10}, // ip of frame 3
313 {true, 0xaaaa},
314 {true, 0xaaaa},
315 {false, 0},
316 {false, 1},
317 });
318
319 RegisterContext context;
320 RegisterContextStackPointer(&context) = input.bottom();
321 RegisterContextInstructionPointer(&context) = kModuleStart;
322 RegisterContextFramePointer(&context) = input.bottom();
323 std::vector<Frame> stack = {
324 Frame(RegisterContextInstructionPointer(&context), module())};
325
326 EXPECT_EQ(UnwindResult::kUnrecognizedFrame,
327 unwinder()->TryUnwind(/*state_capture=*/nullptr, &context,
328 input.top(), &stack));
329 EXPECT_EQ(std::vector<Frame>({
330 {kModuleStart, module()},
331 {kModuleStart + 0x20, module()},
332 {kNonNativeModuleStart + 0x42, non_native_module()},
333 }),
334 stack);
335 }
336
337 // Tests that a V8 frame with an unaligned frame pointer correctly returns
338 // kUnrecognizedFrame and not kAborted.
TEST_F(FramePointerUnwinderTest,NonNativeUnaligned)339 TEST_F(FramePointerUnwinderTest, NonNativeUnaligned) {
340 InputStack input({
341 {true, 4}, // fp of frame 1
342 {false, kModuleStart + 0x20}, // ip of frame 1
343 {false, 0xaaaa},
344 {false, 0xaaaa},
345 {true, 7}, // fp of frame 2
346 {false, kNonNativeModuleStart + 0x42}, // ip of frame 2
347 {false, 0xaaaa},
348 {true, 10}, // fp of frame 3
349 {false, kModuleStart + 0x10}, // ip of frame 3
350 {true, 0xaaaa},
351 {false, 0},
352 {false, 1},
353 });
354
355 RegisterContext context;
356 RegisterContextStackPointer(&context) = input.bottom();
357 RegisterContextInstructionPointer(&context) = kModuleStart;
358 RegisterContextFramePointer(&context) = input.bottom();
359 std::vector<Frame> stack = {
360 Frame(RegisterContextInstructionPointer(&context), module())};
361
362 EXPECT_EQ(UnwindResult::kUnrecognizedFrame,
363 unwinder()->TryUnwind(/*state_capture=*/nullptr, &context,
364 input.top(), &stack));
365 }
366
367 } // namespace base
368