diff options
author | robertshield@chromium.org <robertshield@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2010-04-28 20:59:03 +0000 |
---|---|---|
committer | robertshield@chromium.org <robertshield@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2010-04-28 20:59:03 +0000 |
commit | dcdcfa08eabd39a5efd01eb32e47b261805abf4b (patch) | |
tree | 832f0784469978cbdd2017d7f450f7cb084bc467 /chrome_frame/test/exception_barrier_unittest.cc | |
parent | 5bba583182085a5d9ef2ff586b5a648b6e3e950a (diff) | |
download | chromium_src-dcdcfa08eabd39a5efd01eb32e47b261805abf4b.zip chromium_src-dcdcfa08eabd39a5efd01eb32e47b261805abf4b.tar.gz chromium_src-dcdcfa08eabd39a5efd01eb32e47b261805abf4b.tar.bz2 |
Some cleanup regarding Siggi's comments on http://codereview.chromium.org/1733021/show
BUG=42660
TEST=none
Review URL: http://codereview.chromium.org/1703015
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@45859 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'chrome_frame/test/exception_barrier_unittest.cc')
-rw-r--r-- | chrome_frame/test/exception_barrier_unittest.cc | 361 |
1 files changed, 361 insertions, 0 deletions
diff --git a/chrome_frame/test/exception_barrier_unittest.cc b/chrome_frame/test/exception_barrier_unittest.cc new file mode 100644 index 0000000..515f4a1 --- /dev/null +++ b/chrome_frame/test/exception_barrier_unittest.cc @@ -0,0 +1,361 @@ +// Copyright (c) 2010 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 "gtest/gtest.h" +#include "chrome_frame/exception_barrier.h" + +namespace { + +// retrieves the top SEH registration record +__declspec(naked) EXCEPTION_REGISTRATION* GetTopRegistration() { + __asm { + mov eax, FS:0 + ret + } +} + +// This function walks the SEH chain and attempts to ascertain whether it's +// sane, or rather tests it for any obvious signs of insanity. +// The signs it's capable of looking for are: +// # Is each exception registration in bounds of our stack +// # Is the registration DWORD aligned +// # Does each exception handler point to a module, as opposed to +// e.g. into the stack or never-never land. +// # Do successive entries in the exception chain increase +// monotonically in address +void TestSEHChainSane() { + // get the skinny on our stack segment + MEMORY_BASIC_INFORMATION info = { 0 }; + // Note that we pass the address of the info struct just as a handy + // moniker to anything at all inside our stack allocation + ASSERT_NE(0, ::VirtualQuery(&info, &info, sizeof(info))); + + // The lower bound of our stack. + // We use the address of info as a lower bound, this assumes that if this + // function has an SEH handler, it'll be higher up in our invocation + // record. + EXCEPTION_REGISTRATION* limit = + reinterpret_cast<EXCEPTION_REGISTRATION*>(&info); + // the very top of our stack segment + EXCEPTION_REGISTRATION* top = + reinterpret_cast<EXCEPTION_REGISTRATION*>( + reinterpret_cast<char*>(info.BaseAddress) + info.RegionSize); + + EXCEPTION_REGISTRATION* curr = GetTopRegistration(); + // there MUST be at least one registration + ASSERT_TRUE(NULL != curr); + + EXCEPTION_REGISTRATION* prev = NULL; + const EXCEPTION_REGISTRATION* kSentinel = + reinterpret_cast<EXCEPTION_REGISTRATION*>(0xFFFFFFFF); + for (; kSentinel != curr; prev = curr, curr = curr->prev) { + // registrations must increase monotonically + ASSERT_TRUE(curr > prev); + // Check it's in bounds + ASSERT_GE(top, curr); + ASSERT_LT(limit, curr); + + // check for DWORD alignment + ASSERT_EQ(0, (reinterpret_cast<UINT_PTR>(prev) & 0x00000003)); + + // find the module hosting the handler + ASSERT_NE(0, ::VirtualQuery(curr->handler, &info, sizeof(info))); + wchar_t module_filename[MAX_PATH]; + ASSERT_NE(0, ::GetModuleFileName( + reinterpret_cast<HMODULE>(info.AllocationBase), + module_filename, ARRAYSIZE(module_filename))); + } +} + +void AccessViolationCrash() { + volatile char* null = NULL; + *null = '\0'; +} + +// A simple crash over the exception barrier +void CrashOverExceptionBarrier() { + ExceptionBarrier barrier; + + TestSEHChainSane(); + + AccessViolationCrash(); + + TestSEHChainSane(); +} + +#pragma warning(push) + // Inline asm assigning to 'FS:0' : handler not registered as safe handler + // This warning is in error (the compiler can't know that we register the + // handler as a safe SEH handler in an .asm file) + #pragma warning(disable:4733) +// Hand-generate an SEH frame implicating the ExceptionBarrierHandler, +// then crash to invoke it. +__declspec(naked) void CrashOnManualSEHBarrierHandler() { + __asm { + push ExceptionBarrierHandler + push FS:0 + mov FS:0, esp + call AccessViolationCrash + ret + } +} +#pragma warning(pop) + +class ExceptionBarrierTest: public testing::Test { +public: + ExceptionBarrierTest() : old_handler_(NULL) { + } + + // Install an exception handler for the ExceptionBarrier, and + // set the handled flag to false. This allows us to see whether + // the ExceptionBarrier gets to handle the exception + virtual void SetUp() { + old_handler_ = ExceptionBarrier::handler(); + ExceptionBarrier::set_handler(ExceptionHandler); + s_handled_ = false; + + TestSEHChainSane(); + } + + virtual void TearDown() { + ExceptionBarrier::set_handler(old_handler_); + + TestSEHChainSane(); + } + + // The exception notification callback, sets the handled flag. + static void CALLBACK ExceptionHandler(EXCEPTION_POINTERS* ptrs) { + TestSEHChainSane(); + + s_handled_ = true; + } + + // Old handler to restore on TearDown + ExceptionBarrier::ExceptionHandler old_handler_; + + // Flag is set by handler + static bool s_handled_; +}; + +bool ExceptionBarrierTest::s_handled_; + +bool TestExceptionExceptionBarrierHandler() { + TestSEHChainSane(); + __try { + CrashOnManualSEHBarrierHandler(); + return false; // not reached + } __except(EXCEPTION_ACCESS_VIOLATION == GetExceptionCode() ? + EXCEPTION_EXECUTE_HANDLER : EXCEPTION_CONTINUE_SEARCH) { + TestSEHChainSane(); + return true; + } + + return false; // not reached +} + +typedef EXCEPTION_DISPOSITION +(__cdecl* ExceptionBarrierHandlerFunc)( + struct _EXCEPTION_RECORD* exception_record, + void* establisher_frame, + struct _CONTEXT* context, + void* reserved); + +TEST_F(ExceptionBarrierTest, RegisterUnregister) { + // Assert that registration modifies the chain + // and the registered record as expected + EXCEPTION_REGISTRATION* top = GetTopRegistration(); + ExceptionBarrierHandlerFunc handler = top->handler; + EXCEPTION_REGISTRATION* prev = top->prev; + + EXCEPTION_REGISTRATION registration; + ::RegisterExceptionRecord(®istration, ExceptionBarrierHandler); + EXPECT_EQ(GetTopRegistration(), ®istration); + EXPECT_EQ(ExceptionBarrierHandler, registration.handler); + EXPECT_EQ(top, registration.prev); + + // test the whole chain for good measure + TestSEHChainSane(); + + // Assert that unregistration restores + // everything as expected + ::UnregisterExceptionRecord(®istration); + EXPECT_EQ(top, GetTopRegistration()); + EXPECT_EQ(handler, top->handler); + EXPECT_EQ(prev, top->prev); + + // and again test the whole chain for good measure + TestSEHChainSane(); +} + + +TEST_F(ExceptionBarrierTest, ExceptionBarrierHandler) { + EXPECT_TRUE(TestExceptionExceptionBarrierHandler()); + EXPECT_TRUE(s_handled_); +} + +bool TestExceptionBarrier() { + __try { + CrashOverExceptionBarrier(); + } __except(EXCEPTION_ACCESS_VIOLATION == GetExceptionCode() ? + EXCEPTION_EXECUTE_HANDLER : EXCEPTION_CONTINUE_SEARCH) { + TestSEHChainSane(); + return true; + } + + return false; +} + +TEST_F(ExceptionBarrierTest, HandlesAccessViolationException) { + TestExceptionBarrier(); + EXPECT_TRUE(s_handled_); +} + +void RecurseAndCrash(int depth) { + __try { + __try { + if (0 == depth) + AccessViolationCrash(); + else + RecurseAndCrash(depth - 1); + + TestSEHChainSane(); + } __except(EXCEPTION_CONTINUE_SEARCH) { + TestSEHChainSane(); + } + } __finally { + TestSEHChainSane(); + } +} + +// This test exists only for comparison with TestExceptionBarrierChaining, and +// to "document" how the SEH chain is manipulated under our compiler. +// The two tests are expected to both fail if the particulars of the compiler's +// SEH implementation happens to change. +bool TestRegularChaining(EXCEPTION_REGISTRATION* top) { + // This test relies on compiler-dependent details, notably we rely on the + // compiler to generate a single SEH frame for the entire function, as + // opposed to e.g. generating a separate SEH frame for each __try __except + // statement. + EXCEPTION_REGISTRATION* my_top = GetTopRegistration(); + if (my_top == top) + return false; + + __try { + // we should have the new entry in effect here still + if (GetTopRegistration() != my_top) + return false; + } __except(EXCEPTION_EXECUTE_HANDLER) { + return false; + } + + __try { + AccessViolationCrash(); + return false; // not reached + } __except(EXCEPTION_EXECUTE_HANDLER) { + // and here + if (GetTopRegistration() != my_top) + return false; + } + + __try { + RecurseAndCrash(10); + return false; // not reached + } __except(EXCEPTION_EXECUTE_HANDLER) { + // we should have unrolled to our frame by now + if (GetTopRegistration() != my_top) + return false; + } + + return true; +} + +void RecurseAndCrashOverBarrier(int depth, bool crash) { + ExceptionBarrier barrier; + + if (0 == depth) { + if (crash) + AccessViolationCrash(); + } else { + RecurseAndCrashOverBarrier(depth - 1, crash); + } +} + +// Test that ExceptionBarrier doesn't molest the SEH chain, neither +// for regular unwinding, nor on exception unwinding cases. +// +// Note that while this test shows the ExceptionBarrier leaves the chain +// sane on both those cases, it's not clear that it does the right thing +// during first-chance exception handling. I can't think of a way to test +// that though, because first-chance exception handling is very difficult +// to hook into and to observe. +static bool TestExceptionBarrierChaining(EXCEPTION_REGISTRATION* top) { + TestSEHChainSane(); + + // This test relies on compiler-dependent details, notably we rely on the + // compiler to generate a single SEH frame for the entire function, as + // opposed to e.g. generating a separate SEH frame for each __try __except + // statement. + // Unfortunately we can't use ASSERT macros here, because they create + // temporary objects and the compiler doesn't grok non POD objects + // intermingled with __try and other SEH constructs. + EXCEPTION_REGISTRATION* my_top = GetTopRegistration(); + if (my_top == top) + return false; + + __try { + // we should have the new entry in effect here still + if (GetTopRegistration() != my_top) + return false; + } __except(EXCEPTION_EXECUTE_HANDLER) { + return false; // Not reached + } + + __try { + CrashOverExceptionBarrier(); + return false; // Not reached + } __except(EXCEPTION_EXECUTE_HANDLER) { + // and here + if (GetTopRegistration() != my_top) + return false; + } + TestSEHChainSane(); + + __try { + RecurseAndCrashOverBarrier(10, true); + return false; // not reached + } __except(EXCEPTION_EXECUTE_HANDLER) { + // we should have unrolled to our frame by now + if (GetTopRegistration() != my_top) + return false; + } + TestSEHChainSane(); + + __try { + RecurseAndCrashOverBarrier(10, false); + + // we should have unrolled to our frame by now + if (GetTopRegistration() != my_top) + return false; + } __except(EXCEPTION_EXECUTE_HANDLER) { + return false; // not reached + } + TestSEHChainSane(); + + // success. + return true; +} + +static bool TestChaining() { + EXCEPTION_REGISTRATION* top = GetTopRegistration(); + + return TestRegularChaining(top) && TestExceptionBarrierChaining(top); +} + +// Test that the SEH chain is unmolested by exception barrier, both under +// regular unroll, and under exception unroll. +TEST_F(ExceptionBarrierTest, SEHChainIsSaneAfterException) { + EXPECT_TRUE(TestChaining()); +} + +} // namespace
\ No newline at end of file |