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