blob: 694b7837173a476eb988edb43debbba054d57074 [file] [log] [blame]
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <algorithm>
#include <cstring>
#include <memory>
#include <numeric>
#include <utility>
#include "base/profiler/profile_builder.h"
#include "base/profiler/stack_sampler_impl.h"
#include "base/profiler/thread_delegate.h"
#include "base/profiler/unwinder.h"
#include "base/sampling_heap_profiler/module_cache.h"
#include "base/stl_util.h"
#include "build/build_config.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace base {
namespace {
using ::testing::ElementsAre;
class TestProfileBuilder : public ProfileBuilder {
public:
TestProfileBuilder(ModuleCache* module_cache) : module_cache_(module_cache) {}
TestProfileBuilder(const TestProfileBuilder&) = delete;
TestProfileBuilder& operator=(const TestProfileBuilder&) = delete;
// ProfileBuilder
ModuleCache* GetModuleCache() override { return module_cache_; }
void RecordMetadata() override {}
void OnSampleCompleted(std::vector<Frame> frames) override {}
void OnProfileCompleted(TimeDelta profile_duration,
TimeDelta sampling_period) override {}
private:
ModuleCache* module_cache_;
};
// A thread delegate for use in tests that provides the expected behavior when
// operating on the supplied fake stack.
class TestThreadDelegate : public ThreadDelegate {
public:
class TestScopedSuspendThread : public ThreadDelegate::ScopedSuspendThread {
public:
TestScopedSuspendThread() = default;
TestScopedSuspendThread(const TestScopedSuspendThread&) = delete;
TestScopedSuspendThread& operator=(const TestScopedSuspendThread&) = delete;
bool WasSuccessful() const override { return true; }
};
TestThreadDelegate(const std::vector<uintptr_t>& fake_stack,
// The register context will be initialized to
// *|thread_context| if non-null.
RegisterContext* thread_context = nullptr)
: fake_stack_(fake_stack),
thread_context_(thread_context) {}
TestThreadDelegate(const TestThreadDelegate&) = delete;
TestThreadDelegate& operator=(const TestThreadDelegate&) = delete;
std::unique_ptr<ScopedSuspendThread> CreateScopedSuspendThread() override {
return std::make_unique<TestScopedSuspendThread>();
}
bool GetThreadContext(RegisterContext* thread_context) override {
if (thread_context_)
*thread_context = *thread_context_;
// Set the stack pointer to be consistent with the provided fake stack.
RegisterContextStackPointer(thread_context) =
reinterpret_cast<uintptr_t>(&fake_stack_[0]);
RegisterContextInstructionPointer(thread_context) =
reinterpret_cast<uintptr_t>(fake_stack_[0]);
return true;
}
uintptr_t GetStackBaseAddress() const override {
return reinterpret_cast<uintptr_t>(&fake_stack_[0] + fake_stack_.size());
}
bool CanCopyStack(uintptr_t stack_pointer) override { return true; }
std::vector<uintptr_t*> GetRegistersToRewrite(
RegisterContext* thread_context) override {
return {&RegisterContextFramePointer(thread_context)};
}
private:
// Must be a reference to retain the underlying allocation from the vector
// passed to the constructor.
const std::vector<uintptr_t>& fake_stack_;
RegisterContext* thread_context_;
};
// Trivial unwinder implementation for testing.
class TestUnwinder : public Unwinder {
public:
TestUnwinder(size_t stack_size = 0,
std::vector<uintptr_t>* stack_copy = nullptr,
// Variable to fill in with the bottom address of the
// copied stack. This will be different than
// &(*stack_copy)[0] because |stack_copy| is a copy of the
// copy so does not share memory with the actual copy.
uintptr_t* stack_copy_bottom = nullptr)
: stack_size_(stack_size),
stack_copy_(stack_copy),
stack_copy_bottom_(stack_copy_bottom) {}
bool CanUnwindFrom(const Frame* current_frame) const override { return true; }
UnwindResult TryUnwind(RegisterContext* thread_context,
uintptr_t stack_top,
ModuleCache* module_cache,
std::vector<Frame>* stack) const override {
if (stack_copy_) {
auto* bottom = reinterpret_cast<uintptr_t*>(
RegisterContextStackPointer(thread_context));
auto* top = bottom + stack_size_;
*stack_copy_ = std::vector<uintptr_t>(bottom, top);
}
if (stack_copy_bottom_)
*stack_copy_bottom_ = RegisterContextStackPointer(thread_context);
return UnwindResult::COMPLETED;
}
private:
size_t stack_size_;
std::vector<uintptr_t>* stack_copy_;
uintptr_t* stack_copy_bottom_;
};
class TestModule : public ModuleCache::Module {
public:
TestModule(uintptr_t base_address, size_t size, bool is_native = true)
: base_address_(base_address), size_(size), is_native_(is_native) {}
uintptr_t GetBaseAddress() const override { return base_address_; }
std::string GetId() const override { return ""; }
FilePath GetDebugBasename() const override { return FilePath(); }
size_t GetSize() const override { return size_; }
bool IsNative() const override { return is_native_; }
private:
const uintptr_t base_address_;
const size_t size_;
const bool is_native_;
};
// Injects a fake module covering the initial instruction pointer value, to
// avoid asking the OS to look it up. Windows doesn't return a consistent error
// code when doing so, and we DCHECK_EQ the expected error code.
void InjectModuleForContextInstructionPointer(
const std::vector<uintptr_t>& stack,
ModuleCache* module_cache) {
module_cache->InjectModuleForTesting(
std::make_unique<TestModule>(stack[0], sizeof(uintptr_t)));
}
// Returns a plausible instruction pointer value for use in tests that don't
// care about the instruction pointer value in the context, and hence don't need
// InjectModuleForContextInstructionPointer().
uintptr_t GetTestInstructionPointer() {
return reinterpret_cast<uintptr_t>(&GetTestInstructionPointer);
}
// An unwinder fake that replays the provided outputs.
class FakeTestUnwinder : public Unwinder {
public:
struct Result {
Result(bool can_unwind)
: can_unwind(can_unwind), result(UnwindResult::UNRECOGNIZED_FRAME) {}
Result(UnwindResult result, std::vector<uintptr_t> instruction_pointers)
: can_unwind(true),
result(result),
instruction_pointers(instruction_pointers) {}
bool can_unwind;
UnwindResult result;
std::vector<uintptr_t> instruction_pointers;
};
// Construct the unwinder with the outputs. The relevant unwinder functions
// are expected to be invoked at least as many times as the number of values
// specified in the arrays (except for CanUnwindFrom() which will always
// return true if provided an empty array.
explicit FakeTestUnwinder(std::vector<Result> results)
: results_(std::move(results)) {}
FakeTestUnwinder(const FakeTestUnwinder&) = delete;
FakeTestUnwinder& operator=(const FakeTestUnwinder&) = delete;
bool CanUnwindFrom(const Frame* current_frame) const override {
bool can_unwind = results_[current_unwind_].can_unwind;
// NB: If CanUnwindFrom() returns false then TryUnwind() will not be
// invoked, so current_unwind_ is guarantee to be incremented only once for
// each result.
if (!can_unwind)
++current_unwind_;
return can_unwind;
}
UnwindResult TryUnwind(RegisterContext* thread_context,
uintptr_t stack_top,
ModuleCache* module_cache,
std::vector<Frame>* stack) const override {
CHECK_LT(current_unwind_, results_.size());
const Result& current_result = results_[current_unwind_];
++current_unwind_;
CHECK(current_result.can_unwind);
for (const auto instruction_pointer : current_result.instruction_pointers)
stack->emplace_back(
instruction_pointer,
module_cache->GetModuleForAddress(instruction_pointer));
return current_result.result;
}
private:
mutable size_t current_unwind_ = 0;
std::vector<Result> results_;
};
static constexpr size_t kTestStackBufferSize = sizeof(uintptr_t) * 4;
union alignas(StackSampler::StackBuffer::kPlatformStackAlignment)
TestStackBuffer {
uintptr_t as_uintptr[kTestStackBufferSize / sizeof(uintptr_t)];
uint16_t as_uint16[kTestStackBufferSize / sizeof(uint16_t)];
uint8_t as_uint8[kTestStackBufferSize / sizeof(uint8_t)];
};
} // namespace
TEST(StackSamplerImplTest, RewritePointerIfInOriginalStack_InStack) {
uintptr_t original_stack[4];
uintptr_t stack_copy[4];
EXPECT_EQ(reinterpret_cast<uintptr_t>(&stack_copy[2]),
RewritePointerIfInOriginalStack(
reinterpret_cast<uint8_t*>(&original_stack[0]),
&original_stack[0] + base::size(original_stack),
reinterpret_cast<uint8_t*>(&stack_copy[0]),
reinterpret_cast<uintptr_t>(&original_stack[2])));
}
TEST(StackSamplerImplTest, RewritePointerIfInOriginalStack_NotInStack) {
// We use this variable only for its address, which is outside of
// original_stack.
uintptr_t non_stack_location;
uintptr_t original_stack[4];
uintptr_t stack_copy[4];
EXPECT_EQ(reinterpret_cast<uintptr_t>(&non_stack_location),
RewritePointerIfInOriginalStack(
reinterpret_cast<uint8_t*>(&original_stack[0]),
&original_stack[0] + size(original_stack),
reinterpret_cast<uint8_t*>(&stack_copy[0]),
reinterpret_cast<uintptr_t>(&non_stack_location)));
}
TEST(StackSamplerImplTest, StackCopy) {
TestStackBuffer original_stack;
// Fill the stack buffer with increasing uintptr_t values.
std::iota(&original_stack.as_uintptr[0],
&original_stack.as_uintptr[0] + size(original_stack.as_uintptr),
100);
// Replace the third value with an address within the buffer.
original_stack.as_uintptr[2] =
reinterpret_cast<uintptr_t>(&original_stack.as_uintptr[1]);
TestStackBuffer stack_copy;
CopyStackContentsAndRewritePointers(
&original_stack.as_uint8[0],
&original_stack.as_uintptr[0] + size(original_stack.as_uintptr),
&stack_copy.as_uintptr[0]);
EXPECT_EQ(original_stack.as_uintptr[0], stack_copy.as_uintptr[0]);
EXPECT_EQ(original_stack.as_uintptr[1], stack_copy.as_uintptr[1]);
EXPECT_EQ(reinterpret_cast<uintptr_t>(&stack_copy.as_uintptr[1]),
stack_copy.as_uintptr[2]);
EXPECT_EQ(original_stack.as_uintptr[3], stack_copy.as_uintptr[3]);
}
TEST(StackSamplerImplTest, StackCopy_NonAlignedStackPointerCopy) {
TestStackBuffer stack_buffer;
// Fill the stack buffer with increasing uint16_t values.
std::iota(&stack_buffer.as_uint16[0],
&stack_buffer.as_uint16[0] + size(stack_buffer.as_uint16), 100);
// Set the stack bottom to the unaligned location one uint16_t into the
// buffer.
uint8_t* unaligned_stack_bottom =
reinterpret_cast<uint8_t*>(&stack_buffer.as_uint16[1]);
// Leave extra space within the stack buffer beyond the end of the stack, but
// preserve the platform alignment.
const size_t extra_space = StackSampler::StackBuffer::kPlatformStackAlignment;
uintptr_t* stack_top =
&stack_buffer.as_uintptr[size(stack_buffer.as_uintptr) -
extra_space / sizeof(uintptr_t)];
// Initialize the copy to all zeros.
TestStackBuffer stack_copy_buffer = {{0}};
const uint8_t* stack_copy_bottom = CopyStackContentsAndRewritePointers(
unaligned_stack_bottom, stack_top, &stack_copy_buffer.as_uintptr[0]);
// The stack copy bottom address is expected to be at the same offset into the
// stack copy buffer as the unaligned stack bottom is from the stack buffer.
// Since the buffers have the same platform stack alignment this also ensures
// the alignment of the bottom addresses is the same.
EXPECT_EQ(unaligned_stack_bottom - &stack_buffer.as_uint8[0],
stack_copy_bottom - &stack_copy_buffer.as_uint8[0]);
// The first value in the copy should not be overwritten since the stack
// starts at the second uint16_t.
EXPECT_EQ(0u, stack_copy_buffer.as_uint16[0]);
// The next values up to the extra space should have been copied.
const size_t max_index =
size(stack_copy_buffer.as_uint16) - extra_space / sizeof(uint16_t);
for (size_t i = 1; i < max_index; ++i)
EXPECT_EQ(i + 100, stack_copy_buffer.as_uint16[i]);
// None of the values in the empty space should have been copied.
for (size_t i = max_index; i < size(stack_copy_buffer.as_uint16); ++i)
EXPECT_EQ(0u, stack_copy_buffer.as_uint16[i]);
}
// Checks that an unaligned within-stack pointer value at the start of the stack
// is not rewritten.
TEST(StackSamplerImplTest,
StackCopy_NonAlignedStackPointerUnalignedRewriteAtStart) {
// Initially fill the buffer with 0s.
TestStackBuffer stack_buffer = {{0}};
// Set the stack bottom to the unaligned location one uint16_t into the
// buffer.
uint8_t* unaligned_stack_bottom =
reinterpret_cast<uint8_t*>(&stack_buffer.as_uint16[1]);
// Set the first unaligned pointer-sized value to an address within the stack.
uintptr_t within_stack_pointer =
reinterpret_cast<uintptr_t>(&stack_buffer.as_uintptr[2]);
std::memcpy(unaligned_stack_bottom, &within_stack_pointer,
sizeof(within_stack_pointer));
TestStackBuffer stack_copy_buffer = {{0}};
const uint8_t* stack_copy_bottom = CopyStackContentsAndRewritePointers(
unaligned_stack_bottom,
&stack_buffer.as_uintptr[0] + size(stack_buffer.as_uintptr),
&stack_copy_buffer.as_uintptr[0]);
uintptr_t copied_within_stack_pointer;
std::memcpy(&copied_within_stack_pointer, stack_copy_bottom,
sizeof(copied_within_stack_pointer));
// The rewriting should only operate on pointer-aligned values so the
// unaligned value should be copied verbatim.
EXPECT_EQ(within_stack_pointer, copied_within_stack_pointer);
}
// Checks that an unaligned within-stack pointer after the start of the stack is
// not rewritten.
TEST(StackSamplerImplTest,
StackCopy_NonAlignedStackPointerUnalignedRewriteAfterStart) {
// Initially fill the buffer with 0s.
TestStackBuffer stack_buffer = {{0}};
// Set the stack bottom to the unaligned location one uint16_t into the
// buffer.
uint8_t* unaligned_stack_bottom =
reinterpret_cast<uint8_t*>(&stack_buffer.as_uint16[1]);
// Set the second unaligned pointer-sized value to an address within the
// stack.
uintptr_t within_stack_pointer =
reinterpret_cast<uintptr_t>(&stack_buffer.as_uintptr[2]);
std::memcpy(unaligned_stack_bottom + sizeof(uintptr_t), &within_stack_pointer,
sizeof(within_stack_pointer));
TestStackBuffer stack_copy_buffer = {{0}};
const uint8_t* stack_copy_bottom = CopyStackContentsAndRewritePointers(
unaligned_stack_bottom,
&stack_buffer.as_uintptr[0] + size(stack_buffer.as_uintptr),
&stack_copy_buffer.as_uintptr[0]);
uintptr_t copied_within_stack_pointer;
std::memcpy(&copied_within_stack_pointer,
stack_copy_bottom + sizeof(uintptr_t),
sizeof(copied_within_stack_pointer));
// The rewriting should only operate on pointer-aligned values so the
// unaligned value should be copied verbatim.
EXPECT_EQ(within_stack_pointer, copied_within_stack_pointer);
}
TEST(StackSamplerImplTest, StackCopy_NonAlignedStackPointerAlignedRewrite) {
// Initially fill the buffer with 0s.
TestStackBuffer stack_buffer = {{0}};
// Set the stack bottom to the unaligned location one uint16_t into the
// buffer.
uint8_t* unaligned_stack_bottom =
reinterpret_cast<uint8_t*>(&stack_buffer.as_uint16[1]);
// Set the second aligned pointer-sized value to an address within the stack.
stack_buffer.as_uintptr[1] =
reinterpret_cast<uintptr_t>(&stack_buffer.as_uintptr[2]);
TestStackBuffer stack_copy_buffer = {{0}};
CopyStackContentsAndRewritePointers(
unaligned_stack_bottom,
&stack_buffer.as_uintptr[0] + size(stack_buffer.as_uintptr),
&stack_copy_buffer.as_uintptr[0]);
// The aligned pointer should have been rewritten to point within the stack
// copy.
EXPECT_EQ(reinterpret_cast<uintptr_t>(&stack_copy_buffer.as_uintptr[2]),
stack_copy_buffer.as_uintptr[1]);
}
TEST(StackSamplerImplTest, CopyStack) {
ModuleCache module_cache;
const std::vector<uintptr_t> stack = {0, 1, 2, 3, 4};
InjectModuleForContextInstructionPointer(stack, &module_cache);
std::vector<uintptr_t> stack_copy;
StackSamplerImpl stack_sampler_impl(
std::make_unique<TestThreadDelegate>(stack),
std::make_unique<TestUnwinder>(stack.size(), &stack_copy), &module_cache);
std::unique_ptr<StackSampler::StackBuffer> stack_buffer =
std::make_unique<StackSampler::StackBuffer>(stack.size() *
sizeof(uintptr_t));
TestProfileBuilder profile_builder(&module_cache);
stack_sampler_impl.RecordStackFrames(stack_buffer.get(), &profile_builder);
EXPECT_EQ(stack, stack_copy);
}
TEST(StackSamplerImplTest, CopyStackBufferTooSmall) {
ModuleCache module_cache;
std::vector<uintptr_t> stack = {0, 1, 2, 3, 4};
InjectModuleForContextInstructionPointer(stack, &module_cache);
std::vector<uintptr_t> stack_copy;
StackSamplerImpl stack_sampler_impl(
std::make_unique<TestThreadDelegate>(stack),
std::make_unique<TestUnwinder>(stack.size(), &stack_copy), &module_cache);
std::unique_ptr<StackSampler::StackBuffer> stack_buffer =
std::make_unique<StackSampler::StackBuffer>((stack.size() - 1) *
sizeof(uintptr_t));
// Make the buffer different than the input stack.
stack_buffer->buffer()[0] = 100;
TestProfileBuilder profile_builder(&module_cache);
stack_sampler_impl.RecordStackFrames(stack_buffer.get(), &profile_builder);
// Use the buffer not being overwritten as a proxy for the unwind being
// aborted.
EXPECT_NE(stack, stack_copy);
}
TEST(StackSamplerImplTest, CopyStackAndRewritePointers) {
ModuleCache module_cache;
// Allocate space for the stack, then make its elements point to themselves.
std::vector<uintptr_t> stack(2);
stack[0] = reinterpret_cast<uintptr_t>(&stack[0]);
stack[1] = reinterpret_cast<uintptr_t>(&stack[1]);
InjectModuleForContextInstructionPointer(stack, &module_cache);
std::vector<uintptr_t> stack_copy;
uintptr_t stack_copy_bottom;
StackSamplerImpl stack_sampler_impl(
std::make_unique<TestThreadDelegate>(stack),
std::make_unique<TestUnwinder>(stack.size(), &stack_copy,
&stack_copy_bottom),
&module_cache);
std::unique_ptr<StackSampler::StackBuffer> stack_buffer =
std::make_unique<StackSampler::StackBuffer>(stack.size() *
sizeof(uintptr_t));
TestProfileBuilder profile_builder(&module_cache);
stack_sampler_impl.RecordStackFrames(stack_buffer.get(), &profile_builder);
EXPECT_THAT(stack_copy, ElementsAre(stack_copy_bottom,
stack_copy_bottom + sizeof(uintptr_t)));
}
TEST(StackSamplerImplTest, RewriteRegisters) {
ModuleCache module_cache;
std::vector<uintptr_t> stack = {0, 1, 2};
InjectModuleForContextInstructionPointer(stack, &module_cache);
uintptr_t stack_copy_bottom;
RegisterContext thread_context;
RegisterContextFramePointer(&thread_context) =
reinterpret_cast<uintptr_t>(&stack[1]);
StackSamplerImpl stack_sampler_impl(
std::make_unique<TestThreadDelegate>(stack, &thread_context),
std::make_unique<TestUnwinder>(stack.size(), nullptr, &stack_copy_bottom),
&module_cache);
std::unique_ptr<StackSampler::StackBuffer> stack_buffer =
std::make_unique<StackSampler::StackBuffer>(stack.size() *
sizeof(uintptr_t));
TestProfileBuilder profile_builder(&module_cache);
stack_sampler_impl.RecordStackFrames(stack_buffer.get(), &profile_builder);
EXPECT_EQ(stack_copy_bottom + sizeof(uintptr_t),
RegisterContextFramePointer(&thread_context));
}
TEST(StackSamplerImplTest, WalkStack_Completed) {
ModuleCache module_cache;
RegisterContext thread_context;
RegisterContextInstructionPointer(&thread_context) =
GetTestInstructionPointer();
module_cache.InjectModuleForTesting(std::make_unique<TestModule>(1u, 1u));
FakeTestUnwinder native_unwinder({{UnwindResult::COMPLETED, {1u}}});
std::vector<Frame> stack = StackSamplerImpl::WalkStackForTesting(
&module_cache, &thread_context, 0u, &native_unwinder, nullptr);
ASSERT_EQ(2u, stack.size());
EXPECT_EQ(1u, stack[1].instruction_pointer);
}
TEST(StackSamplerImplTest, WalkStack_Aborted) {
ModuleCache module_cache;
RegisterContext thread_context;
RegisterContextInstructionPointer(&thread_context) =
GetTestInstructionPointer();
module_cache.InjectModuleForTesting(std::make_unique<TestModule>(1u, 1u));
FakeTestUnwinder native_unwinder({{UnwindResult::ABORTED, {1u}}});
std::vector<Frame> stack = StackSamplerImpl::WalkStackForTesting(
&module_cache, &thread_context, 0u, &native_unwinder, nullptr);
ASSERT_EQ(2u, stack.size());
EXPECT_EQ(1u, stack[1].instruction_pointer);
}
TEST(StackSamplerImplTest, WalkStack_NotUnwound) {
ModuleCache module_cache;
RegisterContext thread_context;
RegisterContextInstructionPointer(&thread_context) =
GetTestInstructionPointer();
FakeTestUnwinder native_unwinder({{UnwindResult::UNRECOGNIZED_FRAME, {}}});
std::vector<Frame> stack = StackSamplerImpl::WalkStackForTesting(
&module_cache, &thread_context, 0u, &native_unwinder, nullptr);
ASSERT_EQ(1u, stack.size());
}
TEST(StackSamplerImplTest, WalkStack_AuxUnwind) {
ModuleCache module_cache;
RegisterContext thread_context;
RegisterContextInstructionPointer(&thread_context) =
GetTestInstructionPointer();
// Treat the context instruction pointer as being in the aux unwinder's
// non-native module.
module_cache.AddNonNativeModule(
std::make_unique<TestModule>(GetTestInstructionPointer(), 1u, false));
FakeTestUnwinder aux_unwinder({{UnwindResult::ABORTED, {1u}}});
std::vector<Frame> stack = StackSamplerImpl::WalkStackForTesting(
&module_cache, &thread_context, 0u, nullptr, &aux_unwinder);
ASSERT_EQ(2u, stack.size());
EXPECT_EQ(GetTestInstructionPointer(), stack[0].instruction_pointer);
EXPECT_EQ(1u, stack[1].instruction_pointer);
}
TEST(StackSamplerImplTest, WalkStack_AuxThenNative) {
ModuleCache module_cache;
RegisterContext thread_context;
RegisterContextInstructionPointer(&thread_context) = 0u;
// Treat the context instruction pointer as being in the aux unwinder's
// non-native module.
module_cache.AddNonNativeModule(std::make_unique<TestModule>(0u, 1u, false));
// Inject a fake native module for the second frame.
module_cache.InjectModuleForTesting(std::make_unique<TestModule>(1u, 1u));
FakeTestUnwinder aux_unwinder(
{{{UnwindResult::UNRECOGNIZED_FRAME, {1u}}, {false}}});
FakeTestUnwinder native_unwinder({{UnwindResult::COMPLETED, {2u}}});
std::vector<Frame> stack = StackSamplerImpl::WalkStackForTesting(
&module_cache, &thread_context, 0u, &native_unwinder, &aux_unwinder);
ASSERT_EQ(3u, stack.size());
EXPECT_EQ(0u, stack[0].instruction_pointer);
EXPECT_EQ(1u, stack[1].instruction_pointer);
EXPECT_EQ(2u, stack[2].instruction_pointer);
}
TEST(StackSamplerImplTest, WalkStack_NativeThenAux) {
ModuleCache module_cache;
RegisterContext thread_context;
RegisterContextInstructionPointer(&thread_context) = 0u;
// Inject fake native modules for the instruction pointer from the context and
// the third frame.
module_cache.InjectModuleForTesting(std::make_unique<TestModule>(0u, 1u));
module_cache.InjectModuleForTesting(std::make_unique<TestModule>(2u, 1u));
// Treat the second frame's pointer as being in the aux unwinder's non-native
// module.
module_cache.AddNonNativeModule(std::make_unique<TestModule>(1u, 1u, false));
FakeTestUnwinder aux_unwinder(
{{false}, {UnwindResult::UNRECOGNIZED_FRAME, {2u}}, {false}});
FakeTestUnwinder native_unwinder({{UnwindResult::UNRECOGNIZED_FRAME, {1u}},
{UnwindResult::COMPLETED, {3u}}});
std::vector<Frame> stack = StackSamplerImpl::WalkStackForTesting(
&module_cache, &thread_context, 0u, &native_unwinder, &aux_unwinder);
ASSERT_EQ(4u, stack.size());
EXPECT_EQ(0u, stack[0].instruction_pointer);
EXPECT_EQ(1u, stack[1].instruction_pointer);
EXPECT_EQ(2u, stack[2].instruction_pointer);
EXPECT_EQ(3u, stack[3].instruction_pointer);
}
} // namespace base