| OLD | NEW |
| (Empty) |
| 1 // Copyright (c) 2010 The Chromium Authors. All rights reserved. | |
| 2 // Use of this source code is governed by a BSD-style license that can be | |
| 3 // found in the LICENSE file. | |
| 4 | |
| 5 #include "gtest/gtest.h" | |
| 6 #include "chrome_frame/exception_barrier.h" | |
| 7 | |
| 8 namespace { | |
| 9 | |
| 10 // retrieves the top SEH registration record | |
| 11 __declspec(naked) EXCEPTION_REGISTRATION* GetTopRegistration() { | |
| 12 __asm { | |
| 13 mov eax, FS:0 | |
| 14 ret | |
| 15 } | |
| 16 } | |
| 17 | |
| 18 // This function walks the SEH chain and attempts to ascertain whether it's | |
| 19 // sane, or rather tests it for any obvious signs of insanity. | |
| 20 // The signs it's capable of looking for are: | |
| 21 // # Is each exception registration in bounds of our stack | |
| 22 // # Is the registration DWORD aligned | |
| 23 // # Does each exception handler point to a module, as opposed to | |
| 24 // e.g. into the stack or never-never land. | |
| 25 // # Do successive entries in the exception chain increase | |
| 26 // monotonically in address | |
| 27 void TestSEHChainSane() { | |
| 28 // get the skinny on our stack segment | |
| 29 MEMORY_BASIC_INFORMATION info = { 0 }; | |
| 30 // Note that we pass the address of the info struct just as a handy | |
| 31 // moniker to anything at all inside our stack allocation | |
| 32 ASSERT_NE(0u, ::VirtualQuery(&info, &info, sizeof(info))); | |
| 33 | |
| 34 // The lower bound of our stack. | |
| 35 // We use the address of info as a lower bound, this assumes that if this | |
| 36 // function has an SEH handler, it'll be higher up in our invocation | |
| 37 // record. | |
| 38 EXCEPTION_REGISTRATION* limit = | |
| 39 reinterpret_cast<EXCEPTION_REGISTRATION*>(&info); | |
| 40 // the very top of our stack segment | |
| 41 EXCEPTION_REGISTRATION* top = | |
| 42 reinterpret_cast<EXCEPTION_REGISTRATION*>( | |
| 43 reinterpret_cast<char*>(info.BaseAddress) + info.RegionSize); | |
| 44 | |
| 45 EXCEPTION_REGISTRATION* curr = GetTopRegistration(); | |
| 46 // there MUST be at least one registration | |
| 47 ASSERT_TRUE(NULL != curr); | |
| 48 | |
| 49 EXCEPTION_REGISTRATION* prev = NULL; | |
| 50 const EXCEPTION_REGISTRATION* kSentinel = | |
| 51 reinterpret_cast<EXCEPTION_REGISTRATION*>(0xFFFFFFFF); | |
| 52 for (; kSentinel != curr; prev = curr, curr = curr->prev) { | |
| 53 // registrations must increase monotonically | |
| 54 ASSERT_TRUE(curr > prev); | |
| 55 // Check it's in bounds | |
| 56 ASSERT_GE(top, curr); | |
| 57 ASSERT_LT(limit, curr); | |
| 58 | |
| 59 // check for DWORD alignment | |
| 60 ASSERT_EQ(0, (reinterpret_cast<UINT_PTR>(prev) & 0x00000003)); | |
| 61 | |
| 62 // find the module hosting the handler | |
| 63 ASSERT_NE(0u, ::VirtualQuery(curr->handler, &info, sizeof(info))); | |
| 64 wchar_t module_filename[MAX_PATH]; | |
| 65 ASSERT_NE(0u, ::GetModuleFileName( | |
| 66 reinterpret_cast<HMODULE>(info.AllocationBase), | |
| 67 module_filename, ARRAYSIZE(module_filename))); | |
| 68 } | |
| 69 } | |
| 70 | |
| 71 void AccessViolationCrash() { | |
| 72 volatile char* null = NULL; | |
| 73 *null = '\0'; | |
| 74 } | |
| 75 | |
| 76 // A simple crash over the exception barrier | |
| 77 void CrashOverExceptionBarrier() { | |
| 78 ExceptionBarrierCustomHandler barrier; | |
| 79 | |
| 80 TestSEHChainSane(); | |
| 81 | |
| 82 AccessViolationCrash(); | |
| 83 | |
| 84 TestSEHChainSane(); | |
| 85 } | |
| 86 | |
| 87 #pragma warning(push) | |
| 88 // Inline asm assigning to 'FS:0' : handler not registered as safe handler | |
| 89 // This warning is in error (the compiler can't know that we register the | |
| 90 // handler as a safe SEH handler in an .asm file) | |
| 91 #pragma warning(disable:4733) | |
| 92 // Hand-generate an SEH frame implicating the ExceptionBarrierCallCustomHandler, | |
| 93 // then crash to invoke it. | |
| 94 __declspec(naked) void CrashOnManualSEHBarrierHandler() { | |
| 95 __asm { | |
| 96 push ExceptionBarrierCallCustomHandler | |
| 97 push FS:0 | |
| 98 mov FS:0, esp | |
| 99 call AccessViolationCrash | |
| 100 ret | |
| 101 } | |
| 102 } | |
| 103 #pragma warning(pop) | |
| 104 | |
| 105 | |
| 106 class ExceptionBarrierTest: public testing::Test { | |
| 107 public: | |
| 108 ExceptionBarrierTest() { | |
| 109 } | |
| 110 | |
| 111 // Install an exception handler for the ExceptionBarrier, and | |
| 112 // set the handled flag to false. This allows us to see whether | |
| 113 // the ExceptionBarrier gets to handle the exception | |
| 114 virtual void SetUp() { | |
| 115 ExceptionBarrierConfig::set_enabled(true); | |
| 116 ExceptionBarrierCustomHandler::set_custom_handler(&ExceptionHandler); | |
| 117 s_handled_ = false; | |
| 118 | |
| 119 TestSEHChainSane(); | |
| 120 } | |
| 121 | |
| 122 virtual void TearDown() { | |
| 123 TestSEHChainSane(); | |
| 124 ExceptionBarrierCustomHandler::set_custom_handler(NULL); | |
| 125 ExceptionBarrierConfig::set_enabled(false); | |
| 126 } | |
| 127 | |
| 128 // The exception notification callback, sets the handled flag. | |
| 129 static void CALLBACK ExceptionHandler(EXCEPTION_POINTERS* ptrs) { | |
| 130 TestSEHChainSane(); | |
| 131 s_handled_ = true; | |
| 132 } | |
| 133 | |
| 134 // Flag is set by handler | |
| 135 static bool s_handled_; | |
| 136 }; | |
| 137 | |
| 138 bool ExceptionBarrierTest::s_handled_ = false; | |
| 139 | |
| 140 bool TestExceptionExceptionBarrierHandler() { | |
| 141 TestSEHChainSane(); | |
| 142 __try { | |
| 143 CrashOnManualSEHBarrierHandler(); | |
| 144 return false; // not reached | |
| 145 } __except(EXCEPTION_ACCESS_VIOLATION == GetExceptionCode() ? | |
| 146 EXCEPTION_EXECUTE_HANDLER : EXCEPTION_CONTINUE_SEARCH) { | |
| 147 TestSEHChainSane(); | |
| 148 return true; | |
| 149 } | |
| 150 | |
| 151 return false; // not reached | |
| 152 } | |
| 153 | |
| 154 typedef EXCEPTION_DISPOSITION | |
| 155 (__cdecl* ExceptionBarrierHandlerFunc)( | |
| 156 struct _EXCEPTION_RECORD* exception_record, | |
| 157 void* establisher_frame, | |
| 158 struct _CONTEXT* context, | |
| 159 void* reserved); | |
| 160 | |
| 161 TEST_F(ExceptionBarrierTest, RegisterUnregister) { | |
| 162 // Assert that registration modifies the chain | |
| 163 // and the registered record as expected | |
| 164 EXCEPTION_REGISTRATION* top = GetTopRegistration(); | |
| 165 ExceptionBarrierHandlerFunc handler = top->handler; | |
| 166 EXCEPTION_REGISTRATION* prev = top->prev; | |
| 167 | |
| 168 EXCEPTION_REGISTRATION registration; | |
| 169 ::RegisterExceptionRecord(®istration, ExceptionBarrierHandler); | |
| 170 EXPECT_EQ(GetTopRegistration(), ®istration); | |
| 171 EXPECT_EQ(&ExceptionBarrierHandler, registration.handler); | |
| 172 EXPECT_EQ(top, registration.prev); | |
| 173 | |
| 174 // test the whole chain for good measure | |
| 175 TestSEHChainSane(); | |
| 176 | |
| 177 // Assert that unregistration restores | |
| 178 // everything as expected | |
| 179 ::UnregisterExceptionRecord(®istration); | |
| 180 EXPECT_EQ(top, GetTopRegistration()); | |
| 181 EXPECT_EQ(handler, top->handler); | |
| 182 EXPECT_EQ(prev, top->prev); | |
| 183 | |
| 184 // and again test the whole chain for good measure | |
| 185 TestSEHChainSane(); | |
| 186 } | |
| 187 | |
| 188 | |
| 189 TEST_F(ExceptionBarrierTest, ExceptionBarrierHandler) { | |
| 190 EXPECT_TRUE(TestExceptionExceptionBarrierHandler()); | |
| 191 EXPECT_TRUE(s_handled_); | |
| 192 } | |
| 193 | |
| 194 bool TestExceptionBarrier() { | |
| 195 __try { | |
| 196 CrashOverExceptionBarrier(); | |
| 197 } __except(EXCEPTION_ACCESS_VIOLATION == GetExceptionCode() ? | |
| 198 EXCEPTION_EXECUTE_HANDLER : EXCEPTION_CONTINUE_SEARCH) { | |
| 199 TestSEHChainSane(); | |
| 200 return true; | |
| 201 } | |
| 202 | |
| 203 return false; | |
| 204 } | |
| 205 | |
| 206 TEST_F(ExceptionBarrierTest, HandlesAccessViolationException) { | |
| 207 TestExceptionBarrier(); | |
| 208 EXPECT_TRUE(s_handled_); | |
| 209 } | |
| 210 | |
| 211 void RecurseAndCrash(int depth) { | |
| 212 __try { | |
| 213 __try { | |
| 214 if (0 == depth) | |
| 215 AccessViolationCrash(); | |
| 216 else | |
| 217 RecurseAndCrash(depth - 1); | |
| 218 | |
| 219 TestSEHChainSane(); | |
| 220 } __except(EXCEPTION_CONTINUE_SEARCH) { | |
| 221 TestSEHChainSane(); | |
| 222 } | |
| 223 } __finally { | |
| 224 TestSEHChainSane(); | |
| 225 } | |
| 226 } | |
| 227 | |
| 228 // This test exists only for comparison with TestExceptionBarrierChaining, and | |
| 229 // to "document" how the SEH chain is manipulated under our compiler. | |
| 230 // The two tests are expected to both fail if the particulars of the compiler's | |
| 231 // SEH implementation happens to change. | |
| 232 bool TestRegularChaining(EXCEPTION_REGISTRATION* top) { | |
| 233 // This test relies on compiler-dependent details, notably we rely on the | |
| 234 // compiler to generate a single SEH frame for the entire function, as | |
| 235 // opposed to e.g. generating a separate SEH frame for each __try __except | |
| 236 // statement. | |
| 237 EXCEPTION_REGISTRATION* my_top = GetTopRegistration(); | |
| 238 if (my_top == top) | |
| 239 return false; | |
| 240 | |
| 241 __try { | |
| 242 // we should have the new entry in effect here still | |
| 243 if (GetTopRegistration() != my_top) | |
| 244 return false; | |
| 245 } __except(EXCEPTION_EXECUTE_HANDLER) { | |
| 246 return false; | |
| 247 } | |
| 248 | |
| 249 __try { | |
| 250 AccessViolationCrash(); | |
| 251 return false; // not reached | |
| 252 } __except(EXCEPTION_EXECUTE_HANDLER) { | |
| 253 // and here | |
| 254 if (GetTopRegistration() != my_top) | |
| 255 return false; | |
| 256 } | |
| 257 | |
| 258 __try { | |
| 259 RecurseAndCrash(10); | |
| 260 return false; // not reached | |
| 261 } __except(EXCEPTION_EXECUTE_HANDLER) { | |
| 262 // we should have unrolled to our frame by now | |
| 263 if (GetTopRegistration() != my_top) | |
| 264 return false; | |
| 265 } | |
| 266 | |
| 267 return true; | |
| 268 } | |
| 269 | |
| 270 void RecurseAndCrashOverBarrier(int depth, bool crash) { | |
| 271 ExceptionBarrierCustomHandler barrier; | |
| 272 | |
| 273 if (0 == depth) { | |
| 274 if (crash) | |
| 275 AccessViolationCrash(); | |
| 276 } else { | |
| 277 RecurseAndCrashOverBarrier(depth - 1, crash); | |
| 278 } | |
| 279 } | |
| 280 | |
| 281 // Test that ExceptionBarrier doesn't molest the SEH chain, neither | |
| 282 // for regular unwinding, nor on exception unwinding cases. | |
| 283 // | |
| 284 // Note that while this test shows the ExceptionBarrier leaves the chain | |
| 285 // sane on both those cases, it's not clear that it does the right thing | |
| 286 // during first-chance exception handling. I can't think of a way to test | |
| 287 // that though, because first-chance exception handling is very difficult | |
| 288 // to hook into and to observe. | |
| 289 static bool TestExceptionBarrierChaining(EXCEPTION_REGISTRATION* top) { | |
| 290 TestSEHChainSane(); | |
| 291 | |
| 292 // This test relies on compiler-dependent details, notably we rely on the | |
| 293 // compiler to generate a single SEH frame for the entire function, as | |
| 294 // opposed to e.g. generating a separate SEH frame for each __try __except | |
| 295 // statement. | |
| 296 // Unfortunately we can't use ASSERT macros here, because they create | |
| 297 // temporary objects and the compiler doesn't grok non POD objects | |
| 298 // intermingled with __try and other SEH constructs. | |
| 299 EXCEPTION_REGISTRATION* my_top = GetTopRegistration(); | |
| 300 if (my_top == top) | |
| 301 return false; | |
| 302 | |
| 303 __try { | |
| 304 // we should have the new entry in effect here still | |
| 305 if (GetTopRegistration() != my_top) | |
| 306 return false; | |
| 307 } __except(EXCEPTION_EXECUTE_HANDLER) { | |
| 308 return false; // Not reached | |
| 309 } | |
| 310 | |
| 311 __try { | |
| 312 CrashOverExceptionBarrier(); | |
| 313 return false; // Not reached | |
| 314 } __except(EXCEPTION_EXECUTE_HANDLER) { | |
| 315 // and here | |
| 316 if (GetTopRegistration() != my_top) | |
| 317 return false; | |
| 318 } | |
| 319 TestSEHChainSane(); | |
| 320 | |
| 321 __try { | |
| 322 RecurseAndCrashOverBarrier(10, true); | |
| 323 return false; // not reached | |
| 324 } __except(EXCEPTION_EXECUTE_HANDLER) { | |
| 325 // we should have unrolled to our frame by now | |
| 326 if (GetTopRegistration() != my_top) | |
| 327 return false; | |
| 328 } | |
| 329 TestSEHChainSane(); | |
| 330 | |
| 331 __try { | |
| 332 RecurseAndCrashOverBarrier(10, false); | |
| 333 | |
| 334 // we should have unrolled to our frame by now | |
| 335 if (GetTopRegistration() != my_top) | |
| 336 return false; | |
| 337 } __except(EXCEPTION_EXECUTE_HANDLER) { | |
| 338 return false; // not reached | |
| 339 } | |
| 340 TestSEHChainSane(); | |
| 341 | |
| 342 // success. | |
| 343 return true; | |
| 344 } | |
| 345 | |
| 346 static bool TestChaining() { | |
| 347 EXCEPTION_REGISTRATION* top = GetTopRegistration(); | |
| 348 | |
| 349 return TestRegularChaining(top) && TestExceptionBarrierChaining(top); | |
| 350 } | |
| 351 | |
| 352 // Test that the SEH chain is unmolested by exception barrier, both under | |
| 353 // regular unroll, and under exception unroll. | |
| 354 TEST_F(ExceptionBarrierTest, SEHChainIsSaneAfterException) { | |
| 355 EXPECT_TRUE(TestChaining()); | |
| 356 } | |
| 357 | |
| 358 } // namespace | |
| OLD | NEW |