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 |