258 lines
9.6 KiB
C++
258 lines
9.6 KiB
C++
|
|
// Copyright 2019 The Chromium Authors
|
||
|
|
// 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/memory/raw_ptr.h"
|
||
|
|
#include "base/memory/raw_ref.h"
|
||
|
|
#include "base/profiler/stack_buffer.h"
|
||
|
|
#include "base/profiler/stack_copier_suspend.h"
|
||
|
|
#include "base/profiler/suspendable_thread_delegate.h"
|
||
|
|
#include "build/build_config.h"
|
||
|
|
#include "testing/gmock/include/gmock/gmock.h"
|
||
|
|
#include "testing/gtest/include/gtest/gtest.h"
|
||
|
|
|
||
|
|
#if BUILDFLAG(IS_CHROMEOS)
|
||
|
|
#include "base/memory/page_size.h"
|
||
|
|
#endif
|
||
|
|
|
||
|
|
namespace base {
|
||
|
|
|
||
|
|
namespace {
|
||
|
|
|
||
|
|
using ::testing::Each;
|
||
|
|
using ::testing::ElementsAre;
|
||
|
|
|
||
|
|
// A thread delegate for use in tests that provides the expected behavior when
|
||
|
|
// operating on the supplied fake stack.
|
||
|
|
class TestSuspendableThreadDelegate : public SuspendableThreadDelegate {
|
||
|
|
public:
|
||
|
|
class TestScopedSuspendThread
|
||
|
|
: public SuspendableThreadDelegate::ScopedSuspendThread {
|
||
|
|
public:
|
||
|
|
TestScopedSuspendThread() = default;
|
||
|
|
|
||
|
|
TestScopedSuspendThread(const TestScopedSuspendThread&) = delete;
|
||
|
|
TestScopedSuspendThread& operator=(const TestScopedSuspendThread&) = delete;
|
||
|
|
|
||
|
|
bool WasSuccessful() const override { return true; }
|
||
|
|
};
|
||
|
|
|
||
|
|
TestSuspendableThreadDelegate(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) {}
|
||
|
|
|
||
|
|
TestSuspendableThreadDelegate(const TestSuspendableThreadDelegate&) = delete;
|
||
|
|
TestSuspendableThreadDelegate& operator=(
|
||
|
|
const TestSuspendableThreadDelegate&) = 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;
|
||
|
|
}
|
||
|
|
|
||
|
|
PlatformThreadId GetThreadId() const override { return PlatformThreadId(); }
|
||
|
|
|
||
|
|
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 raw_ref<const std::vector<uintptr_t>> fake_stack_;
|
||
|
|
raw_ptr<RegisterContext> thread_context_;
|
||
|
|
};
|
||
|
|
|
||
|
|
class TestStackCopierDelegate : public StackCopier::Delegate {
|
||
|
|
public:
|
||
|
|
void OnStackCopy() override {
|
||
|
|
on_stack_copy_was_invoked_ = true;
|
||
|
|
}
|
||
|
|
|
||
|
|
bool on_stack_copy_was_invoked() const { return on_stack_copy_was_invoked_; }
|
||
|
|
|
||
|
|
private:
|
||
|
|
bool on_stack_copy_was_invoked_ = false;
|
||
|
|
};
|
||
|
|
|
||
|
|
} // namespace
|
||
|
|
|
||
|
|
TEST(StackCopierSuspendTest, CopyStack) {
|
||
|
|
const std::vector<uintptr_t> stack = {0, 1, 2, 3, 4};
|
||
|
|
StackCopierSuspend stack_copier_suspend(
|
||
|
|
std::make_unique<TestSuspendableThreadDelegate>(stack));
|
||
|
|
|
||
|
|
std::unique_ptr<StackBuffer> stack_buffer =
|
||
|
|
std::make_unique<StackBuffer>(stack.size() * sizeof(uintptr_t));
|
||
|
|
uintptr_t stack_top = 0;
|
||
|
|
TimeTicks timestamp;
|
||
|
|
RegisterContext register_context{};
|
||
|
|
TestStackCopierDelegate stack_copier_delegate;
|
||
|
|
stack_copier_suspend.CopyStack(stack_buffer.get(), &stack_top, ×tamp,
|
||
|
|
®ister_context, &stack_copier_delegate);
|
||
|
|
|
||
|
|
uintptr_t* stack_copy_bottom =
|
||
|
|
reinterpret_cast<uintptr_t*>(stack_buffer.get()->buffer());
|
||
|
|
std::vector<uintptr_t> stack_copy(stack_copy_bottom,
|
||
|
|
stack_copy_bottom + stack.size());
|
||
|
|
EXPECT_EQ(stack, stack_copy);
|
||
|
|
}
|
||
|
|
|
||
|
|
TEST(StackCopierSuspendTest, CopyStackBufferTooSmall) {
|
||
|
|
std::vector<uintptr_t> stack;
|
||
|
|
#if BUILDFLAG(IS_CHROMEOS)
|
||
|
|
// ChromeOS will round up the size of the stack up to the next multiple of
|
||
|
|
// the page size. To make the buffer "too small", the stack must be 1 element
|
||
|
|
// larger than the page size.
|
||
|
|
const size_t kStackElements = (GetPageSize() / sizeof(stack[0])) + 1;
|
||
|
|
#else // #if BUILDFLAG(IS_CHROMEOS)
|
||
|
|
const size_t kStackElements = 5; // Arbitrary
|
||
|
|
#endif
|
||
|
|
stack.reserve(kStackElements);
|
||
|
|
for (size_t i = 0; i < kStackElements; ++i) {
|
||
|
|
stack.push_back(i);
|
||
|
|
}
|
||
|
|
StackCopierSuspend stack_copier_suspend(
|
||
|
|
std::make_unique<TestSuspendableThreadDelegate>(stack));
|
||
|
|
|
||
|
|
std::unique_ptr<StackBuffer> stack_buffer =
|
||
|
|
std::make_unique<StackBuffer>((stack.size() - 1) * sizeof(stack[0]));
|
||
|
|
// Make the buffer different than the input stack.
|
||
|
|
constexpr uintptr_t kBufferInitializer = 100;
|
||
|
|
size_t stack_buffer_elements =
|
||
|
|
stack_buffer->size() / sizeof(stack_buffer->buffer()[0]);
|
||
|
|
std::fill_n(stack_buffer->buffer(), stack_buffer_elements,
|
||
|
|
kBufferInitializer);
|
||
|
|
uintptr_t stack_top = 0;
|
||
|
|
TimeTicks timestamp;
|
||
|
|
RegisterContext register_context{};
|
||
|
|
TestStackCopierDelegate stack_copier_delegate;
|
||
|
|
stack_copier_suspend.CopyStack(stack_buffer.get(), &stack_top, ×tamp,
|
||
|
|
®ister_context, &stack_copier_delegate);
|
||
|
|
|
||
|
|
uintptr_t* stack_copy_bottom =
|
||
|
|
reinterpret_cast<uintptr_t*>(stack_buffer.get()->buffer());
|
||
|
|
std::vector<uintptr_t> stack_copy(stack_copy_bottom,
|
||
|
|
stack_copy_bottom + stack_buffer_elements);
|
||
|
|
// Use the buffer not being overwritten as a proxy for the unwind being
|
||
|
|
// aborted.
|
||
|
|
EXPECT_THAT(stack_copy, Each(kBufferInitializer));
|
||
|
|
}
|
||
|
|
|
||
|
|
TEST(StackCopierSuspendTest, CopyStackAndRewritePointers) {
|
||
|
|
// 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]);
|
||
|
|
StackCopierSuspend stack_copier_suspend(
|
||
|
|
std::make_unique<TestSuspendableThreadDelegate>(stack));
|
||
|
|
|
||
|
|
std::unique_ptr<StackBuffer> stack_buffer =
|
||
|
|
std::make_unique<StackBuffer>(stack.size() * sizeof(uintptr_t));
|
||
|
|
uintptr_t stack_top = 0;
|
||
|
|
TimeTicks timestamp;
|
||
|
|
RegisterContext register_context{};
|
||
|
|
TestStackCopierDelegate stack_copier_delegate;
|
||
|
|
stack_copier_suspend.CopyStack(stack_buffer.get(), &stack_top, ×tamp,
|
||
|
|
®ister_context, &stack_copier_delegate);
|
||
|
|
|
||
|
|
uintptr_t* stack_copy_bottom =
|
||
|
|
reinterpret_cast<uintptr_t*>(stack_buffer.get()->buffer());
|
||
|
|
std::vector<uintptr_t> stack_copy(stack_copy_bottom,
|
||
|
|
stack_copy_bottom + stack.size());
|
||
|
|
EXPECT_THAT(stack_copy,
|
||
|
|
ElementsAre(reinterpret_cast<uintptr_t>(stack_copy_bottom),
|
||
|
|
reinterpret_cast<uintptr_t>(stack_copy_bottom) +
|
||
|
|
sizeof(uintptr_t)));
|
||
|
|
}
|
||
|
|
|
||
|
|
TEST(StackCopierSuspendTest, CopyStackTimeStamp) {
|
||
|
|
const std::vector<uintptr_t> stack = {0};
|
||
|
|
StackCopierSuspend stack_copier_suspend(
|
||
|
|
std::make_unique<TestSuspendableThreadDelegate>(stack));
|
||
|
|
|
||
|
|
std::unique_ptr<StackBuffer> stack_buffer =
|
||
|
|
std::make_unique<StackBuffer>(stack.size() * sizeof(uintptr_t));
|
||
|
|
uintptr_t stack_top = 0;
|
||
|
|
TimeTicks timestamp;
|
||
|
|
RegisterContext register_context{};
|
||
|
|
TestStackCopierDelegate stack_copier_delegate;
|
||
|
|
|
||
|
|
TimeTicks before = TimeTicks::Now();
|
||
|
|
stack_copier_suspend.CopyStack(stack_buffer.get(), &stack_top, ×tamp,
|
||
|
|
®ister_context, &stack_copier_delegate);
|
||
|
|
TimeTicks after = TimeTicks::Now();
|
||
|
|
|
||
|
|
EXPECT_GE(timestamp, before);
|
||
|
|
EXPECT_LE(timestamp, after);
|
||
|
|
}
|
||
|
|
|
||
|
|
TEST(StackCopierSuspendTest, CopyStackDelegateInvoked) {
|
||
|
|
const std::vector<uintptr_t> stack = {0};
|
||
|
|
StackCopierSuspend stack_copier_suspend(
|
||
|
|
std::make_unique<TestSuspendableThreadDelegate>(stack));
|
||
|
|
|
||
|
|
std::unique_ptr<StackBuffer> stack_buffer =
|
||
|
|
std::make_unique<StackBuffer>(stack.size() * sizeof(uintptr_t));
|
||
|
|
uintptr_t stack_top = 0;
|
||
|
|
TimeTicks timestamp;
|
||
|
|
RegisterContext register_context{};
|
||
|
|
TestStackCopierDelegate stack_copier_delegate;
|
||
|
|
|
||
|
|
stack_copier_suspend.CopyStack(stack_buffer.get(), &stack_top, ×tamp,
|
||
|
|
®ister_context, &stack_copier_delegate);
|
||
|
|
|
||
|
|
EXPECT_TRUE(stack_copier_delegate.on_stack_copy_was_invoked());
|
||
|
|
}
|
||
|
|
|
||
|
|
TEST(StackCopierSuspendTest, RewriteRegisters) {
|
||
|
|
std::vector<uintptr_t> stack = {0, 1, 2};
|
||
|
|
RegisterContext register_context{};
|
||
|
|
TestStackCopierDelegate stack_copier_delegate;
|
||
|
|
RegisterContextFramePointer(®ister_context) =
|
||
|
|
reinterpret_cast<uintptr_t>(&stack[1]);
|
||
|
|
StackCopierSuspend stack_copier_suspend(
|
||
|
|
std::make_unique<TestSuspendableThreadDelegate>(stack,
|
||
|
|
®ister_context));
|
||
|
|
|
||
|
|
std::unique_ptr<StackBuffer> stack_buffer =
|
||
|
|
std::make_unique<StackBuffer>(stack.size() * sizeof(uintptr_t));
|
||
|
|
uintptr_t stack_top = 0;
|
||
|
|
TimeTicks timestamp;
|
||
|
|
stack_copier_suspend.CopyStack(stack_buffer.get(), &stack_top, ×tamp,
|
||
|
|
®ister_context, &stack_copier_delegate);
|
||
|
|
|
||
|
|
uintptr_t stack_copy_bottom =
|
||
|
|
reinterpret_cast<uintptr_t>(stack_buffer.get()->buffer());
|
||
|
|
EXPECT_EQ(stack_copy_bottom + sizeof(uintptr_t),
|
||
|
|
RegisterContextFramePointer(®ister_context));
|
||
|
|
}
|
||
|
|
|
||
|
|
} // namespace base
|