xref: /aosp_15_r20/external/cronet/base/profiler/frame_pointer_unwinder_unittest.cc (revision 6777b5387eb2ff775bb5750e3f5d96f37fb7352b)
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::__anoned8b1ce60111::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::__anoned8b1ce60111::InputStack52   uintptr_t bottom() const {
53     return reinterpret_cast<uintptr_t>(buffer.data());
54   }
topbase::__anoned8b1ce60111::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,FPPointsOutsideOfStack)100 TEST_F(FramePointerUnwinderTest, FPPointsOutsideOfStack) {
101   InputStack input({
102       {false, 0x1000},
103       {false, 0x1000},
104       {false, 0x1000},
105       {false, 0x1000},
106       {false, 0x1000},
107   });
108 
109   RegisterContext context;
110   RegisterContextStackPointer(&context) = input.bottom();
111   RegisterContextInstructionPointer(&context) = kModuleStart;
112   RegisterContextFramePointer(&context) = 0x1;
113   std::vector<Frame> stack = {
114       Frame(RegisterContextInstructionPointer(&context), module())};
115 
116   EXPECT_EQ(UnwindResult::kAborted,
117             unwinder()->TryUnwind(&context, input.top(), &stack));
118   EXPECT_EQ(std::vector<Frame>({{kModuleStart, module()}}), stack);
119 
120   RegisterContextFramePointer(&context) = input.bottom() - sizeof(uintptr_t);
121   EXPECT_EQ(UnwindResult::kAborted,
122             unwinder()->TryUnwind(&context, input.top(), &stack));
123   EXPECT_EQ(std::vector<Frame>({{kModuleStart, module()}}), stack);
124 
125   RegisterContextFramePointer(&context) = input.top();
126   EXPECT_EQ(UnwindResult::kAborted,
127             unwinder()->TryUnwind(&context, input.top(), &stack));
128   EXPECT_EQ(std::vector<Frame>({{kModuleStart, module()}}), stack);
129 }
130 
TEST_F(FramePointerUnwinderTest,FPPointsToSelf)131 TEST_F(FramePointerUnwinderTest, FPPointsToSelf) {
132   InputStack input({
133       {true, 0},
134       {false, kModuleStart + 0x10},
135       {true, 4},
136       {false, kModuleStart + 0x20},
137       {false, 0},
138       {false, 0},
139   });
140 
141   RegisterContext context;
142   RegisterContextStackPointer(&context) = input.bottom();
143   RegisterContextInstructionPointer(&context) = kModuleStart;
144   RegisterContextFramePointer(&context) = input.bottom();
145   std::vector<Frame> stack = {
146       Frame(RegisterContextInstructionPointer(&context), module())};
147 
148   EXPECT_EQ(UnwindResult::kAborted,
149             unwinder()->TryUnwind(&context, input.top(), &stack));
150   EXPECT_EQ(std::vector<Frame>({
151                 {kModuleStart, module()},
152             }),
153             stack);
154 }
155 
156 // Tests that two frame pointers that point to each other can't create an
157 // infinite loop
TEST_F(FramePointerUnwinderTest,FPCycle)158 TEST_F(FramePointerUnwinderTest, FPCycle) {
159   InputStack input({
160       {true, 2},
161       {false, kModuleStart + 0x10},
162       {true, 0},
163       {false, kModuleStart + 0x20},
164       {true, 4},
165       {false, kModuleStart + 0x30},
166       {false, 0},
167       {false, 0},
168   });
169 
170   RegisterContext context;
171   RegisterContextStackPointer(&context) = input.bottom();
172   RegisterContextInstructionPointer(&context) = kModuleStart;
173   RegisterContextFramePointer(&context) = input.bottom();
174   std::vector<Frame> stack = {
175       Frame(RegisterContextInstructionPointer(&context), module())};
176 
177   EXPECT_EQ(UnwindResult::kAborted,
178             unwinder()->TryUnwind(&context, input.top(), &stack));
179   EXPECT_EQ(std::vector<Frame>({
180                 {kModuleStart, module()},
181                 {kModuleStart + 0x10, module()},
182             }),
183             stack);
184 }
185 
TEST_F(FramePointerUnwinderTest,NoModuleForIP)186 TEST_F(FramePointerUnwinderTest, NoModuleForIP) {
187   uintptr_t not_in_module = kModuleStart - 0x10;
188   InputStack input({
189       {true, 2},
190       {false, not_in_module},
191       {true, 4},
192       {true, kModuleStart + 0x10},
193       {false, 0},
194       {false, 0},
195   });
196 
197   RegisterContext context;
198   RegisterContextStackPointer(&context) = input.bottom();
199   RegisterContextInstructionPointer(&context) = kModuleStart;
200   RegisterContextFramePointer(&context) = input.bottom();
201   std::vector<Frame> stack = {
202       Frame(RegisterContextInstructionPointer(&context), module())};
203 
204   EXPECT_EQ(UnwindResult::kAborted,
205             unwinder()->TryUnwind(&context, input.top(), &stack));
206   EXPECT_EQ(
207       std::vector<Frame>({{kModuleStart, module()}, {not_in_module, nullptr}}),
208       stack);
209 }
210 
211 // Tests that testing that checking if there's space to read two values from the
212 // stack doesn't overflow.
TEST_F(FramePointerUnwinderTest,FPAdditionOverflows)213 TEST_F(FramePointerUnwinderTest, FPAdditionOverflows) {
214   uintptr_t will_overflow = std::numeric_limits<uintptr_t>::max() - 1;
215   InputStack input({
216       {true, 2},
217       {false, kModuleStart + 0x10},
218       {false, 0},
219       {false, 0},
220   });
221 
222   RegisterContext context;
223   RegisterContextStackPointer(&context) = input.bottom();
224   RegisterContextInstructionPointer(&context) = kModuleStart;
225   RegisterContextFramePointer(&context) = will_overflow;
226   std::vector<Frame> stack = {
227       Frame(RegisterContextInstructionPointer(&context), module())};
228 
229   EXPECT_EQ(UnwindResult::kAborted,
230             unwinder()->TryUnwind(&context, input.top(), &stack));
231   EXPECT_EQ(std::vector<Frame>({
232                 {kModuleStart, module()},
233             }),
234             stack);
235 }
236 
237 // Tests the happy path: a successful unwind with no non-native modules.
TEST_F(FramePointerUnwinderTest,RegularUnwind)238 TEST_F(FramePointerUnwinderTest, RegularUnwind) {
239   InputStack input({
240       {true, 4},                     // fp of frame 1
241       {false, kModuleStart + 0x20},  // ip of frame 1
242       {false, 0xaaaa},
243       {false, 0xaaaa},
244       {true, 8},                     // fp of frame 2
245       {false, kModuleStart + 0x42},  // ip of frame 2
246       {false, 0xaaaa},
247       {false, 0xaaaa},
248       {false, 0},
249       {false, 1},
250   });
251 
252   RegisterContext context;
253   RegisterContextStackPointer(&context) = input.bottom();
254   RegisterContextInstructionPointer(&context) = kModuleStart;
255   RegisterContextFramePointer(&context) = input.bottom();
256   std::vector<Frame> stack = {
257       Frame(RegisterContextInstructionPointer(&context), module())};
258 
259   EXPECT_EQ(UnwindResult::kCompleted,
260             unwinder()->TryUnwind(&context, input.top(), &stack));
261   EXPECT_EQ(std::vector<Frame>({
262                 {kModuleStart, module()},
263                 {kModuleStart + 0x20, module()},
264                 {kModuleStart + 0x42, module()},
265             }),
266             stack);
267 }
268 
269 // Tests that if a V8 frame is encountered, unwinding stops and
270 // kUnrecognizedFrame is returned to facilitate continuing with the V8 unwinder.
TEST_F(FramePointerUnwinderTest,NonNativeFrame)271 TEST_F(FramePointerUnwinderTest, NonNativeFrame) {
272   InputStack input({
273       {true, 4},                     // fp of frame 1
274       {false, kModuleStart + 0x20},  // ip of frame 1
275       {false, 0xaaaa},
276       {false, 0xaaaa},
277       {true, 8},                              // fp of frame 2
278       {false, kNonNativeModuleStart + 0x42},  // ip of frame 2
279       {false, 0xaaaa},
280       {false, 0xaaaa},
281       {true, 12},                    // fp of frame 3
282       {false, kModuleStart + 0x10},  // ip of frame 3
283       {true, 0xaaaa},
284       {true, 0xaaaa},
285       {false, 0},
286       {false, 1},
287   });
288 
289   RegisterContext context;
290   RegisterContextStackPointer(&context) = input.bottom();
291   RegisterContextInstructionPointer(&context) = kModuleStart;
292   RegisterContextFramePointer(&context) = input.bottom();
293   std::vector<Frame> stack = {
294       Frame(RegisterContextInstructionPointer(&context), module())};
295 
296   EXPECT_EQ(UnwindResult::kUnrecognizedFrame,
297             unwinder()->TryUnwind(&context, input.top(), &stack));
298   EXPECT_EQ(std::vector<Frame>({
299                 {kModuleStart, module()},
300                 {kModuleStart + 0x20, module()},
301                 {kNonNativeModuleStart + 0x42, non_native_module()},
302             }),
303             stack);
304 }
305 
306 // Tests that a V8 frame with an unaligned frame pointer correctly returns
307 // kUnrecognizedFrame and not kAborted.
TEST_F(FramePointerUnwinderTest,NonNativeUnaligned)308 TEST_F(FramePointerUnwinderTest, NonNativeUnaligned) {
309   InputStack input({
310       {true, 4},                     // fp of frame 1
311       {false, kModuleStart + 0x20},  // ip of frame 1
312       {false, 0xaaaa},
313       {false, 0xaaaa},
314       {true, 7},                              // fp of frame 2
315       {false, kNonNativeModuleStart + 0x42},  // ip of frame 2
316       {false, 0xaaaa},
317       {true, 10},                    // fp of frame 3
318       {false, kModuleStart + 0x10},  // ip of frame 3
319       {true, 0xaaaa},
320       {false, 0},
321       {false, 1},
322   });
323 
324   RegisterContext context;
325   RegisterContextStackPointer(&context) = input.bottom();
326   RegisterContextInstructionPointer(&context) = kModuleStart;
327   RegisterContextFramePointer(&context) = input.bottom();
328   std::vector<Frame> stack = {
329       Frame(RegisterContextInstructionPointer(&context), module())};
330 
331   EXPECT_EQ(UnwindResult::kUnrecognizedFrame,
332             unwinder()->TryUnwind(&context, input.top(), &stack));
333 }
334 
335 }  // namespace base
336