Chromium Code Reviews| Index: base/profiler/win32_stack_frame_unwinder_unittest.cc |
| 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 0000000000000000000000000000000000000000..f81a54e3cebc87b8df3fff0f17c2153cc27ba1b7 |
| --- /dev/null |
| +++ b/base/profiler/win32_stack_frame_unwinder_unittest.cc |
| @@ -0,0 +1,229 @@ |
| +// 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/compiler_specific.h" |
| +#include "base/memory/scoped_ptr.h" |
| +#include "base/profiler/win32_stack_frame_unwinder.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; |
| + |
| + void SetNoUnwindInfoForNextFrame(); |
| + void SetImageBaseForNextFrame(DWORD64 image_base); |
| + |
| + private: |
| + enum { kRuntimeFunctionPointerIncrement = 1, kImageBaseIncrement = 1 << 20 }; |
| + |
| + static const PRUNTIME_FUNCTION kNonNullRuntimeFunctionPointer; |
| + |
| + DWORD64 supplied_program_counter_; |
| + DWORD64 custom_image_base_; |
| + DWORD64 next_image_base_; |
| + bool next_lookup_returns_null_; |
| + |
| + DISALLOW_COPY_AND_ASSIGN(TestUnwindFunctions); |
| +}; |
| + |
| +// This value is opaque to Win32StackFrameUnwinder. |
| +const PRUNTIME_FUNCTION TestUnwindFunctions::kNonNullRuntimeFunctionPointer = |
| + reinterpret_cast<PRUNTIME_FUNCTION>(8); |
| + |
| +TestUnwindFunctions::TestUnwindFunctions() |
| + : supplied_program_counter_(0), |
| + custom_image_base_(0), |
| + next_image_base_(1 << 20), |
|
Nico
2015/08/18 23:20:24
s/1 << 20/kImageBaseIncrement/ (?)
Mike Wittman
2015/08/18 23:43:10
Done.
|
| + next_lookup_returns_null_(false) { |
| +} |
| + |
| +PRUNTIME_FUNCTION TestUnwindFunctions::LookupFunctionEntry( |
| + DWORD64 program_counter, |
| + PDWORD64 image_base) { |
| + supplied_program_counter_ = program_counter; |
| + if (custom_image_base_) { |
| + *image_base = custom_image_base_; |
| + custom_image_base_ = 0; |
| + } else { |
| + *image_base = next_image_base_; |
| + next_image_base_ += kImageBaseIncrement; |
| + } |
| + if (next_lookup_returns_null_) { |
| + next_lookup_returns_null_ = false; |
| + return nullptr; |
| + } |
| + |
| + return kNonNullRuntimeFunctionPointer; |
| +} |
| + |
| +void TestUnwindFunctions::VirtualUnwind(DWORD64 image_base, |
| + DWORD64 program_counter, |
| + PRUNTIME_FUNCTION runtime_function, |
| + CONTEXT* context) { |
| + EXPECT_EQ(next_image_base_ - kImageBaseIncrement, image_base); |
| + EXPECT_EQ(supplied_program_counter_, program_counter); |
| + // This function should only be called when LookupFunctionEntry returns a |
| + // non-null value. |
| + EXPECT_EQ(kNonNullRuntimeFunctionPointer, runtime_function); |
| +} |
| + |
| +void TestUnwindFunctions::SetNoUnwindInfoForNextFrame() { |
| + next_lookup_returns_null_ = true; |
| +} |
| + |
| +void TestUnwindFunctions::SetImageBaseForNextFrame(DWORD64 image_base) { |
| + next_image_base_ = image_base; |
| +} |
| + |
| +} // namespace |
| + |
| +class Win32StackFrameUnwinderTest : public testing::Test { |
| + protected: |
| + Win32StackFrameUnwinderTest() {} |
| + |
| + scoped_ptr<Win32StackFrameUnwinder> CreateUnwinder(); |
| + |
| + TestUnwindFunctions unwind_functions_; |
| + |
| + private: |
| + DISALLOW_COPY_AND_ASSIGN(Win32StackFrameUnwinderTest); |
| +}; |
| + |
| +scoped_ptr<Win32StackFrameUnwinder> |
| +Win32StackFrameUnwinderTest::CreateUnwinder() { |
| + return make_scoped_ptr(new Win32StackFrameUnwinder(&unwind_functions_)); |
| +} |
| + |
| +// Checks the case where all frames have unwind information. |
| +TEST_F(Win32StackFrameUnwinderTest, FramesWithUnwindInfo) { |
| + scoped_ptr<Win32StackFrameUnwinder> unwinder = CreateUnwinder(); |
|
Nico
2015/08/18 23:20:24
Can't this just live on the stack? (also below) Th
Mike Wittman
2015/08/18 23:43:10
It could, but I believe I'd have to do one of: mak
Nico
2015/08/18 23:49:48
Maybe add a "// This exists so that Win32StackFram
Mike Wittman
2015/08/18 23:58:18
Done.
|
| + CONTEXT context = {0}; |
| + EXPECT_TRUE(unwinder->TryUnwind(&context)); |
| + EXPECT_TRUE(unwinder->TryUnwind(&context)); |
| + EXPECT_TRUE(unwinder->TryUnwind(&context)); |
| +} |
| + |
| +// Checks that the CONTEXT's stack pointer gets popped when the top frame has no |
| +// unwind information. |
| +TEST_F(Win32StackFrameUnwinderTest, FrameAtTopWithoutUnwindInfo) { |
| + scoped_ptr<Win32StackFrameUnwinder> unwinder = CreateUnwinder(); |
| + CONTEXT context = {0}; |
| + const DWORD64 original_rsp = 128; |
| + context.Rsp = original_rsp; |
| + unwind_functions_.SetNoUnwindInfoForNextFrame(); |
| + EXPECT_TRUE(unwinder->TryUnwind(&context)); |
| + EXPECT_EQ(original_rsp, context.Rip); |
| + EXPECT_EQ(original_rsp + 8, context.Rsp); |
| + |
| + EXPECT_TRUE(unwinder->TryUnwind(&context)); |
| + EXPECT_TRUE(unwinder->TryUnwind(&context)); |
| +} |
| + |
| +// Checks that a frame below the top of the stack with missing unwind info |
| +// results in blacklisting the module. |
| +TEST_F(Win32StackFrameUnwinderTest, BlacklistedModule) { |
| + const DWORD64 image_base_for_module_with_bad_function = 1024; |
| + { |
| + // First stack, with a bad function below the top of the stack. |
| + scoped_ptr<Win32StackFrameUnwinder> unwinder = CreateUnwinder(); |
| + CONTEXT context = {0}; |
| + EXPECT_TRUE(unwinder->TryUnwind(&context)); |
| + |
| + unwind_functions_.SetNoUnwindInfoForNextFrame(); |
| + unwind_functions_.SetImageBaseForNextFrame( |
| + image_base_for_module_with_bad_function); |
| + EXPECT_FALSE(unwinder->TryUnwind(&context)); |
| + } |
| + |
| + { |
| + // Second stack; check that a function at the top of the stack without |
| + // unwind info from the previously-seen module is blacklisted. |
| + scoped_ptr<Win32StackFrameUnwinder> unwinder = CreateUnwinder(); |
| + CONTEXT context = {0}; |
| + unwind_functions_.SetNoUnwindInfoForNextFrame(); |
| + unwind_functions_.SetImageBaseForNextFrame( |
| + image_base_for_module_with_bad_function); |
| + EXPECT_FALSE(unwinder->TryUnwind(&context)); |
| + } |
| + |
| + { |
| + // Third stack; check that a function at the top of the stack *with* unwind |
| + // info from the previously-seen module is not blacklisted. Then check that |
| + // functions below the top of the stack with unwind info are not |
| + // blacklisted, regardless of whether they are in the previously-seen |
| + // module. |
| + scoped_ptr<Win32StackFrameUnwinder> unwinder = CreateUnwinder(); |
| + CONTEXT context = {0}; |
| + unwind_functions_.SetImageBaseForNextFrame( |
| + image_base_for_module_with_bad_function); |
| + EXPECT_TRUE(unwinder->TryUnwind(&context)); |
| + |
| + EXPECT_TRUE(unwinder->TryUnwind(&context)); |
| + |
| + unwind_functions_.SetImageBaseForNextFrame( |
| + image_base_for_module_with_bad_function); |
| + EXPECT_TRUE(unwinder->TryUnwind(&context)); |
| + } |
| + |
| + { |
| + // Fourth stack; check that a function at the top of the stack without |
| + // unwind info and not from the previously-seen module is not |
| + // blacklisted. Then check that functions below the top of the stack with |
| + // unwind info are not blacklisted, regardless of whether they are in the |
| + // previously-seen module. |
| + scoped_ptr<Win32StackFrameUnwinder> unwinder = CreateUnwinder(); |
| + CONTEXT context = {0}; |
| + unwind_functions_.SetNoUnwindInfoForNextFrame(); |
| + EXPECT_TRUE(unwinder->TryUnwind(&context)); |
| + |
| + EXPECT_TRUE(unwinder->TryUnwind(&context)); |
| + |
| + unwind_functions_.SetImageBaseForNextFrame( |
| + image_base_for_module_with_bad_function); |
| + EXPECT_TRUE(unwinder->TryUnwind(&context)); |
| + } |
| +} |
| + |
| +// Checks that a frame below the top of the stack with missing unwind info does |
| +// not result in blacklisting the module if the first frame also was missing |
| +// unwind info. This ensures we don't blacklist an innocent module because the |
| +// first frame was bad but we didn't know it at the time. |
| +TEST_F(Win32StackFrameUnwinderTest, ModuleFromQuestionableFrameNotBlacklisted) { |
| + const DWORD64 image_base_for_questionable_module = 2048; |
| + { |
| + // First stack, with both the first and second frames missing unwind info. |
| + scoped_ptr<Win32StackFrameUnwinder> unwinder = CreateUnwinder(); |
| + CONTEXT context = {0}; |
| + unwind_functions_.SetNoUnwindInfoForNextFrame(); |
| + EXPECT_TRUE(unwinder->TryUnwind(&context)); |
| + |
| + unwind_functions_.SetNoUnwindInfoForNextFrame(); |
| + unwind_functions_.SetImageBaseForNextFrame( |
| + image_base_for_questionable_module); |
| + EXPECT_FALSE(unwinder->TryUnwind(&context)); |
| + } |
| + |
| + { |
| + // Second stack; check that the questionable module was not blacklisted. |
| + scoped_ptr<Win32StackFrameUnwinder> unwinder = CreateUnwinder(); |
| + CONTEXT context = {0}; |
| + unwind_functions_.SetNoUnwindInfoForNextFrame(); |
| + unwind_functions_.SetImageBaseForNextFrame( |
| + image_base_for_questionable_module); |
| + EXPECT_TRUE(unwinder->TryUnwind(&context)); |
| + } |
| +} |
| + |
| +} // namespace base |