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.
5 #include "gtest/gtest.h"
6 #include "chrome_frame/exception_barrier.h"
10 // retrieves the top SEH registration record
11 __declspec(naked
) EXCEPTION_REGISTRATION
* GetTopRegistration() {
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
)));
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
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
);
45 EXCEPTION_REGISTRATION
* curr
= GetTopRegistration();
46 // there MUST be at least one registration
47 ASSERT_TRUE(NULL
!= curr
);
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
57 ASSERT_LT(limit
, curr
);
59 // check for DWORD alignment
60 ASSERT_EQ(0, (reinterpret_cast<UINT_PTR
>(prev
) & 0x00000003));
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
)));
71 void AccessViolationCrash() {
72 volatile char* null
= NULL
;
76 // A simple crash over the exception barrier
77 void CrashOverExceptionBarrier() {
78 ExceptionBarrierCustomHandler barrier
;
82 AccessViolationCrash();
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() {
96 push ExceptionBarrierCallCustomHandler
99 call AccessViolationCrash
106 class ExceptionBarrierTest
: public testing::Test
{
108 ExceptionBarrierTest() {
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
);
122 virtual void TearDown() {
124 ExceptionBarrierCustomHandler::set_custom_handler(NULL
);
125 ExceptionBarrierConfig::set_enabled(false);
128 // The exception notification callback, sets the handled flag.
129 static void CALLBACK
ExceptionHandler(EXCEPTION_POINTERS
* ptrs
) {
134 // Flag is set by handler
135 static bool s_handled_
;
138 bool ExceptionBarrierTest::s_handled_
= false;
140 bool TestExceptionExceptionBarrierHandler() {
143 CrashOnManualSEHBarrierHandler();
144 return false; // not reached
145 } __except(EXCEPTION_ACCESS_VIOLATION
== GetExceptionCode() ?
146 EXCEPTION_EXECUTE_HANDLER
: EXCEPTION_CONTINUE_SEARCH
) {
151 return false; // not reached
154 typedef EXCEPTION_DISPOSITION
155 (__cdecl
* ExceptionBarrierHandlerFunc
)(
156 struct _EXCEPTION_RECORD
* exception_record
,
157 void* establisher_frame
,
158 struct _CONTEXT
* context
,
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
;
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
);
174 // test the whole chain for good measure
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
);
184 // and again test the whole chain for good measure
189 TEST_F(ExceptionBarrierTest
, ExceptionBarrierHandler
) {
190 EXPECT_TRUE(TestExceptionExceptionBarrierHandler());
191 EXPECT_TRUE(s_handled_
);
194 bool TestExceptionBarrier() {
196 CrashOverExceptionBarrier();
197 } __except(EXCEPTION_ACCESS_VIOLATION
== GetExceptionCode() ?
198 EXCEPTION_EXECUTE_HANDLER
: EXCEPTION_CONTINUE_SEARCH
) {
206 TEST_F(ExceptionBarrierTest
, HandlesAccessViolationException
) {
207 TestExceptionBarrier();
208 EXPECT_TRUE(s_handled_
);
211 void RecurseAndCrash(int depth
) {
215 AccessViolationCrash();
217 RecurseAndCrash(depth
- 1);
220 } __except(EXCEPTION_CONTINUE_SEARCH
) {
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
237 EXCEPTION_REGISTRATION
* my_top
= GetTopRegistration();
242 // we should have the new entry in effect here still
243 if (GetTopRegistration() != my_top
)
245 } __except(EXCEPTION_EXECUTE_HANDLER
) {
250 AccessViolationCrash();
251 return false; // not reached
252 } __except(EXCEPTION_EXECUTE_HANDLER
) {
254 if (GetTopRegistration() != my_top
)
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
)
270 void RecurseAndCrashOverBarrier(int depth
, bool crash
) {
271 ExceptionBarrierCustomHandler barrier
;
275 AccessViolationCrash();
277 RecurseAndCrashOverBarrier(depth
- 1, crash
);
281 // Test that ExceptionBarrier doesn't molest the SEH chain, neither
282 // for regular unwinding, nor on exception unwinding cases.
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
) {
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
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();
304 // we should have the new entry in effect here still
305 if (GetTopRegistration() != my_top
)
307 } __except(EXCEPTION_EXECUTE_HANDLER
) {
308 return false; // Not reached
312 CrashOverExceptionBarrier();
313 return false; // Not reached
314 } __except(EXCEPTION_EXECUTE_HANDLER
) {
316 if (GetTopRegistration() != my_top
)
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
)
332 RecurseAndCrashOverBarrier(10, false);
334 // we should have unrolled to our frame by now
335 if (GetTopRegistration() != my_top
)
337 } __except(EXCEPTION_EXECUTE_HANDLER
) {
338 return false; // not reached
346 static bool TestChaining() {
347 EXCEPTION_REGISTRATION
* top
= GetTopRegistration();
349 return TestRegularChaining(top
) && TestExceptionBarrierChaining(top
);
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());