blob: 3ca4aff48452fd5691cb0a049e9edd59d03d0319 [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_buffer.h"
#include "base/profiler/stack_copier.h"
#include "base/profiler/stack_sampler_impl.h"
#include "base/profiler/suspendable_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(
ProfileBuilder::MetadataProvider* metadata_provider) override {}
void OnSampleCompleted(std::vector<Frame> frames) override {}
void OnProfileCompleted(TimeDelta profile_duration,
TimeDelta sampling_period) override {}
private:
ModuleCache* module_cache_;
};
// A stack copier for use in tests that provides the expected behavior when
// operating on the supplied fake stack.
class TestStackCopier : public StackCopier {
public:
TestStackCopier(const std::vector<uintptr_t>& fake_stack)
: fake_stack_(fake_stack) {}
bool CopyStack(StackBuffer* stack_buffer,
uintptr_t* stack_top,
ProfileBuilder* profile_builder,
RegisterContext* thread_context) override {
std::memcpy(stack_buffer->buffer(), &fake_stack_[0], fake_stack_.size());
*stack_top =
reinterpret_cast<uintptr_t>(&fake_stack_[0] + fake_stack_.size());
// Set the stack pointer to be consistent with the provided fake stack.
RegisterContextStackPointer(thread_context) =
reinterpret_cast<uintptr_t>(&fake_stack_[0]);
return true;
}
private:
// Must be a reference to retain the underlying allocation from the vector
// passed to the constructor.
const std::vector<uintptr_t>& fake_stack_;
};
// 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_;
};
} // namespace
// TODO(crbug.com/1001923): Fails on Linux MSan.
#if defined(OS_LINUX)
#define MAYBE_CopyStack DISABLED_MAYBE_CopyStack
#else
#define MAYBE_CopyStack CopyStack
#endif
TEST(StackSamplerImplTest, MAYBE_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<TestStackCopier>(stack),
std::make_unique<TestUnwinder>(stack.size(), &stack_copy), &module_cache);
std::unique_ptr<StackBuffer> stack_buffer =
std::make_unique<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, 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