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(0, ::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(0, ::VirtualQuery(curr->handler, &info, sizeof(info))); |
| 64 wchar_t module_filename[MAX_PATH]; |
| 65 ASSERT_NE(0, ::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 ExceptionBarrier 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 ExceptionBarrierHandler, |
| 93 // then crash to invoke it. |
| 94 __declspec(naked) void CrashOnManualSEHBarrierHandler() { |
| 95 __asm { |
| 96 push ExceptionBarrierHandler |
| 97 push FS:0 |
| 98 mov FS:0, esp |
| 99 call AccessViolationCrash |
| 100 ret |
| 101 } |
| 102 } |
| 103 #pragma warning(pop) |
| 104 |
| 105 class ExceptionBarrierTest: public testing::Test { |
| 106 public: |
| 107 ExceptionBarrierTest() : old_handler_(NULL) { |
| 108 } |
| 109 |
| 110 // Install an exception handler for the ExceptionBarrier, and |
| 111 // set the handled flag to false. This allows us to see whether |
| 112 // the ExceptionBarrier gets to handle the exception |
| 113 virtual void SetUp() { |
| 114 old_handler_ = ExceptionBarrier::handler(); |
| 115 ExceptionBarrier::set_handler(ExceptionHandler); |
| 116 s_handled_ = false; |
| 117 |
| 118 TestSEHChainSane(); |
| 119 } |
| 120 |
| 121 virtual void TearDown() { |
| 122 ExceptionBarrier::set_handler(old_handler_); |
| 123 |
| 124 TestSEHChainSane(); |
| 125 } |
| 126 |
| 127 // The exception notification callback, sets the handled flag. |
| 128 static void CALLBACK ExceptionHandler(EXCEPTION_POINTERS* ptrs) { |
| 129 TestSEHChainSane(); |
| 130 |
| 131 s_handled_ = true; |
| 132 } |
| 133 |
| 134 // Old handler to restore on TearDown |
| 135 ExceptionBarrier::ExceptionHandler old_handler_; |
| 136 |
| 137 // Flag is set by handler |
| 138 static bool s_handled_; |
| 139 }; |
| 140 |
| 141 bool ExceptionBarrierTest::s_handled_; |
| 142 |
| 143 bool TestExceptionExceptionBarrierHandler() { |
| 144 TestSEHChainSane(); |
| 145 __try { |
| 146 CrashOnManualSEHBarrierHandler(); |
| 147 return false; // not reached |
| 148 } __except(EXCEPTION_ACCESS_VIOLATION == GetExceptionCode() ? |
| 149 EXCEPTION_EXECUTE_HANDLER : EXCEPTION_CONTINUE_SEARCH) { |
| 150 TestSEHChainSane(); |
| 151 return true; |
| 152 } |
| 153 |
| 154 return false; // not reached |
| 155 } |
| 156 |
| 157 typedef EXCEPTION_DISPOSITION |
| 158 (__cdecl* ExceptionBarrierHandlerFunc)( |
| 159 struct _EXCEPTION_RECORD* exception_record, |
| 160 void* establisher_frame, |
| 161 struct _CONTEXT* context, |
| 162 void* reserved); |
| 163 |
| 164 TEST_F(ExceptionBarrierTest, RegisterUnregister) { |
| 165 // Assert that registration modifies the chain |
| 166 // and the registered record as expected |
| 167 EXCEPTION_REGISTRATION* top = GetTopRegistration(); |
| 168 ExceptionBarrierHandlerFunc handler = top->handler; |
| 169 EXCEPTION_REGISTRATION* prev = top->prev; |
| 170 |
| 171 EXCEPTION_REGISTRATION registration; |
| 172 ::RegisterExceptionRecord(®istration, ExceptionBarrierHandler); |
| 173 EXPECT_EQ(GetTopRegistration(), ®istration); |
| 174 EXPECT_EQ(ExceptionBarrierHandler, registration.handler); |
| 175 EXPECT_EQ(top, registration.prev); |
| 176 |
| 177 // test the whole chain for good measure |
| 178 TestSEHChainSane(); |
| 179 |
| 180 // Assert that unregistration restores |
| 181 // everything as expected |
| 182 ::UnregisterExceptionRecord(®istration); |
| 183 EXPECT_EQ(top, GetTopRegistration()); |
| 184 EXPECT_EQ(handler, top->handler); |
| 185 EXPECT_EQ(prev, top->prev); |
| 186 |
| 187 // and again test the whole chain for good measure |
| 188 TestSEHChainSane(); |
| 189 } |
| 190 |
| 191 |
| 192 TEST_F(ExceptionBarrierTest, ExceptionBarrierHandler) { |
| 193 EXPECT_TRUE(TestExceptionExceptionBarrierHandler()); |
| 194 EXPECT_TRUE(s_handled_); |
| 195 } |
| 196 |
| 197 bool TestExceptionBarrier() { |
| 198 __try { |
| 199 CrashOverExceptionBarrier(); |
| 200 } __except(EXCEPTION_ACCESS_VIOLATION == GetExceptionCode() ? |
| 201 EXCEPTION_EXECUTE_HANDLER : EXCEPTION_CONTINUE_SEARCH) { |
| 202 TestSEHChainSane(); |
| 203 return true; |
| 204 } |
| 205 |
| 206 return false; |
| 207 } |
| 208 |
| 209 TEST_F(ExceptionBarrierTest, HandlesAccessViolationException) { |
| 210 TestExceptionBarrier(); |
| 211 EXPECT_TRUE(s_handled_); |
| 212 } |
| 213 |
| 214 void RecurseAndCrash(int depth) { |
| 215 __try { |
| 216 __try { |
| 217 if (0 == depth) |
| 218 AccessViolationCrash(); |
| 219 else |
| 220 RecurseAndCrash(depth - 1); |
| 221 |
| 222 TestSEHChainSane(); |
| 223 } __except(EXCEPTION_CONTINUE_SEARCH) { |
| 224 TestSEHChainSane(); |
| 225 } |
| 226 } __finally { |
| 227 TestSEHChainSane(); |
| 228 } |
| 229 } |
| 230 |
| 231 // This test exists only for comparison with TestExceptionBarrierChaining, and |
| 232 // to "document" how the SEH chain is manipulated under our compiler. |
| 233 // The two tests are expected to both fail if the particulars of the compiler's |
| 234 // SEH implementation happens to change. |
| 235 bool TestRegularChaining(EXCEPTION_REGISTRATION* top) { |
| 236 // This test relies on compiler-dependent details, notably we rely on the |
| 237 // compiler to generate a single SEH frame for the entire function, as |
| 238 // opposed to e.g. generating a separate SEH frame for each __try __except |
| 239 // statement. |
| 240 EXCEPTION_REGISTRATION* my_top = GetTopRegistration(); |
| 241 if (my_top == top) |
| 242 return false; |
| 243 |
| 244 __try { |
| 245 // we should have the new entry in effect here still |
| 246 if (GetTopRegistration() != my_top) |
| 247 return false; |
| 248 } __except(EXCEPTION_EXECUTE_HANDLER) { |
| 249 return false; |
| 250 } |
| 251 |
| 252 __try { |
| 253 AccessViolationCrash(); |
| 254 return false; // not reached |
| 255 } __except(EXCEPTION_EXECUTE_HANDLER) { |
| 256 // and here |
| 257 if (GetTopRegistration() != my_top) |
| 258 return false; |
| 259 } |
| 260 |
| 261 __try { |
| 262 RecurseAndCrash(10); |
| 263 return false; // not reached |
| 264 } __except(EXCEPTION_EXECUTE_HANDLER) { |
| 265 // we should have unrolled to our frame by now |
| 266 if (GetTopRegistration() != my_top) |
| 267 return false; |
| 268 } |
| 269 |
| 270 return true; |
| 271 } |
| 272 |
| 273 void RecurseAndCrashOverBarrier(int depth, bool crash) { |
| 274 ExceptionBarrier barrier; |
| 275 |
| 276 if (0 == depth) { |
| 277 if (crash) |
| 278 AccessViolationCrash(); |
| 279 } else { |
| 280 RecurseAndCrashOverBarrier(depth - 1, crash); |
| 281 } |
| 282 } |
| 283 |
| 284 // Test that ExceptionBarrier doesn't molest the SEH chain, neither |
| 285 // for regular unwinding, nor on exception unwinding cases. |
| 286 // |
| 287 // Note that while this test shows the ExceptionBarrier leaves the chain |
| 288 // sane on both those cases, it's not clear that it does the right thing |
| 289 // during first-chance exception handling. I can't think of a way to test |
| 290 // that though, because first-chance exception handling is very difficult |
| 291 // to hook into and to observe. |
| 292 static bool TestExceptionBarrierChaining(EXCEPTION_REGISTRATION* top) { |
| 293 TestSEHChainSane(); |
| 294 |
| 295 // This test relies on compiler-dependent details, notably we rely on the |
| 296 // compiler to generate a single SEH frame for the entire function, as |
| 297 // opposed to e.g. generating a separate SEH frame for each __try __except |
| 298 // statement. |
| 299 // Unfortunately we can't use ASSERT macros here, because they create |
| 300 // temporary objects and the compiler doesn't grok non POD objects |
| 301 // intermingled with __try and other SEH constructs. |
| 302 EXCEPTION_REGISTRATION* my_top = GetTopRegistration(); |
| 303 if (my_top == top) |
| 304 return false; |
| 305 |
| 306 __try { |
| 307 // we should have the new entry in effect here still |
| 308 if (GetTopRegistration() != my_top) |
| 309 return false; |
| 310 } __except(EXCEPTION_EXECUTE_HANDLER) { |
| 311 return false; // Not reached |
| 312 } |
| 313 |
| 314 __try { |
| 315 CrashOverExceptionBarrier(); |
| 316 return false; // Not reached |
| 317 } __except(EXCEPTION_EXECUTE_HANDLER) { |
| 318 // and here |
| 319 if (GetTopRegistration() != my_top) |
| 320 return false; |
| 321 } |
| 322 TestSEHChainSane(); |
| 323 |
| 324 __try { |
| 325 RecurseAndCrashOverBarrier(10, true); |
| 326 return false; // not reached |
| 327 } __except(EXCEPTION_EXECUTE_HANDLER) { |
| 328 // we should have unrolled to our frame by now |
| 329 if (GetTopRegistration() != my_top) |
| 330 return false; |
| 331 } |
| 332 TestSEHChainSane(); |
| 333 |
| 334 __try { |
| 335 RecurseAndCrashOverBarrier(10, false); |
| 336 |
| 337 // we should have unrolled to our frame by now |
| 338 if (GetTopRegistration() != my_top) |
| 339 return false; |
| 340 } __except(EXCEPTION_EXECUTE_HANDLER) { |
| 341 return false; // not reached |
| 342 } |
| 343 TestSEHChainSane(); |
| 344 |
| 345 // success. |
| 346 return true; |
| 347 } |
| 348 |
| 349 static bool TestChaining() { |
| 350 EXCEPTION_REGISTRATION* top = GetTopRegistration(); |
| 351 |
| 352 return TestRegularChaining(top) && TestExceptionBarrierChaining(top); |
| 353 } |
| 354 |
| 355 // Test that the SEH chain is unmolested by exception barrier, both under |
| 356 // regular unroll, and under exception unroll. |
| 357 TEST_F(ExceptionBarrierTest, SEHChainIsSaneAfterException) { |
| 358 EXPECT_TRUE(TestChaining()); |
| 359 } |
| 360 |
| 361 } // namespace |
OLD | NEW |