• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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