summaryrefslogtreecommitdiff
path: root/base/profiler/win32_stack_frame_unwinder_unittest.cc
diff options
context:
space:
mode:
Diffstat (limited to 'base/profiler/win32_stack_frame_unwinder_unittest.cc')
-rw-r--r--base/profiler/win32_stack_frame_unwinder_unittest.cc223
1 files changed, 223 insertions, 0 deletions
diff --git a/base/profiler/win32_stack_frame_unwinder_unittest.cc b/base/profiler/win32_stack_frame_unwinder_unittest.cc
new file mode 100644
index 0000000000..cecfe224ec
--- /dev/null
+++ b/base/profiler/win32_stack_frame_unwinder_unittest.cc
@@ -0,0 +1,223 @@
+// Copyright 2015 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 "base/profiler/win32_stack_frame_unwinder.h"
+
+#include <memory>
+#include <utility>
+#include <vector>
+
+#include "base/compiler_specific.h"
+#include "base/macros.h"
+#include "base/memory/ptr_util.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace base {
+
+namespace {
+
+class TestUnwindFunctions : public Win32StackFrameUnwinder::UnwindFunctions {
+ public:
+ TestUnwindFunctions();
+
+ PRUNTIME_FUNCTION LookupFunctionEntry(DWORD64 program_counter,
+ PDWORD64 image_base) override;
+ void VirtualUnwind(DWORD64 image_base,
+ DWORD64 program_counter,
+ PRUNTIME_FUNCTION runtime_function,
+ CONTEXT* context) override;
+ ScopedModuleHandle GetModuleForProgramCounter(
+ DWORD64 program_counter) override;
+
+ // Instructs GetModuleForProgramCounter to return null on the next call.
+ void SetUnloadedModule();
+
+ // These functions set whether the next frame will have a RUNTIME_FUNCTION.
+ void SetHasRuntimeFunction(CONTEXT* context);
+ void SetNoRuntimeFunction(CONTEXT* context);
+
+ private:
+ enum { kImageBaseIncrement = 1 << 20 };
+
+ static RUNTIME_FUNCTION* const kInvalidRuntimeFunction;
+
+ bool module_is_loaded_;
+ DWORD64 expected_program_counter_;
+ DWORD64 next_image_base_;
+ DWORD64 expected_image_base_;
+ RUNTIME_FUNCTION* next_runtime_function_;
+ std::vector<RUNTIME_FUNCTION> runtime_functions_;
+
+ DISALLOW_COPY_AND_ASSIGN(TestUnwindFunctions);
+};
+
+RUNTIME_FUNCTION* const TestUnwindFunctions::kInvalidRuntimeFunction =
+ reinterpret_cast<RUNTIME_FUNCTION*>(static_cast<uintptr_t>(-1));
+
+TestUnwindFunctions::TestUnwindFunctions()
+ : module_is_loaded_(true),
+ expected_program_counter_(0),
+ next_image_base_(kImageBaseIncrement),
+ expected_image_base_(0),
+ next_runtime_function_(kInvalidRuntimeFunction) {
+}
+
+PRUNTIME_FUNCTION TestUnwindFunctions::LookupFunctionEntry(
+ DWORD64 program_counter,
+ PDWORD64 image_base) {
+ EXPECT_EQ(expected_program_counter_, program_counter);
+ *image_base = expected_image_base_ = next_image_base_;
+ next_image_base_ += kImageBaseIncrement;
+ RUNTIME_FUNCTION* return_value = next_runtime_function_;
+ next_runtime_function_ = kInvalidRuntimeFunction;
+ return return_value;
+}
+
+void TestUnwindFunctions::VirtualUnwind(DWORD64 image_base,
+ DWORD64 program_counter,
+ PRUNTIME_FUNCTION runtime_function,
+ CONTEXT* context) {
+ ASSERT_NE(kInvalidRuntimeFunction, runtime_function)
+ << "expected call to SetHasRuntimeFunction() or SetNoRuntimeFunction() "
+ << "before invoking TryUnwind()";
+ EXPECT_EQ(expected_image_base_, image_base);
+ expected_image_base_ = 0;
+ EXPECT_EQ(expected_program_counter_, program_counter);
+ expected_program_counter_ = 0;
+ // This function should only be called when LookupFunctionEntry returns
+ // a RUNTIME_FUNCTION.
+ EXPECT_EQ(&runtime_functions_.back(), runtime_function);
+}
+
+ScopedModuleHandle TestUnwindFunctions::GetModuleForProgramCounter(
+ DWORD64 program_counter) {
+ bool return_non_null_value = module_is_loaded_;
+ module_is_loaded_ = true;
+ return ScopedModuleHandle(return_non_null_value ?
+ ModuleHandleTraits::kNonNullModuleForTesting :
+ nullptr);
+}
+
+void TestUnwindFunctions::SetUnloadedModule() {
+ module_is_loaded_ = false;
+}
+
+void TestUnwindFunctions::SetHasRuntimeFunction(CONTEXT* context) {
+ RUNTIME_FUNCTION runtime_function = {};
+ runtime_function.BeginAddress = 16;
+ runtime_function.EndAddress = runtime_function.BeginAddress + 256;
+ runtime_functions_.push_back(runtime_function);
+ next_runtime_function_ = &runtime_functions_.back();
+
+ expected_program_counter_ = context->Rip =
+ next_image_base_ + runtime_function.BeginAddress + 8;
+}
+
+void TestUnwindFunctions::SetNoRuntimeFunction(CONTEXT* context) {
+ expected_program_counter_ = context->Rip = 100;
+ next_runtime_function_ = nullptr;
+}
+
+} // namespace
+
+class Win32StackFrameUnwinderTest : public testing::Test {
+ protected:
+ Win32StackFrameUnwinderTest() {}
+
+ // This exists so that Win32StackFrameUnwinder's constructor can be private
+ // with a single friend declaration of this test fixture.
+ std::unique_ptr<Win32StackFrameUnwinder> CreateUnwinder();
+
+ // Weak pointer to the unwind functions used by last created unwinder.
+ TestUnwindFunctions* unwind_functions_;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(Win32StackFrameUnwinderTest);
+};
+
+std::unique_ptr<Win32StackFrameUnwinder>
+Win32StackFrameUnwinderTest::CreateUnwinder() {
+ std::unique_ptr<TestUnwindFunctions> unwind_functions(
+ new TestUnwindFunctions);
+ unwind_functions_ = unwind_functions.get();
+ return WrapUnique(
+ new Win32StackFrameUnwinder(std::move(unwind_functions)));
+}
+
+// Checks the case where all frames have unwind information.
+TEST_F(Win32StackFrameUnwinderTest, FramesWithUnwindInfo) {
+ std::unique_ptr<Win32StackFrameUnwinder> unwinder = CreateUnwinder();
+ CONTEXT context = {0};
+ ScopedModuleHandle module;
+
+ unwind_functions_->SetHasRuntimeFunction(&context);
+ EXPECT_TRUE(unwinder->TryUnwind(&context, &module));
+ EXPECT_TRUE(module.IsValid());
+
+ unwind_functions_->SetHasRuntimeFunction(&context);
+ module.Set(nullptr);
+ EXPECT_TRUE(unwinder->TryUnwind(&context, &module));
+ EXPECT_TRUE(module.IsValid());
+
+ unwind_functions_->SetHasRuntimeFunction(&context);
+ module.Set(nullptr);
+ EXPECT_TRUE(unwinder->TryUnwind(&context, &module));
+ EXPECT_TRUE(module.IsValid());
+}
+
+// Checks that an instruction pointer in an unloaded module fails to unwind.
+TEST_F(Win32StackFrameUnwinderTest, UnloadedModule) {
+ std::unique_ptr<Win32StackFrameUnwinder> unwinder = CreateUnwinder();
+ CONTEXT context = {0};
+ ScopedModuleHandle module;
+
+ unwind_functions_->SetUnloadedModule();
+ EXPECT_FALSE(unwinder->TryUnwind(&context, &module));
+}
+
+// Checks that the CONTEXT's stack pointer gets popped when the top frame has no
+// unwind information.
+TEST_F(Win32StackFrameUnwinderTest, FrameAtTopWithoutUnwindInfo) {
+ std::unique_ptr<Win32StackFrameUnwinder> unwinder = CreateUnwinder();
+ CONTEXT context = {0};
+ ScopedModuleHandle module;
+ DWORD64 next_ip = 0x0123456789abcdef;
+ DWORD64 original_rsp = reinterpret_cast<DWORD64>(&next_ip);
+ context.Rsp = original_rsp;
+
+ unwind_functions_->SetNoRuntimeFunction(&context);
+ EXPECT_TRUE(unwinder->TryUnwind(&context, &module));
+ EXPECT_EQ(next_ip, context.Rip);
+ EXPECT_EQ(original_rsp + 8, context.Rsp);
+ EXPECT_TRUE(module.IsValid());
+
+ unwind_functions_->SetHasRuntimeFunction(&context);
+ module.Set(nullptr);
+ EXPECT_TRUE(unwinder->TryUnwind(&context, &module));
+ EXPECT_TRUE(module.IsValid());
+
+ unwind_functions_->SetHasRuntimeFunction(&context);
+ module.Set(nullptr);
+ EXPECT_TRUE(unwinder->TryUnwind(&context, &module));
+ EXPECT_TRUE(module.IsValid());
+}
+
+// Checks that a frame below the top of the stack with missing unwind info
+// terminates the unwinding.
+TEST_F(Win32StackFrameUnwinderTest, FrameBelowTopWithoutUnwindInfo) {
+ {
+ // First stack, with a bad function below the top of the stack.
+ std::unique_ptr<Win32StackFrameUnwinder> unwinder = CreateUnwinder();
+ CONTEXT context = {0};
+ ScopedModuleHandle module;
+ unwind_functions_->SetHasRuntimeFunction(&context);
+ EXPECT_TRUE(unwinder->TryUnwind(&context, &module));
+ EXPECT_TRUE(module.IsValid());
+
+ unwind_functions_->SetNoRuntimeFunction(&context);
+ EXPECT_FALSE(unwinder->TryUnwind(&context, &module));
+ }
+}
+
+} // namespace base