1 // Copyright 2019 The Chromium Authors. All rights reserved.
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 <algorithm>
6 #include <cstring>
7 #include <iterator>
8 #include <memory>
9 #include <numeric>
10 #include <utility>
11
12 #include "base/bind.h"
13 #include "base/memory/ptr_util.h"
14 #include "base/profiler/module_cache.h"
15 #include "base/profiler/profile_builder.h"
16 #include "base/profiler/stack_buffer.h"
17 #include "base/profiler/stack_copier.h"
18 #include "base/profiler/stack_sampler_impl.h"
19 #include "base/profiler/suspendable_thread_delegate.h"
20 #include "base/profiler/unwinder.h"
21 #include "base/stl_util.h"
22 #include "build/build_config.h"
23 #include "testing/gmock/include/gmock/gmock.h"
24 #include "testing/gtest/include/gtest/gtest.h"
25
26 namespace base {
27
28 namespace {
29
30 using ::testing::ElementsAre;
31
32 class TestProfileBuilder : public ProfileBuilder {
33 public:
TestProfileBuilder(ModuleCache * module_cache)34 TestProfileBuilder(ModuleCache* module_cache) : module_cache_(module_cache) {}
35
36 TestProfileBuilder(const TestProfileBuilder&) = delete;
37 TestProfileBuilder& operator=(const TestProfileBuilder&) = delete;
38
39 // ProfileBuilder
GetModuleCache()40 ModuleCache* GetModuleCache() override { return module_cache_; }
RecordMetadata(const MetadataRecorder::MetadataProvider & metadata_provider)41 void RecordMetadata(
42 const MetadataRecorder::MetadataProvider& metadata_provider) override {}
43
OnSampleCompleted(std::vector<Frame> frames,TimeTicks sample_timestamp)44 void OnSampleCompleted(std::vector<Frame> frames,
45 TimeTicks sample_timestamp) override {
46 last_timestamp_ = sample_timestamp;
47 }
48
OnProfileCompleted(TimeDelta profile_duration,TimeDelta sampling_period)49 void OnProfileCompleted(TimeDelta profile_duration,
50 TimeDelta sampling_period) override {}
51
last_timestamp()52 TimeTicks last_timestamp() { return last_timestamp_; }
53
54 private:
55 ModuleCache* module_cache_;
56 TimeTicks last_timestamp_;
57 };
58
59 // A stack copier for use in tests that provides the expected behavior when
60 // operating on the supplied fake stack.
61 class TestStackCopier : public StackCopier {
62 public:
TestStackCopier(const std::vector<uintptr_t> & fake_stack,TimeTicks timestamp=TimeTicks ())63 TestStackCopier(const std::vector<uintptr_t>& fake_stack,
64 TimeTicks timestamp = TimeTicks())
65 : fake_stack_(fake_stack), timestamp_(timestamp) {}
66
CopyStack(StackBuffer * stack_buffer,uintptr_t * stack_top,TimeTicks * timestamp,RegisterContext * thread_context,Delegate * delegate)67 bool CopyStack(StackBuffer* stack_buffer,
68 uintptr_t* stack_top,
69 TimeTicks* timestamp,
70 RegisterContext* thread_context,
71 Delegate* delegate) override {
72 std::memcpy(stack_buffer->buffer(), &fake_stack_[0], fake_stack_.size());
73 *stack_top =
74 reinterpret_cast<uintptr_t>(&fake_stack_[0] + fake_stack_.size());
75 // Set the stack pointer to be consistent with the provided fake stack.
76 *thread_context = {};
77 RegisterContextStackPointer(thread_context) =
78 reinterpret_cast<uintptr_t>(&fake_stack_[0]);
79
80 *timestamp = timestamp_;
81
82 return true;
83 }
84
85 private:
86 // Must be a reference to retain the underlying allocation from the vector
87 // passed to the constructor.
88 const std::vector<uintptr_t>& fake_stack_;
89
90 const TimeTicks timestamp_;
91 };
92
93 // A StackCopier that just invokes the expected functions on the delegate.
94 class DelegateInvokingStackCopier : public StackCopier {
95 public:
CopyStack(StackBuffer * stack_buffer,uintptr_t * stack_top,TimeTicks * timestamp,RegisterContext * thread_context,Delegate * delegate)96 bool CopyStack(StackBuffer* stack_buffer,
97 uintptr_t* stack_top,
98 TimeTicks* timestamp,
99 RegisterContext* thread_context,
100 Delegate* delegate) override {
101 delegate->OnStackCopy();
102 return true;
103 }
104 };
105
106 // Trivial unwinder implementation for testing.
107 class TestUnwinder : public Unwinder {
108 public:
TestUnwinder(size_t stack_size=0,std::vector<uintptr_t> * stack_copy=nullptr,uintptr_t * stack_copy_bottom=nullptr)109 TestUnwinder(size_t stack_size = 0,
110 std::vector<uintptr_t>* stack_copy = nullptr,
111 // Variable to fill in with the bottom address of the
112 // copied stack. This will be different than
113 // &(*stack_copy)[0] because |stack_copy| is a copy of the
114 // copy so does not share memory with the actual copy.
115 uintptr_t* stack_copy_bottom = nullptr)
116 : stack_size_(stack_size),
117 stack_copy_(stack_copy),
118 stack_copy_bottom_(stack_copy_bottom) {}
119
CanUnwindFrom(const Frame & current_frame) const120 bool CanUnwindFrom(const Frame& current_frame) const override { return true; }
121
TryUnwind(RegisterContext * thread_context,uintptr_t stack_top,ModuleCache * module_cache,std::vector<Frame> * stack) const122 UnwindResult TryUnwind(RegisterContext* thread_context,
123 uintptr_t stack_top,
124 ModuleCache* module_cache,
125 std::vector<Frame>* stack) const override {
126 if (stack_copy_) {
127 auto* bottom = reinterpret_cast<uintptr_t*>(
128 RegisterContextStackPointer(thread_context));
129 auto* top = bottom + stack_size_;
130 *stack_copy_ = std::vector<uintptr_t>(bottom, top);
131 }
132 if (stack_copy_bottom_)
133 *stack_copy_bottom_ = RegisterContextStackPointer(thread_context);
134 return UnwindResult::COMPLETED;
135 }
136
137 private:
138 size_t stack_size_;
139 std::vector<uintptr_t>* stack_copy_;
140 uintptr_t* stack_copy_bottom_;
141 };
142
143 // Records invocations of calls to OnStackCapture()/UpdateModules().
144 class CallRecordingUnwinder : public Unwinder {
145 public:
OnStackCapture()146 void OnStackCapture() override { on_stack_capture_was_invoked_ = true; }
147
UpdateModules(ModuleCache *)148 void UpdateModules(ModuleCache*) override {
149 update_modules_was_invoked_ = true;
150 }
151
CanUnwindFrom(const Frame & current_frame) const152 bool CanUnwindFrom(const Frame& current_frame) const override { return true; }
153
TryUnwind(RegisterContext * thread_context,uintptr_t stack_top,ModuleCache * module_cache,std::vector<Frame> * stack) const154 UnwindResult TryUnwind(RegisterContext* thread_context,
155 uintptr_t stack_top,
156 ModuleCache* module_cache,
157 std::vector<Frame>* stack) const override {
158 return UnwindResult::UNRECOGNIZED_FRAME;
159 }
160
on_stack_capture_was_invoked() const161 bool on_stack_capture_was_invoked() const {
162 return on_stack_capture_was_invoked_;
163 }
164
update_modules_was_invoked() const165 bool update_modules_was_invoked() const {
166 return update_modules_was_invoked_;
167 }
168
169 private:
170 bool on_stack_capture_was_invoked_ = false;
171 bool update_modules_was_invoked_ = false;
172 };
173
174 class TestModule : public ModuleCache::Module {
175 public:
TestModule(uintptr_t base_address,size_t size,bool is_native=true)176 TestModule(uintptr_t base_address, size_t size, bool is_native = true)
177 : base_address_(base_address), size_(size), is_native_(is_native) {}
178
GetBaseAddress() const179 uintptr_t GetBaseAddress() const override { return base_address_; }
GetId() const180 std::string GetId() const override { return ""; }
GetDebugBasename() const181 FilePath GetDebugBasename() const override { return FilePath(); }
GetSize() const182 size_t GetSize() const override { return size_; }
IsNative() const183 bool IsNative() const override { return is_native_; }
184
185 private:
186 const uintptr_t base_address_;
187 const size_t size_;
188 const bool is_native_;
189 };
190
191 // Utility function to form a vector from a single module.
ToModuleVector(std::unique_ptr<const ModuleCache::Module> module)192 std::vector<std::unique_ptr<const ModuleCache::Module>> ToModuleVector(
193 std::unique_ptr<const ModuleCache::Module> module) {
194 return std::vector<std::unique_ptr<const ModuleCache::Module>>(
195 std::make_move_iterator(&module), std::make_move_iterator(&module + 1));
196 }
197
198 // Injects a fake module covering the initial instruction pointer value, to
199 // avoid asking the OS to look it up. Windows doesn't return a consistent error
200 // code when doing so, and we DCHECK_EQ the expected error code.
InjectModuleForContextInstructionPointer(const std::vector<uintptr_t> & stack,ModuleCache * module_cache)201 void InjectModuleForContextInstructionPointer(
202 const std::vector<uintptr_t>& stack,
203 ModuleCache* module_cache) {
204 module_cache->AddCustomNativeModule(
205 std::make_unique<TestModule>(stack[0], sizeof(uintptr_t)));
206 }
207
208 // Returns a plausible instruction pointer value for use in tests that don't
209 // care about the instruction pointer value in the context, and hence don't need
210 // InjectModuleForContextInstructionPointer().
GetTestInstructionPointer()211 uintptr_t GetTestInstructionPointer() {
212 return reinterpret_cast<uintptr_t>(&GetTestInstructionPointer);
213 }
214
215 // An unwinder fake that replays the provided outputs.
216 class FakeTestUnwinder : public Unwinder {
217 public:
218 struct Result {
Resultbase::__anon370ecfce0111::FakeTestUnwinder::Result219 Result(bool can_unwind)
220 : can_unwind(can_unwind), result(UnwindResult::UNRECOGNIZED_FRAME) {}
221
Resultbase::__anon370ecfce0111::FakeTestUnwinder::Result222 Result(UnwindResult result, std::vector<uintptr_t> instruction_pointers)
223 : can_unwind(true),
224 result(result),
225 instruction_pointers(instruction_pointers) {}
226
227 bool can_unwind;
228 UnwindResult result;
229 std::vector<uintptr_t> instruction_pointers;
230 };
231
232 // Construct the unwinder with the outputs. The relevant unwinder functions
233 // are expected to be invoked at least as many times as the number of values
234 // specified in the arrays (except for CanUnwindFrom() which will always
235 // return true if provided an empty array.
FakeTestUnwinder(std::vector<Result> results)236 explicit FakeTestUnwinder(std::vector<Result> results)
237 : results_(std::move(results)) {}
238
239 FakeTestUnwinder(const FakeTestUnwinder&) = delete;
240 FakeTestUnwinder& operator=(const FakeTestUnwinder&) = delete;
241
CanUnwindFrom(const Frame & current_frame) const242 bool CanUnwindFrom(const Frame& current_frame) const override {
243 bool can_unwind = results_[current_unwind_].can_unwind;
244 // NB: If CanUnwindFrom() returns false then TryUnwind() will not be
245 // invoked, so current_unwind_ is guarantee to be incremented only once for
246 // each result.
247 if (!can_unwind)
248 ++current_unwind_;
249 return can_unwind;
250 }
251
TryUnwind(RegisterContext * thread_context,uintptr_t stack_top,ModuleCache * module_cache,std::vector<Frame> * stack) const252 UnwindResult TryUnwind(RegisterContext* thread_context,
253 uintptr_t stack_top,
254 ModuleCache* module_cache,
255 std::vector<Frame>* stack) const override {
256 CHECK_LT(current_unwind_, results_.size());
257 const Result& current_result = results_[current_unwind_];
258 ++current_unwind_;
259 CHECK(current_result.can_unwind);
260 for (const auto instruction_pointer : current_result.instruction_pointers)
261 stack->emplace_back(
262 instruction_pointer,
263 module_cache->GetModuleForAddress(instruction_pointer));
264 return current_result.result;
265 }
266
267 private:
268 mutable size_t current_unwind_ = 0;
269 std::vector<Result> results_;
270 };
271
MakeUnwindersFactory(std::unique_ptr<Unwinder> unwinder)272 StackSampler::UnwindersFactory MakeUnwindersFactory(
273 std::unique_ptr<Unwinder> unwinder) {
274 return BindOnce(
275 [](std::unique_ptr<Unwinder> unwinder) {
276 std::vector<std::unique_ptr<Unwinder>> unwinders;
277 unwinders.push_back(std::move(unwinder));
278 return unwinders;
279 },
280 std::move(unwinder));
281 }
282
MakeUnwinderCircularDeque(std::unique_ptr<Unwinder> native_unwinder,std::unique_ptr<Unwinder> aux_unwinder)283 base::circular_deque<std::unique_ptr<Unwinder>> MakeUnwinderCircularDeque(
284 std::unique_ptr<Unwinder> native_unwinder,
285 std::unique_ptr<Unwinder> aux_unwinder) {
286 base::circular_deque<std::unique_ptr<Unwinder>> unwinders;
287 if (native_unwinder)
288 unwinders.push_front(std::move(native_unwinder));
289 if (aux_unwinder)
290 unwinders.push_front(std::move(aux_unwinder));
291 return unwinders;
292 }
293
294 } // namespace
295
296 // TODO(crbug.com/1001923): Fails on Linux MSan.
297 #if defined(OS_LINUX) || defined(OS_CHROMEOS)
298 #define MAYBE_CopyStack DISABLED_MAYBE_CopyStack
299 #else
300 #define MAYBE_CopyStack CopyStack
301 #endif
TEST(StackSamplerImplTest,MAYBE_CopyStack)302 TEST(StackSamplerImplTest, MAYBE_CopyStack) {
303 ModuleCache module_cache;
304 const std::vector<uintptr_t> stack = {0, 1, 2, 3, 4};
305 InjectModuleForContextInstructionPointer(stack, &module_cache);
306 std::vector<uintptr_t> stack_copy;
307 StackSamplerImpl stack_sampler_impl(
308 std::make_unique<TestStackCopier>(stack),
309 MakeUnwindersFactory(
310 std::make_unique<TestUnwinder>(stack.size(), &stack_copy)),
311 &module_cache);
312
313 stack_sampler_impl.Initialize();
314
315 std::unique_ptr<StackBuffer> stack_buffer =
316 std::make_unique<StackBuffer>(stack.size() * sizeof(uintptr_t));
317 TestProfileBuilder profile_builder(&module_cache);
318 stack_sampler_impl.RecordStackFrames(stack_buffer.get(), &profile_builder);
319
320 EXPECT_EQ(stack, stack_copy);
321 }
322
TEST(StackSamplerImplTest,CopyStackTimestamp)323 TEST(StackSamplerImplTest, CopyStackTimestamp) {
324 ModuleCache module_cache;
325 const std::vector<uintptr_t> stack = {0};
326 InjectModuleForContextInstructionPointer(stack, &module_cache);
327 std::vector<uintptr_t> stack_copy;
328 TimeTicks timestamp = TimeTicks::UnixEpoch();
329 StackSamplerImpl stack_sampler_impl(
330 std::make_unique<TestStackCopier>(stack, timestamp),
331 MakeUnwindersFactory(
332 std::make_unique<TestUnwinder>(stack.size(), &stack_copy)),
333 &module_cache);
334
335 stack_sampler_impl.Initialize();
336
337 std::unique_ptr<StackBuffer> stack_buffer =
338 std::make_unique<StackBuffer>(stack.size() * sizeof(uintptr_t));
339 TestProfileBuilder profile_builder(&module_cache);
340 stack_sampler_impl.RecordStackFrames(stack_buffer.get(), &profile_builder);
341
342 EXPECT_EQ(timestamp, profile_builder.last_timestamp());
343 }
344
TEST(StackSamplerImplTest,UnwinderInvokedWhileRecordingStackFrames)345 TEST(StackSamplerImplTest, UnwinderInvokedWhileRecordingStackFrames) {
346 std::unique_ptr<StackBuffer> stack_buffer = std::make_unique<StackBuffer>(10);
347 auto owned_unwinder = std::make_unique<CallRecordingUnwinder>();
348 CallRecordingUnwinder* unwinder = owned_unwinder.get();
349 ModuleCache module_cache;
350 TestProfileBuilder profile_builder(&module_cache);
351 StackSamplerImpl stack_sampler_impl(
352 std::make_unique<DelegateInvokingStackCopier>(),
353 MakeUnwindersFactory(std::move(owned_unwinder)), &module_cache);
354
355 stack_sampler_impl.Initialize();
356
357 stack_sampler_impl.RecordStackFrames(stack_buffer.get(), &profile_builder);
358
359 EXPECT_TRUE(unwinder->on_stack_capture_was_invoked());
360 EXPECT_TRUE(unwinder->update_modules_was_invoked());
361 }
362
TEST(StackSamplerImplTest,AuxUnwinderInvokedWhileRecordingStackFrames)363 TEST(StackSamplerImplTest, AuxUnwinderInvokedWhileRecordingStackFrames) {
364 std::unique_ptr<StackBuffer> stack_buffer = std::make_unique<StackBuffer>(10);
365 ModuleCache module_cache;
366 TestProfileBuilder profile_builder(&module_cache);
367 StackSamplerImpl stack_sampler_impl(
368 std::make_unique<DelegateInvokingStackCopier>(),
369 MakeUnwindersFactory(std::make_unique<CallRecordingUnwinder>()),
370 &module_cache);
371
372 stack_sampler_impl.Initialize();
373
374 auto owned_aux_unwinder = std::make_unique<CallRecordingUnwinder>();
375 CallRecordingUnwinder* aux_unwinder = owned_aux_unwinder.get();
376 stack_sampler_impl.AddAuxUnwinder(std::move(owned_aux_unwinder));
377
378 stack_sampler_impl.RecordStackFrames(stack_buffer.get(), &profile_builder);
379
380 EXPECT_TRUE(aux_unwinder->on_stack_capture_was_invoked());
381 EXPECT_TRUE(aux_unwinder->update_modules_was_invoked());
382 }
383
TEST(StackSamplerImplTest,WalkStack_Completed)384 TEST(StackSamplerImplTest, WalkStack_Completed) {
385 ModuleCache module_cache;
386 RegisterContext thread_context;
387 RegisterContextInstructionPointer(&thread_context) =
388 GetTestInstructionPointer();
389 module_cache.AddCustomNativeModule(std::make_unique<TestModule>(1u, 1u));
390 auto native_unwinder =
391 WrapUnique(new FakeTestUnwinder({{UnwindResult::COMPLETED, {1u}}}));
392
393 std::vector<Frame> stack = StackSamplerImpl::WalkStackForTesting(
394 &module_cache, &thread_context, 0u,
395 MakeUnwinderCircularDeque(std::move(native_unwinder), nullptr));
396
397 ASSERT_EQ(2u, stack.size());
398 EXPECT_EQ(1u, stack[1].instruction_pointer);
399 }
400
TEST(StackSamplerImplTest,WalkStack_Aborted)401 TEST(StackSamplerImplTest, WalkStack_Aborted) {
402 ModuleCache module_cache;
403 RegisterContext thread_context;
404 RegisterContextInstructionPointer(&thread_context) =
405 GetTestInstructionPointer();
406 module_cache.AddCustomNativeModule(std::make_unique<TestModule>(1u, 1u));
407 auto native_unwinder =
408 WrapUnique(new FakeTestUnwinder({{UnwindResult::ABORTED, {1u}}}));
409
410 std::vector<Frame> stack = StackSamplerImpl::WalkStackForTesting(
411 &module_cache, &thread_context, 0u,
412 MakeUnwinderCircularDeque(std::move(native_unwinder), nullptr));
413
414 ASSERT_EQ(2u, stack.size());
415 EXPECT_EQ(1u, stack[1].instruction_pointer);
416 }
417
TEST(StackSamplerImplTest,WalkStack_NotUnwound)418 TEST(StackSamplerImplTest, WalkStack_NotUnwound) {
419 ModuleCache module_cache;
420 RegisterContext thread_context;
421 RegisterContextInstructionPointer(&thread_context) =
422 GetTestInstructionPointer();
423 auto native_unwinder = WrapUnique(
424 new FakeTestUnwinder({{UnwindResult::UNRECOGNIZED_FRAME, {}}}));
425
426 std::vector<Frame> stack = StackSamplerImpl::WalkStackForTesting(
427 &module_cache, &thread_context, 0u,
428 MakeUnwinderCircularDeque(std::move(native_unwinder), nullptr));
429
430 ASSERT_EQ(1u, stack.size());
431 }
432
TEST(StackSamplerImplTest,WalkStack_AuxUnwind)433 TEST(StackSamplerImplTest, WalkStack_AuxUnwind) {
434 ModuleCache module_cache;
435 RegisterContext thread_context;
436 RegisterContextInstructionPointer(&thread_context) =
437 GetTestInstructionPointer();
438
439 // Treat the context instruction pointer as being in the aux unwinder's
440 // non-native module.
441 module_cache.UpdateNonNativeModules(
442 {}, ToModuleVector(std::make_unique<TestModule>(
443 GetTestInstructionPointer(), 1u, false)));
444
445 auto aux_unwinder =
446 WrapUnique(new FakeTestUnwinder({{UnwindResult::ABORTED, {1u}}}));
447 std::vector<Frame> stack = StackSamplerImpl::WalkStackForTesting(
448 &module_cache, &thread_context, 0u,
449 MakeUnwinderCircularDeque(nullptr, std::move(aux_unwinder)));
450
451 ASSERT_EQ(2u, stack.size());
452 EXPECT_EQ(GetTestInstructionPointer(), stack[0].instruction_pointer);
453 EXPECT_EQ(1u, stack[1].instruction_pointer);
454 }
455
TEST(StackSamplerImplTest,WalkStack_AuxThenNative)456 TEST(StackSamplerImplTest, WalkStack_AuxThenNative) {
457 ModuleCache module_cache;
458 RegisterContext thread_context;
459 RegisterContextInstructionPointer(&thread_context) = 0u;
460
461 // Treat the context instruction pointer as being in the aux unwinder's
462 // non-native module.
463 module_cache.UpdateNonNativeModules(
464 {}, ToModuleVector(std::make_unique<TestModule>(0u, 1u, false)));
465 // Inject a fake native module for the second frame.
466 module_cache.AddCustomNativeModule(std::make_unique<TestModule>(1u, 1u));
467
468 auto aux_unwinder = WrapUnique(
469 new FakeTestUnwinder({{UnwindResult::UNRECOGNIZED_FRAME, {1u}}, false}));
470 auto native_unwinder =
471 WrapUnique(new FakeTestUnwinder({{UnwindResult::COMPLETED, {2u}}}));
472
473 std::vector<Frame> stack = StackSamplerImpl::WalkStackForTesting(
474 &module_cache, &thread_context, 0u,
475 MakeUnwinderCircularDeque(std::move(native_unwinder),
476 std::move(aux_unwinder)));
477
478 ASSERT_EQ(3u, stack.size());
479 EXPECT_EQ(0u, stack[0].instruction_pointer);
480 EXPECT_EQ(1u, stack[1].instruction_pointer);
481 EXPECT_EQ(2u, stack[2].instruction_pointer);
482 }
483
TEST(StackSamplerImplTest,WalkStack_NativeThenAux)484 TEST(StackSamplerImplTest, WalkStack_NativeThenAux) {
485 ModuleCache module_cache;
486 RegisterContext thread_context;
487 RegisterContextInstructionPointer(&thread_context) = 0u;
488
489 // Inject fake native modules for the instruction pointer from the context and
490 // the third frame.
491 module_cache.AddCustomNativeModule(std::make_unique<TestModule>(0u, 1u));
492 module_cache.AddCustomNativeModule(std::make_unique<TestModule>(2u, 1u));
493 // Treat the second frame's pointer as being in the aux unwinder's non-native
494 // module.
495 module_cache.UpdateNonNativeModules(
496 {}, ToModuleVector(std::make_unique<TestModule>(1u, 1u, false)));
497
498 auto aux_unwinder = WrapUnique(new FakeTestUnwinder(
499 {{false}, {UnwindResult::UNRECOGNIZED_FRAME, {2u}}, {false}}));
500 auto native_unwinder =
501 WrapUnique(new FakeTestUnwinder({{UnwindResult::UNRECOGNIZED_FRAME, {1u}},
502 {UnwindResult::COMPLETED, {3u}}}));
503
504 std::vector<Frame> stack = StackSamplerImpl::WalkStackForTesting(
505 &module_cache, &thread_context, 0u,
506 MakeUnwinderCircularDeque(std::move(native_unwinder),
507 std::move(aux_unwinder)));
508
509 ASSERT_EQ(4u, stack.size());
510 EXPECT_EQ(0u, stack[0].instruction_pointer);
511 EXPECT_EQ(1u, stack[1].instruction_pointer);
512 EXPECT_EQ(2u, stack[2].instruction_pointer);
513 EXPECT_EQ(3u, stack[3].instruction_pointer);
514 }
515
516 } // namespace base
517