Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(119)

Side by Side Diff: chrome_frame/test/exception_barrier_unittest.cc

Issue 1703015: Some cleanup regarding Siggi's comments on http://codereview.chromium.org/173... (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src/
Patch Set: '' Created 10 years, 7 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
« no previous file with comments | « chrome_frame/exception_barrier.cc ('k') | no next file » | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(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(&registration, ExceptionBarrierHandler);
173 EXPECT_EQ(GetTopRegistration(), &registration);
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(&registration);
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
OLDNEW
« no previous file with comments | « chrome_frame/exception_barrier.cc ('k') | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698