summaryrefslogtreecommitdiffstats
path: root/chrome_frame/test/exception_barrier_unittest.cc
diff options
context:
space:
mode:
authorrobertshield@chromium.org <robertshield@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2010-04-28 20:59:03 +0000
committerrobertshield@chromium.org <robertshield@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2010-04-28 20:59:03 +0000
commitdcdcfa08eabd39a5efd01eb32e47b261805abf4b (patch)
tree832f0784469978cbdd2017d7f450f7cb084bc467 /chrome_frame/test/exception_barrier_unittest.cc
parent5bba583182085a5d9ef2ff586b5a648b6e3e950a (diff)
downloadchromium_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.cc361
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(&registration, ExceptionBarrierHandler);
+ EXPECT_EQ(GetTopRegistration(), &registration);
+ 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(&registration);
+ 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