blob: 2dfc37a2a5a3f958a78f0c06b6debb3aba9d9f64 [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 <memory>
#include <utility>
#include "base/profiler/profile_builder.h"
#include "base/profiler/stack_sampler_impl.h"
#include "base/profiler/thread_delegate.h"
#include "base/sampling_heap_profiler/module_cache.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,
// Vector to fill in with the copied stack.
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,
// The register context will be initialized to
// *|thread_context| if non-null.
RegisterContext* thread_context = nullptr)
: fake_stack_(fake_stack),
stack_copy_(stack_copy),
stack_copy_bottom_(stack_copy_bottom),
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]);
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)};
}
UnwindResult WalkNativeFrames(
RegisterContext* thread_context,
uintptr_t stack_top,
ModuleCache* module_cache,
std::vector<ProfileBuilder::Frame>* stack) override {
if (stack_copy_) {
auto* bottom = reinterpret_cast<uintptr_t*>(
RegisterContextStackPointer(thread_context));
auto* top = bottom + fake_stack_.size();
*stack_copy_ = std::vector<uintptr_t>(bottom, top);
}
if (stack_copy_bottom_)
*stack_copy_bottom_ = RegisterContextStackPointer(thread_context);
return UnwindResult::COMPLETED;
}
private:
// Must be a reference to retain the underlying allocation from the vector
// passed to the constructor.
const std::vector<uintptr_t>& fake_stack_;
std::vector<uintptr_t>* stack_copy_;
uintptr_t* stack_copy_bottom_;
RegisterContext* thread_context_;
};
} // namespace
TEST(StackSamplerImplTest, CopyStack) {
ModuleCache module_cache;
const std::vector<uintptr_t> stack = {0, 1, 2, 3, 4};
std::vector<uintptr_t> stack_copy;
StackSamplerImpl stack_sampler_impl(
std::make_unique<TestThreadDelegate>(stack, &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};
std::vector<uintptr_t> stack_copy;
StackSamplerImpl stack_sampler_impl(
std::make_unique<TestThreadDelegate>(stack, &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]);
std::vector<uintptr_t> stack_copy;
uintptr_t stack_copy_bottom;
StackSamplerImpl stack_sampler_impl(
std::make_unique<TestThreadDelegate>(stack, &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(3);
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, nullptr, &stack_copy_bottom,
&thread_context),
&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));
}
} // namespace base