blob: eeffd2a3b85f6b642800f5094958072e3cd47700 [file] [log] [blame]
[email protected]dcdcfa02010-04-28 20:59:031// 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
8namespace {
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
27void 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
71void AccessViolationCrash() {
72 volatile char* null = NULL;
73 *null = '\0';
74}
75
76// A simple crash over the exception barrier
77void CrashOverExceptionBarrier() {
[email protected]72354312010-05-01 02:10:0678 ExceptionBarrierCustomHandler barrier;
[email protected]dcdcfa02010-04-28 20:59:0379
80 TestSEHChainSane();
81
82 AccessViolationCrash();
83
84 TestSEHChainSane();
85}
86
87#pragma warning(push)
[email protected]72354312010-05-01 02:10:0688// 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,
[email protected]dcdcfa02010-04-28 20:59:0393// then crash to invoke it.
94__declspec(naked) void CrashOnManualSEHBarrierHandler() {
95 __asm {
[email protected]72354312010-05-01 02:10:0696 push ExceptionBarrierCallCustomHandler
[email protected]dcdcfa02010-04-28 20:59:0397 push FS:0
98 mov FS:0, esp
99 call AccessViolationCrash
100 ret
101 }
102}
103#pragma warning(pop)
104
[email protected]72354312010-05-01 02:10:06105
[email protected]dcdcfa02010-04-28 20:59:03106class ExceptionBarrierTest: public testing::Test {
[email protected]72354312010-05-01 02:10:06107 public:
108 ExceptionBarrierTest() {
[email protected]dcdcfa02010-04-28 20:59:03109 }
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() {
[email protected]72354312010-05-01 02:10:06115 ExceptionBarrierConfig::set_enabled(true);
116 ExceptionBarrierCustomHandler::set_custom_handler(&ExceptionHandler);
[email protected]dcdcfa02010-04-28 20:59:03117 s_handled_ = false;
118
119 TestSEHChainSane();
120 }
121
122 virtual void TearDown() {
[email protected]dcdcfa02010-04-28 20:59:03123 TestSEHChainSane();
[email protected]72354312010-05-01 02:10:06124 ExceptionBarrierCustomHandler::set_custom_handler(NULL);
125 ExceptionBarrierConfig::set_enabled(false);
[email protected]dcdcfa02010-04-28 20:59:03126 }
127
128 // The exception notification callback, sets the handled flag.
129 static void CALLBACK ExceptionHandler(EXCEPTION_POINTERS* ptrs) {
130 TestSEHChainSane();
[email protected]dcdcfa02010-04-28 20:59:03131 s_handled_ = true;
132 }
133
[email protected]dcdcfa02010-04-28 20:59:03134 // Flag is set by handler
135 static bool s_handled_;
136};
137
[email protected]72354312010-05-01 02:10:06138bool ExceptionBarrierTest::s_handled_ = false;
[email protected]dcdcfa02010-04-28 20:59:03139
140bool 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
154typedef EXCEPTION_DISPOSITION
155(__cdecl* ExceptionBarrierHandlerFunc)(
156 struct _EXCEPTION_RECORD* exception_record,
157 void* establisher_frame,
158 struct _CONTEXT* context,
159 void* reserved);
160
161TEST_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(&registration, ExceptionBarrierHandler);
170 EXPECT_EQ(GetTopRegistration(), &registration);
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(&registration);
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
189TEST_F(ExceptionBarrierTest, ExceptionBarrierHandler) {
190 EXPECT_TRUE(TestExceptionExceptionBarrierHandler());
191 EXPECT_TRUE(s_handled_);
192}
193
194bool 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
206TEST_F(ExceptionBarrierTest, HandlesAccessViolationException) {
207 TestExceptionBarrier();
208 EXPECT_TRUE(s_handled_);
209}
210
211void 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.
232bool 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
270void RecurseAndCrashOverBarrier(int depth, bool crash) {
[email protected]72354312010-05-01 02:10:06271 ExceptionBarrierCustomHandler barrier;
[email protected]dcdcfa02010-04-28 20:59:03272
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.
289static 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
346static 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.
354TEST_F(ExceptionBarrierTest, SEHChainIsSaneAfterException) {
355 EXPECT_TRUE(TestChaining());
356}
357
[email protected]e8982942010-04-30 19:26:08358} // namespace