224 lines
7.6 KiB
C++
224 lines
7.6 KiB
C++
// 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
|