// 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 #include #include "base/compiler_specific.h" #include "base/macros.h" #include "base/memory/scoped_ptr.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_functions_; DISALLOW_COPY_AND_ASSIGN(TestUnwindFunctions); }; RUNTIME_FUNCTION* const TestUnwindFunctions::kInvalidRuntimeFunction = reinterpret_cast(static_cast(-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. scoped_ptr CreateUnwinder(); // Weak pointer to the unwind functions used by last created unwinder. TestUnwindFunctions* unwind_functions_; private: DISALLOW_COPY_AND_ASSIGN(Win32StackFrameUnwinderTest); }; scoped_ptr Win32StackFrameUnwinderTest::CreateUnwinder() { scoped_ptr unwind_functions(new TestUnwindFunctions); unwind_functions_ = unwind_functions.get(); return make_scoped_ptr( new Win32StackFrameUnwinder(std::move(unwind_functions))); } // Checks the case where all frames have unwind information. TEST_F(Win32StackFrameUnwinderTest, FramesWithUnwindInfo) { scoped_ptr 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) { scoped_ptr 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) { scoped_ptr unwinder = CreateUnwinder(); CONTEXT context = {0}; ScopedModuleHandle module; DWORD64 next_ip = 0x0123456789abcdef; DWORD64 original_rsp = reinterpret_cast(&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. scoped_ptr 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