1 | // Licensed to the .NET Foundation under one or more agreements. |
2 | // The .NET Foundation licenses this file to you under the MIT license. |
3 | // See the LICENSE file in the project root for more information. |
4 | // |
5 | |
6 | // |
7 | |
8 | /* EXCEP.CPP: |
9 | * |
10 | */ |
11 | |
12 | #include "common.h" |
13 | |
14 | #include "frames.h" |
15 | #include "threads.h" |
16 | #include "excep.h" |
17 | #include "object.h" |
18 | #include "field.h" |
19 | #include "dbginterface.h" |
20 | #include "cgensys.h" |
21 | #include "comutilnative.h" |
22 | #include "siginfo.hpp" |
23 | #include "gcheaputilities.h" |
24 | #include "eedbginterfaceimpl.h" //so we can clearexception in RealCOMPlusThrow |
25 | #include "perfcounters.h" |
26 | #include "dllimportcallback.h" |
27 | #include "stackwalk.h" //for CrawlFrame, in SetIPFromSrcToDst |
28 | #include "shimload.h" |
29 | #include "eeconfig.h" |
30 | #include "virtualcallstub.h" |
31 | #include "typestring.h" |
32 | |
33 | #ifndef FEATURE_PAL |
34 | #include "dwreport.h" |
35 | #endif // !FEATURE_PAL |
36 | |
37 | #include "eventreporter.h" |
38 | |
39 | #ifdef FEATURE_COMINTEROP |
40 | #include<roerrorapi.h> |
41 | #endif |
42 | #ifdef WIN64EXCEPTIONS |
43 | #include "exceptionhandling.h" |
44 | #endif |
45 | |
46 | #include <errorrep.h> |
47 | #ifndef FEATURE_PAL |
48 | // Include definition of GenericModeBlock |
49 | #include <msodw.h> |
50 | #endif // FEATURE_PAL |
51 | |
52 | |
53 | // Support for extracting MethodDesc of a delegate. |
54 | #include "comdelegate.h" |
55 | |
56 | |
57 | #ifndef FEATURE_PAL |
58 | // Windows uses 64kB as the null-reference area |
59 | #define NULL_AREA_SIZE (64 * 1024) |
60 | #else // !FEATURE_PAL |
61 | #define NULL_AREA_SIZE GetOsPageSize() |
62 | #endif // !FEATURE_PAL |
63 | |
64 | #ifndef CROSSGEN_COMPILE |
65 | |
66 | BOOL IsIPInEE(void *ip); |
67 | |
68 | //---------------------------------------------------------------------------- |
69 | // |
70 | // IsExceptionFromManagedCode - determine if pExceptionRecord points to a managed exception |
71 | // |
72 | // Arguments: |
73 | // pExceptionRecord - pointer to exception record |
74 | // |
75 | // Return Value: |
76 | // TRUE or FALSE |
77 | // |
78 | //---------------------------------------------------------------------------- |
79 | BOOL IsExceptionFromManagedCode(const EXCEPTION_RECORD * pExceptionRecord) |
80 | { |
81 | CONTRACTL { |
82 | NOTHROW; |
83 | GC_NOTRIGGER; |
84 | SO_TOLERANT; |
85 | SUPPORTS_DAC; |
86 | PRECONDITION(CheckPointer(pExceptionRecord)); |
87 | } CONTRACTL_END; |
88 | |
89 | if (pExceptionRecord == NULL) |
90 | { |
91 | return FALSE; |
92 | } |
93 | |
94 | DACCOP_IGNORE(FieldAccess, "EXCEPTION_RECORD is a OS structure, and ExceptionAddress is actually a target address here." ); |
95 | UINT_PTR address = reinterpret_cast<UINT_PTR>(pExceptionRecord->ExceptionAddress); |
96 | |
97 | // An exception code of EXCEPTION_COMPLUS indicates a managed exception |
98 | // has occurred (most likely due to executing a "throw" instruction). |
99 | // |
100 | // Also, a hardware level exception may not have an exception code of |
101 | // EXCEPTION_COMPLUS. In this case, an exception address that resides in |
102 | // managed code indicates a managed exception has occurred. |
103 | return (IsComPlusException(pExceptionRecord) || |
104 | (ExecutionManager::IsManagedCode((PCODE)address))); |
105 | } |
106 | |
107 | |
108 | #ifndef DACCESS_COMPILE |
109 | |
110 | #define SZ_UNHANDLED_EXCEPTION W("Unhandled Exception:") |
111 | #define SZ_UNHANDLED_EXCEPTION_CHARLEN ((sizeof(SZ_UNHANDLED_EXCEPTION) / sizeof(WCHAR))) |
112 | |
113 | |
114 | typedef struct { |
115 | OBJECTREF pThrowable; |
116 | STRINGREF s1; |
117 | OBJECTREF pTmpThrowable; |
118 | } ProtectArgsStruct; |
119 | |
120 | PEXCEPTION_REGISTRATION_RECORD GetCurrentSEHRecord(); |
121 | BOOL IsUnmanagedToManagedSEHHandler(EXCEPTION_REGISTRATION_RECORD*); |
122 | |
123 | VOID DECLSPEC_NORETURN RealCOMPlusThrow(OBJECTREF throwable, BOOL rethrow |
124 | #ifdef FEATURE_CORRUPTING_EXCEPTIONS |
125 | , CorruptionSeverity severity = NotCorrupting |
126 | #endif // FEATURE_CORRUPTING_EXCEPTIONS |
127 | ); |
128 | |
129 | //------------------------------------------------------------------------------- |
130 | // Basically, this asks whether the exception is a managed exception thrown by |
131 | // this instance of the CLR. |
132 | // |
133 | // The way the result is used, however, is to decide whether this instance is the |
134 | // one to throw up the Watson box. |
135 | //------------------------------------------------------------------------------- |
136 | BOOL ShouldOurUEFDisplayUI(PEXCEPTION_POINTERS pExceptionInfo) |
137 | { |
138 | STATIC_CONTRACT_NOTHROW; |
139 | STATIC_CONTRACT_GC_NOTRIGGER; |
140 | STATIC_CONTRACT_FORBID_FAULT; |
141 | |
142 | // Test first for the canned SO EXCEPTION_POINTERS structure as it has a NULL context record and will break the code below. |
143 | extern EXCEPTION_POINTERS g_SOExceptionPointers; |
144 | if (pExceptionInfo == &g_SOExceptionPointers) |
145 | { |
146 | return TRUE; |
147 | } |
148 | return IsComPlusException(pExceptionInfo->ExceptionRecord) || ExecutionManager::IsManagedCode(GetIP(pExceptionInfo->ContextRecord)); |
149 | } |
150 | |
151 | BOOL NotifyAppDomainsOfUnhandledException( |
152 | PEXCEPTION_POINTERS pExceptionPointers, |
153 | OBJECTREF *pThrowableIn, |
154 | BOOL useLastThrownObject, |
155 | BOOL isTerminating); |
156 | |
157 | VOID SetManagedUnhandledExceptionBit( |
158 | BOOL useLastThrownObject); |
159 | |
160 | |
161 | void COMPlusThrowBoot(HRESULT hr) |
162 | { |
163 | STATIC_CONTRACT_THROWS; |
164 | |
165 | _ASSERTE(g_fEEShutDown >= ShutDown_Finalize2 || !"This should not be called unless we are in the last phase of shutdown!" ); |
166 | ULONG_PTR arg = hr; |
167 | RaiseException(BOOTUP_EXCEPTION_COMPLUS, EXCEPTION_NONCONTINUABLE, 1, &arg); |
168 | } |
169 | |
170 | |
171 | //------------------------------------------------------------------------------- |
172 | // This simply tests to see if the exception object is a subclass of |
173 | // the descriminating class specified in the exception clause. |
174 | //------------------------------------------------------------------------------- |
175 | BOOL ExceptionIsOfRightType(TypeHandle clauseType, TypeHandle thrownType) |
176 | { |
177 | CONTRACTL |
178 | { |
179 | NOTHROW; |
180 | GC_NOTRIGGER; |
181 | MODE_ANY; |
182 | FORBID_FAULT; |
183 | } |
184 | CONTRACTL_END; |
185 | |
186 | // if not resolved to, then it wasn't loaded and couldn't have been thrown |
187 | if (clauseType.IsNull()) |
188 | return FALSE; |
189 | |
190 | if (clauseType == thrownType) |
191 | return TRUE; |
192 | |
193 | // now look for parent match |
194 | TypeHandle superType = thrownType; |
195 | while (!superType.IsNull()) { |
196 | if (superType == clauseType) { |
197 | break; |
198 | } |
199 | superType = superType.GetParent(); |
200 | } |
201 | |
202 | return !superType.IsNull(); |
203 | } |
204 | |
205 | //=========================================================================== |
206 | // Gets the message text from an exception |
207 | //=========================================================================== |
208 | ULONG GetExceptionMessage(OBJECTREF throwable, |
209 | __inout_ecount(bufferLength) LPWSTR buffer, |
210 | ULONG bufferLength) |
211 | { |
212 | CONTRACTL |
213 | { |
214 | THROWS; |
215 | GC_TRIGGERS; |
216 | MODE_COOPERATIVE; |
217 | INJECT_FAULT(ThrowOutOfMemory()); |
218 | } |
219 | CONTRACTL_END; |
220 | |
221 | // Prefast buffer sanity check. Don't call the API with a zero length buffer. |
222 | if (bufferLength == 0) |
223 | { |
224 | _ASSERTE(bufferLength > 0); |
225 | return 0; |
226 | } |
227 | |
228 | StackSString result; |
229 | GetExceptionMessage(throwable, result); |
230 | |
231 | ULONG length = result.GetCount(); |
232 | LPCWSTR chars = result.GetUnicode(); |
233 | |
234 | if (length < bufferLength) |
235 | { |
236 | wcsncpy_s(buffer, bufferLength, chars, length); |
237 | } |
238 | else |
239 | { |
240 | wcsncpy_s(buffer, bufferLength, chars, bufferLength-1); |
241 | } |
242 | |
243 | return length; |
244 | } |
245 | |
246 | //----------------------------------------------------------------------------- |
247 | // Given an object, get the "message" from it. If the object is an Exception |
248 | // call Exception.InternalToString, otherwise, call Object.ToString |
249 | //----------------------------------------------------------------------------- |
250 | void GetExceptionMessage(OBJECTREF throwable, SString &result) |
251 | { |
252 | CONTRACTL |
253 | { |
254 | THROWS; |
255 | GC_TRIGGERS; |
256 | MODE_COOPERATIVE; |
257 | INJECT_FAULT(ThrowOutOfMemory()); |
258 | } |
259 | CONTRACTL_END; |
260 | |
261 | STRINGREF pString = GetExceptionMessage(throwable); |
262 | |
263 | // If call returned NULL (not empty), oh well, no message. |
264 | if (pString != NULL) |
265 | pString->GetSString(result); |
266 | } // void GetExceptionMessage() |
267 | |
268 | #if FEATURE_COMINTEROP |
269 | // This method returns IRestrictedErrorInfo associated with the ErrorObject. |
270 | // It checks whether the given managed exception object has __HasRestrictedLanguageErrorObject set |
271 | // in which case it returns the IRestrictedErrorInfo associated with the __RestrictedErrorObject. |
272 | IRestrictedErrorInfo* GetRestrictedErrorInfoFromErrorObject(OBJECTREF throwable) |
273 | { |
274 | CONTRACTL |
275 | { |
276 | THROWS; |
277 | GC_TRIGGERS; |
278 | MODE_COOPERATIVE; |
279 | INJECT_FAULT(ThrowOutOfMemory()); |
280 | } |
281 | CONTRACTL_END; |
282 | |
283 | IRestrictedErrorInfo* pRestrictedErrorInfo = NULL; |
284 | |
285 | // If there is no object, there is no restricted error. |
286 | if (throwable == NULL) |
287 | return NULL; |
288 | |
289 | _ASSERTE(IsException(throwable->GetMethodTable())); // what is the pathway here? |
290 | if (!IsException(throwable->GetMethodTable())) |
291 | { |
292 | return NULL; |
293 | } |
294 | |
295 | struct _gc { |
296 | OBJECTREF Throwable; |
297 | OBJECTREF RestrictedErrorInfoObjRef; |
298 | } gc; |
299 | |
300 | ZeroMemory(&gc, sizeof(gc)); |
301 | GCPROTECT_BEGIN(gc); |
302 | |
303 | gc.Throwable = throwable; |
304 | |
305 | // Get the MethodDesc on which we'll call. |
306 | MethodDescCallSite getRestrictedLanguageErrorObject(METHOD__EXCEPTION__TRY_GET_RESTRICTED_LANGUAGE_ERROR_OBJECT, &gc.Throwable); |
307 | |
308 | // Make the call. |
309 | ARG_SLOT Args[] = |
310 | { |
311 | ObjToArgSlot(gc.Throwable), |
312 | PtrToArgSlot(&gc.RestrictedErrorInfoObjRef) |
313 | }; |
314 | |
315 | BOOL bHasLanguageRestrictedErrorObject = (BOOL)getRestrictedLanguageErrorObject.Call_RetBool(Args); |
316 | |
317 | if(bHasLanguageRestrictedErrorObject) |
318 | { |
319 | // The __RestrictedErrorObject represents IRestrictedErrorInfo RCW of a non-CLR platform. Lets get the corresponding IRestrictedErrorInfo for it. |
320 | pRestrictedErrorInfo = (IRestrictedErrorInfo *)GetComIPFromObjectRef(&gc.RestrictedErrorInfoObjRef, IID_IRestrictedErrorInfo); |
321 | } |
322 | |
323 | GCPROTECT_END(); |
324 | |
325 | return pRestrictedErrorInfo; |
326 | } |
327 | #endif |
328 | |
329 | STRINGREF GetExceptionMessage(OBJECTREF throwable) |
330 | { |
331 | CONTRACTL |
332 | { |
333 | THROWS; |
334 | GC_TRIGGERS; |
335 | MODE_COOPERATIVE; |
336 | INJECT_FAULT(ThrowOutOfMemory()); |
337 | } |
338 | CONTRACTL_END; |
339 | |
340 | // If there is no object, there is no message. |
341 | if (throwable == NULL) |
342 | return NULL; |
343 | |
344 | // Assume we're calling Exception.InternalToString() ... |
345 | BinderMethodID sigID = METHOD__EXCEPTION__INTERNAL_TO_STRING; |
346 | |
347 | // ... but if it isn't an exception, call Object.ToString(). |
348 | _ASSERTE(IsException(throwable->GetMethodTable())); // what is the pathway here? |
349 | if (!IsException(throwable->GetMethodTable())) |
350 | { |
351 | sigID = METHOD__OBJECT__TO_STRING; |
352 | } |
353 | |
354 | // Return value. |
355 | STRINGREF pString = NULL; |
356 | |
357 | GCPROTECT_BEGIN(throwable); |
358 | |
359 | // Get the MethodDesc on which we'll call. |
360 | MethodDescCallSite toString(sigID, &throwable); |
361 | |
362 | // Make the call. |
363 | ARG_SLOT arg[1] = {ObjToArgSlot(throwable)}; |
364 | pString = toString.Call_RetSTRINGREF(arg); |
365 | |
366 | GCPROTECT_END(); |
367 | |
368 | return pString; |
369 | } |
370 | |
371 | HRESULT GetExceptionHResult(OBJECTREF throwable) |
372 | { |
373 | CONTRACTL |
374 | { |
375 | NOTHROW; |
376 | GC_NOTRIGGER; |
377 | MODE_COOPERATIVE; |
378 | SO_TOLERANT; |
379 | } |
380 | CONTRACTL_END; |
381 | |
382 | HRESULT hr = E_FAIL; |
383 | if (throwable == NULL) |
384 | return hr; |
385 | |
386 | // Since any object can be thrown in managed code, not only instances of System.Exception subclasses |
387 | // we need to check to see if we are dealing with an exception before attempting to retrieve |
388 | // the HRESULT field. If we are not dealing with an exception, then we will simply return E_FAIL. |
389 | _ASSERTE(IsException(throwable->GetMethodTable())); // what is the pathway here? |
390 | if (IsException(throwable->GetMethodTable())) |
391 | { |
392 | hr = ((EXCEPTIONREF)throwable)->GetHResult(); |
393 | } |
394 | |
395 | return hr; |
396 | } // HRESULT GetExceptionHResult() |
397 | |
398 | DWORD GetExceptionXCode(OBJECTREF throwable) |
399 | { |
400 | CONTRACTL |
401 | { |
402 | NOTHROW; |
403 | GC_NOTRIGGER; |
404 | MODE_COOPERATIVE; |
405 | SO_TOLERANT; |
406 | } |
407 | CONTRACTL_END; |
408 | |
409 | HRESULT hr = E_FAIL; |
410 | if (throwable == NULL) |
411 | return hr; |
412 | |
413 | // Since any object can be thrown in managed code, not only instances of System.Exception subclasses |
414 | // we need to check to see if we are dealing with an exception before attempting to retrieve |
415 | // the HRESULT field. If we are not dealing with an exception, then we will simply return E_FAIL. |
416 | _ASSERTE(IsException(throwable->GetMethodTable())); // what is the pathway here? |
417 | if (IsException(throwable->GetMethodTable())) |
418 | { |
419 | hr = ((EXCEPTIONREF)throwable)->GetXCode(); |
420 | } |
421 | |
422 | return hr; |
423 | } // DWORD GetExceptionXCode() |
424 | |
425 | //------------------------------------------------------------------------------ |
426 | // This function will extract some information from an Access Violation SEH |
427 | // exception, and store it in the System.AccessViolationException object. |
428 | // - the faulting instruction's IP. |
429 | // - the target address of the faulting instruction. |
430 | // - a code indicating attempted read vs write |
431 | //------------------------------------------------------------------------------ |
432 | void SetExceptionAVParameters( // No return. |
433 | OBJECTREF throwable, // The object into which to set the values. |
434 | EXCEPTION_RECORD *pExceptionRecord) // The SEH exception information. |
435 | { |
436 | CONTRACTL |
437 | { |
438 | THROWS; |
439 | GC_TRIGGERS; |
440 | MODE_COOPERATIVE; |
441 | PRECONDITION(throwable != NULL); |
442 | } |
443 | CONTRACTL_END; |
444 | |
445 | GCPROTECT_BEGIN(throwable) |
446 | { |
447 | // This should only be called for AccessViolationException |
448 | _ASSERTE(MscorlibBinder::GetException(kAccessViolationException) == throwable->GetMethodTable()); |
449 | |
450 | FieldDesc *pFD_ip = MscorlibBinder::GetField(FIELD__ACCESS_VIOLATION_EXCEPTION__IP); |
451 | FieldDesc *pFD_target = MscorlibBinder::GetField(FIELD__ACCESS_VIOLATION_EXCEPTION__TARGET); |
452 | FieldDesc *pFD_access = MscorlibBinder::GetField(FIELD__ACCESS_VIOLATION_EXCEPTION__ACCESSTYPE); |
453 | |
454 | _ASSERTE(pFD_ip->GetFieldType() == ELEMENT_TYPE_I); |
455 | _ASSERTE(pFD_target->GetFieldType() == ELEMENT_TYPE_I); |
456 | _ASSERTE(pFD_access->GetFieldType() == ELEMENT_TYPE_I4); |
457 | |
458 | void *ip = pExceptionRecord->ExceptionAddress; |
459 | void *target = (void*)(pExceptionRecord->ExceptionInformation[1]); |
460 | DWORD access = (DWORD)(pExceptionRecord->ExceptionInformation[0]); |
461 | |
462 | pFD_ip->SetValuePtr(throwable, ip); |
463 | pFD_target->SetValuePtr(throwable, target); |
464 | pFD_access->SetValue32(throwable, access); |
465 | |
466 | } |
467 | GCPROTECT_END(); |
468 | |
469 | } // void SetExceptionAVParameters() |
470 | |
471 | //------------------------------------------------------------------------------ |
472 | // This will call InternalPreserveStackTrace (if the throwable derives from |
473 | // System.Exception), to copy the stack trace to the _remoteStackTraceString. |
474 | // Doing so allows the stack trace of an exception caught by the runtime, and |
475 | // rethrown with COMPlusThrow(OBJECTREF thowable), to be preserved. Otherwise |
476 | // the exception handling code may clear the stack trace. (Generally, we see |
477 | // the stack trace preserved on win32 and cleared on win64.) |
478 | //------------------------------------------------------------------------------ |
479 | void ExceptionPreserveStackTrace( // No return. |
480 | OBJECTREF throwable) // Object about to be thrown. |
481 | { |
482 | CONTRACTL |
483 | { |
484 | THROWS; |
485 | GC_TRIGGERS; |
486 | MODE_COOPERATIVE; |
487 | INJECT_FAULT(ThrowOutOfMemory()); |
488 | } |
489 | CONTRACTL_END; |
490 | |
491 | // If there is no object, there is no stack trace to save. |
492 | if (throwable == NULL) |
493 | return; |
494 | |
495 | GCPROTECT_BEGIN(throwable); |
496 | |
497 | // Make sure it is derived from System.Exception, that it is not one of the |
498 | // preallocated exception objects, and that it has a stack trace to save. |
499 | if (IsException(throwable->GetMethodTable()) && |
500 | !CLRException::IsPreallocatedExceptionObject(throwable)) |
501 | { |
502 | LOG((LF_EH, LL_INFO1000, "ExceptionPreserveStackTrace called\n" )); |
503 | |
504 | // We're calling Exception.InternalPreserveStackTrace() ... |
505 | BinderMethodID sigID = METHOD__EXCEPTION__INTERNAL_PRESERVE_STACK_TRACE; |
506 | |
507 | |
508 | // Get the MethodDesc on which we'll call. |
509 | MethodDescCallSite preserveStackTrace(sigID, &throwable); |
510 | |
511 | // Make the call. |
512 | ARG_SLOT arg[1] = {ObjToArgSlot(throwable)}; |
513 | preserveStackTrace.Call(arg); |
514 | } |
515 | |
516 | GCPROTECT_END(); |
517 | |
518 | } // void ExceptionPreserveStackTrace() |
519 | |
520 | |
521 | // We have to cache the MethodTable and FieldDesc for wrapped non-compliant exceptions the first |
522 | // time we wrap, because we cannot tolerate a GC when it comes time to detect and unwrap one. |
523 | |
524 | static MethodTable *pMT_RuntimeWrappedException; |
525 | static FieldDesc *pFD_WrappedException; |
526 | |
527 | // Non-compliant exceptions are immediately wrapped in a RuntimeWrappedException instance. The entire |
528 | // exception system can now ignore the possibility of these cases except: |
529 | // |
530 | // 1) IL_Throw, which must wrap via this API |
531 | // 2) Calls to Filters & Catch handlers, which must unwrap based on whether the assembly is on the legacy |
532 | // plan. |
533 | // |
534 | void WrapNonCompliantException(OBJECTREF *ppThrowable) |
535 | { |
536 | CONTRACTL |
537 | { |
538 | THROWS; |
539 | GC_TRIGGERS; |
540 | MODE_COOPERATIVE; |
541 | PRECONDITION(IsProtectedByGCFrame(ppThrowable)); |
542 | } |
543 | CONTRACTL_END; |
544 | |
545 | _ASSERTE(!IsException((*ppThrowable)->GetMethodTable())); |
546 | |
547 | EX_TRY |
548 | { |
549 | // idempotent operations, so the race condition is okay. |
550 | if (pMT_RuntimeWrappedException == NULL) |
551 | pMT_RuntimeWrappedException = MscorlibBinder::GetException(kRuntimeWrappedException); |
552 | |
553 | if (pFD_WrappedException == NULL) |
554 | pFD_WrappedException = MscorlibBinder::GetField(FIELD__RUNTIME_WRAPPED_EXCEPTION__WRAPPED_EXCEPTION); |
555 | |
556 | OBJECTREF orWrapper = AllocateObject(MscorlibBinder::GetException(kRuntimeWrappedException)); |
557 | |
558 | GCPROTECT_BEGIN(orWrapper); |
559 | |
560 | MethodDescCallSite ctor(METHOD__RUNTIME_WRAPPED_EXCEPTION__OBJ_CTOR, &orWrapper); |
561 | |
562 | ARG_SLOT args[] = |
563 | { |
564 | ObjToArgSlot(orWrapper), |
565 | ObjToArgSlot(*ppThrowable) |
566 | }; |
567 | |
568 | ctor.Call(args); |
569 | |
570 | *ppThrowable = orWrapper; |
571 | |
572 | GCPROTECT_END(); |
573 | } |
574 | EX_CATCH |
575 | { |
576 | // If we took an exception while binding, or running the constructor of the RuntimeWrappedException |
577 | // instance, we know that this new exception is CLS compliant. In fact, it's likely to be |
578 | // OutOfMemoryException, StackOverflowException or ThreadAbortException. |
579 | OBJECTREF orReplacement = GET_THROWABLE(); |
580 | |
581 | _ASSERTE(IsException(orReplacement->GetMethodTable())); |
582 | |
583 | *ppThrowable = orReplacement; |
584 | |
585 | } EX_END_CATCH(SwallowAllExceptions); |
586 | } |
587 | |
588 | // Before presenting an exception object to a handler (filter or catch, not finally or fault), it |
589 | // may be necessary to turn it back into a non-compliant exception. This is conditioned on an |
590 | // assembly level setting. |
591 | OBJECTREF PossiblyUnwrapThrowable(OBJECTREF throwable, Assembly *pAssembly) |
592 | { |
593 | // Check if we are required to compute the RuntimeWrapExceptions status. |
594 | BOOL fIsRuntimeWrappedException = ((throwable != NULL) && (throwable->GetMethodTable() == pMT_RuntimeWrappedException)); |
595 | BOOL fRequiresComputingRuntimeWrapExceptionsStatus = (fIsRuntimeWrappedException && |
596 | (!(pAssembly->GetManifestModule()->IsRuntimeWrapExceptionsStatusComputed()))); |
597 | |
598 | CONTRACTL |
599 | { |
600 | THROWS; |
601 | // If we are required to compute the status of RuntimeWrapExceptions, then the operation could trigger a GC. |
602 | // Thus, conditionally setup the contract. |
603 | if (fRequiresComputingRuntimeWrapExceptionsStatus) GC_TRIGGERS; else GC_NOTRIGGER; |
604 | MODE_COOPERATIVE; |
605 | PRECONDITION(CheckPointer(pAssembly)); |
606 | } |
607 | CONTRACTL_END; |
608 | |
609 | if (fIsRuntimeWrappedException && (!pAssembly->GetManifestModule()->IsRuntimeWrapExceptions())) |
610 | { |
611 | // We already created the instance, fetched the field. We know it is |
612 | // not marshal by ref, or any of the other cases that might trigger GC. |
613 | ENABLE_FORBID_GC_LOADER_USE_IN_THIS_SCOPE(); |
614 | |
615 | throwable = pFD_WrappedException->GetRefValue(throwable); |
616 | } |
617 | |
618 | return throwable; |
619 | } |
620 | |
621 | |
622 | // This is used by a holder in CreateTypeInitializationExceptionObject to |
623 | // reset the state as appropriate. |
624 | void ResetTypeInitializationExceptionState(BOOL isAlreadyCreating) |
625 | { |
626 | LIMITED_METHOD_CONTRACT; |
627 | if (!isAlreadyCreating) |
628 | GetThread()->ResetIsCreatingTypeInitException(); |
629 | } |
630 | |
631 | void CreateTypeInitializationExceptionObject(LPCWSTR pTypeThatFailed, |
632 | OBJECTREF *pInnerException, |
633 | OBJECTREF *pInitException, |
634 | OBJECTREF *pThrowable) |
635 | { |
636 | CONTRACTL { |
637 | NOTHROW; |
638 | GC_TRIGGERS; |
639 | MODE_COOPERATIVE; |
640 | PRECONDITION(CheckPointer(pInnerException, NULL_OK)); |
641 | PRECONDITION(CheckPointer(pInitException)); |
642 | PRECONDITION(CheckPointer(pThrowable)); |
643 | PRECONDITION(IsProtectedByGCFrame(pInnerException)); |
644 | PRECONDITION(IsProtectedByGCFrame(pInitException)); |
645 | PRECONDITION(IsProtectedByGCFrame(pThrowable)); |
646 | PRECONDITION(CheckPointer(GetThread())); |
647 | } CONTRACTL_END; |
648 | |
649 | Thread *pThread = GetThread(); |
650 | *pThrowable = NULL; |
651 | |
652 | // This will make sure to put the thread back to its original state if something |
653 | // throws out of this function (like an OOM exception or something) |
654 | Holder< BOOL, DoNothing< BOOL >, ResetTypeInitializationExceptionState, FALSE, NoNull< BOOL > > |
655 | isAlreadyCreating(pThread->IsCreatingTypeInitException()); |
656 | |
657 | EX_TRY { |
658 | // This will contain the type of exception we want to create. Read comment below |
659 | // on why we'd want to create an exception other than TypeInitException |
660 | MethodTable *pMT; |
661 | BinderMethodID methodID; |
662 | |
663 | // If we are already in the midst of creating a TypeInitializationException object, |
664 | // and we get here, it means there was an exception thrown while initializing the |
665 | // TypeInitializationException type itself, or one of the types used by its class |
666 | // constructor. In this case, we're going to back down and use a SystemException |
667 | // object in its place. It is *KNOWN* that both these exception types have identical |
668 | // .ctor sigs "void instance (string, exception)" so both can be used interchangeably |
669 | // in the code that follows. |
670 | if (!isAlreadyCreating.GetValue()) { |
671 | pThread->SetIsCreatingTypeInitException(); |
672 | pMT = MscorlibBinder::GetException(kTypeInitializationException); |
673 | methodID = METHOD__TYPE_INIT_EXCEPTION__STR_EX_CTOR; |
674 | } |
675 | else { |
676 | // If we ever hit one of these asserts, then it is bad |
677 | // because we do not know what exception to return then. |
678 | _ASSERTE(pInnerException != NULL); |
679 | _ASSERTE(*pInnerException != NULL); |
680 | *pThrowable = *pInnerException; |
681 | *pInitException = *pInnerException; |
682 | goto ErrExit; |
683 | } |
684 | |
685 | // Allocate the exception object |
686 | *pThrowable = AllocateObject(pMT); |
687 | |
688 | MethodDescCallSite ctor(methodID, pThrowable); |
689 | |
690 | // Since the inner exception object in the .ctor is of type Exception, make sure |
691 | // that the object we're passed in derives from Exception. If not, pass NULL. |
692 | BOOL isException = FALSE; |
693 | if (pInnerException != NULL) |
694 | isException = IsException((*pInnerException)->GetMethodTable()); |
695 | |
696 | _ASSERTE(isException); // What pathway can give us non-compliant exceptions? |
697 | |
698 | STRINGREF sType = StringObject::NewString(pTypeThatFailed); |
699 | |
700 | // If the inner object derives from exception, set it as the third argument. |
701 | ARG_SLOT args[] = { ObjToArgSlot(*pThrowable), |
702 | ObjToArgSlot(sType), |
703 | ObjToArgSlot(isException ? *pInnerException : NULL) }; |
704 | |
705 | // Call the .ctor |
706 | ctor.Call(args); |
707 | |
708 | // On success, set the init exception. |
709 | *pInitException = *pThrowable; |
710 | } |
711 | EX_CATCH { |
712 | // If calling the constructor fails, then we'll call ourselves again, and this time |
713 | // through we will try and create an EEException object. If that fails, then the |
714 | // else block of this will be executed. |
715 | if (!isAlreadyCreating.GetValue()) { |
716 | CreateTypeInitializationExceptionObject(pTypeThatFailed, pInnerException, pInitException, pThrowable); |
717 | } |
718 | |
719 | // If we were already in the middle of creating a type init |
720 | // exception when we were called, we would have tried to create an EEException instead |
721 | // of a TypeInitException. |
722 | else { |
723 | // If we're recursing, then we should be calling ourselves from DoRunClassInitThrowing, |
724 | // in which case we're guaranteed that we're passing in all three arguments. |
725 | *pInitException = pInnerException ? *pInnerException : NULL; |
726 | *pThrowable = GET_THROWABLE(); |
727 | } |
728 | } EX_END_CATCH(SwallowAllExceptions); |
729 | |
730 | CONSISTENCY_CHECK(*pInitException != NULL || !pInnerException); |
731 | |
732 | ErrExit: |
733 | ; |
734 | } |
735 | |
736 | // ========================================================================== |
737 | // ComputeEnclosingHandlerNestingLevel |
738 | // |
739 | // This is code factored out of COMPlusThrowCallback to figure out |
740 | // what the number of nested exception handlers is. |
741 | // ========================================================================== |
742 | DWORD ComputeEnclosingHandlerNestingLevel(IJitManager *pIJM, |
743 | const METHODTOKEN& mdTok, |
744 | SIZE_T offsNat) |
745 | { |
746 | CONTRACTL |
747 | { |
748 | NOTHROW; |
749 | GC_NOTRIGGER; |
750 | MODE_ANY; |
751 | FORBID_FAULT; |
752 | } |
753 | CONTRACTL_END; |
754 | |
755 | // Determine the nesting level of EHClause. Just walk the table |
756 | // again, and find out how many handlers enclose it |
757 | DWORD nestingLevel = 0; |
758 | EH_CLAUSE_ENUMERATOR pEnumState; |
759 | unsigned EHCount = pIJM->InitializeEHEnumeration(mdTok, &pEnumState); |
760 | |
761 | for (unsigned j=0; j<EHCount; j++) |
762 | { |
763 | EE_ILEXCEPTION_CLAUSE EHClause; |
764 | |
765 | pIJM->GetNextEHClause(&pEnumState,&EHClause); |
766 | _ASSERTE(EHClause.HandlerEndPC != (DWORD) -1); // <TODO> remove, only protects against a deprecated convention</TODO> |
767 | |
768 | if ((offsNat > EHClause.HandlerStartPC) && |
769 | (offsNat < EHClause.HandlerEndPC)) |
770 | { |
771 | nestingLevel++; |
772 | } |
773 | } |
774 | |
775 | return nestingLevel; |
776 | } |
777 | |
778 | // ******************************* EHRangeTreeNode ************************** // |
779 | EHRangeTreeNode::EHRangeTreeNode(void) |
780 | { |
781 | WRAPPER_NO_CONTRACT; |
782 | CommonCtor(0, false); |
783 | } |
784 | |
785 | EHRangeTreeNode::EHRangeTreeNode(DWORD offset, bool fIsRange /* = false */) |
786 | { |
787 | WRAPPER_NO_CONTRACT; |
788 | CommonCtor(offset, fIsRange); |
789 | } |
790 | |
791 | void EHRangeTreeNode::CommonCtor(DWORD offset, bool fIsRange) |
792 | { |
793 | LIMITED_METHOD_CONTRACT; |
794 | |
795 | m_pTree = NULL; |
796 | m_clause = NULL; |
797 | |
798 | m_pContainedBy = NULL; |
799 | |
800 | m_offset = offset; |
801 | m_fIsRange = fIsRange; |
802 | m_fIsRoot = false; // must set this flag explicitly |
803 | } |
804 | |
805 | inline bool EHRangeTreeNode::IsRange() |
806 | { |
807 | // Please see the header file for an explanation of this assertion. |
808 | _ASSERTE(m_fIsRoot || m_clause != NULL || !m_fIsRange); |
809 | return m_fIsRange; |
810 | } |
811 | |
812 | void EHRangeTreeNode::MarkAsRange() |
813 | { |
814 | m_offset = 0; |
815 | m_fIsRange = true; |
816 | m_fIsRoot = false; |
817 | } |
818 | |
819 | inline bool EHRangeTreeNode::IsRoot() |
820 | { |
821 | // Please see the header file for an explanation of this assertion. |
822 | _ASSERTE(m_fIsRoot || m_clause != NULL || !m_fIsRange); |
823 | return m_fIsRoot; |
824 | } |
825 | |
826 | void EHRangeTreeNode::MarkAsRoot(DWORD offset) |
827 | { |
828 | m_offset = offset; |
829 | m_fIsRange = true; |
830 | m_fIsRoot = true; |
831 | } |
832 | |
833 | inline DWORD EHRangeTreeNode::GetOffset() |
834 | { |
835 | _ASSERTE(m_clause == NULL); |
836 | _ASSERTE(IsRoot() || !IsRange()); |
837 | return m_offset; |
838 | } |
839 | |
840 | inline DWORD EHRangeTreeNode::GetTryStart() |
841 | { |
842 | _ASSERTE(IsRange()); |
843 | _ASSERTE(!IsRoot()); |
844 | if (IsRoot()) |
845 | { |
846 | return 0; |
847 | } |
848 | else |
849 | { |
850 | return m_clause->TryStartPC; |
851 | } |
852 | } |
853 | |
854 | inline DWORD EHRangeTreeNode::GetTryEnd() |
855 | { |
856 | _ASSERTE(IsRange()); |
857 | _ASSERTE(!IsRoot()); |
858 | if (IsRoot()) |
859 | { |
860 | return GetOffset(); |
861 | } |
862 | else |
863 | { |
864 | return m_clause->TryEndPC; |
865 | } |
866 | } |
867 | |
868 | inline DWORD EHRangeTreeNode::GetHandlerStart() |
869 | { |
870 | _ASSERTE(IsRange()); |
871 | _ASSERTE(!IsRoot()); |
872 | if (IsRoot()) |
873 | { |
874 | return 0; |
875 | } |
876 | else |
877 | { |
878 | return m_clause->HandlerStartPC; |
879 | } |
880 | } |
881 | |
882 | inline DWORD EHRangeTreeNode::GetHandlerEnd() |
883 | { |
884 | _ASSERTE(IsRange()); |
885 | _ASSERTE(!IsRoot()); |
886 | if (IsRoot()) |
887 | { |
888 | return GetOffset(); |
889 | } |
890 | else |
891 | { |
892 | return m_clause->HandlerEndPC; |
893 | } |
894 | } |
895 | |
896 | inline DWORD EHRangeTreeNode::GetFilterStart() |
897 | { |
898 | _ASSERTE(IsRange()); |
899 | _ASSERTE(!IsRoot()); |
900 | if (IsRoot()) |
901 | { |
902 | return 0; |
903 | } |
904 | else |
905 | { |
906 | return m_clause->FilterOffset; |
907 | } |
908 | } |
909 | |
910 | // Get the end offset of the filter clause. This offset is exclusive. |
911 | inline DWORD EHRangeTreeNode::GetFilterEnd() |
912 | { |
913 | _ASSERTE(IsRange()); |
914 | _ASSERTE(!IsRoot()); |
915 | if (IsRoot()) |
916 | { |
917 | // We should never get here if the "this" node is the root. |
918 | // By definition, the root contains everything. No checking is necessary. |
919 | return 0; |
920 | } |
921 | else |
922 | { |
923 | return m_FilterEndPC; |
924 | } |
925 | } |
926 | |
927 | bool EHRangeTreeNode::Contains(DWORD offset) |
928 | { |
929 | WRAPPER_NO_CONTRACT; |
930 | |
931 | EHRangeTreeNode node(offset); |
932 | return Contains(&node); |
933 | } |
934 | |
935 | bool EHRangeTreeNode::TryContains(DWORD offset) |
936 | { |
937 | WRAPPER_NO_CONTRACT; |
938 | |
939 | EHRangeTreeNode node(offset); |
940 | return TryContains(&node); |
941 | } |
942 | |
943 | bool EHRangeTreeNode::HandlerContains(DWORD offset) |
944 | { |
945 | WRAPPER_NO_CONTRACT; |
946 | |
947 | EHRangeTreeNode node(offset); |
948 | return HandlerContains(&node); |
949 | } |
950 | |
951 | bool EHRangeTreeNode::FilterContains(DWORD offset) |
952 | { |
953 | WRAPPER_NO_CONTRACT; |
954 | |
955 | EHRangeTreeNode node(offset); |
956 | return FilterContains(&node); |
957 | } |
958 | |
959 | bool EHRangeTreeNode::Contains(EHRangeTreeNode* pNode) |
960 | { |
961 | LIMITED_METHOD_CONTRACT; |
962 | |
963 | // If we are checking a range of address, then we should check the end address inclusively. |
964 | if (pNode->IsRoot()) |
965 | { |
966 | // No node contains the root node. |
967 | return false; |
968 | } |
969 | else if (this->IsRoot()) |
970 | { |
971 | return (pNode->IsRange() ? |
972 | (pNode->GetTryEnd() <= this->GetOffset()) && (pNode->GetHandlerEnd() <= this->GetOffset()) |
973 | : (pNode->GetOffset() < this->GetOffset()) ); |
974 | } |
975 | else |
976 | { |
977 | return (this->TryContains(pNode) || this->HandlerContains(pNode) || this->FilterContains(pNode)); |
978 | } |
979 | } |
980 | |
981 | bool EHRangeTreeNode::TryContains(EHRangeTreeNode* pNode) |
982 | { |
983 | LIMITED_METHOD_CONTRACT; |
984 | |
985 | _ASSERTE(this->IsRange()); |
986 | |
987 | if (pNode->IsRoot()) |
988 | { |
989 | // No node contains the root node. |
990 | return false; |
991 | } |
992 | else if (this->IsRoot()) |
993 | { |
994 | // We will only get here from GetTcf() to determine if an address is in a try clause. |
995 | // In this case we want to return false. |
996 | return false; |
997 | } |
998 | else |
999 | { |
1000 | DWORD tryStart = this->GetTryStart(); |
1001 | DWORD tryEnd = this->GetTryEnd(); |
1002 | |
1003 | // If we are checking a range of address, then we should check the end address inclusively. |
1004 | if (pNode->IsRange()) |
1005 | { |
1006 | DWORD start = pNode->GetTryStart(); |
1007 | DWORD end = pNode->GetTryEnd(); |
1008 | |
1009 | if (start == tryStart && end == tryEnd) |
1010 | { |
1011 | return false; |
1012 | } |
1013 | else if (start == end) |
1014 | { |
1015 | // This is effectively a single offset. |
1016 | if ((tryStart <= start) && (end < tryEnd)) |
1017 | { |
1018 | return true; |
1019 | } |
1020 | } |
1021 | else if ((tryStart <= start) && (end <= tryEnd)) |
1022 | { |
1023 | return true; |
1024 | } |
1025 | } |
1026 | else |
1027 | { |
1028 | DWORD offset = pNode->GetOffset(); |
1029 | if ((tryStart <= offset) && (offset < tryEnd)) |
1030 | { |
1031 | return true; |
1032 | } |
1033 | } |
1034 | } |
1035 | |
1036 | #ifdef WIN64EXCEPTIONS |
1037 | // If we are boot-strapping the tree, don't recurse down because the result could be unreliable. Note that |
1038 | // even if we don't recurse, given a particular node, we can still always find its most specific container with |
1039 | // the logic above, i.e. it's always safe to do one depth level of checking. |
1040 | // |
1041 | // To build the tree, all we need to know is the most specific container of a particular node. This can be |
1042 | // done by just comparing the offsets of the try regions. However, funclets create a problem because even if |
1043 | // a funclet is conceptually contained in a try region, we cannot determine this fact just by comparing the offsets. |
1044 | // This is when we need to recurse the tree. Here is a classic example: |
1045 | // try |
1046 | // { |
1047 | // try |
1048 | // { |
1049 | // } |
1050 | // catch |
1051 | // { |
1052 | // // If the offset is here, then we need to recurse. |
1053 | // } |
1054 | // } |
1055 | // catch |
1056 | // { |
1057 | // } |
1058 | if (!m_pTree->m_fInitializing) |
1059 | { |
1060 | // Iterate all the contained clauses, and for the ones which are contained in the try region, |
1061 | // ask if the requested range is contained by it. |
1062 | USHORT i = 0; |
1063 | USHORT numNodes = m_containees.Count(); |
1064 | EHRangeTreeNode** ppNodes = NULL; |
1065 | for (i = 0, ppNodes = m_containees.Table(); i < numNodes; i++, ppNodes++) |
1066 | { |
1067 | // This variable is purely used for readability. |
1068 | EHRangeTreeNode* pNodeCur = *ppNodes; |
1069 | |
1070 | // it's possible for nested try blocks to have the same beginning and end offsets |
1071 | if ( ( this->GetTryStart() <= pNodeCur->GetTryStart() ) && |
1072 | ( pNodeCur->GetTryEnd() <= this->GetTryEnd() ) ) |
1073 | { |
1074 | if (pNodeCur->Contains(pNode)) |
1075 | { |
1076 | return true; |
1077 | } |
1078 | } |
1079 | } |
1080 | } |
1081 | #endif // WIN64EXCEPTIONS |
1082 | |
1083 | return false; |
1084 | } |
1085 | |
1086 | bool EHRangeTreeNode::HandlerContains(EHRangeTreeNode* pNode) |
1087 | { |
1088 | LIMITED_METHOD_CONTRACT; |
1089 | |
1090 | _ASSERTE(this->IsRange()); |
1091 | |
1092 | if (pNode->IsRoot()) |
1093 | { |
1094 | // No node contains the root node. |
1095 | return false; |
1096 | } |
1097 | else if (this->IsRoot()) |
1098 | { |
1099 | // We will only get here from GetTcf() to determine if an address is in a try clause. |
1100 | // In this case we want to return false. |
1101 | return false; |
1102 | } |
1103 | else |
1104 | { |
1105 | DWORD handlerStart = this->GetHandlerStart(); |
1106 | DWORD handlerEnd = this->GetHandlerEnd(); |
1107 | |
1108 | // If we are checking a range of address, then we should check the end address inclusively. |
1109 | if (pNode->IsRange()) |
1110 | { |
1111 | DWORD start = pNode->GetTryStart(); |
1112 | DWORD end = pNode->GetTryEnd(); |
1113 | |
1114 | if (start == handlerStart && end == handlerEnd) |
1115 | { |
1116 | return false; |
1117 | } |
1118 | else if ((handlerStart <= start) && (end <= handlerEnd)) |
1119 | { |
1120 | return true; |
1121 | } |
1122 | } |
1123 | else |
1124 | { |
1125 | DWORD offset = pNode->GetOffset(); |
1126 | if ((handlerStart <= offset) && (offset < handlerEnd)) |
1127 | { |
1128 | return true; |
1129 | } |
1130 | } |
1131 | } |
1132 | |
1133 | #ifdef WIN64EXCEPTIONS |
1134 | // Refer to the comment in TryContains(). |
1135 | if (!m_pTree->m_fInitializing) |
1136 | { |
1137 | // Iterate all the contained clauses, and for the ones which are contained in the try region, |
1138 | // ask if the requested range is contained by it. |
1139 | USHORT i = 0; |
1140 | USHORT numNodes = m_containees.Count(); |
1141 | EHRangeTreeNode** ppNodes = NULL; |
1142 | for (i = 0, ppNodes = m_containees.Table(); i < numNodes; i++, ppNodes++) |
1143 | { |
1144 | // This variable is purely used for readability. |
1145 | EHRangeTreeNode* pNodeCur = *ppNodes; |
1146 | |
1147 | if ( ( this->GetHandlerStart() <= pNodeCur->GetTryStart() ) && |
1148 | ( pNodeCur->GetTryEnd() < this->GetHandlerEnd() ) ) |
1149 | { |
1150 | if (pNodeCur->Contains(pNode)) |
1151 | { |
1152 | return true; |
1153 | } |
1154 | } |
1155 | } |
1156 | } |
1157 | #endif // WIN64EXCEPTIONS |
1158 | |
1159 | return false; |
1160 | } |
1161 | |
1162 | bool EHRangeTreeNode::FilterContains(EHRangeTreeNode* pNode) |
1163 | { |
1164 | LIMITED_METHOD_CONTRACT; |
1165 | |
1166 | _ASSERTE(this->IsRange()); |
1167 | |
1168 | if (pNode->IsRoot()) |
1169 | { |
1170 | // No node contains the root node. |
1171 | return false; |
1172 | } |
1173 | else if (this->IsRoot() || !IsFilterHandler(this->m_clause)) |
1174 | { |
1175 | // We will only get here from GetTcf() to determine if an address is in a try clause. |
1176 | // In this case we want to return false. |
1177 | return false; |
1178 | } |
1179 | else |
1180 | { |
1181 | DWORD filterStart = this->GetFilterStart(); |
1182 | DWORD filterEnd = this->GetFilterEnd(); |
1183 | |
1184 | // If we are checking a range of address, then we should check the end address inclusively. |
1185 | if (pNode->IsRange()) |
1186 | { |
1187 | DWORD start = pNode->GetTryStart(); |
1188 | DWORD end = pNode->GetTryEnd(); |
1189 | |
1190 | if (start == filterStart && end == filterEnd) |
1191 | { |
1192 | return false; |
1193 | } |
1194 | else if ((filterStart <= start) && (end <= filterEnd)) |
1195 | { |
1196 | return true; |
1197 | } |
1198 | } |
1199 | else |
1200 | { |
1201 | DWORD offset = pNode->GetOffset(); |
1202 | if ((filterStart <= offset) && (offset < filterEnd)) |
1203 | { |
1204 | return true; |
1205 | } |
1206 | } |
1207 | } |
1208 | |
1209 | #ifdef WIN64EXCEPTIONS |
1210 | // Refer to the comment in TryContains(). |
1211 | if (!m_pTree->m_fInitializing) |
1212 | { |
1213 | // Iterate all the contained clauses, and for the ones which are contained in the try region, |
1214 | // ask if the requested range is contained by it. |
1215 | USHORT i = 0; |
1216 | USHORT numNodes = m_containees.Count(); |
1217 | EHRangeTreeNode** ppNodes = NULL; |
1218 | for (i = 0, ppNodes = m_containees.Table(); i < numNodes; i++, ppNodes++) |
1219 | { |
1220 | // This variable is purely used for readability. |
1221 | EHRangeTreeNode* pNodeCur = *ppNodes; |
1222 | |
1223 | if ( ( this->GetFilterStart() <= pNodeCur->GetTryStart() ) && |
1224 | ( pNodeCur->GetTryEnd() < this->GetFilterEnd() ) ) |
1225 | { |
1226 | if (pNodeCur->Contains(pNode)) |
1227 | { |
1228 | return true; |
1229 | } |
1230 | } |
1231 | } |
1232 | } |
1233 | #endif // WIN64EXCEPTIONS |
1234 | |
1235 | return false; |
1236 | } |
1237 | |
1238 | EHRangeTreeNode* EHRangeTreeNode::GetContainer() |
1239 | { |
1240 | return m_pContainedBy; |
1241 | } |
1242 | |
1243 | HRESULT EHRangeTreeNode::AddNode(EHRangeTreeNode *pNode) |
1244 | { |
1245 | CONTRACTL |
1246 | { |
1247 | NOTHROW; |
1248 | GC_NOTRIGGER; |
1249 | MODE_ANY; |
1250 | INJECT_FAULT(return E_OUTOFMEMORY;); |
1251 | PRECONDITION(pNode != NULL); |
1252 | } |
1253 | CONTRACTL_END; |
1254 | |
1255 | EHRangeTreeNode **ppEH = m_containees.Append(); |
1256 | |
1257 | if (ppEH == NULL) |
1258 | return E_OUTOFMEMORY; |
1259 | |
1260 | (*ppEH) = pNode; |
1261 | return S_OK; |
1262 | } |
1263 | |
1264 | // ******************************* EHRangeTree ************************** // |
1265 | |
1266 | EHRangeTree::EHRangeTree(IJitManager* pIJM, |
1267 | const METHODTOKEN& methodToken, |
1268 | DWORD methodSize, |
1269 | int cFunclet, |
1270 | const DWORD * rgFunclet) |
1271 | { |
1272 | LIMITED_METHOD_CONTRACT; |
1273 | |
1274 | LOG((LF_CORDB, LL_INFO10000, "EHRT::ERHT: already loaded!\n" )); |
1275 | |
1276 | EH_CLAUSE_ENUMERATOR pEnumState; |
1277 | m_EHCount = pIJM->InitializeEHEnumeration(methodToken, &pEnumState); |
1278 | |
1279 | _ASSERTE(m_EHCount != 0xFFFFFFFF); |
1280 | |
1281 | ULONG i = 0; |
1282 | |
1283 | m_rgClauses = NULL; |
1284 | m_rgNodes = NULL; |
1285 | m_root = NULL; |
1286 | m_hrInit = S_OK; |
1287 | m_fInitializing = true; |
1288 | |
1289 | if (m_EHCount > 0) |
1290 | { |
1291 | m_rgClauses = new (nothrow) EE_ILEXCEPTION_CLAUSE[m_EHCount]; |
1292 | if (m_rgClauses == NULL) |
1293 | { |
1294 | m_hrInit = E_OUTOFMEMORY; |
1295 | goto LError; |
1296 | } |
1297 | } |
1298 | |
1299 | LOG((LF_CORDB, LL_INFO10000, "EHRT::CC: m_ehcount:0x%x, m_rgClauses:0%x\n" , |
1300 | m_EHCount, m_rgClauses)); |
1301 | |
1302 | m_rgNodes = new (nothrow) EHRangeTreeNode[m_EHCount+1]; |
1303 | if (m_rgNodes == NULL) |
1304 | { |
1305 | m_hrInit = E_OUTOFMEMORY; |
1306 | goto LError; |
1307 | } |
1308 | |
1309 | //this contains everything, even stuff on the last IP |
1310 | m_root = &(m_rgNodes[m_EHCount]); |
1311 | m_root->MarkAsRoot(methodSize + 1); |
1312 | |
1313 | LOG((LF_CORDB, LL_INFO10000, "EHRT::CC: rgNodes:0x%x\n" , m_rgNodes)); |
1314 | |
1315 | if (m_EHCount ==0) |
1316 | { |
1317 | LOG((LF_CORDB, LL_INFO10000, "EHRT::CC: About to leave!\n" )); |
1318 | goto LSuccess; |
1319 | } |
1320 | |
1321 | LOG((LF_CORDB, LL_INFO10000, "EHRT::CC: Sticking around!\n" )); |
1322 | |
1323 | // First, load all the EH clauses into the object. |
1324 | for (i = 0; i < m_EHCount; i++) |
1325 | { |
1326 | EE_ILEXCEPTION_CLAUSE * pEHClause = &(m_rgClauses[i]); |
1327 | |
1328 | LOG((LF_CORDB, LL_INFO10000, "EHRT::CC: i:0x%x!\n" , i)); |
1329 | |
1330 | pIJM->GetNextEHClause(&pEnumState, pEHClause); |
1331 | |
1332 | LOG((LF_CORDB, LL_INFO10000, "EHRT::CC: EHRTT_JIT_MANAGER got clause\n" , i)); |
1333 | |
1334 | LOG((LF_CORDB, LL_INFO10000, "EHRT::CC: clause 0x%x," |
1335 | "addrof:0x%x\n" , i, pEHClause )); |
1336 | |
1337 | _ASSERTE(pEHClause->HandlerEndPC != (DWORD) -1); // <TODO> remove, only protects against a deprecated convention</TODO> |
1338 | |
1339 | EHRangeTreeNode * pNodeCur = &(m_rgNodes[i]); |
1340 | |
1341 | pNodeCur->m_pTree = this; |
1342 | pNodeCur->m_clause = pEHClause; |
1343 | |
1344 | if (pEHClause->Flags == COR_ILEXCEPTION_CLAUSE_FILTER) |
1345 | { |
1346 | #ifdef WIN64EXCEPTIONS |
1347 | // Because of funclets, there is no way to guarantee the placement of a filter. |
1348 | // Thus, we need to loop through the funclets to find the end offset. |
1349 | for (int f = 0; f < cFunclet; f++) |
1350 | { |
1351 | // Check the start offset of the filter funclet. |
1352 | if (pEHClause->FilterOffset == rgFunclet[f]) |
1353 | { |
1354 | if (f < (cFunclet - 1)) |
1355 | { |
1356 | // If it's NOT the last funclet, use the start offset of the next funclet. |
1357 | pNodeCur->m_FilterEndPC = rgFunclet[f + 1]; |
1358 | } |
1359 | else |
1360 | { |
1361 | // If it's the last funclet, use the size of the method. |
1362 | pNodeCur->m_FilterEndPC = methodSize; |
1363 | } |
1364 | break; |
1365 | } |
1366 | } |
1367 | #else // WIN64EXCEPTIONS |
1368 | // On x86, since the filter doesn't have an end FilterPC, the only way we can know the size |
1369 | // of the filter is if it's located immediately prior to it's handler and immediately after |
1370 | // its try region. We assume that this is, and if it isn't, we're so amazingly hosed that |
1371 | // we can't continue. |
1372 | if ((pEHClause->FilterOffset >= pEHClause->HandlerStartPC) || |
1373 | (pEHClause->FilterOffset < pEHClause->TryEndPC)) |
1374 | { |
1375 | m_hrInit = CORDBG_E_SET_IP_IMPOSSIBLE; |
1376 | goto LError; |
1377 | } |
1378 | pNodeCur->m_FilterEndPC = pEHClause->HandlerStartPC; |
1379 | #endif // WIN64EXCEPTIONS |
1380 | } |
1381 | |
1382 | pNodeCur->MarkAsRange(); |
1383 | } |
1384 | |
1385 | LOG((LF_CORDB, LL_INFO10000, "EHRT::CC: about to do the second pass\n" )); |
1386 | |
1387 | |
1388 | // Second, for each EH, find it's most limited, containing clause |
1389 | // On WIN64, we have duplicate clauses. There are two types of duplicate clauses. |
1390 | // |
1391 | // The first type is described in ExceptionHandling.cpp. This type doesn't add additional information to the |
1392 | // EH tree structure. For example, if an offset is in the try region of a duplicate clause of this type, |
1393 | // then some clause which comes before the duplicate clause should contain the offset in its handler region. |
1394 | // Therefore, even though this type of duplicate clauses are added to the EH tree, they should never be used. |
1395 | // |
1396 | // The second type is what's called the protected clause. These clauses are used to mark the cloned finally |
1397 | // region. They have an empty try region. Here's an example: |
1398 | // |
1399 | // // C# code |
1400 | // try |
1401 | // { |
1402 | // A |
1403 | // } |
1404 | // finally |
1405 | // { |
1406 | // B |
1407 | // } |
1408 | // |
1409 | // // jitted code |
1410 | // parent |
1411 | // ------- |
1412 | // A |
1413 | // B' |
1414 | // ------- |
1415 | // |
1416 | // funclet |
1417 | // ------- |
1418 | // B |
1419 | // ------- |
1420 | // |
1421 | // A protected clause covers the B' region in the parent method. In essence you can think of the method as |
1422 | // having two try/finally regions, and that's exactly how protected clauses are handled in the EH tree. |
1423 | // They are added to the EH tree just like any other EH clauses. |
1424 | for (i = 0; i < m_EHCount; i++) |
1425 | { |
1426 | LOG((LF_CORDB, LL_INFO10000, "EHRT::CC: SP:0x%x\n" , i)); |
1427 | |
1428 | EHRangeTreeNode * pNodeCur = &(m_rgNodes[i]); |
1429 | |
1430 | EHRangeTreeNode *pNodeCandidate = NULL; |
1431 | pNodeCandidate = FindContainer(pNodeCur); |
1432 | _ASSERTE(pNodeCandidate != NULL); |
1433 | |
1434 | pNodeCur->m_pContainedBy = pNodeCandidate; |
1435 | |
1436 | LOG((LF_CORDB, LL_INFO10000, "EHRT::CC: SP: about to add to tree\n" )); |
1437 | |
1438 | HRESULT hr = pNodeCandidate->AddNode(pNodeCur); |
1439 | if (FAILED(hr)) |
1440 | { |
1441 | m_hrInit = hr; |
1442 | goto LError; |
1443 | } |
1444 | } |
1445 | |
1446 | LSuccess: |
1447 | m_fInitializing = false; |
1448 | return; |
1449 | |
1450 | LError: |
1451 | LOG((LF_CORDB, LL_INFO10000, "EHRT::CC: LError - something went wrong!\n" )); |
1452 | |
1453 | if (m_rgClauses != NULL) |
1454 | { |
1455 | delete [] m_rgClauses; |
1456 | m_rgClauses = NULL; |
1457 | } |
1458 | |
1459 | if (m_rgNodes != NULL) |
1460 | { |
1461 | delete [] m_rgNodes; |
1462 | m_rgNodes = NULL; |
1463 | } |
1464 | |
1465 | m_fInitializing = false; |
1466 | |
1467 | LOG((LF_CORDB, LL_INFO10000, "EHRT::CC: Falling off of LError!\n" )); |
1468 | } // Ctor Core |
1469 | |
1470 | EHRangeTree::~EHRangeTree() |
1471 | { |
1472 | LIMITED_METHOD_CONTRACT; |
1473 | |
1474 | if (m_rgNodes != NULL) |
1475 | delete [] m_rgNodes; |
1476 | |
1477 | if (m_rgClauses != NULL) |
1478 | delete [] m_rgClauses; |
1479 | } //Dtor |
1480 | |
1481 | EHRangeTreeNode *EHRangeTree::FindContainer(EHRangeTreeNode *pNodeSearch) |
1482 | { |
1483 | LIMITED_METHOD_CONTRACT; |
1484 | |
1485 | EHRangeTreeNode *pNodeCandidate = NULL; |
1486 | |
1487 | // Examine the root, too. |
1488 | for (ULONG iInner = 0; iInner < m_EHCount+1; iInner++) |
1489 | { |
1490 | EHRangeTreeNode *pNodeCur = &(m_rgNodes[iInner]); |
1491 | |
1492 | // Check if the current node contains the node we are searching for. |
1493 | if ((pNodeSearch != pNodeCur) && |
1494 | pNodeCur->Contains(pNodeSearch)) |
1495 | { |
1496 | // Update the candidate node if it is NULL or if it contains the current node |
1497 | // (i.e. the current node is more specific than the candidate node). |
1498 | if ((pNodeCandidate == NULL) || |
1499 | pNodeCandidate->Contains(pNodeCur)) |
1500 | { |
1501 | pNodeCandidate = pNodeCur; |
1502 | } |
1503 | } |
1504 | } |
1505 | |
1506 | return pNodeCandidate; |
1507 | } |
1508 | |
1509 | EHRangeTreeNode *EHRangeTree::FindMostSpecificContainer(DWORD addr) |
1510 | { |
1511 | WRAPPER_NO_CONTRACT; |
1512 | |
1513 | EHRangeTreeNode node(addr); |
1514 | return FindContainer(&node); |
1515 | } |
1516 | |
1517 | EHRangeTreeNode *EHRangeTree::FindNextMostSpecificContainer(EHRangeTreeNode *pNodeSearch, DWORD addr) |
1518 | { |
1519 | WRAPPER_NO_CONTRACT; |
1520 | |
1521 | _ASSERTE(!m_fInitializing); |
1522 | |
1523 | EHRangeTreeNode **rgpNodes = pNodeSearch->m_containees.Table(); |
1524 | |
1525 | if (NULL == rgpNodes) |
1526 | return pNodeSearch; |
1527 | |
1528 | // It's possible that no subrange contains the desired address, so |
1529 | // keep a reasonable default around. |
1530 | EHRangeTreeNode *pNodeCandidate = pNodeSearch; |
1531 | |
1532 | USHORT cSubRanges = pNodeSearch->m_containees.Count(); |
1533 | EHRangeTreeNode **ppNodeCur = pNodeSearch->m_containees.Table(); |
1534 | |
1535 | for (int i = 0; i < cSubRanges; i++, ppNodeCur++) |
1536 | { |
1537 | if ((*ppNodeCur)->Contains(addr) && |
1538 | pNodeCandidate->Contains((*ppNodeCur))) |
1539 | { |
1540 | pNodeCandidate = (*ppNodeCur); |
1541 | } |
1542 | } |
1543 | |
1544 | return pNodeCandidate; |
1545 | } |
1546 | |
1547 | BOOL EHRangeTree::isAtStartOfCatch(DWORD offset) |
1548 | { |
1549 | LIMITED_METHOD_CONTRACT; |
1550 | |
1551 | if (NULL != m_rgNodes && m_EHCount != 0) |
1552 | { |
1553 | for(unsigned i = 0; i < m_EHCount;i++) |
1554 | { |
1555 | if (m_rgNodes[i].m_clause->HandlerStartPC == offset && |
1556 | (!IsFilterHandler(m_rgNodes[i].m_clause) && !IsFaultOrFinally(m_rgNodes[i].m_clause))) |
1557 | return TRUE; |
1558 | } |
1559 | } |
1560 | |
1561 | return FALSE; |
1562 | } |
1563 | |
1564 | enum TRY_CATCH_FINALLY |
1565 | { |
1566 | TCF_NONE= 0, |
1567 | TCF_TRY, |
1568 | TCF_FILTER, |
1569 | TCF_CATCH, |
1570 | TCF_FINALLY, |
1571 | TCF_COUNT, //count of all elements, not an element itself |
1572 | }; |
1573 | |
1574 | #ifdef LOGGING |
1575 | const char *TCFStringFromConst(TRY_CATCH_FINALLY tcf) |
1576 | { |
1577 | LIMITED_METHOD_CONTRACT; |
1578 | |
1579 | switch( tcf ) |
1580 | { |
1581 | case TCF_NONE: |
1582 | return "TCFS_NONE" ; |
1583 | break; |
1584 | case TCF_TRY: |
1585 | return "TCFS_TRY" ; |
1586 | break; |
1587 | case TCF_FILTER: |
1588 | return "TCF_FILTER" ; |
1589 | break; |
1590 | case TCF_CATCH: |
1591 | return "TCFS_CATCH" ; |
1592 | break; |
1593 | case TCF_FINALLY: |
1594 | return "TCFS_FINALLY" ; |
1595 | break; |
1596 | case TCF_COUNT: |
1597 | return "TCFS_COUNT" ; |
1598 | break; |
1599 | default: |
1600 | return "INVALID TCFS VALUE" ; |
1601 | break; |
1602 | } |
1603 | } |
1604 | #endif //LOGGING |
1605 | |
1606 | #ifndef WIN64EXCEPTIONS |
1607 | // We're unwinding if we'll return to the EE's code. Otherwise |
1608 | // we'll return to someplace in the current code. Anywhere outside |
1609 | // this function is "EE code". |
1610 | bool FinallyIsUnwinding(EHRangeTreeNode *pNode, |
1611 | ICodeManager* pEECM, |
1612 | PREGDISPLAY pReg, |
1613 | SLOT addrStart) |
1614 | { |
1615 | CONTRACTL |
1616 | { |
1617 | NOTHROW; |
1618 | GC_NOTRIGGER; |
1619 | MODE_ANY; |
1620 | FORBID_FAULT; |
1621 | } |
1622 | CONTRACTL_END; |
1623 | |
1624 | const BYTE *pbRetAddr = pEECM->GetFinallyReturnAddr(pReg); |
1625 | |
1626 | if (pbRetAddr < (const BYTE *)addrStart) |
1627 | return true; |
1628 | |
1629 | DWORD offset = (DWORD)(size_t)(pbRetAddr - addrStart); |
1630 | EHRangeTreeNode *pRoot = pNode->m_pTree->m_root; |
1631 | |
1632 | if (!pRoot->Contains(offset)) |
1633 | return true; |
1634 | else |
1635 | return false; |
1636 | } |
1637 | |
1638 | BOOL LeaveCatch(ICodeManager* pEECM, |
1639 | Thread *pThread, |
1640 | CONTEXT *pCtx, |
1641 | GCInfoToken gcInfoToken, |
1642 | unsigned offset) |
1643 | { |
1644 | CONTRACTL |
1645 | { |
1646 | THROWS; |
1647 | GC_TRIGGERS; |
1648 | MODE_ANY; |
1649 | } |
1650 | CONTRACTL_END; |
1651 | |
1652 | // We can assert these things here, and skip a call |
1653 | // to COMPlusCheckForAbort later. |
1654 | |
1655 | // If no abort has been requested, |
1656 | _ASSERTE((pThread->GetThrowable() != NULL) || |
1657 | // or if there is a pending exception. |
1658 | (!pThread->IsAbortRequested()) ); |
1659 | |
1660 | LPVOID esp = COMPlusEndCatchWorker(pThread); |
1661 | |
1662 | PopNestedExceptionRecords(esp, pCtx, pThread->GetExceptionListPtr()); |
1663 | |
1664 | // Do JIT-specific work |
1665 | pEECM->LeaveCatch(gcInfoToken, offset, pCtx); |
1666 | |
1667 | SetSP(pCtx, (UINT_PTR)esp); |
1668 | return TRUE; |
1669 | } |
1670 | #endif // WIN64EXCEPTIONS |
1671 | |
1672 | TRY_CATCH_FINALLY GetTcf(EHRangeTreeNode *pNode, |
1673 | unsigned offset) |
1674 | { |
1675 | CONTRACTL |
1676 | { |
1677 | NOTHROW; |
1678 | GC_NOTRIGGER; |
1679 | MODE_ANY; |
1680 | FORBID_FAULT; |
1681 | } |
1682 | CONTRACTL_END; |
1683 | |
1684 | _ASSERTE(pNode->IsRange() && !pNode->IsRoot()); |
1685 | |
1686 | TRY_CATCH_FINALLY tcf; |
1687 | |
1688 | if (!pNode->Contains(offset)) |
1689 | { |
1690 | tcf = TCF_NONE; |
1691 | } |
1692 | else if (pNode->TryContains(offset)) |
1693 | { |
1694 | tcf = TCF_TRY; |
1695 | } |
1696 | else if (pNode->FilterContains(offset)) |
1697 | { |
1698 | tcf = TCF_FILTER; |
1699 | } |
1700 | else |
1701 | { |
1702 | _ASSERTE(pNode->HandlerContains(offset)); |
1703 | if (IsFaultOrFinally(pNode->m_clause)) |
1704 | tcf = TCF_FINALLY; |
1705 | else |
1706 | tcf = TCF_CATCH; |
1707 | } |
1708 | |
1709 | return tcf; |
1710 | } |
1711 | |
1712 | const DWORD bEnter = 0x01; |
1713 | const DWORD bLeave = 0x02; |
1714 | |
1715 | HRESULT IsLegalTransition(Thread *pThread, |
1716 | bool fCanSetIPOnly, |
1717 | DWORD fEnter, |
1718 | EHRangeTreeNode *pNode, |
1719 | DWORD offFrom, |
1720 | DWORD offTo, |
1721 | ICodeManager* pEECM, |
1722 | PREGDISPLAY pReg, |
1723 | SLOT addrStart, |
1724 | GCInfoToken gcInfoToken, |
1725 | PCONTEXT pCtx) |
1726 | { |
1727 | CONTRACTL |
1728 | { |
1729 | THROWS; |
1730 | GC_TRIGGERS; |
1731 | MODE_ANY; |
1732 | } |
1733 | CONTRACTL_END; |
1734 | |
1735 | #ifdef _DEBUG |
1736 | if (fEnter & bEnter) |
1737 | { |
1738 | _ASSERTE(pNode->Contains(offTo)); |
1739 | } |
1740 | if (fEnter & bLeave) |
1741 | { |
1742 | _ASSERTE(pNode->Contains(offFrom)); |
1743 | } |
1744 | #endif //_DEBUG |
1745 | |
1746 | // First, figure out where we're coming from/going to |
1747 | TRY_CATCH_FINALLY tcfFrom = GetTcf(pNode, |
1748 | offFrom); |
1749 | |
1750 | TRY_CATCH_FINALLY tcfTo = GetTcf(pNode, |
1751 | offTo); |
1752 | |
1753 | LOG((LF_CORDB, LL_INFO10000, "ILT: from %s to %s\n" , |
1754 | TCFStringFromConst(tcfFrom), |
1755 | TCFStringFromConst(tcfTo))); |
1756 | |
1757 | // Now we'll consider, case-by-case, the various permutations that |
1758 | // can arise |
1759 | switch(tcfFrom) |
1760 | { |
1761 | case TCF_NONE: |
1762 | case TCF_TRY: |
1763 | { |
1764 | switch(tcfTo) |
1765 | { |
1766 | case TCF_NONE: |
1767 | case TCF_TRY: |
1768 | { |
1769 | return S_OK; |
1770 | break; |
1771 | } |
1772 | |
1773 | case TCF_FILTER: |
1774 | { |
1775 | return CORDBG_E_CANT_SETIP_INTO_OR_OUT_OF_FILTER; |
1776 | break; |
1777 | } |
1778 | |
1779 | case TCF_CATCH: |
1780 | { |
1781 | return CORDBG_E_CANT_SET_IP_INTO_CATCH; |
1782 | break; |
1783 | } |
1784 | |
1785 | case TCF_FINALLY: |
1786 | { |
1787 | return CORDBG_E_CANT_SET_IP_INTO_FINALLY; |
1788 | break; |
1789 | } |
1790 | default: |
1791 | break; |
1792 | } |
1793 | break; |
1794 | } |
1795 | |
1796 | case TCF_FILTER: |
1797 | { |
1798 | switch(tcfTo) |
1799 | { |
1800 | case TCF_NONE: |
1801 | case TCF_TRY: |
1802 | case TCF_CATCH: |
1803 | case TCF_FINALLY: |
1804 | { |
1805 | return CORDBG_E_CANT_SETIP_INTO_OR_OUT_OF_FILTER; |
1806 | break; |
1807 | } |
1808 | case TCF_FILTER: |
1809 | { |
1810 | return S_OK; |
1811 | break; |
1812 | } |
1813 | default: |
1814 | break; |
1815 | |
1816 | } |
1817 | break; |
1818 | } |
1819 | |
1820 | case TCF_CATCH: |
1821 | { |
1822 | switch(tcfTo) |
1823 | { |
1824 | case TCF_NONE: |
1825 | case TCF_TRY: |
1826 | { |
1827 | #if !defined(WIN64EXCEPTIONS) |
1828 | CONTEXT *pFilterCtx = pThread->GetFilterContext(); |
1829 | if (pFilterCtx == NULL) |
1830 | return CORDBG_E_SET_IP_IMPOSSIBLE; |
1831 | |
1832 | if (!fCanSetIPOnly) |
1833 | { |
1834 | if (!LeaveCatch(pEECM, |
1835 | pThread, |
1836 | pFilterCtx, |
1837 | gcInfoToken, |
1838 | offFrom)) |
1839 | return E_FAIL; |
1840 | } |
1841 | return S_OK; |
1842 | #else // WIN64EXCEPTIONS |
1843 | // <NOTE> |
1844 | // Setting IP out of a catch clause is not supported for WIN64EXCEPTIONS because of funclets. |
1845 | // This scenario is disabled with approval from VS because it's not considered to |
1846 | // be a common user scenario. |
1847 | // </NOTE> |
1848 | return CORDBG_E_CANT_SET_IP_OUT_OF_CATCH_ON_WIN64; |
1849 | #endif // !WIN64EXCEPTIONS |
1850 | break; |
1851 | } |
1852 | |
1853 | case TCF_FILTER: |
1854 | { |
1855 | return CORDBG_E_CANT_SETIP_INTO_OR_OUT_OF_FILTER; |
1856 | break; |
1857 | } |
1858 | |
1859 | case TCF_CATCH: |
1860 | { |
1861 | return S_OK; |
1862 | break; |
1863 | } |
1864 | |
1865 | case TCF_FINALLY: |
1866 | { |
1867 | return CORDBG_E_CANT_SET_IP_INTO_FINALLY; |
1868 | break; |
1869 | } |
1870 | default: |
1871 | break; |
1872 | } |
1873 | break; |
1874 | } |
1875 | |
1876 | case TCF_FINALLY: |
1877 | { |
1878 | switch(tcfTo) |
1879 | { |
1880 | case TCF_NONE: |
1881 | case TCF_TRY: |
1882 | { |
1883 | #ifndef WIN64EXCEPTIONS |
1884 | if (!FinallyIsUnwinding(pNode, pEECM, pReg, addrStart)) |
1885 | { |
1886 | CONTEXT *pFilterCtx = pThread->GetFilterContext(); |
1887 | if (pFilterCtx == NULL) |
1888 | return CORDBG_E_SET_IP_IMPOSSIBLE; |
1889 | |
1890 | if (!fCanSetIPOnly) |
1891 | { |
1892 | if (!pEECM->LeaveFinally(gcInfoToken, |
1893 | offFrom, |
1894 | pFilterCtx)) |
1895 | return E_FAIL; |
1896 | } |
1897 | return S_OK; |
1898 | } |
1899 | else |
1900 | { |
1901 | return CORDBG_E_CANT_SET_IP_OUT_OF_FINALLY; |
1902 | } |
1903 | #else // !WIN64EXCEPTIONS |
1904 | // <NOTE> |
1905 | // Setting IP out of a non-unwinding finally clause is not supported on WIN64EXCEPTIONS because of funclets. |
1906 | // This scenario is disabled with approval from VS because it's not considered to be a common user |
1907 | // scenario. |
1908 | // </NOTE> |
1909 | return CORDBG_E_CANT_SET_IP_OUT_OF_FINALLY_ON_WIN64; |
1910 | #endif // WIN64EXCEPTIONS |
1911 | |
1912 | break; |
1913 | } |
1914 | |
1915 | case TCF_FILTER: |
1916 | { |
1917 | return CORDBG_E_CANT_SETIP_INTO_OR_OUT_OF_FILTER; |
1918 | break; |
1919 | } |
1920 | |
1921 | case TCF_CATCH: |
1922 | { |
1923 | return CORDBG_E_CANT_SET_IP_INTO_CATCH; |
1924 | break; |
1925 | } |
1926 | |
1927 | case TCF_FINALLY: |
1928 | { |
1929 | return S_OK; |
1930 | break; |
1931 | } |
1932 | default: |
1933 | break; |
1934 | } |
1935 | break; |
1936 | } |
1937 | break; |
1938 | default: |
1939 | break; |
1940 | } |
1941 | |
1942 | _ASSERTE( !"IsLegalTransition: We should never reach this point!" ); |
1943 | |
1944 | return CORDBG_E_SET_IP_IMPOSSIBLE; |
1945 | } |
1946 | |
1947 | // We need this to determine what |
1948 | // to do based on whether the stack in general is empty |
1949 | HRESULT DestinationIsValid(void *pDjiToken, |
1950 | DWORD offTo, |
1951 | EHRangeTree *pEHRT) |
1952 | { |
1953 | CONTRACTL |
1954 | { |
1955 | NOTHROW; |
1956 | GC_NOTRIGGER; |
1957 | MODE_ANY; |
1958 | FORBID_FAULT; |
1959 | } |
1960 | CONTRACTL_END; |
1961 | |
1962 | // We'll add a call to the DebugInterface that takes this |
1963 | // & tells us if the destination is a stack empty point. |
1964 | // DebuggerJitInfo *pDji = (DebuggerJitInfo *)pDjiToken; |
1965 | |
1966 | if (pEHRT->isAtStartOfCatch(offTo)) |
1967 | return CORDBG_S_BAD_START_SEQUENCE_POINT; |
1968 | else |
1969 | return S_OK; |
1970 | } // HRESULT DestinationIsValid() |
1971 | |
1972 | // We want to keep the 'worst' HRESULT - if one has failed (..._E_...) & the |
1973 | // other hasn't, take the failing one. If they've both/neither failed, then |
1974 | // it doesn't matter which we take. |
1975 | // Note that this macro favors retaining the first argument |
1976 | #define WORST_HR(hr1,hr2) (FAILED(hr1)?hr1:hr2) |
1977 | HRESULT SetIPFromSrcToDst(Thread *pThread, |
1978 | SLOT addrStart, // base address of method |
1979 | DWORD offFrom, // native offset |
1980 | DWORD offTo, // native offset |
1981 | bool fCanSetIPOnly, // if true, don't do any real work |
1982 | PREGDISPLAY pReg, |
1983 | PCONTEXT pCtx, |
1984 | void *pDji, |
1985 | EHRangeTree *pEHRT) |
1986 | { |
1987 | CONTRACTL |
1988 | { |
1989 | THROWS; |
1990 | GC_TRIGGERS; |
1991 | MODE_ANY; |
1992 | INJECT_FAULT(return E_OUTOFMEMORY;); |
1993 | } |
1994 | CONTRACTL_END; |
1995 | |
1996 | HRESULT hr = S_OK; |
1997 | HRESULT hrReturn = S_OK; |
1998 | bool fCheckOnly = true; |
1999 | |
2000 | EECodeInfo codeInfo((TADDR)(addrStart)); |
2001 | |
2002 | ICodeManager * pEECM = codeInfo.GetCodeManager(); |
2003 | GCInfoToken gcInfoToken = codeInfo.GetGCInfoToken(); |
2004 | |
2005 | // Do both checks here so compiler doesn't complain about skipping |
2006 | // initialization b/c of goto. |
2007 | if (fCanSetIPOnly && !pEECM->IsGcSafe(&codeInfo, offFrom)) |
2008 | { |
2009 | hrReturn = WORST_HR(hrReturn, CORDBG_E_SET_IP_IMPOSSIBLE); |
2010 | } |
2011 | |
2012 | if (fCanSetIPOnly && !pEECM->IsGcSafe(&codeInfo, offTo)) |
2013 | { |
2014 | hrReturn = WORST_HR(hrReturn, CORDBG_E_SET_IP_IMPOSSIBLE); |
2015 | } |
2016 | |
2017 | if ((hr = DestinationIsValid(pDji, offTo, pEHRT)) != S_OK |
2018 | && fCanSetIPOnly) |
2019 | { |
2020 | hrReturn = WORST_HR(hrReturn,hr); |
2021 | } |
2022 | |
2023 | // The basic approach is this: We'll start with the most specific (smallest) |
2024 | // EHClause that contains the starting address. We'll 'back out', to larger |
2025 | // and larger ranges, until we either find an EHClause that contains both |
2026 | // the from and to addresses, or until we reach the root EHRangeTreeNode, |
2027 | // which contains all addresses within it. At each step, we check/do work |
2028 | // that the various transitions (from inside to outside a catch, etc). |
2029 | // At that point, we do the reverse process - we go from the EHClause that |
2030 | // encompasses both from and to, and narrow down to the smallest EHClause that |
2031 | // encompasses the to point. We use our nifty data structure to manage |
2032 | // the tree structure inherent in this process. |
2033 | // |
2034 | // NOTE: We do this process twice, once to check that we're not doing an |
2035 | // overall illegal transition, such as ultimately set the IP into |
2036 | // a catch, which is never allowed. We're doing this because VS |
2037 | // calls SetIP without calling CanSetIP first, and so we should be able |
2038 | // to return an error code and have the stack in the same condition |
2039 | // as the start of the call, and so we shouldn't back out of clauses |
2040 | // or move into them until we're sure that can be done. |
2041 | |
2042 | retryForCommit: |
2043 | |
2044 | EHRangeTreeNode *node; |
2045 | EHRangeTreeNode *nodeNext; |
2046 | node = pEHRT->FindMostSpecificContainer(offFrom); |
2047 | |
2048 | while (!node->Contains(offTo)) |
2049 | { |
2050 | hr = IsLegalTransition(pThread, |
2051 | fCheckOnly, |
2052 | bLeave, |
2053 | node, |
2054 | offFrom, |
2055 | offTo, |
2056 | pEECM, |
2057 | pReg, |
2058 | addrStart, |
2059 | gcInfoToken, |
2060 | pCtx); |
2061 | |
2062 | if (FAILED(hr)) |
2063 | { |
2064 | hrReturn = WORST_HR(hrReturn,hr); |
2065 | } |
2066 | |
2067 | node = node->GetContainer(); |
2068 | // m_root prevents node from ever being NULL. |
2069 | } |
2070 | |
2071 | if (node != pEHRT->m_root) |
2072 | { |
2073 | hr = IsLegalTransition(pThread, |
2074 | fCheckOnly, |
2075 | bEnter|bLeave, |
2076 | node, |
2077 | offFrom, |
2078 | offTo, |
2079 | pEECM, |
2080 | pReg, |
2081 | addrStart, |
2082 | gcInfoToken, |
2083 | pCtx); |
2084 | |
2085 | if (FAILED(hr)) |
2086 | { |
2087 | hrReturn = WORST_HR(hrReturn,hr); |
2088 | } |
2089 | } |
2090 | |
2091 | nodeNext = pEHRT->FindNextMostSpecificContainer(node, |
2092 | offTo); |
2093 | |
2094 | while(nodeNext != node) |
2095 | { |
2096 | hr = IsLegalTransition(pThread, |
2097 | fCheckOnly, |
2098 | bEnter, |
2099 | nodeNext, |
2100 | offFrom, |
2101 | offTo, |
2102 | pEECM, |
2103 | pReg, |
2104 | addrStart, |
2105 | gcInfoToken, |
2106 | pCtx); |
2107 | |
2108 | if (FAILED(hr)) |
2109 | { |
2110 | hrReturn = WORST_HR(hrReturn, hr); |
2111 | } |
2112 | |
2113 | node = nodeNext; |
2114 | nodeNext = pEHRT->FindNextMostSpecificContainer(node, |
2115 | offTo); |
2116 | } |
2117 | |
2118 | // If it was the intention to actually set the IP and the above transition checks succeeded, |
2119 | // then go back and do it all again but this time widen and narrow the thread's actual scope |
2120 | if (!fCanSetIPOnly && fCheckOnly && SUCCEEDED(hrReturn)) |
2121 | { |
2122 | fCheckOnly = false; |
2123 | goto retryForCommit; |
2124 | } |
2125 | |
2126 | return hrReturn; |
2127 | } // HRESULT SetIPFromSrcToDst() |
2128 | |
2129 | // This function should only be called if the thread is suspended and sitting in jitted code |
2130 | BOOL IsInFirstFrameOfHandler(Thread *pThread, IJitManager *pJitManager, const METHODTOKEN& MethodToken, DWORD offset) |
2131 | { |
2132 | CONTRACTL |
2133 | { |
2134 | NOTHROW; |
2135 | GC_NOTRIGGER; |
2136 | MODE_ANY; |
2137 | FORBID_FAULT; |
2138 | } |
2139 | CONTRACTL_END; |
2140 | |
2141 | // if don't have a throwable the aren't processing an exception |
2142 | if (IsHandleNullUnchecked(pThread->GetThrowableAsHandle())) |
2143 | return FALSE; |
2144 | |
2145 | EH_CLAUSE_ENUMERATOR pEnumState; |
2146 | unsigned EHCount = pJitManager->InitializeEHEnumeration(MethodToken, &pEnumState); |
2147 | |
2148 | for(ULONG i=0; i < EHCount; i++) |
2149 | { |
2150 | EE_ILEXCEPTION_CLAUSE EHClause; |
2151 | pJitManager->GetNextEHClause(&pEnumState, &EHClause); |
2152 | _ASSERTE(IsValidClause(&EHClause)); |
2153 | |
2154 | if ( offset >= EHClause.HandlerStartPC && offset < EHClause.HandlerEndPC) |
2155 | return TRUE; |
2156 | |
2157 | // check if it's in the filter itself if we're not in the handler |
2158 | if (IsFilterHandler(&EHClause) && offset >= EHClause.FilterOffset && offset < EHClause.HandlerStartPC) |
2159 | return TRUE; |
2160 | } |
2161 | return FALSE; |
2162 | } // BOOL IsInFirstFrameOfHandler() |
2163 | |
2164 | |
2165 | #if !defined(WIN64EXCEPTIONS) |
2166 | |
2167 | //****************************************************************************** |
2168 | // LookForHandler -- search for a function that will handle the exception. |
2169 | //****************************************************************************** |
2170 | LFH LookForHandler( // LFH return types |
2171 | const EXCEPTION_POINTERS *pExceptionPointers, // The ExceptionRecord and ExceptionContext |
2172 | Thread *pThread, // Thread on which to look (always current?) |
2173 | ThrowCallbackType *tct) // Structure to pass back to callback functions. |
2174 | { |
2175 | // We don't want to use a runtime contract here since this codepath is used during |
2176 | // the processing of a hard SO. Contracts use a significant amount of stack |
2177 | // which we can't afford for those cases. |
2178 | STATIC_CONTRACT_THROWS; |
2179 | STATIC_CONTRACT_GC_TRIGGERS; |
2180 | STATIC_CONTRACT_MODE_COOPERATIVE; |
2181 | |
2182 | // go through to find if anyone handles the exception |
2183 | StackWalkAction action = pThread->StackWalkFrames((PSTACKWALKFRAMESCALLBACK)COMPlusThrowCallback, |
2184 | tct, |
2185 | 0, //can't use FUNCTIONSONLY because the callback uses non-function frames to stop the walk |
2186 | tct->pBottomFrame); |
2187 | |
2188 | // If someone handles it, the action will be SWA_ABORT with pFunc and dHandler indicating the |
2189 | // function and handler that is handling the exception. Debugger can put a hook in here. |
2190 | if (action == SWA_ABORT && tct->pFunc != NULL) |
2191 | return LFH_FOUND; |
2192 | |
2193 | // nobody is handling it |
2194 | return LFH_NOT_FOUND; |
2195 | } // LFH LookForHandler() |
2196 | |
2197 | StackWalkAction COMPlusUnwindCallback (CrawlFrame *pCf, ThrowCallbackType *pData); |
2198 | |
2199 | //****************************************************************************** |
2200 | // UnwindFrames |
2201 | //****************************************************************************** |
2202 | void UnwindFrames( // No return value. |
2203 | Thread *pThread, // Thread to unwind. |
2204 | ThrowCallbackType *tct) // Structure to pass back to callback function. |
2205 | { |
2206 | STATIC_CONTRACT_THROWS; |
2207 | STATIC_CONTRACT_GC_NOTRIGGER; |
2208 | STATIC_CONTRACT_MODE_COOPERATIVE; |
2209 | |
2210 | if (pThread->IsExceptionInProgress()) |
2211 | { |
2212 | pThread->GetExceptionState()->GetFlags()->SetUnwindHasStarted(); |
2213 | } |
2214 | |
2215 | #ifdef DEBUGGING_SUPPORTED |
2216 | // |
2217 | // If a debugger is attached, notify it that unwinding is going on. |
2218 | // |
2219 | if (CORDebuggerAttached()) |
2220 | { |
2221 | g_pDebugInterface->ManagedExceptionUnwindBegin(pThread); |
2222 | } |
2223 | #endif // DEBUGGING_SUPPORTED |
2224 | |
2225 | LOG((LF_EH, LL_INFO1000, "UnwindFrames: going to: pFunc:%#X, pStack:%#X\n" , |
2226 | tct->pFunc, tct->pStack)); |
2227 | |
2228 | pThread->StackWalkFrames((PSTACKWALKFRAMESCALLBACK)COMPlusUnwindCallback, |
2229 | tct, |
2230 | POPFRAMES, |
2231 | tct->pBottomFrame); |
2232 | } // void UnwindFrames() |
2233 | |
2234 | #endif // !defined(WIN64EXCEPTIONS) |
2235 | |
2236 | void StackTraceInfo::SaveStackTrace(BOOL bAllowAllocMem, OBJECTHANDLE hThrowable, BOOL bReplaceStack, BOOL bSkipLastElement) |
2237 | { |
2238 | CONTRACTL |
2239 | { |
2240 | NOTHROW; |
2241 | GC_TRIGGERS; |
2242 | MODE_COOPERATIVE; |
2243 | } |
2244 | CONTRACTL_END; |
2245 | |
2246 | // Do not save stacktrace to preallocated exception. These are shared. |
2247 | if (CLRException::IsPreallocatedExceptionHandle(hThrowable)) |
2248 | { |
2249 | // Preallocated exceptions will never have this flag set. However, its possible |
2250 | // that after this flag is set for a regular exception but before we throw, we have an async |
2251 | // exception like a RudeThreadAbort, which will replace the exception |
2252 | // containing the restored stack trace. |
2253 | // |
2254 | // In such a case, we should clear the flag as the throwable representing the |
2255 | // preallocated exception will not have the restored (or any) stack trace. |
2256 | PTR_ThreadExceptionState pCurTES = GetThread()->GetExceptionState(); |
2257 | pCurTES->ResetRaisingForeignException(); |
2258 | |
2259 | return; |
2260 | } |
2261 | |
2262 | LOG((LF_EH, LL_INFO1000, "StackTraceInfo::SaveStackTrace (%p), alloc = %d, replace = %d, skiplast = %d\n" , this, bAllowAllocMem, bReplaceStack, bSkipLastElement)); |
2263 | |
2264 | // if have bSkipLastElement, must also keep the stack |
2265 | _ASSERTE(! bSkipLastElement || ! bReplaceStack); |
2266 | |
2267 | bool fSuccess = false; |
2268 | MethodTable* pMT = ObjectFromHandle(hThrowable)->GetMethodTable(); |
2269 | |
2270 | // Check if the flag indicating foreign exception raise has been setup or not, |
2271 | // and then reset it so that subsequent processing of managed frames proceeds |
2272 | // normally. |
2273 | PTR_ThreadExceptionState pCurTES = GetThread()->GetExceptionState(); |
2274 | BOOL fRaisingForeignException = pCurTES->IsRaisingForeignException(); |
2275 | pCurTES->ResetRaisingForeignException(); |
2276 | |
2277 | if (bAllowAllocMem && m_dFrameCount != 0) |
2278 | { |
2279 | EX_TRY |
2280 | { |
2281 | // Only save stack trace info on exceptions |
2282 | _ASSERTE(IsException(pMT)); // what is the pathway here? |
2283 | if (!IsException(pMT)) |
2284 | { |
2285 | fSuccess = true; |
2286 | } |
2287 | else |
2288 | { |
2289 | // If the stack trace contains DynamicMethodDescs, we need to save the corrosponding |
2290 | // System.Resolver objects in the Exception._dynamicMethods field. Failing to do that |
2291 | // will cause an AV in the runtime when we try to visit those MethodDescs in the |
2292 | // Exception._stackTrace field, because they have been recycled or destroyed. |
2293 | unsigned iNumDynamics = 0; |
2294 | |
2295 | // How many DynamicMethodDescs do we need to keep alive? |
2296 | for (unsigned iElement=0; iElement < m_dFrameCount; iElement++) |
2297 | { |
2298 | MethodDesc *pMethod = m_pStackTrace[iElement].pFunc; |
2299 | _ASSERTE(pMethod); |
2300 | |
2301 | if (pMethod->IsLCGMethod()) |
2302 | { |
2303 | // Increment the number of new dynamic methods we have found |
2304 | iNumDynamics++; |
2305 | } |
2306 | else |
2307 | if (pMethod->GetMethodTable()->Collectible()) |
2308 | { |
2309 | iNumDynamics++; |
2310 | } |
2311 | } |
2312 | |
2313 | struct _gc |
2314 | { |
2315 | StackTraceArray stackTrace; |
2316 | StackTraceArray stackTraceTemp; |
2317 | PTRARRAYREF dynamicMethodsArrayTemp; |
2318 | PTRARRAYREF dynamicMethodsArray; // Object array of Managed Resolvers |
2319 | PTRARRAYREF pOrigDynamicArray; |
2320 | |
2321 | _gc() |
2322 | : stackTrace() |
2323 | , stackTraceTemp() |
2324 | , dynamicMethodsArrayTemp(static_cast<PTRArray *>(NULL)) |
2325 | , dynamicMethodsArray(static_cast<PTRArray *>(NULL)) |
2326 | , pOrigDynamicArray(static_cast<PTRArray *>(NULL)) |
2327 | {} |
2328 | }; |
2329 | |
2330 | _gc gc; |
2331 | GCPROTECT_BEGIN(gc); |
2332 | |
2333 | // If the flag indicating foreign exception raise has been setup, then check |
2334 | // if the exception object has stacktrace or not. If we have an async non-preallocated |
2335 | // exception after setting this flag but before we throw, then the new |
2336 | // exception will not have any stack trace set and thus, we should behave as if |
2337 | // the flag was not setup. |
2338 | if (fRaisingForeignException) |
2339 | { |
2340 | // Get the reference to stack trace and reset our flag if applicable. |
2341 | ((EXCEPTIONREF)ObjectFromHandle(hThrowable))->GetStackTrace(gc.stackTraceTemp); |
2342 | if (gc.stackTraceTemp.Size() == 0) |
2343 | { |
2344 | fRaisingForeignException = FALSE; |
2345 | } |
2346 | } |
2347 | |
2348 | // Replace stack (i.e. build a new stack trace) only if we are not raising a foreign exception. |
2349 | // If we are, then we will continue to extend the existing stack trace. |
2350 | if (bReplaceStack |
2351 | && (!fRaisingForeignException) |
2352 | ) |
2353 | { |
2354 | // Cleanup previous info |
2355 | gc.stackTrace.Append(m_pStackTrace, m_pStackTrace + m_dFrameCount); |
2356 | |
2357 | if (iNumDynamics) |
2358 | { |
2359 | // Adjust the allocation size of the array, if required |
2360 | if (iNumDynamics > m_cDynamicMethodItems) |
2361 | { |
2362 | S_UINT32 cNewSize = S_UINT32(2) * S_UINT32(iNumDynamics); |
2363 | if (cNewSize.IsOverflow()) |
2364 | { |
2365 | // Overflow here implies we cannot allocate memory anymore |
2366 | LOG((LF_EH, LL_INFO100, "StackTraceInfo::SaveStackTrace - Cannot calculate initial resolver array size due to overflow!\n" )); |
2367 | COMPlusThrowOM(); |
2368 | } |
2369 | |
2370 | m_cDynamicMethodItems = cNewSize.Value(); |
2371 | } |
2372 | |
2373 | gc.dynamicMethodsArray = (PTRARRAYREF)AllocateObjectArray(m_cDynamicMethodItems, g_pObjectClass); |
2374 | LOG((LF_EH, LL_INFO100, "StackTraceInfo::SaveStackTrace - allocated dynamic array for first frame of size %lu\n" , |
2375 | m_cDynamicMethodItems)); |
2376 | } |
2377 | |
2378 | m_dCurrentDynamicIndex = 0; |
2379 | } |
2380 | else |
2381 | { |
2382 | // Fetch the stacktrace and the dynamic method array |
2383 | ((EXCEPTIONREF)ObjectFromHandle(hThrowable))->GetStackTrace(gc.stackTrace, &gc.pOrigDynamicArray); |
2384 | |
2385 | if (fRaisingForeignException) |
2386 | { |
2387 | // Just before we append to the stack trace, mark the last recorded frame to be from |
2388 | // the foreign thread so that we can insert an annotation indicating so when building |
2389 | // the stack trace string. |
2390 | size_t numCurrentFrames = gc.stackTrace.Size(); |
2391 | if (numCurrentFrames > 0) |
2392 | { |
2393 | // "numCurrentFrames" can be zero if the user created an EDI using |
2394 | // an unthrown exception. |
2395 | StackTraceElement & refLastElementFromForeignStackTrace = gc.stackTrace[numCurrentFrames - 1]; |
2396 | refLastElementFromForeignStackTrace.fIsLastFrameFromForeignStackTrace = TRUE; |
2397 | } |
2398 | } |
2399 | |
2400 | if (!bSkipLastElement) |
2401 | gc.stackTrace.Append(m_pStackTrace, m_pStackTrace + m_dFrameCount); |
2402 | |
2403 | ////////////////////////////// |
2404 | |
2405 | unsigned cOrigDynamic = 0; // number of objects in the old array |
2406 | if (gc.pOrigDynamicArray != NULL) |
2407 | { |
2408 | cOrigDynamic = gc.pOrigDynamicArray->GetNumComponents(); |
2409 | } |
2410 | else |
2411 | { |
2412 | // Since there is no dynamic method array, reset the corresponding state variables |
2413 | m_dCurrentDynamicIndex = 0; |
2414 | m_cDynamicMethodItems = 0; |
2415 | } |
2416 | |
2417 | if ((gc.pOrigDynamicArray != NULL) |
2418 | || (fRaisingForeignException) |
2419 | ) |
2420 | { |
2421 | // Since we have just restored the dynamic method array as well, |
2422 | // calculate the dynamic array index which would be the total |
2423 | // number of dynamic methods present in the stack trace. |
2424 | // |
2425 | // In addition to the ForeignException scenario, we need to reset these |
2426 | // values incase the exception object in question is being thrown by |
2427 | // multiple threads in parallel and thus, could have potentially different |
2428 | // dynamic method array contents/size as opposed to the current state of |
2429 | // StackTraceInfo. |
2430 | |
2431 | unsigned iStackTraceElements = (unsigned)gc.stackTrace.Size(); |
2432 | m_dCurrentDynamicIndex = 0; |
2433 | for (unsigned iIndex = 0; iIndex < iStackTraceElements; iIndex++) |
2434 | { |
2435 | MethodDesc *pMethod = gc.stackTrace[iIndex].pFunc; |
2436 | if (pMethod) |
2437 | { |
2438 | if ((pMethod->IsLCGMethod()) || (pMethod->GetMethodTable()->Collectible())) |
2439 | { |
2440 | // Increment the number of new dynamic methods we have found |
2441 | m_dCurrentDynamicIndex++; |
2442 | } |
2443 | } |
2444 | } |
2445 | |
2446 | // Total number of elements in the dynamic method array should also be |
2447 | // reset based upon the restored array size. |
2448 | m_cDynamicMethodItems = cOrigDynamic; |
2449 | } |
2450 | |
2451 | // Make the dynamic Array field reference the original array we got from the |
2452 | // Exception object. If, below, we have to add new entries, we will add it to the |
2453 | // array if it is allocated, or else, we will allocate it before doing so. |
2454 | gc.dynamicMethodsArray = gc.pOrigDynamicArray; |
2455 | |
2456 | // Create an object array if we have new dynamic method entries AND |
2457 | // if we are at the (or went past) the current size limit |
2458 | if (iNumDynamics > 0) |
2459 | { |
2460 | // Reallocate the array if we are at the (or went past) the current size limit |
2461 | unsigned cTotalDynamicMethodCount = m_dCurrentDynamicIndex; |
2462 | |
2463 | S_UINT32 cNewSum = S_UINT32(cTotalDynamicMethodCount) + S_UINT32(iNumDynamics); |
2464 | if (cNewSum.IsOverflow()) |
2465 | { |
2466 | // If the current size is already the UINT32 max size, then we |
2467 | // cannot go further. Overflow here implies we cannot allocate memory anymore. |
2468 | LOG((LF_EH, LL_INFO100, "StackTraceInfo::SaveStackTrace - Cannot calculate resolver array size due to overflow!\n" )); |
2469 | COMPlusThrowOM(); |
2470 | } |
2471 | |
2472 | cTotalDynamicMethodCount = cNewSum.Value(); |
2473 | |
2474 | if (cTotalDynamicMethodCount > m_cDynamicMethodItems) |
2475 | { |
2476 | // Double the current limit of the array. |
2477 | S_UINT32 cNewSize = S_UINT32(2) * S_UINT32(cTotalDynamicMethodCount); |
2478 | if (cNewSize.IsOverflow()) |
2479 | { |
2480 | // Overflow here implies that we cannot allocate any more memory |
2481 | LOG((LF_EH, LL_INFO100, "StackTraceInfo::SaveStackTrace - Cannot resize resolver array beyond max size due to overflow!\n" )); |
2482 | COMPlusThrowOM(); |
2483 | } |
2484 | |
2485 | m_cDynamicMethodItems = cNewSize.Value(); |
2486 | gc.dynamicMethodsArray = (PTRARRAYREF)AllocateObjectArray(m_cDynamicMethodItems, |
2487 | g_pObjectClass); |
2488 | |
2489 | _ASSERTE(!(cOrigDynamic && !gc.pOrigDynamicArray)); |
2490 | |
2491 | LOG((LF_EH, LL_INFO100, "StackTraceInfo::SaveStackTrace - resized dynamic array to size %lu\n" , |
2492 | m_cDynamicMethodItems)); |
2493 | |
2494 | // Copy previous entries if there are any, and update iCurDynamic to point |
2495 | // to the following index. |
2496 | if (cOrigDynamic && (gc.pOrigDynamicArray != NULL)) |
2497 | { |
2498 | memmoveGCRefs(gc.dynamicMethodsArray->GetDataPtr(), |
2499 | gc.pOrigDynamicArray->GetDataPtr(), |
2500 | cOrigDynamic * sizeof(Object *)); |
2501 | |
2502 | // m_dCurrentDynamicIndex is already referring to the correct index |
2503 | // at which the next resolver object will be saved |
2504 | } |
2505 | } |
2506 | else |
2507 | { |
2508 | // We are adding objects to the existing array. |
2509 | // |
2510 | // We have new dynamic method entries for which |
2511 | // resolver objects need to be saved. Ensure |
2512 | // that we have the array to store them |
2513 | if (gc.dynamicMethodsArray == NULL) |
2514 | { |
2515 | _ASSERTE(m_cDynamicMethodItems > 0); |
2516 | |
2517 | gc.dynamicMethodsArray = (PTRARRAYREF)AllocateObjectArray(m_cDynamicMethodItems, |
2518 | g_pObjectClass); |
2519 | m_dCurrentDynamicIndex = 0; |
2520 | LOG((LF_EH, LL_INFO100, "StackTraceInfo::SaveStackTrace - allocated dynamic array of size %lu\n" , |
2521 | m_cDynamicMethodItems)); |
2522 | } |
2523 | else |
2524 | { |
2525 | // The array exists for storing resolver objects. |
2526 | // Simply set the index at which the next resolver |
2527 | // will be stored in it. |
2528 | } |
2529 | } |
2530 | } |
2531 | } |
2532 | |
2533 | // Update _dynamicMethods field |
2534 | if (iNumDynamics) |
2535 | { |
2536 | // At this point, we should be having a valid array for storage |
2537 | _ASSERTE(gc.dynamicMethodsArray != NULL); |
2538 | |
2539 | // Assert that we are in valid range of the array in which resolver objects will be saved. |
2540 | // We subtract 1 below since storage will start from m_dCurrentDynamicIndex onwards and not |
2541 | // from (m_dCurrentDynamicIndex + 1). |
2542 | _ASSERTE((m_dCurrentDynamicIndex + iNumDynamics - 1) < gc.dynamicMethodsArray->GetNumComponents()); |
2543 | |
2544 | for (unsigned i=0; i < m_dFrameCount; i++) |
2545 | { |
2546 | MethodDesc *pMethod = m_pStackTrace[i].pFunc; |
2547 | _ASSERTE(pMethod); |
2548 | |
2549 | if (pMethod->IsLCGMethod()) |
2550 | { |
2551 | // We need to append the corresponding System.Resolver for |
2552 | // this DynamicMethodDesc to keep it alive. |
2553 | DynamicMethodDesc *pDMD = (DynamicMethodDesc *) pMethod; |
2554 | OBJECTREF pResolver = pDMD->GetLCGMethodResolver()->GetManagedResolver(); |
2555 | |
2556 | _ASSERTE(pResolver != NULL); |
2557 | |
2558 | // Store Resolver information in the array |
2559 | gc.dynamicMethodsArray->SetAt(m_dCurrentDynamicIndex++, pResolver); |
2560 | } |
2561 | else |
2562 | if (pMethod->GetMethodTable()->Collectible()) |
2563 | { |
2564 | OBJECTREF pLoaderAllocator = pMethod->GetMethodTable()->GetLoaderAllocator()->GetExposedObject(); |
2565 | _ASSERTE(pLoaderAllocator != NULL); |
2566 | gc.dynamicMethodsArray->SetAt (m_dCurrentDynamicIndex++, pLoaderAllocator); |
2567 | } |
2568 | } |
2569 | } |
2570 | |
2571 | ((EXCEPTIONREF)ObjectFromHandle(hThrowable))->SetStackTrace(gc.stackTrace, gc.dynamicMethodsArray); |
2572 | |
2573 | // Update _stackTraceString field. |
2574 | ((EXCEPTIONREF)ObjectFromHandle(hThrowable))->SetStackTraceString(NULL); |
2575 | fSuccess = true; |
2576 | |
2577 | GCPROTECT_END(); // gc |
2578 | } |
2579 | } |
2580 | EX_CATCH |
2581 | { |
2582 | } |
2583 | EX_END_CATCH(SwallowAllExceptions) |
2584 | } |
2585 | |
2586 | ClearStackTrace(); |
2587 | |
2588 | if (!fSuccess) |
2589 | { |
2590 | EX_TRY |
2591 | { |
2592 | _ASSERTE(IsException(pMT)); // what is the pathway here? |
2593 | if (bReplaceStack && IsException(pMT)) |
2594 | ((EXCEPTIONREF)ObjectFromHandle(hThrowable))->ClearStackTraceForThrow(); |
2595 | } |
2596 | EX_CATCH |
2597 | { |
2598 | // Do nothing |
2599 | } |
2600 | EX_END_CATCH(SwallowAllExceptions); |
2601 | } |
2602 | } |
2603 | |
2604 | // Copy a context record, being careful about whether or not the target |
2605 | // is large enough to support CONTEXT_EXTENDED_REGISTERS. |
2606 | // |
2607 | // NOTE: this function can ONLY be used when a filter function will return |
2608 | // EXCEPTION_CONTINUE_EXECUTION. On AMD64, replacing the CONTEXT in any other |
2609 | // situation may break exception unwinding. |
2610 | // |
2611 | // NOTE: this function MUST be used on AMD64. During exception handling, |
2612 | // parts of the CONTEXT struct must not be modified. |
2613 | |
2614 | |
2615 | // High 2 bytes are machine type. Low 2 bytes are register subset. |
2616 | #define CONTEXT_EXTENDED_BIT (CONTEXT_EXTENDED_REGISTERS & 0xffff) |
2617 | |
2618 | VOID |
2619 | ReplaceExceptionContextRecord(CONTEXT *pTarget, CONTEXT *pSource) |
2620 | { |
2621 | LIMITED_METHOD_CONTRACT; |
2622 | |
2623 | _ASSERTE(pTarget); |
2624 | _ASSERTE(pSource); |
2625 | |
2626 | #if defined(_TARGET_X86_) |
2627 | //<TODO> |
2628 | // @TODO IA64: CONTEXT_DEBUG_REGISTERS not defined on IA64, may need updated SDK |
2629 | //</TODO> |
2630 | |
2631 | // Want CONTROL, INTEGER, SEGMENTS. If we have Floating Point, fine. |
2632 | _ASSERTE((pSource->ContextFlags & CONTEXT_FULL) == CONTEXT_FULL); |
2633 | #endif // _TARGET_X86_ |
2634 | |
2635 | #ifdef CONTEXT_EXTENDED_REGISTERS |
2636 | |
2637 | if (pSource->ContextFlags & CONTEXT_EXTENDED_BIT) |
2638 | { |
2639 | if (pTarget->ContextFlags & CONTEXT_EXTENDED_BIT) |
2640 | { // Source and Target have EXTENDED bit set. |
2641 | *pTarget = *pSource; |
2642 | } |
2643 | else |
2644 | { // Source has but Target doesn't have EXTENDED bit set. (Target is shorter than Source.) |
2645 | // Copy non-extended part of the struct, and reset the bit on the Target, as it was. |
2646 | memcpy(pTarget, pSource, offsetof(CONTEXT, ExtendedRegisters)); |
2647 | pTarget->ContextFlags &= ~CONTEXT_EXTENDED_BIT; // Target was short. Reset the extended bit. |
2648 | } |
2649 | } |
2650 | else |
2651 | { // Source does not have EXTENDED bit. Copy only non-extended part of the struct. |
2652 | memcpy(pTarget, pSource, offsetof(CONTEXT, ExtendedRegisters)); |
2653 | } |
2654 | STRESS_LOG3(LF_SYNC, LL_INFO1000, "ReSet thread context EIP = %p ESP = %p EBP = %p\n" , |
2655 | GetIP((CONTEXT*)pTarget), GetSP((CONTEXT*)pTarget), GetFP((CONTEXT*)pTarget)); |
2656 | |
2657 | #else // !CONTEXT_EXTENDED_REGISTERS |
2658 | |
2659 | // Everything that's left |
2660 | *pTarget = *pSource; |
2661 | |
2662 | #endif // !CONTEXT_EXTENDED_REGISTERS |
2663 | } |
2664 | |
2665 | VOID FixupOnRethrow(Thread* pCurThread, EXCEPTION_POINTERS* pExceptionPointers) |
2666 | { |
2667 | WRAPPER_NO_CONTRACT; |
2668 | |
2669 | ThreadExceptionState* pExState = pCurThread->GetExceptionState(); |
2670 | |
2671 | #ifdef FEATURE_INTERPRETER |
2672 | // Abort if we don't have any state from the original exception. |
2673 | if (!pExState->IsExceptionInProgress()) |
2674 | { |
2675 | return; |
2676 | } |
2677 | #endif // FEATURE_INTERPRETER |
2678 | |
2679 | // Don't allow rethrow of a STATUS_STACK_OVERFLOW -- it's a new throw of the COM+ exception. |
2680 | if (pExState->GetExceptionCode() == STATUS_STACK_OVERFLOW) |
2681 | { |
2682 | return; |
2683 | } |
2684 | |
2685 | // For COMPLUS exceptions, we don't need the original context for our rethrow. |
2686 | if (!(pExState->IsComPlusException())) |
2687 | { |
2688 | _ASSERTE(pExState->GetExceptionRecord()); |
2689 | |
2690 | // don't copy parm args as have already supplied them on the throw |
2691 | memcpy((void*)pExceptionPointers->ExceptionRecord, |
2692 | (void*)pExState->GetExceptionRecord(), |
2693 | offsetof(EXCEPTION_RECORD, ExceptionInformation)); |
2694 | |
2695 | // Replacing the exception context breaks unwinding on AMD64. It also breaks exception dispatch on IA64. |
2696 | // The info saved by pExState will be given to exception filters. |
2697 | #ifndef WIN64EXCEPTIONS |
2698 | // Restore original context if available. |
2699 | if (pExState->GetContextRecord()) |
2700 | { |
2701 | ReplaceExceptionContextRecord(pExceptionPointers->ContextRecord, |
2702 | pExState->GetContextRecord()); |
2703 | } |
2704 | #endif // !WIN64EXCEPTIONS |
2705 | } |
2706 | |
2707 | pExState->GetFlags()->SetIsRethrown(); |
2708 | } |
2709 | |
2710 | struct RaiseExceptionFilterParam |
2711 | { |
2712 | BOOL isRethrown; |
2713 | }; |
2714 | |
2715 | LONG RaiseExceptionFilter(EXCEPTION_POINTERS* ep, LPVOID pv) |
2716 | { |
2717 | STATIC_CONTRACT_NOTHROW; |
2718 | STATIC_CONTRACT_GC_NOTRIGGER; |
2719 | STATIC_CONTRACT_MODE_ANY; |
2720 | |
2721 | RaiseExceptionFilterParam *pParam = (RaiseExceptionFilterParam *) pv; |
2722 | |
2723 | if (1 == pParam->isRethrown) |
2724 | { |
2725 | // need to reset the EH info back to the original thrown exception |
2726 | FixupOnRethrow(GetThread(), ep); |
2727 | #ifdef WIN64EXCEPTIONS |
2728 | // only do this once |
2729 | pParam->isRethrown++; |
2730 | #endif // WIN64EXCEPTIONS |
2731 | } |
2732 | else |
2733 | { |
2734 | CONSISTENCY_CHECK((2 == pParam->isRethrown) || (0 == pParam->isRethrown)); |
2735 | } |
2736 | |
2737 | return EXCEPTION_CONTINUE_SEARCH; |
2738 | } |
2739 | |
2740 | //========================================================================== |
2741 | // Throw an object. |
2742 | //========================================================================== |
2743 | VOID DECLSPEC_NORETURN RaiseTheException(OBJECTREF throwable, BOOL rethrow |
2744 | #ifdef FEATURE_CORRUPTING_EXCEPTIONS |
2745 | , CorruptionSeverity severity |
2746 | #endif // FEATURE_CORRUPTING_EXCEPTIONS |
2747 | ) |
2748 | { |
2749 | STATIC_CONTRACT_THROWS; |
2750 | STATIC_CONTRACT_GC_TRIGGERS; |
2751 | STATIC_CONTRACT_MODE_COOPERATIVE; |
2752 | |
2753 | LOG((LF_EH, LL_INFO100, "RealCOMPlusThrow throwing %s\n" , |
2754 | throwable->GetMethodTable()->GetDebugClassName())); |
2755 | |
2756 | if (throwable == NULL) |
2757 | { |
2758 | _ASSERTE(!"RealCOMPlusThrow(OBJECTREF) called with NULL argument. Somebody forgot to post an exception!" ); |
2759 | EEPOLICY_HANDLE_FATAL_ERROR(COR_E_EXECUTIONENGINE); |
2760 | } |
2761 | |
2762 | if (g_CLRPolicyRequested && |
2763 | throwable->GetMethodTable() == g_pOutOfMemoryExceptionClass) |
2764 | { |
2765 | // We depends on UNINSTALL_UNWIND_AND_CONTINUE_HANDLER to handle out of memory escalation. |
2766 | // We should throw c++ exception instead. |
2767 | ThrowOutOfMemory(); |
2768 | } |
2769 | #ifdef FEATURE_STACK_PROBE |
2770 | else if (throwable == CLRException::GetPreallocatedStackOverflowException()) |
2771 | { |
2772 | ThrowStackOverflow(); |
2773 | } |
2774 | #else |
2775 | _ASSERTE(throwable != CLRException::GetPreallocatedStackOverflowException()); |
2776 | #endif |
2777 | |
2778 | #ifdef FEATURE_CORRUPTING_EXCEPTIONS |
2779 | if (!g_pConfig->LegacyCorruptedStateExceptionsPolicy()) |
2780 | { |
2781 | // This is Scenario 3 described in clrex.h around the definition of SET_CE_RETHROW_FLAG_FOR_EX_CATCH macro. |
2782 | // |
2783 | // We are here because the VM is attempting to throw a managed exception. It is posssible this exception |
2784 | // may not be seen by CLR's exception handler for managed code (e.g. there maybe an EX_CATCH up the stack |
2785 | // that will swallow or rethrow this exception). In the following scenario: |
2786 | // |
2787 | // [VM1 - RethrowCSE] -> [VM2 - RethrowCSE] -> [VM3 - RethrowCSE] -> <managed code> |
2788 | // |
2789 | // When managed code throws a CSE (e.g. TargetInvocationException flagged as CSE), [VM3] will rethrow it and we will |
2790 | // enter EX_CATCH in VM2 which is supposed to rethrow it as well. Two things can happen: |
2791 | // |
2792 | // 1) The implementation of EX_CATCH in VM2 throws a new managed exception *before* rethrow policy is applied and control |
2793 | // will reach EX_CATCH in VM1, OR |
2794 | // |
2795 | // 2) EX_CATCH in VM2 swallows the exception, comes out of the catch block and later throws a new managed exception that |
2796 | // will be caught by EX_CATCH in VM1. |
2797 | // |
2798 | // In either of the cases, rethrow in VM1 should be on the basis of the new managed exception's corruption severity. |
2799 | // |
2800 | // To support this scenario, we set corruption severity of the managed exception VM is throwing. If its a rethrow, |
2801 | // it implies we are rethrowing the last exception that was seen by CLR's managed code exception handler. In such a case, |
2802 | // we will copy over the corruption severity of that exception. |
2803 | |
2804 | // If throwable indicates corrupted state, forcibly set the severity. |
2805 | if (CEHelper::IsProcessCorruptedStateException(throwable)) |
2806 | { |
2807 | severity = ProcessCorrupting; |
2808 | } |
2809 | |
2810 | // No one should have passed us an invalid severity. |
2811 | _ASSERTE(severity > NotSet); |
2812 | |
2813 | if (severity == NotSet) |
2814 | { |
2815 | severity = NotCorrupting; |
2816 | } |
2817 | |
2818 | // Update the corruption severity of the exception being thrown by the VM. |
2819 | GetThread()->GetExceptionState()->SetLastActiveExceptionCorruptionSeverity(severity); |
2820 | |
2821 | // Exception's corruption severity should be reused in reraise if this exception leaks out from the VM |
2822 | // into managed code |
2823 | CEHelper::MarkLastActiveExceptionCorruptionSeverityForReraiseReuse(); |
2824 | |
2825 | LOG((LF_EH, LL_INFO100, "RaiseTheException - Set VM thrown managed exception severity to %d.\n" , severity)); |
2826 | } |
2827 | |
2828 | #endif // FEATURE_CORRUPTING_EXCEPTIONS |
2829 | |
2830 | RaiseTheExceptionInternalOnly(throwable,rethrow); |
2831 | } |
2832 | |
2833 | HRESULT GetHRFromThrowable(OBJECTREF throwable) |
2834 | { |
2835 | STATIC_CONTRACT_THROWS; |
2836 | STATIC_CONTRACT_GC_TRIGGERS; |
2837 | STATIC_CONTRACT_MODE_ANY; |
2838 | |
2839 | HRESULT hr = E_FAIL; |
2840 | MethodTable *pMT = throwable->GetMethodTable(); |
2841 | |
2842 | // Only Exception objects have a HResult field |
2843 | // So don't fetch the field unless we have an exception |
2844 | |
2845 | _ASSERTE(IsException(pMT)); // what is the pathway here? |
2846 | if (IsException(pMT)) |
2847 | { |
2848 | hr = ((EXCEPTIONREF)throwable)->GetHResult(); |
2849 | } |
2850 | |
2851 | return hr; |
2852 | } |
2853 | |
2854 | |
2855 | VOID DECLSPEC_NORETURN RaiseTheExceptionInternalOnly(OBJECTREF throwable, BOOL rethrow, BOOL fForStackOverflow) |
2856 | { |
2857 | STATIC_CONTRACT_THROWS; |
2858 | STATIC_CONTRACT_GC_TRIGGERS; |
2859 | STATIC_CONTRACT_MODE_COOPERATIVE; |
2860 | |
2861 | STRESS_LOG3(LF_EH, LL_INFO100, "******* MANAGED EXCEPTION THROWN: Object thrown: %p MT %pT rethrow %d\n" , |
2862 | OBJECTREFToObject(throwable), (throwable!=0)?throwable->GetMethodTable():0, rethrow); |
2863 | |
2864 | #ifdef STRESS_LOG |
2865 | // Any object could have been thrown, but System.Exception objects have useful information for the stress log |
2866 | if (!NingenEnabled() && throwable == CLRException::GetPreallocatedStackOverflowException()) |
2867 | { |
2868 | // if are handling an SO, don't try to get all that other goop. It isn't there anyway, |
2869 | // and it could cause us to take another SO. |
2870 | STRESS_LOG1(LF_EH, LL_INFO100, "Exception HRESULT = 0x%x \n" , COR_E_STACKOVERFLOW); |
2871 | } |
2872 | else if (throwable != 0) |
2873 | { |
2874 | _ASSERTE(IsException(throwable->GetMethodTable())); |
2875 | |
2876 | int hr = ((EXCEPTIONREF)throwable)->GetHResult(); |
2877 | STRINGREF message = ((EXCEPTIONREF)throwable)->GetMessage(); |
2878 | OBJECTREF innerEH = ((EXCEPTIONREF)throwable)->GetInnerException(); |
2879 | |
2880 | STRESS_LOG4(LF_EH, LL_INFO100, "Exception HRESULT = 0x%x Message String 0x%p (db will display) InnerException %p MT %pT\n" , |
2881 | hr, OBJECTREFToObject(message), OBJECTREFToObject(innerEH), (innerEH!=0)?innerEH->GetMethodTable():0); |
2882 | } |
2883 | #endif |
2884 | |
2885 | struct Param : RaiseExceptionFilterParam |
2886 | { |
2887 | OBJECTREF throwable; |
2888 | BOOL fForStackOverflow; |
2889 | ULONG_PTR exceptionArgs[INSTANCE_TAGGED_SEH_PARAM_ARRAY_SIZE]; |
2890 | Thread *pThread; |
2891 | ThreadExceptionState* pExState; |
2892 | } param; |
2893 | param.isRethrown = rethrow ? 1 : 0; // normalize because we use it as a count in RaiseExceptionFilter |
2894 | param.throwable = throwable; |
2895 | param.fForStackOverflow = fForStackOverflow; |
2896 | param.pThread = GetThread(); |
2897 | |
2898 | _ASSERTE(param.pThread); |
2899 | param.pExState = param.pThread->GetExceptionState(); |
2900 | |
2901 | if (param.pThread->IsRudeAbortInitiated()) |
2902 | { |
2903 | // Nobody should be able to swallow rude thread abort. |
2904 | param.throwable = CLRException::GetPreallocatedRudeThreadAbortException(); |
2905 | } |
2906 | |
2907 | #if 0 |
2908 | // TODO: enable this after we change RealCOMPlusThrow |
2909 | #ifdef _DEBUG |
2910 | // If ThreadAbort exception is thrown, the thread should be marked with AbortRequest. |
2911 | // If not, we may see unhandled exception. |
2912 | if (param.throwable->GetMethodTable() == g_pThreadAbortExceptionClass) |
2913 | { |
2914 | _ASSERTE(GetThread()->IsAbortRequested() |
2915 | #ifdef _TARGET_X86_ |
2916 | || |
2917 | GetFirstCOMPlusSEHRecord(this) == EXCEPTION_CHAIN_END |
2918 | #endif |
2919 | ); |
2920 | } |
2921 | #endif |
2922 | #endif |
2923 | |
2924 | // raise |
2925 | PAL_TRY(Param *, pParam, ¶m) |
2926 | { |
2927 | //_ASSERTE(! pParam->isRethrown || pParam->pExState->m_pExceptionRecord); |
2928 | ULONG_PTR *args = NULL; |
2929 | ULONG argCount = 0; |
2930 | ULONG flags = 0; |
2931 | ULONG code = 0; |
2932 | |
2933 | // Always save the current object in the handle so on rethrow we can reuse it. This is important as it |
2934 | // contains stack trace info. |
2935 | // |
2936 | // Note: we use SafeSetLastThrownObject, which will try to set the throwable and if there are any problems, |
2937 | // it will set the throwable to something appropiate (like OOM exception) and return the new |
2938 | // exception. Thus, the user's exception object can be replaced here. |
2939 | pParam->throwable = NingenEnabled() ? NULL : pParam->pThread->SafeSetLastThrownObject(pParam->throwable); |
2940 | |
2941 | if (!pParam->isRethrown || |
2942 | #ifdef FEATURE_INTERPRETER |
2943 | !pParam->pExState->IsExceptionInProgress() || |
2944 | #endif // FEATURE_INTERPRETER |
2945 | pParam->pExState->IsComPlusException() || |
2946 | (pParam->pExState->GetExceptionCode() == STATUS_STACK_OVERFLOW)) |
2947 | { |
2948 | ULONG_PTR hr = NingenEnabled() ? E_FAIL : GetHRFromThrowable(pParam->throwable); |
2949 | |
2950 | args = pParam->exceptionArgs; |
2951 | argCount = MarkAsThrownByUs(args, hr); |
2952 | flags = EXCEPTION_NONCONTINUABLE; |
2953 | code = EXCEPTION_COMPLUS; |
2954 | } |
2955 | else |
2956 | { |
2957 | // Exception code should be consistent. |
2958 | _ASSERTE((DWORD)(pParam->pExState->GetExceptionRecord()->ExceptionCode) == pParam->pExState->GetExceptionCode()); |
2959 | |
2960 | args = pParam->pExState->GetExceptionRecord()->ExceptionInformation; |
2961 | argCount = pParam->pExState->GetExceptionRecord()->NumberParameters; |
2962 | flags = pParam->pExState->GetExceptionRecord()->ExceptionFlags; |
2963 | code = pParam->pExState->GetExceptionRecord()->ExceptionCode; |
2964 | } |
2965 | |
2966 | if (pParam->pThread->IsAbortInitiated () && IsExceptionOfType(kThreadAbortException,&pParam->throwable)) |
2967 | { |
2968 | pParam->pThread->ResetPreparingAbort(); |
2969 | |
2970 | if (pParam->pThread->GetFrame() == FRAME_TOP) |
2971 | { |
2972 | // There is no more managed code on stack. |
2973 | pParam->pThread->EEResetAbort(Thread::TAR_ALL); |
2974 | } |
2975 | } |
2976 | |
2977 | // Can't access the exception object when are in pre-emptive, so find out before |
2978 | // if its an SO. |
2979 | BOOL fIsStackOverflow = IsExceptionOfType(kStackOverflowException, &pParam->throwable); |
2980 | |
2981 | if (fIsStackOverflow || pParam->fForStackOverflow) |
2982 | { |
2983 | // Don't probe if we're already handling an SO. Just throw the exception. |
2984 | RaiseException(code, flags, argCount, args); |
2985 | } |
2986 | |
2987 | // Probe for sufficient stack. |
2988 | PUSH_STACK_PROBE_FOR_THROW(pParam->pThread); |
2989 | |
2990 | #ifndef STACK_GUARDS_DEBUG |
2991 | // This needs to be both here and inside the handler below |
2992 | // enable preemptive mode before call into OS |
2993 | GCX_PREEMP_NO_DTOR(); |
2994 | |
2995 | // In non-debug, we can just raise the exception once we've probed. |
2996 | RaiseException(code, flags, argCount, args); |
2997 | |
2998 | #else |
2999 | // In a debug build, we need to unwind our probe structure off the stack. |
3000 | BaseStackGuard *pThrowGuard = NULL; |
3001 | // Stach away the address of the guard we just pushed above in PUSH_STACK_PROBE_FOR_THROW |
3002 | SAVE_ADDRESS_OF_STACK_PROBE_FOR_THROW(pThrowGuard); |
3003 | |
3004 | // Add the stack guard reference to the structure below so that it can be accessed within |
3005 | // PAL_TRY as well |
3006 | struct ParamInner |
3007 | { |
3008 | ULONG code; |
3009 | ULONG flags; |
3010 | ULONG argCount; |
3011 | ULONG_PTR *args; |
3012 | BaseStackGuard *pGuard; |
3013 | } param; |
3014 | param.code = code; |
3015 | param.flags = flags; |
3016 | param.argCount = argCount; |
3017 | param.args = args; |
3018 | param.pGuard = pThrowGuard; |
3019 | |
3020 | PAL_TRY(ParamInner *, pParam, ¶m) |
3021 | { |
3022 | // enable preemptive mode before call into OS |
3023 | GCX_PREEMP_NO_DTOR(); |
3024 | |
3025 | RaiseException(pParam->code, pParam->flags, pParam->argCount, pParam->args); |
3026 | |
3027 | // We never return from RaiseException, so shouldn't have to call SetNoException. |
3028 | // However, in the debugger we can, and if we don't call SetNoException we get |
3029 | // a short-circuit return assert. |
3030 | RESET_EXCEPTION_FROM_STACK_PROBE_FOR_THROW(pParam->pGuard); |
3031 | } |
3032 | PAL_FINALLY |
3033 | { |
3034 | // pop the guard that we pushed above in PUSH_STACK_PROBE_FOR_THROW |
3035 | POP_STACK_PROBE_FOR_THROW(pThrowGuard); |
3036 | } |
3037 | PAL_ENDTRY |
3038 | #endif |
3039 | } |
3040 | PAL_EXCEPT_FILTER (RaiseExceptionFilter) |
3041 | { |
3042 | } |
3043 | PAL_ENDTRY |
3044 | _ASSERTE(!"Cannot continue after COM+ exception" ); // Debugger can bring you here. |
3045 | // For example, |
3046 | // Debugger breaks in due to second chance exception (unhandled) |
3047 | // User hits 'g' |
3048 | // Then debugger can bring us here. |
3049 | EEPOLICY_HANDLE_FATAL_ERROR(COR_E_EXECUTIONENGINE); |
3050 | } |
3051 | |
3052 | |
3053 | // INSTALL_COMPLUS_EXCEPTION_HANDLER has a filter, so must put the call in a separate fcn |
3054 | static VOID DECLSPEC_NORETURN RealCOMPlusThrowWorker(OBJECTREF throwable, BOOL rethrow |
3055 | #ifdef FEATURE_CORRUPTING_EXCEPTIONS |
3056 | , CorruptionSeverity severity |
3057 | #endif // FEATURE_CORRUPTING_EXCEPTIONS |
3058 | ) { |
3059 | STATIC_CONTRACT_THROWS; |
3060 | STATIC_CONTRACT_GC_TRIGGERS; |
3061 | STATIC_CONTRACT_MODE_ANY; |
3062 | |
3063 | // RaiseTheException will throw C++ OOM and SO, so that our escalation policy can kick in. |
3064 | // Unfortunately, COMPlusFrameHandler installed here, will try to create managed exception object. |
3065 | // We may hit a recursion. |
3066 | |
3067 | if (g_CLRPolicyRequested && |
3068 | throwable->GetMethodTable() == g_pOutOfMemoryExceptionClass) |
3069 | { |
3070 | // We depends on UNINSTALL_UNWIND_AND_CONTINUE_HANDLER to handle out of memory escalation. |
3071 | // We should throw c++ exception instead. |
3072 | ThrowOutOfMemory(); |
3073 | } |
3074 | #ifdef FEATURE_STACK_PROBE |
3075 | else if (throwable == CLRException::GetPreallocatedStackOverflowException()) |
3076 | { |
3077 | ThrowStackOverflow(); |
3078 | } |
3079 | #else |
3080 | _ASSERTE(throwable != CLRException::GetPreallocatedStackOverflowException()); |
3081 | #endif |
3082 | |
3083 | // TODO: Do we need to install COMPlusFrameHandler here? |
3084 | INSTALL_COMPLUS_EXCEPTION_HANDLER(); |
3085 | RaiseTheException(throwable, rethrow |
3086 | #ifdef FEATURE_CORRUPTING_EXCEPTIONS |
3087 | , severity |
3088 | #endif // FEATURE_CORRUPTING_EXCEPTIONS |
3089 | ); |
3090 | UNINSTALL_COMPLUS_EXCEPTION_HANDLER(); |
3091 | } |
3092 | |
3093 | |
3094 | VOID DECLSPEC_NORETURN RealCOMPlusThrow(OBJECTREF throwable, BOOL rethrow |
3095 | #ifdef FEATURE_CORRUPTING_EXCEPTIONS |
3096 | , CorruptionSeverity severity |
3097 | #endif // FEATURE_CORRUPTING_EXCEPTIONS |
3098 | ) { |
3099 | STATIC_CONTRACT_THROWS; |
3100 | STATIC_CONTRACT_GC_TRIGGERS; |
3101 | STATIC_CONTRACT_MODE_ANY; |
3102 | GCPROTECT_BEGIN(throwable); |
3103 | |
3104 | _ASSERTE(IsException(throwable->GetMethodTable())); |
3105 | |
3106 | // This may look a bit odd, but there is an explaination. The rethrow boolean |
3107 | // means that an actual RaiseException(EXCEPTION_COMPLUS,...) is being re-thrown, |
3108 | // and that the exception context saved on the Thread object should replace |
3109 | // the exception context from the upcoming RaiseException(). There is logic |
3110 | // in the stack trace code to preserve MOST of the stack trace, but to drop the |
3111 | // last element of the stack trace (has to do with having the address of the rethrow |
3112 | // instead of the address of the original call in the stack trace. That is |
3113 | // controversial itself, but we won't get into that here.) |
3114 | // However, if this is not re-raising that original exception, but rather a new |
3115 | // os exception for what may be an existing exception object, it is generally |
3116 | // a good thing to preserve the stack trace. |
3117 | if (!rethrow) |
3118 | { |
3119 | ExceptionPreserveStackTrace(throwable); |
3120 | } |
3121 | |
3122 | RealCOMPlusThrowWorker(throwable, rethrow |
3123 | #ifdef FEATURE_CORRUPTING_EXCEPTIONS |
3124 | , severity |
3125 | #endif // FEATURE_CORRUPTING_EXCEPTIONS |
3126 | ); |
3127 | |
3128 | GCPROTECT_END(); |
3129 | } |
3130 | |
3131 | VOID DECLSPEC_NORETURN RealCOMPlusThrow(OBJECTREF throwable |
3132 | #ifdef FEATURE_CORRUPTING_EXCEPTIONS |
3133 | , CorruptionSeverity severity |
3134 | #endif // FEATURE_CORRUPTING_EXCEPTIONS |
3135 | ) |
3136 | { |
3137 | CONTRACTL |
3138 | { |
3139 | THROWS; |
3140 | GC_TRIGGERS; |
3141 | MODE_COOPERATIVE; |
3142 | } |
3143 | CONTRACTL_END; |
3144 | |
3145 | RealCOMPlusThrow(throwable, FALSE |
3146 | #ifdef FEATURE_CORRUPTING_EXCEPTIONS |
3147 | , severity |
3148 | #endif // FEATURE_CORRUPTING_EXCEPTIONS |
3149 | ); |
3150 | } |
3151 | |
3152 | // this function finds the managed callback to get a resource |
3153 | // string from the then current local domain and calls it |
3154 | // this could be a lot of work |
3155 | STRINGREF GetResourceStringFromManaged(STRINGREF key) |
3156 | { |
3157 | CONTRACTL |
3158 | { |
3159 | THROWS; |
3160 | GC_TRIGGERS; |
3161 | MODE_COOPERATIVE; |
3162 | PRECONDITION(key != NULL); |
3163 | } |
3164 | CONTRACTL_END; |
3165 | |
3166 | struct xx { |
3167 | STRINGREF key; |
3168 | STRINGREF ret; |
3169 | } gc; |
3170 | |
3171 | gc.key = key; |
3172 | gc.ret = NULL; |
3173 | |
3174 | // The standard probe isn't good enough here. It's possible that we only have ~14 pages of stack |
3175 | // left. By the time we transition to the default domain and start fetching this resource string, |
3176 | // another 12 page probe could fail. |
3177 | // This failing probe would cause us to unload the default appdomain, which would cause us |
3178 | // to take down the process. |
3179 | |
3180 | // Instead, let's probe for a lots more stack to make sure that doesn' happen. |
3181 | |
3182 | // We need to have enough stack to survive 2 more probes... the original entrypoint back |
3183 | // into mscorwks after we go into managed code, and a "large" probe that protects the GC |
3184 | |
3185 | INTERIOR_STACK_PROBE_FOR(GetThread(), DEFAULT_ENTRY_PROBE_AMOUNT * 2); |
3186 | GCPROTECT_BEGIN(gc); |
3187 | |
3188 | MethodDescCallSite getResourceStringLocal(METHOD__ENVIRONMENT__GET_RESOURCE_STRING_LOCAL); |
3189 | |
3190 | // Call Environment::GetResourceStringLocal(String name). Returns String value (or maybe null) |
3191 | |
3192 | ENTER_DOMAIN_PTR(SystemDomain::System()->DefaultDomain(),ADV_DEFAULTAD); |
3193 | |
3194 | // Don't need to GCPROTECT pArgs, since it's not used after the function call. |
3195 | |
3196 | ARG_SLOT pArgs[1] = { ObjToArgSlot(gc.key) }; |
3197 | gc.ret = getResourceStringLocal.Call_RetSTRINGREF(pArgs); |
3198 | |
3199 | END_DOMAIN_TRANSITION; |
3200 | |
3201 | GCPROTECT_END(); |
3202 | |
3203 | END_INTERIOR_STACK_PROBE; |
3204 | |
3205 | |
3206 | return gc.ret; |
3207 | } |
3208 | |
3209 | // This function does poentially a LOT of work (loading possibly 50 classes). |
3210 | // The return value is an un-GC-protected string ref, or possibly NULL. |
3211 | void ResMgrGetString(LPCWSTR wszResourceName, STRINGREF * ppMessage) |
3212 | { |
3213 | CONTRACTL |
3214 | { |
3215 | THROWS; |
3216 | GC_TRIGGERS; |
3217 | MODE_COOPERATIVE; |
3218 | } |
3219 | CONTRACTL_END; |
3220 | |
3221 | _ASSERTE(ppMessage != NULL); |
3222 | |
3223 | if (wszResourceName == NULL || *wszResourceName == W('\0')) |
3224 | { |
3225 | ppMessage = NULL; |
3226 | return; |
3227 | } |
3228 | |
3229 | // this function never looks at name again after |
3230 | // calling the helper so no need to GCPROTECT it |
3231 | STRINGREF name = StringObject::NewString(wszResourceName); |
3232 | |
3233 | if (wszResourceName != NULL) |
3234 | { |
3235 | STRINGREF value = GetResourceStringFromManaged(name); |
3236 | |
3237 | _ASSERTE(value!=NULL || !"Resource string lookup failed - possible misspelling or .resources missing or out of date?" ); |
3238 | *ppMessage = value; |
3239 | } |
3240 | } |
3241 | |
3242 | // GetResourceFromDefault |
3243 | // transition to the default domain and get a resource there |
3244 | FCIMPL1(Object*, GetResourceFromDefault, StringObject* keyUnsafe) |
3245 | { |
3246 | FCALL_CONTRACT; |
3247 | |
3248 | STRINGREF ret = NULL; |
3249 | STRINGREF key = (STRINGREF)keyUnsafe; |
3250 | |
3251 | HELPER_METHOD_FRAME_BEGIN_RET_2(ret, key); |
3252 | |
3253 | ret = GetResourceStringFromManaged(key); |
3254 | |
3255 | HELPER_METHOD_FRAME_END(); |
3256 | |
3257 | return OBJECTREFToObject(ret); |
3258 | } |
3259 | FCIMPLEND |
3260 | |
3261 | void FreeExceptionData(ExceptionData *pedata) |
3262 | { |
3263 | CONTRACTL |
3264 | { |
3265 | NOTHROW; |
3266 | GC_TRIGGERS; |
3267 | SO_TOLERANT; |
3268 | } |
3269 | CONTRACTL_END; |
3270 | |
3271 | _ASSERTE(pedata != NULL); |
3272 | |
3273 | // <TODO>@NICE: At one point, we had the comment: |
3274 | // (DM) Remove this when shutdown works better.</TODO> |
3275 | // This test may no longer be necessary. Remove at own peril. |
3276 | Thread *pThread = GetThread(); |
3277 | if (!pThread) |
3278 | return; |
3279 | |
3280 | if (pedata->bstrSource) |
3281 | SysFreeString(pedata->bstrSource); |
3282 | if (pedata->bstrDescription) |
3283 | SysFreeString(pedata->bstrDescription); |
3284 | if (pedata->bstrHelpFile) |
3285 | SysFreeString(pedata->bstrHelpFile); |
3286 | #ifdef FEATURE_COMINTEROP |
3287 | if (pedata->bstrRestrictedError) |
3288 | SysFreeString(pedata->bstrRestrictedError); |
3289 | if (pedata->bstrReference) |
3290 | SysFreeString(pedata->bstrReference); |
3291 | if (pedata->bstrCapabilitySid) |
3292 | SysFreeString(pedata->bstrCapabilitySid); |
3293 | if (pedata->pRestrictedErrorInfo) |
3294 | { |
3295 | ULONG cbRef = SafeRelease(pedata->pRestrictedErrorInfo); |
3296 | LogInteropRelease(pedata->pRestrictedErrorInfo, cbRef, "IRestrictedErrorInfo" ); |
3297 | } |
3298 | #endif // FEATURE_COMINTEROP |
3299 | } |
3300 | |
3301 | void GetExceptionForHR(HRESULT hr, IErrorInfo* pErrInfo, bool fUseCOMException, OBJECTREF* pProtectedThrowable, IRestrictedErrorInfo *pResErrorInfo, BOOL bHasLangRestrictedErrInfo) |
3302 | { |
3303 | CONTRACTL |
3304 | { |
3305 | THROWS; |
3306 | GC_TRIGGERS; |
3307 | MODE_COOPERATIVE; |
3308 | PRECONDITION(IsProtectedByGCFrame(pProtectedThrowable)); |
3309 | } |
3310 | CONTRACTL_END; |
3311 | |
3312 | // Initialize |
3313 | *pProtectedThrowable = NULL; |
3314 | |
3315 | #if defined(FEATURE_COMINTEROP) && !defined(CROSSGEN_COMPILE) |
3316 | if (pErrInfo != NULL) |
3317 | { |
3318 | // If this represents a managed object... |
3319 | // ...then get the managed exception object and also check if it is a __ComObject... |
3320 | if (IsManagedObject(pErrInfo)) |
3321 | { |
3322 | GetObjectRefFromComIP(pProtectedThrowable, pErrInfo); |
3323 | if ((*pProtectedThrowable) != NULL) |
3324 | { |
3325 | // ...if it is, then we'll just default to an exception based on the IErrorInfo. |
3326 | if ((*pProtectedThrowable)->GetMethodTable()->IsComObjectType()) |
3327 | { |
3328 | (*pProtectedThrowable) = NULL; |
3329 | } |
3330 | else |
3331 | { |
3332 | // We have created an exception. Release the IErrorInfo |
3333 | ULONG cbRef = SafeRelease(pErrInfo); |
3334 | LogInteropRelease(pErrInfo, cbRef, "IErrorInfo release" ); |
3335 | return; |
3336 | } |
3337 | } |
3338 | } |
3339 | |
3340 | // If we got here and we don't have an exception object, we have a native IErrorInfo or |
3341 | // a managed __ComObject based IErrorInfo, so we'll just create an exception based on |
3342 | // the native IErrorInfo. |
3343 | if ((*pProtectedThrowable) == NULL) |
3344 | { |
3345 | EECOMException ex(hr, pErrInfo, fUseCOMException, pResErrorInfo, bHasLangRestrictedErrInfo COMMA_INDEBUG(FALSE)); |
3346 | (*pProtectedThrowable) = ex.GetThrowable(); |
3347 | } |
3348 | } |
3349 | #endif // defined(FEATURE_COMINTEROP) && !defined(CROSSGEN_COMPILE) |
3350 | |
3351 | // If we made it here and we don't have an exception object, we didn't have a valid IErrorInfo |
3352 | // so we'll create an exception based solely on the hresult. |
3353 | if ((*pProtectedThrowable) == NULL) |
3354 | { |
3355 | EEMessageException ex(hr, fUseCOMException); |
3356 | (*pProtectedThrowable) = ex.GetThrowable(); |
3357 | } |
3358 | } |
3359 | |
3360 | void GetExceptionForHR(HRESULT hr, IErrorInfo* pErrInfo, OBJECTREF* pProtectedThrowable) |
3361 | { |
3362 | WRAPPER_NO_CONTRACT; |
3363 | |
3364 | GetExceptionForHR(hr, pErrInfo, true, pProtectedThrowable); |
3365 | } |
3366 | |
3367 | void GetExceptionForHR(HRESULT hr, OBJECTREF* pProtectedThrowable) |
3368 | { |
3369 | CONTRACTL |
3370 | { |
3371 | THROWS; |
3372 | GC_TRIGGERS; // because of IErrorInfo |
3373 | MODE_ANY; |
3374 | } |
3375 | CONTRACTL_END; |
3376 | |
3377 | // Get an IErrorInfo if one is available. |
3378 | IErrorInfo *pErrInfo = NULL; |
3379 | #ifndef CROSSGEN_COMPILE |
3380 | if (SafeGetErrorInfo(&pErrInfo) != S_OK) |
3381 | pErrInfo = NULL; |
3382 | #endif |
3383 | |
3384 | GetExceptionForHR(hr, pErrInfo, true, pProtectedThrowable); |
3385 | } |
3386 | |
3387 | |
3388 | // |
3389 | // Maps a Win32 fault to a COM+ Exception enumeration code |
3390 | // |
3391 | DWORD MapWin32FaultToCOMPlusException(EXCEPTION_RECORD *pExceptionRecord) |
3392 | { |
3393 | WRAPPER_NO_CONTRACT; |
3394 | |
3395 | switch (pExceptionRecord->ExceptionCode) |
3396 | { |
3397 | case STATUS_FLOAT_INEXACT_RESULT: |
3398 | case STATUS_FLOAT_INVALID_OPERATION: |
3399 | case STATUS_FLOAT_STACK_CHECK: |
3400 | case STATUS_FLOAT_UNDERFLOW: |
3401 | return (DWORD) kArithmeticException; |
3402 | case STATUS_FLOAT_OVERFLOW: |
3403 | case STATUS_INTEGER_OVERFLOW: |
3404 | return (DWORD) kOverflowException; |
3405 | |
3406 | case STATUS_FLOAT_DIVIDE_BY_ZERO: |
3407 | case STATUS_INTEGER_DIVIDE_BY_ZERO: |
3408 | return (DWORD) kDivideByZeroException; |
3409 | |
3410 | case STATUS_FLOAT_DENORMAL_OPERAND: |
3411 | return (DWORD) kFormatException; |
3412 | |
3413 | case STATUS_ACCESS_VIOLATION: |
3414 | { |
3415 | // We have a config key, InsecurelyTreatAVsAsNullReference, that ensures we always translate to |
3416 | // NullReferenceException instead of doing the new AV translation logic. |
3417 | if ((g_pConfig != NULL) && !g_pConfig->LegacyNullReferenceExceptionPolicy()) |
3418 | { |
3419 | #if defined(FEATURE_HIJACK) && !defined(PLATFORM_UNIX) |
3420 | // If we got the exception on a redirect function it means the original exception happened in managed code: |
3421 | if (Thread::IsAddrOfRedirectFunc(pExceptionRecord->ExceptionAddress)) |
3422 | return (DWORD) kNullReferenceException; |
3423 | |
3424 | if (pExceptionRecord->ExceptionAddress == (LPVOID)GetEEFuncEntryPoint(THROW_CONTROL_FOR_THREAD_FUNCTION)) |
3425 | { |
3426 | return (DWORD) kNullReferenceException; |
3427 | } |
3428 | #endif // FEATURE_HIJACK && !PLATFORM_UNIX |
3429 | |
3430 | // If the IP of the AV is not in managed code, then its an AccessViolationException. |
3431 | if (!ExecutionManager::IsManagedCode((PCODE)pExceptionRecord->ExceptionAddress)) |
3432 | { |
3433 | return (DWORD) kAccessViolationException; |
3434 | } |
3435 | |
3436 | // If the address accessed is above 64k (Windows) or page size (PAL), then its an AccessViolationException. |
3437 | // Note: Win9x is a little different... it never gives you the proper address of the read or write that caused |
3438 | // the fault. It always gives -1, so we can't use it as part of the decision... just give |
3439 | // NullReferenceException instead. |
3440 | if (pExceptionRecord->ExceptionInformation[1] >= NULL_AREA_SIZE) |
3441 | { |
3442 | return (DWORD) kAccessViolationException; |
3443 | } |
3444 | } |
3445 | |
3446 | return (DWORD) kNullReferenceException; |
3447 | } |
3448 | |
3449 | case STATUS_ARRAY_BOUNDS_EXCEEDED: |
3450 | return (DWORD) kIndexOutOfRangeException; |
3451 | |
3452 | case STATUS_NO_MEMORY: |
3453 | return (DWORD) kOutOfMemoryException; |
3454 | |
3455 | case STATUS_STACK_OVERFLOW: |
3456 | return (DWORD) kStackOverflowException; |
3457 | |
3458 | #ifdef ALIGN_ACCESS |
3459 | case STATUS_DATATYPE_MISALIGNMENT: |
3460 | return (DWORD) kDataMisalignedException; |
3461 | #endif // ALIGN_ACCESS |
3462 | |
3463 | default: |
3464 | return kSEHException; |
3465 | } |
3466 | } |
3467 | |
3468 | #ifdef _DEBUG |
3469 | #ifndef WIN64EXCEPTIONS |
3470 | // check if anyone has written to the stack above the handler which would wipe out the EH registration |
3471 | void CheckStackBarrier(EXCEPTION_REGISTRATION_RECORD *exRecord) |
3472 | { |
3473 | LIMITED_METHOD_CONTRACT; |
3474 | |
3475 | if (exRecord->Handler != (PEXCEPTION_ROUTINE)COMPlusFrameHandler) |
3476 | return; |
3477 | |
3478 | DWORD *stackOverwriteBarrier = (DWORD *)((BYTE*)exRecord - offsetof(FrameHandlerExRecordWithBarrier, m_ExRecord)); |
3479 | for (int i =0; i < STACK_OVERWRITE_BARRIER_SIZE; i++) { |
3480 | if (*(stackOverwriteBarrier+i) != STACK_OVERWRITE_BARRIER_VALUE) { |
3481 | // to debug this error, you must determine who erroneously overwrote the stack |
3482 | _ASSERTE(!"Fatal error: the stack has been overwritten" ); |
3483 | } |
3484 | } |
3485 | } |
3486 | #endif // WIN64EXCEPTIONS |
3487 | #endif // _DEBUG |
3488 | |
3489 | //------------------------------------------------------------------------- |
3490 | // A marker for JIT -> EE transition when we know we're in preemptive |
3491 | // gc mode. As we leave the EE, we fix a few things: |
3492 | // |
3493 | // - the gc state must be set back to preemptive-operative |
3494 | // - the COM+ frame chain must be rewound to what it was on entry |
3495 | // - ExInfo()->m_pSearchBoundary must be adjusted |
3496 | // if we popped the frame that is identified as begnning the next |
3497 | // crawl. |
3498 | //------------------------------------------------------------------------- |
3499 | |
3500 | void COMPlusCooperativeTransitionHandler(Frame* pFrame) |
3501 | { |
3502 | CONTRACTL |
3503 | { |
3504 | NOTHROW; |
3505 | GC_TRIGGERS; |
3506 | MODE_ANY; |
3507 | } |
3508 | CONTRACTL_END; |
3509 | |
3510 | LOG((LF_EH, LL_INFO1000, "COMPlusCooprativeTransitionHandler unwinding\n" )); |
3511 | |
3512 | { |
3513 | Thread* pThread = GetThread(); |
3514 | |
3515 | // Restore us to cooperative gc mode. |
3516 | GCX_COOP(); |
3517 | |
3518 | // Pop the frame chain. |
3519 | UnwindFrameChain(pThread, pFrame); |
3520 | CONSISTENCY_CHECK(pFrame == pThread->GetFrame()); |
3521 | |
3522 | #ifndef WIN64EXCEPTIONS |
3523 | // An exception is being thrown through here. The COM+ exception |
3524 | // info keeps a pointer to a frame that is used by the next |
3525 | // COM+ Exception Handler as the starting point of its crawl. |
3526 | // We may have popped this marker -- in which case, we need to |
3527 | // update it to the current frame. |
3528 | // |
3529 | ThreadExceptionState* pExState = pThread->GetExceptionState(); |
3530 | Frame* pSearchBoundary = NULL; |
3531 | |
3532 | if (pThread->IsExceptionInProgress()) |
3533 | { |
3534 | pSearchBoundary = pExState->m_currentExInfo.m_pSearchBoundary; |
3535 | } |
3536 | |
3537 | if (pSearchBoundary && pSearchBoundary < pFrame) |
3538 | { |
3539 | LOG((LF_EH, LL_INFO1000, "\tpExInfo->m_pSearchBoundary = %08x\n" , (void*)pFrame)); |
3540 | pExState->m_currentExInfo.m_pSearchBoundary = pFrame; |
3541 | } |
3542 | #endif // WIN64EXCEPTIONS |
3543 | } |
3544 | |
3545 | // Restore us to preemptive gc mode. |
3546 | GCX_PREEMP_NO_DTOR(); |
3547 | } |
3548 | |
3549 | |
3550 | |
3551 | void StackTraceInfo::Init() |
3552 | { |
3553 | CONTRACTL |
3554 | { |
3555 | NOTHROW; |
3556 | GC_NOTRIGGER; |
3557 | MODE_ANY; |
3558 | FORBID_FAULT; |
3559 | SO_TOLERANT; |
3560 | } |
3561 | CONTRACTL_END; |
3562 | |
3563 | LOG((LF_EH, LL_INFO10000, "StackTraceInfo::Init (%p)\n" , this)); |
3564 | |
3565 | m_pStackTrace = NULL; |
3566 | m_cStackTrace = 0; |
3567 | m_dFrameCount = 0; |
3568 | m_cDynamicMethodItems = 0; |
3569 | m_dCurrentDynamicIndex = 0; |
3570 | } |
3571 | |
3572 | void StackTraceInfo::FreeStackTrace() |
3573 | { |
3574 | CONTRACTL |
3575 | { |
3576 | NOTHROW; |
3577 | GC_NOTRIGGER; |
3578 | MODE_ANY; |
3579 | FORBID_FAULT; |
3580 | SO_TOLERANT; |
3581 | } |
3582 | CONTRACTL_END; |
3583 | |
3584 | if (m_pStackTrace) |
3585 | { |
3586 | delete [] m_pStackTrace; |
3587 | m_pStackTrace = NULL; |
3588 | m_cStackTrace = 0; |
3589 | m_dFrameCount = 0; |
3590 | m_cDynamicMethodItems = 0; |
3591 | m_dCurrentDynamicIndex = 0; |
3592 | } |
3593 | } |
3594 | |
3595 | BOOL StackTraceInfo::IsEmpty() |
3596 | { |
3597 | LIMITED_METHOD_CONTRACT; |
3598 | |
3599 | return 0 == m_dFrameCount; |
3600 | } |
3601 | |
3602 | void StackTraceInfo::ClearStackTrace() |
3603 | { |
3604 | LIMITED_METHOD_CONTRACT; |
3605 | |
3606 | LOG((LF_EH, LL_INFO1000, "StackTraceInfo::ClearStackTrace (%p)\n" , this)); |
3607 | m_dFrameCount = 0; |
3608 | } |
3609 | |
3610 | // allocate stack trace info. As each function is found in the stack crawl, it will be added |
3611 | // to this list. If the list is too small, it is reallocated. |
3612 | void StackTraceInfo::AllocateStackTrace() |
3613 | { |
3614 | STATIC_CONTRACT_NOTHROW; |
3615 | STATIC_CONTRACT_GC_NOTRIGGER; |
3616 | STATIC_CONTRACT_MODE_ANY; |
3617 | STATIC_CONTRACT_FORBID_FAULT; |
3618 | |
3619 | LOG((LF_EH, LL_INFO1000, "StackTraceInfo::AllocateStackTrace (%p)\n" , this)); |
3620 | |
3621 | if (!m_pStackTrace) |
3622 | { |
3623 | #ifdef _DEBUG |
3624 | unsigned int allocSize = 2; // make small to exercise realloc |
3625 | #else |
3626 | unsigned int allocSize = 30; |
3627 | #endif |
3628 | |
3629 | SCAN_IGNORE_FAULT; // A fault of new is okay here. The rest of the system is cool if we don't have enough |
3630 | // memory to remember the stack as we run our first pass. |
3631 | m_pStackTrace = new (nothrow) StackTraceElement[allocSize]; |
3632 | |
3633 | if (m_pStackTrace != NULL) |
3634 | { |
3635 | // Remember how much we allocated. |
3636 | m_cStackTrace = allocSize; |
3637 | m_cDynamicMethodItems = allocSize; |
3638 | } |
3639 | else |
3640 | { |
3641 | m_cStackTrace = 0; |
3642 | m_cDynamicMethodItems = 0; |
3643 | } |
3644 | } |
3645 | } |
3646 | |
3647 | // |
3648 | // Returns true if it appended the element, false otherwise. |
3649 | // |
3650 | BOOL StackTraceInfo::AppendElement(BOOL bAllowAllocMem, UINT_PTR currentIP, UINT_PTR currentSP, MethodDesc* pFunc, CrawlFrame* pCf) |
3651 | { |
3652 | CONTRACTL |
3653 | { |
3654 | GC_TRIGGERS; |
3655 | NOTHROW; |
3656 | } |
3657 | CONTRACTL_END |
3658 | |
3659 | LOG((LF_EH, LL_INFO10000, "StackTraceInfo::AppendElement (%p), IP = %p, SP = %p, %s::%s\n" , this, currentIP, currentSP, pFunc ? pFunc->m_pszDebugClassName : "" , pFunc ? pFunc->m_pszDebugMethodName : "" )); |
3660 | BOOL bRetVal = FALSE; |
3661 | |
3662 | if (pFunc != NULL && pFunc->IsILStub()) |
3663 | return FALSE; |
3664 | |
3665 | // Save this function in the stack trace array, which we only build on the first pass. We'll try to expand the |
3666 | // stack trace array if we don't have enough room. Note that we only try to expand if we're allowed to allocate |
3667 | // memory (bAllowAllocMem). |
3668 | if (bAllowAllocMem && (m_dFrameCount >= m_cStackTrace)) |
3669 | { |
3670 | StackTraceElement* pTempElement = new (nothrow) StackTraceElement[m_cStackTrace*2]; |
3671 | |
3672 | if (pTempElement != NULL) |
3673 | { |
3674 | memcpy(pTempElement, m_pStackTrace, m_cStackTrace * sizeof(StackTraceElement)); |
3675 | delete [] m_pStackTrace; |
3676 | m_pStackTrace = pTempElement; |
3677 | m_cStackTrace *= 2; |
3678 | } |
3679 | } |
3680 | |
3681 | // Add the function to the stack trace array if there's room. |
3682 | if (m_dFrameCount < m_cStackTrace) |
3683 | { |
3684 | StackTraceElement* pStackTraceElem; |
3685 | |
3686 | // If we get in here, we'd better have a stack trace array. |
3687 | CONSISTENCY_CHECK(m_pStackTrace != NULL); |
3688 | |
3689 | pStackTraceElem = &(m_pStackTrace[m_dFrameCount]); |
3690 | |
3691 | pStackTraceElem->pFunc = pFunc; |
3692 | |
3693 | pStackTraceElem->ip = currentIP; |
3694 | pStackTraceElem->sp = currentSP; |
3695 | |
3696 | // When we are building stack trace as we encounter managed frames during exception dispatch, |
3697 | // then none of those frames represent a stack trace from a foreign exception (as they represent |
3698 | // the current exception). Hence, set the corresponding flag to FALSE. |
3699 | pStackTraceElem->fIsLastFrameFromForeignStackTrace = FALSE; |
3700 | |
3701 | // This is a workaround to fix the generation of stack traces from exception objects so that |
3702 | // they point to the line that actually generated the exception instead of the line |
3703 | // following. |
3704 | if (!(pCf->HasFaulted() || pCf->IsIPadjusted()) && pStackTraceElem->ip != 0) |
3705 | { |
3706 | pStackTraceElem->ip -= 1; |
3707 | } |
3708 | |
3709 | ++m_dFrameCount; |
3710 | bRetVal = TRUE; |
3711 | COUNTER_ONLY(GetPerfCounters().m_Excep.cThrowToCatchStackDepth++); |
3712 | } |
3713 | |
3714 | #ifndef FEATURE_PAL // Watson is supported on Windows only |
3715 | Thread *pThread = GetThread(); |
3716 | _ASSERTE(pThread); |
3717 | |
3718 | if (pThread && (currentIP != 0)) |
3719 | { |
3720 | // Setup the watson bucketing details for the initial throw |
3721 | // callback only if we dont already have them. |
3722 | ThreadExceptionState *pExState = pThread->GetExceptionState(); |
3723 | if (!pExState->GetFlags()->GotWatsonBucketDetails()) |
3724 | { |
3725 | // Adjust the IP if necessary. |
3726 | UINT_PTR adjustedIp = currentIP; |
3727 | // This is a workaround copied from above. |
3728 | if (!(pCf->HasFaulted() || pCf->IsIPadjusted()) && adjustedIp != 0) |
3729 | { |
3730 | adjustedIp -= 1; |
3731 | } |
3732 | |
3733 | // Setup the bucketing details for the initial throw |
3734 | SetupInitialThrowBucketDetails(adjustedIp); |
3735 | } |
3736 | } |
3737 | #endif // !FEATURE_PAL |
3738 | |
3739 | return bRetVal; |
3740 | } |
3741 | |
3742 | void StackTraceInfo::GetLeafFrameInfo(StackTraceElement* pStackTraceElement) |
3743 | { |
3744 | LIMITED_METHOD_CONTRACT; |
3745 | |
3746 | if (NULL == m_pStackTrace) |
3747 | { |
3748 | return; |
3749 | } |
3750 | _ASSERTE(NULL != pStackTraceElement); |
3751 | |
3752 | *pStackTraceElement = m_pStackTrace[0]; |
3753 | } |
3754 | |
3755 | |
3756 | void UnwindFrameChain(Thread* pThread, LPVOID pvLimitSP) |
3757 | { |
3758 | CONTRACTL |
3759 | { |
3760 | NOTHROW; |
3761 | DISABLED(GC_TRIGGERS); // some Frames' ExceptionUnwind methods trigger :( |
3762 | MODE_ANY; |
3763 | SO_TOLERANT; |
3764 | } |
3765 | CONTRACTL_END; |
3766 | |
3767 | // @todo - Remove this and add a hard SO probe as can't throw from here. |
3768 | CONTRACT_VIOLATION(SOToleranceViolation); |
3769 | |
3770 | Frame* pFrame = pThread->m_pFrame; |
3771 | if (pFrame < pvLimitSP) |
3772 | { |
3773 | GCX_COOP_THREAD_EXISTS(pThread); |
3774 | |
3775 | // |
3776 | // call ExceptionUnwind with the Frame chain intact |
3777 | // |
3778 | pFrame = pThread->NotifyFrameChainOfExceptionUnwind(pFrame, pvLimitSP); |
3779 | |
3780 | // |
3781 | // now pop the frames off by trimming the Frame chain |
3782 | // |
3783 | pThread->SetFrame(pFrame); |
3784 | } |
3785 | } |
3786 | |
3787 | BOOL IsExceptionOfType(RuntimeExceptionKind reKind, Exception *pException) |
3788 | { |
3789 | STATIC_CONTRACT_NOTHROW; |
3790 | STATIC_CONTRACT_GC_TRIGGERS; |
3791 | STATIC_CONTRACT_MODE_ANY; |
3792 | STATIC_CONTRACT_FORBID_FAULT; |
3793 | |
3794 | if (pException->IsType(reKind)) |
3795 | return TRUE; |
3796 | |
3797 | if (pException->IsType(CLRException::GetType())) |
3798 | { |
3799 | // Since we're going to be holding onto the Throwable object we |
3800 | // need to be in COOPERATIVE. |
3801 | GCX_COOP(); |
3802 | |
3803 | OBJECTREF Throwable=((CLRException*)pException)->GetThrowable(); |
3804 | |
3805 | GCX_FORBID(); |
3806 | if (IsExceptionOfType(reKind, &Throwable)) |
3807 | return TRUE; |
3808 | } |
3809 | return FALSE; |
3810 | } |
3811 | |
3812 | BOOL IsExceptionOfType(RuntimeExceptionKind reKind, OBJECTREF *pThrowable) |
3813 | { |
3814 | STATIC_CONTRACT_NOTHROW; |
3815 | STATIC_CONTRACT_GC_NOTRIGGER; |
3816 | STATIC_CONTRACT_MODE_COOPERATIVE; |
3817 | STATIC_CONTRACT_FORBID_FAULT; |
3818 | |
3819 | _ASSERTE(pThrowable != NULL); |
3820 | |
3821 | if (*pThrowable == NULL) |
3822 | return FALSE; |
3823 | |
3824 | MethodTable *pThrowableMT = (*pThrowable)->GetMethodTable(); |
3825 | |
3826 | // IsExceptionOfType is supported for mscorlib exception types only |
3827 | _ASSERTE(reKind <= kLastExceptionInMscorlib); |
3828 | return MscorlibBinder::IsException(pThrowableMT, reKind); |
3829 | } |
3830 | |
3831 | BOOL IsAsyncThreadException(OBJECTREF *pThrowable) { |
3832 | STATIC_CONTRACT_NOTHROW; |
3833 | STATIC_CONTRACT_GC_NOTRIGGER; |
3834 | STATIC_CONTRACT_MODE_COOPERATIVE; |
3835 | STATIC_CONTRACT_FORBID_FAULT; |
3836 | |
3837 | if ( (GetThread() && GetThread()->IsRudeAbort() && GetThread()->IsRudeAbortInitiated()) |
3838 | ||IsExceptionOfType(kThreadAbortException, pThrowable) |
3839 | ||IsExceptionOfType(kThreadInterruptedException, pThrowable)) { |
3840 | return TRUE; |
3841 | } else { |
3842 | return FALSE; |
3843 | } |
3844 | } |
3845 | |
3846 | BOOL IsUncatchable(OBJECTREF *pThrowable) |
3847 | { |
3848 | CONTRACTL { |
3849 | SO_TOLERANT; |
3850 | NOTHROW; |
3851 | GC_NOTRIGGER; |
3852 | MODE_COOPERATIVE; |
3853 | FORBID_FAULT; |
3854 | } CONTRACTL_END; |
3855 | |
3856 | _ASSERTE(pThrowable != NULL); |
3857 | |
3858 | Thread *pThread = GetThread(); |
3859 | |
3860 | if (pThread) |
3861 | { |
3862 | if (pThread->IsAbortInitiated()) |
3863 | return TRUE; |
3864 | |
3865 | if (OBJECTREFToObject(*pThrowable)->GetMethodTable() == g_pExecutionEngineExceptionClass) |
3866 | return TRUE; |
3867 | |
3868 | #ifdef FEATURE_CORRUPTING_EXCEPTIONS |
3869 | // Corrupting exceptions are also uncatchable |
3870 | if (CEHelper::IsProcessCorruptedStateException(*pThrowable)) |
3871 | { |
3872 | return TRUE; |
3873 | } |
3874 | #endif //FEATURE_CORRUPTING_EXCEPTIONS |
3875 | } |
3876 | |
3877 | return FALSE; |
3878 | } |
3879 | |
3880 | BOOL IsStackOverflowException(Thread* pThread, EXCEPTION_RECORD* pExceptionRecord) |
3881 | { |
3882 | if (IsSOExceptionCode(pExceptionRecord->ExceptionCode)) |
3883 | { |
3884 | return true; |
3885 | } |
3886 | |
3887 | if (IsComPlusException(pExceptionRecord) && |
3888 | pThread->IsLastThrownObjectStackOverflowException()) |
3889 | { |
3890 | return true; |
3891 | } |
3892 | |
3893 | return false; |
3894 | } |
3895 | |
3896 | |
3897 | #ifdef _DEBUG |
3898 | BOOL IsValidClause(EE_ILEXCEPTION_CLAUSE *EHClause) |
3899 | { |
3900 | LIMITED_METHOD_CONTRACT; |
3901 | |
3902 | #if 0 |
3903 | DWORD valid = COR_ILEXCEPTION_CLAUSE_FILTER | COR_ILEXCEPTION_CLAUSE_FINALLY | |
3904 | COR_ILEXCEPTION_CLAUSE_FAULT | COR_ILEXCEPTION_CLAUSE_CACHED_CLASS; |
3905 | |
3906 | // <TODO>@NICE: enable this when VC stops generatng a bogus 0x8000.</TODO> |
3907 | if (EHClause->Flags & ~valid) |
3908 | return FALSE; |
3909 | #endif |
3910 | if (EHClause->TryStartPC > EHClause->TryEndPC) |
3911 | return FALSE; |
3912 | return TRUE; |
3913 | } |
3914 | #endif |
3915 | |
3916 | |
3917 | #ifdef DEBUGGING_SUPPORTED |
3918 | LONG NotifyDebuggerLastChance(Thread *pThread, |
3919 | EXCEPTION_POINTERS *pExceptionInfo, |
3920 | BOOL jitAttachRequested) |
3921 | { |
3922 | STATIC_CONTRACT_NOTHROW; |
3923 | STATIC_CONTRACT_GC_TRIGGERS; |
3924 | STATIC_CONTRACT_MODE_ANY; |
3925 | |
3926 | LONG retval = EXCEPTION_CONTINUE_SEARCH; |
3927 | |
3928 | // Debugger does func-evals inside this call, which may take nested exceptions. We need a nested exception |
3929 | // handler to allow this. |
3930 | INSTALL_NESTED_EXCEPTION_HANDLER(pThread->GetFrame()); |
3931 | |
3932 | EXCEPTION_POINTERS dummy; |
3933 | dummy.ExceptionRecord = NULL; |
3934 | dummy.ContextRecord = NULL; |
3935 | |
3936 | if (NULL == pExceptionInfo) |
3937 | { |
3938 | pExceptionInfo = &dummy; |
3939 | } |
3940 | else if (NULL != pExceptionInfo->ExceptionRecord && NULL == pExceptionInfo->ContextRecord) |
3941 | { |
3942 | // In a soft stack overflow, we have an exception record but not a context record. |
3943 | // Debugger::LastChanceManagedException requires that both ExceptionRecord and |
3944 | // ContextRecord be valid or both be NULL. |
3945 | pExceptionInfo = &dummy; |
3946 | } |
3947 | |
3948 | if (g_pDebugInterface && g_pDebugInterface->LastChanceManagedException(pExceptionInfo, |
3949 | pThread, |
3950 | jitAttachRequested) == ExceptionContinueExecution) |
3951 | { |
3952 | retval = EXCEPTION_CONTINUE_EXECUTION; |
3953 | } |
3954 | |
3955 | UNINSTALL_NESTED_EXCEPTION_HANDLER(); |
3956 | |
3957 | #ifdef DEBUGGER_EXCEPTION_INTERCEPTION_SUPPORTED |
3958 | EX_TRY |
3959 | { |
3960 | // if the debugger wants to intercept the unhandled exception then we immediately unwind without returning |
3961 | // If there is a problem with this function unwinding here it could be separated out however |
3962 | // we need to be very careful. Previously we had the opposite problem in that we notified the debugger |
3963 | // of an unhandled exception and then either: |
3964 | // a) never gave the debugger a chance to intercept later, or |
3965 | // b) code changed more process state unaware that the debugger would be handling the exception |
3966 | if ((pThread->IsExceptionInProgress()) && pThread->GetExceptionState()->GetFlags()->DebuggerInterceptInfo()) |
3967 | { |
3968 | // The debugger wants to intercept this exception. It may return in a failure case, in which case we want |
3969 | // to continue thru this path. |
3970 | ClrDebuggerDoUnwindAndIntercept(X86_FIRST_ARG(EXCEPTION_CHAIN_END) pExceptionInfo->ExceptionRecord); |
3971 | } |
3972 | } |
3973 | EX_CATCH // if we fail to intercept just continue as is |
3974 | { |
3975 | } |
3976 | EX_END_CATCH(SwallowAllExceptions); |
3977 | #endif // DEBUGGER_EXCEPTION_INTERCEPTION_SUPPORTED |
3978 | |
3979 | return retval; |
3980 | } |
3981 | |
3982 | #ifndef FEATURE_PAL |
3983 | //---------------------------------------------------------------------------- |
3984 | // |
3985 | // DoReportFault - wrapper for ReportFault in FaultRep.dll, which also handles |
3986 | // debugger launch synchronization if the user chooses to launch |
3987 | // a debugger |
3988 | // |
3989 | // Arguments: |
3990 | // pExceptionInfo - pointer to exception info |
3991 | // |
3992 | // Return Value: |
3993 | // The returned EFaultRepRetVal value from ReportFault |
3994 | // |
3995 | // Note: |
3996 | // |
3997 | //---------------------------------------------------------------------------- |
3998 | EFaultRepRetVal DoReportFault(EXCEPTION_POINTERS * pExceptionInfo) |
3999 | { |
4000 | LIMITED_METHOD_CONTRACT; |
4001 | |
4002 | HINSTANCE hmod = WszLoadLibrary(W("FaultRep.dll" )); |
4003 | EFaultRepRetVal r = frrvErr; |
4004 | if (hmod) |
4005 | { |
4006 | pfn_REPORTFAULT pfnReportFault = (pfn_REPORTFAULT)GetProcAddress(hmod, "ReportFault" ); |
4007 | if (pfnReportFault) |
4008 | { |
4009 | r = pfnReportFault(pExceptionInfo, 0); |
4010 | } |
4011 | FreeLibrary(hmod); |
4012 | } |
4013 | |
4014 | if (r == frrvLaunchDebugger) |
4015 | { |
4016 | // Wait until the pending managed debugger attach is completed |
4017 | if (g_pDebugInterface != NULL) |
4018 | { |
4019 | g_pDebugInterface->WaitForDebuggerAttach(); |
4020 | } |
4021 | } |
4022 | return r; |
4023 | } |
4024 | |
4025 | //---------------------------------------------------------------------------- |
4026 | // |
4027 | // DisableOSWatson - Set error mode to disable OS Watson |
4028 | // |
4029 | // Arguments: |
4030 | // None |
4031 | // |
4032 | // Return Value: |
4033 | // None |
4034 | // |
4035 | // Note: SetErrorMode changes the process wide error mode, which can be overridden by other threads |
4036 | // in a race. The solution is to use new Win7 per thread error mode APIs, which take precedence |
4037 | // over process wide error mode. However, we shall not use per thread error mode if the runtime |
4038 | // is being hosted because with per thread error mode being used the OS will ignore the process |
4039 | // wide error mode set by the host. |
4040 | // |
4041 | //---------------------------------------------------------------------------- |
4042 | void DisableOSWatson(void) |
4043 | { |
4044 | LIMITED_METHOD_CONTRACT; |
4045 | |
4046 | // When a debugger is attached (or will be attaching), we need to disable the OS GPF dialog. |
4047 | // If we don't, an unhandled managed exception will launch the OS watson dialog even when |
4048 | // the debugger is attached. |
4049 | const UINT lastErrorMode = SetErrorMode(0); |
4050 | SetErrorMode(lastErrorMode | SEM_NOGPFAULTERRORBOX); |
4051 | LOG((LF_EH, LL_INFO100, "DisableOSWatson: SetErrorMode = 0x%x\n" , lastErrorMode | SEM_NOGPFAULTERRORBOX)); |
4052 | |
4053 | } |
4054 | #endif // !FEATURE_PAL |
4055 | |
4056 | //------------------------------------------------------------------------------ |
4057 | // This function is called on an unhandled exception, via the runtime's |
4058 | // Unhandled Exception Filter (Hence the name, "last chance", because this |
4059 | // is the last chance to see the exception. When running under a native |
4060 | // debugger, that won't generally happen, because the OS notifies the debugger |
4061 | // instead of calling the application's registered UEF; the debugger will |
4062 | // show the exception as second chance.) |
4063 | // The function is also called sometimes for the side effects, which are |
4064 | // to possibly invoke Watson and to possibly notify the managed debugger. |
4065 | // If running in a debugger already, either native or managed, we shouldn't |
4066 | // invoke Watson. |
4067 | // If not running under a managed debugger, we shouldn't try to send a debugger |
4068 | // notification. |
4069 | //------------------------------------------------------------------------------ |
4070 | LONG WatsonLastChance( // EXCEPTION_CONTINUE_SEARCH, _CONTINUE_EXECUTION |
4071 | Thread *pThread, // Thread object. |
4072 | EXCEPTION_POINTERS *pExceptionInfo,// Information about reported exception. |
4073 | TypeOfReportedError tore) // Just what kind of error is reported? |
4074 | { |
4075 | STATIC_CONTRACT_NOTHROW; |
4076 | STATIC_CONTRACT_GC_TRIGGERS; |
4077 | STATIC_CONTRACT_MODE_ANY; |
4078 | |
4079 | // If allocation fails, we may not produce watson dump. But this is not fatal. |
4080 | CONTRACT_VIOLATION(AllViolation); |
4081 | LOG((LF_EH, LL_INFO10, "D::WLC: Enter WatsonLastChance\n" )); |
4082 | |
4083 | #ifndef FEATURE_PAL |
4084 | static DWORD fDisableWatson = -1; |
4085 | if (fDisableWatson == -1) |
4086 | { |
4087 | fDisableWatson = CLRConfig::GetConfigValue(CLRConfig::INTERNAL_DisableWatsonForManagedExceptions); |
4088 | } |
4089 | |
4090 | if (fDisableWatson && (tore.GetType() == TypeOfReportedError::UnhandledException)) |
4091 | { |
4092 | DisableOSWatson(); |
4093 | LOG((LF_EH, LL_INFO10, "D::WLC: OS Watson is disabled for an managed unhandled exception\n" )); |
4094 | return EXCEPTION_CONTINUE_SEARCH; |
4095 | } |
4096 | #endif // !FEATURE_PAL |
4097 | |
4098 | // We don't want to launch Watson if a debugger is already attached to |
4099 | // the process. |
4100 | BOOL shouldNotifyDebugger = FALSE; // Assume we won't debug. |
4101 | |
4102 | // VS debugger team requested the Whidbey experience, which is no Watson when the debugger thread detects |
4103 | // that the debugger process is abruptly terminated, and triggers a failfast error. In this particular |
4104 | // scenario CORDebuggerAttached() will be TRUE, but IsDebuggerPresent() will be FALSE because from OS |
4105 | // perspective the native debugger has been detached from the debuggee, but CLR has not yet marked the |
4106 | // managed debugger as detached. Therefore, CORDebuggerAttached() is checked, so Watson will not pop up |
4107 | // when a debugger is abruptly terminated. It also prevents a debugger from being launched on a helper |
4108 | // thread. |
4109 | BOOL alreadyDebugging = CORDebuggerAttached() || IsDebuggerPresent(); |
4110 | |
4111 | BOOL jitAttachRequested = !alreadyDebugging; // Launch debugger if not already running. |
4112 | |
4113 | #ifdef _DEBUG |
4114 | // If BreakOnUnCaughtException is set, we may be using a native debugger to debug this stuff |
4115 | BOOL BreakOnUnCaughtException = CLRConfig::GetConfigValue(CLRConfig::INTERNAL_BreakOnUncaughtException); |
4116 | if(!alreadyDebugging || (!CORDebuggerAttached() && BreakOnUnCaughtException) ) |
4117 | #else |
4118 | if (!alreadyDebugging) |
4119 | #endif |
4120 | { |
4121 | LOG((LF_EH, LL_INFO10, "WatsonLastChance: Debugger not attached at sp %p ...\n" , GetCurrentSP())); |
4122 | |
4123 | #ifndef FEATURE_PAL |
4124 | FaultReportResult result = FaultReportResultQuit; |
4125 | |
4126 | BOOL fSOException = FALSE; |
4127 | |
4128 | if ((pExceptionInfo != NULL) && |
4129 | (pExceptionInfo->ExceptionRecord != NULL) && |
4130 | (pExceptionInfo->ExceptionRecord->ExceptionCode == STATUS_STACK_OVERFLOW)) |
4131 | { |
4132 | fSOException = TRUE; |
4133 | } |
4134 | |
4135 | if (g_pDebugInterface) |
4136 | { |
4137 | // we are about to let the OS trigger jit attach, however we need to synchronize with our |
4138 | // own jit attach that we might be doing on another thread |
4139 | // PreJitAttach races this thread against any others which might be attaching and if some other |
4140 | // thread is doing it then we wait for its attach to complete first |
4141 | g_pDebugInterface->PreJitAttach(TRUE, FALSE, FALSE); |
4142 | } |
4143 | |
4144 | // Let unhandled excpetions except stack overflow go to the OS |
4145 | if (tore.IsUnhandledException() && !fSOException) |
4146 | { |
4147 | return EXCEPTION_CONTINUE_SEARCH; |
4148 | } |
4149 | else if (tore.IsUserBreakpoint()) |
4150 | { |
4151 | DoReportFault(pExceptionInfo); |
4152 | } |
4153 | else |
4154 | { |
4155 | BOOL fWatsonAlreadyLaunched = FALSE; |
4156 | if (FastInterlockCompareExchange(&g_watsonAlreadyLaunched, 1, 0) != 0) |
4157 | { |
4158 | fWatsonAlreadyLaunched = TRUE; |
4159 | } |
4160 | |
4161 | // Logic to avoid double prompt if more than one threads calling into WatsonLastChance |
4162 | if (!fWatsonAlreadyLaunched) |
4163 | { |
4164 | // EEPolicy::HandleFatalStackOverflow pushes a FaultingExceptionFrame on the stack after SO |
4165 | // exception. Our hijack code runs in the exception context, and overwrites the stack space |
4166 | // after SO excpetion, so we need to pop up this frame before invoking RaiseFailFast. |
4167 | // This cumbersome code should be removed once SO synchronization is moved to be completely |
4168 | // out-of-process. |
4169 | if (fSOException && pThread && pThread->GetFrame() != FRAME_TOP) |
4170 | { |
4171 | GCX_COOP(); // Must be cooperative to modify frame chain. |
4172 | pThread->GetFrame()->Pop(pThread); |
4173 | } |
4174 | |
4175 | LOG((LF_EH, LL_INFO10, "D::WLC: Call RaiseFailFastException\n" )); |
4176 | |
4177 | // enable preemptive mode before call into OS to allow runtime suspend to finish |
4178 | GCX_PREEMP(); |
4179 | |
4180 | STRESS_LOG0(LF_CORDB, LL_INFO10, "D::RFFE: About to call RaiseFailFastException\n" ); |
4181 | RaiseFailFastException(pExceptionInfo == NULL ? NULL : pExceptionInfo->ExceptionRecord, |
4182 | pExceptionInfo == NULL ? NULL : pExceptionInfo->ContextRecord, |
4183 | 0); |
4184 | STRESS_LOG0(LF_CORDB, LL_INFO10, "D::RFFE: Return from RaiseFailFastException\n" ); |
4185 | } |
4186 | } |
4187 | |
4188 | if (g_pDebugInterface) |
4189 | { |
4190 | // if execution resumed here then we may or may not be attached |
4191 | // either way we need to end the attach process and unblock any other |
4192 | // threads which were waiting for the attach here to complete |
4193 | g_pDebugInterface->PostJitAttach(); |
4194 | } |
4195 | |
4196 | |
4197 | if (IsDebuggerPresent()) |
4198 | { |
4199 | result = FaultReportResultDebug; |
4200 | jitAttachRequested = FALSE; |
4201 | } |
4202 | |
4203 | switch(result) |
4204 | { |
4205 | case FaultReportResultAbort: |
4206 | { |
4207 | // We couldn't launch watson properly. First fall-back to OS error-reporting |
4208 | // so that we don't break native apps. |
4209 | EFaultRepRetVal r = frrvErr; |
4210 | |
4211 | if (pExceptionInfo != NULL) |
4212 | { |
4213 | GCX_PREEMP(); |
4214 | |
4215 | if (pExceptionInfo->ExceptionRecord->ExceptionCode != STATUS_STACK_OVERFLOW) |
4216 | { |
4217 | r = DoReportFault(pExceptionInfo); |
4218 | } |
4219 | else |
4220 | { |
4221 | // Since the StackOverflow handler also calls us, we must keep our stack budget |
4222 | // to a minimum. Thus, we will launch a thread to do the actual work. |
4223 | FaultReportInfo fri; |
4224 | fri.m_fDoReportFault = TRUE; |
4225 | fri.m_pExceptionInfo = pExceptionInfo; |
4226 | // DoFaultCreateThreadReportCallback will overwrite this - if it doesn't, we'll assume it failed. |
4227 | fri.m_faultRepRetValResult = frrvErr; |
4228 | |
4229 | // Stack overflow case - we don't have enough stack on our own thread so let the debugger |
4230 | // helper thread do the work. |
4231 | if (!g_pDebugInterface || FAILED(g_pDebugInterface->RequestFavor(DoFaultReportDoFavorCallback, &fri))) |
4232 | { |
4233 | // If we can't initialize the debugger helper thread or we are running on the debugger helper |
4234 | // thread, give it up. We don't have enough stack space. |
4235 | } |
4236 | |
4237 | r = fri.m_faultRepRetValResult; |
4238 | } |
4239 | } |
4240 | |
4241 | if ((r == frrvErr) || (r == frrvErrNoDW) || (r == frrvErrTimeout)) |
4242 | { |
4243 | // If we don't have an exception record, or otherwise can't use OS error |
4244 | // reporting then offer the old "press OK to terminate, cancel to debug" |
4245 | // dialog as a futher fallback. |
4246 | if (g_pDebugInterface && g_pDebugInterface->FallbackJITAttachPrompt()) |
4247 | { |
4248 | // User requested to launch the debugger |
4249 | shouldNotifyDebugger = TRUE; |
4250 | } |
4251 | } |
4252 | else if (r == frrvLaunchDebugger) |
4253 | { |
4254 | // User requested to launch the debugger |
4255 | shouldNotifyDebugger = TRUE; |
4256 | } |
4257 | break; |
4258 | } |
4259 | case FaultReportResultQuit: |
4260 | // No debugger, just exit normally |
4261 | break; |
4262 | case FaultReportResultDebug: |
4263 | // JIT attach a debugger here. |
4264 | shouldNotifyDebugger = TRUE; |
4265 | break; |
4266 | default: |
4267 | UNREACHABLE_MSG("Unknown FaultReportResult" ); |
4268 | break; |
4269 | } |
4270 | } |
4271 | // When the debugger thread detects that the debugger process is abruptly terminated, and triggers |
4272 | // a failfast error, CORDebuggerAttached() will be TRUE, but IsDebuggerPresent() will be FALSE. |
4273 | // If IsDebuggerPresent() is FALSE, do not try to notify the deubgger. |
4274 | else if (CORDebuggerAttached() && IsDebuggerPresent()) |
4275 | #else |
4276 | } |
4277 | else if (CORDebuggerAttached()) |
4278 | #endif // !FEATURE_PAL |
4279 | { |
4280 | // Already debugging with a managed debugger. Should let that debugger know. |
4281 | LOG((LF_EH, LL_INFO100, "WatsonLastChance: Managed debugger already attached at sp %p ...\n" , GetCurrentSP())); |
4282 | |
4283 | // The managed EH subsystem ignores native breakpoints and single step exceptions. These exceptions are |
4284 | // not considered managed, and the managed debugger should not be notified. Moreover, we won't have |
4285 | // created a managed exception object at this point. |
4286 | if (tore.GetType() != TypeOfReportedError::NativeBreakpoint) |
4287 | { |
4288 | shouldNotifyDebugger = TRUE; |
4289 | } |
4290 | } |
4291 | |
4292 | #ifndef FEATURE_PAL |
4293 | DisableOSWatson(); |
4294 | #endif // !FEATURE_PAL |
4295 | |
4296 | if (!shouldNotifyDebugger) |
4297 | { |
4298 | LOG((LF_EH, LL_INFO100, "WatsonLastChance: should not notify debugger. Returning EXCEPTION_CONTINUE_SEARCH\n" )); |
4299 | return EXCEPTION_CONTINUE_SEARCH; |
4300 | } |
4301 | |
4302 | // If no debugger interface, we can't notify the debugger. |
4303 | if (g_pDebugInterface == NULL) |
4304 | { |
4305 | LOG((LF_EH, LL_INFO100, "WatsonLastChance: No debugger interface. Returning EXCEPTION_CONTINUE_SEARCH\n" )); |
4306 | return EXCEPTION_CONTINUE_SEARCH; |
4307 | } |
4308 | |
4309 | LOG((LF_EH, LL_INFO10, "WatsonLastChance: Notifying debugger\n" )); |
4310 | |
4311 | switch (tore.GetType()) |
4312 | { |
4313 | case TypeOfReportedError::FatalError: |
4314 | #ifdef MDA_SUPPORTED |
4315 | { |
4316 | MdaFatalExecutionEngineError * pMDA = MDA_GET_ASSISTANT_EX(FatalExecutionEngineError); |
4317 | |
4318 | if ((pMDA != NULL) && (pExceptionInfo != NULL) && (pExceptionInfo->ExceptionRecord != NULL)) |
4319 | { |
4320 | TADDR addr = (TADDR) pExceptionInfo->ExceptionRecord->ExceptionAddress; |
4321 | HRESULT hrError = pExceptionInfo->ExceptionRecord->ExceptionCode; |
4322 | pMDA->ReportFEEE(addr, hrError); |
4323 | } |
4324 | } |
4325 | #endif // MDA_SUPPORTED |
4326 | |
4327 | if (pThread != NULL) |
4328 | { |
4329 | NotifyDebuggerLastChance(pThread, pExceptionInfo, jitAttachRequested); |
4330 | |
4331 | // If the registed debugger is not a managed debugger, we need to stop the debugger here. |
4332 | if (!CORDebuggerAttached() && IsDebuggerPresent()) |
4333 | { |
4334 | DebugBreak(); |
4335 | } |
4336 | } |
4337 | else |
4338 | { |
4339 | g_pDebugInterface->LaunchDebuggerForUser(GetThread(), pExceptionInfo, FALSE, FALSE); |
4340 | } |
4341 | |
4342 | return EXCEPTION_CONTINUE_SEARCH; |
4343 | |
4344 | case TypeOfReportedError::UnhandledException: |
4345 | case TypeOfReportedError::NativeBreakpoint: |
4346 | // Notify the debugger only if this is a managed thread. |
4347 | if (pThread != NULL) |
4348 | { |
4349 | return NotifyDebuggerLastChance(pThread, pExceptionInfo, jitAttachRequested); |
4350 | } |
4351 | else |
4352 | { |
4353 | g_pDebugInterface->JitAttach(pThread, pExceptionInfo, FALSE, FALSE); |
4354 | |
4355 | // return EXCEPTION_CONTINUE_SEARCH, so OS's UEF will reraise the unhandled exception for debuggers |
4356 | return EXCEPTION_CONTINUE_SEARCH; |
4357 | } |
4358 | |
4359 | case TypeOfReportedError::UserBreakpoint: |
4360 | g_pDebugInterface->LaunchDebuggerForUser(pThread, pExceptionInfo, TRUE, FALSE); |
4361 | |
4362 | return EXCEPTION_CONTINUE_EXECUTION; |
4363 | |
4364 | case TypeOfReportedError::NativeThreadUnhandledException: |
4365 | g_pDebugInterface->JitAttach(pThread, pExceptionInfo, FALSE, FALSE); |
4366 | |
4367 | // return EXCEPTION_CONTINUE_SEARCH, so OS's UEF will reraise the unhandled exception for debuggers |
4368 | return EXCEPTION_CONTINUE_SEARCH; |
4369 | |
4370 | default: |
4371 | _ASSERTE(!"Unknown case in WatsonLastChance" ); |
4372 | return EXCEPTION_CONTINUE_SEARCH; |
4373 | } |
4374 | |
4375 | UNREACHABLE(); |
4376 | } // LONG WatsonLastChance() |
4377 | |
4378 | //--------------------------------------------------------------------------------------- |
4379 | // |
4380 | // This is just a simple helper to do some basic checking to see if an exception is intercepted. |
4381 | // It checks that we are on a managed thread and that an exception is indeed in progress. |
4382 | // |
4383 | // Return Value: |
4384 | // true iff we are on a managed thread and an exception is in flight |
4385 | // |
4386 | |
4387 | bool CheckThreadExceptionStateForInterception() |
4388 | { |
4389 | LIMITED_METHOD_CONTRACT; |
4390 | |
4391 | Thread* pThread = GetThread(); |
4392 | |
4393 | if (pThread == NULL) |
4394 | { |
4395 | return false; |
4396 | } |
4397 | |
4398 | if (!pThread->IsExceptionInProgress()) |
4399 | { |
4400 | return false; |
4401 | } |
4402 | |
4403 | return true; |
4404 | } |
4405 | #endif |
4406 | |
4407 | //=========================================================================================== |
4408 | // |
4409 | // UNHANDLED EXCEPTION HANDLING |
4410 | // |
4411 | |
4412 | static Volatile<BOOL> fReady = 0; |
4413 | static SpinLock initLock; |
4414 | |
4415 | void DECLSPEC_NORETURN RaiseDeadLockException() |
4416 | { |
4417 | STATIC_CONTRACT_THROWS; |
4418 | STATIC_CONTRACT_SO_TOLERANT; |
4419 | |
4420 | // Disable the "initialization of static local vars is no thread safe" error |
4421 | #ifdef _MSC_VER |
4422 | #pragma warning(disable: 4640) |
4423 | #endif |
4424 | CHECK_LOCAL_STATIC_VAR(static SString s); |
4425 | #ifdef _MSC_VER |
4426 | #pragma warning(default : 4640) |
4427 | #endif |
4428 | if (!fReady) |
4429 | { |
4430 | WCHAR name[256]; |
4431 | HRESULT hr = S_OK; |
4432 | { |
4433 | FAULT_NOT_FATAL(); |
4434 | GCX_COOP(); |
4435 | hr = UtilLoadStringRC(IDS_EE_THREAD_DEADLOCK_VICTIM, name, sizeof(name)/sizeof(WCHAR), 1); |
4436 | } |
4437 | initLock.Init(LOCK_TYPE_DEFAULT); |
4438 | SpinLockHolder __spinLockHolder(&initLock); |
4439 | if (!fReady) |
4440 | { |
4441 | if (SUCCEEDED(hr)) |
4442 | { |
4443 | s.Set(name); |
4444 | fReady = 1; |
4445 | } |
4446 | else |
4447 | { |
4448 | ThrowHR(hr); |
4449 | } |
4450 | } |
4451 | } |
4452 | |
4453 | ThrowHR(HOST_E_DEADLOCK, s); |
4454 | } |
4455 | |
4456 | //****************************************************************************** |
4457 | // |
4458 | // ExceptionIsAlwaysSwallowed |
4459 | // |
4460 | // Determine whether an exception is of a type that it should always |
4461 | // be swallowed, even when exceptions otherwise are left to go unhandled. |
4462 | // (For Whidbey, ThreadAbort, RudeThreadAbort, or AppDomainUnload exception) |
4463 | // |
4464 | // Parameters: |
4465 | // pExceptionInfo EXCEPTION_POINTERS for current exception |
4466 | // |
4467 | // Returns: |
4468 | // true If the exception is of a type that is always swallowed. |
4469 | // |
4470 | BOOL ExceptionIsAlwaysSwallowed(EXCEPTION_POINTERS *pExceptionInfo) |
4471 | { |
4472 | BOOL isSwallowed = false; |
4473 | |
4474 | // The exception code must be ours, if it is one of our Exceptions. |
4475 | if (IsComPlusException(pExceptionInfo->ExceptionRecord)) |
4476 | { |
4477 | // Our exception code. Get the current exception from the thread. |
4478 | Thread *pThread = GetThread(); |
4479 | if (pThread) |
4480 | { |
4481 | OBJECTREF throwable; |
4482 | |
4483 | GCX_COOP(); |
4484 | if ((throwable = pThread->GetThrowable()) == NULL) |
4485 | { |
4486 | throwable = pThread->LastThrownObject(); |
4487 | } |
4488 | //@todo: could throwable be NULL here? |
4489 | isSwallowed = IsExceptionOfType(kThreadAbortException, &throwable); |
4490 | } |
4491 | } |
4492 | |
4493 | return isSwallowed; |
4494 | } // BOOL ExceptionIsAlwaysSwallowed() |
4495 | |
4496 | // |
4497 | // UserBreakpointFilter is used to ensure that we get a popup on user breakpoints (DebugBreak(), hard-coded int 3, |
4498 | // etc.) as soon as possible. |
4499 | // |
4500 | LONG UserBreakpointFilter(EXCEPTION_POINTERS* pEP) |
4501 | { |
4502 | CONTRACTL |
4503 | { |
4504 | NOTHROW; |
4505 | GC_NOTRIGGER; |
4506 | MODE_ANY; |
4507 | FORBID_FAULT; |
4508 | SO_TOLERANT; |
4509 | } |
4510 | CONTRACTL_END; |
4511 | |
4512 | #ifdef DEBUGGING_SUPPORTED |
4513 | // Invoke the unhandled exception filter, bypassing any further first pass exception processing and treating |
4514 | // user breakpoints as if they're unhandled exceptions right away. |
4515 | // |
4516 | // @todo: The InternalUnhandledExceptionFilter can trigger. |
4517 | CONTRACT_VIOLATION(GCViolation | ThrowsViolation | ModeViolation | FaultViolation | FaultNotFatal); |
4518 | |
4519 | #ifdef FEATURE_PAL |
4520 | int result = COMUnhandledExceptionFilter(pEP); |
4521 | #else |
4522 | int result = UnhandledExceptionFilter(pEP); |
4523 | #endif |
4524 | |
4525 | if (result == EXCEPTION_CONTINUE_SEARCH) |
4526 | { |
4527 | // A debugger got attached. Instead of allowing the exception to continue up, and hope for the |
4528 | // second-chance, we cause it to happen again. The debugger snags all int3's on first-chance. NOTE: the |
4529 | // InternalUnhandledExceptionFilter allowed GC's to occur, but it may be the case that some managed frames |
4530 | // may have been unprotected. Therefore, you may have GC holes if you attempt to continue execution from |
4531 | // here. |
4532 | return EXCEPTION_CONTINUE_EXECUTION; |
4533 | } |
4534 | #endif // DEBUGGING_SUPPORTED |
4535 | |
4536 | if(ETW_EVENT_ENABLED(MICROSOFT_WINDOWS_DOTNETRUNTIME_PRIVATE_PROVIDER_Context, FailFast)) |
4537 | { |
4538 | // Fire an ETW FailFast event |
4539 | FireEtwFailFast(W("StatusBreakpoint" ), |
4540 | (const PVOID)((pEP && pEP->ContextRecord) ? GetIP(pEP->ContextRecord) : 0), |
4541 | ((pEP && pEP->ExceptionRecord) ? pEP->ExceptionRecord->ExceptionCode : 0), |
4542 | STATUS_BREAKPOINT, |
4543 | GetClrInstanceId()); |
4544 | } |
4545 | |
4546 | // Otherwise, we termintate the process. |
4547 | TerminateProcess(GetCurrentProcess(), STATUS_BREAKPOINT); |
4548 | |
4549 | // Shouldn't get here ... |
4550 | return EXCEPTION_CONTINUE_EXECUTION; |
4551 | } // LONG UserBreakpointFilter() |
4552 | |
4553 | //****************************************************************************** |
4554 | // |
4555 | // DefaultCatchFilter |
4556 | // |
4557 | // The old default except filter (v1.0/v1.1) . For user breakpoints, call out to UserBreakpointFilter() |
4558 | // but otherwise return EXCEPTION_EXECUTE_HANDLER, to swallow the exception. |
4559 | // |
4560 | // Parameters: |
4561 | // pExceptionInfo EXCEPTION_POINTERS for current exception |
4562 | // pv A constant as an INT_PTR. Must be COMPLUS_EXCEPTION_EXECUTE_HANDLER. |
4563 | // |
4564 | // Returns: |
4565 | // EXCEPTION_EXECUTE_HANDLER Generally returns this to swallow the exception. |
4566 | // |
4567 | // IMPORTANT!! READ ME!! |
4568 | // |
4569 | // This filter is very similar to DefaultCatchNoSwallowFilter, except when unhandled |
4570 | // exception policy/config dictate swallowing the exception. |
4571 | // If you make any changes to this function, look to see if the other one also needs |
4572 | // the same change. |
4573 | // |
4574 | LONG DefaultCatchFilter(EXCEPTION_POINTERS *ep, PVOID pv) |
4575 | { |
4576 | CONTRACTL |
4577 | { |
4578 | NOTHROW; |
4579 | GC_NOTRIGGER; |
4580 | MODE_ANY; |
4581 | FORBID_FAULT; |
4582 | SO_TOLERANT; |
4583 | } |
4584 | CONTRACTL_END; |
4585 | |
4586 | // |
4587 | // @TODO: this seems like a strong candidate for elimination due to duplication with |
4588 | // our vectored exception handler. |
4589 | // |
4590 | |
4591 | DefaultCatchFilterParam *pParam; |
4592 | pParam = (DefaultCatchFilterParam *) pv; |
4593 | |
4594 | // the only valid parameter for DefaultCatchFilter so far |
4595 | _ASSERTE(pParam->pv == COMPLUS_EXCEPTION_EXECUTE_HANDLER); |
4596 | |
4597 | PEXCEPTION_RECORD er = ep->ExceptionRecord; |
4598 | DWORD code = er->ExceptionCode; |
4599 | |
4600 | if (code == STATUS_SINGLE_STEP || code == STATUS_BREAKPOINT) |
4601 | { |
4602 | return UserBreakpointFilter(ep); |
4603 | } |
4604 | |
4605 | // return EXCEPTION_EXECUTE_HANDLER to swallow the exception. |
4606 | return EXCEPTION_EXECUTE_HANDLER; |
4607 | } // LONG DefaultCatchFilter() |
4608 | |
4609 | |
4610 | //****************************************************************************** |
4611 | // |
4612 | // DefaultCatchNoSwallowFilter |
4613 | // |
4614 | // The new default except filter (v2.0). For user breakpoints, call out to UserBreakpointFilter(). |
4615 | // Otherwise consults host policy and config file to return EXECUTE_HANDLER / CONTINUE_SEARCH. |
4616 | // |
4617 | // Parameters: |
4618 | // pExceptionInfo EXCEPTION_POINTERS for current exception |
4619 | // pv A constant as an INT_PTR. Must be COMPLUS_EXCEPTION_EXECUTE_HANDLER. |
4620 | // |
4621 | // Returns: |
4622 | // EXCEPTION_CONTINUE_SEARCH Generally returns this to let the exception go unhandled. |
4623 | // EXCEPTION_EXECUTE_HANDLER May return this to swallow the exception. |
4624 | // |
4625 | // IMPORTANT!! READ ME!! |
4626 | // |
4627 | // This filter is very similar to DefaultCatchFilter, except when unhandled |
4628 | // exception policy/config dictate swallowing the exception. |
4629 | // If you make any changes to this function, look to see if the other one also needs |
4630 | // the same change. |
4631 | // |
4632 | LONG DefaultCatchNoSwallowFilter(EXCEPTION_POINTERS *ep, PVOID pv) |
4633 | { |
4634 | CONTRACTL |
4635 | { |
4636 | THROWS; |
4637 | GC_TRIGGERS; |
4638 | MODE_ANY; |
4639 | } |
4640 | CONTRACTL_END; |
4641 | |
4642 | DefaultCatchFilterParam *pParam; pParam = (DefaultCatchFilterParam *) pv; |
4643 | |
4644 | // the only valid parameter for DefaultCatchFilter so far |
4645 | _ASSERTE(pParam->pv == COMPLUS_EXCEPTION_EXECUTE_HANDLER); |
4646 | |
4647 | PEXCEPTION_RECORD er = ep->ExceptionRecord; |
4648 | DWORD code = er->ExceptionCode; |
4649 | |
4650 | if (code == STATUS_SINGLE_STEP || code == STATUS_BREAKPOINT) |
4651 | { |
4652 | return UserBreakpointFilter(ep); |
4653 | } |
4654 | |
4655 | // If host policy or config file says "swallow"... |
4656 | if (SwallowUnhandledExceptions()) |
4657 | { // ...return EXCEPTION_EXECUTE_HANDLER to swallow the exception. |
4658 | return EXCEPTION_EXECUTE_HANDLER; |
4659 | } |
4660 | |
4661 | // If the exception is of a type that is always swallowed (ThreadAbort, AppDomainUnload)... |
4662 | if (ExceptionIsAlwaysSwallowed(ep)) |
4663 | { // ...return EXCEPTION_EXECUTE_HANDLER to swallow the exception. |
4664 | return EXCEPTION_EXECUTE_HANDLER; |
4665 | } |
4666 | |
4667 | // Otherwise, continue search. i.e. let the exception go unhandled (at least for now). |
4668 | return EXCEPTION_CONTINUE_SEARCH; |
4669 | } // LONG DefaultCatchNoSwallowFilter() |
4670 | |
4671 | // Note: This is used only for CoreCLR on WLC. |
4672 | // |
4673 | // We keep a pointer to the previous unhandled exception filter. After we install, we use |
4674 | // this to call the previous guy. When we un-install, we put them back. Putting them back |
4675 | // is a bug -- we have no guarantee that the DLL unload order matches the DLL load order -- we |
4676 | // may in fact be putting back a pointer to a DLL that has been unloaded. |
4677 | // |
4678 | |
4679 | // initialize to -1 because NULL won't detect difference between us not having installed our handler |
4680 | // yet and having installed it but the original handler was NULL. |
4681 | static LPTOP_LEVEL_EXCEPTION_FILTER g_pOriginalUnhandledExceptionFilter = (LPTOP_LEVEL_EXCEPTION_FILTER)-1; |
4682 | #define FILTER_NOT_INSTALLED (LPTOP_LEVEL_EXCEPTION_FILTER) -1 |
4683 | |
4684 | |
4685 | BOOL InstallUnhandledExceptionFilter() { |
4686 | STATIC_CONTRACT_NOTHROW; |
4687 | STATIC_CONTRACT_GC_NOTRIGGER; |
4688 | STATIC_CONTRACT_MODE_ANY; |
4689 | STATIC_CONTRACT_FORBID_FAULT; |
4690 | |
4691 | #ifndef FEATURE_PAL |
4692 | // We will be here only for CoreCLR on WLC since we dont |
4693 | // register UEF for SL. |
4694 | if (g_pOriginalUnhandledExceptionFilter == FILTER_NOT_INSTALLED) { |
4695 | |
4696 | #pragma prefast(push) |
4697 | #pragma prefast(suppress:28725, "Calling to SetUnhandledExceptionFilter is intentional in this case.") |
4698 | g_pOriginalUnhandledExceptionFilter = SetUnhandledExceptionFilter(COMUnhandledExceptionFilter); |
4699 | #pragma prefast(pop) |
4700 | |
4701 | // make sure is set (ie. is not our special value to indicate unset) |
4702 | LOG((LF_EH, LL_INFO10, "InstallUnhandledExceptionFilter registered UEF with OS for CoreCLR!\n" )); |
4703 | } |
4704 | _ASSERTE(g_pOriginalUnhandledExceptionFilter != FILTER_NOT_INSTALLED); |
4705 | #endif // !FEATURE_PAL |
4706 | |
4707 | // All done - successfully! |
4708 | return TRUE; |
4709 | } |
4710 | |
4711 | void UninstallUnhandledExceptionFilter() { |
4712 | STATIC_CONTRACT_NOTHROW; |
4713 | STATIC_CONTRACT_GC_NOTRIGGER; |
4714 | STATIC_CONTRACT_MODE_ANY; |
4715 | STATIC_CONTRACT_FORBID_FAULT; |
4716 | |
4717 | #ifndef FEATURE_PAL |
4718 | // We will be here only for CoreCLR on WLC or on Mac SL. |
4719 | if (g_pOriginalUnhandledExceptionFilter != FILTER_NOT_INSTALLED) { |
4720 | |
4721 | #pragma prefast(push) |
4722 | #pragma prefast(suppress:28725, "Calling to SetUnhandledExceptionFilter is intentional in this case.") |
4723 | SetUnhandledExceptionFilter(g_pOriginalUnhandledExceptionFilter); |
4724 | #pragma prefast(pop) |
4725 | |
4726 | g_pOriginalUnhandledExceptionFilter = FILTER_NOT_INSTALLED; |
4727 | LOG((LF_EH, LL_INFO10, "UninstallUnhandledExceptionFilter unregistered UEF from OS for CoreCLR!\n" )); |
4728 | } |
4729 | #endif // !FEATURE_PAL |
4730 | } |
4731 | |
4732 | // |
4733 | // Update the current throwable on the thread if necessary. If we're looking at one of our exceptions, and if the |
4734 | // current throwable on the thread is NULL, then we'll set it to something more useful based on the |
4735 | // LastThrownObject. |
4736 | // |
4737 | BOOL UpdateCurrentThrowable(PEXCEPTION_RECORD pExceptionRecord) |
4738 | { |
4739 | STATIC_CONTRACT_THROWS; |
4740 | STATIC_CONTRACT_MODE_ANY; |
4741 | STATIC_CONTRACT_GC_TRIGGERS; |
4742 | |
4743 | BOOL useLastThrownObject = FALSE; |
4744 | |
4745 | Thread* pThread = GetThread(); |
4746 | |
4747 | // GetThrowable needs cooperative. |
4748 | GCX_COOP(); |
4749 | |
4750 | if ((pThread->GetThrowable() == NULL) && (pThread->LastThrownObject() != NULL)) |
4751 | { |
4752 | // If GetThrowable is NULL and LastThrownObject is not, use lastThrownObject. |
4753 | // In current (June 05) implementation, this is only used to pass to |
4754 | // NotifyAppDomainsOfUnhandledException, which needs to get a throwable |
4755 | // from somewhere, with which to notify the AppDomains. |
4756 | useLastThrownObject = TRUE; |
4757 | |
4758 | if (IsComPlusException(pExceptionRecord)) |
4759 | { |
4760 | #ifndef WIN64EXCEPTIONS |
4761 | OBJECTREF oThrowable = pThread->LastThrownObject(); |
4762 | |
4763 | // @TODO: we have a problem on Win64 where we won't have any place to |
4764 | // store the throwable on an unhandled exception. Currently this |
4765 | // only effects the managed debugging services as they will try |
4766 | // to inspect the thread to see what the throwable is on an unhandled |
4767 | // exception.. (but clearly it needs to be fixed asap) |
4768 | // We have the same problem in EEPolicy::LogFatalError(). |
4769 | LOG((LF_EH, LL_INFO100, "UpdateCurrentThrowable: setting throwable to %s\n" , (oThrowable == NULL) ? "NULL" : oThrowable->GetMethodTable()->GetDebugClassName())); |
4770 | pThread->SafeSetThrowables(oThrowable); |
4771 | #endif // WIN64EXCEPTIONS |
4772 | } |
4773 | } |
4774 | |
4775 | return useLastThrownObject; |
4776 | } |
4777 | |
4778 | // |
4779 | // COMUnhandledExceptionFilter is used to catch all unhandled exceptions. |
4780 | // The debugger will either handle the exception, attach a debugger, or |
4781 | // notify an existing attached debugger. |
4782 | // |
4783 | |
4784 | struct SaveIPFilterParam |
4785 | { |
4786 | SLOT ExceptionEIP; |
4787 | }; |
4788 | |
4789 | LONG SaveIPFilter(EXCEPTION_POINTERS* ep, LPVOID pv) |
4790 | { |
4791 | WRAPPER_NO_CONTRACT; |
4792 | |
4793 | SaveIPFilterParam *pParam = (SaveIPFilterParam *) pv; |
4794 | pParam->ExceptionEIP = (SLOT)GetIP(ep->ContextRecord); |
4795 | DefaultCatchFilterParam param(COMPLUS_EXCEPTION_EXECUTE_HANDLER); |
4796 | return DefaultCatchFilter(ep, ¶m); |
4797 | } |
4798 | |
4799 | //------------------------------------------------------------------------------ |
4800 | // Description |
4801 | // Does not call any previous UnhandledExceptionFilter. The assumption is that |
4802 | // either it is inappropriate to call it (because we have elected to rip the |
4803 | // process without transitioning completely to the base of the thread), or |
4804 | // the caller has already consulted the previously installed UnhandledExceptionFilter. |
4805 | // |
4806 | // So we know we are ripping and Watson is appropriate. |
4807 | // |
4808 | // **** Note***** |
4809 | // This is a stack-sensitive function if we have an unhandled SO. |
4810 | // Do not allocate more than a few bytes on the stack or we risk taking an |
4811 | // AV while trying to throw up Watson. |
4812 | |
4813 | // Parameters |
4814 | // pExceptionInfo -- information about the exception that caused the error. |
4815 | // If the error is not the result of an exception, pass NULL for this |
4816 | // parameter |
4817 | // |
4818 | // Returns |
4819 | // EXCEPTION_CONTINUE_SEARCH -- we've done anything we will with the exception. |
4820 | // As far as the runtime is concerned, the process is doomed. |
4821 | // EXCEPTION_CONTINUE_EXECUTION -- means a debugger "caught" the exception and |
4822 | // wants to continue running. |
4823 | // EXCEPTION_EXECUTE_HANDLER -- CoreCLR only, and only when not running as a UEF. |
4824 | // Returned only if the host has asked us to swallow unhandled exceptions on |
4825 | // managed threads in an AD they (the host) creates. |
4826 | //------------------------------------------------------------------------------ |
4827 | LONG InternalUnhandledExceptionFilter_Worker( |
4828 | EXCEPTION_POINTERS *pExceptionInfo) // Information about the exception |
4829 | { |
4830 | STATIC_CONTRACT_THROWS; |
4831 | STATIC_CONTRACT_GC_TRIGGERS; |
4832 | STATIC_CONTRACT_MODE_ANY; |
4833 | |
4834 | #ifdef _DEBUG |
4835 | static int fBreakOnUEF = -1; |
4836 | if (fBreakOnUEF==-1) fBreakOnUEF = CLRConfig::GetConfigValue(CLRConfig::INTERNAL_BreakOnUEF); |
4837 | _ASSERTE(!fBreakOnUEF); |
4838 | #endif |
4839 | |
4840 | STRESS_LOG2(LF_EH, LL_INFO10, "In InternalUnhandledExceptionFilter_Worker, Exception = %x, sp = %p\n" , |
4841 | pExceptionInfo->ExceptionRecord->ExceptionCode, GetCurrentSP()); |
4842 | |
4843 | // If we can't enter the EE, done. |
4844 | if (g_fForbidEnterEE) |
4845 | { |
4846 | LOG((LF_EH, LL_INFO100, "InternalUnhandledExceptionFilter_Worker: g_fForbidEnterEE is TRUE\n" )); |
4847 | return EXCEPTION_CONTINUE_SEARCH; |
4848 | } |
4849 | |
4850 | |
4851 | if (GetEEPolicy()->GetActionOnFailure(FAIL_FatalRuntime) == eDisableRuntime) |
4852 | { |
4853 | ETaskType type = ::GetCurrentTaskType(); |
4854 | if (type != TT_UNKNOWN && type != TT_USER) |
4855 | { |
4856 | LOG((LF_EH, LL_INFO100, "InternalUnhandledExceptionFilter_Worker: calling EEPolicy::HandleFatalError\n" )); |
4857 | EEPolicy::HandleFatalError(COR_E_EXECUTIONENGINE, (UINT_PTR)GetIP(pExceptionInfo->ContextRecord), NULL, pExceptionInfo); |
4858 | } |
4859 | } |
4860 | |
4861 | // We don't do anything when this is called from an unmanaged thread. |
4862 | Thread *pThread = GetThread(); |
4863 | |
4864 | #ifdef _DEBUG |
4865 | static bool bBreakOnUncaught = false; |
4866 | static int fBreakOnUncaught = 0; |
4867 | |
4868 | if (!bBreakOnUncaught) |
4869 | { |
4870 | fBreakOnUncaught = CLRConfig::GetConfigValue(CLRConfig::INTERNAL_BreakOnUncaughtException); |
4871 | bBreakOnUncaught = true; |
4872 | } |
4873 | if (fBreakOnUncaught != 0) |
4874 | { |
4875 | if (pExceptionInfo->ExceptionRecord->ExceptionCode == STATUS_STACK_OVERFLOW) |
4876 | { |
4877 | // if we've got an uncaught SO, we don't have enough stack to pop a debug break. So instead, |
4878 | // loop infinitely and we can attach a debugger at that point and break in. |
4879 | LOG((LF_EH, LL_INFO100, "InternalUnhandledExceptionFilter_Worker: Infinite loop on uncaught SO\n" )); |
4880 | for ( ;; ) |
4881 | { |
4882 | } |
4883 | } |
4884 | else |
4885 | { |
4886 | LOG((LF_EH, LL_INFO100, "InternalUnhandledExceptionFilter_Worker: ASSERTING on uncaught\n" )); |
4887 | _ASSERTE(!"BreakOnUnCaughtException" ); |
4888 | } |
4889 | } |
4890 | #endif |
4891 | |
4892 | #ifdef _DEBUG_ADUNLOAD |
4893 | printf("%x InternalUnhandledExceptionFilter_Worker: Called for %x\n" , |
4894 | ((pThread == NULL) ? NULL : pThread->GetThreadId()), pExceptionInfo->ExceptionRecord->ExceptionCode); |
4895 | fflush(stdout); |
4896 | #endif |
4897 | |
4898 | // This shouldn't be possible, but MSVC re-installs us... for now, just bail if this happens. |
4899 | if (g_fNoExceptions) |
4900 | { |
4901 | return EXCEPTION_CONTINUE_SEARCH; |
4902 | } |
4903 | |
4904 | // Are we looking at a stack overflow here? |
4905 | if ((pThread != NULL) && !pThread->DetermineIfGuardPagePresent()) |
4906 | { |
4907 | g_fForbidEnterEE = true; |
4908 | } |
4909 | |
4910 | #ifdef DEBUGGING_SUPPORTED |
4911 | |
4912 | // Mark that this exception has gone unhandled. At the moment only the debugger will |
4913 | // ever look at this flag. This should come before any user-visible side effect of an exception |
4914 | // being unhandled as seen from managed code or from a debugger. These include the |
4915 | // managed unhandled notification callback, execution of catch/finally clauses, |
4916 | // receiving the managed debugger unhandled exception event, |
4917 | // the OS sending the debugger 2nd pass native exception notification, etc. |
4918 | // |
4919 | // This needs to be done before the check for TSNC_ProcessedUnhandledException because it is perfectly |
4920 | // legitimate (though rare) for the debugger to be inspecting exceptions which are nested in finally |
4921 | // clauses that run after an unhandled exception has already occurred on the thread |
4922 | if ((pThread != NULL) && pThread->IsExceptionInProgress()) |
4923 | { |
4924 | LOG((LF_EH, LL_INFO1000, "InternalUnhandledExceptionFilter_Worker: Set unhandled exception flag at %p\n" , |
4925 | pThread->GetExceptionState()->GetFlags() )); |
4926 | pThread->GetExceptionState()->GetFlags()->SetUnhandled(); |
4927 | } |
4928 | #endif |
4929 | |
4930 | // If we have already done unhandled exception processing for this thread, then |
4931 | // simply return back. See comment in threads.h for details for the flag |
4932 | // below. |
4933 | // |
4934 | if (pThread && (pThread->HasThreadStateNC(Thread::TSNC_ProcessedUnhandledException) || pThread->HasThreadStateNC(Thread::TSNC_AppDomainContainUnhandled))) |
4935 | { |
4936 | // This assert shouldnt be hit in CoreCLR since: |
4937 | // |
4938 | // 1) It has no concept of managed entry point that is invoked by the shim. You can |
4939 | // only run managed code via hosting APIs that will run code in non-default domains. |
4940 | // |
4941 | // 2) Managed threads cannot be created in DefaultDomain since no user code executes |
4942 | // in default domain. |
4943 | // |
4944 | // So, if this is hit, something is not right! |
4945 | if (pThread->HasThreadStateNC(Thread::TSNC_ProcessedUnhandledException)) |
4946 | { |
4947 | _ASSERTE(!"How come a thread with TSNC_ProcessedUnhandledException state entered the UEF on CoreCLR?" ); |
4948 | } |
4949 | |
4950 | LOG((LF_EH, LL_INFO100, "InternalUnhandledExceptionFilter_Worker: have already processed unhandled exception for this thread.\n" )); |
4951 | return EXCEPTION_CONTINUE_SEARCH; |
4952 | } |
4953 | |
4954 | LOG((LF_EH, LL_INFO100, "InternalUnhandledExceptionFilter_Worker: Handling\n" )); |
4955 | |
4956 | struct Param : SaveIPFilterParam |
4957 | { |
4958 | EXCEPTION_POINTERS *pExceptionInfo; |
4959 | Thread *pThread; |
4960 | LONG retval; |
4961 | BOOL fIgnore; |
4962 | }; Param param; |
4963 | |
4964 | param.ExceptionEIP = 0; |
4965 | param.pExceptionInfo = pExceptionInfo; |
4966 | param.pThread = pThread; |
4967 | param.retval = EXCEPTION_CONTINUE_SEARCH; // Result of UEF filter. |
4968 | |
4969 | // Is this a particular kind of exception that we'd like to ignore? |
4970 | param.fIgnore = ((param.pExceptionInfo->ExceptionRecord->ExceptionCode == STATUS_BREAKPOINT) || |
4971 | (param.pExceptionInfo->ExceptionRecord->ExceptionCode == STATUS_SINGLE_STEP)); |
4972 | |
4973 | PAL_TRY(Param *, pParam, ¶m) |
4974 | { |
4975 | // If fIgnore, then this is some sort of breakpoint, not a "normal" unhandled exception. But, the |
4976 | // breakpoint is due to an int3 or debugger step instruction, not due to calling Debugger.Break() |
4977 | TypeOfReportedError tore = pParam->fIgnore ? TypeOfReportedError::NativeBreakpoint : TypeOfReportedError::UnhandledException; |
4978 | |
4979 | // |
4980 | // If this exception is on a thread without managed code, then report this as a NativeThreadUnhandledException |
4981 | // |
4982 | // The thread object may exist if there was once managed code on the stack, but if the exception never |
4983 | // bubbled thru managed code, ie no managed code is on its stack, then this is a native unhandled exception |
4984 | // |
4985 | // Ignore breakpoints and single-step. |
4986 | if (!pParam->fIgnore) |
4987 | { // Possibly interesting exception. Is there no Thread at all? Or, is there a Thread, |
4988 | // but with no exception at all on it? |
4989 | if ((pParam->pThread == NULL) || |
4990 | (pParam->pThread->IsThrowableNull() && pParam->pThread->IsLastThrownObjectNull()) ) |
4991 | { // Whatever this exception is, we don't know about it. Treat as Native. |
4992 | tore = TypeOfReportedError::NativeThreadUnhandledException; |
4993 | } |
4994 | } |
4995 | |
4996 | // If there is no throwable on the thread, go ahead and update from the last thrown exception if possible. |
4997 | // Note: don't do this for exceptions that we're going to ignore below anyway... |
4998 | BOOL useLastThrownObject = FALSE; |
4999 | if (!pParam->fIgnore && (pParam->pThread != NULL)) |
5000 | { |
5001 | useLastThrownObject = UpdateCurrentThrowable(pParam->pExceptionInfo->ExceptionRecord); |
5002 | } |
5003 | |
5004 | #ifdef DEBUGGING_SUPPORTED |
5005 | |
5006 | LOG((LF_EH, LL_INFO100, "InternalUnhandledExceptionFilter_Worker: Notifying Debugger...\n" )); |
5007 | |
5008 | // If we are using the throwable in LastThrownObject, mark that it is now unhandled |
5009 | if ((pParam->pThread != NULL) && useLastThrownObject) |
5010 | { |
5011 | LOG((LF_EH, LL_INFO1000, "InternalUnhandledExceptionFilter_Worker: Set lto is unhandled\n" )); |
5012 | pParam->pThread->MarkLastThrownObjectUnhandled(); |
5013 | } |
5014 | |
5015 | // |
5016 | // We don't want the managed debugger to try to "intercept" breakpoints |
5017 | // or singlestep exceptions. |
5018 | // TODO: why does the exception handling code need to set this? Shouldn't the debugger code |
5019 | // be able to determine what it can/should intercept? |
5020 | if ((pParam->pThread != NULL) && pParam->pThread->IsExceptionInProgress() && pParam->fIgnore) |
5021 | { |
5022 | pParam->pThread->GetExceptionState()->GetFlags()->SetDebuggerInterceptNotPossible(); |
5023 | } |
5024 | |
5025 | |
5026 | if (pParam->pThread != NULL) |
5027 | { |
5028 | BOOL fIsProcessTerminating = TRUE; |
5029 | |
5030 | // In CoreCLR, we can be asked to not let an exception go unhandled on managed threads in a given AppDomain. |
5031 | // If the exception reaches the top of the thread's stack, we simply deliver AppDomain's UnhandledException event and |
5032 | // return back to the filter, instead of letting the process terminate because of unhandled exception. |
5033 | |
5034 | // Below is how we perform the check: |
5035 | // |
5036 | // 1) The flag is specified on the AD when it is created by the host and all managed threads created |
5037 | // in such an AD will inherit the flag. For non-finalizer and non-threadpool threads, we check the flag against the thread. |
5038 | // 2) The finalizer thread always switches to the AD of the object that is going to be finalized. Thus, |
5039 | // while it wont have the flag specified, the AD it switches to will. |
5040 | // 3) The threadpool thread also switches to the correct AD before executing the request. The thread wont have the |
5041 | // flag specified, but the AD it switches to will. |
5042 | |
5043 | // This code must only be exercised when running as a normal filter; returning |
5044 | // EXCEPTION_EXECUTE_HANDLER is not valid if this code is being invoked from |
5045 | // the UEF. |
5046 | // Fortunately, we should never get into this case, since the thread flag about |
5047 | // ignoring unhandled exceptions cannot be set on the default domain. |
5048 | |
5049 | if (IsFinalizerThread() || (pParam->pThread->IsThreadPoolThread())) |
5050 | fIsProcessTerminating = !(pParam->pThread->GetDomain()->IgnoreUnhandledExceptions()); |
5051 | else |
5052 | fIsProcessTerminating = !(pParam->pThread->HasThreadStateNC(Thread::TSNC_IgnoreUnhandledExceptions)); |
5053 | |
5054 | #ifndef FEATURE_PAL |
5055 | // Setup the watson bucketing details for UE processing. |
5056 | // do this before notifying appdomains of the UE so if an AD attempts to |
5057 | // retrieve the bucket params in the UE event handler it gets the correct data. |
5058 | SetupWatsonBucketsForUEF(useLastThrownObject); |
5059 | #endif // !FEATURE_PAL |
5060 | |
5061 | // Send notifications to the AppDomains. |
5062 | NotifyAppDomainsOfUnhandledException(pParam->pExceptionInfo, NULL, useLastThrownObject, fIsProcessTerminating /*isTerminating*/); |
5063 | |
5064 | // If the process is not terminating, then return back to the filter and ask it to execute |
5065 | if (!fIsProcessTerminating) |
5066 | { |
5067 | pParam->retval = EXCEPTION_EXECUTE_HANDLER; |
5068 | goto lDone; |
5069 | } |
5070 | } |
5071 | else |
5072 | { |
5073 | LOG((LF_EH, LL_INFO100, "InternalUnhandledExceptionFilter_Worker: Not collecting bucket information as thread object does not exist\n" )); |
5074 | } |
5075 | |
5076 | // AppDomain.UnhandledException event could have thrown an exception that would have gone unhandled in managed code. |
5077 | // The runtime swallows all such exceptions. Hence, if we are not using LastThrownObject and the current LastThrownObject |
5078 | // is not the same as the one in active exception tracker (if available), then update the last thrown object. |
5079 | if ((pParam->pThread != NULL) && (!useLastThrownObject)) |
5080 | { |
5081 | GCX_COOP_NO_DTOR(); |
5082 | |
5083 | OBJECTREF oThrowable = pParam->pThread->GetThrowable(); |
5084 | if ((oThrowable != NULL) && (pParam->pThread->LastThrownObject() != oThrowable)) |
5085 | { |
5086 | pParam->pThread->SafeSetLastThrownObject(oThrowable); |
5087 | LOG((LF_EH, LL_INFO100, "InternalUnhandledExceptionFilter_Worker: Resetting the LastThrownObject as it appears to have changed.\n" )); |
5088 | } |
5089 | |
5090 | GCX_COOP_NO_DTOR_END(); |
5091 | } |
5092 | |
5093 | // Launch Watson and see if we want to debug the process |
5094 | // |
5095 | // Note that we need to do this before "ignoring" exceptions like |
5096 | // breakpoints and single step exceptions |
5097 | // |
5098 | |
5099 | LOG((LF_EH, LL_INFO100, "InternalUnhandledExceptionFilter_Worker: Launching Watson at sp %p ...\n" , GetCurrentSP())); |
5100 | |
5101 | if (WatsonLastChance(pParam->pThread, pParam->pExceptionInfo, tore) == EXCEPTION_CONTINUE_EXECUTION) |
5102 | { |
5103 | LOG((LF_EH, LL_INFO100, "InternalUnhandledExceptionFilter_Worker: debugger ==> EXCEPTION_CONTINUE_EXECUTION\n" )); |
5104 | pParam->retval = EXCEPTION_CONTINUE_EXECUTION; |
5105 | goto lDone; |
5106 | } |
5107 | |
5108 | LOG((LF_EH, LL_INFO100, "InternalUnhandledExceptionFilter_Worker: ... returned.\n" )); |
5109 | #endif // DEBUGGING_SUPPORTED |
5110 | |
5111 | |
5112 | // |
5113 | // Except for notifying debugger, ignore exception if unmanaged, or |
5114 | // if it's a debugger-generated exception or user breakpoint exception. |
5115 | // |
5116 | if (tore.GetType() == TypeOfReportedError::NativeThreadUnhandledException) |
5117 | { |
5118 | pParam->retval = EXCEPTION_CONTINUE_SEARCH; |
5119 | #if defined(FEATURE_EVENT_TRACE) && !defined(FEATURE_PAL) |
5120 | DoReportForUnhandledNativeException(pParam->pExceptionInfo); |
5121 | #endif |
5122 | goto lDone; |
5123 | } |
5124 | |
5125 | if (pParam->fIgnore) |
5126 | { |
5127 | LOG((LF_EH, LL_INFO100, "InternalUnhandledExceptionFilter_Worker, ignoring the exception\n" )); |
5128 | pParam->retval = EXCEPTION_CONTINUE_SEARCH; |
5129 | #if defined(FEATURE_EVENT_TRACE) && !defined(FEATURE_PAL) |
5130 | DoReportForUnhandledNativeException(pParam->pExceptionInfo); |
5131 | #endif |
5132 | goto lDone; |
5133 | } |
5134 | |
5135 | LOG((LF_EH, LL_INFO100, "InternalUnhandledExceptionFilter_Worker: Calling DefaultCatchHandler\n" )); |
5136 | |
5137 | // Call our default catch handler to do the managed unhandled exception work. |
5138 | DefaultCatchHandler(pParam->pExceptionInfo, NULL, useLastThrownObject, |
5139 | TRUE /*isTerminating*/, FALSE /*isThreadBaseFIlter*/, FALSE /*sendAppDomainEvents*/, TRUE /* sendWindowsEventLog */); |
5140 | |
5141 | lDone: ; |
5142 | } |
5143 | PAL_EXCEPT_FILTER (SaveIPFilter) |
5144 | { |
5145 | // Should never get here. |
5146 | #ifdef _DEBUG |
5147 | char buffer[200]; |
5148 | sprintf_s(buffer, 200, "\nInternal error: Uncaught exception was thrown from IP = %p in UnhandledExceptionFilter_Worker on thread 0x%08x\n" , |
5149 | param.ExceptionEIP, ((GetThread() == NULL) ? NULL : GetThread()->GetThreadId())); |
5150 | PrintToStdErrA(buffer); |
5151 | _ASSERTE(!"Unexpected exception in UnhandledExceptionFilter_Worker" ); |
5152 | #endif |
5153 | EEPOLICY_HANDLE_FATAL_ERROR(COR_E_EXECUTIONENGINE) |
5154 | } |
5155 | PAL_ENDTRY; |
5156 | |
5157 | //if (param.fIgnore) |
5158 | //{ |
5159 | // VC's try/catch ignores breakpoint or single step exceptions. We can not continue running. |
5160 | // TerminateProcess(GetCurrentProcess(), pExceptionInfo->ExceptionRecord->ExceptionCode); |
5161 | //} |
5162 | |
5163 | return param.retval; |
5164 | } // LONG InternalUnhandledExceptionFilter_Worker() |
5165 | |
5166 | //------------------------------------------------------------------------------ |
5167 | // Description |
5168 | // Calls our InternalUnhandledExceptionFilter for Watson at the appropriate |
5169 | // place in the chain. |
5170 | // |
5171 | // For non-side-by-side CLR's, we call everyone else's UEF first. |
5172 | // |
5173 | // For side-by-side CLR's, we call our own filter first. This is primary |
5174 | // so Whidbey's UEF won't put up a second dialog box. In exchange, |
5175 | // side-by-side CLR's won't put up UI's unless the EH really came |
5176 | // from that instance's managed code. |
5177 | // |
5178 | // Parameters |
5179 | // pExceptionInfo -- information about the exception that caused the error. |
5180 | // If the error is not the result of an exception, pass NULL for this |
5181 | // parameter |
5182 | // |
5183 | // Returns |
5184 | // EXCEPTION_CONTINUE_SEARCH -- we've done anything we will with the exception. |
5185 | // As far as the runtime is concerned, the process is doomed. |
5186 | // EXCEPTION_CONTINUE_EXECUTION -- means a debugger "caught" the exception and |
5187 | // wants to continue running. |
5188 | //------------------------------------------------------------------------------ |
5189 | LONG InternalUnhandledExceptionFilter( |
5190 | EXCEPTION_POINTERS *pExceptionInfo) // Information about the exception |
5191 | { |
5192 | STATIC_CONTRACT_THROWS; |
5193 | STATIC_CONTRACT_GC_TRIGGERS; |
5194 | STATIC_CONTRACT_MODE_ANY; |
5195 | // We don't need to be SO-robust for an unhandled exception |
5196 | SO_NOT_MAINLINE_FUNCTION; |
5197 | |
5198 | LOG((LF_EH, LL_INFO100, "InternalUnhandledExceptionFilter: at sp %p.\n" , GetCurrentSP())); |
5199 | |
5200 | // Side-by-side UEF: Calls ours first, then the rest (unless we put up a UI for |
5201 | // the exception.) |
5202 | |
5203 | LONG retval = InternalUnhandledExceptionFilter_Worker(pExceptionInfo); // Result of UEF filter. |
5204 | |
5205 | // Keep looking, or done? |
5206 | if (retval != EXCEPTION_CONTINUE_SEARCH) |
5207 | { // done. |
5208 | return retval; |
5209 | } |
5210 | |
5211 | BOOL fShouldOurUEFDisplayUI = ShouldOurUEFDisplayUI(pExceptionInfo); |
5212 | |
5213 | // If this is a managed exception thrown by this instance of the CLR, the exception is no one's |
5214 | // business but ours (nudge, nudge: Whidbey). Break the UEF chain at this point. |
5215 | if (fShouldOurUEFDisplayUI) |
5216 | { |
5217 | return retval; |
5218 | } |
5219 | |
5220 | // Chaining back to previous UEF handler could be a potential security risk. See |
5221 | // http://uninformed.org/index.cgi?v=4&a=5&p=1 for details. We are not alone in |
5222 | // stopping the chain - CRT (as of Orcas) is also doing that. |
5223 | // |
5224 | // The change below applies to a thread that starts in native mode and transitions to managed. |
5225 | |
5226 | // Let us assume the process loaded two CoreCLRs, C1 and C2, in that order. Thus, in the UEF chain |
5227 | // (assuming no other entity setup their UEF), C2?s UEF will be the topmost. |
5228 | // |
5229 | // Now, assume the stack looks like the following (stack grows down): |
5230 | // |
5231 | // Native frame |
5232 | // Managed Frame (C1) |
5233 | // Managed Frame (C2) |
5234 | // Managed Frame (C1) |
5235 | // Managed Frame (C2) |
5236 | // Managed Frame (C1) |
5237 | // |
5238 | // Suppose an exception is thrown in C1 instance in the last managed frame and it goes unhandled. Eventually |
5239 | // it will reach the OS which will invoke the UEF. Note that the topmost UEF belongs to C2 instance and it |
5240 | // will start processing the exception. C2?s UEF could return EXCEPTION_CONTINUE_SEARCH to indicate |
5241 | // that we should handoff the processing to the last installed UEF. In the example above, we would handoff |
5242 | // the control to the UEF of the CoreCLR instance that actually threw the exception today. In reality, it |
5243 | // could be some unknown code too. |
5244 | // |
5245 | // Not chaining back to the last UEF, in the case of this example, would imply that certain notifications |
5246 | // (e.g. Unhandled Exception Notification to the AppDomain) specific to the instance that raised the exception |
5247 | // will not get fired. However, similar behavior can happen today if another UEF sits between |
5248 | // C1 and C2 and that may not callback to C1 or perhaps just terminate process. |
5249 | // |
5250 | // For CoreCLR, this will not be an issue. See |
5251 | // http://sharepoint/sites/clros/Shared%20Documents/Design%20Documents/EH/Chaining%20in%20%20UEF%20-%20One%20Pager.docx |
5252 | // for details. |
5253 | // |
5254 | // Note: Also see the conditional UEF registration with the OS in EEStartupHelper. |
5255 | |
5256 | // We would be here only on CoreCLR for WLC since we dont register |
5257 | // the UEF with the OS for SL. |
5258 | if (g_pOriginalUnhandledExceptionFilter != FILTER_NOT_INSTALLED |
5259 | && g_pOriginalUnhandledExceptionFilter != NULL) |
5260 | { |
5261 | STRESS_LOG1(LF_EH, LL_INFO100, "InternalUnhandledExceptionFilter: Not chaining back to previous UEF at address %p on CoreCLR!\n" , g_pOriginalUnhandledExceptionFilter); |
5262 | } |
5263 | |
5264 | return retval; |
5265 | |
5266 | } // LONG InternalUnhandledExceptionFilter() |
5267 | |
5268 | // This filter is used to trigger unhandled exception processing for the entrypoint thread |
5269 | // incase an exception goes unhandled from it. This makes us independent of the OS |
5270 | // UEF mechanism to invoke our registered UEF to trigger CLR specific unhandled exception |
5271 | // processing since that can be skipped if another UEF registered over ours and not chain back. |
5272 | LONG EntryPointFilter(PEXCEPTION_POINTERS pExceptionInfo, PVOID _pData) |
5273 | { |
5274 | CONTRACTL |
5275 | { |
5276 | THROWS; |
5277 | GC_TRIGGERS; |
5278 | MODE_ANY; |
5279 | SO_TOLERANT; |
5280 | } |
5281 | CONTRACTL_END; |
5282 | |
5283 | LONG ret = -1; |
5284 | |
5285 | BEGIN_SO_INTOLERANT_CODE_NO_THROW_CHECK_THREAD(return EXCEPTION_CONTINUE_SEARCH;); |
5286 | |
5287 | // Invoke the UEF worker to perform unhandled exception processing |
5288 | ret = InternalUnhandledExceptionFilter_Worker (pExceptionInfo); |
5289 | |
5290 | Thread* pThread = GetThread(); |
5291 | if (pThread) |
5292 | { |
5293 | // Set the flag that we have done unhandled exception processing for this thread |
5294 | // so that we dont duplicate the effort in the UEF. |
5295 | // |
5296 | // For details on this flag, refer to threads.h. |
5297 | LOG((LF_EH, LL_INFO100, "EntryPointFilter: setting TSNC_ProcessedUnhandledException\n" )); |
5298 | pThread->SetThreadStateNC(Thread::TSNC_ProcessedUnhandledException); |
5299 | } |
5300 | |
5301 | |
5302 | END_SO_INTOLERANT_CODE; |
5303 | |
5304 | return ret; |
5305 | } |
5306 | |
5307 | //------------------------------------------------------------------------------ |
5308 | // Description |
5309 | // The actual UEF. Defers to InternalUnhandledExceptionFilter. |
5310 | // |
5311 | // Updated to be in its own code segment named CLR_UEF_SECTION_NAME to prevent |
5312 | // "VirtualProtect" calls from affecting its pages and thus, its |
5313 | // invocation. For details, see the comment within the implementation of |
5314 | // CExecutionEngine::ClrVirtualProtect. |
5315 | // |
5316 | // Parameters |
5317 | // pExceptionInfo -- information about the exception |
5318 | // |
5319 | // Returns |
5320 | // the result of calling InternalUnhandledExceptionFilter |
5321 | //------------------------------------------------------------------------------ |
5322 | #if !defined(FEATURE_PAL) |
5323 | #pragma code_seg(push, uef, CLR_UEF_SECTION_NAME) |
5324 | #endif // !FEATURE_PAL |
5325 | LONG __stdcall COMUnhandledExceptionFilter( // EXCEPTION_CONTINUE_SEARCH or EXCEPTION_CONTINUE_EXECUTION |
5326 | EXCEPTION_POINTERS *pExceptionInfo) // Information about the exception. |
5327 | { |
5328 | STATIC_CONTRACT_THROWS; |
5329 | STATIC_CONTRACT_GC_TRIGGERS; |
5330 | STATIC_CONTRACT_MODE_ANY; |
5331 | // We don't need to be SO-robust for an unhandled exception |
5332 | SO_NOT_MAINLINE_FUNCTION; |
5333 | |
5334 | LONG retVal = EXCEPTION_CONTINUE_SEARCH; |
5335 | |
5336 | // Incase of unhandled exceptions on managed threads, we kick in our UE processing at the thread base and also invoke |
5337 | // UEF callbacks that various runtimes have registered with us. Once the callbacks return, we return back to the OS |
5338 | // to give other registered UEFs a chance to do their custom processing. |
5339 | // |
5340 | // If the topmost UEF registered with the OS belongs to mscoruef.dll (or someone chained back to its UEF callback), |
5341 | // it will start invoking the UEF callbacks (which is this function, COMUnhandledExceptionFiler) registered by |
5342 | // various runtimes again. |
5343 | // |
5344 | // Thus, check if this UEF has already been invoked in context of this thread and runtime and if so, dont invoke it again. |
5345 | if (GetThread() && (GetThread()->HasThreadStateNC(Thread::TSNC_ProcessedUnhandledException) || |
5346 | GetThread()->HasThreadStateNC(Thread::TSNC_AppDomainContainUnhandled))) |
5347 | { |
5348 | LOG((LF_EH, LL_INFO10, "Exiting COMUnhandledExceptionFilter since we have already done UE processing for this thread!\n" )); |
5349 | return retVal; |
5350 | } |
5351 | |
5352 | |
5353 | retVal = InternalUnhandledExceptionFilter(pExceptionInfo); |
5354 | |
5355 | // If thread object exists, mark that this thread has done unhandled exception processing |
5356 | if (GetThread()) |
5357 | { |
5358 | LOG((LF_EH, LL_INFO100, "COMUnhandledExceptionFilter: setting TSNC_ProcessedUnhandledException\n" )); |
5359 | GetThread()->SetThreadStateNC(Thread::TSNC_ProcessedUnhandledException); |
5360 | } |
5361 | |
5362 | return retVal; |
5363 | } // LONG __stdcall COMUnhandledExceptionFilter() |
5364 | #if !defined(FEATURE_PAL) |
5365 | #pragma code_seg(pop, uef) |
5366 | #endif // !FEATURE_PAL |
5367 | |
5368 | void PrintStackTraceToStdout(); |
5369 | |
5370 | static SString GetExceptionMessageWrapper(Thread* pThread, OBJECTREF throwable) |
5371 | { |
5372 | STATIC_CONTRACT_THROWS; |
5373 | STATIC_CONTRACT_MODE_COOPERATIVE; |
5374 | STATIC_CONTRACT_GC_TRIGGERS; |
5375 | |
5376 | StackSString result; |
5377 | |
5378 | INSTALL_NESTED_EXCEPTION_HANDLER(pThread->GetFrame()); |
5379 | GetExceptionMessage(throwable, result); |
5380 | UNINSTALL_NESTED_EXCEPTION_HANDLER(); |
5381 | |
5382 | return result; |
5383 | } |
5384 | |
5385 | void STDMETHODCALLTYPE |
5386 | DefaultCatchHandlerExceptionMessageWorker(Thread* pThread, |
5387 | OBJECTREF throwable, |
5388 | __inout_ecount(buf_size) WCHAR *buf, |
5389 | const int buf_size, |
5390 | BOOL sendWindowsEventLog) |
5391 | { |
5392 | GCPROTECT_BEGIN(throwable); |
5393 | if (throwable != NULL) |
5394 | { |
5395 | PrintToStdErrA("\n" ); |
5396 | |
5397 | if (FAILED(UtilLoadResourceString(CCompRC::Error, IDS_EE_UNHANDLED_EXCEPTION, buf, buf_size))) |
5398 | { |
5399 | wcsncpy_s(buf, buf_size, SZ_UNHANDLED_EXCEPTION, SZ_UNHANDLED_EXCEPTION_CHARLEN); |
5400 | } |
5401 | |
5402 | PrintToStdErrW(buf); |
5403 | PrintToStdErrA(" " ); |
5404 | |
5405 | SString message = GetExceptionMessageWrapper(pThread, throwable); |
5406 | |
5407 | if (!message.IsEmpty()) |
5408 | { |
5409 | NPrintToStdErrW(message, message.GetCount()); |
5410 | } |
5411 | |
5412 | PrintToStdErrA("\n" ); |
5413 | |
5414 | #if defined(FEATURE_EVENT_TRACE) && !defined(FEATURE_PAL) |
5415 | // Send the log to Windows Event Log |
5416 | if (sendWindowsEventLog && ShouldLogInEventLog()) |
5417 | { |
5418 | EX_TRY |
5419 | { |
5420 | EventReporter reporter(EventReporter::ERT_UnhandledException); |
5421 | |
5422 | if (IsException(throwable->GetMethodTable())) |
5423 | { |
5424 | if (!message.IsEmpty()) |
5425 | { |
5426 | reporter.AddDescription(message); |
5427 | } |
5428 | reporter.Report(); |
5429 | } |
5430 | else |
5431 | { |
5432 | StackSString s; |
5433 | TypeString::AppendType(s, TypeHandle(throwable->GetMethodTable()), TypeString::FormatNamespace | TypeString::FormatFullInst); |
5434 | reporter.AddDescription(s); |
5435 | LogCallstackForEventReporter(reporter); |
5436 | } |
5437 | } |
5438 | EX_CATCH |
5439 | { |
5440 | } |
5441 | EX_END_CATCH(SwallowAllExceptions); |
5442 | } |
5443 | #endif |
5444 | } |
5445 | GCPROTECT_END(); |
5446 | } |
5447 | |
5448 | //****************************************************************************** |
5449 | // DefaultCatchHandler -- common processing for otherwise uncaught exceptions. |
5450 | //****************************************************************************** |
5451 | void STDMETHODCALLTYPE |
5452 | DefaultCatchHandler(PEXCEPTION_POINTERS pExceptionPointers, |
5453 | OBJECTREF *pThrowableIn, |
5454 | BOOL useLastThrownObject, |
5455 | BOOL isTerminating, |
5456 | BOOL isThreadBaseFilter, |
5457 | BOOL sendAppDomainEvents, |
5458 | BOOL sendWindowsEventLog) |
5459 | { |
5460 | CONTRACTL |
5461 | { |
5462 | THROWS; |
5463 | GC_TRIGGERS; |
5464 | MODE_ANY; |
5465 | } |
5466 | CONTRACTL_END; |
5467 | |
5468 | // <TODO> The strings in here should be translatable.</TODO> |
5469 | LOG((LF_EH, LL_INFO10, "In DefaultCatchHandler\n" )); |
5470 | |
5471 | #if defined(_DEBUG) |
5472 | static bool bHaveInitialized_BreakOnUncaught = false; |
5473 | enum BreakOnUncaughtAction { |
5474 | breakOnNone = 0, // Default. |
5475 | breakOnAll = 1, // Always break. |
5476 | breakSelective = 2, // Break on exceptions application can catch, |
5477 | // but not ThreadAbort, AppdomainUnload |
5478 | breakOnMax = 2 |
5479 | }; |
5480 | static DWORD breakOnUncaught = breakOnNone; |
5481 | |
5482 | if (!bHaveInitialized_BreakOnUncaught) |
5483 | { |
5484 | breakOnUncaught = CLRConfig::GetConfigValue(CLRConfig::INTERNAL_BreakOnUncaughtException); |
5485 | if (breakOnUncaught > breakOnMax) |
5486 | { // Could turn it off completely, or turn into legal value. Since it is debug code, be accommodating. |
5487 | breakOnUncaught = breakOnAll; |
5488 | } |
5489 | bHaveInitialized_BreakOnUncaught = true; |
5490 | } |
5491 | |
5492 | if (breakOnUncaught == breakOnAll) |
5493 | { |
5494 | _ASSERTE(!"BreakOnUnCaughtException" ); |
5495 | } |
5496 | |
5497 | int suppressSelectiveBreak = false; // to filter for the case where breakOnUncaught == "2" |
5498 | #endif |
5499 | |
5500 | Thread *pThread = GetThread(); |
5501 | |
5502 | // The following reduces a window for a race during shutdown. |
5503 | if (!pThread) |
5504 | { |
5505 | _ASSERTE(g_fEEShutDown); |
5506 | return; |
5507 | } |
5508 | |
5509 | _ASSERTE(pThread); |
5510 | |
5511 | ThreadPreventAsyncHolder prevAsync; |
5512 | |
5513 | GCX_COOP(); |
5514 | |
5515 | OBJECTREF throwable; |
5516 | |
5517 | if (pThrowableIn != NULL) |
5518 | { |
5519 | throwable = *pThrowableIn; |
5520 | } |
5521 | else if (useLastThrownObject) |
5522 | { |
5523 | throwable = pThread->LastThrownObject(); |
5524 | } |
5525 | else |
5526 | { |
5527 | throwable = pThread->GetThrowable(); |
5528 | } |
5529 | |
5530 | // If we've got no managed object, then we can't send an event or print a message, so we just return. |
5531 | if (throwable == NULL) |
5532 | { |
5533 | #ifdef LOGGING |
5534 | if (!pThread->IsRudeAbortInitiated()) |
5535 | { |
5536 | LOG((LF_EH, LL_INFO10, "Unhandled exception, throwable == NULL\n" )); |
5537 | } |
5538 | #endif |
5539 | |
5540 | return; |
5541 | } |
5542 | |
5543 | #ifdef _DEBUG |
5544 | DWORD unbreakableLockCount = 0; |
5545 | // Do not care about lock check for unhandled exception. |
5546 | while (pThread->HasUnbreakableLock()) |
5547 | { |
5548 | pThread->DecUnbreakableLockCount(); |
5549 | unbreakableLockCount ++; |
5550 | } |
5551 | BOOL fOwnsSpinLock = pThread->HasThreadStateNC(Thread::TSNC_OwnsSpinLock); |
5552 | if (fOwnsSpinLock) |
5553 | { |
5554 | pThread->ResetThreadStateNC(Thread::TSNC_OwnsSpinLock); |
5555 | } |
5556 | #endif |
5557 | |
5558 | GCPROTECT_BEGIN(throwable); |
5559 | //BOOL IsStackOverflow = (throwable->GetMethodTable() == g_pStackOverflowExceptionClass); |
5560 | BOOL IsOutOfMemory = (throwable->GetMethodTable() == g_pOutOfMemoryExceptionClass); |
5561 | |
5562 | // Notify the AppDomain that we have taken an unhandled exception. Can't notify of stack overflow -- guard |
5563 | // page is not yet reset. |
5564 | BOOL SentEvent = FALSE; |
5565 | |
5566 | // Send up the unhandled exception appdomain event. |
5567 | if (sendAppDomainEvents) |
5568 | { |
5569 | SentEvent = NotifyAppDomainsOfUnhandledException(pExceptionPointers, &throwable, useLastThrownObject, isTerminating); |
5570 | } |
5571 | |
5572 | const int buf_size = 128; |
5573 | WCHAR buf[buf_size] = {0}; |
5574 | |
5575 | // See detailed explanation of this flag in threads.cpp. But the basic idea is that we already |
5576 | // reported the exception in the AppDomain where it went unhandled, so we don't need to report |
5577 | // it at the process level. |
5578 | // Print the unhandled exception message. |
5579 | if (!pThread->HasThreadStateNC(Thread::TSNC_AppDomainContainUnhandled)) |
5580 | { |
5581 | EX_TRY |
5582 | { |
5583 | EX_TRY |
5584 | { |
5585 | // If this isn't ThreadAbortException, we want to print a stack trace to indicate why this thread abruptly |
5586 | // terminated. Exceptions kill threads rarely enough that an uncached name check is reasonable. |
5587 | BOOL dump = TRUE; |
5588 | |
5589 | if (/*IsStackOverflow ||*/ |
5590 | !pThread->DetermineIfGuardPagePresent() || |
5591 | IsOutOfMemory) |
5592 | { |
5593 | // We have to be very careful. If we walk off the end of the stack, the process will just |
5594 | // die. e.g. IsAsyncThreadException() and Exception.ToString both consume too much stack -- and can't |
5595 | // be called here. |
5596 | dump = FALSE; |
5597 | PrintToStdErrA("\n" ); |
5598 | |
5599 | if (FAILED(UtilLoadStringRC(IDS_EE_UNHANDLED_EXCEPTION, buf, buf_size))) |
5600 | { |
5601 | wcsncpy_s(buf, COUNTOF(buf), SZ_UNHANDLED_EXCEPTION, SZ_UNHANDLED_EXCEPTION_CHARLEN); |
5602 | } |
5603 | |
5604 | PrintToStdErrW(buf); |
5605 | |
5606 | if (IsOutOfMemory) |
5607 | { |
5608 | PrintToStdErrA(" OutOfMemoryException.\n" ); |
5609 | } |
5610 | else |
5611 | { |
5612 | PrintToStdErrA(" StackOverflowException.\n" ); |
5613 | } |
5614 | } |
5615 | else if (!CanRunManagedCode(LoaderLockCheck::None)) |
5616 | { |
5617 | // Well, if we can't enter the runtime, we very well can't get the exception message. |
5618 | dump = FALSE; |
5619 | } |
5620 | else if (SentEvent || IsAsyncThreadException(&throwable)) |
5621 | { |
5622 | // We don't print anything on async exceptions, like ThreadAbort. |
5623 | dump = FALSE; |
5624 | INDEBUG(suppressSelectiveBreak=TRUE); |
5625 | } |
5626 | |
5627 | // Finally, should we print the message? |
5628 | if (dump) |
5629 | { |
5630 | // this is stack heavy because of the CQuickWSTRBase, so we break it out |
5631 | // and don't have to carry the weight through our other code paths. |
5632 | DefaultCatchHandlerExceptionMessageWorker(pThread, throwable, buf, buf_size, sendWindowsEventLog); |
5633 | } |
5634 | } |
5635 | EX_CATCH |
5636 | { |
5637 | LOG((LF_EH, LL_INFO10, "Exception occurred while processing uncaught exception\n" )); |
5638 | UtilLoadStringRC(IDS_EE_EXCEPTION_TOSTRING_FAILED, buf, buf_size); |
5639 | PrintToStdErrA("\n " ); |
5640 | PrintToStdErrW(buf); |
5641 | PrintToStdErrA("\n" ); |
5642 | } |
5643 | EX_END_CATCH(SwallowAllExceptions); |
5644 | } |
5645 | EX_CATCH |
5646 | { // If we got here, we can't even print the localized error message. Print non-localized. |
5647 | LOG((LF_EH, LL_INFO10, "Exception occurred while logging processing uncaught exception\n" )); |
5648 | PrintToStdErrA("\n Error: Can't print exception string because Exception.ToString() failed.\n" ); |
5649 | } |
5650 | EX_END_CATCH(SwallowAllExceptions); |
5651 | } |
5652 | |
5653 | #if defined(_DEBUG) |
5654 | if ((breakOnUncaught == breakSelective) && !suppressSelectiveBreak) |
5655 | { |
5656 | _ASSERTE(!"BreakOnUnCaughtException" ); |
5657 | } |
5658 | #endif // defined(_DEBUG) |
5659 | |
5660 | FlushLogging(); // Flush any logging output |
5661 | GCPROTECT_END(); |
5662 | |
5663 | #ifdef _DEBUG |
5664 | // Do not care about lock check for unhandled exception. |
5665 | while (unbreakableLockCount) |
5666 | { |
5667 | pThread->IncUnbreakableLockCount(); |
5668 | unbreakableLockCount --; |
5669 | } |
5670 | if (fOwnsSpinLock) |
5671 | { |
5672 | pThread->SetThreadStateNC(Thread::TSNC_OwnsSpinLock); |
5673 | } |
5674 | #endif |
5675 | } // DefaultCatchHandler() |
5676 | |
5677 | |
5678 | //****************************************************************************** |
5679 | // NotifyAppDomainsOfUnhandledException -- common processing for otherwise uncaught exceptions. |
5680 | //****************************************************************************** |
5681 | BOOL NotifyAppDomainsOfUnhandledException( |
5682 | PEXCEPTION_POINTERS pExceptionPointers, |
5683 | OBJECTREF *pThrowableIn, |
5684 | BOOL useLastThrownObject, |
5685 | BOOL isTerminating) |
5686 | { |
5687 | CONTRACTL |
5688 | { |
5689 | THROWS; |
5690 | GC_TRIGGERS; |
5691 | MODE_ANY; |
5692 | } |
5693 | CONTRACTL_END; |
5694 | |
5695 | #ifdef _DEBUG |
5696 | static int fBreakOnNotify = -1; |
5697 | if (fBreakOnNotify==-1) fBreakOnNotify = CLRConfig::GetConfigValue(CLRConfig::INTERNAL_BreakOnNotify); |
5698 | _ASSERTE(!fBreakOnNotify); |
5699 | #endif |
5700 | |
5701 | BOOL SentEvent = FALSE; |
5702 | |
5703 | LOG((LF_EH, LL_INFO10, "In NotifyAppDomainsOfUnhandledException\n" )); |
5704 | |
5705 | Thread *pThread = GetThread(); |
5706 | |
5707 | // The following reduces a window for a race during shutdown. |
5708 | if (!pThread) |
5709 | { |
5710 | _ASSERTE(g_fEEShutDown); |
5711 | return FALSE; |
5712 | } |
5713 | |
5714 | // See detailed explanation of this flag in threads.cpp. But the basic idea is that we already |
5715 | // reported the exception in the AppDomain where it went unhandled, so we don't need to report |
5716 | // it at the process level. |
5717 | if (pThread->HasThreadStateNC(Thread::TSNC_AppDomainContainUnhandled)) |
5718 | return FALSE; |
5719 | |
5720 | ThreadPreventAsyncHolder prevAsync; |
5721 | |
5722 | GCX_COOP(); |
5723 | |
5724 | OBJECTREF throwable; |
5725 | |
5726 | if (pThrowableIn != NULL) |
5727 | { |
5728 | throwable = *pThrowableIn; |
5729 | } |
5730 | else if (useLastThrownObject) |
5731 | { |
5732 | throwable = pThread->LastThrownObject(); |
5733 | } |
5734 | else |
5735 | { |
5736 | throwable = pThread->GetThrowable(); |
5737 | } |
5738 | |
5739 | // If we've got no managed object, then we can't send an event, so we just return. |
5740 | if (throwable == NULL) |
5741 | { |
5742 | return FALSE; |
5743 | } |
5744 | |
5745 | #ifdef _DEBUG |
5746 | DWORD unbreakableLockCount = 0; |
5747 | // Do not care about lock check for unhandled exception. |
5748 | while (pThread->HasUnbreakableLock()) |
5749 | { |
5750 | pThread->DecUnbreakableLockCount(); |
5751 | unbreakableLockCount ++; |
5752 | } |
5753 | BOOL fOwnsSpinLock = pThread->HasThreadStateNC(Thread::TSNC_OwnsSpinLock); |
5754 | if (fOwnsSpinLock) |
5755 | { |
5756 | pThread->ResetThreadStateNC(Thread::TSNC_OwnsSpinLock); |
5757 | } |
5758 | #endif |
5759 | |
5760 | GCPROTECT_BEGIN(throwable); |
5761 | //BOOL IsStackOverflow = (throwable->GetMethodTable() == g_pStackOverflowExceptionClass); |
5762 | |
5763 | // Notify the AppDomain that we have taken an unhandled exception. Can't notify of stack overflow -- guard |
5764 | // page is not yet reset. |
5765 | |
5766 | // Send up the unhandled exception appdomain event. |
5767 | // |
5768 | // If we can't run managed code, we can't deliver the event. Nor do we attempt to delieve the event in stack |
5769 | // overflow or OOM conditions. |
5770 | if (/*!IsStackOverflow &&*/ |
5771 | pThread->DetermineIfGuardPagePresent() && |
5772 | CanRunManagedCode(LoaderLockCheck::None)) |
5773 | { |
5774 | |
5775 | // x86 only |
5776 | #if !defined(WIN64EXCEPTIONS) |
5777 | // If the Thread object's exception state's exception pointers |
5778 | // is null, use the passed-in pointer. |
5779 | BOOL bSetPointers = FALSE; |
5780 | |
5781 | ThreadExceptionState* pExceptionState = pThread->GetExceptionState(); |
5782 | |
5783 | if (pExceptionState->GetExceptionPointers() == NULL) |
5784 | { |
5785 | bSetPointers = TRUE; |
5786 | pExceptionState->SetExceptionPointers(pExceptionPointers); |
5787 | } |
5788 | |
5789 | #endif // !defined(WIN64EXCEPTIONS) |
5790 | |
5791 | INSTALL_NESTED_EXCEPTION_HANDLER(pThread->GetFrame()); |
5792 | |
5793 | // This guy will never throw, but it will need a spot to store |
5794 | // any nested exceptions it might find. |
5795 | SentEvent = AppDomain::OnUnhandledException(&throwable, isTerminating); |
5796 | |
5797 | UNINSTALL_NESTED_EXCEPTION_HANDLER(); |
5798 | |
5799 | #if !defined(WIN64EXCEPTIONS) |
5800 | |
5801 | if (bSetPointers) |
5802 | { |
5803 | pExceptionState->SetExceptionPointers(NULL); |
5804 | } |
5805 | |
5806 | #endif // !defined(WIN64EXCEPTIONS) |
5807 | |
5808 | } |
5809 | |
5810 | GCPROTECT_END(); |
5811 | |
5812 | #ifdef _DEBUG |
5813 | // Do not care about lock check for unhandled exception. |
5814 | while (unbreakableLockCount) |
5815 | { |
5816 | pThread->IncUnbreakableLockCount(); |
5817 | unbreakableLockCount --; |
5818 | } |
5819 | if (fOwnsSpinLock) |
5820 | { |
5821 | pThread->SetThreadStateNC(Thread::TSNC_OwnsSpinLock); |
5822 | } |
5823 | #endif |
5824 | |
5825 | return SentEvent; |
5826 | |
5827 | } // NotifyAppDomainsOfUnhandledException() |
5828 | |
5829 | |
5830 | //****************************************************************************** |
5831 | // |
5832 | // ThreadBaseExceptionFilter_Worker |
5833 | // |
5834 | // The return from the function can be EXCEPTION_CONTINUE_SEARCH to let an |
5835 | // exception go unhandled. This is the default behaviour (starting in v2.0), |
5836 | // but can be overridden by hosts or by config file. |
5837 | // When the behaviour is overridden, the return will be EXCEPTION_EXECUTE_HANDLER |
5838 | // to swallow the exception. |
5839 | // Note that some exceptions are always swallowed: ThreadAbort, and AppDomainUnload. |
5840 | // |
5841 | // Parameters: |
5842 | // pExceptionInfo EXCEPTION_POINTERS for current exception |
5843 | // _location A constant as an INT_PTR. Tells the context from whence called. |
5844 | // swallowing Are we swallowing unhandled exceptions based on policy? |
5845 | // |
5846 | // Returns: |
5847 | // EXCEPTION_CONTINUE_SEARCH Generally returns this to let the exception go unhandled. |
5848 | // EXCEPTION_EXECUTE_HANDLER May return this to swallow the exception. |
5849 | // |
5850 | static LONG ThreadBaseExceptionFilter_Worker(PEXCEPTION_POINTERS pExceptionInfo, |
5851 | PVOID pvParam, |
5852 | BOOL swallowing) |
5853 | { |
5854 | CONTRACTL |
5855 | { |
5856 | THROWS; |
5857 | GC_TRIGGERS; |
5858 | MODE_ANY; |
5859 | } |
5860 | CONTRACTL_END; |
5861 | |
5862 | LOG((LF_EH, LL_INFO100, "ThreadBaseExceptionFilter_Worker: Enter\n" )); |
5863 | |
5864 | ThreadBaseExceptionFilterParam *pParam = (ThreadBaseExceptionFilterParam *) pvParam; |
5865 | UnhandledExceptionLocation location = pParam->location; |
5866 | |
5867 | _ASSERTE(!g_fNoExceptions); |
5868 | |
5869 | Thread* pThread = GetThread(); |
5870 | _ASSERTE(pThread); |
5871 | |
5872 | #ifdef _DEBUG |
5873 | if (CLRConfig::GetConfigValue(CLRConfig::INTERNAL_BreakOnUncaughtException) && |
5874 | !(swallowing && (SwallowUnhandledExceptions() || ExceptionIsAlwaysSwallowed(pExceptionInfo))) && |
5875 | !(location == ClassInitUnhandledException && pThread->IsRudeAbortInitiated())) |
5876 | _ASSERTE(!"BreakOnUnCaughtException" ); |
5877 | #endif |
5878 | |
5879 | BOOL doDefault = ((location != ClassInitUnhandledException) && |
5880 | (pExceptionInfo->ExceptionRecord->ExceptionCode != STATUS_BREAKPOINT) && |
5881 | (pExceptionInfo->ExceptionRecord->ExceptionCode != STATUS_SINGLE_STEP)); |
5882 | |
5883 | if (swallowing) |
5884 | { |
5885 | // The default handling for versions v1.0 and v1.1 was to swallow unhandled exceptions. |
5886 | // With v2.0, the default is to let them go unhandled. Hosts & config files can modify the default |
5887 | // to retain the v1.1 behaviour. |
5888 | // Should we swallow this exception, or let it continue up and be unhandled? |
5889 | if (!SwallowUnhandledExceptions()) |
5890 | { |
5891 | // No, don't swallow unhandled exceptions... |
5892 | |
5893 | // ...except if the exception is of a type that is always swallowed (ThreadAbort, AppDomainUnload)... |
5894 | if (ExceptionIsAlwaysSwallowed(pExceptionInfo)) |
5895 | { // ...return EXCEPTION_EXECUTE_HANDLER to swallow the exception anyway. |
5896 | return EXCEPTION_EXECUTE_HANDLER; |
5897 | } |
5898 | |
5899 | #ifdef _DEBUG |
5900 | if (CLRConfig::GetConfigValue(CLRConfig::INTERNAL_BreakOnUncaughtException)) |
5901 | _ASSERTE(!"BreakOnUnCaughtException" ); |
5902 | #endif |
5903 | |
5904 | // ...so, continue search. i.e. let the exception go unhandled. |
5905 | return EXCEPTION_CONTINUE_SEARCH; |
5906 | } |
5907 | } |
5908 | |
5909 | #ifdef DEBUGGING_SUPPORTED |
5910 | // If there's a debugger (and not doing a thread abort), give the debugger a shot at the exception. |
5911 | // If the debugger is going to try to continue the exception, it will return ContinueException (which |
5912 | // we see here as EXCEPTION_CONTINUE_EXECUTION). |
5913 | if (!pThread->IsAbortRequested()) |
5914 | { |
5915 | // TODO: do we really need this check? I don't think we do |
5916 | if(CORDebuggerAttached()) |
5917 | { |
5918 | if (NotifyDebuggerLastChance(pThread, pExceptionInfo, FALSE) == EXCEPTION_CONTINUE_EXECUTION) |
5919 | { |
5920 | LOG((LF_EH, LL_INFO100, "ThreadBaseExceptionFilter_Worker: EXCEPTION_CONTINUE_EXECUTION\n" )); |
5921 | return EXCEPTION_CONTINUE_EXECUTION; |
5922 | } |
5923 | } |
5924 | } |
5925 | #endif // DEBUGGING_SUPPORTED |
5926 | |
5927 | // Do default handling, but ignore breakpoint exceptions and class init exceptions |
5928 | if (doDefault) |
5929 | { |
5930 | LOG((LF_EH, LL_INFO100, "ThreadBaseExceptionFilter_Worker: Calling DefaultCatchHandler\n" )); |
5931 | |
5932 | BOOL useLastThrownObject = UpdateCurrentThrowable(pExceptionInfo->ExceptionRecord); |
5933 | |
5934 | DefaultCatchHandler(pExceptionInfo, |
5935 | NULL, |
5936 | useLastThrownObject, |
5937 | FALSE, |
5938 | location == ManagedThread || location == ThreadPoolThread || location == FinalizerThread); |
5939 | } |
5940 | |
5941 | // Return EXCEPTION_EXECUTE_HANDLER to swallow the exception. |
5942 | return (swallowing |
5943 | ? EXCEPTION_EXECUTE_HANDLER |
5944 | : EXCEPTION_CONTINUE_SEARCH); |
5945 | } // LONG ThreadBaseExceptionFilter_Worker() |
5946 | |
5947 | |
5948 | // This is the filter for new managed threads, for threadpool threads, and for |
5949 | // running finalizer methods. |
5950 | LONG ThreadBaseExceptionSwallowingFilter(PEXCEPTION_POINTERS pExceptionInfo, PVOID pvParam) |
5951 | { |
5952 | return ThreadBaseExceptionFilter_Worker(pExceptionInfo, pvParam, /*swallowing=*/true); |
5953 | } |
5954 | |
5955 | // This was the filter for new managed threads in v1.0 and v1.1. Now used |
5956 | // for delegate invoke, various things in the thread pool, and the |
5957 | // class init handler. |
5958 | LONG ThreadBaseExceptionFilter(PEXCEPTION_POINTERS pExceptionInfo, PVOID pvParam) |
5959 | { |
5960 | return ThreadBaseExceptionFilter_Worker(pExceptionInfo, pvParam, /*swallowing=*/false); |
5961 | } |
5962 | |
5963 | |
5964 | // This is the filter that we install when transitioning an AppDomain at the base of a managed |
5965 | // thread. Nothing interesting will get swallowed after us. So we never decide to continue |
5966 | // the search. Instead, we let it go unhandled and get the Watson report and debugging |
5967 | // experience before the AD transition has an opportunity to catch/rethrow and lose all the |
5968 | // relevant information. |
5969 | LONG ThreadBaseExceptionAppDomainFilter(EXCEPTION_POINTERS *pExceptionInfo, PVOID pvParam) |
5970 | { |
5971 | LONG ret = ThreadBaseExceptionSwallowingFilter(pExceptionInfo, pvParam); |
5972 | |
5973 | if (ret != EXCEPTION_CONTINUE_SEARCH) |
5974 | return ret; |
5975 | |
5976 | // Consider the exception to be unhandled |
5977 | return InternalUnhandledExceptionFilter_Worker(pExceptionInfo); |
5978 | } |
5979 | |
5980 | // Filter for calls out from the 'vm' to native code, if there's a possibility of SEH exceptions |
5981 | // in the native code. |
5982 | LONG CallOutFilter(PEXCEPTION_POINTERS pExceptionInfo, PVOID pv) |
5983 | { |
5984 | CallOutFilterParam *pParam = static_cast<CallOutFilterParam *>(pv); |
5985 | |
5986 | _ASSERTE(pParam->OneShot && (pParam->OneShot == TRUE || pParam->OneShot == FALSE)); |
5987 | |
5988 | if (pParam->OneShot == TRUE) |
5989 | { |
5990 | pParam->OneShot = FALSE; |
5991 | |
5992 | // Replace whatever SEH exception is in flight, with an SEHException derived from |
5993 | // CLRException. But if the exception already looks like one of ours, let it |
5994 | // go past since LastThrownObject should already represent it. |
5995 | if ((!IsComPlusException(pExceptionInfo->ExceptionRecord)) && |
5996 | (pExceptionInfo->ExceptionRecord->ExceptionCode != EXCEPTION_MSVC)) |
5997 | PAL_CPP_THROW(SEHException *, new SEHException(pExceptionInfo->ExceptionRecord, |
5998 | pExceptionInfo->ContextRecord)); |
5999 | } |
6000 | return EXCEPTION_CONTINUE_SEARCH; |
6001 | } |
6002 | |
6003 | |
6004 | //========================================================================== |
6005 | // Convert the format string used by sprintf to the format used by String.Format. |
6006 | // Using the managed formatting routine avoids bogus access violations |
6007 | // that happen for long strings in Win32's FormatMessage. |
6008 | // |
6009 | // Note: This is not general purpose routine. It handles only cases found |
6010 | // in TypeLoadException and FileLoadException. |
6011 | //========================================================================== |
6012 | static BOOL GetManagedFormatStringForResourceID(CCompRC::ResourceCategory eCategory, UINT32 resId, SString & converted) |
6013 | { |
6014 | STANDARD_VM_CONTRACT; |
6015 | |
6016 | StackSString temp; |
6017 | if (!temp.LoadResource(eCategory, resId)) |
6018 | return FALSE; |
6019 | |
6020 | SString::Iterator itr = temp.Begin(); |
6021 | while (*itr) |
6022 | { |
6023 | WCHAR c = *itr++; |
6024 | switch (c) { |
6025 | case '%': |
6026 | { |
6027 | WCHAR fmt = *itr++; |
6028 | if (fmt >= '1' && fmt <= '9') { |
6029 | converted.Append(W("{" )); |
6030 | converted.Append(fmt - 1); // the managed args start at 0 |
6031 | converted.Append(W("}" )); |
6032 | } |
6033 | else |
6034 | if (fmt == '%') { |
6035 | converted.Append(W("%" )); |
6036 | } |
6037 | else { |
6038 | _ASSERTE(!"Unexpected formating string: %s" ); |
6039 | } |
6040 | } |
6041 | break; |
6042 | case '{': |
6043 | converted.Append(W("{{" )); |
6044 | break; |
6045 | case '}': |
6046 | converted.Append(W("}}" )); |
6047 | break; |
6048 | default: |
6049 | converted.Append(c); |
6050 | break; |
6051 | } |
6052 | } |
6053 | return TRUE; |
6054 | } |
6055 | |
6056 | //========================================================================== |
6057 | // Private helper for TypeLoadException. |
6058 | //========================================================================== |
6059 | void QCALLTYPE GetTypeLoadExceptionMessage(UINT32 resId, QCall::StringHandleOnStack retString) |
6060 | { |
6061 | QCALL_CONTRACT; |
6062 | |
6063 | BEGIN_QCALL; |
6064 | |
6065 | StackSString format; |
6066 | GetManagedFormatStringForResourceID(CCompRC::Error, resId ? resId : IDS_CLASSLOAD_GENERAL, format); |
6067 | retString.Set(format); |
6068 | |
6069 | END_QCALL; |
6070 | } |
6071 | |
6072 | |
6073 | |
6074 | //========================================================================== |
6075 | // Private helper for FileLoadException and FileNotFoundException. |
6076 | //========================================================================== |
6077 | |
6078 | void QCALLTYPE GetFileLoadExceptionMessage(UINT32 hr, QCall::StringHandleOnStack retString) |
6079 | { |
6080 | QCALL_CONTRACT; |
6081 | |
6082 | BEGIN_QCALL; |
6083 | |
6084 | StackSString format; |
6085 | GetManagedFormatStringForResourceID(CCompRC::Error, GetResourceIDForFileLoadExceptionHR(hr), format); |
6086 | retString.Set(format); |
6087 | |
6088 | END_QCALL; |
6089 | } |
6090 | |
6091 | //========================================================================== |
6092 | // Private helper for FileLoadException and FileNotFoundException. |
6093 | //========================================================================== |
6094 | void QCALLTYPE FileLoadException_GetMessageForHR(UINT32 hresult, QCall::StringHandleOnStack retString) |
6095 | { |
6096 | QCALL_CONTRACT; |
6097 | |
6098 | BEGIN_QCALL; |
6099 | |
6100 | BOOL bNoGeekStuff = FALSE; |
6101 | switch ((HRESULT)hresult) |
6102 | { |
6103 | // These are not usually app errors - as long |
6104 | // as the message is reasonably clear, we can live without the hex code stuff. |
6105 | case COR_E_FILENOTFOUND: |
6106 | case __HRESULT_FROM_WIN32(ERROR_MOD_NOT_FOUND): |
6107 | case __HRESULT_FROM_WIN32(ERROR_PATH_NOT_FOUND): |
6108 | case __HRESULT_FROM_WIN32(ERROR_INVALID_NAME): |
6109 | case __HRESULT_FROM_WIN32(ERROR_BAD_NET_NAME): |
6110 | case __HRESULT_FROM_WIN32(ERROR_BAD_NETPATH): |
6111 | case __HRESULT_FROM_WIN32(ERROR_DLL_NOT_FOUND): |
6112 | case CTL_E_FILENOTFOUND: |
6113 | case COR_E_DLLNOTFOUND: |
6114 | case COR_E_PATHTOOLONG: |
6115 | case E_ACCESSDENIED: |
6116 | case COR_E_BADIMAGEFORMAT: |
6117 | case COR_E_NEWER_RUNTIME: |
6118 | case COR_E_ASSEMBLYEXPECTED: |
6119 | bNoGeekStuff = TRUE; |
6120 | break; |
6121 | } |
6122 | |
6123 | SString s; |
6124 | GetHRMsg((HRESULT)hresult, s, bNoGeekStuff); |
6125 | retString.Set(s); |
6126 | |
6127 | END_QCALL; |
6128 | } |
6129 | |
6130 | |
6131 | #define ValidateSigBytes(_size) do { if ((_size) > csig) COMPlusThrow(kArgumentException, W("Argument_BadSigFormat")); csig -= (_size); } while (false) |
6132 | |
6133 | //========================================================================== |
6134 | // Unparses an individual type. |
6135 | //========================================================================== |
6136 | const BYTE *UnparseType(const BYTE *pType, DWORD& csig, StubLinker *psl) |
6137 | { |
6138 | CONTRACTL |
6139 | { |
6140 | THROWS; |
6141 | GC_NOTRIGGER; |
6142 | MODE_ANY; |
6143 | INJECT_FAULT(ThrowOutOfMemory();); // Emitting data to the StubLinker can throw OOM. |
6144 | } |
6145 | CONTRACTL_END; |
6146 | |
6147 | LPCUTF8 pName = NULL; |
6148 | |
6149 | ValidateSigBytes(sizeof(BYTE)); |
6150 | switch ( (CorElementType) *(pType++) ) { |
6151 | case ELEMENT_TYPE_VOID: |
6152 | psl->EmitUtf8("void" ); |
6153 | break; |
6154 | |
6155 | case ELEMENT_TYPE_BOOLEAN: |
6156 | psl->EmitUtf8("boolean" ); |
6157 | break; |
6158 | |
6159 | case ELEMENT_TYPE_CHAR: |
6160 | psl->EmitUtf8("char" ); |
6161 | break; |
6162 | |
6163 | case ELEMENT_TYPE_U1: |
6164 | psl->EmitUtf8("unsigned " ); |
6165 | //fallthru |
6166 | case ELEMENT_TYPE_I1: |
6167 | psl->EmitUtf8("byte" ); |
6168 | break; |
6169 | |
6170 | case ELEMENT_TYPE_U2: |
6171 | psl->EmitUtf8("unsigned " ); |
6172 | //fallthru |
6173 | case ELEMENT_TYPE_I2: |
6174 | psl->EmitUtf8("short" ); |
6175 | break; |
6176 | |
6177 | case ELEMENT_TYPE_U4: |
6178 | psl->EmitUtf8("unsigned " ); |
6179 | //fallthru |
6180 | case ELEMENT_TYPE_I4: |
6181 | psl->EmitUtf8("int" ); |
6182 | break; |
6183 | |
6184 | case ELEMENT_TYPE_I: |
6185 | psl->EmitUtf8("native int" ); |
6186 | break; |
6187 | case ELEMENT_TYPE_U: |
6188 | psl->EmitUtf8("native unsigned" ); |
6189 | break; |
6190 | |
6191 | case ELEMENT_TYPE_U8: |
6192 | psl->EmitUtf8("unsigned " ); |
6193 | //fallthru |
6194 | case ELEMENT_TYPE_I8: |
6195 | psl->EmitUtf8("long" ); |
6196 | break; |
6197 | |
6198 | |
6199 | case ELEMENT_TYPE_R4: |
6200 | psl->EmitUtf8("float" ); |
6201 | break; |
6202 | |
6203 | case ELEMENT_TYPE_R8: |
6204 | psl->EmitUtf8("double" ); |
6205 | break; |
6206 | |
6207 | case ELEMENT_TYPE_STRING: |
6208 | psl->EmitUtf8(g_StringName); |
6209 | break; |
6210 | |
6211 | case ELEMENT_TYPE_VAR: |
6212 | case ELEMENT_TYPE_OBJECT: |
6213 | psl->EmitUtf8(g_ObjectName); |
6214 | break; |
6215 | |
6216 | case ELEMENT_TYPE_PTR: |
6217 | pType = UnparseType(pType, csig, psl); |
6218 | psl->EmitUtf8("*" ); |
6219 | break; |
6220 | |
6221 | case ELEMENT_TYPE_BYREF: |
6222 | pType = UnparseType(pType, csig, psl); |
6223 | psl->EmitUtf8("&" ); |
6224 | break; |
6225 | |
6226 | case ELEMENT_TYPE_VALUETYPE: |
6227 | case ELEMENT_TYPE_CLASS: |
6228 | pName = (LPCUTF8)pType; |
6229 | while (true) { |
6230 | ValidateSigBytes(sizeof(CHAR)); |
6231 | if (*(pType++) == '\0') |
6232 | break; |
6233 | } |
6234 | psl->EmitUtf8(pName); |
6235 | break; |
6236 | |
6237 | case ELEMENT_TYPE_SZARRAY: |
6238 | { |
6239 | pType = UnparseType(pType, csig, psl); |
6240 | psl->EmitUtf8("[]" ); |
6241 | } |
6242 | break; |
6243 | |
6244 | case ELEMENT_TYPE_ARRAY: |
6245 | { |
6246 | pType = UnparseType(pType, csig, psl); |
6247 | ValidateSigBytes(sizeof(DWORD)); |
6248 | DWORD rank = GET_UNALIGNED_VAL32(pType); |
6249 | pType += sizeof(DWORD); |
6250 | if (rank) |
6251 | { |
6252 | ValidateSigBytes(sizeof(UINT32)); |
6253 | UINT32 nsizes = GET_UNALIGNED_VAL32(pType); // Get # of sizes |
6254 | ValidateSigBytes(nsizes * sizeof(UINT32)); |
6255 | pType += 4 + nsizes*4; |
6256 | ValidateSigBytes(sizeof(UINT32)); |
6257 | UINT32 nlbounds = GET_UNALIGNED_VAL32(pType); // Get # of lower bounds |
6258 | ValidateSigBytes(nlbounds * sizeof(UINT32)); |
6259 | pType += 4 + nlbounds*4; |
6260 | |
6261 | |
6262 | while (rank--) { |
6263 | psl->EmitUtf8("[]" ); |
6264 | } |
6265 | |
6266 | } |
6267 | |
6268 | } |
6269 | break; |
6270 | |
6271 | case ELEMENT_TYPE_TYPEDBYREF: |
6272 | psl->EmitUtf8("&" ); |
6273 | break; |
6274 | |
6275 | case ELEMENT_TYPE_FNPTR: |
6276 | psl->EmitUtf8("ftnptr" ); |
6277 | break; |
6278 | |
6279 | default: |
6280 | psl->EmitUtf8("?" ); |
6281 | break; |
6282 | } |
6283 | |
6284 | return pType; |
6285 | } |
6286 | |
6287 | |
6288 | |
6289 | //========================================================================== |
6290 | // Helper for MissingMemberException. |
6291 | //========================================================================== |
6292 | static STRINGREF MissingMemberException_FormatSignature_Internal(I1ARRAYREF* ppPersistedSig) |
6293 | { |
6294 | CONTRACTL |
6295 | { |
6296 | THROWS; |
6297 | GC_TRIGGERS; |
6298 | MODE_COOPERATIVE; |
6299 | INJECT_FAULT(ThrowOutOfMemory();); |
6300 | } |
6301 | CONTRACTL_END; |
6302 | |
6303 | STRINGREF pString = NULL; |
6304 | |
6305 | DWORD csig = 0; |
6306 | const BYTE *psig = 0; |
6307 | StubLinker *psl = NULL; |
6308 | StubHolder<Stub> pstub; |
6309 | |
6310 | if ((*ppPersistedSig) != NULL) |
6311 | csig = (*ppPersistedSig)->GetNumComponents(); |
6312 | |
6313 | if (csig == 0) |
6314 | { |
6315 | return StringObject::NewString("Unknown signature" ); |
6316 | } |
6317 | |
6318 | psig = (const BYTE*)_alloca(csig); |
6319 | CopyMemory((BYTE*)psig, |
6320 | (*ppPersistedSig)->GetDirectPointerToNonObjectElements(), |
6321 | csig); |
6322 | |
6323 | { |
6324 | GCX_PREEMP(); |
6325 | |
6326 | StubLinker sl; |
6327 | psl = &sl; |
6328 | pstub = NULL; |
6329 | |
6330 | ValidateSigBytes(sizeof(UINT32)); |
6331 | UINT32 cconv = GET_UNALIGNED_VAL32(psig); |
6332 | psig += 4; |
6333 | |
6334 | if (cconv == IMAGE_CEE_CS_CALLCONV_FIELD) { |
6335 | psig = UnparseType(psig, csig, psl); |
6336 | } else { |
6337 | ValidateSigBytes(sizeof(UINT32)); |
6338 | UINT32 nargs = GET_UNALIGNED_VAL32(psig); |
6339 | psig += 4; |
6340 | |
6341 | // Unparse return type |
6342 | psig = UnparseType(psig, csig, psl); |
6343 | psl->EmitUtf8("(" ); |
6344 | while (nargs--) { |
6345 | psig = UnparseType(psig, csig, psl); |
6346 | if (nargs) { |
6347 | psl->EmitUtf8(", " ); |
6348 | } |
6349 | } |
6350 | psl->EmitUtf8(")" ); |
6351 | } |
6352 | psl->Emit8('\0'); |
6353 | |
6354 | pstub = psl->Link(NULL); |
6355 | } |
6356 | |
6357 | pString = StringObject::NewString( (LPCUTF8)(pstub->GetEntryPoint()) ); |
6358 | return pString; |
6359 | } |
6360 | |
6361 | FCIMPL1(Object*, MissingMemberException_FormatSignature, I1Array* pPersistedSigUNSAFE) |
6362 | { |
6363 | FCALL_CONTRACT; |
6364 | |
6365 | STRINGREF pString = NULL; |
6366 | I1ARRAYREF pPersistedSig = (I1ARRAYREF) pPersistedSigUNSAFE; |
6367 | HELPER_METHOD_FRAME_BEGIN_RET_1(pPersistedSig); |
6368 | |
6369 | pString = MissingMemberException_FormatSignature_Internal(&pPersistedSig); |
6370 | |
6371 | HELPER_METHOD_FRAME_END(); |
6372 | return OBJECTREFToObject(pString); |
6373 | } |
6374 | FCIMPLEND |
6375 | |
6376 | // Check if there is a pending exception or the thread is already aborting. Returns 0 if yes. |
6377 | // Otherwise, sets the thread up for generating an abort and returns address of ThrowControlForThread |
6378 | // It is the caller's responsibility to set up Thread::m_OSContext prior to this call. This is used as |
6379 | // the context for checking if a ThreadAbort is allowed, and also as the context for the ThreadAbortException |
6380 | // itself. |
6381 | LPVOID COMPlusCheckForAbort(UINT_PTR uTryCatchResumeAddress) |
6382 | { |
6383 | CONTRACTL |
6384 | { |
6385 | NOTHROW; |
6386 | GC_NOTRIGGER; |
6387 | MODE_ANY; |
6388 | SO_TOLERANT; |
6389 | } |
6390 | CONTRACTL_END; |
6391 | |
6392 | // Initialize the return address |
6393 | LPVOID pRetAddress = 0; |
6394 | |
6395 | Thread* pThread = GetThread(); |
6396 | |
6397 | if ((!pThread->IsAbortRequested()) || // if no abort has been requested |
6398 | (!pThread->IsRudeAbort() && |
6399 | (pThread->GetThrowable() != NULL)) ) // or if there is a pending exception |
6400 | { |
6401 | goto exit; |
6402 | } |
6403 | |
6404 | // Reverse COM interop IL stubs map all exceptions to HRESULTs and must not propagate Thread.Abort |
6405 | // to their unmanaged callers. |
6406 | if (uTryCatchResumeAddress != NULL) |
6407 | { |
6408 | MethodDesc * pMDResumeMethod = ExecutionManager::GetCodeMethodDesc((PCODE)uTryCatchResumeAddress); |
6409 | if (pMDResumeMethod->IsILStub()) |
6410 | goto exit; |
6411 | } |
6412 | |
6413 | // else we must produce an abort |
6414 | if ((pThread->GetThrowable() == NULL) && |
6415 | (pThread->IsAbortInitiated())) |
6416 | { |
6417 | // Oops, we just swallowed an abort, must restart the process |
6418 | pThread->ResetAbortInitiated(); |
6419 | } |
6420 | |
6421 | // Question: Should we also check for (pThread->m_PreventAsync == 0) |
6422 | |
6423 | #if !defined(WIN64EXCEPTIONS) && defined(FEATURE_STACK_PROBE) |
6424 | // On Win64, this function is called by our exception handling code which has probed. |
6425 | // But on X86, this is called from JIT code directly. We probe here so that |
6426 | // we can restore the state of the thread below. |
6427 | if (GetEEPolicy()->GetActionOnFailure(FAIL_StackOverflow) == eRudeUnloadAppDomain) |
6428 | { |
6429 | // In case of SO, we will skip the managed code. |
6430 | CONTRACT_VIOLATION(ThrowsViolation); |
6431 | RetailStackProbe(ADJUST_PROBE(DEFAULT_ENTRY_PROBE_AMOUNT), pThread); |
6432 | } |
6433 | #endif // !WIN64EXCEPTIONS && FEATURE_STACK_PROBE |
6434 | |
6435 | pThread->SetThrowControlForThread(Thread::InducedThreadRedirectAtEndOfCatch); |
6436 | if (!pThread->ReadyForAbort()) |
6437 | { |
6438 | pThread->ResetThrowControlForThread(); |
6439 | goto exit; |
6440 | } |
6441 | pThread->SetThrowControlForThread(Thread::InducedThreadStop); |
6442 | |
6443 | pRetAddress = (LPVOID)THROW_CONTROL_FOR_THREAD_FUNCTION; |
6444 | |
6445 | exit: |
6446 | |
6447 | #ifndef FEATURE_PAL |
6448 | |
6449 | // Only proceed if Watson is enabled - CoreCLR may have it disabled. |
6450 | if (IsWatsonEnabled()) |
6451 | { |
6452 | BOOL fClearUEWatsonBucketTracker = TRUE; |
6453 | PTR_EHWatsonBucketTracker pUEWatsonBucketTracker = pThread->GetExceptionState()->GetUEWatsonBucketTracker(); |
6454 | |
6455 | if (pRetAddress && pThread->IsAbortRequested()) |
6456 | { |
6457 | // Since we are going to reraise the thread abort exception, we would like to assert that |
6458 | // the buckets present in the UE tracker are the ones which were setup TAE was first raised. |
6459 | // |
6460 | // However, these buckets could come from across AD transition as well and thus, would be |
6461 | // marked for "Captured at AD transition". Thus, we cannot just assert them to be only from |
6462 | // TAE raise. |
6463 | // |
6464 | // We try to preserve buckets incase there is another catch that may catch the exception we reraise |
6465 | // and it attempts to FailFast using the TA exception object. In such a case, |
6466 | // we should maintain the original exception point's bucket details. |
6467 | if (pUEWatsonBucketTracker->RetrieveWatsonBucketIp() != NULL) |
6468 | { |
6469 | _ASSERTE(pUEWatsonBucketTracker->CapturedForThreadAbort() || pUEWatsonBucketTracker->CapturedAtADTransition()); |
6470 | fClearUEWatsonBucketTracker = FALSE; |
6471 | } |
6472 | #ifdef _DEBUG |
6473 | else |
6474 | { |
6475 | // If we are here and UE Watson bucket tracker is empty, |
6476 | // then it is possible that a thread abort was signalled when the catch was executing |
6477 | // and thus, hijack for TA from here is not a reraise but an initial raise. |
6478 | // |
6479 | // However, if we have partial details, then something is really not right. |
6480 | if (!((pUEWatsonBucketTracker->RetrieveWatsonBucketIp() == NULL) && |
6481 | (pUEWatsonBucketTracker->RetrieveWatsonBuckets() == NULL))) |
6482 | { |
6483 | _ASSERTE(!"How come TA is being [re]raised and we have incomplete watson bucket details?" ); |
6484 | } |
6485 | } |
6486 | #endif // _DEBUG |
6487 | } |
6488 | |
6489 | if (fClearUEWatsonBucketTracker) |
6490 | { |
6491 | // Clear the UE watson bucket tracker for future use since it does not have anything |
6492 | // useful for us right now. |
6493 | pUEWatsonBucketTracker->ClearWatsonBucketDetails(); |
6494 | LOG((LF_EH, LL_INFO100, "COMPlusCheckForAbort - Cleared UE watson bucket tracker since TAE was not being reraised.\n" )); |
6495 | } |
6496 | } |
6497 | |
6498 | #endif // !FEATURE_PAL |
6499 | |
6500 | return pRetAddress; |
6501 | } |
6502 | |
6503 | |
6504 | BOOL IsThreadHijackedForThreadStop(Thread* pThread, EXCEPTION_RECORD* pExceptionRecord) |
6505 | { |
6506 | CONTRACTL |
6507 | { |
6508 | NOTHROW; |
6509 | GC_NOTRIGGER; |
6510 | MODE_ANY; |
6511 | FORBID_FAULT; |
6512 | SO_TOLERANT; |
6513 | } |
6514 | CONTRACTL_END; |
6515 | |
6516 | if (IsComPlusException(pExceptionRecord)) |
6517 | { |
6518 | if (pThread->ThrewControlForThread() == Thread::InducedThreadStop) |
6519 | { |
6520 | LOG((LF_EH, LL_INFO100, "Asynchronous Thread Stop or Abort\n" )); |
6521 | return TRUE; |
6522 | } |
6523 | } |
6524 | else if (IsStackOverflowException(pThread, pExceptionRecord)) |
6525 | { |
6526 | // SO happens before we are able to change the state to InducedThreadStop, but |
6527 | // we are still in our hijack routine. |
6528 | if (pThread->ThrewControlForThread() == Thread::InducedThreadRedirect) |
6529 | { |
6530 | LOG((LF_EH, LL_INFO100, "Asynchronous Thread Stop or Abort caused by SO\n" )); |
6531 | return TRUE; |
6532 | } |
6533 | } |
6534 | return FALSE; |
6535 | } |
6536 | |
6537 | // We sometimes move a thread's execution so it will throw an exception for us. |
6538 | // But then we have to treat the exception as if it came from the instruction |
6539 | // the thread was originally running. |
6540 | // |
6541 | // NOTE: This code depends on the fact that there are no register-based data dependencies |
6542 | // between a try block and a catch, fault, or finally block. If there were, then we need |
6543 | // to preserve more of the register context. |
6544 | |
6545 | void AdjustContextForThreadStop(Thread* pThread, |
6546 | CONTEXT* pContext) |
6547 | { |
6548 | CONTRACTL |
6549 | { |
6550 | NOTHROW; |
6551 | GC_NOTRIGGER; |
6552 | MODE_ANY; |
6553 | FORBID_FAULT; |
6554 | SO_TOLERANT; |
6555 | } |
6556 | CONTRACTL_END; |
6557 | |
6558 | _ASSERTE(pThread->m_OSContext); |
6559 | |
6560 | #ifndef WIN64EXCEPTIONS |
6561 | SetIP(pContext, GetIP(pThread->m_OSContext)); |
6562 | SetSP(pContext, (GetSP(pThread->m_OSContext))); |
6563 | |
6564 | if (GetFP(pThread->m_OSContext) != 0) // ebp = 0 implies that we got here with the right values for ebp |
6565 | { |
6566 | SetFP(pContext, GetFP(pThread->m_OSContext)); |
6567 | } |
6568 | |
6569 | // We might have been interrupted execution at a point where the jit has roots in |
6570 | // registers. We just need to store a "safe" value in here so that the collector |
6571 | // doesn't trap. We're not going to use these objects after the exception. |
6572 | // |
6573 | // Only callee saved registers are going to be reported by the faulting excepiton frame. |
6574 | #if defined(_TARGET_X86_) |
6575 | // Ebx,esi,edi are important. Eax,ecx,edx are not. |
6576 | pContext->Ebx = 0; |
6577 | pContext->Edi = 0; |
6578 | pContext->Esi = 0; |
6579 | #else |
6580 | PORTABILITY_ASSERT("AdjustContextForThreadStop" ); |
6581 | #endif |
6582 | |
6583 | #else // !WIN64EXCEPTIONS |
6584 | CopyOSContext(pContext, pThread->m_OSContext); |
6585 | #if defined(_TARGET_ARM_) && defined(_DEBUG) |
6586 | // Make sure that the thumb bit is set on the IP of the original abort context we just restored. |
6587 | PCODE controlPC = GetIP(pContext); |
6588 | _ASSERTE(controlPC & THUMB_CODE); |
6589 | #endif // _TARGET_ARM_ |
6590 | #endif // !WIN64EXCEPTIONS |
6591 | |
6592 | pThread->ResetThrowControlForThread(); |
6593 | |
6594 | // Should never get here if we're already throwing an exception. |
6595 | _ASSERTE(!pThread->IsExceptionInProgress() || pThread->IsRudeAbort()); |
6596 | |
6597 | // Should never get here if we're already abort initiated. |
6598 | _ASSERTE(!pThread->IsAbortInitiated() || pThread->IsRudeAbort()); |
6599 | |
6600 | if (pThread->IsAbortRequested()) |
6601 | { |
6602 | pThread->SetAbortInitiated(); // to prevent duplicate aborts |
6603 | } |
6604 | } |
6605 | |
6606 | // Create a COM+ exception , stick it in the thread. |
6607 | OBJECTREF |
6608 | CreateCOMPlusExceptionObject(Thread *pThread, EXCEPTION_RECORD *pExceptionRecord, BOOL bAsynchronousThreadStop) |
6609 | { |
6610 | CONTRACTL |
6611 | { |
6612 | NOTHROW; |
6613 | GC_TRIGGERS; |
6614 | MODE_COOPERATIVE; |
6615 | FORBID_FAULT; |
6616 | SO_TOLERANT; |
6617 | } |
6618 | CONTRACTL_END; |
6619 | |
6620 | _ASSERTE(GetThread() == pThread); |
6621 | |
6622 | DWORD exceptionCode = pExceptionRecord->ExceptionCode; |
6623 | |
6624 | OBJECTREF result = 0; |
6625 | |
6626 | DWORD COMPlusExceptionCode = (bAsynchronousThreadStop |
6627 | ? kThreadAbortException |
6628 | : MapWin32FaultToCOMPlusException(pExceptionRecord)); |
6629 | |
6630 | if (exceptionCode == STATUS_NO_MEMORY) |
6631 | { |
6632 | result = CLRException::GetBestOutOfMemoryException(); |
6633 | } |
6634 | else if (IsStackOverflowException(pThread, pExceptionRecord)) |
6635 | { |
6636 | result = CLRException::GetPreallocatedStackOverflowException(); |
6637 | } |
6638 | else if (bAsynchronousThreadStop && pThread->IsAbortRequested() && pThread->IsRudeAbort()) |
6639 | { |
6640 | result = CLRException::GetPreallocatedRudeThreadAbortException(); |
6641 | } |
6642 | else |
6643 | { |
6644 | EX_TRY |
6645 | { |
6646 | // We need to disable the backout stack validation at this point since CreateThrowable can |
6647 | // take arbitrarily large amounts of stack for different exception types; however we know |
6648 | // for a fact that we will never go through this code path if the exception is a stack |
6649 | // overflow exception since we already handled that case above with the pre-allocated SO exception. |
6650 | DISABLE_BACKOUT_STACK_VALIDATION; |
6651 | |
6652 | FAULT_NOT_FATAL(); |
6653 | |
6654 | ThreadPreventAsyncHolder preventAsync; |
6655 | ResetProcessorStateHolder procState; |
6656 | |
6657 | INSTALL_UNWIND_AND_CONTINUE_HANDLER; |
6658 | |
6659 | GCPROTECT_BEGIN(result) |
6660 | |
6661 | EEException e((RuntimeExceptionKind)COMPlusExceptionCode); |
6662 | result = e.CreateThrowable(); |
6663 | |
6664 | // EEException is "one size fits all". But AV needs some more information. |
6665 | if (COMPlusExceptionCode == kAccessViolationException) |
6666 | { |
6667 | SetExceptionAVParameters(result, pExceptionRecord); |
6668 | } |
6669 | |
6670 | GCPROTECT_END(); |
6671 | |
6672 | UNINSTALL_UNWIND_AND_CONTINUE_HANDLER; |
6673 | } |
6674 | EX_CATCH |
6675 | { |
6676 | // If we get an exception trying to build the managed exception object, then go ahead and return the |
6677 | // thrown object as the result of this function. This is preferable to letting the exception try to |
6678 | // percolate up through the EH code, and it effectively replaces the thrown exception with this |
6679 | // exception. |
6680 | result = GET_THROWABLE(); |
6681 | } |
6682 | EX_END_CATCH(SwallowAllExceptions); |
6683 | } |
6684 | |
6685 | return result; |
6686 | } |
6687 | |
6688 | LONG FilterAccessViolation(PEXCEPTION_POINTERS pExceptionPointers, LPVOID lpvParam) |
6689 | { |
6690 | CONTRACTL |
6691 | { |
6692 | THROWS; |
6693 | GC_NOTRIGGER; |
6694 | MODE_ANY; |
6695 | FORBID_FAULT; |
6696 | } |
6697 | CONTRACTL_END; |
6698 | |
6699 | if (pExceptionPointers->ExceptionRecord->ExceptionCode == EXCEPTION_ACCESS_VIOLATION) |
6700 | return EXCEPTION_EXECUTE_HANDLER; |
6701 | |
6702 | return EXCEPTION_CONTINUE_SEARCH; |
6703 | } |
6704 | |
6705 | /* |
6706 | * IsContinuableException |
6707 | * |
6708 | * Returns whether this is an exception the EE knows how to intercept and continue from. |
6709 | * |
6710 | * Parameters: |
6711 | * pThread - The thread the exception occurred on. |
6712 | * |
6713 | * Returns: |
6714 | * TRUE if the exception on the thread is interceptable or not. |
6715 | * |
6716 | * Notes: |
6717 | * Conditions for an interceptable exception: |
6718 | * 1) must be on a managed thread |
6719 | * 2) an exception must be in progress |
6720 | * 3) a managed exception object must have been created |
6721 | * 4) the thread must not be aborting |
6722 | * 5) the exception must not be a breakpoint, a single step, or a stack overflow |
6723 | * 6) the exception dispatch must be in the first pass |
6724 | * 7) the exception must not be a fatal error, as determined by the EE policy (see LogFatalError()) |
6725 | */ |
6726 | bool IsInterceptableException(Thread *pThread) |
6727 | { |
6728 | CONTRACTL |
6729 | { |
6730 | MODE_ANY; |
6731 | NOTHROW; |
6732 | GC_NOTRIGGER; |
6733 | } |
6734 | CONTRACTL_END; |
6735 | |
6736 | return ((pThread != NULL) && |
6737 | (!pThread->IsAbortRequested()) && |
6738 | (pThread->IsExceptionInProgress()) && |
6739 | (!pThread->IsThrowableNull()) |
6740 | |
6741 | #ifdef DEBUGGING_SUPPORTED |
6742 | && |
6743 | pThread->GetExceptionState()->IsDebuggerInterceptable() |
6744 | #endif |
6745 | |
6746 | ); |
6747 | } |
6748 | |
6749 | // Determines whether we hit an DO_A_GC_HERE marker in JITted code, and returns the |
6750 | // appropriate exception code, or zero if the code is not a GC marker. |
6751 | DWORD GetGcMarkerExceptionCode(LPVOID ip) |
6752 | { |
6753 | #if defined(HAVE_GCCOVER) |
6754 | WRAPPER_NO_CONTRACT; |
6755 | |
6756 | if (GCStress<cfg_any>::IsEnabled() && IsGcCoverageInterrupt(ip)) |
6757 | { |
6758 | return STATUS_CLR_GCCOVER_CODE; |
6759 | } |
6760 | #else // defined(HAVE_GCCOVER) |
6761 | LIMITED_METHOD_CONTRACT; |
6762 | #endif // defined(HAVE_GCCOVER) |
6763 | return 0; |
6764 | } |
6765 | |
6766 | // Did we hit an DO_A_GC_HERE marker in JITted code? |
6767 | bool IsGcMarker(CONTEXT* pContext, EXCEPTION_RECORD *pExceptionRecord) |
6768 | { |
6769 | DWORD exceptionCode = pExceptionRecord->ExceptionCode; |
6770 | #ifdef HAVE_GCCOVER |
6771 | WRAPPER_NO_CONTRACT; |
6772 | |
6773 | if (GCStress<cfg_any>::IsEnabled()) |
6774 | { |
6775 | #if defined(GCCOVER_TOLERATE_SPURIOUS_AV) |
6776 | |
6777 | // We sometimes can't suspend the EE to update the GC marker instruction so |
6778 | // we update it directly without suspending. This can sometimes yield |
6779 | // a STATUS_ACCESS_VIOLATION instead of STATUS_CLR_GCCOVER_CODE. In |
6780 | // this case we let the AV through and retry the instruction as hopefully |
6781 | // the race will have resolved. We'll track the IP of the instruction |
6782 | // that generated an AV so we don't mix up a real AV with a "fake" AV. |
6783 | // |
6784 | // See comments in function DoGcStress for more details on this race. |
6785 | // |
6786 | // Note these "fake" AVs will be reported by the kernel as reads from |
6787 | // address 0xF...F so we also use that as a screen. |
6788 | Thread* pThread = GetThread(); |
6789 | if (exceptionCode == STATUS_ACCESS_VIOLATION && |
6790 | GCStress<cfg_instr>::IsEnabled() && |
6791 | pExceptionRecord->ExceptionInformation[0] == 0 && |
6792 | pExceptionRecord->ExceptionInformation[1] == ~0 && |
6793 | pThread->GetLastAVAddress() != (LPVOID)GetIP(pContext) && |
6794 | !IsIPInEE((LPVOID)GetIP(pContext))) |
6795 | { |
6796 | pThread->SetLastAVAddress((LPVOID)GetIP(pContext)); |
6797 | return true; |
6798 | } |
6799 | #endif // defined(GCCOVER_TOLERATE_SPURIOUS_AV) |
6800 | |
6801 | if (exceptionCode == STATUS_CLR_GCCOVER_CODE) |
6802 | { |
6803 | if (OnGcCoverageInterrupt(pContext)) |
6804 | { |
6805 | return true; |
6806 | } |
6807 | |
6808 | { |
6809 | // ExecutionManager::IsManagedCode takes a spinlock. Since this is in a debug-only |
6810 | // check, we'll allow the lock. |
6811 | CONTRACT_VIOLATION(TakesLockViolation); |
6812 | |
6813 | // Should never be in managed code. |
6814 | CONSISTENCY_CHECK_MSG(!ExecutionManager::IsManagedCode(GetIP(pContext)), "hit privileged instruction!" ); |
6815 | } |
6816 | } |
6817 | } |
6818 | #else |
6819 | LIMITED_METHOD_CONTRACT; |
6820 | #endif // HAVE_GCCOVER |
6821 | return false; |
6822 | } |
6823 | |
6824 | #ifndef FEATURE_PAL |
6825 | |
6826 | // Return true if the access violation is well formed (has two info parameters |
6827 | // at the end) |
6828 | static inline BOOL |
6829 | IsWellFormedAV(EXCEPTION_RECORD *pExceptionRecord) |
6830 | { |
6831 | LIMITED_METHOD_CONTRACT; |
6832 | |
6833 | #define NUM_AV_PARAMS 2 |
6834 | |
6835 | if (pExceptionRecord->NumberParameters == NUM_AV_PARAMS) |
6836 | { |
6837 | return TRUE; |
6838 | } |
6839 | else |
6840 | { |
6841 | return FALSE; |
6842 | } |
6843 | } |
6844 | |
6845 | static inline BOOL |
6846 | IsDebuggerFault(EXCEPTION_RECORD *pExceptionRecord, |
6847 | CONTEXT *pContext, |
6848 | DWORD exceptionCode, |
6849 | Thread *pThread) |
6850 | { |
6851 | LIMITED_METHOD_CONTRACT; |
6852 | |
6853 | #ifdef DEBUGGING_SUPPORTED |
6854 | SO_NOT_MAINLINE_FUNCTION; |
6855 | |
6856 | #ifdef _TARGET_ARM_ |
6857 | // On ARM we don't have any reliable hardware support for single stepping so it is emulated in software. |
6858 | // The implementation will end up throwing an EXCEPTION_BREAKPOINT rather than an EXCEPTION_SINGLE_STEP |
6859 | // and leaves other aspects of the thread context in an invalid state. Therefore we use this opportunity |
6860 | // to fixup the state before any other part of the system uses it (we do it here since only the debugger |
6861 | // uses single step functionality). |
6862 | |
6863 | // First ask the emulation itself whether this exception occurred while single stepping was enabled. If so |
6864 | // it will fix up the context to be consistent again and return true. If so and the exception was |
6865 | // EXCEPTION_BREAKPOINT then we translate it to EXCEPTION_SINGLE_STEP (otherwise we leave it be, e.g. the |
6866 | // instruction stepped caused an access violation). since this is called from our VEH there might not |
6867 | // be a thread object so we must check pThread first. |
6868 | if ((pThread != NULL) && pThread->HandleSingleStep(pContext, exceptionCode) && (exceptionCode == EXCEPTION_BREAKPOINT)) |
6869 | { |
6870 | exceptionCode = EXCEPTION_SINGLE_STEP; |
6871 | pExceptionRecord->ExceptionCode = EXCEPTION_SINGLE_STEP; |
6872 | pExceptionRecord->ExceptionAddress = (PVOID)pContext->Pc; |
6873 | } |
6874 | #endif // _TARGET_ARM_ |
6875 | |
6876 | // Is this exception really meant for the COM+ Debugger? Note: we will let the debugger have a chance if there |
6877 | // is a debugger attached to any part of the process. It is incorrect to consider whether or not the debugger |
6878 | // is attached the the thread's current app domain at this point. |
6879 | |
6880 | // Even if a debugger is not attached, we must let the debugger handle the exception in case it's coming from a |
6881 | // patch-skipper. |
6882 | if ((!IsComPlusException(pExceptionRecord)) && |
6883 | (GetThread() != NULL) && |
6884 | (g_pDebugInterface != NULL) && |
6885 | g_pDebugInterface->FirstChanceNativeException(pExceptionRecord, |
6886 | pContext, |
6887 | exceptionCode, |
6888 | pThread)) |
6889 | { |
6890 | LOG((LF_EH | LF_CORDB, LL_INFO1000, "IsDebuggerFault - it's the debugger's fault\n" )); |
6891 | return true; |
6892 | } |
6893 | #endif // DEBUGGING_SUPPORTED |
6894 | return false; |
6895 | } |
6896 | |
6897 | #endif // FEATURE_PAL |
6898 | |
6899 | #ifdef WIN64EXCEPTIONS |
6900 | |
6901 | #ifndef _TARGET_X86_ |
6902 | EXTERN_C void JIT_MemSet_End(); |
6903 | EXTERN_C void JIT_MemCpy_End(); |
6904 | |
6905 | EXTERN_C void JIT_WriteBarrier_End(); |
6906 | EXTERN_C void JIT_CheckedWriteBarrier_End(); |
6907 | EXTERN_C void JIT_ByRefWriteBarrier_End(); |
6908 | #endif // _TARGET_X86_ |
6909 | |
6910 | #if defined(_TARGET_AMD64_) && defined(_DEBUG) |
6911 | EXTERN_C void JIT_WriteBarrier_Debug(); |
6912 | EXTERN_C void JIT_WriteBarrier_Debug_End(); |
6913 | #endif |
6914 | |
6915 | #ifdef _TARGET_ARM_ |
6916 | EXTERN_C void FCallMemcpy_End(); |
6917 | #endif |
6918 | |
6919 | // Check if the passed in instruction pointer is in one of the |
6920 | // JIT helper functions. |
6921 | bool IsIPInMarkedJitHelper(UINT_PTR uControlPc) |
6922 | { |
6923 | LIMITED_METHOD_CONTRACT; |
6924 | |
6925 | #define CHECK_RANGE(name) \ |
6926 | if (GetEEFuncEntryPoint(name) <= uControlPc && uControlPc < GetEEFuncEntryPoint(name##_End)) return true; |
6927 | |
6928 | #ifndef _TARGET_X86_ |
6929 | CHECK_RANGE(JIT_MemSet) |
6930 | CHECK_RANGE(JIT_MemCpy) |
6931 | |
6932 | CHECK_RANGE(JIT_WriteBarrier) |
6933 | CHECK_RANGE(JIT_CheckedWriteBarrier) |
6934 | CHECK_RANGE(JIT_ByRefWriteBarrier) |
6935 | #else |
6936 | #ifdef FEATURE_PAL |
6937 | CHECK_RANGE(JIT_WriteBarrierGroup) |
6938 | CHECK_RANGE(JIT_PatchedWriteBarrierGroup) |
6939 | #endif // FEATURE_PAL |
6940 | #endif // _TARGET_X86_ |
6941 | |
6942 | #if defined(_TARGET_AMD64_) && defined(_DEBUG) |
6943 | CHECK_RANGE(JIT_WriteBarrier_Debug) |
6944 | #endif |
6945 | |
6946 | #ifdef _TARGET_ARM_ |
6947 | CHECK_RANGE(FCallMemcpy) |
6948 | #endif |
6949 | |
6950 | return false; |
6951 | } |
6952 | #endif // WIN64EXCEPTIONS |
6953 | |
6954 | // Returns TRUE if caller should resume execution. |
6955 | BOOL |
6956 | AdjustContextForWriteBarrier( |
6957 | EXCEPTION_RECORD *pExceptionRecord, |
6958 | CONTEXT *pContext) |
6959 | { |
6960 | WRAPPER_NO_CONTRACT; |
6961 | |
6962 | #ifdef FEATURE_DATABREAKPOINT |
6963 | |
6964 | // If pExceptionRecord is null, it means it is called from EEDbgInterfaceImpl::AdjustContextForWriteBarrierForDebugger() |
6965 | // This is called only when a data breakpoint is hitm which could be inside a JIT write barrier helper and required |
6966 | // this logic to help unwind out of it. For the x86, not patched case, we assume the IP lies within the region where we |
6967 | // have already saved the registers on the stack, and therefore the code unwind those registers as well. This is not true |
6968 | // for the usual AV case where the registers are not saved yet. |
6969 | |
6970 | if (pExceptionRecord == nullptr) |
6971 | { |
6972 | PCODE ip = GetIP(pContext); |
6973 | #if defined(_TARGET_X86_) |
6974 | bool withinWriteBarrierGroup = ((ip >= (PCODE) JIT_WriteBarrierGroup) && (ip <= (PCODE) JIT_WriteBarrierGroup_End)); |
6975 | bool withinPatchedWriteBarrierGroup = ((ip >= (PCODE) JIT_PatchedWriteBarrierGroup) && (ip <= (PCODE) JIT_PatchedWriteBarrierGroup_End)); |
6976 | if (withinWriteBarrierGroup || withinPatchedWriteBarrierGroup) |
6977 | { |
6978 | DWORD* esp = (DWORD*)pContext->Esp; |
6979 | if (withinWriteBarrierGroup) |
6980 | { |
6981 | #if defined(WRITE_BARRIER_CHECK) |
6982 | pContext->Ebp = *esp++; |
6983 | pContext->Ecx = *esp++; |
6984 | #endif |
6985 | } |
6986 | pContext->Eip = *esp++; |
6987 | pContext->Esp = (DWORD)esp; |
6988 | return TRUE; |
6989 | } |
6990 | #elif defined(_TARGET_AMD64_) |
6991 | if (IsIPInMarkedJitHelper((UINT_PTR)ip)) |
6992 | { |
6993 | Thread::VirtualUnwindToFirstManagedCallFrame(pContext); |
6994 | return TRUE; |
6995 | } |
6996 | #else |
6997 | #error Not supported |
6998 | #endif |
6999 | return FALSE; |
7000 | } |
7001 | |
7002 | #endif // FEATURE_DATABREAKPOINT |
7003 | |
7004 | #if defined(_TARGET_X86_) && !defined(PLATFORM_UNIX) |
7005 | void* f_IP = (void *)GetIP(pContext); |
7006 | |
7007 | if (((f_IP >= (void *) JIT_WriteBarrierGroup) && (f_IP <= (void *) JIT_WriteBarrierGroup_End)) || |
7008 | ((f_IP >= (void *) JIT_PatchedWriteBarrierGroup) && (f_IP <= (void *) JIT_PatchedWriteBarrierGroup_End))) |
7009 | { |
7010 | // set the exception IP to be the instruction that called the write barrier |
7011 | void* callsite = (void *)GetAdjustedCallAddress(*dac_cast<PTR_PCODE>(GetSP(pContext))); |
7012 | pExceptionRecord->ExceptionAddress = callsite; |
7013 | SetIP(pContext, (PCODE)callsite); |
7014 | |
7015 | // put ESP back to what it was before the call. |
7016 | SetSP(pContext, PCODE((BYTE*)GetSP(pContext) + sizeof(void*))); |
7017 | } |
7018 | return FALSE; |
7019 | #elif defined(WIN64EXCEPTIONS) // _TARGET_X86_ && !PLATFORM_UNIX |
7020 | void* f_IP = dac_cast<PTR_VOID>(GetIP(pContext)); |
7021 | |
7022 | CONTEXT tempContext; |
7023 | CONTEXT* pExceptionContext = pContext; |
7024 | |
7025 | BOOL fExcluded = IsIPInMarkedJitHelper((UINT_PTR)f_IP); |
7026 | |
7027 | if (fExcluded) |
7028 | { |
7029 | bool fShouldHandleManagedFault = false; |
7030 | |
7031 | if (pContext != &tempContext) |
7032 | { |
7033 | tempContext = *pContext; |
7034 | pContext = &tempContext; |
7035 | } |
7036 | |
7037 | Thread::VirtualUnwindToFirstManagedCallFrame(pContext); |
7038 | |
7039 | #if defined(_TARGET_ARM_) || defined(_TARGET_ARM64_) |
7040 | // We had an AV in the writebarrier that needs to be treated |
7041 | // as originating in managed code. At this point, the stack (growing |
7042 | // from left->right) looks like this: |
7043 | // |
7044 | // ManagedFunc -> Native_WriteBarrierInVM -> AV |
7045 | // |
7046 | // We just performed an unwind from the write-barrier |
7047 | // and now have the context in ManagedFunc. Since |
7048 | // ManagedFunc called into the write-barrier, the return |
7049 | // address in the unwound context corresponds to the |
7050 | // instruction where the call will return. |
7051 | // |
7052 | // On ARM, just like we perform ControlPC adjustment |
7053 | // during exception dispatch (refer to ExceptionTracker::InitializeCrawlFrame), |
7054 | // we will need to perform the corresponding adjustment of IP |
7055 | // we got from unwind above, so as to indicate that the AV |
7056 | // happened "before" the call to the writebarrier and not at |
7057 | // the instruction at which the control will return. |
7058 | PCODE ControlPCPostAdjustment = GetIP(pContext) - STACKWALK_CONTROLPC_ADJUST_OFFSET; |
7059 | |
7060 | // Now we save the address back into the context so that it gets used |
7061 | // as the faulting address. |
7062 | SetIP(pContext, ControlPCPostAdjustment); |
7063 | #endif // _TARGET_ARM_ || _TARGET_ARM64_ |
7064 | |
7065 | // Unwind the frame chain - On Win64, this is required since we may handle the managed fault and to do so, |
7066 | // we will replace the exception context with the managed context and "continue execution" there. Thus, we do not |
7067 | // want any explicit frames active below the resumption SP. |
7068 | // |
7069 | // Question: Why do we unwind before determining whether we will handle the exception or not? |
7070 | UnwindFrameChain(GetThread(), (Frame*)GetSP(pContext)); |
7071 | fShouldHandleManagedFault = ShouldHandleManagedFault(pExceptionRecord,pContext, |
7072 | NULL, // establisher frame (x86 only) |
7073 | NULL // pThread (x86 only) |
7074 | ); |
7075 | |
7076 | if (fShouldHandleManagedFault) |
7077 | { |
7078 | ReplaceExceptionContextRecord(pExceptionContext, pContext); |
7079 | pExceptionRecord->ExceptionAddress = dac_cast<PTR_VOID>(GetIP(pContext)); |
7080 | return TRUE; |
7081 | } |
7082 | } |
7083 | |
7084 | return FALSE; |
7085 | #else // WIN64EXCEPTIONS |
7086 | PORTABILITY_ASSERT("AdjustContextForWriteBarrier" ); |
7087 | return FALSE; |
7088 | #endif // ELSE |
7089 | } |
7090 | |
7091 | #if defined(USE_FEF) && !defined(FEATURE_PAL) |
7092 | |
7093 | struct SavedExceptionInfo |
7094 | { |
7095 | EXCEPTION_RECORD m_ExceptionRecord; |
7096 | CONTEXT m_ExceptionContext; |
7097 | CrstStatic m_Crst; |
7098 | |
7099 | void SaveExceptionRecord(EXCEPTION_RECORD *pExceptionRecord) |
7100 | { |
7101 | LIMITED_METHOD_CONTRACT; |
7102 | size_t erSize = offsetof(EXCEPTION_RECORD, ExceptionInformation) + |
7103 | pExceptionRecord->NumberParameters * sizeof(pExceptionRecord->ExceptionInformation[0]); |
7104 | memcpy(&m_ExceptionRecord, pExceptionRecord, erSize); |
7105 | |
7106 | } |
7107 | |
7108 | void SaveContext(CONTEXT *pContext) |
7109 | { |
7110 | LIMITED_METHOD_CONTRACT; |
7111 | #ifdef CONTEXT_EXTENDED_REGISTERS |
7112 | |
7113 | size_t contextSize = offsetof(CONTEXT, ExtendedRegisters); |
7114 | if ((pContext->ContextFlags & CONTEXT_EXTENDED_REGISTERS) == CONTEXT_EXTENDED_REGISTERS) |
7115 | contextSize += sizeof(pContext->ExtendedRegisters); |
7116 | memcpy(&m_ExceptionContext, pContext, contextSize); |
7117 | |
7118 | #else // !CONTEXT_EXTENDED_REGISTERS |
7119 | |
7120 | size_t contextSize = sizeof(CONTEXT); |
7121 | memcpy(&m_ExceptionContext, pContext, contextSize); |
7122 | |
7123 | #endif // !CONTEXT_EXTENDED_REGISTERS |
7124 | } |
7125 | |
7126 | DEBUG_NOINLINE void Enter() |
7127 | { |
7128 | WRAPPER_NO_CONTRACT; |
7129 | ANNOTATION_SPECIAL_HOLDER_CALLER_NEEDS_DYNAMIC_CONTRACT; |
7130 | m_Crst.Enter(); |
7131 | } |
7132 | |
7133 | DEBUG_NOINLINE void Leave() |
7134 | { |
7135 | WRAPPER_NO_CONTRACT; |
7136 | ANNOTATION_SPECIAL_HOLDER_CALLER_NEEDS_DYNAMIC_CONTRACT; |
7137 | m_Crst.Leave(); |
7138 | } |
7139 | |
7140 | void Init() |
7141 | { |
7142 | WRAPPER_NO_CONTRACT; |
7143 | m_Crst.Init(CrstSavedExceptionInfo, CRST_UNSAFE_ANYMODE); |
7144 | } |
7145 | }; |
7146 | |
7147 | SavedExceptionInfo g_SavedExceptionInfo; // Globals are guaranteed zero-init; |
7148 | |
7149 | void InitSavedExceptionInfo() |
7150 | { |
7151 | g_SavedExceptionInfo.Init(); |
7152 | } |
7153 | |
7154 | EXTERN_C VOID FixContextForFaultingExceptionFrame ( |
7155 | EXCEPTION_RECORD* pExceptionRecord, |
7156 | CONTEXT *pContextRecord) |
7157 | { |
7158 | WRAPPER_NO_CONTRACT; |
7159 | |
7160 | // don't copy parm args as have already supplied them on the throw |
7161 | memcpy((void*) pExceptionRecord, |
7162 | (void*) &g_SavedExceptionInfo.m_ExceptionRecord, |
7163 | offsetof(EXCEPTION_RECORD, ExceptionInformation) |
7164 | ); |
7165 | |
7166 | ReplaceExceptionContextRecord(pContextRecord, &g_SavedExceptionInfo.m_ExceptionContext); |
7167 | |
7168 | g_SavedExceptionInfo.Leave(); |
7169 | |
7170 | GetThread()->ResetThreadStateNC(Thread::TSNC_DebuggerIsManagedException); |
7171 | } |
7172 | |
7173 | EXTERN_C VOID __fastcall |
7174 | LinkFrameAndThrow(FaultingExceptionFrame* pFrame) |
7175 | { |
7176 | WRAPPER_NO_CONTRACT; |
7177 | |
7178 | *(TADDR*)pFrame = FaultingExceptionFrame::GetMethodFrameVPtr(); |
7179 | *pFrame->GetGSCookiePtr() = GetProcessGSCookie(); |
7180 | |
7181 | pFrame->InitAndLink(&g_SavedExceptionInfo.m_ExceptionContext); |
7182 | |
7183 | GetThread()->SetThreadStateNC(Thread::TSNC_DebuggerIsManagedException); |
7184 | |
7185 | ULONG argcount = g_SavedExceptionInfo.m_ExceptionRecord.NumberParameters; |
7186 | ULONG flags = g_SavedExceptionInfo.m_ExceptionRecord.ExceptionFlags; |
7187 | ULONG code = g_SavedExceptionInfo.m_ExceptionRecord.ExceptionCode; |
7188 | ULONG_PTR* args = &g_SavedExceptionInfo.m_ExceptionRecord.ExceptionInformation[0]; |
7189 | |
7190 | RaiseException(code, flags, argcount, args); |
7191 | } |
7192 | |
7193 | void SetNakedThrowHelperArgRegistersInContext(CONTEXT* pContext) |
7194 | { |
7195 | #if defined(_TARGET_AMD64_) |
7196 | pContext->Rcx = (UINT_PTR)GetIP(pContext); |
7197 | #elif defined(_TARGET_ARM_) || defined(_TARGET_ARM64_) |
7198 | // Save the original IP in LR |
7199 | pContext->Lr = (DWORD)GetIP(pContext); |
7200 | #else |
7201 | PORTABILITY_WARNING("NakedThrowHelper argument not defined" ); |
7202 | #endif |
7203 | } |
7204 | |
7205 | EXTERN_C VOID STDCALL NakedThrowHelper(VOID); |
7206 | |
7207 | void HandleManagedFault(EXCEPTION_RECORD* pExceptionRecord, |
7208 | CONTEXT* pContext, |
7209 | EXCEPTION_REGISTRATION_RECORD* pEstablisherFrame, |
7210 | Thread* pThread) |
7211 | { |
7212 | WRAPPER_NO_CONTRACT; |
7213 | |
7214 | // Ok. Now we have a brand new fault in jitted code. |
7215 | g_SavedExceptionInfo.Enter(); |
7216 | g_SavedExceptionInfo.SaveExceptionRecord(pExceptionRecord); |
7217 | g_SavedExceptionInfo.SaveContext(pContext); |
7218 | |
7219 | SetNakedThrowHelperArgRegistersInContext(pContext); |
7220 | |
7221 | SetIP(pContext, GetEEFuncEntryPoint(NakedThrowHelper)); |
7222 | } |
7223 | |
7224 | #else // USE_FEF && !FEATURE_PAL |
7225 | |
7226 | void InitSavedExceptionInfo() |
7227 | { |
7228 | } |
7229 | |
7230 | #endif // USE_FEF && !FEATURE_PAL |
7231 | |
7232 | // |
7233 | // Init a new frame |
7234 | // |
7235 | void FaultingExceptionFrame::Init(CONTEXT *pContext) |
7236 | { |
7237 | WRAPPER_NO_CONTRACT; |
7238 | #ifndef WIN64EXCEPTIONS |
7239 | #ifdef _TARGET_X86_ |
7240 | CalleeSavedRegisters *pRegs = GetCalleeSavedRegisters(); |
7241 | #define CALLEE_SAVED_REGISTER(regname) pRegs->regname = pContext->regname; |
7242 | ENUM_CALLEE_SAVED_REGISTERS(); |
7243 | #undef CALLEE_SAVED_REGISTER |
7244 | m_ReturnAddress = ::GetIP(pContext); |
7245 | m_Esp = (DWORD)GetSP(pContext); |
7246 | #else // _TARGET_X86_ |
7247 | PORTABILITY_ASSERT("FaultingExceptionFrame::Init" ); |
7248 | #endif // _TARGET_???_ (ELSE) |
7249 | #else // !WIN64EXCEPTIONS |
7250 | m_ReturnAddress = ::GetIP(pContext); |
7251 | CopyOSContext(&m_ctx, pContext); |
7252 | #endif // !WIN64EXCEPTIONS |
7253 | } |
7254 | |
7255 | // |
7256 | // Init and Link in a new frame |
7257 | // |
7258 | void FaultingExceptionFrame::InitAndLink(CONTEXT *pContext) |
7259 | { |
7260 | WRAPPER_NO_CONTRACT; |
7261 | |
7262 | Init(pContext); |
7263 | |
7264 | Push(); |
7265 | } |
7266 | |
7267 | |
7268 | bool ShouldHandleManagedFault( |
7269 | EXCEPTION_RECORD* pExceptionRecord, |
7270 | CONTEXT* pContext, |
7271 | EXCEPTION_REGISTRATION_RECORD* pEstablisherFrame, |
7272 | Thread* pThread) |
7273 | { |
7274 | CONTRACTL |
7275 | { |
7276 | NOTHROW; |
7277 | GC_NOTRIGGER; |
7278 | SO_TOLERANT; |
7279 | MODE_ANY; |
7280 | } |
7281 | CONTRACTL_END; |
7282 | |
7283 | // If we get a faulting instruction inside managed code, we're going to |
7284 | // 1. Allocate the correct exception object, store it in the thread. |
7285 | // 2. Save the EIP in the thread. |
7286 | // 3. Change the EIP to our throw helper |
7287 | // 4. Resume execution. |
7288 | // |
7289 | // The helper will push a frame for us, and then throw the correct managed exception. |
7290 | // |
7291 | // Is this exception really meant for the COM+ Debugger? Note: we will let the debugger have a chance if there is a |
7292 | // debugger attached to any part of the process. It is incorrect to consider whether or not the debugger is attached |
7293 | // the the thread's current app domain at this point. |
7294 | |
7295 | |
7296 | // A managed exception never comes from managed code, and we can ignore all breakpoint |
7297 | // exceptions. |
7298 | // |
7299 | DWORD exceptionCode = pExceptionRecord->ExceptionCode; |
7300 | if (IsComPlusException(pExceptionRecord) |
7301 | || exceptionCode == STATUS_BREAKPOINT |
7302 | || exceptionCode == STATUS_SINGLE_STEP) |
7303 | { |
7304 | return false; |
7305 | } |
7306 | |
7307 | #ifdef _DEBUG |
7308 | // This is a workaround, but it's debug-only as is gc stress 4. |
7309 | // The problem is that if we get an exception with this code that |
7310 | // didn't come from GCStress=4, then we won't push a FeF and will |
7311 | // end up with a gc hole and potential crash. |
7312 | if (exceptionCode == STATUS_CLR_GCCOVER_CODE) |
7313 | return false; |
7314 | #endif // _DEBUG |
7315 | |
7316 | #ifndef WIN64EXCEPTIONS |
7317 | // If there's any frame below the ESP of the exception, then we can forget it. |
7318 | if (pThread->m_pFrame < dac_cast<PTR_VOID>(GetSP(pContext))) |
7319 | return false; |
7320 | |
7321 | // If we're a subsequent handler forget it. |
7322 | EXCEPTION_REGISTRATION_RECORD* pBottomMostHandler = pThread->GetExceptionState()->m_currentExInfo.m_pBottomMostHandler; |
7323 | if (pBottomMostHandler != NULL && pEstablisherFrame > pBottomMostHandler) |
7324 | { |
7325 | return false; |
7326 | } |
7327 | #endif // WIN64EXCEPTIONS |
7328 | |
7329 | { |
7330 | // If it's not a fault in jitted code, forget it. |
7331 | |
7332 | // ExecutionManager::IsManagedCode takes a spinlock. Since we're in the middle of throwing, |
7333 | // we'll allow the lock, even if a caller didn't expect it. |
7334 | CONTRACT_VIOLATION(TakesLockViolation); |
7335 | |
7336 | if (!ExecutionManager::IsManagedCode(GetIP(pContext))) |
7337 | return false; |
7338 | } |
7339 | |
7340 | // caller should call HandleManagedFault and resume execution. |
7341 | return true; |
7342 | } |
7343 | |
7344 | #ifndef FEATURE_PAL |
7345 | |
7346 | LONG WINAPI CLRVectoredExceptionHandlerPhase2(PEXCEPTION_POINTERS pExceptionInfo); |
7347 | |
7348 | enum VEH_ACTION |
7349 | { |
7350 | VEH_NO_ACTION = 0, |
7351 | VEH_EXECUTE_HANDLE_MANAGED_EXCEPTION, |
7352 | VEH_CONTINUE_EXECUTION, |
7353 | VEH_CONTINUE_SEARCH, |
7354 | VEH_EXECUTE_HANDLER |
7355 | }; |
7356 | |
7357 | |
7358 | VEH_ACTION WINAPI CLRVectoredExceptionHandlerPhase3(PEXCEPTION_POINTERS pExceptionInfo); |
7359 | |
7360 | LONG WINAPI CLRVectoredExceptionHandler(PEXCEPTION_POINTERS pExceptionInfo) |
7361 | { |
7362 | // It is not safe to execute code inside VM after we shutdown EE. One example is DisablePreemptiveGC |
7363 | // will block forever. |
7364 | if (g_fForbidEnterEE) |
7365 | { |
7366 | return EXCEPTION_CONTINUE_SEARCH; |
7367 | } |
7368 | |
7369 | // |
7370 | // For images ngen'd with FEATURE_LAZY_COW_PAGES, the .data section will be read-only. Any writes to that data need to be |
7371 | // preceded by a call to EnsureWritablePages. This code is here to catch the ones we forget. |
7372 | // |
7373 | #ifdef FEATURE_LAZY_COW_PAGES |
7374 | if (pExceptionInfo->ExceptionRecord->ExceptionCode == STATUS_ACCESS_VIOLATION && |
7375 | IsWellFormedAV(pExceptionInfo->ExceptionRecord) && |
7376 | pExceptionInfo->ExceptionRecord->ExceptionInformation[0] == 1 /* means this was a failed write */) |
7377 | { |
7378 | void* location = (void*)pExceptionInfo->ExceptionRecord->ExceptionInformation[1]; |
7379 | |
7380 | if (IsInReadOnlyLazyCOWPage(location)) |
7381 | { |
7382 | #ifdef _DEBUG |
7383 | if (CLRConfig::GetConfigValue(CLRConfig::INTERNAL_DebugAssertOnMissedCOWPage)) |
7384 | _ASSERTE_MSG(false, "Writes to NGen'd data must be protected by EnsureWritablePages." ); |
7385 | #endif |
7386 | |
7387 | #pragma push_macro("VirtualQuery") |
7388 | #undef VirtualQuery |
7389 | MEMORY_BASIC_INFORMATION mbi; |
7390 | if (!::VirtualQuery(location, &mbi, sizeof(mbi))) |
7391 | { |
7392 | EEPOLICY_HANDLE_FATAL_ERROR(COR_E_OUTOFMEMORY); |
7393 | } |
7394 | #pragma pop_macro("VirtualQuery") |
7395 | |
7396 | bool executable = (mbi.Protect == PAGE_EXECUTE_READ) || |
7397 | (mbi.Protect == PAGE_EXECUTE_READWRITE) || |
7398 | (mbi.Protect == PAGE_EXECUTE_READ) || |
7399 | (mbi.Protect == PAGE_EXECUTE_WRITECOPY); |
7400 | |
7401 | if (!(executable ? EnsureWritableExecutablePagesNoThrow(location, 1) : EnsureWritablePagesNoThrow(location, 1))) |
7402 | { |
7403 | // Note that this failfast is very rare. It will only be hit in the theoretical cases there is |
7404 | // missing EnsureWritablePages probe (there should be none when we ship), and the OS run into OOM |
7405 | // exactly at the point when we executed the code with the missing probe. |
7406 | EEPOLICY_HANDLE_FATAL_ERROR(COR_E_OUTOFMEMORY); |
7407 | } |
7408 | |
7409 | return EXCEPTION_CONTINUE_EXECUTION; |
7410 | } |
7411 | } |
7412 | #endif //FEATURE_LAZY_COW_PAGES |
7413 | |
7414 | |
7415 | // |
7416 | // DO NOT USE CONTRACTS HERE AS THIS ROUTINE MAY NEVER RETURN. You can use |
7417 | // static contracts, but currently this is all WRAPPER_NO_CONTRACT. |
7418 | // |
7419 | |
7420 | |
7421 | // |
7422 | // READ THIS! |
7423 | // |
7424 | // |
7425 | // You cannot put any code in here that allocates during an out-of-memory handling. |
7426 | // This routine runs before *any* other handlers, including __try. Thus, if you |
7427 | // allocate anything in this routine then it will throw out-of-memory and end up |
7428 | // right back here. |
7429 | // |
7430 | // There are various things that allocate that you may not expect to allocate. One |
7431 | // instance of this is STRESS_LOG. It allocates the log buffer if the thread does |
7432 | // not already have one allocated. Thus, if we OOM during the setting up of the |
7433 | // thread, the log buffer will not be allocated and this will try to do so. Thus, |
7434 | // all STRESS_LOGs in here need to be after you have guaranteed the allocation has |
7435 | // already occurred. |
7436 | // |
7437 | |
7438 | Thread *pThread; |
7439 | |
7440 | { |
7441 | MAYBE_FAULT_FORBID_NO_ALLOC((pExceptionInfo->ExceptionRecord->ExceptionCode == STATUS_NO_MEMORY)); |
7442 | |
7443 | pThread = GetThread(); |
7444 | |
7445 | // |
7446 | // Since we are in an OOM situation, we test the thread object before logging since if the |
7447 | // thread exists we know the log buffer has been allocated already. |
7448 | // |
7449 | if (pThread != NULL) |
7450 | { |
7451 | CantAllocHolder caHolder; |
7452 | STRESS_LOG4(LF_EH, LL_INFO100, "In CLRVectoredExceptionHandler, Exception = %x, Context = %p, IP = %p SP = %p\n" , |
7453 | pExceptionInfo->ExceptionRecord->ExceptionCode, pExceptionInfo->ContextRecord, |
7454 | GetIP(pExceptionInfo->ContextRecord), GetSP(pExceptionInfo->ContextRecord)); |
7455 | } |
7456 | |
7457 | } |
7458 | |
7459 | // We need to unhijack the thread here if it is not unhijacked already. On x86 systems, |
7460 | // we do this in Thread::StackWalkFramesEx, but on amd64 systems we have the OS walk the |
7461 | // stack for us. If we leave CLRVectoredExceptionHandler with a thread still hijacked, |
7462 | // the operating system will not be able to walk the stack and not find the handlers for |
7463 | // the exception. It is safe to unhijack the thread in this case for two reasons: |
7464 | // 1. pThread refers to *this* thread. |
7465 | // 2. If another thread tries to hijack this thread, it will see we are not in managed |
7466 | // code (and thus won't try to hijack us). |
7467 | #if defined(WIN64EXCEPTIONS) && defined(FEATURE_HIJACK) |
7468 | if (pThread != NULL) |
7469 | { |
7470 | pThread->UnhijackThreadNoAlloc(); |
7471 | } |
7472 | #endif // defined(WIN64EXCEPTIONS) && defined(FEATURE_HIJACK) |
7473 | |
7474 | if (IsSOExceptionCode(pExceptionInfo->ExceptionRecord->ExceptionCode)) |
7475 | { |
7476 | // |
7477 | // Not an Out-of-memory situation, so no need for a forbid fault region here |
7478 | // |
7479 | return EXCEPTION_CONTINUE_SEARCH; |
7480 | } |
7481 | |
7482 | LONG retVal = 0; |
7483 | |
7484 | #ifdef FEATURE_STACK_PROBE |
7485 | // See if we've got enough stack to handle this exception |
7486 | |
7487 | // There isn't much stack left to attempt to report an exception. Let's trigger a hard |
7488 | // SO, so we clear the guard page and give us at least another page of stack to work with. |
7489 | |
7490 | if (pThread && !pThread->IsStackSpaceAvailable(ADJUST_PROBE(1))) |
7491 | { |
7492 | DontCallDirectlyForceStackOverflow(); |
7493 | } |
7494 | #endif // FEATURE_STACK_PROBE |
7495 | |
7496 | // We can't probe here, because we won't return from the CLRVectoredExceptionHandlerPhase2 |
7497 | // on WIN64 |
7498 | // |
7499 | |
7500 | if (pThread) |
7501 | { |
7502 | FAULT_FORBID_NO_ALLOC(); |
7503 | CantAllocHolder caHolder; |
7504 | } |
7505 | |
7506 | retVal = CLRVectoredExceptionHandlerPhase2(pExceptionInfo); |
7507 | |
7508 | // |
7509 | //END_ENTRYPOINT_VOIDRET; |
7510 | // |
7511 | return retVal; |
7512 | } |
7513 | |
7514 | LONG WINAPI CLRVectoredExceptionHandlerPhase2(PEXCEPTION_POINTERS pExceptionInfo) |
7515 | { |
7516 | // |
7517 | // DO NOT USE CONTRACTS HERE AS THIS ROUTINE MAY NEVER RETURN. You can use |
7518 | // static contracts, but currently this is all WRAPPER_NO_CONTRACT. |
7519 | // |
7520 | |
7521 | // |
7522 | // READ THIS! |
7523 | // |
7524 | // |
7525 | // You cannot put any code in here that allocates during an out-of-memory handling. |
7526 | // This routine runs before *any* other handlers, including __try. Thus, if you |
7527 | // allocate anything in this routine then it will throw out-of-memory and end up |
7528 | // right back here. |
7529 | // |
7530 | // There are various things that allocate that you may not expect to allocate. One |
7531 | // instance of this is STRESS_LOG. It allocates the log buffer if the thread does |
7532 | // not already have one allocated. Thus, if we OOM during the setting up of the |
7533 | // thread, the log buffer will not be allocated and this will try to do so. Thus, |
7534 | // all STRESS_LOGs in here need to be after you have guaranteed the allocation has |
7535 | // already occurred. |
7536 | // |
7537 | |
7538 | PEXCEPTION_RECORD pExceptionRecord = pExceptionInfo->ExceptionRecord; |
7539 | VEH_ACTION action; |
7540 | |
7541 | { |
7542 | MAYBE_FAULT_FORBID_NO_ALLOC((pExceptionRecord->ExceptionCode == STATUS_NO_MEMORY)); |
7543 | CantAllocHolder caHolder; |
7544 | |
7545 | action = CLRVectoredExceptionHandlerPhase3(pExceptionInfo); |
7546 | } |
7547 | |
7548 | if (action == VEH_CONTINUE_EXECUTION) |
7549 | { |
7550 | return EXCEPTION_CONTINUE_EXECUTION; |
7551 | } |
7552 | |
7553 | if (action == VEH_CONTINUE_SEARCH) |
7554 | { |
7555 | return EXCEPTION_CONTINUE_SEARCH; |
7556 | } |
7557 | |
7558 | if (action == VEH_EXECUTE_HANDLER) |
7559 | { |
7560 | return EXCEPTION_EXECUTE_HANDLER; |
7561 | } |
7562 | |
7563 | #if defined(WIN64EXCEPTIONS) |
7564 | |
7565 | if (action == VEH_EXECUTE_HANDLE_MANAGED_EXCEPTION) |
7566 | { |
7567 | // |
7568 | // If the exception context was unwound by Phase3 then |
7569 | // we'll jump here to save the managed context and resume execution at |
7570 | // NakedThrowHelper. This needs to be done outside of any holder's |
7571 | // scope, because HandleManagedFault may not return. |
7572 | // |
7573 | HandleManagedFault(pExceptionInfo->ExceptionRecord, |
7574 | pExceptionInfo->ContextRecord, |
7575 | NULL, // establisher frame (x86 only) |
7576 | NULL // pThread (x86 only) |
7577 | ); |
7578 | return EXCEPTION_CONTINUE_EXECUTION; |
7579 | } |
7580 | |
7581 | #endif // defined(WIN64EXCEPTIONS) |
7582 | |
7583 | |
7584 | // |
7585 | // In OOM situations, this call better not fault. |
7586 | // |
7587 | { |
7588 | MAYBE_FAULT_FORBID_NO_ALLOC((pExceptionRecord->ExceptionCode == STATUS_NO_MEMORY)); |
7589 | CantAllocHolder caHolder; |
7590 | |
7591 | // Give the debugger a chance. Note that its okay for this call to trigger a GC, since the debugger will take |
7592 | // special steps to make that okay. |
7593 | // |
7594 | // @TODO: I'd love a way to call into the debugger with GCX_NOTRIGGER still in scope, and force them to make |
7595 | // the choice to break the no-trigger region after taking all necessary precautions. |
7596 | if (IsDebuggerFault(pExceptionRecord, pExceptionInfo->ContextRecord, pExceptionRecord->ExceptionCode, GetThread())) |
7597 | { |
7598 | return EXCEPTION_CONTINUE_EXECUTION; |
7599 | } |
7600 | } |
7601 | |
7602 | // |
7603 | // No reason to put a forbid fault region here as the exception code is not STATUS_NO_MEMORY. |
7604 | // |
7605 | |
7606 | // Handle a user breakpoint. Note that its okay for the UserBreakpointFilter to trigger a GC, since we're going |
7607 | // to either a) terminate the process, or b) let a user attach an unmanaged debugger, and debug knowing that |
7608 | // managed state may be messed up. |
7609 | if ((pExceptionRecord->ExceptionCode == STATUS_BREAKPOINT) || |
7610 | (pExceptionRecord->ExceptionCode == STATUS_SINGLE_STEP)) |
7611 | { |
7612 | // A breakpoint outside managed code and outside the runtime will have to be handled by some |
7613 | // other piece of code. |
7614 | |
7615 | BOOL fExternalException = FALSE; |
7616 | |
7617 | BEGIN_SO_INTOLERANT_CODE_NOPROBE; |
7618 | |
7619 | { |
7620 | // ExecutionManager::IsManagedCode takes a spinlock. Since we're in the middle of throwing, |
7621 | // we'll allow the lock, even if a caller didn't expect it. |
7622 | CONTRACT_VIOLATION(TakesLockViolation); |
7623 | |
7624 | fExternalException = (!ExecutionManager::IsManagedCode(GetIP(pExceptionInfo->ContextRecord)) && |
7625 | !IsIPInModule(g_pMSCorEE, GetIP(pExceptionInfo->ContextRecord))); |
7626 | } |
7627 | |
7628 | END_SO_INTOLERANT_CODE_NOPROBE; |
7629 | |
7630 | if (fExternalException) |
7631 | { |
7632 | // The breakpoint was not ours. Someone else can handle it. (Or if not, we'll get it again as |
7633 | // an unhandled exception.) |
7634 | return EXCEPTION_CONTINUE_SEARCH; |
7635 | } |
7636 | |
7637 | // The breakpoint was from managed or the runtime. Handle it. |
7638 | return UserBreakpointFilter(pExceptionInfo); |
7639 | } |
7640 | |
7641 | #if defined(WIN64EXCEPTIONS) |
7642 | BOOL fShouldHandleManagedFault; |
7643 | |
7644 | { |
7645 | MAYBE_FAULT_FORBID_NO_ALLOC((pExceptionRecord->ExceptionCode == STATUS_NO_MEMORY)); |
7646 | CantAllocHolder caHolder; |
7647 | fShouldHandleManagedFault = ShouldHandleManagedFault(pExceptionInfo->ExceptionRecord, |
7648 | pExceptionInfo->ContextRecord, |
7649 | NULL, // establisher frame (x86 only) |
7650 | NULL // pThread (x86 only) |
7651 | ); |
7652 | } |
7653 | |
7654 | if (fShouldHandleManagedFault) |
7655 | { |
7656 | // |
7657 | // HandleManagedFault may never return, so we cannot use a forbid fault region around it. |
7658 | // |
7659 | HandleManagedFault(pExceptionInfo->ExceptionRecord, |
7660 | pExceptionInfo->ContextRecord, |
7661 | NULL, // establisher frame (x86 only) |
7662 | NULL // pThread (x86 only) |
7663 | ); |
7664 | return EXCEPTION_CONTINUE_EXECUTION; |
7665 | } |
7666 | #endif // defined(WIN64EXCEPTIONS) |
7667 | |
7668 | return EXCEPTION_EXECUTE_HANDLER; |
7669 | } |
7670 | |
7671 | /* |
7672 | * CLRVectoredExceptionHandlerPhase3 |
7673 | * |
7674 | * This routine does some basic processing on the exception, making decisions about common |
7675 | * exception types and whether to continue them or not. It has side-effects, in that it may |
7676 | * adjust the context in the exception. |
7677 | * |
7678 | * Parameters: |
7679 | * pExceptionInfo - pointer to the exception |
7680 | * |
7681 | * Returns: |
7682 | * VEH_NO_ACTION - This indicates that Phase3 has no specific action to take and that further |
7683 | * processing of this exception should continue. |
7684 | * VEH_EXECUTE_HANDLE_MANAGED_EXCEPTION - This indicates that the caller should call HandleMandagedException |
7685 | * immediately. |
7686 | * VEH_CONTINUE_EXECUTION - Caller should return EXCEPTION_CONTINUE_EXECUTION. |
7687 | * VEH_CONTINUE_SEARCH - Caller should return EXCEPTION_CONTINUE_SEARCH; |
7688 | * VEH_EXECUTE_HANDLER - Caller should return EXCEPTION_EXECUTE_HANDLER. |
7689 | * |
7690 | * Note that in all cases the context in the exception may have been adjusted. |
7691 | * |
7692 | */ |
7693 | |
7694 | VEH_ACTION WINAPI CLRVectoredExceptionHandlerPhase3(PEXCEPTION_POINTERS pExceptionInfo) |
7695 | { |
7696 | // |
7697 | // DO NOT USE CONTRACTS HERE AS THIS ROUTINE MAY NEVER RETURN. You can use |
7698 | // static contracts, but currently this is all WRAPPER_NO_CONTRACT. |
7699 | // |
7700 | |
7701 | // |
7702 | // READ THIS! |
7703 | // |
7704 | // |
7705 | // You cannot put any code in here that allocates during an out-of-memory handling. |
7706 | // This routine runs before *any* other handlers, including __try. Thus, if you |
7707 | // allocate anything in this routine then it will throw out-of-memory and end up |
7708 | // right back here. |
7709 | // |
7710 | // There are various things that allocate that you may not expect to allocate. One |
7711 | // instance of this is STRESS_LOG. It allocates the log buffer if the thread does |
7712 | // not already have one allocated. Thus, if we OOM during the setting up of the |
7713 | // thread, the log buffer will not be allocated and this will try to do so. Thus, |
7714 | // all STRESS_LOGs in here need to be after you have guaranteed the allocation has |
7715 | // already occurred. |
7716 | // |
7717 | |
7718 | // Handle special cases which are common amongst all filters. |
7719 | PEXCEPTION_RECORD pExceptionRecord = pExceptionInfo->ExceptionRecord; |
7720 | PCONTEXT pContext = pExceptionInfo->ContextRecord; |
7721 | DWORD exceptionCode = pExceptionRecord->ExceptionCode; |
7722 | |
7723 | // Its extremely important that no one trigger a GC in here. This is called from CPFH_FirstPassHandler, in |
7724 | // cases where we've taken an unmanaged exception on a managed thread (AV, divide by zero, etc.) but |
7725 | // _before_ we've done our work to erect a FaultingExceptionFrame. Thus, the managed frames are |
7726 | // unprotected. We setup a GCX_NOTRIGGER holder in this scope to prevent us from messing this up. Note |
7727 | // that the scope of this is limited, since there are times when its okay to trigger even in this special |
7728 | // case. The debugger is a good example: if it gets a breakpoint in managed code, it has the smarts to |
7729 | // prevent the GC before enabling GC, thus its okay for it to trigger. |
7730 | |
7731 | GCX_NOTRIGGER(); |
7732 | |
7733 | #ifdef USE_REDIRECT_FOR_GCSTRESS |
7734 | // NOTE: this is effectively ifdef (_TARGET_AMD64_ || _TARGET_ARM_), and does not actually trigger |
7735 | // a GC. This will redirect the exception context to a stub which will |
7736 | // push a frame and cause GC. |
7737 | if (IsGcMarker(pContext, pExceptionRecord)) |
7738 | { |
7739 | return VEH_CONTINUE_EXECUTION;; |
7740 | } |
7741 | #endif // USE_REDIRECT_FOR_GCSTRESS |
7742 | |
7743 | #if defined(FEATURE_HIJACK) && !defined(PLATFORM_UNIX) |
7744 | #ifdef _TARGET_X86_ |
7745 | CPFH_AdjustContextForThreadSuspensionRace(pContext, GetThread()); |
7746 | #endif // _TARGET_X86_ |
7747 | #endif // FEATURE_HIJACK && !PLATFORM_UNIX |
7748 | |
7749 | // Some other parts of the EE use exceptions in their own nefarious ways. We do some up-front processing |
7750 | // here to fix up the exception if needed. |
7751 | if (exceptionCode == STATUS_ACCESS_VIOLATION) |
7752 | { |
7753 | if (IsWellFormedAV(pExceptionRecord)) |
7754 | { |
7755 | if (AdjustContextForWriteBarrier(pExceptionRecord, pContext)) |
7756 | { |
7757 | // On x86, AdjustContextForWriteBarrier simply backs up AV's |
7758 | // in write barrier helpers into the calling frame, so that |
7759 | // the subsequent logic here sees a managed fault. |
7760 | // |
7761 | // On 64-bit, some additional work is required.. |
7762 | #ifdef WIN64EXCEPTIONS |
7763 | return VEH_EXECUTE_HANDLE_MANAGED_EXCEPTION; |
7764 | #endif // defined(WIN64EXCEPTIONS) |
7765 | } |
7766 | else if (AdjustContextForVirtualStub(pExceptionRecord, pContext)) |
7767 | { |
7768 | #ifdef WIN64EXCEPTIONS |
7769 | return VEH_EXECUTE_HANDLE_MANAGED_EXCEPTION; |
7770 | #endif |
7771 | } |
7772 | |
7773 | // Remember the EIP for stress debugging purposes. |
7774 | g_LastAccessViolationEIP = (void*) ::GetIP(pContext); |
7775 | |
7776 | // Note: we have a holder, called AVInRuntimeImplOkayHolder, that tells us that its okay to have an |
7777 | // AV in the Runtime's implementation in certain places. So, if its okay to have an AV at this |
7778 | // time, then skip the check for whether or not the AV is in our impl. |
7779 | // AVs are ok on the Helper thread (for which there is no pThread object, |
7780 | // and so the AVInRuntime holder doesn't work. |
7781 | Thread *pThread = GetThread(); |
7782 | |
7783 | bool fAVisOk = |
7784 | (IsDbgHelperSpecialThread() || IsETWRundownSpecialThread() || |
7785 | ((pThread != NULL) && (pThread->AVInRuntimeImplOkay())) ); |
7786 | |
7787 | |
7788 | // It is unnecessary to check this on second pass as we would have torn down |
7789 | // the process on the first pass. Also, the context record is not reliable |
7790 | // on second pass and this subjects us to false positives. |
7791 | if ((!fAVisOk) && !(pExceptionRecord->ExceptionFlags & EXCEPTION_UNWINDING)) |
7792 | { |
7793 | PCODE ip = (PCODE)GetIP(pContext); |
7794 | if (IsIPInModule(g_pMSCorEE, ip) || IsIPInModule(GCHeapUtilities::GetGCModule(), ip)) |
7795 | { |
7796 | CONTRACT_VIOLATION(ThrowsViolation|FaultViolation|SOToleranceViolation); |
7797 | |
7798 | // |
7799 | // If you're debugging, set the debugger to catch first-chance AV's, then simply hit F5 or |
7800 | // 'go' and continue after the assert. We'll recgonize that a debugger is attached, and |
7801 | // return EXCEPTION_CONTINUE_EXECUTION. You'll re-execute the faulting instruction, and the |
7802 | // debugger will stop at the AV. The value of EXCEPTION_CONTINUE_EXECUTION is -1, just in |
7803 | // case you need to verify the return value for some reason. If you need to actually debug |
7804 | // the failure path, then set your IP around the check below. |
7805 | // |
7806 | // You can also use Windbg's .cxr command to set the context to pContext. |
7807 | // |
7808 | #if defined(_DEBUG) |
7809 | const char * pStack = "<stack not available>" ; |
7810 | StackScratchBuffer buffer; |
7811 | SString sStack; |
7812 | if (GetStackTraceAtContext(sStack, pContext)) |
7813 | { |
7814 | pStack = sStack.GetANSI(buffer); |
7815 | } |
7816 | |
7817 | DWORD tid = GetCurrentThreadId(); |
7818 | |
7819 | BOOL debuggerPresentBeforeAssert = IsDebuggerPresent(); |
7820 | |
7821 | |
7822 | CONSISTENCY_CHECK_MSGF(false, ("AV in clr at this callstack:\n------\n%s\n-----\n.AV on tid=0x%x (%d), cxr=%p, exr=%p\n" , |
7823 | pStack, tid, tid, pContext, pExceptionRecord)); |
7824 | |
7825 | // @todo - this may not be what we want for interop-debugging... |
7826 | // |
7827 | // If there was no debugger before the assert, but there is one now, then go ahead and |
7828 | // return EXCEPTION_CONTINUE_EXECUTION to re-execute the faulting instruction. This is |
7829 | // supposed to be a nice little feature for CLR devs who attach debuggers on the "Av in |
7830 | // mscorwks" assert above. Since this is only for that case, its only in debug builds. |
7831 | if (!debuggerPresentBeforeAssert && IsDebuggerPresent()) |
7832 | { |
7833 | return VEH_CONTINUE_EXECUTION;; |
7834 | } |
7835 | #endif // defined(_DEBUG) |
7836 | |
7837 | EEPOLICY_HANDLE_FATAL_ERROR_USING_EXCEPTION_INFO(COR_E_EXECUTIONENGINE, pExceptionInfo); |
7838 | } |
7839 | } |
7840 | } |
7841 | } |
7842 | else if (exceptionCode == BOOTUP_EXCEPTION_COMPLUS) |
7843 | { |
7844 | // Don't handle a boot exception |
7845 | return VEH_CONTINUE_SEARCH; |
7846 | } |
7847 | |
7848 | return VEH_NO_ACTION; |
7849 | } |
7850 | |
7851 | #endif // !FEATURE_PAL |
7852 | |
7853 | BOOL IsIPInEE(void *ip) |
7854 | { |
7855 | WRAPPER_NO_CONTRACT; |
7856 | |
7857 | #if defined(FEATURE_PREJIT) && !defined(FEATURE_PAL) |
7858 | if ((TADDR)ip > g_runtimeLoadedBaseAddress && |
7859 | (TADDR)ip < g_runtimeLoadedBaseAddress + g_runtimeVirtualSize) |
7860 | { |
7861 | return TRUE; |
7862 | } |
7863 | else |
7864 | #endif // FEATURE_PREJIT && !FEATURE_PAL |
7865 | { |
7866 | return FALSE; |
7867 | } |
7868 | } |
7869 | |
7870 | #if defined(FEATURE_HIJACK) && (!defined(_TARGET_X86_) || defined(FEATURE_PAL)) |
7871 | |
7872 | // This function is used to check if the specified IP is in the prolog or not. |
7873 | bool IsIPInProlog(EECodeInfo *pCodeInfo) |
7874 | { |
7875 | CONTRACTL |
7876 | { |
7877 | NOTHROW; |
7878 | GC_NOTRIGGER; |
7879 | MODE_ANY; |
7880 | } |
7881 | CONTRACTL_END; |
7882 | |
7883 | bool fInsideProlog = true; |
7884 | |
7885 | _ASSERTE(pCodeInfo->IsValid()); |
7886 | |
7887 | #ifdef _TARGET_AMD64_ |
7888 | |
7889 | // Optimized version for AMD64 that doesn't need to go through the GC info decoding |
7890 | PTR_RUNTIME_FUNCTION funcEntry = pCodeInfo->GetFunctionEntry(); |
7891 | |
7892 | // We should always get a function entry for a managed method |
7893 | _ASSERTE(funcEntry != NULL); |
7894 | |
7895 | // Get the unwindInfo from the function entry |
7896 | PUNWIND_INFO pUnwindInfo = (PUNWIND_INFO)(pCodeInfo->GetModuleBase() + funcEntry->UnwindData); |
7897 | |
7898 | // Check if the specified IP is beyond the prolog or not. |
7899 | DWORD prologLen = pUnwindInfo->SizeOfProlog; |
7900 | |
7901 | #else // _TARGET_AMD64_ |
7902 | |
7903 | GCInfoToken gcInfoToken = pCodeInfo->GetGCInfoToken(); |
7904 | |
7905 | #ifdef USE_GC_INFO_DECODER |
7906 | |
7907 | GcInfoDecoder gcInfoDecoder( |
7908 | gcInfoToken, |
7909 | DECODE_PROLOG_LENGTH |
7910 | ); |
7911 | |
7912 | DWORD prologLen = gcInfoDecoder.GetPrologSize(); |
7913 | |
7914 | #else // USE_GC_INFO_DECODER |
7915 | |
7916 | size_t prologLen; |
7917 | pCodeInfo->GetCodeManager()->IsInPrologOrEpilog(0, gcInfoToken, &prologLen); |
7918 | |
7919 | #endif // USE_GC_INFO_DECODER |
7920 | |
7921 | #endif // _TARGET_AMD64_ |
7922 | |
7923 | if (pCodeInfo->GetRelOffset() >= prologLen) |
7924 | { |
7925 | fInsideProlog = false; |
7926 | } |
7927 | |
7928 | return fInsideProlog; |
7929 | } |
7930 | |
7931 | // This function is used to check if the specified IP is in the epilog or not. |
7932 | bool IsIPInEpilog(PTR_CONTEXT pContextToCheck, EECodeInfo *pCodeInfo, BOOL *pSafeToInjectThreadAbort) |
7933 | { |
7934 | CONTRACTL |
7935 | { |
7936 | NOTHROW; |
7937 | GC_NOTRIGGER; |
7938 | MODE_ANY; |
7939 | PRECONDITION(pContextToCheck != NULL); |
7940 | PRECONDITION(ExecutionManager::IsManagedCode(GetIP(pContextToCheck))); |
7941 | PRECONDITION(pSafeToInjectThreadAbort != NULL); |
7942 | } |
7943 | CONTRACTL_END; |
7944 | |
7945 | TADDR ipToCheck = GetIP(pContextToCheck); |
7946 | |
7947 | _ASSERTE(pCodeInfo->IsValid()); |
7948 | |
7949 | // The Codeinfo should correspond to the IP we are interested in. |
7950 | _ASSERTE(PCODEToPINSTR(ipToCheck) == pCodeInfo->GetCodeAddress()); |
7951 | |
7952 | // By default, assume its safe to inject the abort. |
7953 | *pSafeToInjectThreadAbort = TRUE; |
7954 | |
7955 | // If we are inside a prolog, then we are obviously not in the epilog. |
7956 | // Its safe to inject the abort here. |
7957 | if (IsIPInProlog(pCodeInfo)) |
7958 | { |
7959 | return false; |
7960 | } |
7961 | |
7962 | // We are not inside the prolog. We could either be in the middle of the method body or |
7963 | // inside the epilog. While unwindInfo contains the prolog length, it does not contain the |
7964 | // epilog length. |
7965 | // |
7966 | // Thus, to determine if we are inside the epilog, we use a property of RtlVirtualUnwind. |
7967 | // When invoked for an IP, it will return a NULL for personality routine in only two scenarios: |
7968 | // |
7969 | // 1) The unwindInfo does not contain any personality routine information, OR |
7970 | // 2) The IP is in prolog or epilog. |
7971 | // |
7972 | // For jitted code, (1) is not applicable since we *always* emit details of the managed personality routine |
7973 | // in the unwindInfo. Thus, since we have already determined that we are not inside the prolog, if performing |
7974 | // RtlVirtualUnwind against "ipToCheck" results in a NULL personality routine, it implies that we are inside |
7975 | // the epilog. |
7976 | |
7977 | DWORD_PTR imageBase = 0; |
7978 | CONTEXT tempContext; |
7979 | PVOID HandlerData; |
7980 | DWORD_PTR establisherFrame = 0; |
7981 | PEXCEPTION_ROUTINE personalityRoutine = NULL; |
7982 | |
7983 | // Lookup the function entry for the IP |
7984 | PTR_RUNTIME_FUNCTION funcEntry = pCodeInfo->GetFunctionEntry(); |
7985 | |
7986 | // We should always get a function entry for a managed method |
7987 | _ASSERTE(funcEntry != NULL); |
7988 | |
7989 | imageBase = pCodeInfo->GetModuleBase(); |
7990 | |
7991 | ZeroMemory(&tempContext, sizeof(CONTEXT)); |
7992 | CopyOSContext(&tempContext, pContextToCheck); |
7993 | KNONVOLATILE_CONTEXT_POINTERS ctxPtrs; |
7994 | ZeroMemory(&ctxPtrs, sizeof(ctxPtrs)); |
7995 | |
7996 | personalityRoutine = RtlVirtualUnwind(UNW_FLAG_EHANDLER, // HandlerType |
7997 | imageBase, |
7998 | ipToCheck, |
7999 | funcEntry, |
8000 | &tempContext, |
8001 | &HandlerData, |
8002 | &establisherFrame, |
8003 | &ctxPtrs); |
8004 | |
8005 | bool fIsInEpilog = false; |
8006 | |
8007 | if (personalityRoutine == NULL) |
8008 | { |
8009 | // We are in epilog. |
8010 | fIsInEpilog = true; |
8011 | |
8012 | #ifdef _TARGET_AMD64_ |
8013 | // Check if context pointers has returned the address of the stack location in the hijacked function |
8014 | // from where RBP was restored. If the address is NULL, then it implies that RBP has been popped off. |
8015 | // Since JIT64 ensures that pop of RBP is the last instruction before ret/jmp, it implies its not safe |
8016 | // to inject an abort @ this point as EstablisherFrame (which will be based |
8017 | // of RBP for managed code since that is the FramePointer register, as indicated in the UnwindInfo) |
8018 | // will be off and can result in bad managed exception dispatch. |
8019 | if (ctxPtrs.Rbp == NULL) |
8020 | #endif |
8021 | { |
8022 | *pSafeToInjectThreadAbort = FALSE; |
8023 | } |
8024 | } |
8025 | |
8026 | return fIsInEpilog; |
8027 | } |
8028 | |
8029 | #endif // FEATURE_HIJACK && (!_TARGET_X86_ || FEATURE_PAL) |
8030 | |
8031 | #define EXCEPTION_VISUALCPP_DEBUGGER ((DWORD) (1<<30 | 0x6D<<16 | 5000)) |
8032 | |
8033 | #if defined(_TARGET_X86_) |
8034 | |
8035 | // This holder is used to capture the FPU state, reset it to what the CLR expects |
8036 | // and then restore the original state that was captured. |
8037 | // |
8038 | // FPU has a set of exception masks which the CLR expects to be always set, |
8039 | // implying that any corresponding condition will *not* result in FPU raising |
8040 | // an exception. |
8041 | // |
8042 | // However, native code (e.g. high precision math libs) can change this mask. |
8043 | // Thus, when control enters the CLR (e.g. via exception dispatch into the VEH), |
8044 | // we will end up using floating point instructions that could satify the exception mask |
8045 | // condition and raise an exception. This could result in an infinite loop, resulting in |
8046 | // SO. |
8047 | // |
8048 | // We use this holder to protect applicable parts of the runtime from running into such cases. |
8049 | extern "C" void CaptureFPUContext(BYTE *pFPBUBuf); |
8050 | extern "C" void RestoreFPUContext(BYTE *pFPBUBuf); |
8051 | |
8052 | // This is FPU specific and only applicable to x86 on Windows. |
8053 | class FPUStateHolder |
8054 | { |
8055 | // Capturing FPU state requires a 28byte buffer |
8056 | BYTE m_bufFPUState[28]; |
8057 | |
8058 | public: |
8059 | FPUStateHolder() |
8060 | { |
8061 | LIMITED_METHOD_CONTRACT; |
8062 | |
8063 | BYTE *pFPUBuf = m_bufFPUState; |
8064 | |
8065 | // Save the FPU state using the non-waiting instruction |
8066 | // so that FPU may not raise an exception incase the |
8067 | // exception masks are unset in the FPU Control Word |
8068 | CaptureFPUContext(pFPUBuf); |
8069 | |
8070 | // Reset the FPU state |
8071 | ResetCurrentContext(); |
8072 | } |
8073 | |
8074 | ~FPUStateHolder() |
8075 | { |
8076 | LIMITED_METHOD_CONTRACT; |
8077 | |
8078 | BYTE *pFPUBuf = m_bufFPUState; |
8079 | |
8080 | // Restore the capture FPU state |
8081 | RestoreFPUContext(pFPUBuf); |
8082 | } |
8083 | }; |
8084 | |
8085 | #endif // defined(_TARGET_X86_) |
8086 | |
8087 | #ifndef FEATURE_PAL |
8088 | |
8089 | LONG WINAPI CLRVectoredExceptionHandlerShim(PEXCEPTION_POINTERS pExceptionInfo) |
8090 | { |
8091 | // |
8092 | // HandleManagedFault will take a Crst that causes an unbalanced |
8093 | // notrigger scope, and this contract will whack the thread's |
8094 | // ClrDebugState to what it was on entry in the dtor, which causes |
8095 | // us to assert when we finally release the Crst later on. |
8096 | // |
8097 | // CONTRACTL |
8098 | // { |
8099 | // NOTHROW; |
8100 | // GC_NOTRIGGER; |
8101 | // MODE_ANY; |
8102 | // } |
8103 | // CONTRACTL_END; |
8104 | |
8105 | // |
8106 | // WARNING WARNING WARNING WARNING WARNING WARNING WARNING |
8107 | // |
8108 | // o This function should not call functions that acquire |
8109 | // synchronization objects or allocate memory, because this |
8110 | // can cause problems. <-- quoteth MSDN -- probably for |
8111 | // the same reason as we cannot use LOG(); we'll recurse |
8112 | // into a stack overflow. |
8113 | // |
8114 | // o You cannot use LOG() in here because that will trigger an |
8115 | // exception which will cause infinite recursion with this |
8116 | // function. We work around this by ignoring all non-error |
8117 | // exception codes, which serves as the base of the recursion. |
8118 | // That way, we can LOG() from the rest of the function |
8119 | // |
8120 | // The same goes for any function called by this |
8121 | // function. |
8122 | // |
8123 | // WARNING WARNING WARNING WARNING WARNING WARNING WARNING |
8124 | // |
8125 | |
8126 | // If exceptions (or runtime) have been disabled, then simply return. |
8127 | if (g_fForbidEnterEE || g_fNoExceptions) |
8128 | { |
8129 | return EXCEPTION_CONTINUE_SEARCH; |
8130 | } |
8131 | |
8132 | // WARNING |
8133 | // |
8134 | // We must preserve this so that GCStress=4 eh processing doesnt kill last error. |
8135 | // Note that even GetThread below can affect the LastError. |
8136 | // Keep this in mind when adding code above this line! |
8137 | // |
8138 | // WARNING |
8139 | DWORD dwLastError = GetLastError(); |
8140 | |
8141 | #if defined(_TARGET_X86_) |
8142 | // Capture the FPU state before we do anything involving floating point instructions |
8143 | FPUStateHolder captureFPUState; |
8144 | #endif // defined(_TARGET_X86_) |
8145 | |
8146 | #ifdef FEATURE_INTEROP_DEBUGGING |
8147 | // For interop debugging we have a fancy exception queueing stunt. When the debugger |
8148 | // initially gets the first chance exception notification it may not know whether to |
8149 | // continue it handled or unhandled, but it must continue the process to allow the |
8150 | // in-proc helper thread to work. What it does is continue the exception unhandled which |
8151 | // will let the thread immediately execute to this point. Inside this worker the thread |
8152 | // will block until the debugger knows how to continue the exception. If it decides the |
8153 | // exception was handled then we immediately resume execution as if the exeption had never |
8154 | // even been allowed to run into this handler. If it is unhandled then we keep processing |
8155 | // this handler |
8156 | // |
8157 | // WARNING: This function could potentially throw an exception, however it should only |
8158 | // be able to do so when an interop debugger is attached |
8159 | if(g_pDebugInterface != NULL) |
8160 | { |
8161 | if(g_pDebugInterface->FirstChanceSuspendHijackWorker(pExceptionInfo->ContextRecord, |
8162 | pExceptionInfo->ExceptionRecord) == EXCEPTION_CONTINUE_EXECUTION) |
8163 | return EXCEPTION_CONTINUE_EXECUTION; |
8164 | } |
8165 | #endif |
8166 | |
8167 | |
8168 | DWORD dwCode = pExceptionInfo->ExceptionRecord->ExceptionCode; |
8169 | if (dwCode == DBG_PRINTEXCEPTION_C || dwCode == EXCEPTION_VISUALCPP_DEBUGGER) |
8170 | { |
8171 | return EXCEPTION_CONTINUE_SEARCH; |
8172 | } |
8173 | |
8174 | #if defined(_TARGET_X86_) |
8175 | if (dwCode == EXCEPTION_BREAKPOINT || dwCode == EXCEPTION_SINGLE_STEP) |
8176 | { |
8177 | // For interop debugging, debugger bashes our managed exception handler. |
8178 | // Interop debugging does not work with real vectored exception handler :( |
8179 | return EXCEPTION_CONTINUE_SEARCH; |
8180 | } |
8181 | #endif |
8182 | |
8183 | bool bIsGCMarker = false; |
8184 | |
8185 | #ifdef USE_REDIRECT_FOR_GCSTRESS |
8186 | // This is AMD64 & ARM specific as the macro above is defined for AMD64 & ARM only |
8187 | bIsGCMarker = IsGcMarker(pExceptionInfo->ContextRecord, pExceptionInfo->ExceptionRecord); |
8188 | #elif defined(_TARGET_X86_) && defined(HAVE_GCCOVER) |
8189 | // This is the equivalent of the check done in COMPlusFrameHandler, incase the exception is |
8190 | // seen by VEH first on x86. |
8191 | bIsGCMarker = IsGcMarker(pExceptionInfo->ContextRecord, pExceptionInfo->ExceptionRecord); |
8192 | #endif // USE_REDIRECT_FOR_GCSTRESS |
8193 | |
8194 | // Do not update the TLS with exception details for exceptions pertaining to GCStress |
8195 | // as they are continueable in nature. |
8196 | if (!bIsGCMarker) |
8197 | { |
8198 | SaveCurrentExceptionInfo(pExceptionInfo->ExceptionRecord, pExceptionInfo->ContextRecord); |
8199 | } |
8200 | |
8201 | |
8202 | LONG result = EXCEPTION_CONTINUE_SEARCH; |
8203 | |
8204 | // If we cannot obtain a Thread object, then we have no business processing any |
8205 | // exceptions on this thread. Indeed, even checking to see if the faulting |
8206 | // address is in JITted code is problematic if we have no Thread object, since |
8207 | // this thread will bypass all our locks. |
8208 | Thread *pThread = GetThread(); |
8209 | |
8210 | if (pThread) |
8211 | { |
8212 | // Fiber-friendly Vectored Exception Handling: |
8213 | // Check if the current and the cached stack-base match. |
8214 | // If they don't match then probably the thread is running on a different Fiber |
8215 | // than during the initialization of the Thread-object. |
8216 | void* stopPoint = pThread->GetCachedStackBase(); |
8217 | void* currentStackBase = Thread::GetStackUpperBound(); |
8218 | if (currentStackBase != stopPoint) |
8219 | { |
8220 | CantAllocHolder caHolder; |
8221 | STRESS_LOG2(LF_EH, LL_INFO100, "In CLRVectoredExceptionHandler: mismatch of cached and current stack-base indicating use of Fibers, return with EXCEPTION_CONTINUE_SEARCH: current = %p; cache = %p\n" , |
8222 | currentStackBase, stopPoint); |
8223 | return EXCEPTION_CONTINUE_SEARCH; |
8224 | } |
8225 | } |
8226 | |
8227 | |
8228 | // Also check if the exception was in the EE or not |
8229 | BOOL fExceptionInEE = FALSE; |
8230 | if (!pThread) |
8231 | { |
8232 | // Check if the exception was in EE only if Thread object isnt available. |
8233 | // This will save us from unnecessary checks |
8234 | fExceptionInEE = IsIPInEE(pExceptionInfo->ExceptionRecord->ExceptionAddress); |
8235 | } |
8236 | |
8237 | // We are going to process the exception only if one of the following conditions is true: |
8238 | // |
8239 | // 1) We have a valid Thread object (implies exception on managed thread) |
8240 | // 2) Not a valid Thread object but the IP is in the execution engine (implies native thread within EE faulted) |
8241 | if (pThread || fExceptionInEE) |
8242 | { |
8243 | if (!bIsGCMarker) |
8244 | result = CLRVectoredExceptionHandler(pExceptionInfo); |
8245 | else |
8246 | result = EXCEPTION_CONTINUE_EXECUTION; |
8247 | |
8248 | if (EXCEPTION_EXECUTE_HANDLER == result) |
8249 | { |
8250 | result = EXCEPTION_CONTINUE_SEARCH; |
8251 | } |
8252 | |
8253 | #ifdef _DEBUG |
8254 | #ifndef WIN64EXCEPTIONS |
8255 | { |
8256 | CantAllocHolder caHolder; |
8257 | |
8258 | PEXCEPTION_REGISTRATION_RECORD pRecord = GetCurrentSEHRecord(); |
8259 | while (pRecord != EXCEPTION_CHAIN_END) |
8260 | { |
8261 | STRESS_LOG2(LF_EH, LL_INFO10000, "CLRVectoredExceptionHandlerShim: FS:0 %p:%p\n" , |
8262 | pRecord, pRecord->Handler); |
8263 | pRecord = pRecord->Next; |
8264 | } |
8265 | } |
8266 | #endif // WIN64EXCEPTIONS |
8267 | |
8268 | { |
8269 | // The call to "CLRVectoredExceptionHandler" above can return EXCEPTION_CONTINUE_SEARCH |
8270 | // for different scenarios like StackOverFlow/SOFT_SO, or if it is forbidden to enter the EE. |
8271 | // Thus, if we dont have a Thread object for the thread that has faulted and we came this far |
8272 | // because the fault was in MSCORWKS, then we work with the frame chain below only if we have |
8273 | // valid Thread object. |
8274 | |
8275 | if (pThread) |
8276 | { |
8277 | CantAllocHolder caHolder; |
8278 | |
8279 | TADDR* sp; |
8280 | sp = (TADDR*)&sp; |
8281 | DWORD count = 0; |
8282 | void* stopPoint = pThread->GetCachedStackBase(); |
8283 | // If Frame chain is corrupted, we may get AV while accessing frames, and this function will be |
8284 | // called recursively. We use Frame chain to limit our search range. It is not disaster if we |
8285 | // can not use it. |
8286 | if (!(dwCode == STATUS_ACCESS_VIOLATION && |
8287 | IsIPInEE(pExceptionInfo->ExceptionRecord->ExceptionAddress))) |
8288 | { |
8289 | // Find the stop point (most jitted function) |
8290 | Frame* pFrame = pThread->GetFrame(); |
8291 | for(;;) |
8292 | { |
8293 | // skip GC frames |
8294 | if (pFrame == 0 || pFrame == (Frame*) -1) |
8295 | break; |
8296 | |
8297 | Frame::ETransitionType type = pFrame->GetTransitionType(); |
8298 | if (type == Frame::TT_M2U || type == Frame::TT_InternalCall) |
8299 | { |
8300 | stopPoint = pFrame; |
8301 | break; |
8302 | } |
8303 | pFrame = pFrame->Next(); |
8304 | } |
8305 | } |
8306 | STRESS_LOG0(LF_EH, LL_INFO100, "CLRVectoredExceptionHandlerShim: stack" ); |
8307 | while (count < 20 && sp < stopPoint) |
8308 | { |
8309 | if (IsIPInEE((BYTE*)*sp)) |
8310 | { |
8311 | STRESS_LOG1(LF_EH, LL_INFO100, "%pK\n" , *sp); |
8312 | count ++; |
8313 | } |
8314 | sp += 1; |
8315 | } |
8316 | } |
8317 | } |
8318 | #endif // _DEBUG |
8319 | |
8320 | #ifndef WIN64EXCEPTIONS |
8321 | { |
8322 | CantAllocHolder caHolder; |
8323 | STRESS_LOG1(LF_EH, LL_INFO1000, "CLRVectoredExceptionHandlerShim: returning %d\n" , result); |
8324 | } |
8325 | #endif // WIN64EXCEPTIONS |
8326 | |
8327 | } |
8328 | |
8329 | SetLastError(dwLastError); |
8330 | |
8331 | return result; |
8332 | } |
8333 | |
8334 | #endif // !FEATURE_PAL |
8335 | |
8336 | // Contains the handle to the registered VEH |
8337 | static PVOID g_hVectoredExceptionHandler = NULL; |
8338 | |
8339 | void CLRAddVectoredHandlers(void) |
8340 | { |
8341 | #ifndef FEATURE_PAL |
8342 | |
8343 | // We now install a vectored exception handler on all supporting Windows architectures. |
8344 | g_hVectoredExceptionHandler = AddVectoredExceptionHandler(TRUE, (PVECTORED_EXCEPTION_HANDLER)CLRVectoredExceptionHandlerShim); |
8345 | if (g_hVectoredExceptionHandler == NULL) |
8346 | { |
8347 | LOG((LF_EH, LL_INFO100, "CLRAddVectoredHandlers: AddVectoredExceptionHandler() failed\n" )); |
8348 | COMPlusThrowHR(E_FAIL); |
8349 | } |
8350 | |
8351 | LOG((LF_EH, LL_INFO100, "CLRAddVectoredHandlers: AddVectoredExceptionHandler() succeeded\n" )); |
8352 | #endif // !FEATURE_PAL |
8353 | } |
8354 | |
8355 | // This function removes the vectored exception and continue handler registration |
8356 | // from the OS. |
8357 | void CLRRemoveVectoredHandlers(void) |
8358 | { |
8359 | CONTRACTL |
8360 | { |
8361 | NOTHROW; |
8362 | GC_NOTRIGGER; |
8363 | MODE_ANY; |
8364 | } |
8365 | CONTRACTL_END; |
8366 | #ifndef FEATURE_PAL |
8367 | |
8368 | // Unregister the vectored exception handler if one is registered (and we can). |
8369 | if (g_hVectoredExceptionHandler != NULL) |
8370 | { |
8371 | // Unregister the vectored exception handler |
8372 | if (RemoveVectoredExceptionHandler(g_hVectoredExceptionHandler) == FALSE) |
8373 | { |
8374 | LOG((LF_EH, LL_INFO100, "CLRRemoveVectoredHandlers: RemoveVectoredExceptionHandler() failed.\n" )); |
8375 | } |
8376 | else |
8377 | { |
8378 | LOG((LF_EH, LL_INFO100, "CLRRemoveVectoredHandlers: RemoveVectoredExceptionHandler() succeeded.\n" )); |
8379 | } |
8380 | } |
8381 | #endif // !FEATURE_PAL |
8382 | } |
8383 | |
8384 | // |
8385 | // This does the work of the Unwind and Continue Hanlder inside the catch clause of that handler. The stack has not |
8386 | // been unwound when this is called. Keep that in mind when deciding where to put new code :) |
8387 | // |
8388 | void UnwindAndContinueRethrowHelperInsideCatch(Frame* pEntryFrame, Exception* pException) |
8389 | { |
8390 | STATIC_CONTRACT_NOTHROW; |
8391 | STATIC_CONTRACT_GC_TRIGGERS; |
8392 | STATIC_CONTRACT_MODE_ANY; |
8393 | STATIC_CONTRACT_SO_TOLERANT; |
8394 | |
8395 | Thread* pThread = GetThread(); |
8396 | |
8397 | GCX_COOP(); |
8398 | |
8399 | LOG((LF_EH, LL_INFO1000, "UNWIND_AND_CONTINUE inside catch, unwinding frame chain\n" )); |
8400 | |
8401 | // This SetFrame is OK because we will not have frames that require ExceptionUnwind in strictly unmanaged EE |
8402 | // code chunks which is all that an UnC handler can guard. |
8403 | // |
8404 | // @todo: we'd rather use UnwindFrameChain, but there is a concern: some of the ExceptionUnwind methods on some |
8405 | // of the Frame types do a great deal of work; load classes, throw exceptions, etc. We need to decide on some |
8406 | // policy here. Do we want to let such funcitons throw, etc.? Right now, we believe that there are no such |
8407 | // frames on the stack to be unwound, so the SetFrame is alright (see the first comment above.) At the very |
8408 | // least, we should add some way to assert that. |
8409 | pThread->SetFrame(pEntryFrame); |
8410 | |
8411 | #ifdef _DEBUG |
8412 | if (!NingenEnabled()) |
8413 | { |
8414 | CONTRACT_VIOLATION(ThrowsViolation); |
8415 | BEGIN_SO_INTOLERANT_CODE(pThread); |
8416 | // Call CLRException::GetThrowableFromException to force us to retrieve the THROWABLE |
8417 | // while we are still within the context of the catch block. This will help diagnose |
8418 | // cases where the last thrown object is NULL. |
8419 | OBJECTREF orThrowable = CLRException::GetThrowableFromException(pException); |
8420 | CONSISTENCY_CHECK(orThrowable != NULL); |
8421 | END_SO_INTOLERANT_CODE; |
8422 | } |
8423 | #endif |
8424 | } |
8425 | |
8426 | // |
8427 | // This does the work of the Unwind and Continue Hanlder after the catch clause of that handler. The stack has been |
8428 | // unwound by the time this is called. Keep that in mind when deciding where to put new code :) |
8429 | // |
8430 | VOID DECLSPEC_NORETURN UnwindAndContinueRethrowHelperAfterCatch(Frame* pEntryFrame, Exception* pException) |
8431 | { |
8432 | STATIC_CONTRACT_THROWS; |
8433 | STATIC_CONTRACT_GC_TRIGGERS; |
8434 | STATIC_CONTRACT_MODE_ANY; |
8435 | STATIC_CONTRACT_SO_TOLERANT; |
8436 | |
8437 | // We really should probe before switching to cooperative mode, although there's no chance |
8438 | // we'll SO in doing that as we've just caught an exception. We can't probe just |
8439 | // yet though, because we want to avoid reprobing on an SO exception and we need to switch |
8440 | // to cooperative to check the throwable for an SO as well as the pException object (as the |
8441 | // pException could be a LastThrownObjectException.) Blech. |
8442 | CONTRACT_VIOLATION(SOToleranceViolation); |
8443 | |
8444 | GCX_COOP(); |
8445 | |
8446 | LOG((LF_EH, LL_INFO1000, "UNWIND_AND_CONTINUE caught and will rethrow\n" )); |
8447 | |
8448 | OBJECTREF orThrowable = NingenEnabled() ? NULL : CLRException::GetThrowableFromException(pException); |
8449 | LOG((LF_EH, LL_INFO1000, "UNWIND_AND_CONTINUE got throwable %p\n" , |
8450 | OBJECTREFToObject(orThrowable))); |
8451 | |
8452 | Exception::Delete(pException); |
8453 | |
8454 | if (orThrowable != NULL && g_CLRPolicyRequested) |
8455 | { |
8456 | if (orThrowable->GetMethodTable() == g_pOutOfMemoryExceptionClass) |
8457 | { |
8458 | EEPolicy::HandleOutOfMemory(); |
8459 | } |
8460 | else if (orThrowable->GetMethodTable() == g_pStackOverflowExceptionClass) |
8461 | { |
8462 | #ifdef FEATURE_STACK_PROBE |
8463 | EEPolicy::HandleSoftStackOverflow(); |
8464 | #else |
8465 | /* The parameters of the function do not matter here */ |
8466 | EEPolicy::HandleStackOverflow(SOD_UnmanagedFrameHandler, NULL); |
8467 | #endif |
8468 | } |
8469 | } |
8470 | |
8471 | RaiseTheExceptionInternalOnly(orThrowable, FALSE); |
8472 | } |
8473 | |
8474 | void SaveCurrentExceptionInfo(PEXCEPTION_RECORD pRecord, PCONTEXT pContext) |
8475 | { |
8476 | CONTRACTL |
8477 | { |
8478 | NOTHROW; |
8479 | GC_NOTRIGGER; |
8480 | MODE_ANY; |
8481 | SO_TOLERANT; |
8482 | } |
8483 | CONTRACTL_END; |
8484 | |
8485 | if ((pRecord->ExceptionFlags & (EXCEPTION_UNWINDING | EXCEPTION_EXIT_UNWIND))) |
8486 | { |
8487 | // If exception is unwinding the stack, the ExceptionCode may have been changed to |
8488 | // STATUS_UNWIND if RtlUnwind is called with a NULL ExceptionRecord. |
8489 | // Since we have captured exception info in the first pass, we don't need to capture it again. |
8490 | return; |
8491 | } |
8492 | |
8493 | if (CExecutionEngine::CheckThreadStateNoCreate(TlsIdx_PEXCEPTION_RECORD)) |
8494 | { |
8495 | BOOL fSave = TRUE; |
8496 | if (!IsSOExceptionCode(pRecord->ExceptionCode)) |
8497 | { |
8498 | DWORD dwLastExceptionCode = (DWORD)(SIZE_T) (ClrFlsGetValue(TlsIdx_EXCEPTION_CODE)); |
8499 | if (IsSOExceptionCode(dwLastExceptionCode)) |
8500 | { |
8501 | PEXCEPTION_RECORD lastRecord = |
8502 | static_cast<PEXCEPTION_RECORD> (ClrFlsGetValue(TlsIdx_PEXCEPTION_RECORD)); |
8503 | |
8504 | // We are trying to see if C++ is attempting a rethrow of a SO exception. If so, |
8505 | // we want to prevent updating the exception details in the TLS. This is a workaround, |
8506 | // as explained below. |
8507 | if (pRecord->ExceptionCode == EXCEPTION_MSVC) |
8508 | { |
8509 | // This is a workaround. |
8510 | // When C++ rethrows, C++ internally gets rid of the new exception record after |
8511 | // unwinding stack, and present the original exception record to the thread. |
8512 | // When we get VC's support to obtain exception record in try/catch, we will replace |
8513 | // this code. |
8514 | if (pRecord < lastRecord) |
8515 | { |
8516 | // For the C++ rethrow workaround, ensure that the last exception record is still valid and as we expect it to be. |
8517 | // |
8518 | // Its possible that we are still below the address of last exception record |
8519 | // but since the execution stack could have changed, simply comparing its address |
8520 | // with the address of the current exception record may not be enough. |
8521 | // |
8522 | // Thus, ensure that its still valid and holds the exception code we expect it to |
8523 | // have (i.e. value in dwLastExceptionCode). |
8524 | if ((lastRecord != NULL) && (lastRecord->ExceptionCode == dwLastExceptionCode)) |
8525 | { |
8526 | fSave = FALSE; |
8527 | } |
8528 | } |
8529 | } |
8530 | } |
8531 | } |
8532 | if (fSave) |
8533 | { |
8534 | ClrFlsSetValue(TlsIdx_EXCEPTION_CODE, (void*)(size_t)(pRecord->ExceptionCode)); |
8535 | ClrFlsSetValue(TlsIdx_PEXCEPTION_RECORD, pRecord); |
8536 | ClrFlsSetValue(TlsIdx_PCONTEXT, pContext); |
8537 | } |
8538 | } |
8539 | } |
8540 | |
8541 | #ifndef DACCESS_COMPILE |
8542 | //****************************************************************************** |
8543 | // |
8544 | // NotifyOfCHFFilterWrapper |
8545 | // |
8546 | // Helper function to deliver notifications of CatchHandlerFound inside a |
8547 | // EX_TRY/EX_CATCH. |
8548 | // |
8549 | // Parameters: |
8550 | // pExceptionInfo - the pExceptionInfo passed to a filter function. |
8551 | // pCatcherStackAddr - a Frame* from the PAL_TRY/PAL_EXCEPT_FILTER site. |
8552 | // |
8553 | // Return: |
8554 | // always returns EXCEPTION_CONTINUE_SEARCH. |
8555 | // |
8556 | //****************************************************************************** |
8557 | LONG NotifyOfCHFFilterWrapper( |
8558 | EXCEPTION_POINTERS *pExceptionInfo, // the pExceptionInfo passed to a filter function. |
8559 | PVOID pParam) // contains a Frame* from the PAL_TRY/PAL_EXCEPT_FILTER site. |
8560 | { |
8561 | LIMITED_METHOD_CONTRACT; |
8562 | |
8563 | PVOID pCatcherStackAddr = ((NotifyOfCHFFilterWrapperParam *)pParam)->pFrame; |
8564 | ULONG ret = EXCEPTION_CONTINUE_SEARCH; |
8565 | |
8566 | // We are here to send an event notification to the debugger and to the appdomain. To |
8567 | // determine if it is safe to send these notifications, check the following: |
8568 | // 1) The thread object has been set up. |
8569 | // 2) The thread has an exception on it. |
8570 | // 3) The exception is the same as the one this filter is called on. |
8571 | Thread *pThread = GetThread(); |
8572 | if ( (pThread == NULL) || |
8573 | (pThread->GetExceptionState()->GetContextRecord() == NULL) || |
8574 | (GetSP(pThread->GetExceptionState()->GetContextRecord()) != GetSP(pExceptionInfo->ContextRecord) ) ) |
8575 | { |
8576 | LOG((LF_EH, LL_INFO1000, "NotifyOfCHFFilterWrapper: not sending notices. pThread: %0x8" , pThread)); |
8577 | if (pThread) |
8578 | { |
8579 | LOG((LF_EH, LL_INFO1000, ", Thread SP: %0x8, Exception SP: %08x" , |
8580 | pThread->GetExceptionState()->GetContextRecord() ? GetSP(pThread->GetExceptionState()->GetContextRecord()) : NULL, |
8581 | pExceptionInfo->ContextRecord ? GetSP(pExceptionInfo->ContextRecord) : NULL )); |
8582 | } |
8583 | LOG((LF_EH, LL_INFO1000, "\n" )); |
8584 | return ret; |
8585 | } |
8586 | |
8587 | if (g_pDebugInterface) |
8588 | { |
8589 | // It looks safe, so make the debugger notification. |
8590 | ret = g_pDebugInterface->NotifyOfCHFFilter(pExceptionInfo, pCatcherStackAddr); |
8591 | } |
8592 | |
8593 | return ret; |
8594 | } // LONG NotifyOfCHFFilterWrapper() |
8595 | |
8596 | // This filter will be used process exceptions escaping out of AD transition boundaries |
8597 | // that are not at the base of the managed thread. Those are handled in ThreadBaseRedirectingFilter. |
8598 | // This will be invoked when an exception is going unhandled from the called AppDomain. |
8599 | // |
8600 | // This can be used to do last moment work before the exception gets caught by the EX_CATCH setup |
8601 | // at the AD transition point. |
8602 | LONG AppDomainTransitionExceptionFilter( |
8603 | EXCEPTION_POINTERS *pExceptionInfo, // the pExceptionInfo passed to a filter function. |
8604 | PVOID pParam) |
8605 | { |
8606 | // Ideally, we would be NOTHROW here. However, NotifyOfCHFFilterWrapper calls into |
8607 | // NotifyOfCHFFilter that is THROWS. Thus, to prevent contract violation, |
8608 | // we abide by the rules and be THROWS. |
8609 | // |
8610 | // Same rationale for GC_TRIGGERS as well. |
8611 | CONTRACTL |
8612 | { |
8613 | GC_TRIGGERS; |
8614 | MODE_ANY; |
8615 | THROWS; |
8616 | } |
8617 | CONTRACTL_END; |
8618 | |
8619 | ULONG ret = EXCEPTION_CONTINUE_SEARCH; |
8620 | |
8621 | // First, call into NotifyOfCHFFilterWrapper |
8622 | ret = NotifyOfCHFFilterWrapper(pExceptionInfo, pParam); |
8623 | |
8624 | #ifndef FEATURE_PAL |
8625 | // Setup the watson bucketing details if the escaping |
8626 | // exception is preallocated. |
8627 | if (SetupWatsonBucketsForEscapingPreallocatedExceptions()) |
8628 | { |
8629 | // Set the flag that these were captured at AD Transition |
8630 | DEBUG_STMT(GetThread()->GetExceptionState()->GetUEWatsonBucketTracker()->SetCapturedAtADTransition()); |
8631 | } |
8632 | |
8633 | // Attempt to capture buckets for non-preallocated exceptions just before the AppDomain transition boundary |
8634 | { |
8635 | GCX_COOP(); |
8636 | OBJECTREF oThrowable = GetThread()->GetThrowable(); |
8637 | if ((oThrowable != NULL) && (CLRException::IsPreallocatedExceptionObject(oThrowable) == FALSE)) |
8638 | { |
8639 | SetupWatsonBucketsForNonPreallocatedExceptions(); |
8640 | } |
8641 | } |
8642 | #endif // !FEATURE_PAL |
8643 | |
8644 | return ret; |
8645 | } // LONG AppDomainTransitionExceptionFilter() |
8646 | |
8647 | // This filter will be used process exceptions escaping out of dynamic reflection invocation as |
8648 | // unhandled and will eventually be caught in the VM to be made as inner exception of |
8649 | // TargetInvocationException that will be thrown from the VM. |
8650 | LONG ReflectionInvocationExceptionFilter( |
8651 | EXCEPTION_POINTERS *pExceptionInfo, // the pExceptionInfo passed to a filter function. |
8652 | PVOID pParam) |
8653 | { |
8654 | // Ideally, we would be NOTHROW here. However, NotifyOfCHFFilterWrapper calls into |
8655 | // NotifyOfCHFFilter that is THROWS. Thus, to prevent contract violation, |
8656 | // we abide by the rules and be THROWS. |
8657 | // |
8658 | // Same rationale for GC_TRIGGERS as well. |
8659 | CONTRACTL |
8660 | { |
8661 | GC_TRIGGERS; |
8662 | MODE_ANY; |
8663 | THROWS; |
8664 | } |
8665 | CONTRACTL_END; |
8666 | |
8667 | ULONG ret = EXCEPTION_CONTINUE_SEARCH; |
8668 | |
8669 | // First, call into NotifyOfCHFFilterWrapper |
8670 | ret = NotifyOfCHFFilterWrapper(pExceptionInfo, pParam); |
8671 | |
8672 | #ifndef FEATURE_PAL |
8673 | // Setup the watson bucketing details if the escaping |
8674 | // exception is preallocated. |
8675 | if (SetupWatsonBucketsForEscapingPreallocatedExceptions()) |
8676 | { |
8677 | // Set the flag that these were captured during Reflection Invocation |
8678 | DEBUG_STMT(GetThread()->GetExceptionState()->GetUEWatsonBucketTracker()->SetCapturedAtReflectionInvocation()); |
8679 | } |
8680 | |
8681 | // Attempt to capture buckets for non-preallocated exceptions just before the ReflectionInvocation boundary |
8682 | { |
8683 | GCX_COOP(); |
8684 | OBJECTREF oThrowable = GetThread()->GetThrowable(); |
8685 | if ((oThrowable != NULL) && (CLRException::IsPreallocatedExceptionObject(oThrowable) == FALSE)) |
8686 | { |
8687 | SetupWatsonBucketsForNonPreallocatedExceptions(); |
8688 | } |
8689 | } |
8690 | #endif // !FEATURE_PAL |
8691 | |
8692 | // If the application has opted into triggering a failfast when a CorruptedStateException enters the Reflection system, |
8693 | // then do the needful. |
8694 | if (CLRConfig::GetConfigValue(CLRConfig::UNSUPPORTED_FailFastOnCorruptedStateException) == 1) |
8695 | { |
8696 | // Get the thread and the managed exception object - they must exist at this point |
8697 | Thread *pCurThread = GetThread(); |
8698 | _ASSERTE(pCurThread != NULL); |
8699 | |
8700 | // Get the thread exception state |
8701 | ThreadExceptionState * pCurTES = pCurThread->GetExceptionState(); |
8702 | _ASSERTE(pCurTES != NULL); |
8703 | |
8704 | // Get the exception tracker for the current exception |
8705 | #ifdef WIN64EXCEPTIONS |
8706 | PTR_ExceptionTracker pEHTracker = pCurTES->GetCurrentExceptionTracker(); |
8707 | #elif _TARGET_X86_ |
8708 | PTR_ExInfo pEHTracker = pCurTES->GetCurrentExceptionTracker(); |
8709 | #else // !(_WIN64 || _TARGET_X86_) |
8710 | #error Unsupported platform |
8711 | #endif // _WIN64 |
8712 | |
8713 | #ifdef FEATURE_CORRUPTING_EXCEPTIONS |
8714 | if (pEHTracker->GetCorruptionSeverity() == ProcessCorrupting) |
8715 | { |
8716 | EEPolicy::HandleFatalError(COR_E_FAILFAST, reinterpret_cast<UINT_PTR>(pExceptionInfo->ExceptionRecord->ExceptionAddress), NULL, pExceptionInfo); |
8717 | } |
8718 | #endif // FEATURE_CORRUPTING_EXCEPTIONS |
8719 | } |
8720 | |
8721 | return ret; |
8722 | } // LONG ReflectionInvocationExceptionFilter() |
8723 | |
8724 | #endif // !DACCESS_COMPILE |
8725 | |
8726 | #ifdef _DEBUG |
8727 | bool DebugIsEECxxExceptionPointer(void* pv) |
8728 | { |
8729 | CONTRACTL |
8730 | { |
8731 | DISABLED(NOTHROW); |
8732 | GC_NOTRIGGER; |
8733 | MODE_ANY; |
8734 | DEBUG_ONLY; |
8735 | } |
8736 | CONTRACTL_END; |
8737 | |
8738 | if (pv == NULL) |
8739 | { |
8740 | return false; |
8741 | } |
8742 | |
8743 | // check whether the memory is readable in no-throw way |
8744 | if (!isMemoryReadable((TADDR)pv, sizeof(UINT_PTR))) |
8745 | { |
8746 | return false; |
8747 | } |
8748 | |
8749 | bool retVal = false; |
8750 | |
8751 | EX_TRY |
8752 | { |
8753 | UINT_PTR vtbl = *(UINT_PTR*)pv; |
8754 | |
8755 | // ex.h |
8756 | |
8757 | HRException boilerplate1; |
8758 | COMException boilerplate2; |
8759 | SEHException boilerplate3; |
8760 | |
8761 | // clrex.h |
8762 | |
8763 | CLRException boilerplate4; |
8764 | CLRLastThrownObjectException boilerplate5; |
8765 | EEException boilerplate6; |
8766 | EEMessageException boilerplate7; |
8767 | EEResourceException boilerplate8; |
8768 | |
8769 | // EECOMException::~EECOMException calls FreeExceptionData, which is GC_TRIGGERS, |
8770 | // but it won't trigger in this case because EECOMException's members remain NULL. |
8771 | CONTRACT_VIOLATION(GCViolation); |
8772 | EECOMException boilerplate9; |
8773 | |
8774 | EEFieldException boilerplate10; |
8775 | EEMethodException boilerplate11; |
8776 | EEArgumentException boilerplate12; |
8777 | EETypeLoadException boilerplate13; |
8778 | EEFileLoadException boilerplate14; |
8779 | ObjrefException boilerplate15; |
8780 | |
8781 | UINT_PTR ValidVtbls[] = |
8782 | { |
8783 | *((TADDR*)&boilerplate1), |
8784 | *((TADDR*)&boilerplate2), |
8785 | *((TADDR*)&boilerplate3), |
8786 | *((TADDR*)&boilerplate4), |
8787 | *((TADDR*)&boilerplate5), |
8788 | *((TADDR*)&boilerplate6), |
8789 | *((TADDR*)&boilerplate7), |
8790 | *((TADDR*)&boilerplate8), |
8791 | *((TADDR*)&boilerplate9), |
8792 | *((TADDR*)&boilerplate10), |
8793 | *((TADDR*)&boilerplate11), |
8794 | *((TADDR*)&boilerplate12), |
8795 | *((TADDR*)&boilerplate13), |
8796 | *((TADDR*)&boilerplate14), |
8797 | *((TADDR*)&boilerplate15) |
8798 | }; |
8799 | |
8800 | const int nVtbls = sizeof(ValidVtbls) / sizeof(ValidVtbls[0]); |
8801 | |
8802 | for (int i = 0; i < nVtbls; i++) |
8803 | { |
8804 | if (vtbl == ValidVtbls[i]) |
8805 | { |
8806 | retVal = true; |
8807 | break; |
8808 | } |
8809 | } |
8810 | } |
8811 | EX_CATCH |
8812 | { |
8813 | // Swallow any exception out of the exception constructors above and simply return false. |
8814 | } |
8815 | EX_END_CATCH(SwallowAllExceptions); |
8816 | |
8817 | return retVal; |
8818 | } |
8819 | |
8820 | void *DebugGetCxxException(EXCEPTION_RECORD* pExceptionRecord); |
8821 | |
8822 | bool DebugIsEECxxException(EXCEPTION_RECORD* pExceptionRecord) |
8823 | { |
8824 | return DebugIsEECxxExceptionPointer(DebugGetCxxException(pExceptionRecord)); |
8825 | } |
8826 | |
8827 | // |
8828 | // C++ EH cracking material gleaned from the debugger: |
8829 | // (DO NOT USE THIS KNOWLEDGE IN NON-DEBUG CODE!!!) |
8830 | // |
8831 | // EHExceptionRecord::EHParameters |
8832 | // [0] magicNumber : uint |
8833 | // [1] pExceptionObject : void* |
8834 | // [2] pThrowInfo : ThrowInfo* |
8835 | |
8836 | #ifdef _WIN64 |
8837 | #define NUM_CXX_EXCEPTION_PARAMS 4 |
8838 | #else |
8839 | #define NUM_CXX_EXCEPTION_PARAMS 3 |
8840 | #endif |
8841 | |
8842 | void *DebugGetCxxException(EXCEPTION_RECORD* pExceptionRecord) |
8843 | { |
8844 | WRAPPER_NO_CONTRACT; |
8845 | |
8846 | bool fExCodeIsCxx = (EXCEPTION_MSVC == pExceptionRecord->ExceptionCode); |
8847 | bool fExHasCorrectNumParams = (NUM_CXX_EXCEPTION_PARAMS == pExceptionRecord->NumberParameters); |
8848 | |
8849 | if (fExCodeIsCxx && fExHasCorrectNumParams) |
8850 | { |
8851 | void** ppException = (void**)pExceptionRecord->ExceptionInformation[1]; |
8852 | |
8853 | if (NULL == ppException) |
8854 | { |
8855 | return NULL; |
8856 | } |
8857 | |
8858 | return *ppException; |
8859 | |
8860 | } |
8861 | |
8862 | CONSISTENCY_CHECK_MSG(!fExCodeIsCxx || fExHasCorrectNumParams, "We expected an EXCEPTION_MSVC exception to have 3 parameters. Did the CRT change its exception format?" ); |
8863 | |
8864 | return NULL; |
8865 | } |
8866 | |
8867 | #endif // _DEBUG |
8868 | |
8869 | #endif // #ifndef DACCESS_COMPILE |
8870 | |
8871 | BOOL IsException(MethodTable *pMT) { |
8872 | CONTRACTL |
8873 | { |
8874 | NOTHROW; |
8875 | GC_NOTRIGGER; |
8876 | MODE_ANY; |
8877 | SO_TOLERANT; |
8878 | SUPPORTS_DAC; |
8879 | } |
8880 | CONTRACTL_END; |
8881 | |
8882 | ASSERT(g_pExceptionClass != NULL); |
8883 | |
8884 | while (pMT != NULL && pMT != g_pExceptionClass) { |
8885 | pMT = pMT->GetParentMethodTable(); |
8886 | } |
8887 | |
8888 | return pMT != NULL; |
8889 | } // BOOL IsException() |
8890 | |
8891 | // Returns TRUE iff calling get_StackTrace on an exception of the given type ends up |
8892 | // executing some other code than just Exception.get_StackTrace. |
8893 | BOOL ExceptionTypeOverridesStackTraceGetter(PTR_MethodTable pMT) |
8894 | { |
8895 | CONTRACTL |
8896 | { |
8897 | THROWS; |
8898 | GC_NOTRIGGER; |
8899 | MODE_ANY; |
8900 | SO_TOLERANT; |
8901 | SUPPORTS_DAC; |
8902 | } |
8903 | CONTRACTL_END; |
8904 | |
8905 | _ASSERTE(IsException(pMT)); |
8906 | |
8907 | if (pMT == g_pExceptionClass) |
8908 | { |
8909 | // if the type is System.Exception, it certainly doesn't override anything |
8910 | return FALSE; |
8911 | } |
8912 | |
8913 | // find the slot corresponding to get_StackTrace |
8914 | for (DWORD slot = g_pObjectClass->GetNumVirtuals(); slot < g_pExceptionClass->GetNumVirtuals(); slot++) |
8915 | { |
8916 | MethodDesc *pMD = g_pExceptionClass->GetMethodDescForSlot(slot); |
8917 | LPCUTF8 name = pMD->GetName(); |
8918 | |
8919 | if (name != NULL && strcmp(name, "get_StackTrace" ) == 0) |
8920 | { |
8921 | // see if the slot is overriden by pMT |
8922 | MethodDesc *pDerivedMD = pMT->GetMethodDescForSlot(slot); |
8923 | return (pDerivedMD != pMD); |
8924 | } |
8925 | } |
8926 | |
8927 | // there must be get_StackTrace on System.Exception |
8928 | UNREACHABLE(); |
8929 | } |
8930 | |
8931 | // Removes source file names/paths and line information from a stack trace generated |
8932 | // by Environment.GetStackTrace. |
8933 | void StripFileInfoFromStackTrace(SString &ssStackTrace) |
8934 | { |
8935 | CONTRACTL |
8936 | { |
8937 | THROWS; |
8938 | GC_NOTRIGGER; |
8939 | MODE_ANY; |
8940 | SUPPORTS_DAC; |
8941 | } |
8942 | CONTRACTL_END; |
8943 | |
8944 | SString::Iterator i = ssStackTrace.Begin(); |
8945 | SString::Iterator end; |
8946 | int countBracket = 0; |
8947 | int position = 0; |
8948 | |
8949 | while (i < ssStackTrace.End()) |
8950 | { |
8951 | if (i[0] == W('(')) |
8952 | { |
8953 | countBracket ++; |
8954 | } |
8955 | else if (i[0] == W(')')) |
8956 | { |
8957 | if (countBracket == 1) |
8958 | { |
8959 | end = i + 1; |
8960 | SString::Iterator j = i + 1; |
8961 | while (j < ssStackTrace.End()) |
8962 | { |
8963 | if (j[0] == W('\r') || j[0] == W('\n')) |
8964 | { |
8965 | break; |
8966 | } |
8967 | j++; |
8968 | } |
8969 | if (j > end) |
8970 | { |
8971 | ssStackTrace.Delete(end,j-end); |
8972 | i = ssStackTrace.Begin() + position; |
8973 | } |
8974 | } |
8975 | countBracket --; |
8976 | } |
8977 | i ++; |
8978 | position ++; |
8979 | } |
8980 | ssStackTrace.Truncate(end); |
8981 | } |
8982 | |
8983 | #ifdef _DEBUG |
8984 | //============================================================================== |
8985 | // This function will set a thread state indicating if an exception is escaping |
8986 | // the last CLR personality routine on the stack in a reverse pinvoke scenario. |
8987 | // |
8988 | // If the exception continues to go unhandled, it will eventually reach the OS |
8989 | // that will start invoking the UEFs. Since CLR registers its UEF only to handle |
8990 | // unhandled exceptions on such reverse pinvoke threads, we will assert this |
8991 | // state in our UEF to ensure it does not get called for any other reason. |
8992 | // |
8993 | // This function should be called only if the personality routine returned |
8994 | // EXCEPTION_CONTINUE_SEARCH. |
8995 | //============================================================================== |
8996 | void SetReversePInvokeEscapingUnhandledExceptionStatus(BOOL fIsUnwinding, |
8997 | #if defined(_TARGET_X86_) |
8998 | EXCEPTION_REGISTRATION_RECORD * pEstablisherFrame |
8999 | #elif defined(WIN64EXCEPTIONS) |
9000 | ULONG64 pEstablisherFrame |
9001 | #else |
9002 | #error Unsupported platform |
9003 | #endif |
9004 | ) |
9005 | { |
9006 | #ifndef DACCESS_COMPILE |
9007 | |
9008 | LIMITED_METHOD_CONTRACT; |
9009 | |
9010 | Thread *pCurThread = GetThread(); |
9011 | _ASSERTE(pCurThread); |
9012 | |
9013 | if (pCurThread->GetExceptionState()->IsExceptionInProgress()) |
9014 | { |
9015 | if (!fIsUnwinding) |
9016 | { |
9017 | // Get the top-most Frame of this thread. |
9018 | Frame* pCurFrame = pCurThread->GetFrame(); |
9019 | Frame* pTopMostFrame = pCurFrame; |
9020 | while (pCurFrame && (pCurFrame != FRAME_TOP)) |
9021 | { |
9022 | pTopMostFrame = pCurFrame; |
9023 | pCurFrame = pCurFrame->PtrNextFrame(); |
9024 | } |
9025 | |
9026 | // Is the exception escaping the last CLR personality routine on the stack of a |
9027 | // reverse pinvoke thread? |
9028 | if (((pTopMostFrame == NULL) || (pTopMostFrame == FRAME_TOP)) || |
9029 | ((void *)(pEstablisherFrame) > (void *)(pTopMostFrame))) |
9030 | { |
9031 | LOG((LF_EH, LL_INFO100, "SetReversePInvokeEscapingUnhandledExceptionStatus: setting Ex_RPInvokeEscapingException\n" )); |
9032 | // Set the flag on the thread indicating the exception is escaping the |
9033 | // top most reverse pinvoke exception handler. |
9034 | pCurThread->GetExceptionState()->GetFlags()->SetReversePInvokeEscapingException(); |
9035 | } |
9036 | } |
9037 | else |
9038 | { |
9039 | // Since we are unwinding, simply unset the flag indicating escaping unhandled exception |
9040 | // if it was set. |
9041 | if (pCurThread->GetExceptionState()->GetFlags()->ReversePInvokeEscapingException()) |
9042 | { |
9043 | LOG((LF_EH, LL_INFO100, "SetReversePInvokeEscapingUnhandledExceptionStatus: unsetting Ex_RPInvokeEscapingException\n" )); |
9044 | pCurThread->GetExceptionState()->GetFlags()->ResetReversePInvokeEscapingException(); |
9045 | } |
9046 | } |
9047 | } |
9048 | else |
9049 | { |
9050 | LOG((LF_EH, LL_INFO100, "SetReversePInvokeEscapingUnhandledExceptionStatus: not setting Ex_RPInvokeEscapingException since no exception is in progress.\n" )); |
9051 | } |
9052 | #endif // !DACCESS_COMPILE |
9053 | } |
9054 | |
9055 | #endif // _DEBUG |
9056 | |
9057 | #ifndef FEATURE_PAL |
9058 | |
9059 | // This function will capture the watson buckets for the current exception object that is: |
9060 | // |
9061 | // 1) Non-preallocated |
9062 | // 2) Already contains the IP for watson bucketing |
9063 | BOOL SetupWatsonBucketsForNonPreallocatedExceptions(OBJECTREF oThrowable /* = NULL */) |
9064 | { |
9065 | #ifndef DACCESS_COMPILE |
9066 | |
9067 | // CoreCLR may have watson bucketing conditionally enabled. |
9068 | if (!IsWatsonEnabled()) |
9069 | { |
9070 | return FALSE; |
9071 | } |
9072 | |
9073 | CONTRACTL |
9074 | { |
9075 | GC_TRIGGERS; |
9076 | MODE_COOPERATIVE; |
9077 | NOTHROW; |
9078 | PRECONDITION(GetThread() != NULL); |
9079 | } |
9080 | CONTRACTL_END; |
9081 | |
9082 | // By default, assume we didnt get the buckets |
9083 | BOOL fSetupWatsonBuckets = FALSE; |
9084 | |
9085 | Thread * pThread = GetThread(); |
9086 | |
9087 | struct |
9088 | { |
9089 | OBJECTREF oThrowable; |
9090 | } gc; |
9091 | ZeroMemory(&gc, sizeof(gc)); |
9092 | GCPROTECT_BEGIN(gc); |
9093 | |
9094 | // Get the throwable to be used |
9095 | gc.oThrowable = (oThrowable != NULL) ? oThrowable : pThread->GetThrowable(); |
9096 | if (gc.oThrowable == NULL) |
9097 | { |
9098 | // If we have no throwable, then simply return back. |
9099 | // |
9100 | // We could be here because the VM may have raised an exception, |
9101 | // and not managed code, for its internal usage (e.g. TA to end the |
9102 | // threads when unloading an AppDomain). Thus, there would be no throwable |
9103 | // present since the exception has not been seen by the runtime's |
9104 | // personality routine. |
9105 | // |
9106 | // Hence, we have no work to do here. |
9107 | LOG((LF_EH, LL_INFO100, "SetupWatsonBucketsForNonPreallocatedExceptions - No throwable available.\n" )); |
9108 | goto done; |
9109 | } |
9110 | |
9111 | // The exception object should be non-preallocated |
9112 | _ASSERTE(!CLRException::IsPreallocatedExceptionObject(gc.oThrowable)); |
9113 | |
9114 | if (((EXCEPTIONREF)gc.oThrowable)->AreWatsonBucketsPresent() == FALSE) |
9115 | { |
9116 | // Attempt to capture the watson buckets since they are not present. |
9117 | UINT_PTR ip = ((EXCEPTIONREF)gc.oThrowable)->GetIPForWatsonBuckets(); |
9118 | if (ip != NULL) |
9119 | { |
9120 | // Attempt to capture the buckets |
9121 | PTR_VOID pBuckets = GetBucketParametersForManagedException(ip, TypeOfReportedError::UnhandledException, pThread, &gc.oThrowable); |
9122 | if (pBuckets != NULL) |
9123 | { |
9124 | // Got the buckets - save them to the exception object |
9125 | fSetupWatsonBuckets = FALSE; |
9126 | EX_TRY |
9127 | { |
9128 | fSetupWatsonBuckets = CopyWatsonBucketsToThrowable(pBuckets, gc.oThrowable); |
9129 | } |
9130 | EX_CATCH |
9131 | { |
9132 | // OOM can bring us here |
9133 | fSetupWatsonBuckets = FALSE; |
9134 | } |
9135 | EX_END_CATCH(SwallowAllExceptions); |
9136 | |
9137 | if (!fSetupWatsonBuckets) |
9138 | { |
9139 | LOG((LF_EH, LL_INFO100, "SetupWatsonBucketsForNonPreallocatedExceptions - Unable to copy buckets to throwable likely due to OOM.\n" )); |
9140 | } |
9141 | else |
9142 | { |
9143 | // Clear the saved IP since we have captured the buckets |
9144 | ((EXCEPTIONREF)gc.oThrowable)->SetIPForWatsonBuckets(NULL); |
9145 | LOG((LF_EH, LL_INFO100, "SetupWatsonBucketsForNonPreallocatedExceptions - Buckets copied to throwable.\n" )); |
9146 | } |
9147 | FreeBucketParametersForManagedException(pBuckets); |
9148 | } |
9149 | else |
9150 | { |
9151 | LOG((LF_EH, LL_INFO100, "SetupWatsonBucketsForNonPreallocatedExceptions - Unable to capture buckets from IP likely due to OOM.\n" )); |
9152 | } |
9153 | } |
9154 | else |
9155 | { |
9156 | LOG((LF_EH, LL_INFO100, "SetupWatsonBucketsForNonPreallocatedExceptions - No IP available to capture buckets from.\n" )); |
9157 | } |
9158 | } |
9159 | |
9160 | done:; |
9161 | GCPROTECT_END(); |
9162 | |
9163 | return fSetupWatsonBuckets; |
9164 | #else // DACCESS_COMPILE |
9165 | return FALSE; |
9166 | #endif // !DACCESS_COMPILE |
9167 | } |
9168 | |
9169 | // When exceptions are escaping out of various transition boundaries, |
9170 | // we will need to capture bucket details for the original exception |
9171 | // before the exception goes across the boundary to the caller. |
9172 | // |
9173 | // Examples of such boundaries include: |
9174 | // |
9175 | // 1) AppDomain transition boundaries (these are physical transition boundaries) |
9176 | // 2) Dynamic method invocation in Reflection (these are logical transition boundaries). |
9177 | // |
9178 | // This function will capture the bucketing details in the UE tracker so that |
9179 | // they can be used once we cross over. |
9180 | BOOL SetupWatsonBucketsForEscapingPreallocatedExceptions() |
9181 | { |
9182 | #ifndef DACCESS_COMPILE |
9183 | |
9184 | // CoreCLR may have watson bucketing conditionally enabled. |
9185 | if (!IsWatsonEnabled()) |
9186 | { |
9187 | return FALSE; |
9188 | } |
9189 | |
9190 | CONTRACTL |
9191 | { |
9192 | GC_NOTRIGGER; |
9193 | MODE_ANY; |
9194 | NOTHROW; |
9195 | PRECONDITION(GetThread() != NULL); |
9196 | } |
9197 | CONTRACTL_END; |
9198 | |
9199 | // By default, assume we didnt get the buckets |
9200 | BOOL fSetupWatsonBuckets = FALSE; |
9201 | PTR_EHWatsonBucketTracker pUEWatsonBucketTracker; |
9202 | |
9203 | Thread * pThread = GetThread(); |
9204 | |
9205 | // If the exception going unhandled is preallocated, then capture the Watson buckets in the UE Watson |
9206 | // bucket tracker provided its not already populated. |
9207 | // |
9208 | // Switch to COOP mode |
9209 | GCX_COOP(); |
9210 | |
9211 | struct |
9212 | { |
9213 | OBJECTREF oThrowable; |
9214 | } gc; |
9215 | ZeroMemory(&gc, sizeof(gc)); |
9216 | GCPROTECT_BEGIN(gc); |
9217 | |
9218 | // Get the throwable corresponding to the escaping exception |
9219 | gc.oThrowable = pThread->GetThrowable(); |
9220 | if (gc.oThrowable == NULL) |
9221 | { |
9222 | // If we have no throwable, then simply return back. |
9223 | // |
9224 | // We could be here because the VM may have raised an exception, |
9225 | // and not managed code, for its internal usage (e.g. TA to end the |
9226 | // threads when unloading an AppDomain). Thus, there would be no throwable |
9227 | // present since the exception has not been seen by the runtime's |
9228 | // personality routine. |
9229 | // |
9230 | // Hence, we have no work to do here. |
9231 | LOG((LF_EH, LL_INFO100, "SetupWatsonBucketsForEscapingPreallocatedExceptions - No throwable available.\n" )); |
9232 | goto done; |
9233 | } |
9234 | |
9235 | // Is the exception preallocated? We are not going to process non-preallocated exception objects since |
9236 | // they already have the watson buckets in them. |
9237 | // |
9238 | // We skip thread abort as well since we track them in the UE watson bucket tracker at |
9239 | // throw time itself. |
9240 | if (!((CLRException::IsPreallocatedExceptionObject(gc.oThrowable)) && |
9241 | !IsThrowableThreadAbortException(gc.oThrowable))) |
9242 | { |
9243 | // Its either not preallocated or a thread abort exception, |
9244 | // neither of which we need to process. |
9245 | goto done; |
9246 | } |
9247 | |
9248 | // The UE watson bucket tracker could be non-empty if there were earlier transitions |
9249 | // on the threads stack before the exception got raised. |
9250 | pUEWatsonBucketTracker = pThread->GetExceptionState()->GetUEWatsonBucketTracker(); |
9251 | _ASSERTE(pUEWatsonBucketTracker != NULL); |
9252 | |
9253 | // Proceed to capture bucketing details only if the UE watson bucket tracker is empty. |
9254 | if((pUEWatsonBucketTracker->RetrieveWatsonBucketIp() == NULL) && (pUEWatsonBucketTracker->RetrieveWatsonBuckets() == NULL)) |
9255 | { |
9256 | // Get the Watson Bucket tracker for this preallocated exception |
9257 | PTR_EHWatsonBucketTracker pCurWatsonBucketTracker = GetWatsonBucketTrackerForPreallocatedException(gc.oThrowable, FALSE); |
9258 | |
9259 | if (pCurWatsonBucketTracker != NULL) |
9260 | { |
9261 | // If the tracker exists, we must have the throw site IP |
9262 | _ASSERTE(pCurWatsonBucketTracker->RetrieveWatsonBucketIp() != NULL); |
9263 | |
9264 | // Init the UE Watson bucket tracker |
9265 | pUEWatsonBucketTracker->ClearWatsonBucketDetails(); |
9266 | |
9267 | // Copy the Bucket details to the UE watson bucket tracker |
9268 | pUEWatsonBucketTracker->CopyEHWatsonBucketTracker(*(pCurWatsonBucketTracker)); |
9269 | |
9270 | // If the buckets dont exist, capture them now |
9271 | if (pUEWatsonBucketTracker->RetrieveWatsonBuckets() == NULL) |
9272 | { |
9273 | pUEWatsonBucketTracker->CaptureUnhandledInfoForWatson(TypeOfReportedError::UnhandledException, pThread, &gc.oThrowable); |
9274 | } |
9275 | |
9276 | // If the IP was in managed code, we will have the buckets. |
9277 | if(pUEWatsonBucketTracker->RetrieveWatsonBuckets() != NULL) |
9278 | { |
9279 | fSetupWatsonBuckets = TRUE; |
9280 | LOG((LF_EH, LL_INFO100, "SetupWatsonBucketsForEscapingPreallocatedExceptions - Captured watson buckets for preallocated exception at transition.\n" )); |
9281 | } |
9282 | else |
9283 | { |
9284 | // IP was likely in native code - hence, watson helper functions couldnt get us the buckets |
9285 | LOG((LF_EH, LL_INFO100, "SetupWatsonBucketsForEscapingPreallocatedExceptions - Watson buckets not found for IP. IP likely in native code.\n" )); |
9286 | |
9287 | // Clear the UE tracker |
9288 | pUEWatsonBucketTracker->ClearWatsonBucketDetails(); |
9289 | } |
9290 | } |
9291 | else |
9292 | { |
9293 | LOG((LF_EH, LL_INFO100, "SetupWatsonBucketsForEscapingPreallocatedExceptions - Watson bucket tracker for preallocated exception not found. Exception likely thrown in native code.\n" )); |
9294 | } |
9295 | } |
9296 | |
9297 | done:; |
9298 | GCPROTECT_END(); |
9299 | |
9300 | return fSetupWatsonBuckets; |
9301 | #else // DACCESS_COMPILE |
9302 | return FALSE; |
9303 | #endif // !DACCESS_COMPILE |
9304 | } |
9305 | |
9306 | // This function is invoked from the UEF worker to setup the watson buckets |
9307 | // for the exception going unhandled, if details are available. See |
9308 | // implementation below for specifics. |
9309 | void SetupWatsonBucketsForUEF(BOOL fUseLastThrownObject) |
9310 | { |
9311 | #ifndef DACCESS_COMPILE |
9312 | |
9313 | // CoreCLR may have watson bucketing conditionally enabled. |
9314 | if (!IsWatsonEnabled()) |
9315 | { |
9316 | return; |
9317 | } |
9318 | |
9319 | CONTRACTL |
9320 | { |
9321 | GC_TRIGGERS; |
9322 | MODE_ANY; |
9323 | NOTHROW; |
9324 | PRECONDITION(GetThread() != NULL); |
9325 | } |
9326 | CONTRACTL_END; |
9327 | |
9328 | Thread *pThread = GetThread(); |
9329 | |
9330 | PTR_EHWatsonBucketTracker pCurWatsonBucketTracker = NULL; |
9331 | ThreadExceptionState *pExState = pThread->GetExceptionState(); |
9332 | _ASSERTE(pExState != NULL); |
9333 | |
9334 | // If the exception tracker exists, then copy the bucketing details |
9335 | // from it to the UE Watson Bucket tracker. |
9336 | // |
9337 | // On 64bit, the EH system allocates the EHTracker only in the case of an exception. |
9338 | // Thus, assume a reverse pinvoke thread transitions to managed code from native, |
9339 | // does some work in managed and returns back to native code. |
9340 | // |
9341 | // In the native code, it has an exception that goes unhandled and the OS |
9342 | // ends up invoking our UEF, and thus, we land up here. |
9343 | // |
9344 | // In such a case, on 64bit, we wont have an exception tracker since there |
9345 | // was no managed exception active. On 32bit, we will have a tracker |
9346 | // but there wont be an IP corresponding to the throw site since exception |
9347 | // was raised in native code. |
9348 | // |
9349 | // But if the tracker exists, simply copy the bucket details to the UE Watson Bucket |
9350 | // tracker for use by the "WatsonLastChance" path. |
9351 | BOOL fDoWeHaveWatsonBuckets = FALSE; |
9352 | if (pExState->GetCurrentExceptionTracker() != NULL) |
9353 | { |
9354 | // Check the exception state if we have Watson bucket details |
9355 | fDoWeHaveWatsonBuckets = pExState->GetFlags()->GotWatsonBucketDetails(); |
9356 | } |
9357 | |
9358 | // Switch to COOP mode before working with the throwable |
9359 | GCX_COOP(); |
9360 | |
9361 | // Get the throwable we are going to work with |
9362 | struct |
9363 | { |
9364 | OBJECTREF oThrowable; |
9365 | } gc; |
9366 | ZeroMemory(&gc, sizeof(gc)); |
9367 | GCPROTECT_BEGIN(gc); |
9368 | |
9369 | gc.oThrowable = fUseLastThrownObject ? pThread->LastThrownObject() : pThread->GetThrowable(); |
9370 | BOOL fThrowableExists = (gc.oThrowable != NULL); |
9371 | BOOL fIsThrowablePreallocated = !fThrowableExists ? FALSE : CLRException::IsPreallocatedExceptionObject(gc.oThrowable); |
9372 | |
9373 | if ((!fDoWeHaveWatsonBuckets) && fThrowableExists) |
9374 | { |
9375 | // Check the throwable if it has buckets - this could be the scenario |
9376 | // of native code calling into a non-default domain and thus, have an AD |
9377 | // transition in between that could reraise the exception but that would |
9378 | // never be seen by our exception handler. Thus, there wont be any tracker |
9379 | // or tracker state. |
9380 | // |
9381 | // Invocation of entry point on WLC via reverse pinvoke is an example. |
9382 | if (!fIsThrowablePreallocated) |
9383 | { |
9384 | fDoWeHaveWatsonBuckets = ((EXCEPTIONREF)gc.oThrowable)->AreWatsonBucketsPresent(); |
9385 | if (!fDoWeHaveWatsonBuckets) |
9386 | { |
9387 | // If buckets are not present, then we may have IP to capture the buckets from. |
9388 | fDoWeHaveWatsonBuckets = ((EXCEPTIONREF)gc.oThrowable)->IsIPForWatsonBucketsPresent(); |
9389 | } |
9390 | } |
9391 | else |
9392 | { |
9393 | // Get the watson bucket tracker for the preallocated exception |
9394 | PTR_EHWatsonBucketTracker pCurWBTracker = GetWatsonBucketTrackerForPreallocatedException(gc.oThrowable, FALSE); |
9395 | |
9396 | // We would have buckets if we have the IP |
9397 | if (pCurWBTracker && (pCurWBTracker->RetrieveWatsonBucketIp() != NULL)) |
9398 | { |
9399 | fDoWeHaveWatsonBuckets = TRUE; |
9400 | } |
9401 | } |
9402 | } |
9403 | |
9404 | if (fDoWeHaveWatsonBuckets) |
9405 | { |
9406 | // Get the UE Watson bucket tracker |
9407 | PTR_EHWatsonBucketTracker pUEWatsonBucketTracker = pExState->GetUEWatsonBucketTracker(); |
9408 | |
9409 | // Clear any existing information |
9410 | pUEWatsonBucketTracker->ClearWatsonBucketDetails(); |
9411 | |
9412 | if (fIsThrowablePreallocated) |
9413 | { |
9414 | // Get the watson bucket tracker for the preallocated exception |
9415 | PTR_EHWatsonBucketTracker pCurWBTracker = GetWatsonBucketTrackerForPreallocatedException(gc.oThrowable, FALSE); |
9416 | |
9417 | if (pCurWBTracker != NULL) |
9418 | { |
9419 | // We should be having an IP for this exception at this point |
9420 | _ASSERTE(pCurWBTracker->RetrieveWatsonBucketIp() != NULL); |
9421 | |
9422 | // Copy the existing bucketing details to the UE tracker |
9423 | pUEWatsonBucketTracker->CopyEHWatsonBucketTracker(*(pCurWBTracker)); |
9424 | |
9425 | // Get the buckets if we dont already have them since we |
9426 | // dont want to overwrite existing bucket information (e.g. |
9427 | // from an AD transition) |
9428 | if (pUEWatsonBucketTracker->RetrieveWatsonBuckets() == NULL) |
9429 | { |
9430 | pUEWatsonBucketTracker->CaptureUnhandledInfoForWatson(TypeOfReportedError::UnhandledException, pThread, &gc.oThrowable); |
9431 | if (pUEWatsonBucketTracker->RetrieveWatsonBuckets() != NULL) |
9432 | { |
9433 | LOG((LF_EH, LL_INFO100, "SetupWatsonBucketsForUEF: Collected watson bucket information for preallocated exception\n" )); |
9434 | } |
9435 | else |
9436 | { |
9437 | // If we are here, then one of the following could have happened: |
9438 | // |
9439 | // 1) pCurWBTracker had buckets but we couldnt copy them over to pUEWatsonBucketTracker due to OOM, or |
9440 | // 2) pCurWBTracker's IP was in native code; thus pUEWatsonBucketTracker->CaptureUnhandledInfoForWatson() |
9441 | // couldnt get us the watson buckets. |
9442 | LOG((LF_EH, LL_INFO100, "SetupWatsonBucketsForUEF: Unable to collect watson bucket information for preallocated exception due to OOM or IP being in native code.\n" )); |
9443 | } |
9444 | } |
9445 | } |
9446 | else |
9447 | { |
9448 | // We likely had an OOM earlier (while copying the bucket information) if we are here |
9449 | LOG((LF_EH, LL_INFO100, "SetupWatsonBucketsForUEF: Watson bucket tracker for preallocated exception not found.\n" )); |
9450 | } |
9451 | } |
9452 | else |
9453 | { |
9454 | // Throwable is not preallocated - get the bucket details from it for use by Watson |
9455 | _ASSERTE_MSG(((EXCEPTIONREF)gc.oThrowable)->AreWatsonBucketsPresent() || |
9456 | ((EXCEPTIONREF)gc.oThrowable)->IsIPForWatsonBucketsPresent(), |
9457 | "How come we dont have watson buckets (or IP) for a non-preallocated exception in the UEF?" ); |
9458 | |
9459 | if ((((EXCEPTIONREF)gc.oThrowable)->AreWatsonBucketsPresent() == FALSE) && |
9460 | ((EXCEPTIONREF)gc.oThrowable)->IsIPForWatsonBucketsPresent()) |
9461 | { |
9462 | // Capture the buckets using the IP we have. |
9463 | SetupWatsonBucketsForNonPreallocatedExceptions(gc.oThrowable); |
9464 | } |
9465 | |
9466 | if (((EXCEPTIONREF)gc.oThrowable)->AreWatsonBucketsPresent()) |
9467 | { |
9468 | pUEWatsonBucketTracker->CopyBucketsFromThrowable(gc.oThrowable); |
9469 | } |
9470 | |
9471 | if (pUEWatsonBucketTracker->RetrieveWatsonBuckets() == NULL) |
9472 | { |
9473 | LOG((LF_EH, LL_INFO100, "SetupWatsonBucketsForUEF: Unable to copy watson buckets from regular exception throwable (%p), likely due to OOM.\n" , |
9474 | OBJECTREFToObject(gc.oThrowable))); |
9475 | } |
9476 | } |
9477 | } |
9478 | else |
9479 | { |
9480 | // We dont have the watson buckets; exception was in native code that we dont care about |
9481 | LOG((LF_EH, LL_INFO100, "SetupWatsonBucketsForUEF: We dont have watson buckets - likely an exception in native code.\n" )); |
9482 | } |
9483 | |
9484 | GCPROTECT_END(); |
9485 | #endif // !DACCESS_COMPILE |
9486 | } |
9487 | |
9488 | // Given a throwable, this function will return a BOOL indicating |
9489 | // if it corresponds to any of the following thread abort exception |
9490 | // objects: |
9491 | // |
9492 | // 1) Regular allocated ThreadAbortException |
9493 | // 2) Preallocated ThreadAbortException |
9494 | // 3) Preallocated RudeThreadAbortException |
9495 | BOOL IsThrowableThreadAbortException(OBJECTREF oThrowable) |
9496 | { |
9497 | #ifndef DACCESS_COMPILE |
9498 | CONTRACTL |
9499 | { |
9500 | GC_NOTRIGGER; |
9501 | MODE_COOPERATIVE; |
9502 | NOTHROW; |
9503 | PRECONDITION(GetThread() != NULL); |
9504 | PRECONDITION(oThrowable != NULL); |
9505 | } |
9506 | CONTRACTL_END; |
9507 | |
9508 | BOOL fIsTAE = FALSE; |
9509 | |
9510 | struct |
9511 | { |
9512 | OBJECTREF oThrowable; |
9513 | } gc; |
9514 | ZeroMemory(&gc, sizeof(gc)); |
9515 | GCPROTECT_BEGIN(gc); |
9516 | |
9517 | gc.oThrowable = oThrowable; |
9518 | |
9519 | fIsTAE = (IsExceptionOfType(kThreadAbortException,&(gc.oThrowable)) || // regular TAE |
9520 | ((g_pPreallocatedThreadAbortException != NULL) && |
9521 | (gc.oThrowable == CLRException::GetPreallocatedThreadAbortException())) || |
9522 | ((g_pPreallocatedRudeThreadAbortException != NULL) && |
9523 | (gc.oThrowable == CLRException::GetPreallocatedRudeThreadAbortException()))); |
9524 | |
9525 | GCPROTECT_END(); |
9526 | |
9527 | return fIsTAE; |
9528 | |
9529 | #else // DACCESS_COMPILE |
9530 | return FALSE; |
9531 | #endif // !DACCESS_COMPILE |
9532 | } |
9533 | |
9534 | // Given a throwable, this function will walk the exception tracker |
9535 | // list to return the tracker, if available, corresponding to the preallocated |
9536 | // exception object. |
9537 | // |
9538 | // The caller can also specify the starting EHTracker to walk the list from. |
9539 | // If not specified, this will default to the current exception tracker active |
9540 | // on the thread. |
9541 | #if defined(WIN64EXCEPTIONS) |
9542 | PTR_ExceptionTracker GetEHTrackerForPreallocatedException(OBJECTREF oPreAllocThrowable, |
9543 | PTR_ExceptionTracker pStartingEHTracker) |
9544 | #elif _TARGET_X86_ |
9545 | PTR_ExInfo GetEHTrackerForPreallocatedException(OBJECTREF oPreAllocThrowable, |
9546 | PTR_ExInfo pStartingEHTracker) |
9547 | #else |
9548 | #error Unsupported platform |
9549 | #endif |
9550 | { |
9551 | CONTRACTL |
9552 | { |
9553 | GC_NOTRIGGER; |
9554 | MODE_COOPERATIVE; |
9555 | NOTHROW; |
9556 | PRECONDITION(GetThread() != NULL); |
9557 | PRECONDITION(oPreAllocThrowable != NULL); |
9558 | PRECONDITION(CLRException::IsPreallocatedExceptionObject(oPreAllocThrowable)); |
9559 | PRECONDITION(IsWatsonEnabled()); |
9560 | } |
9561 | CONTRACTL_END; |
9562 | |
9563 | // Get the reference to the current exception tracker |
9564 | #if defined(WIN64EXCEPTIONS) |
9565 | PTR_ExceptionTracker pEHTracker = (pStartingEHTracker != NULL) ? pStartingEHTracker : GetThread()->GetExceptionState()->GetCurrentExceptionTracker(); |
9566 | #elif _TARGET_X86_ |
9567 | PTR_ExInfo pEHTracker = (pStartingEHTracker != NULL) ? pStartingEHTracker : GetThread()->GetExceptionState()->GetCurrentExceptionTracker(); |
9568 | #else // !(_WIN64 || _TARGET_X86_) |
9569 | #error Unsupported platform |
9570 | #endif // _WIN64 |
9571 | |
9572 | BOOL fFoundTracker = FALSE; |
9573 | |
9574 | struct |
9575 | { |
9576 | OBJECTREF oPreAllocThrowable; |
9577 | } gc; |
9578 | ZeroMemory(&gc, sizeof(gc)); |
9579 | GCPROTECT_BEGIN(gc); |
9580 | |
9581 | gc.oPreAllocThrowable = oPreAllocThrowable; |
9582 | |
9583 | // Start walking the list to find the tracker correponding |
9584 | // to the preallocated exception object. |
9585 | while (pEHTracker != NULL) |
9586 | { |
9587 | if (pEHTracker->GetThrowable() == gc.oPreAllocThrowable) |
9588 | { |
9589 | // found the tracker - break out. |
9590 | fFoundTracker = TRUE; |
9591 | break; |
9592 | } |
9593 | |
9594 | // move to the previous tracker... |
9595 | pEHTracker = pEHTracker->GetPreviousExceptionTracker(); |
9596 | } |
9597 | |
9598 | GCPROTECT_END(); |
9599 | |
9600 | return fFoundTracker ? pEHTracker : NULL; |
9601 | } |
9602 | |
9603 | // This function will return the pointer to EHWatsonBucketTracker corresponding to the |
9604 | // preallocated exception object. If none is found, it will return NULL. |
9605 | PTR_EHWatsonBucketTracker GetWatsonBucketTrackerForPreallocatedException(OBJECTREF oPreAllocThrowable, |
9606 | BOOL fCaptureBucketsIfNotPresent, |
9607 | BOOL fStartSearchFromPreviousTracker /*= FALSE*/) |
9608 | { |
9609 | #ifndef DACCESS_COMPILE |
9610 | CONTRACTL |
9611 | { |
9612 | GC_NOTRIGGER; |
9613 | MODE_COOPERATIVE; |
9614 | NOTHROW; |
9615 | PRECONDITION(GetThread() != NULL); |
9616 | PRECONDITION(oPreAllocThrowable != NULL); |
9617 | PRECONDITION(CLRException::IsPreallocatedExceptionObject(oPreAllocThrowable)); |
9618 | PRECONDITION(IsWatsonEnabled()); |
9619 | } |
9620 | CONTRACTL_END; |
9621 | |
9622 | PTR_EHWatsonBucketTracker pWBTracker = NULL; |
9623 | |
9624 | struct |
9625 | { |
9626 | OBJECTREF oPreAllocThrowable; |
9627 | } gc; |
9628 | ZeroMemory(&gc, sizeof(gc)); |
9629 | GCPROTECT_BEGIN(gc); |
9630 | |
9631 | gc.oPreAllocThrowable = oPreAllocThrowable; |
9632 | |
9633 | // Before doing anything, check if this is a thread abort exception. If it is, |
9634 | // then simply return the reference to the UE watson bucket tracker since it |
9635 | // tracks the bucketing details for all types of TAE. |
9636 | if (IsThrowableThreadAbortException(gc.oPreAllocThrowable)) |
9637 | { |
9638 | pWBTracker = GetThread()->GetExceptionState()->GetUEWatsonBucketTracker(); |
9639 | LOG((LF_EH, LL_INFO100, "GetWatsonBucketTrackerForPreallocatedException - Setting UE Watson Bucket Tracker to be returned for preallocated ThreadAbortException.\n" )); |
9640 | goto doValidation; |
9641 | } |
9642 | |
9643 | { |
9644 | // Find the reference to the exception tracker corresponding to the preallocated exception, |
9645 | // starting the search from the current exception tracker (2nd arg of NULL specifies that). |
9646 | #if defined(WIN64EXCEPTIONS) |
9647 | PTR_ExceptionTracker pEHTracker = NULL; |
9648 | PTR_ExceptionTracker pPreviousEHTracker = NULL; |
9649 | |
9650 | #elif _TARGET_X86_ |
9651 | PTR_ExInfo pEHTracker = NULL; |
9652 | PTR_ExInfo pPreviousEHTracker = NULL; |
9653 | #else // !(_WIN64 || _TARGET_X86_) |
9654 | #error Unsupported platform |
9655 | #endif // _WIN64 |
9656 | |
9657 | if (fStartSearchFromPreviousTracker) |
9658 | { |
9659 | // Get the exception tracker previous to the current one |
9660 | pPreviousEHTracker = GetThread()->GetExceptionState()->GetCurrentExceptionTracker()->GetPreviousExceptionTracker(); |
9661 | |
9662 | // If there is no previous tracker to start from, then simply abort the search attempt. |
9663 | // If we couldnt find the exception tracker, then buckets are not available |
9664 | if (pPreviousEHTracker == NULL) |
9665 | { |
9666 | LOG((LF_EH, LL_INFO100, "GetWatsonBucketTrackerForPreallocatedException - Couldnt find the previous EHTracker to start the search from.\n" )); |
9667 | pWBTracker = NULL; |
9668 | goto done; |
9669 | } |
9670 | } |
9671 | |
9672 | pEHTracker = GetEHTrackerForPreallocatedException(gc.oPreAllocThrowable, pPreviousEHTracker); |
9673 | |
9674 | // If we couldnt find the exception tracker, then buckets are not available |
9675 | if (pEHTracker == NULL) |
9676 | { |
9677 | LOG((LF_EH, LL_INFO100, "GetWatsonBucketTrackerForPreallocatedException - Couldnt find EHTracker for preallocated exception object.\n" )); |
9678 | pWBTracker = NULL; |
9679 | goto done; |
9680 | } |
9681 | |
9682 | // Get the Watson Bucket Tracker from the exception tracker |
9683 | pWBTracker = pEHTracker->GetWatsonBucketTracker(); |
9684 | } |
9685 | doValidation: |
9686 | _ASSERTE(pWBTracker != NULL); |
9687 | |
9688 | // Incase of an OOM, we may not have an IP in the Watson bucket tracker. A scenario |
9689 | // would be default domain calling to AD 2 that calls into AD 3. |
9690 | // |
9691 | // AD 3 has an exception that is represented by a preallocated exception object. The |
9692 | // exception goes unhandled and reaches AD2/AD3 transition boundary. The bucketing details |
9693 | // from AD3 are copied to UETracker and once the exception is reraised in AD2, we will |
9694 | // enter SetupInitialThrowBucketingDetails to copy the bucketing details to the active |
9695 | // exception tracker. |
9696 | // |
9697 | // This copy operation could fail due to OOM and the active exception tracker in AD 2, |
9698 | // for the preallocated exception object, will not have any bucketing details. If the |
9699 | // exception remains unhandled in AD 2, then just before it reaches DefDomain/AD2 boundary, |
9700 | // we will attempt to capture the bucketing details in AppDomainTransitionExceptionFilter, |
9701 | // that will bring us here. |
9702 | // |
9703 | // In such a case, the active exception tracker will not have any bucket details for the |
9704 | // preallocated exception. In such a case, if the IP does not exist, we will return NULL |
9705 | // indicating that we couldnt find the Watson bucket tracker, since returning a tracker |
9706 | // that does not have any bucketing details will be of no use to the caller. |
9707 | if (pWBTracker->RetrieveWatsonBucketIp() != NULL) |
9708 | { |
9709 | // Check if the buckets exist or not.. |
9710 | PTR_VOID pBuckets = pWBTracker->RetrieveWatsonBuckets(); |
9711 | |
9712 | // If they dont exist and we have been asked to collect them, |
9713 | // then do so. |
9714 | if (pBuckets == NULL) |
9715 | { |
9716 | if (fCaptureBucketsIfNotPresent) |
9717 | { |
9718 | pWBTracker->CaptureUnhandledInfoForWatson(TypeOfReportedError::UnhandledException, GetThread(), &gc.oPreAllocThrowable); |
9719 | |
9720 | // Check if we have the buckets now |
9721 | if (pWBTracker->RetrieveWatsonBuckets() != NULL) |
9722 | { |
9723 | LOG((LF_EH, LL_INFO100, "GetWatsonBucketTrackerForPreallocatedException - Captured watson buckets for preallocated exception object.\n" )); |
9724 | } |
9725 | else |
9726 | { |
9727 | LOG((LF_EH, LL_INFO100, "GetWatsonBucketTrackerForPreallocatedException - Unable to capture watson buckets for preallocated exception object due to OOM.\n" )); |
9728 | } |
9729 | } |
9730 | else |
9731 | { |
9732 | LOG((LF_EH, LL_INFO100, "GetWatsonBucketTrackerForPreallocatedException - Found IP but no buckets for preallocated exception object.\n" )); |
9733 | } |
9734 | } |
9735 | else |
9736 | { |
9737 | LOG((LF_EH, LL_INFO100, "GetWatsonBucketTrackerForPreallocatedException - Buckets already exist for preallocated exception object.\n" )); |
9738 | } |
9739 | } |
9740 | else |
9741 | { |
9742 | LOG((LF_EH, LL_INFO100, "GetWatsonBucketTrackerForPreallocatedException - Returning NULL EHWatsonBucketTracker since bucketing IP does not exist. This is likely due to an earlier OOM.\n" )); |
9743 | pWBTracker = NULL; |
9744 | } |
9745 | |
9746 | done:; |
9747 | |
9748 | GCPROTECT_END(); |
9749 | |
9750 | // Return the Watson bucket tracker |
9751 | return pWBTracker; |
9752 | #else // DACCESS_COMPILE |
9753 | return NULL; |
9754 | #endif // !DACCESS_COMPILE |
9755 | } |
9756 | |
9757 | // Given an exception object, this function will attempt to look up |
9758 | // the watson buckets for it and set them up against the thread |
9759 | // for use by FailFast mechanism. |
9760 | // Return TRUE when it succeeds or Waston is disabled on CoreCLR |
9761 | // Return FALSE when refException neither has buckets nor has inner exception |
9762 | BOOL SetupWatsonBucketsForFailFast(EXCEPTIONREF refException) |
9763 | { |
9764 | BOOL fResult = TRUE; |
9765 | |
9766 | #ifndef DACCESS_COMPILE |
9767 | // On CoreCLR, Watson may not be enabled. Thus, we should |
9768 | // skip this. |
9769 | if (!IsWatsonEnabled()) |
9770 | { |
9771 | return fResult; |
9772 | } |
9773 | |
9774 | CONTRACTL |
9775 | { |
9776 | GC_TRIGGERS; |
9777 | MODE_ANY; |
9778 | NOTHROW; |
9779 | PRECONDITION(GetThread() != NULL); |
9780 | PRECONDITION(refException != NULL); |
9781 | PRECONDITION(IsWatsonEnabled()); |
9782 | } |
9783 | CONTRACTL_END; |
9784 | |
9785 | // Switch to COOP mode |
9786 | GCX_COOP(); |
9787 | |
9788 | struct |
9789 | { |
9790 | OBJECTREF refException; |
9791 | OBJECTREF oInnerMostExceptionThrowable; |
9792 | } gc; |
9793 | ZeroMemory(&gc, sizeof(gc)); |
9794 | GCPROTECT_BEGIN(gc); |
9795 | gc.refException = refException; |
9796 | |
9797 | Thread *pThread = GetThread(); |
9798 | |
9799 | // If we dont already have the bucketing details for the exception |
9800 | // being thrown, then get them. |
9801 | ThreadExceptionState *pExState = pThread->GetExceptionState(); |
9802 | |
9803 | // Check if the exception object is preallocated or not |
9804 | BOOL fIsPreallocatedException = CLRException::IsPreallocatedExceptionObject(gc.refException); |
9805 | |
9806 | // Get the WatsonBucketTracker where bucketing details will be copied to |
9807 | PTR_EHWatsonBucketTracker pUEWatsonBucketTracker = pExState->GetUEWatsonBucketTracker(); |
9808 | |
9809 | // Check if this is a thread abort exception of any kind. |
9810 | // See IsThrowableThreadAbortException implementation for details. |
9811 | BOOL fIsThreadAbortException = IsThrowableThreadAbortException(gc.refException); |
9812 | |
9813 | if (fIsPreallocatedException) |
9814 | { |
9815 | // If the exception being used to FailFast is preallocated, |
9816 | // then it cannot have any inner exception. Thus, try to |
9817 | // find the watson bucket tracker corresponding to this exception. |
9818 | // |
9819 | // Also, capture the buckets if we dont have them already. |
9820 | PTR_EHWatsonBucketTracker pTargetWatsonBucketTracker = GetWatsonBucketTrackerForPreallocatedException(gc.refException, TRUE); |
9821 | if ((pTargetWatsonBucketTracker != NULL) && (!fIsThreadAbortException)) |
9822 | { |
9823 | // Buckets are not captured proactively for preallocated exception objects. We only |
9824 | // save the IP in the watson bucket tracker (see SetupInitialThrowBucketingDetails for |
9825 | // details). |
9826 | // |
9827 | // Thus, if, say in DefDomain, a preallocated exception is thrown and we enter |
9828 | // the catch block and invoke the FailFast API with the reference to the preallocated |
9829 | // exception object, we will have the IP but not the buckets. In such a case, |
9830 | // capture the buckets before proceeding ahead. |
9831 | if (pTargetWatsonBucketTracker->RetrieveWatsonBuckets() == NULL) |
9832 | { |
9833 | LOG((LF_EH, LL_INFO100, "SetupWatsonBucketsForFailFast - Collecting watson bucket details for preallocated exception.\n" )); |
9834 | pTargetWatsonBucketTracker->CaptureUnhandledInfoForWatson(TypeOfReportedError::UnhandledException, pThread, &gc.refException); |
9835 | } |
9836 | |
9837 | // Copy the buckets to the UE tracker |
9838 | pUEWatsonBucketTracker->ClearWatsonBucketDetails(); |
9839 | pUEWatsonBucketTracker->CopyEHWatsonBucketTracker(*pTargetWatsonBucketTracker); |
9840 | if (pUEWatsonBucketTracker->RetrieveWatsonBuckets() != NULL) |
9841 | { |
9842 | LOG((LF_EH, LL_INFO100, "SetupWatsonBucketsForFailFast - Collected watson bucket details for preallocated exception in UE tracker.\n" )); |
9843 | } |
9844 | else |
9845 | { |
9846 | // If we are here, then the copy operation above had an OOM, resulting |
9847 | // in no buckets for us. |
9848 | LOG((LF_EH, LL_INFO100, "SetupWatsonBucketsForFailFast - Unable to collect watson bucket details for preallocated exception due to out of memory.\n" )); |
9849 | |
9850 | // Make sure the tracker is clean. |
9851 | pUEWatsonBucketTracker->ClearWatsonBucketDetails(); |
9852 | } |
9853 | } |
9854 | else |
9855 | { |
9856 | // For TAE, UE watson bucket tracker is the one that tracks the buckets. It *may* |
9857 | // not have the bucket details if FailFast is being invoked from outside the |
9858 | // managed EH clauses. But if invoked from within the active EH clause for the exception, |
9859 | // UETracker will have the bucketing details (see SetupInitialThrowBucketingDetails for details). |
9860 | if (fIsThreadAbortException && (pUEWatsonBucketTracker->RetrieveWatsonBuckets() != NULL)) |
9861 | { |
9862 | _ASSERTE(pTargetWatsonBucketTracker == pUEWatsonBucketTracker); |
9863 | LOG((LF_EH, LL_INFO100, "SetupWatsonBucketsForFailFast - UE tracker already watson bucket details for preallocated thread abort exception.\n" )); |
9864 | } |
9865 | else |
9866 | { |
9867 | LOG((LF_EH, LL_INFO100, "SetupWatsonBucketsForFailFast - Unable to find bucket details for preallocated %s exception.\n" , |
9868 | fIsThreadAbortException?"rude/thread abort" :"" )); |
9869 | |
9870 | // Make sure the tracker is clean. |
9871 | pUEWatsonBucketTracker->ClearWatsonBucketDetails(); |
9872 | } |
9873 | } |
9874 | } |
9875 | else |
9876 | { |
9877 | // Since the exception object is not preallocated, start by assuming |
9878 | // that we dont need to check it for watson buckets |
9879 | BOOL fCheckThrowableForWatsonBuckets = FALSE; |
9880 | |
9881 | // Get the innermost exception object (if any) |
9882 | gc.oInnerMostExceptionThrowable = ((EXCEPTIONREF)gc.refException)->GetBaseException(); |
9883 | if (gc.oInnerMostExceptionThrowable != NULL) |
9884 | { |
9885 | if (CLRException::IsPreallocatedExceptionObject(gc.oInnerMostExceptionThrowable)) |
9886 | { |
9887 | // If the inner most exception being used to FailFast is preallocated, |
9888 | // try to find the watson bucket tracker corresponding to it. |
9889 | // |
9890 | // Also, capture the buckets if we dont have them already. |
9891 | PTR_EHWatsonBucketTracker pTargetWatsonBucketTracker = |
9892 | GetWatsonBucketTrackerForPreallocatedException(gc.oInnerMostExceptionThrowable, TRUE); |
9893 | |
9894 | if (pTargetWatsonBucketTracker != NULL) |
9895 | { |
9896 | if (pTargetWatsonBucketTracker->RetrieveWatsonBuckets() == NULL) |
9897 | { |
9898 | LOG((LF_EH, LL_INFO1000, "SetupWatsonBucketsForFailFast - Capturing Watson bucket details for preallocated inner exception.\n" )); |
9899 | pTargetWatsonBucketTracker->CaptureUnhandledInfoForWatson(TypeOfReportedError::UnhandledException, pThread, &gc.oInnerMostExceptionThrowable); |
9900 | } |
9901 | |
9902 | // Copy the details to the UE tracker |
9903 | pUEWatsonBucketTracker->ClearWatsonBucketDetails(); |
9904 | pUEWatsonBucketTracker->CopyEHWatsonBucketTracker(*pTargetWatsonBucketTracker); |
9905 | if (pUEWatsonBucketTracker->RetrieveWatsonBuckets() != NULL) |
9906 | { |
9907 | LOG((LF_EH, LL_INFO1000, "SetupWatsonBucketsForFailFast - Watson bucket details collected for preallocated inner exception.\n" )); |
9908 | } |
9909 | else |
9910 | { |
9911 | // If we are here, copy operation failed likely due to OOM |
9912 | LOG((LF_EH, LL_INFO1000, "SetupWatsonBucketsForFailFast - Unable to copy watson bucket details for preallocated inner exception.\n" )); |
9913 | |
9914 | // Keep the UETracker clean |
9915 | pUEWatsonBucketTracker->ClearWatsonBucketDetails(); |
9916 | } |
9917 | } |
9918 | else |
9919 | { |
9920 | LOG((LF_EH, LL_INFO1000, "SetupWatsonBucketsForFailFast - Unable to find bucket details for preallocated inner exception.\n" )); |
9921 | |
9922 | // Keep the UETracker clean |
9923 | pUEWatsonBucketTracker->ClearWatsonBucketDetails(); |
9924 | |
9925 | // Since we couldnt find the watson bucket tracker for the the inner most exception, |
9926 | // try to look for the buckets in the throwable. |
9927 | fCheckThrowableForWatsonBuckets = TRUE; |
9928 | } |
9929 | } |
9930 | else |
9931 | { |
9932 | // Inner most exception is not preallocated. |
9933 | // |
9934 | // If it has the IP but not the buckets, then capture them now. |
9935 | if ((((EXCEPTIONREF)gc.oInnerMostExceptionThrowable)->AreWatsonBucketsPresent() == FALSE) && |
9936 | (((EXCEPTIONREF)gc.oInnerMostExceptionThrowable)->IsIPForWatsonBucketsPresent())) |
9937 | { |
9938 | SetupWatsonBucketsForNonPreallocatedExceptions(gc.oInnerMostExceptionThrowable); |
9939 | } |
9940 | |
9941 | // If it has the buckets, copy them over to the current Watson bucket tracker |
9942 | if (((EXCEPTIONREF)gc.oInnerMostExceptionThrowable)->AreWatsonBucketsPresent()) |
9943 | { |
9944 | pUEWatsonBucketTracker->ClearWatsonBucketDetails(); |
9945 | pUEWatsonBucketTracker->CopyBucketsFromThrowable(gc.oInnerMostExceptionThrowable); |
9946 | if (pUEWatsonBucketTracker->RetrieveWatsonBuckets() != NULL) |
9947 | { |
9948 | LOG((LF_EH, LL_INFO1000, "SetupWatsonBucketsForFailFast - Got watson buckets from regular innermost exception.\n" )); |
9949 | } |
9950 | else |
9951 | { |
9952 | // Copy operation can fail due to OOM |
9953 | LOG((LF_EH, LL_INFO1000, "SetupWatsonBucketsForFailFast - Unable to copy watson buckets from regular innermost exception, likely due to OOM.\n" )); |
9954 | } |
9955 | } |
9956 | else |
9957 | { |
9958 | // Since the inner most exception didnt have the buckets, |
9959 | // try to look for them in the throwable |
9960 | fCheckThrowableForWatsonBuckets = TRUE; |
9961 | LOG((LF_EH, LL_INFO1000, "SetupWatsonBucketsForFailFast - Neither exception object nor its inner exception has watson buckets.\n" )); |
9962 | } |
9963 | } |
9964 | } |
9965 | else |
9966 | { |
9967 | // There is no innermost exception - try to look for buckets |
9968 | // in the throwable |
9969 | fCheckThrowableForWatsonBuckets = TRUE; |
9970 | LOG((LF_EH, LL_INFO1000, "SetupWatsonBucketsForFailFast - Innermost exception does not exist\n" )); |
9971 | } |
9972 | |
9973 | if (fCheckThrowableForWatsonBuckets) |
9974 | { |
9975 | // Since we have not found buckets anywhere, try to look for them |
9976 | // in the throwable. |
9977 | if ((((EXCEPTIONREF)gc.refException)->AreWatsonBucketsPresent() == FALSE) && |
9978 | (((EXCEPTIONREF)gc.refException)->IsIPForWatsonBucketsPresent())) |
9979 | { |
9980 | // Capture the buckets from the IP. |
9981 | SetupWatsonBucketsForNonPreallocatedExceptions(gc.refException); |
9982 | } |
9983 | |
9984 | if (((EXCEPTIONREF)gc.refException)->AreWatsonBucketsPresent()) |
9985 | { |
9986 | // Copy the buckets to the current watson bucket tracker |
9987 | pUEWatsonBucketTracker->ClearWatsonBucketDetails(); |
9988 | pUEWatsonBucketTracker->CopyBucketsFromThrowable(gc.refException); |
9989 | if (pUEWatsonBucketTracker->RetrieveWatsonBuckets() != NULL) |
9990 | { |
9991 | LOG((LF_EH, LL_INFO1000, "SetupWatsonBucketsForFailFast - Watson buckets copied from the exception object.\n" )); |
9992 | } |
9993 | else |
9994 | { |
9995 | LOG((LF_EH, LL_INFO1000, "SetupWatsonBucketsForFailFast - Unable to copy Watson buckets copied from the exception object, likely due to OOM.\n" )); |
9996 | } |
9997 | } |
9998 | else |
9999 | { |
10000 | fResult = FALSE; |
10001 | LOG((LF_EH, LL_INFO1000, "SetupWatsonBucketsForFailFast - Exception object neither has buckets nor has inner exception.\n" )); |
10002 | } |
10003 | } |
10004 | } |
10005 | |
10006 | GCPROTECT_END(); |
10007 | |
10008 | #endif // !DACCESS_COMPILE |
10009 | |
10010 | return fResult; |
10011 | } |
10012 | |
10013 | // This function will setup the bucketing details in the exception |
10014 | // tracker or the throwable, if they are not already setup. |
10015 | // |
10016 | // This is called when an exception is thrown (or raised): |
10017 | // |
10018 | // 1) from outside the confines of managed EH clauses, OR |
10019 | // 2) from within the confines of managed EH clauses but the |
10020 | // exception does not have bucketing details with it, OR |
10021 | // 3) When an exception is reraised at AD transition boundary |
10022 | // after it has been marshalled over to the returning AD. |
10023 | void SetupInitialThrowBucketDetails(UINT_PTR adjustedIp) |
10024 | { |
10025 | #ifndef DACCESS_COMPILE |
10026 | |
10027 | // On CoreCLR, Watson may not be enabled. Thus, we should |
10028 | // skip this. |
10029 | if (!IsWatsonEnabled()) |
10030 | { |
10031 | return; |
10032 | } |
10033 | |
10034 | CONTRACTL |
10035 | { |
10036 | GC_TRIGGERS; |
10037 | MODE_ANY; |
10038 | NOTHROW; |
10039 | PRECONDITION(GetThread() != NULL); |
10040 | PRECONDITION(!(GetThread()->GetExceptionState()->GetFlags()->GotWatsonBucketDetails())); |
10041 | PRECONDITION(adjustedIp != NULL); |
10042 | PRECONDITION(IsWatsonEnabled()); |
10043 | } |
10044 | CONTRACTL_END; |
10045 | |
10046 | Thread *pThread = GetThread(); |
10047 | |
10048 | // If we dont already have the bucketing details for the exception |
10049 | // being thrown, then get them. |
10050 | ThreadExceptionState *pExState = pThread->GetExceptionState(); |
10051 | |
10052 | // Ensure that the exception tracker exists |
10053 | _ASSERTE(pExState->GetCurrentExceptionTracker() != NULL); |
10054 | |
10055 | // Switch to COOP mode |
10056 | GCX_COOP(); |
10057 | |
10058 | // Get the throwable for the exception being thrown |
10059 | struct |
10060 | { |
10061 | OBJECTREF oCurrentThrowable; |
10062 | OBJECTREF oInnerMostExceptionThrowable; |
10063 | } gc; |
10064 | ZeroMemory(&gc, sizeof(gc)); |
10065 | |
10066 | GCPROTECT_BEGIN(gc); |
10067 | |
10068 | gc.oCurrentThrowable = pExState->GetThrowable(); |
10069 | |
10070 | // Check if the exception object is preallocated or not |
10071 | BOOL fIsPreallocatedException = CLRException::IsPreallocatedExceptionObject(gc.oCurrentThrowable); |
10072 | |
10073 | // Get the WatsonBucketTracker for the current exception |
10074 | PTR_EHWatsonBucketTracker pWatsonBucketTracker = pExState->GetCurrentExceptionTracker()->GetWatsonBucketTracker(); |
10075 | |
10076 | // Get the innermost exception object (if any) |
10077 | gc.oInnerMostExceptionThrowable = ((EXCEPTIONREF)gc.oCurrentThrowable)->GetBaseException(); |
10078 | |
10079 | // By default, assume that no watson bucketing details are available and inner exception |
10080 | // is not preallocated |
10081 | BOOL fAreBucketingDetailsPresent = FALSE; |
10082 | BOOL fIsInnerExceptionPreallocated = FALSE; |
10083 | |
10084 | // Check if this is a thread abort exception of any kind. See IsThrowableThreadAbortException implementation for details. |
10085 | // We shouldnt use the thread state as well to determine if it is a TAE since, in cases like throwing a cached exception |
10086 | // as part of type initialization failure, we could throw a TAE but the thread will not be in abort state (which is expected). |
10087 | BOOL fIsThreadAbortException = IsThrowableThreadAbortException(gc.oCurrentThrowable); |
10088 | |
10089 | // If we are here, then this was a new exception raised |
10090 | // from outside the managed EH clauses (fault/finally/catch). |
10091 | // |
10092 | // The throwable *may* have the bucketing details already |
10093 | // if this exception was raised when it was crossing over |
10094 | // an AD transition boundary. Those are stored in UE watson bucket |
10095 | // tracker by AppDomainTransitionExceptionFilter. |
10096 | if (fIsPreallocatedException) |
10097 | { |
10098 | PTR_EHWatsonBucketTracker pUEWatsonBucketTracker = pExState->GetUEWatsonBucketTracker(); |
10099 | fAreBucketingDetailsPresent = ((pUEWatsonBucketTracker->RetrieveWatsonBucketIp() != NULL) && |
10100 | (pUEWatsonBucketTracker->RetrieveWatsonBuckets() != NULL)); |
10101 | |
10102 | // If they are present, copy them over to the watson tracker for the exception |
10103 | // being processed. |
10104 | if (fAreBucketingDetailsPresent) |
10105 | { |
10106 | #ifdef _DEBUG |
10107 | // Under OOM scenarios, its possible that when we are raising a threadabort, |
10108 | // the throwable may get converted to preallocated OOM object when RaiseTheExceptionInternalOnly |
10109 | // invokes Thread::SafeSetLastThrownObject. We check if this is the current case and use it in |
10110 | // our validation below. |
10111 | BOOL fIsPreallocatedOOMExceptionForTA = FALSE; |
10112 | if ((!fIsThreadAbortException) && pUEWatsonBucketTracker->CapturedForThreadAbort()) |
10113 | { |
10114 | fIsPreallocatedOOMExceptionForTA = (gc.oCurrentThrowable == CLRException::GetPreallocatedOutOfMemoryException()); |
10115 | if (fIsPreallocatedOOMExceptionForTA) |
10116 | { |
10117 | LOG((LF_EH, LL_INFO100, "SetupInitialThrowBucketDetails - Got preallocated OOM throwable for buckets captured for thread abort.\n" )); |
10118 | } |
10119 | } |
10120 | #endif // _DEBUG |
10121 | // These should have been captured at AD transition OR |
10122 | // could be bucketing details of preallocated [rude] thread abort exception. |
10123 | _ASSERTE(pUEWatsonBucketTracker->CapturedAtADTransition() || |
10124 | ((fIsThreadAbortException || fIsPreallocatedOOMExceptionForTA) && pUEWatsonBucketTracker->CapturedForThreadAbort())); |
10125 | |
10126 | if (!fIsThreadAbortException) |
10127 | { |
10128 | // The watson bucket tracker for the exceptiong being raised should be empty at this point |
10129 | // since we are here because of a cross AD reraise of the original exception. |
10130 | _ASSERTE((pWatsonBucketTracker->RetrieveWatsonBucketIp() == NULL) && (pWatsonBucketTracker->RetrieveWatsonBuckets() == NULL)); |
10131 | |
10132 | // Copy the buckets over to it |
10133 | pWatsonBucketTracker->CopyEHWatsonBucketTracker(*(pUEWatsonBucketTracker)); |
10134 | if (pWatsonBucketTracker->RetrieveWatsonBuckets() == NULL) |
10135 | { |
10136 | // If we dont have buckets after the copy operation, its due to us running out of |
10137 | // memory. |
10138 | LOG((LF_EH, LL_INFO100, "SetupInitialThrowBucketDetails - Unable to copy watson buckets from cross AD rethrow, likely due to out of memory.\n" )); |
10139 | } |
10140 | else |
10141 | { |
10142 | LOG((LF_EH, LL_INFO100, "SetupInitialThrowBucketDetails - Copied watson buckets from cross AD rethrow.\n" )); |
10143 | } |
10144 | } |
10145 | else |
10146 | { |
10147 | // Thread abort watson bucket details are already present in the |
10148 | // UE watson bucket tracker. |
10149 | LOG((LF_EH, LL_INFO100, "SetupInitialThrowBucketDetails - Already have watson buckets for preallocated thread abort reraise.\n" )); |
10150 | } |
10151 | } |
10152 | else if (fIsThreadAbortException) |
10153 | { |
10154 | // This is a preallocated thread abort exception. |
10155 | UINT_PTR ip = pUEWatsonBucketTracker->RetrieveWatsonBucketIp(); |
10156 | if (ip != NULL) |
10157 | { |
10158 | // Since we have the IP, assert that this was the one setup |
10159 | // for ThreadAbort. This is for the reraise scenario where |
10160 | // the original exception was non-preallocated TA but the |
10161 | // reraise resulted in preallocated TA. |
10162 | // |
10163 | // In this case, we will update the ip to be used as the |
10164 | // one we have. The control flow below will automatically |
10165 | // endup using it. |
10166 | _ASSERTE(pUEWatsonBucketTracker->CapturedForThreadAbort()); |
10167 | adjustedIp = ip; |
10168 | LOG((LF_EH, LL_INFO100, "SetupInitialThrowBucketDetails - Setting an existing IP (%p) to be used for capturing buckets for preallocated thread abort.\n" , ip)); |
10169 | goto phase1; |
10170 | } |
10171 | } |
10172 | |
10173 | if (!fAreBucketingDetailsPresent || !fIsThreadAbortException) |
10174 | { |
10175 | // Clear the UE Watson bucket tracker so that its usable |
10176 | // in future. We dont clear this for ThreadAbort since |
10177 | // the UE watson bucket tracker carries bucketing details |
10178 | // for the same, unless the UE tracker is not containing them |
10179 | // already. |
10180 | pUEWatsonBucketTracker->ClearWatsonBucketDetails(); |
10181 | } |
10182 | } |
10183 | else |
10184 | { |
10185 | // The exception object is not preallocated |
10186 | fAreBucketingDetailsPresent = ((EXCEPTIONREF)gc.oCurrentThrowable)->AreWatsonBucketsPresent(); |
10187 | if (!fAreBucketingDetailsPresent) |
10188 | { |
10189 | // If buckets are not present, check if the bucketing IP is present. |
10190 | fAreBucketingDetailsPresent = ((EXCEPTIONREF)gc.oCurrentThrowable)->IsIPForWatsonBucketsPresent(); |
10191 | } |
10192 | |
10193 | // If throwable does not have buckets and this is a thread abort exception, |
10194 | // then this maybe a reraise of the original thread abort. |
10195 | // |
10196 | // We can also be here if an exception was caught at AppDomain transition and |
10197 | // in the returning domain, a non-preallocated TAE was raised. In such a case, |
10198 | // the UE tracker flags could indicate the exception is from AD transition. |
10199 | // This is similar to preallocated case above. |
10200 | // |
10201 | // Check the UE Watson bucket tracker if it has the buckets and if it does, |
10202 | // copy them over to the current throwable. |
10203 | if (!fAreBucketingDetailsPresent && fIsThreadAbortException) |
10204 | { |
10205 | PTR_EHWatsonBucketTracker pUEWatsonBucketTracker = pExState->GetUEWatsonBucketTracker(); |
10206 | UINT_PTR ip = pUEWatsonBucketTracker->RetrieveWatsonBucketIp(); |
10207 | if (ip != NULL) |
10208 | { |
10209 | // Confirm that we had the buckets captured for thread abort |
10210 | _ASSERTE(pUEWatsonBucketTracker->CapturedForThreadAbort() || pUEWatsonBucketTracker->CapturedAtADTransition()); |
10211 | |
10212 | if (pUEWatsonBucketTracker->RetrieveWatsonBuckets() != NULL) |
10213 | { |
10214 | // Copy the buckets to the current throwable - CopyWatsonBucketsToThrowable |
10215 | // can throw in OOM. However, since the current function is called as part of |
10216 | // setting up the stack trace, where we bail out incase of OOM, we will do |
10217 | // no different here as well. |
10218 | BOOL fCopiedBuckets = TRUE; |
10219 | EX_TRY |
10220 | { |
10221 | CopyWatsonBucketsToThrowable(pUEWatsonBucketTracker->RetrieveWatsonBuckets()); |
10222 | _ASSERTE(((EXCEPTIONREF)gc.oCurrentThrowable)->AreWatsonBucketsPresent()); |
10223 | } |
10224 | EX_CATCH |
10225 | { |
10226 | fCopiedBuckets = FALSE; |
10227 | } |
10228 | EX_END_CATCH(SwallowAllExceptions); |
10229 | |
10230 | if (fCopiedBuckets) |
10231 | { |
10232 | // Since the throwable has the buckets, set the flag that indicates so |
10233 | fAreBucketingDetailsPresent = TRUE; |
10234 | LOG((LF_EH, LL_INFO100, "SetupInitialThrowBucketDetails - Setup watson buckets for thread abort reraise.\n" )); |
10235 | } |
10236 | } |
10237 | else |
10238 | { |
10239 | // Copy the faulting IP from the UE tracker to the exception object. This was setup in COMPlusCheckForAbort |
10240 | // for non-preallocated exceptions. |
10241 | ((EXCEPTIONREF)gc.oCurrentThrowable)->SetIPForWatsonBuckets(ip); |
10242 | fAreBucketingDetailsPresent = TRUE; |
10243 | LOG((LF_EH, LL_INFO100, "SetupInitialThrowBucketDetails - Setup watson bucket IP for thread abort reraise.\n" )); |
10244 | } |
10245 | } |
10246 | else |
10247 | { |
10248 | // Clear the UE Watson bucket tracker so that its usable |
10249 | // in future. |
10250 | pUEWatsonBucketTracker->ClearWatsonBucketDetails(); |
10251 | LOG((LF_EH, LL_INFO100, "SetupInitialThrowBucketDetails - Didnt find watson buckets for thread abort - likely being raised.\n" )); |
10252 | } |
10253 | } |
10254 | } |
10255 | |
10256 | phase1: |
10257 | if (fAreBucketingDetailsPresent) |
10258 | { |
10259 | // Since we already have the buckets, simply bail out |
10260 | LOG((LF_EH, LL_INFO100, "SetupInitialThrowBucketDetails - Already had watson ip/buckets.\n" )); |
10261 | goto done; |
10262 | } |
10263 | |
10264 | // Check if an inner most exception exists and if it does, examine |
10265 | // it for watson bucketing details. |
10266 | if (gc.oInnerMostExceptionThrowable != NULL) |
10267 | { |
10268 | // Preallocated exception objects do not have inner exception objects. |
10269 | // Thus, if we are here, then the current throwable cannot be |
10270 | // a preallocated exception object. |
10271 | _ASSERTE(!fIsPreallocatedException); |
10272 | |
10273 | fIsInnerExceptionPreallocated = CLRException::IsPreallocatedExceptionObject(gc.oInnerMostExceptionThrowable); |
10274 | |
10275 | // If we are here, then this was a "throw" with inner exception |
10276 | // outside of any managed EH clauses. |
10277 | // |
10278 | // If the inner exception object is preallocated, then we will need to create the |
10279 | // watson buckets since we are outside the managed EH clauses with no exception tracking |
10280 | // information relating to the inner exception. |
10281 | // |
10282 | // But if the inner exception object was not preallocated, create new watson buckets |
10283 | // only if inner exception does not have them. |
10284 | if (fIsInnerExceptionPreallocated) |
10285 | { |
10286 | fAreBucketingDetailsPresent = FALSE; |
10287 | } |
10288 | else |
10289 | { |
10290 | // Do we have either the IP for Watson buckets or the buckets themselves? |
10291 | fAreBucketingDetailsPresent = (((EXCEPTIONREF)gc.oInnerMostExceptionThrowable)->AreWatsonBucketsPresent() || |
10292 | ((EXCEPTIONREF)gc.oInnerMostExceptionThrowable)->IsIPForWatsonBucketsPresent()); |
10293 | } |
10294 | } |
10295 | |
10296 | if (!fAreBucketingDetailsPresent) |
10297 | { |
10298 | // Collect the bucketing details since they are not already present |
10299 | pWatsonBucketTracker->SaveIpForWatsonBucket(adjustedIp); |
10300 | |
10301 | if (!fIsPreallocatedException || fIsThreadAbortException) |
10302 | { |
10303 | if (!fIsPreallocatedException) |
10304 | { |
10305 | // Save the IP for Watson bucketing in the exception object for non-preallocated exception |
10306 | // objects |
10307 | ((EXCEPTIONREF)gc.oCurrentThrowable)->SetIPForWatsonBuckets(adjustedIp); |
10308 | |
10309 | // Save the IP in the UE tracker as well for TAE if an abort is in progress |
10310 | // since when we attempt reraise, the exception object is not available. Otherwise, |
10311 | // treat the exception like a regular non-preallocated exception and not do anything else. |
10312 | if (fIsThreadAbortException && pThread->IsAbortInitiated()) |
10313 | { |
10314 | PTR_EHWatsonBucketTracker pUEWatsonBucketTracker = pExState->GetUEWatsonBucketTracker(); |
10315 | |
10316 | pUEWatsonBucketTracker->ClearWatsonBucketDetails(); |
10317 | pUEWatsonBucketTracker->SaveIpForWatsonBucket(adjustedIp); |
10318 | |
10319 | // Set the flag that we captured the IP for Thread abort |
10320 | DEBUG_STMT(pUEWatsonBucketTracker->SetCapturedForThreadAbort()); |
10321 | LOG((LF_EH, LL_INFO100, "SetupInitialThrowBucketDetails - Saved bucket IP for initial thread abort raise.\n" )); |
10322 | } |
10323 | } |
10324 | else |
10325 | { |
10326 | // Create the buckets proactively for preallocated threadabort exception |
10327 | pWatsonBucketTracker->CaptureUnhandledInfoForWatson(TypeOfReportedError::UnhandledException, pThread, &gc.oCurrentThrowable); |
10328 | PTR_VOID pUnmanagedBuckets = pWatsonBucketTracker->RetrieveWatsonBuckets(); |
10329 | if(pUnmanagedBuckets != NULL) |
10330 | { |
10331 | // Copy the details over to the UE Watson bucket tracker so that we can use them if the exception |
10332 | // is "reraised" after invoking the catch block. |
10333 | // |
10334 | // Since we can be here for preallocated threadabort exception when UE Tracker is simply |
10335 | // carrying the IP (that has been copied to pWatsonBucketTracker and buckets captured for it), |
10336 | // we will need to clear UE tracker so that we can copy over the captured buckets. |
10337 | PTR_EHWatsonBucketTracker pUEWatsonBucketTracker = pExState->GetUEWatsonBucketTracker(); |
10338 | pUEWatsonBucketTracker->ClearWatsonBucketDetails(); |
10339 | |
10340 | // Copy over the buckets from the current tracker that captured them. |
10341 | pUEWatsonBucketTracker->CopyEHWatsonBucketTracker(*(pWatsonBucketTracker)); |
10342 | |
10343 | // Buckets should be present now (unless the copy operation had an OOM) |
10344 | if (pUEWatsonBucketTracker->RetrieveWatsonBuckets() != NULL) |
10345 | { |
10346 | // Set the flag that we captured buckets for Thread abort |
10347 | DEBUG_STMT(pUEWatsonBucketTracker->SetCapturedForThreadAbort()); |
10348 | LOG((LF_EH, LL_INFO100, "SetupInitialThrowBucketDetails - Saved buckets for Watson Bucketing for initial thread abort raise.\n" )); |
10349 | } |
10350 | else |
10351 | { |
10352 | // If we are here, then the bucket copy operation (above) failed due to OOM. |
10353 | LOG((LF_EH, LL_INFO100, "SetupInitialThrowBucketDetails - Unable to save buckets for Watson Bucketing for initial thread abort raise, likely due to OOM.\n" )); |
10354 | pUEWatsonBucketTracker->ClearWatsonBucketDetails(); |
10355 | } |
10356 | } |
10357 | else |
10358 | { |
10359 | // Watson helper function can bail out on us under OOM scenarios and return a NULL. |
10360 | // We cannot do much in such a case. |
10361 | LOG((LF_EH, LL_INFO100, "SetupInitialThrowBucketDetails - No buckets were captured and returned to us for initial thread abort raise. Likely encountered an OOM.\n" )); |
10362 | } |
10363 | |
10364 | // Clear the buckets since we no longer need them |
10365 | pWatsonBucketTracker->ClearWatsonBucketDetails(); |
10366 | } |
10367 | } |
10368 | else |
10369 | { |
10370 | // We have already saved the throw site IP for bucketing the non-ThreadAbort preallocated exceptions |
10371 | LOG((LF_EH, LL_INFO100, "SetupInitialThrowBucketDetails - Saved IP (%p) for Watson Bucketing for a preallocated exception\n" , adjustedIp)); |
10372 | } |
10373 | } |
10374 | else |
10375 | { |
10376 | // The inner exception object should be having either the IP for watson bucketing or the buckets themselves. |
10377 | // We shall copy over, whatever is available, to the current exception object. |
10378 | _ASSERTE(gc.oInnerMostExceptionThrowable != NULL); |
10379 | _ASSERTE(((EXCEPTIONREF)gc.oInnerMostExceptionThrowable)->AreWatsonBucketsPresent() || |
10380 | ((EXCEPTIONREF)gc.oInnerMostExceptionThrowable)->IsIPForWatsonBucketsPresent()); |
10381 | |
10382 | if (((EXCEPTIONREF)gc.oInnerMostExceptionThrowable)->AreWatsonBucketsPresent()) |
10383 | { |
10384 | EX_TRY |
10385 | { |
10386 | // Copy the bucket details from innermost exception to the current exception object. |
10387 | CopyWatsonBucketsFromThrowableToCurrentThrowable(gc.oInnerMostExceptionThrowable); |
10388 | } |
10389 | EX_CATCH |
10390 | { |
10391 | } |
10392 | EX_END_CATCH(SwallowAllExceptions); |
10393 | |
10394 | LOG((LF_EH, LL_INFO100, "SetupInitialThrowBucketDetails - Copied watson bucket details from the innermost exception\n" )); |
10395 | } |
10396 | else |
10397 | { |
10398 | // Copy the IP to the current exception object |
10399 | ((EXCEPTIONREF)gc.oCurrentThrowable)->SetIPForWatsonBuckets(((EXCEPTIONREF)gc.oInnerMostExceptionThrowable)->GetIPForWatsonBuckets()); |
10400 | LOG((LF_EH, LL_INFO100, "SetupInitialThrowBucketDetails - Copied watson bucket IP from the innermost exception\n" )); |
10401 | } |
10402 | } |
10403 | |
10404 | done: |
10405 | // Set the flag that we have got the bucketing details |
10406 | pExState->GetFlags()->SetGotWatsonBucketDetails(); |
10407 | |
10408 | GCPROTECT_END(); |
10409 | |
10410 | #endif // !DACCESS_COMPILE |
10411 | } |
10412 | |
10413 | // This function is a wrapper to copy the watson bucket byte[] from the specified |
10414 | // throwable to the current throwable. |
10415 | void CopyWatsonBucketsFromThrowableToCurrentThrowable(OBJECTREF oThrowableFrom) |
10416 | { |
10417 | #ifndef DACCESS_COMPILE |
10418 | |
10419 | CONTRACTL |
10420 | { |
10421 | GC_TRIGGERS; |
10422 | MODE_COOPERATIVE; |
10423 | THROWS; |
10424 | PRECONDITION(oThrowableFrom != NULL); |
10425 | PRECONDITION(!CLRException::IsPreallocatedExceptionObject(oThrowableFrom)); |
10426 | PRECONDITION(((EXCEPTIONREF)oThrowableFrom)->AreWatsonBucketsPresent()); |
10427 | PRECONDITION(IsWatsonEnabled()); |
10428 | } |
10429 | CONTRACTL_END; |
10430 | |
10431 | struct |
10432 | { |
10433 | OBJECTREF oThrowableFrom; |
10434 | } _gc; |
10435 | |
10436 | ZeroMemory(&_gc, sizeof(_gc)); |
10437 | GCPROTECT_BEGIN(_gc); |
10438 | _gc.oThrowableFrom = oThrowableFrom; |
10439 | |
10440 | // Copy the watson buckets to the current throwable by NOT passing |
10441 | // the second argument that will default to NULL. |
10442 | // |
10443 | // CopyWatsonBucketsBetweenThrowables will pass that NULL to |
10444 | // CopyWatsonBucketsToThrowables that will make it copy the buckets |
10445 | // to the current throwable. |
10446 | CopyWatsonBucketsBetweenThrowables(_gc.oThrowableFrom); |
10447 | |
10448 | GCPROTECT_END(); |
10449 | |
10450 | #endif // !DACCESS_COMPILE |
10451 | } |
10452 | |
10453 | // This function will copy the watson bucket byte[] from the source |
10454 | // throwable to the destination throwable. |
10455 | // |
10456 | // If the destination throwable is NULL, it will result in the buckets |
10457 | // being copied to the current throwable. |
10458 | void CopyWatsonBucketsBetweenThrowables(OBJECTREF oThrowableFrom, OBJECTREF oThrowableTo /*=NULL*/) |
10459 | { |
10460 | #ifndef DACCESS_COMPILE |
10461 | |
10462 | CONTRACTL |
10463 | { |
10464 | GC_TRIGGERS; |
10465 | MODE_COOPERATIVE; |
10466 | THROWS; |
10467 | PRECONDITION(oThrowableFrom != NULL); |
10468 | PRECONDITION(!CLRException::IsPreallocatedExceptionObject(oThrowableFrom)); |
10469 | PRECONDITION(((EXCEPTIONREF)oThrowableFrom)->AreWatsonBucketsPresent()); |
10470 | PRECONDITION(IsWatsonEnabled()); |
10471 | } |
10472 | CONTRACTL_END; |
10473 | |
10474 | BOOL fRetVal = FALSE; |
10475 | |
10476 | struct |
10477 | { |
10478 | OBJECTREF oFrom; |
10479 | OBJECTREF oTo; |
10480 | OBJECTREF oWatsonBuckets; |
10481 | } _gc; |
10482 | |
10483 | ZeroMemory(&_gc, sizeof(_gc)); |
10484 | GCPROTECT_BEGIN(_gc); |
10485 | |
10486 | _gc.oFrom = oThrowableFrom; |
10487 | _gc.oTo = (oThrowableTo == NULL)?GetThread()->GetThrowable():oThrowableTo; |
10488 | _ASSERTE(_gc.oTo != NULL); |
10489 | |
10490 | // The target throwable to which Watson buckets are going to be copied |
10491 | // shouldnt be preallocated exception object. |
10492 | _ASSERTE(!CLRException::IsPreallocatedExceptionObject(_gc.oTo)); |
10493 | |
10494 | // Size of a watson bucket |
10495 | DWORD size = sizeof(GenericModeBlock); |
10496 | |
10497 | // Create the managed byte[] to hold the bucket details |
10498 | _gc.oWatsonBuckets = AllocatePrimitiveArray(ELEMENT_TYPE_U1, size); |
10499 | if (_gc.oWatsonBuckets == NULL) |
10500 | { |
10501 | // return failure if failed to create bucket array |
10502 | fRetVal = FALSE; |
10503 | } |
10504 | else |
10505 | { |
10506 | // Get the raw array data pointer of the source array |
10507 | U1ARRAYREF refSourceWatsonBucketArray = ((EXCEPTIONREF)_gc.oFrom)->GetWatsonBucketReference(); |
10508 | PTR_VOID pRawSourceWatsonBucketArray = dac_cast<PTR_VOID>(refSourceWatsonBucketArray->GetDataPtr()); |
10509 | |
10510 | // Get the raw array data pointer to the destination array |
10511 | U1ARRAYREF refDestWatsonBucketArray = (U1ARRAYREF)_gc.oWatsonBuckets; |
10512 | PTR_VOID pRawDestWatsonBucketArray = dac_cast<PTR_VOID>(refDestWatsonBucketArray->GetDataPtr()); |
10513 | |
10514 | // Deep copy the bucket information to the managed array |
10515 | memcpyNoGCRefs(pRawDestWatsonBucketArray, pRawSourceWatsonBucketArray, size); |
10516 | |
10517 | // Setup the managed field reference to point to the byte array. |
10518 | // |
10519 | // The throwable, to which the buckets are being copied to, may be |
10520 | // having existing buckets (e.g. when TypeInitialization exception |
10521 | // maybe thrown again when attempt is made to load the originally |
10522 | // failed type). |
10523 | // |
10524 | // This is also possible if exception object is used as singleton |
10525 | // and thrown by multiple threads. |
10526 | if (((EXCEPTIONREF)_gc.oTo)->AreWatsonBucketsPresent()) |
10527 | { |
10528 | LOG((LF_EH, LL_INFO1000, "CopyWatsonBucketsBetweenThrowables: Throwable (%p) being copied to had previous buckets.\n" , OBJECTREFToObject(_gc.oTo))); |
10529 | } |
10530 | |
10531 | ((EXCEPTIONREF)_gc.oTo)->SetWatsonBucketReference(_gc.oWatsonBuckets); |
10532 | |
10533 | fRetVal = TRUE; |
10534 | } |
10535 | |
10536 | // We shouldn't be here when fRetVal is FALSE since failure to allocate the primitive |
10537 | // array should throw an OOM. |
10538 | _ASSERTE(fRetVal); |
10539 | |
10540 | GCPROTECT_END(); |
10541 | #endif // !DACCESS_COMPILE |
10542 | } |
10543 | |
10544 | // This function will copy the watson bucket information to the managed byte[] in |
10545 | // the specified managed exception object. |
10546 | // |
10547 | // If throwable is not specified, it will be copied to the current throwable. |
10548 | // |
10549 | // pUnmanagedBuckets is a pointer to native memory that cannot be affected by GC. |
10550 | BOOL CopyWatsonBucketsToThrowable(PTR_VOID pUnmanagedBuckets, OBJECTREF oTargetThrowable /*= NULL*/) |
10551 | { |
10552 | #ifndef DACCESS_COMPILE |
10553 | |
10554 | CONTRACTL |
10555 | { |
10556 | GC_TRIGGERS; |
10557 | MODE_COOPERATIVE; |
10558 | THROWS; |
10559 | PRECONDITION(GetThread() != NULL); |
10560 | PRECONDITION(pUnmanagedBuckets != NULL); |
10561 | PRECONDITION(!CLRException::IsPreallocatedExceptionObject((oTargetThrowable == NULL)?GetThread()->GetThrowable():oTargetThrowable)); |
10562 | PRECONDITION(IsWatsonEnabled()); |
10563 | } |
10564 | CONTRACTL_END; |
10565 | |
10566 | BOOL fRetVal = TRUE; |
10567 | struct |
10568 | { |
10569 | OBJECTREF oThrowable; |
10570 | OBJECTREF oWatsonBuckets; |
10571 | } _gc; |
10572 | |
10573 | ZeroMemory(&_gc, sizeof(_gc)); |
10574 | GCPROTECT_BEGIN(_gc); |
10575 | _gc.oThrowable = (oTargetThrowable == NULL)?GetThread()->GetThrowable():oTargetThrowable; |
10576 | |
10577 | // Throwable to which buckets should be copied to, must exist. |
10578 | _ASSERTE(_gc.oThrowable != NULL); |
10579 | |
10580 | // Size of a watson bucket |
10581 | DWORD size = sizeof(GenericModeBlock); |
10582 | |
10583 | _gc.oWatsonBuckets = AllocatePrimitiveArray(ELEMENT_TYPE_U1, size); |
10584 | if (_gc.oWatsonBuckets == NULL) |
10585 | { |
10586 | // return failure if failed to create bucket array |
10587 | fRetVal = FALSE; |
10588 | } |
10589 | else |
10590 | { |
10591 | // Get the raw array data pointer |
10592 | U1ARRAYREF refWatsonBucketArray = (U1ARRAYREF)_gc.oWatsonBuckets; |
10593 | PTR_VOID pRawWatsonBucketArray = dac_cast<PTR_VOID>(refWatsonBucketArray->GetDataPtr()); |
10594 | |
10595 | // Deep copy the bucket information to the managed array |
10596 | memcpyNoGCRefs(pRawWatsonBucketArray, pUnmanagedBuckets, size); |
10597 | |
10598 | // Setup the managed field reference to point to the byte array. |
10599 | // |
10600 | // The throwable, to which the buckets are being copied to, may be |
10601 | // having existing buckets (e.g. when TypeInitialization exception |
10602 | // maybe thrown again when attempt is made to load the originally |
10603 | // failed type). |
10604 | // |
10605 | // This is also possible if exception object is used as singleton |
10606 | // and thrown by multiple threads. |
10607 | if (((EXCEPTIONREF)_gc.oThrowable)->AreWatsonBucketsPresent()) |
10608 | { |
10609 | LOG((LF_EH, LL_INFO1000, "CopyWatsonBucketsToThrowable: Throwable (%p) being copied to had previous buckets.\n" , OBJECTREFToObject(_gc.oThrowable))); |
10610 | } |
10611 | |
10612 | ((EXCEPTIONREF)_gc.oThrowable)->SetWatsonBucketReference(_gc.oWatsonBuckets); |
10613 | } |
10614 | |
10615 | GCPROTECT_END(); |
10616 | |
10617 | return fRetVal; |
10618 | #else // DACCESS_COMPILE |
10619 | return TRUE; |
10620 | #endif // !DACCESS_COMPILE |
10621 | } |
10622 | |
10623 | // This function will setup the bucketing information for nested exceptions |
10624 | // raised. These would be any exceptions thrown from within the confines of |
10625 | // managed EH clauses and include "rethrow" and "throw new ...". |
10626 | // |
10627 | // This is called from within CLR's personality routine for managed |
10628 | // exceptions to preemptively setup the watson buckets from the ones that may |
10629 | // already exist. If none exist already, we will automatically endup in the |
10630 | // path (SetupInitialThrowBucketDetails) that will set up buckets for the |
10631 | // exception being thrown. |
10632 | void SetStateForWatsonBucketing(BOOL fIsRethrownException, OBJECTHANDLE ohOriginalException) |
10633 | { |
10634 | #ifndef DACCESS_COMPILE |
10635 | |
10636 | // On CoreCLR, Watson may not be enabled. Thus, we should |
10637 | // skip this. |
10638 | if (!IsWatsonEnabled()) |
10639 | { |
10640 | return; |
10641 | } |
10642 | |
10643 | CONTRACTL |
10644 | { |
10645 | GC_TRIGGERS; |
10646 | MODE_ANY; |
10647 | NOTHROW; |
10648 | PRECONDITION(GetThread() != NULL); |
10649 | PRECONDITION(IsWatsonEnabled()); |
10650 | } |
10651 | CONTRACTL_END; |
10652 | |
10653 | // Switch to COOP mode |
10654 | GCX_COOP(); |
10655 | |
10656 | struct |
10657 | { |
10658 | OBJECTREF oCurrentThrowable; |
10659 | OBJECTREF oInnerMostExceptionThrowable; |
10660 | } gc; |
10661 | ZeroMemory(&gc, sizeof(gc)); |
10662 | GCPROTECT_BEGIN(gc); |
10663 | |
10664 | Thread* pThread = GetThread(); |
10665 | |
10666 | // Get the current exception state of the thread |
10667 | ThreadExceptionState* pCurExState = pThread->GetExceptionState(); |
10668 | _ASSERTE(NULL != pCurExState); |
10669 | |
10670 | // Ensure that the exception tracker exists |
10671 | _ASSERTE(pCurExState->GetCurrentExceptionTracker() != NULL); |
10672 | |
10673 | // Get the current throwable |
10674 | gc.oCurrentThrowable = pThread->GetThrowable(); |
10675 | _ASSERTE(gc.oCurrentThrowable != NULL); |
10676 | |
10677 | // Is the throwable a preallocated exception object? |
10678 | BOOL fIsPreallocatedExceptionObject = CLRException::IsPreallocatedExceptionObject(gc.oCurrentThrowable); |
10679 | |
10680 | // Copy the bucketing details from the original exception tracker if the current exception is a rethrow |
10681 | // AND the throwable is a preallocated exception object. |
10682 | // |
10683 | // For rethrown non-preallocated exception objects, the throwable would already have the bucketing |
10684 | // details inside it. |
10685 | if (fIsRethrownException) |
10686 | { |
10687 | if (fIsPreallocatedExceptionObject) |
10688 | { |
10689 | // Get the WatsonBucket tracker for the original exception, starting search from the previous EH tracker. |
10690 | // This is required so that when a preallocated exception is rethrown, then the current tracker would have |
10691 | // the same throwable as the original exception but no bucketing details. |
10692 | // |
10693 | // To ensure GetWatsonBucketTrackerForPreallocatedException uses the EH tracker corresponding to the original |
10694 | // exception to get the bucketing details, we pass TRUE as the third parameter. |
10695 | PTR_EHWatsonBucketTracker pPreallocWatsonBucketTracker = GetWatsonBucketTrackerForPreallocatedException(gc.oCurrentThrowable, FALSE, TRUE); |
10696 | if (pPreallocWatsonBucketTracker != NULL) |
10697 | { |
10698 | if (!IsThrowableThreadAbortException(gc.oCurrentThrowable)) |
10699 | { |
10700 | // For non-thread abort preallocated exceptions, we copy the bucketing details |
10701 | // from their corresponding watson bucket tracker to the one corresponding to the |
10702 | // rethrow that is taking place. |
10703 | // |
10704 | // Bucketing details for preallocated exception may not be present if the exception came |
10705 | // from across AD transition and we attempted to copy them over from the UETracker, when |
10706 | // the exception was reraised in the calling AD, and the copy operation failed due to OOM. |
10707 | // |
10708 | // In such a case, when the reraised exception is caught and rethrown, we will not have |
10709 | // any bucketing details. |
10710 | if (NULL != pPreallocWatsonBucketTracker->RetrieveWatsonBucketIp()) |
10711 | { |
10712 | // Copy the bucketing details now |
10713 | pCurExState->GetCurrentExceptionTracker()->GetWatsonBucketTracker()->CopyEHWatsonBucketTracker(*pPreallocWatsonBucketTracker); |
10714 | } |
10715 | else |
10716 | { |
10717 | LOG((LF_EH, LL_INFO1000, "SetStateForWatsonBucketing - Watson bucketing details for rethrown preallocated exception not found in the EH tracker corresponding to the original exception. This is likely due to a previous OOM.\n" )); |
10718 | LOG((LF_EH, LL_INFO1000, ">>>>>>>>>>>>>>>>>>>>>>>>>> Original WatsonBucketTracker = %p\n" , pPreallocWatsonBucketTracker)); |
10719 | |
10720 | // Make the active tracker clear |
10721 | pCurExState->GetCurrentExceptionTracker()->GetWatsonBucketTracker()->ClearWatsonBucketDetails(); |
10722 | } |
10723 | } |
10724 | #ifdef _DEBUG |
10725 | else |
10726 | { |
10727 | // For thread abort exceptions, the returned watson bucket tracker |
10728 | // would correspond to UE Watson bucket tracker and it will have |
10729 | // all the details. |
10730 | _ASSERTE(pPreallocWatsonBucketTracker == pCurExState->GetUEWatsonBucketTracker()); |
10731 | } |
10732 | #endif // _DEBUG |
10733 | } |
10734 | else |
10735 | { |
10736 | // OOM can result in not having a Watson bucket tracker with valid bucketing details for a preallocated exception. |
10737 | // Thus, we may end up here. For details, see implementation of GetWatsonBucketTrackerForPreallocatedException. |
10738 | LOG((LF_EH, LL_INFO1000, "SetStateForWatsonBucketing - Watson bucketing tracker for rethrown preallocated exception not found. This is likely due to a previous OOM.\n" )); |
10739 | |
10740 | // Make the active tracker clear |
10741 | pCurExState->GetCurrentExceptionTracker()->GetWatsonBucketTracker()->ClearWatsonBucketDetails(); |
10742 | } |
10743 | } |
10744 | else |
10745 | { |
10746 | // We dont need to do anything here since the throwable would already have the bucketing |
10747 | // details inside it. Simply assert that the original exception object is the same as the current throwable. |
10748 | // |
10749 | // We cannot assert for Watson buckets since the original throwable may not have got them in |
10750 | // SetupInitialThrowBucketDetails due to OOM |
10751 | _ASSERTE((NULL != ohOriginalException) && (ObjectFromHandle(ohOriginalException) == gc.oCurrentThrowable)); |
10752 | if ((((EXCEPTIONREF)gc.oCurrentThrowable)->AreWatsonBucketsPresent() == FALSE) && |
10753 | (((EXCEPTIONREF)gc.oCurrentThrowable)->IsIPForWatsonBucketsPresent() == FALSE)) |
10754 | { |
10755 | LOG((LF_EH, LL_INFO1000, "SetStateForWatsonBucketing - Regular rethrown exception (%p) does not have Watson buckets, likely due to OOM.\n" , |
10756 | OBJECTREFToObject(gc.oCurrentThrowable))); |
10757 | } |
10758 | } |
10759 | |
10760 | // Set the flag that we have bucketing details for the exception |
10761 | pCurExState->GetFlags()->SetGotWatsonBucketDetails(); |
10762 | LOG((LF_EH, LL_INFO1000, "SetStateForWatsonBucketing - Using original exception details for Watson bucketing for rethrown exception.\n" )); |
10763 | } |
10764 | else |
10765 | { |
10766 | // If we are here, then an exception is being thrown from within the |
10767 | // managed EH clauses of fault, finally or catch, with an inner exception. |
10768 | |
10769 | // By default, we will create buckets based upon the exception being thrown unless |
10770 | // thrown exception has an inner exception that has got bucketing details |
10771 | BOOL fCreateBucketsForExceptionBeingThrown = TRUE; |
10772 | |
10773 | // Start off by assuming that inner exception object is not preallocated |
10774 | BOOL fIsInnerExceptionPreallocated = FALSE; |
10775 | |
10776 | // Reference to the WatsonBucket tracker for the inner exception, if it is preallocated |
10777 | PTR_EHWatsonBucketTracker pInnerExceptionWatsonBucketTracker = NULL; |
10778 | |
10779 | // Since this is a new exception being thrown, we will check if it has buckets already or not. |
10780 | // This is possible when Reflection throws TargetInvocationException with an inner exception |
10781 | // that is preallocated exception object. In such a case, we copy the inner exception details |
10782 | // to the TargetInvocationException object already. This is done in InvokeImpl in ReflectionInvocation.cpp. |
10783 | if (((EXCEPTIONREF)gc.oCurrentThrowable)->AreWatsonBucketsPresent() || |
10784 | ((EXCEPTIONREF)gc.oCurrentThrowable)->IsIPForWatsonBucketsPresent()) |
10785 | { |
10786 | goto done; |
10787 | } |
10788 | |
10789 | // If no buckets are present, then we will check if it has an innermost exception or not. |
10790 | // If it does, then we will make the exception being thrown use the bucketing details of the |
10791 | // innermost exception. |
10792 | // |
10793 | // If there is no innermost exception or if one is present without bucketing details, then |
10794 | // we will have bucket details based upon the exception being thrown. |
10795 | |
10796 | // Get the innermost exception from the exception being thrown. |
10797 | gc.oInnerMostExceptionThrowable = ((EXCEPTIONREF)gc.oCurrentThrowable)->GetBaseException(); |
10798 | if (gc.oInnerMostExceptionThrowable != NULL) |
10799 | { |
10800 | fIsInnerExceptionPreallocated = CLRException::IsPreallocatedExceptionObject(gc.oInnerMostExceptionThrowable); |
10801 | |
10802 | // Preallocated exception objects do not have inner exception objects. |
10803 | // Thus, if we are here, then the current throwable cannot be |
10804 | // a preallocated exception object. |
10805 | _ASSERTE(!fIsPreallocatedExceptionObject); |
10806 | |
10807 | // Create the new buckets only if the innermost exception object |
10808 | // does not have them already. |
10809 | if (fIsInnerExceptionPreallocated) |
10810 | { |
10811 | // If we are able to find the watson bucket tracker for the preallocated |
10812 | // inner exception, then we dont need to create buckets for throw site. |
10813 | pInnerExceptionWatsonBucketTracker = GetWatsonBucketTrackerForPreallocatedException(gc.oInnerMostExceptionThrowable, FALSE, TRUE); |
10814 | fCreateBucketsForExceptionBeingThrown = ((pInnerExceptionWatsonBucketTracker != NULL) && |
10815 | (pInnerExceptionWatsonBucketTracker->RetrieveWatsonBucketIp() != NULL)) ? FALSE : TRUE; |
10816 | } |
10817 | else |
10818 | { |
10819 | // Since the inner exception object is not preallocated, create |
10820 | // watson buckets only if it does not have them. |
10821 | fCreateBucketsForExceptionBeingThrown = !(((EXCEPTIONREF)gc.oInnerMostExceptionThrowable)->AreWatsonBucketsPresent() || |
10822 | ((EXCEPTIONREF)gc.oInnerMostExceptionThrowable)->IsIPForWatsonBucketsPresent()); |
10823 | } |
10824 | } |
10825 | |
10826 | // If we are NOT going to create buckets for the thrown exception, |
10827 | // then copy them over from the inner exception object. |
10828 | // |
10829 | // If we have to create the buckets for the thrown exception, |
10830 | // we wont do that now - it will be done in StackTraceInfo::AppendElement |
10831 | // when we get the IP for bucketing. |
10832 | if (!fCreateBucketsForExceptionBeingThrown) |
10833 | { |
10834 | // Preallocated exception objects do not have inner exception objects. |
10835 | // Thus, if we are here, then the current throwable cannot be |
10836 | // a preallocated exception object. |
10837 | _ASSERTE(!fIsPreallocatedExceptionObject); |
10838 | |
10839 | if (fIsInnerExceptionPreallocated) |
10840 | { |
10841 | |
10842 | // We should have the inner exception watson bucket tracker |
10843 | _ASSERTE((pInnerExceptionWatsonBucketTracker != NULL) && (pInnerExceptionWatsonBucketTracker->RetrieveWatsonBucketIp() != NULL)); |
10844 | |
10845 | // Capture the buckets for the innermost exception if they dont already exist. |
10846 | // Since the current throwable cannot be preallocated (see the assert above), |
10847 | // copy the buckets to the throwable. |
10848 | PTR_VOID pInnerExceptionWatsonBuckets = pInnerExceptionWatsonBucketTracker->RetrieveWatsonBuckets(); |
10849 | if (pInnerExceptionWatsonBuckets == NULL) |
10850 | { |
10851 | // Capture the buckets since they dont exist |
10852 | pInnerExceptionWatsonBucketTracker->CaptureUnhandledInfoForWatson(TypeOfReportedError::UnhandledException, pThread, &gc.oInnerMostExceptionThrowable); |
10853 | pInnerExceptionWatsonBuckets = pInnerExceptionWatsonBucketTracker->RetrieveWatsonBuckets(); |
10854 | } |
10855 | |
10856 | if (pInnerExceptionWatsonBuckets == NULL) |
10857 | { |
10858 | // Couldnt capture details like due to OOM |
10859 | LOG((LF_EH, LL_INFO1000, "SetStateForWatsonBucketing - Preallocated inner-exception's WBTracker (%p) has no bucketing details for the thrown exception, likely due to OOM.\n" , pInnerExceptionWatsonBucketTracker)); |
10860 | } |
10861 | else |
10862 | { |
10863 | // Copy the buckets to the current throwable |
10864 | BOOL fCopied = TRUE; |
10865 | EX_TRY |
10866 | { |
10867 | fCopied = CopyWatsonBucketsToThrowable(pInnerExceptionWatsonBuckets); |
10868 | _ASSERTE(fCopied); |
10869 | } |
10870 | EX_CATCH |
10871 | { |
10872 | // Dont do anything if we fail to copy the buckets - this is no different than |
10873 | // the native watson helper functions failing under OOM |
10874 | fCopied = FALSE; |
10875 | } |
10876 | EX_END_CATCH(SwallowAllExceptions); |
10877 | } |
10878 | } |
10879 | else |
10880 | { |
10881 | // Assert that the inner exception has the Watson buckets |
10882 | _ASSERTE(gc.oInnerMostExceptionThrowable != NULL); |
10883 | _ASSERTE(((EXCEPTIONREF)gc.oInnerMostExceptionThrowable)->AreWatsonBucketsPresent() || |
10884 | ((EXCEPTIONREF)gc.oInnerMostExceptionThrowable)->IsIPForWatsonBucketsPresent()); |
10885 | |
10886 | if (((EXCEPTIONREF)gc.oInnerMostExceptionThrowable)->AreWatsonBucketsPresent()) |
10887 | { |
10888 | // Copy the bucket information from the inner exception object to the current throwable |
10889 | EX_TRY |
10890 | { |
10891 | CopyWatsonBucketsFromThrowableToCurrentThrowable(gc.oInnerMostExceptionThrowable); |
10892 | } |
10893 | EX_CATCH |
10894 | { |
10895 | // Dont do anything if we fail to copy the buckets - this is no different than |
10896 | // the native watson helper functions failing under OOM |
10897 | } |
10898 | EX_END_CATCH(SwallowAllExceptions); |
10899 | } |
10900 | else |
10901 | { |
10902 | // Copy the IP for Watson bucketing to the exception object |
10903 | ((EXCEPTIONREF)gc.oCurrentThrowable)->SetIPForWatsonBuckets(((EXCEPTIONREF)gc.oInnerMostExceptionThrowable)->GetIPForWatsonBuckets()); |
10904 | } |
10905 | } |
10906 | |
10907 | // Set the flag that we got bucketing details for the exception |
10908 | pCurExState->GetFlags()->SetGotWatsonBucketDetails(); |
10909 | LOG((LF_EH, LL_INFO1000, "SetStateForWatsonBucketing - Using innermost exception details for Watson bucketing for thrown exception.\n" )); |
10910 | } |
10911 | done:; |
10912 | } |
10913 | |
10914 | GCPROTECT_END(); |
10915 | |
10916 | #endif // !DACCESS_COMPILE |
10917 | } |
10918 | |
10919 | // Constructor that will do the initialization of the object |
10920 | EHWatsonBucketTracker::EHWatsonBucketTracker() |
10921 | { |
10922 | LIMITED_METHOD_CONTRACT; |
10923 | |
10924 | Init(); |
10925 | } |
10926 | |
10927 | // Reset the fields to default values |
10928 | void EHWatsonBucketTracker::Init() |
10929 | { |
10930 | LIMITED_METHOD_CONTRACT; |
10931 | |
10932 | m_WatsonUnhandledInfo.m_UnhandledIp = 0; |
10933 | m_WatsonUnhandledInfo.m_pUnhandledBuckets = NULL; |
10934 | |
10935 | DEBUG_STMT(ResetFlags()); |
10936 | |
10937 | LOG((LF_EH, LL_INFO1000, "EHWatsonBucketTracker::Init - initializing watson bucket tracker (%p)\n" , this)); |
10938 | } |
10939 | |
10940 | // This method copies the bucketing details from the specified throwable |
10941 | // to the current Watson Bucket tracker. |
10942 | void EHWatsonBucketTracker::CopyBucketsFromThrowable(OBJECTREF oThrowable) |
10943 | { |
10944 | #ifndef DACCESS_COMPILE |
10945 | CONTRACTL |
10946 | { |
10947 | NOTHROW; |
10948 | GC_NOTRIGGER; |
10949 | MODE_ANY; |
10950 | PRECONDITION(oThrowable != NULL); |
10951 | PRECONDITION(((EXCEPTIONREF)oThrowable)->AreWatsonBucketsPresent()); |
10952 | PRECONDITION(IsWatsonEnabled()); |
10953 | } |
10954 | CONTRACTL_END; |
10955 | |
10956 | GCX_COOP(); |
10957 | |
10958 | struct |
10959 | { |
10960 | OBJECTREF oFrom; |
10961 | } _gc; |
10962 | |
10963 | ZeroMemory(&_gc, sizeof(_gc)); |
10964 | GCPROTECT_BEGIN(_gc); |
10965 | |
10966 | _gc.oFrom = oThrowable; |
10967 | |
10968 | LOG((LF_EH, LL_INFO1000, "EHWatsonBucketTracker::CopyEHWatsonBucketTracker - Copying bucketing details from throwable (%p) to tracker (%p)\n" , |
10969 | OBJECTREFToObject(_gc.oFrom), this)); |
10970 | |
10971 | // Watson bucket is a "GenericModeBlock" type. Set up an empty GenericModeBlock |
10972 | // to hold the bucket parameters. |
10973 | GenericModeBlock *pgmb = new (nothrow) GenericModeBlock; |
10974 | if (pgmb == NULL) |
10975 | { |
10976 | // If we are unable to allocate memory to hold the WatsonBucket, then |
10977 | // reset the IP and bucket pointer to NULL and bail out |
10978 | SaveIpForWatsonBucket(NULL); |
10979 | m_WatsonUnhandledInfo.m_pUnhandledBuckets = NULL; |
10980 | } |
10981 | else |
10982 | { |
10983 | // Get the raw array data pointer |
10984 | U1ARRAYREF refWatsonBucketArray = ((EXCEPTIONREF)_gc.oFrom)->GetWatsonBucketReference(); |
10985 | PTR_VOID pRawWatsonBucketArray = dac_cast<PTR_VOID>(refWatsonBucketArray->GetDataPtr()); |
10986 | |
10987 | // Copy over the details to our new allocation |
10988 | memcpyNoGCRefs(pgmb, pRawWatsonBucketArray, sizeof(GenericModeBlock)); |
10989 | |
10990 | // and save the address where the buckets were copied |
10991 | _ASSERTE(m_WatsonUnhandledInfo.m_pUnhandledBuckets == NULL); |
10992 | m_WatsonUnhandledInfo.m_pUnhandledBuckets = pgmb; |
10993 | } |
10994 | |
10995 | GCPROTECT_END(); |
10996 | |
10997 | LOG((LF_EH, LL_INFO1000, "EHWatsonBucketTracker::CopyEHWatsonBucketTracker - Copied Watson Buckets from throwable to (%p)\n" , |
10998 | m_WatsonUnhandledInfo.m_pUnhandledBuckets)); |
10999 | #endif // !DACCESS_COMPILE |
11000 | } |
11001 | |
11002 | // This method copies the bucketing details from the specified Watson Bucket tracker |
11003 | // to the current one. |
11004 | void EHWatsonBucketTracker::CopyEHWatsonBucketTracker(const EHWatsonBucketTracker& srcTracker) |
11005 | { |
11006 | #ifndef DACCESS_COMPILE |
11007 | CONTRACTL |
11008 | { |
11009 | NOTHROW; |
11010 | GC_NOTRIGGER; |
11011 | MODE_ANY; |
11012 | PRECONDITION(m_WatsonUnhandledInfo.m_UnhandledIp == 0); |
11013 | PRECONDITION(m_WatsonUnhandledInfo.m_pUnhandledBuckets == NULL); |
11014 | PRECONDITION(IsWatsonEnabled()); |
11015 | } |
11016 | CONTRACTL_END; |
11017 | |
11018 | LOG((LF_EH, LL_INFO1000, "EHWatsonBucketTracker::CopyEHWatsonBucketTracker - Copying bucketing details from %p to %p\n" , &srcTracker, this)); |
11019 | |
11020 | // Copy the tracking details over from the specified tracker |
11021 | SaveIpForWatsonBucket(srcTracker.m_WatsonUnhandledInfo.m_UnhandledIp); |
11022 | |
11023 | if (srcTracker.m_WatsonUnhandledInfo.m_pUnhandledBuckets != NULL) |
11024 | { |
11025 | // To save the bucket information, we will need to memcpy. |
11026 | // This is to ensure that if the original watson bucket tracker |
11027 | // (for original exception) is released and its memory deallocated, |
11028 | // the new watson bucket tracker (for rethrown exception, for e.g.) |
11029 | // would still have all the bucket details. |
11030 | |
11031 | // Watson bucket is a "GenericModeBlock" type. Set up an empty GenericModeBlock |
11032 | // to hold the bucket parameters. |
11033 | GenericModeBlock *pgmb = new (nothrow) GenericModeBlock; |
11034 | if (pgmb == NULL) |
11035 | { |
11036 | // If we are unable to allocate memory to hold the WatsonBucket, then |
11037 | // reset the IP and bucket pointer to NULL and bail out |
11038 | SaveIpForWatsonBucket(NULL); |
11039 | m_WatsonUnhandledInfo.m_pUnhandledBuckets = NULL; |
11040 | |
11041 | LOG((LF_EH, LL_INFO1000, "EHWatsonBucketTracker::CopyEHWatsonBucketTracker - Not copying buckets due to out of memory.\n" )); |
11042 | } |
11043 | else |
11044 | { |
11045 | // Copy over the details to our new allocation |
11046 | memcpyNoGCRefs(pgmb, srcTracker.m_WatsonUnhandledInfo.m_pUnhandledBuckets, sizeof(GenericModeBlock)); |
11047 | |
11048 | // and save the address where the buckets were copied |
11049 | m_WatsonUnhandledInfo.m_pUnhandledBuckets = pgmb; |
11050 | } |
11051 | } |
11052 | |
11053 | LOG((LF_EH, LL_INFO1000, "EHWatsonBucketTracker::CopyEHWatsonBucketTracker - Copied Watson Bucket to (%p)\n" , m_WatsonUnhandledInfo.m_pUnhandledBuckets)); |
11054 | #endif // !DACCESS_COMPILE |
11055 | } |
11056 | |
11057 | void EHWatsonBucketTracker::SaveIpForWatsonBucket( |
11058 | UINT_PTR ip) // The new IP. |
11059 | { |
11060 | #ifndef DACCESS_COMPILE |
11061 | CONTRACTL |
11062 | { |
11063 | NOTHROW; |
11064 | GC_NOTRIGGER; |
11065 | MODE_ANY; |
11066 | SO_TOLERANT; |
11067 | PRECONDITION(IsWatsonEnabled()); |
11068 | } |
11069 | CONTRACTL_END; |
11070 | |
11071 | LOG((LF_EH, LL_INFO1000, "EHWatsonBucketTracker::SaveIpForUnhandledInfo - this = %p, IP = %p\n" , this, ip)); |
11072 | |
11073 | // Since we are setting a new IP for tracking buckets, |
11074 | // clear any existing details we may hold |
11075 | ClearWatsonBucketDetails(); |
11076 | |
11077 | // Save the new IP for bucketing |
11078 | m_WatsonUnhandledInfo.m_UnhandledIp = ip; |
11079 | #endif // !DACCESS_COMPILE |
11080 | } |
11081 | |
11082 | UINT_PTR EHWatsonBucketTracker::RetrieveWatsonBucketIp() |
11083 | { |
11084 | LIMITED_METHOD_CONTRACT; |
11085 | |
11086 | LOG((LF_EH, LL_INFO1000, "EHWatsonBucketTracker::RetrieveWatsonBucketIp - this = %p, IP = %p\n" , this, m_WatsonUnhandledInfo.m_UnhandledIp)); |
11087 | |
11088 | return m_WatsonUnhandledInfo.m_UnhandledIp; |
11089 | } |
11090 | |
11091 | // This function returns the reference to the Watson buckets tracked by the |
11092 | // instance of WatsonBucket tracker. |
11093 | // |
11094 | // This is *also* invoked from the DAC when buckets are requested. |
11095 | PTR_VOID EHWatsonBucketTracker::RetrieveWatsonBuckets() |
11096 | { |
11097 | #if !defined(DACCESS_COMPILE) |
11098 | if (!IsWatsonEnabled()) |
11099 | { |
11100 | return NULL; |
11101 | } |
11102 | #endif //!defined(DACCESS_COMPILE) |
11103 | |
11104 | CONTRACTL |
11105 | { |
11106 | NOTHROW; |
11107 | GC_NOTRIGGER; |
11108 | MODE_ANY; |
11109 | SO_TOLERANT; |
11110 | PRECONDITION(IsWatsonEnabled()); |
11111 | } |
11112 | CONTRACTL_END; |
11113 | |
11114 | LOG((LF_EH, LL_INFO1000, "EHWatsonBucketTracker::RetrieveWatsonBuckets - this = %p, bucket address = %p\n" , this, m_WatsonUnhandledInfo.m_pUnhandledBuckets)); |
11115 | |
11116 | return m_WatsonUnhandledInfo.m_pUnhandledBuckets; |
11117 | } |
11118 | |
11119 | void EHWatsonBucketTracker::ClearWatsonBucketDetails() |
11120 | { |
11121 | #ifndef DACCESS_COMPILE |
11122 | |
11123 | if (!IsWatsonEnabled()) |
11124 | { |
11125 | return; |
11126 | } |
11127 | |
11128 | CONTRACTL |
11129 | { |
11130 | NOTHROW; |
11131 | GC_NOTRIGGER; |
11132 | MODE_ANY; |
11133 | SO_TOLERANT; |
11134 | PRECONDITION(IsWatsonEnabled()); |
11135 | } |
11136 | CONTRACTL_END; |
11137 | |
11138 | |
11139 | LOG((LF_EH, LL_INFO1000, "EHWatsonBucketTracker::ClearWatsonBucketDetails for tracker (%p)\n" , this)); |
11140 | |
11141 | if (m_WatsonUnhandledInfo.m_pUnhandledBuckets != NULL) |
11142 | { |
11143 | FreeBucketParametersForManagedException(m_WatsonUnhandledInfo.m_pUnhandledBuckets); |
11144 | } |
11145 | |
11146 | Init(); |
11147 | #endif // !DACCESS_COMPILE |
11148 | } |
11149 | |
11150 | void EHWatsonBucketTracker::CaptureUnhandledInfoForWatson(TypeOfReportedError tore, Thread * pThread, OBJECTREF * pThrowable) |
11151 | { |
11152 | #ifndef DACCESS_COMPILE |
11153 | CONTRACTL |
11154 | { |
11155 | NOTHROW; |
11156 | GC_NOTRIGGER; |
11157 | MODE_ANY; |
11158 | PRECONDITION(IsWatsonEnabled()); |
11159 | } |
11160 | CONTRACTL_END; |
11161 | |
11162 | LOG((LF_EH, LL_INFO1000, "EHWatsonBucketTracker::CaptureUnhandledInfoForWatson capturing watson bucket details for (%p)\n" , this)); |
11163 | |
11164 | // Only capture the bucket information if there is an IP AND we dont already have collected them. |
11165 | // We could have collected them from a previous AD transition and wouldnt want to overwrite them. |
11166 | if (m_WatsonUnhandledInfo.m_UnhandledIp != 0) |
11167 | { |
11168 | if (m_WatsonUnhandledInfo.m_pUnhandledBuckets == NULL) |
11169 | { |
11170 | // Get the bucket details since we dont have them |
11171 | m_WatsonUnhandledInfo.m_pUnhandledBuckets = GetBucketParametersForManagedException(m_WatsonUnhandledInfo.m_UnhandledIp, tore, pThread, pThrowable); |
11172 | LOG((LF_EH, LL_INFO1000, "EHWatsonBucketTracker::CaptureUnhandledInfoForWatson captured the following watson bucket details: (this = %p, bucket addr = %p)\n" , |
11173 | this, m_WatsonUnhandledInfo.m_pUnhandledBuckets)); |
11174 | } |
11175 | else |
11176 | { |
11177 | // We already have the bucket details - so no need to capture them again |
11178 | LOG((LF_EH, LL_INFO1000, "EHWatsonBucketTracker::CaptureUnhandledInfoForWatson already have the watson bucket details: (this = %p, bucket addr = %p)\n" , |
11179 | this, m_WatsonUnhandledInfo.m_pUnhandledBuckets)); |
11180 | } |
11181 | } |
11182 | else |
11183 | { |
11184 | LOG((LF_EH, LL_INFO1000, "EHWatsonBucketTracker::CaptureUnhandledInfoForWatson didnt have an IP to use for capturing watson buckets\n" )); |
11185 | } |
11186 | #endif // !DACCESS_COMPILE |
11187 | } |
11188 | #endif // !FEATURE_PAL |
11189 | |
11190 | // Given a throwable, this function will attempt to find an active EH tracker corresponding to it. |
11191 | // If none found, it will return NULL |
11192 | #ifdef WIN64EXCEPTIONS |
11193 | PTR_ExceptionTracker GetEHTrackerForException(OBJECTREF oThrowable, PTR_ExceptionTracker pStartingEHTracker) |
11194 | #elif _TARGET_X86_ |
11195 | PTR_ExInfo GetEHTrackerForException(OBJECTREF oThrowable, PTR_ExInfo pStartingEHTracker) |
11196 | #else |
11197 | #error Unsupported platform |
11198 | #endif |
11199 | { |
11200 | CONTRACTL |
11201 | { |
11202 | GC_NOTRIGGER; |
11203 | MODE_COOPERATIVE; |
11204 | NOTHROW; |
11205 | SO_TOLERANT; |
11206 | PRECONDITION(GetThread() != NULL); |
11207 | PRECONDITION(oThrowable != NULL); |
11208 | } |
11209 | CONTRACTL_END; |
11210 | |
11211 | // Get the reference to the exception tracker to start with. If one has been provided to us, |
11212 | // then use it. Otherwise, start from the current one. |
11213 | #ifdef WIN64EXCEPTIONS |
11214 | PTR_ExceptionTracker pEHTracker = (pStartingEHTracker != NULL) ? pStartingEHTracker : GetThread()->GetExceptionState()->GetCurrentExceptionTracker(); |
11215 | #elif _TARGET_X86_ |
11216 | PTR_ExInfo pEHTracker = (pStartingEHTracker != NULL) ? pStartingEHTracker : GetThread()->GetExceptionState()->GetCurrentExceptionTracker(); |
11217 | #else |
11218 | #error Unsupported platform |
11219 | #endif |
11220 | |
11221 | BOOL fFoundTracker = FALSE; |
11222 | |
11223 | // Start walking the list to find the tracker correponding |
11224 | // to the exception object. |
11225 | while (pEHTracker != NULL) |
11226 | { |
11227 | if (pEHTracker->GetThrowable() == oThrowable) |
11228 | { |
11229 | // found the tracker - break out. |
11230 | fFoundTracker = TRUE; |
11231 | break; |
11232 | } |
11233 | |
11234 | // move to the previous tracker... |
11235 | pEHTracker = pEHTracker->GetPreviousExceptionTracker(); |
11236 | } |
11237 | |
11238 | return fFoundTracker ? pEHTracker : NULL; |
11239 | } |
11240 | |
11241 | #ifdef FEATURE_CORRUPTING_EXCEPTIONS |
11242 | // ----------------------------------------------------------------------- |
11243 | // Support for CorruptedState Exceptions |
11244 | // ----------------------------------------------------------------------- |
11245 | |
11246 | // Given an exception code, this method returns a BOOL to indicate if the |
11247 | // code belongs to a corrupting exception or not. |
11248 | /* static */ |
11249 | BOOL CEHelper::IsProcessCorruptedStateException(DWORD dwExceptionCode, BOOL fCheckForSO /*= TRUE*/) |
11250 | { |
11251 | CONTRACTL |
11252 | { |
11253 | NOTHROW; |
11254 | GC_NOTRIGGER; |
11255 | MODE_ANY; |
11256 | SO_TOLERANT; |
11257 | } |
11258 | CONTRACTL_END; |
11259 | |
11260 | if (g_pConfig->LegacyCorruptedStateExceptionsPolicy()) |
11261 | { |
11262 | return FALSE; |
11263 | } |
11264 | |
11265 | // Call into the utilcode helper function to check if this |
11266 | // is a CE or not. |
11267 | return (::IsProcessCorruptedStateException(dwExceptionCode, fCheckForSO)); |
11268 | } |
11269 | |
11270 | // This is used in the VM folder version of "SET_CE_RETHROW_FLAG_FOR_EX_CATCH" (in clrex.h) |
11271 | // to check if the managed exception caught by EX_END_CATCH is CSE or not. |
11272 | // |
11273 | // If you are using it from rethrow boundaries (e.g. SET_CE_RETHROW_FLAG_FOR_EX_CATCH |
11274 | // macro that is used to automatically rethrow corrupting exceptions), then you may |
11275 | // want to set the "fMarkForReuseIfCorrupting" to TRUE to enable propagation of the |
11276 | // corruption severity when the reraised exception is seen by managed code again. |
11277 | /* static */ |
11278 | BOOL CEHelper::IsLastActiveExceptionCorrupting(BOOL fMarkForReuseIfCorrupting /* = FALSE */) |
11279 | { |
11280 | CONTRACTL |
11281 | { |
11282 | NOTHROW; |
11283 | GC_NOTRIGGER; |
11284 | MODE_ANY; |
11285 | PRECONDITION(GetThread() != NULL); |
11286 | } |
11287 | CONTRACTL_END; |
11288 | |
11289 | if (g_pConfig->LegacyCorruptedStateExceptionsPolicy()) |
11290 | { |
11291 | return FALSE; |
11292 | } |
11293 | |
11294 | BOOL fIsCorrupting = FALSE; |
11295 | ThreadExceptionState *pCurTES = GetThread()->GetExceptionState(); |
11296 | |
11297 | // Check the corruption severity |
11298 | CorruptionSeverity severity = pCurTES->GetLastActiveExceptionCorruptionSeverity(); |
11299 | fIsCorrupting = (severity == ProcessCorrupting); |
11300 | if (fIsCorrupting && fMarkForReuseIfCorrupting) |
11301 | { |
11302 | // Mark the corruption severity for reuse |
11303 | CEHelper::MarkLastActiveExceptionCorruptionSeverityForReraiseReuse(); |
11304 | } |
11305 | |
11306 | LOG((LF_EH, LL_INFO100, "CEHelper::IsLastActiveExceptionCorrupting - Using corruption severity from TES.\n" )); |
11307 | |
11308 | return fIsCorrupting; |
11309 | } |
11310 | |
11311 | // Given a MethodDesc, this method will return a BOOL to indicate if |
11312 | // the containing assembly was built for PreV4 runtime or not. |
11313 | /* static */ |
11314 | BOOL CEHelper::IsMethodInPreV4Assembly(PTR_MethodDesc pMethodDesc) |
11315 | { |
11316 | CONTRACTL |
11317 | { |
11318 | NOTHROW; |
11319 | GC_NOTRIGGER; |
11320 | MODE_ANY; |
11321 | PRECONDITION(pMethodDesc != NULL); |
11322 | } |
11323 | CONTRACTL_END; |
11324 | |
11325 | // By default, assume that the containing assembly was not |
11326 | // built for PreV4 runtimes. |
11327 | BOOL fBuiltForPreV4Runtime = FALSE; |
11328 | |
11329 | if (g_pConfig->LegacyCorruptedStateExceptionsPolicy()) |
11330 | { |
11331 | return TRUE; |
11332 | } |
11333 | |
11334 | LPCSTR pszVersion = NULL; |
11335 | |
11336 | // Retrieve the manifest metadata reference since that contains |
11337 | // the "built-for" runtime details |
11338 | IMDInternalImport *pImport = pMethodDesc->GetAssembly()->GetManifestImport(); |
11339 | if (pImport && SUCCEEDED(pImport->GetVersionString(&pszVersion))) |
11340 | { |
11341 | if (pszVersion != NULL) |
11342 | { |
11343 | // If version begins with "v1.*" or "v2.*", it was built for preV4 runtime |
11344 | if ((pszVersion[0] == 'v' || pszVersion[0] == 'V') && |
11345 | IS_DIGIT(pszVersion[1]) && |
11346 | (pszVersion[2] == '.') ) |
11347 | { |
11348 | // Looks like a version. Is it lesser than v4.0 major version where we start using new behavior? |
11349 | fBuiltForPreV4Runtime = ((DIGIT_TO_INT(pszVersion[1]) != 0) && |
11350 | (DIGIT_TO_INT(pszVersion[1]) <= HIGHEST_MAJOR_VERSION_OF_PREV4_RUNTIME)); |
11351 | } |
11352 | } |
11353 | } |
11354 | |
11355 | return fBuiltForPreV4Runtime; |
11356 | } |
11357 | |
11358 | // Given a MethodDesc and CorruptionSeverity, this method will return a |
11359 | // BOOL indicating if the method can handle those kinds of CEs or not. |
11360 | /* static */ |
11361 | BOOL CEHelper::CanMethodHandleCE(PTR_MethodDesc pMethodDesc, CorruptionSeverity severity, BOOL fCalculateSecurityInfo /*= TRUE*/) |
11362 | { |
11363 | BOOL fCanMethodHandleSeverity = FALSE; |
11364 | |
11365 | #ifndef DACCESS_COMPILE |
11366 | CONTRACTL |
11367 | { |
11368 | if (fCalculateSecurityInfo) |
11369 | { |
11370 | GC_TRIGGERS; // CEHelper::CanMethodHandleCE will invoke Security::IsMethodCritical that could endup invoking MethodTable::LoadEnclosingMethodTable that is GC_TRIGGERS |
11371 | } |
11372 | else |
11373 | { |
11374 | // See comment in COMPlusUnwindCallback for details. |
11375 | GC_NOTRIGGER; |
11376 | } |
11377 | // First pass requires THROWS and in 2nd we need to be due to the AppX check below where GetFusionAssemblyName can throw. |
11378 | THROWS; |
11379 | MODE_ANY; |
11380 | PRECONDITION(pMethodDesc != NULL); |
11381 | } |
11382 | CONTRACTL_END; |
11383 | |
11384 | |
11385 | if (g_pConfig->LegacyCorruptedStateExceptionsPolicy()) |
11386 | { |
11387 | return TRUE; |
11388 | } |
11389 | |
11390 | // Since the method is Security Critical, now check if it is |
11391 | // attributed to handle the CE or not. |
11392 | IMDInternalImport *pImport = pMethodDesc->GetMDImport(); |
11393 | if (pImport != NULL) |
11394 | { |
11395 | mdMethodDef methodDef = pMethodDesc->GetMemberDef(); |
11396 | switch(severity) |
11397 | { |
11398 | case ProcessCorrupting: |
11399 | fCanMethodHandleSeverity = (S_OK == pImport->GetCustomAttributeByName( |
11400 | methodDef, |
11401 | HANDLE_PROCESS_CORRUPTED_STATE_EXCEPTION_ATTRIBUTE, |
11402 | NULL, |
11403 | NULL)); |
11404 | break; |
11405 | default: |
11406 | _ASSERTE(!"Unknown Exception Corruption Severity!" ); |
11407 | break; |
11408 | } |
11409 | } |
11410 | #endif // !DACCESS_COMPILE |
11411 | |
11412 | return fCanMethodHandleSeverity; |
11413 | } |
11414 | |
11415 | // Given a MethodDesc, this method will return a BOOL to indicate if the method should be examined for exception |
11416 | // handlers for the specified exception. |
11417 | // |
11418 | // This method accounts for both corrupting and non-corrupting exceptions. |
11419 | /* static */ |
11420 | BOOL CEHelper::CanMethodHandleException(CorruptionSeverity severity, PTR_MethodDesc pMethodDesc, BOOL fCalculateSecurityInfo /*= TRUE*/) |
11421 | { |
11422 | CONTRACTL |
11423 | { |
11424 | // CEHelper::CanMethodHandleCE will invoke Security::IsMethodCritical that could endup invoking MethodTable::LoadEnclosingMethodTable that is GC_TRIGGERS/THROWS |
11425 | if (fCalculateSecurityInfo) |
11426 | { |
11427 | GC_TRIGGERS; |
11428 | } |
11429 | else |
11430 | { |
11431 | // See comment in COMPlusUnwindCallback for details. |
11432 | GC_NOTRIGGER; |
11433 | } |
11434 | THROWS; |
11435 | MODE_ANY; |
11436 | PRECONDITION(pMethodDesc != NULL); |
11437 | } |
11438 | CONTRACTL_END; |
11439 | |
11440 | // By default, assume that the runtime shouldn't look for exception handlers |
11441 | // in the method pointed by the MethodDesc |
11442 | BOOL fLookForExceptionHandlersInMethod = FALSE; |
11443 | |
11444 | if (g_pConfig->LegacyCorruptedStateExceptionsPolicy()) |
11445 | { |
11446 | return TRUE; |
11447 | } |
11448 | |
11449 | // If we have been asked to use the last active corruption severity (e.g. in cases of Reflection |
11450 | // or COM interop), then retrieve it. |
11451 | if (severity == UseLast) |
11452 | { |
11453 | LOG((LF_EH, LL_INFO100, "CEHelper::CanMethodHandleException - Using LastActiveExceptionCorruptionSeverity.\n" )); |
11454 | severity = GetThread()->GetExceptionState()->GetLastActiveExceptionCorruptionSeverity(); |
11455 | } |
11456 | |
11457 | LOG((LF_EH, LL_INFO100, "CEHelper::CanMethodHandleException - Processing CorruptionSeverity: %d.\n" , severity)); |
11458 | |
11459 | if (severity > NotCorrupting) |
11460 | { |
11461 | // If the method lies in an assembly built for pre-V4 runtime, allow the runtime |
11462 | // to look for exception handler for the CE. |
11463 | BOOL fIsMethodInPreV4Assembly = FALSE; |
11464 | fIsMethodInPreV4Assembly = CEHelper::IsMethodInPreV4Assembly(pMethodDesc); |
11465 | |
11466 | if (!fIsMethodInPreV4Assembly) |
11467 | { |
11468 | // Method lies in an assembly built for V4 or later runtime. |
11469 | LOG((LF_EH, LL_INFO100, "CEHelper::CanMethodHandleException - Method is in an assembly built for V4 or later runtime.\n" )); |
11470 | |
11471 | // Depending upon the corruption severity of the exception, see if the |
11472 | // method supports handling that. |
11473 | LOG((LF_EH, LL_INFO100, "CEHelper::CanMethodHandleException - Exception is corrupting.\n" )); |
11474 | |
11475 | // Check if the method can handle the severity specified in the exception object. |
11476 | fLookForExceptionHandlersInMethod = CEHelper::CanMethodHandleCE(pMethodDesc, severity, fCalculateSecurityInfo); |
11477 | } |
11478 | else |
11479 | { |
11480 | // Method is in a Pre-V4 assembly - allow it to be examined for processing the CE |
11481 | fLookForExceptionHandlersInMethod = TRUE; |
11482 | } |
11483 | } |
11484 | else |
11485 | { |
11486 | // Non-corrupting exceptions can continue to be delivered |
11487 | fLookForExceptionHandlersInMethod = TRUE; |
11488 | } |
11489 | |
11490 | return fLookForExceptionHandlersInMethod; |
11491 | } |
11492 | |
11493 | // Given a managed exception object, this method will return a BOOL |
11494 | // indicating if it corresponds to a ProcessCorruptedState exception |
11495 | // or not. |
11496 | /* static */ |
11497 | BOOL CEHelper::IsProcessCorruptedStateException(OBJECTREF oThrowable) |
11498 | { |
11499 | CONTRACTL |
11500 | { |
11501 | NOTHROW; |
11502 | GC_NOTRIGGER; |
11503 | MODE_COOPERATIVE; |
11504 | SO_TOLERANT; |
11505 | PRECONDITION(oThrowable != NULL); |
11506 | } |
11507 | CONTRACTL_END; |
11508 | |
11509 | if (g_pConfig->LegacyCorruptedStateExceptionsPolicy()) |
11510 | { |
11511 | return FALSE; |
11512 | } |
11513 | |
11514 | #ifndef DACCESS_COMPILE |
11515 | // If the throwable represents preallocated SO, then indicate it as a CSE |
11516 | if (CLRException::GetPreallocatedStackOverflowException() == oThrowable) |
11517 | { |
11518 | return TRUE; |
11519 | } |
11520 | #endif // !DACCESS_COMPILE |
11521 | |
11522 | // Check if we have an exception tracker for this exception |
11523 | // and if so, if it represents corrupting exception or not. |
11524 | // Get the exception tracker for the current exception |
11525 | #ifdef WIN64EXCEPTIONS |
11526 | PTR_ExceptionTracker pEHTracker = GetEHTrackerForException(oThrowable, NULL); |
11527 | #elif _TARGET_X86_ |
11528 | PTR_ExInfo pEHTracker = GetEHTrackerForException(oThrowable, NULL); |
11529 | #else |
11530 | #error Unsupported platform |
11531 | #endif |
11532 | |
11533 | if (pEHTracker != NULL) |
11534 | { |
11535 | // Found the tracker for exception object - check if its CSE or not. |
11536 | return (pEHTracker->GetCorruptionSeverity() == ProcessCorrupting); |
11537 | } |
11538 | |
11539 | return FALSE; |
11540 | } |
11541 | |
11542 | #ifdef WIN64EXCEPTIONS |
11543 | void CEHelper::SetupCorruptionSeverityForActiveExceptionInUnwindPass(Thread *pCurThread, PTR_ExceptionTracker pEHTracker, BOOL fIsFirstPass, |
11544 | DWORD dwExceptionCode) |
11545 | { |
11546 | #ifndef DACCESS_COMPILE |
11547 | CONTRACTL |
11548 | { |
11549 | NOTHROW; |
11550 | GC_NOTRIGGER; |
11551 | MODE_ANY; |
11552 | SO_TOLERANT; |
11553 | PRECONDITION(!fIsFirstPass); // This method should only be called during an unwind |
11554 | PRECONDITION(pCurThread != NULL); |
11555 | } |
11556 | CONTRACTL_END; |
11557 | |
11558 | // <WIN64> |
11559 | // |
11560 | // Typically, exception tracker is created for an exception when the OS is in the first pass. |
11561 | // However, it may be created during the 2nd pass under specific cases. Managed C++ provides |
11562 | // such a scenario. In the following, stack grows left to right: |
11563 | // |
11564 | // CallDescrWorker -> ILStub1 -> <Native Main> -> UMThunkStub -> IL_Stub2 -> <Managed Main> |
11565 | // |
11566 | // If a CSE exception goes unhandled from managed main, it will reach the OS. The [CRT in?] OS triggers |
11567 | // unwind that results in invoking the personality routine of UMThunkStub, called UMThunkStubUnwindFrameChainHandler, |
11568 | // that releases all exception trackers below it. Thus, the tracker for the CSE, which went unhandled, is also |
11569 | // released. This detail is 64bit specific and the crux of this issue. |
11570 | // |
11571 | // Now, it is expected that by the time we are in the unwind pass, the corruption severity would have already been setup in the |
11572 | // exception tracker and thread exception state (TES) as part of the first pass, and thus, are identical. |
11573 | // |
11574 | // However, for the scenario above, when the unwind continues and reaches ILStub1, its personality routine (which is ProcessCLRException) |
11575 | // is invoked. It attempts to get the exception tracker corresponding to the exception. Since none exists, it creates a brand new one, |
11576 | // which has the exception corruption severity as NotSet. |
11577 | // |
11578 | // During the stack walk, we know (from TES) that the active exception was a CSE, and thus, ILStub1 cannot handle the exception. Prior |
11579 | // to bailing out, we assert that our data structures are intact by comparing the exception severity in TES with the one in the current |
11580 | // exception tracker. Since the tracker was recreated, it had the severity as NotSet and this does not match the severity in TES. |
11581 | // Thus, the assert fires. [This check is performed in ProcessManagedCallFrame.] |
11582 | // |
11583 | // To address such a case, if we have created a new exception tracker in the unwind (2nd) pass, then set its |
11584 | // exception corruption severity to what the TES holds currently. This will maintain the same semantic as the case |
11585 | // where new tracker is not created (for e.g. the exception was caught in Managed main). |
11586 | // |
11587 | // The exception is the scenario of code that uses longjmp to jump to a different context. Longjmp results in a raise |
11588 | // of a new exception with the longjmp exception code (0x80000026) but with ExceptionFlags set indicating unwind. When this is |
11589 | // seen by ProcessCLRException (64bit personality routine), it will create a new tracker in the 2nd pass. |
11590 | // |
11591 | // Longjmp outside an exceptional path does not interest us, but the one in the exceptional |
11592 | // path would only happen when a method attributed to handle CSE invokes it. Thus, if the longjmp happened during the 2nd pass of a CSE, |
11593 | // we want it to proceed (and thus, jump) as expected and not apply the CSE severity to the tracker - this is equivalent to |
11594 | // a catch block that handles a CSE and then does a "throw new Exception();". The new exception raised is |
11595 | // non-CSE in nature as well. |
11596 | // |
11597 | // http://www.nynaeve.net/?p=105 has a brief description of how exception-safe setjmp/longjmp works. |
11598 | // |
11599 | // </WIN64> |
11600 | if (pEHTracker->GetCorruptionSeverity() == NotSet) |
11601 | { |
11602 | // Get the thread exception state |
11603 | ThreadExceptionState *pCurTES = pCurThread->GetExceptionState(); |
11604 | |
11605 | // Set the tracker to have the same corruption severity as the last active severity unless we are dealing |
11606 | // with LONGJMP |
11607 | if (dwExceptionCode == STATUS_LONGJUMP) |
11608 | { |
11609 | pCurTES->SetLastActiveExceptionCorruptionSeverity(NotCorrupting); |
11610 | } |
11611 | |
11612 | pEHTracker->SetCorruptionSeverity(pCurTES->GetLastActiveExceptionCorruptionSeverity()); |
11613 | LOG((LF_EH, LL_INFO100, "CEHelper::SetupCorruptionSeverityForActiveExceptionInUnwindPass - Setup the corruption severity in the second pass.\n" )); |
11614 | } |
11615 | #endif // !DACCESS_COMPILE |
11616 | } |
11617 | #endif // WIN64EXCEPTIONS |
11618 | |
11619 | // This method is invoked from the personality routine for managed code and is used to setup the |
11620 | // corruption severity for the active exception on the thread exception state and the |
11621 | // exception tracker corresponding to the exception. |
11622 | /* static */ |
11623 | void CEHelper::SetupCorruptionSeverityForActiveException(BOOL fIsRethrownException, BOOL fIsNestedException, BOOL fShouldTreatExceptionAsNonCorrupting /* = FALSE */) |
11624 | { |
11625 | #ifndef DACCESS_COMPILE |
11626 | CONTRACTL |
11627 | { |
11628 | NOTHROW; |
11629 | GC_NOTRIGGER; |
11630 | MODE_COOPERATIVE; |
11631 | } |
11632 | CONTRACTL_END; |
11633 | |
11634 | // Get the thread and the managed exception object - they must exist at this point |
11635 | Thread *pCurThread = GetThread(); |
11636 | _ASSERTE(pCurThread != NULL); |
11637 | |
11638 | OBJECTREF oThrowable = pCurThread->GetThrowable(); |
11639 | _ASSERTE(oThrowable != NULL); |
11640 | |
11641 | // Get the thread exception state |
11642 | ThreadExceptionState * pCurTES = pCurThread->GetExceptionState(); |
11643 | _ASSERTE(pCurTES != NULL); |
11644 | |
11645 | // Get the exception tracker for the current exception |
11646 | #ifdef WIN64EXCEPTIONS |
11647 | PTR_ExceptionTracker pEHTracker = pCurTES->GetCurrentExceptionTracker(); |
11648 | #elif _TARGET_X86_ |
11649 | PTR_ExInfo pEHTracker = pCurTES->GetCurrentExceptionTracker(); |
11650 | #else // !(_WIN64 || _TARGET_X86_) |
11651 | #error Unsupported platform |
11652 | #endif // _WIN64 |
11653 | |
11654 | _ASSERTE(pEHTracker != NULL); |
11655 | |
11656 | // Get the current exception code from the tracker. |
11657 | PEXCEPTION_RECORD pEHRecord = pCurTES->GetExceptionRecord(); |
11658 | _ASSERTE(pEHRecord != NULL); |
11659 | DWORD dwActiveExceptionCode = pEHRecord->ExceptionCode; |
11660 | |
11661 | if (pEHTracker->GetCorruptionSeverity() != NotSet) |
11662 | { |
11663 | // Since the exception tracker already has the corruption severity set, |
11664 | // we dont have much to do. Just confirm that our assumptions are correct. |
11665 | _ASSERTE(pEHTracker->GetCorruptionSeverity() == pCurTES->GetLastActiveExceptionCorruptionSeverity()); |
11666 | |
11667 | LOG((LF_EH, LL_INFO100, "CEHelper::SetupCorruptionSeverityForActiveException - Current tracker already has the corruption severity set.\n" )); |
11668 | return; |
11669 | } |
11670 | |
11671 | // If the exception in question is to be treated as non-corrupting, |
11672 | // then flag it and exit. |
11673 | if (fShouldTreatExceptionAsNonCorrupting || g_pConfig->LegacyCorruptedStateExceptionsPolicy()) |
11674 | { |
11675 | pEHTracker->SetCorruptionSeverity(NotCorrupting); |
11676 | LOG((LF_EH, LL_INFO100, "CEHelper::SetupCorruptionSeverityForActiveException - Exception treated as non-corrupting.\n" )); |
11677 | goto done; |
11678 | } |
11679 | |
11680 | if (!fIsRethrownException && !fIsNestedException) |
11681 | { |
11682 | // There should be no previously active exception for this case |
11683 | _ASSERTE(pEHTracker->GetPreviousExceptionTracker() == NULL); |
11684 | |
11685 | CorruptionSeverity severityTES = NotSet; |
11686 | |
11687 | if (pCurTES->ShouldLastActiveExceptionCorruptionSeverityBeReused()) |
11688 | { |
11689 | // Get the corruption severity from the ThreadExceptionState (TES) for the last active exception |
11690 | severityTES = pCurTES->GetLastActiveExceptionCorruptionSeverity(); |
11691 | |
11692 | // Incase of scenarios like AD transition or Reflection invocation, |
11693 | // TES would hold corruption severity of the last active exception. To propagate it |
11694 | // to the current exception, we will apply it to current tracker and only if the applied |
11695 | // severity is "NotSet", will we proceed to check the current exception for corruption |
11696 | // severity. |
11697 | pEHTracker->SetCorruptionSeverity(severityTES); |
11698 | } |
11699 | |
11700 | // Reset TES Corruption Severity |
11701 | pCurTES->SetLastActiveExceptionCorruptionSeverity(NotSet); |
11702 | |
11703 | if (severityTES == NotSet) |
11704 | { |
11705 | // Since the last active exception's severity was "NotSet", we will look up the |
11706 | // exception code and the exception object to see if the exception should be marked |
11707 | // corrupting. |
11708 | // |
11709 | // Since this exception was neither rethrown nor is nested, it implies that we are |
11710 | // outside an active exception. Thus, even if it contains inner exceptions, we wont have |
11711 | // corruption severity for them since that information is tracked in EH tracker and |
11712 | // we wont have an EH tracker for the inner most exception. |
11713 | |
11714 | if (CEHelper::IsProcessCorruptedStateException(dwActiveExceptionCode) || |
11715 | CEHelper::IsProcessCorruptedStateException(oThrowable)) |
11716 | { |
11717 | pEHTracker->SetCorruptionSeverity(ProcessCorrupting); |
11718 | LOG((LF_EH, LL_INFO100, "CEHelper::SetupCorruptionSeverityForActiveException - Marked non-rethrow/non-nested exception as ProcessCorrupting.\n" )); |
11719 | } |
11720 | else |
11721 | { |
11722 | pEHTracker->SetCorruptionSeverity(NotCorrupting); |
11723 | LOG((LF_EH, LL_INFO100, "CEHelper::SetupCorruptionSeverityForActiveException - Marked non-rethrow/non-nested exception as NotCorrupting.\n" )); |
11724 | } |
11725 | } |
11726 | else |
11727 | { |
11728 | LOG((LF_EH, LL_INFO100, "CEHelper::SetupCorruptionSeverityForActiveException - Copied the corruption severity to tracker from ThreadExceptionState for non-rethrow/non-nested exception.\n" )); |
11729 | } |
11730 | } |
11731 | else |
11732 | { |
11733 | // Its either a rethrow or nested exception |
11734 | |
11735 | #ifdef WIN64EXCEPTIONS |
11736 | PTR_ExceptionTracker pOrigEHTracker = NULL; |
11737 | #elif _TARGET_X86_ |
11738 | PTR_ExInfo pOrigEHTracker = NULL; |
11739 | #else |
11740 | #error Unsupported platform |
11741 | #endif |
11742 | |
11743 | BOOL fDoWeHaveCorruptionSeverity = FALSE; |
11744 | |
11745 | if (fIsRethrownException) |
11746 | { |
11747 | // Rethrown exceptions are nested by nature (of our implementation). The |
11748 | // original EHTracker will exist for the exception - infact, it will be |
11749 | // the tracker previous to the current one. We will simply copy |
11750 | // its severity to the current EH tracker representing the rethrow. |
11751 | pOrigEHTracker = pEHTracker->GetPreviousExceptionTracker(); |
11752 | _ASSERTE(pOrigEHTracker != NULL); |
11753 | |
11754 | // Ideally, we would like have the assert below enabled. But, as may happen under OOM |
11755 | // stress, this can be false. Here's how it will happen: |
11756 | // |
11757 | // An exception is thrown, which is later caught and rethrown in the catch block. Rethrow |
11758 | // results in calling IL_Rethrow that will call RaiseTheExceptionInternalOnly to actually |
11759 | // raise the exception. Prior to the raise, we update the last thrown object on the thread |
11760 | // by calling Thread::SafeSetLastThrownObject which, internally, could have an OOM, resulting |
11761 | // in "changing" the throwable used to raise the exception to be preallocated OOM object. |
11762 | // |
11763 | // When the rethrow happens and CLR's exception handler for managed code sees the exception, |
11764 | // the exception tracker created for the rethrown exception will contain the reference to |
11765 | // the last thrown object, which will be the preallocated OOM object. |
11766 | // |
11767 | // Thus, though, we came here because of a rethrow, and logically, the throwable should remain |
11768 | // the same, it neednt be. Simply put, rethrow can result in working with a completely different |
11769 | // exception object than what was originally thrown. |
11770 | // |
11771 | // Hence, the assert cannot be enabled. |
11772 | // |
11773 | // Thus, we will use the EH tracker corresponding to the original exception, to get the |
11774 | // rethrown exception's corruption severity, only when the rethrown throwable is the same |
11775 | // as the original throwable. Otherwise, we will pretend that we didnt get the original tracker |
11776 | // and will automatically enter the path below to set the corruption severity based upon the |
11777 | // rethrown throwable. |
11778 | |
11779 | // _ASSERTE(pOrigEHTracker->GetThrowable() == oThrowable); |
11780 | if (pOrigEHTracker->GetThrowable() != oThrowable) |
11781 | { |
11782 | pOrigEHTracker = NULL; |
11783 | LOG((LF_EH, LL_INFO100, "CEHelper::SetupCorruptionSeverityForActiveException - Rethrown throwable does not match the original throwable. Corruption severity will be set based upon rethrown throwable.\n" )); |
11784 | } |
11785 | } |
11786 | else |
11787 | { |
11788 | // Get the corruption severity from the ThreadExceptionState (TES) for the last active exception |
11789 | CorruptionSeverity severityTES = NotSet; |
11790 | |
11791 | if (pCurTES->ShouldLastActiveExceptionCorruptionSeverityBeReused()) |
11792 | { |
11793 | severityTES = pCurTES->GetLastActiveExceptionCorruptionSeverity(); |
11794 | |
11795 | // Incase of scenarios like AD transition or Reflection invocation, |
11796 | // TES would hold corruption severity of the last active exception. To propagate it |
11797 | // to the current exception, we will apply it to current tracker and only if the applied |
11798 | // severity is "NotSet", will we proceed to check the current exception for corruption |
11799 | // severity. |
11800 | pEHTracker->SetCorruptionSeverity(severityTES); |
11801 | } |
11802 | |
11803 | // Reset TES Corruption Severity |
11804 | pCurTES->SetLastActiveExceptionCorruptionSeverity(NotSet); |
11805 | |
11806 | // If the last exception didnt have any corruption severity, proceed to look for it. |
11807 | if (severityTES == NotSet) |
11808 | { |
11809 | // This is a nested exception - check if it has an inner exception(s). If it does, |
11810 | // find the EH tracker corresponding to the innermost exception and we will copy the |
11811 | // corruption severity from the original tracker to the current one. |
11812 | OBJECTREF oInnermostThrowable = ((EXCEPTIONREF)oThrowable)->GetBaseException(); |
11813 | if (oInnermostThrowable != NULL) |
11814 | { |
11815 | // Find the tracker corresponding to the inner most exception, starting from |
11816 | // the tracker previous to the current one. An EH tracker may not be found if |
11817 | // the code did the following inside a catch clause: |
11818 | // |
11819 | // Exception ex = new Exception("inner exception"); |
11820 | // throw new Exception("message", ex); |
11821 | // |
11822 | // Or, an exception like AV happened in the catch clause. |
11823 | pOrigEHTracker = GetEHTrackerForException(oInnermostThrowable, pEHTracker->GetPreviousExceptionTracker()); |
11824 | } |
11825 | } |
11826 | else |
11827 | { |
11828 | // We have the corruption severity from the TES. Set the flag indicating so. |
11829 | fDoWeHaveCorruptionSeverity = TRUE; |
11830 | LOG((LF_EH, LL_INFO100, "CEHelper::SetupCorruptionSeverityForActiveException - Copied the corruption severity to tracker from ThreadExceptionState for nested exception.\n" )); |
11831 | } |
11832 | } |
11833 | |
11834 | if (!fDoWeHaveCorruptionSeverity) |
11835 | { |
11836 | if (pOrigEHTracker != NULL) |
11837 | { |
11838 | // Copy the severity from the original EH tracker to the current one |
11839 | CorruptionSeverity origCorruptionSeverity = pOrigEHTracker->GetCorruptionSeverity(); |
11840 | _ASSERTE(origCorruptionSeverity != NotSet); |
11841 | pEHTracker->SetCorruptionSeverity(origCorruptionSeverity); |
11842 | |
11843 | LOG((LF_EH, LL_INFO100, "CEHelper::SetupCorruptionSeverityForActiveException - Copied the corruption severity (%d) from the original EH tracker for rethrown exception.\n" , origCorruptionSeverity)); |
11844 | } |
11845 | else |
11846 | { |
11847 | if (CEHelper::IsProcessCorruptedStateException(dwActiveExceptionCode) || |
11848 | CEHelper::IsProcessCorruptedStateException(oThrowable)) |
11849 | { |
11850 | pEHTracker->SetCorruptionSeverity(ProcessCorrupting); |
11851 | LOG((LF_EH, LL_INFO100, "CEHelper::SetupCorruptionSeverityForActiveException - Marked nested exception as ProcessCorrupting.\n" )); |
11852 | } |
11853 | else |
11854 | { |
11855 | pEHTracker->SetCorruptionSeverity(NotCorrupting); |
11856 | LOG((LF_EH, LL_INFO100, "CEHelper::SetupCorruptionSeverityForActiveException - Marked nested exception as NotCorrupting.\n" )); |
11857 | } |
11858 | } |
11859 | } |
11860 | } |
11861 | |
11862 | done: |
11863 | // Save the current exception's corruption severity in the ThreadExceptionState (TES) |
11864 | // for cases when we catch the managed exception in the runtime using EX_CATCH. |
11865 | // At such a time, all exception trackers get released (due to unwind triggered |
11866 | // by EX_END_CATCH) and yet we need the corruption severity information for |
11867 | // scenarios like AD Transition, Reflection invocation, etc. |
11868 | CorruptionSeverity currentSeverity = pEHTracker->GetCorruptionSeverity(); |
11869 | |
11870 | // We should be having a valid corruption severity at this point |
11871 | _ASSERTE(currentSeverity != NotSet); |
11872 | |
11873 | // Save it in the TES |
11874 | pCurTES->SetLastActiveExceptionCorruptionSeverity(currentSeverity); |
11875 | LOG((LF_EH, LL_INFO100, "CEHelper::SetupCorruptionSeverityForActiveException - Copied the corruption severity (%d) to ThreadExceptionState.\n" , currentSeverity)); |
11876 | |
11877 | #endif // !DACCESS_COMPILE |
11878 | } |
11879 | |
11880 | // CE can be caught in the VM and later reraised again. Examples of such scenarios |
11881 | // include AD transition, COM interop, Reflection invocation, to name a few. |
11882 | // In such cases, we want to mark the corruption severity for reuse upon reraise, |
11883 | // implying that when the VM does a reraise of such an exception, we should use |
11884 | // the original corruption severity for the new raised exception, instead of creating |
11885 | // a new one for it. |
11886 | /* static */ |
11887 | void CEHelper::MarkLastActiveExceptionCorruptionSeverityForReraiseReuse() |
11888 | { |
11889 | CONTRACTL |
11890 | { |
11891 | NOTHROW; |
11892 | GC_NOTRIGGER; |
11893 | MODE_ANY; |
11894 | SO_TOLERANT; |
11895 | PRECONDITION(GetThread() != NULL); |
11896 | } |
11897 | CONTRACTL_END; |
11898 | |
11899 | // If the last active exception's corruption severity is anything but |
11900 | // "NotSet", mark it for ReraiseReuse |
11901 | ThreadExceptionState *pCurTES = GetThread()->GetExceptionState(); |
11902 | _ASSERTE(pCurTES != NULL); |
11903 | |
11904 | CorruptionSeverity severityTES = pCurTES->GetLastActiveExceptionCorruptionSeverity(); |
11905 | if (severityTES != NotSet) |
11906 | { |
11907 | pCurTES->SetLastActiveExceptionCorruptionSeverity((CorruptionSeverity)(severityTES | ReuseForReraise)); |
11908 | } |
11909 | } |
11910 | |
11911 | // This method will return a BOOL to indicate if the current exception is to be treated as |
11912 | // non-corrupting. Currently, this returns true for NullReferenceException only. |
11913 | /* static */ |
11914 | BOOL CEHelper::ShouldTreatActiveExceptionAsNonCorrupting() |
11915 | { |
11916 | BOOL fShouldTreatAsNonCorrupting = FALSE; |
11917 | |
11918 | #ifndef DACCESS_COMPILE |
11919 | CONTRACTL |
11920 | { |
11921 | THROWS; |
11922 | GC_TRIGGERS; |
11923 | MODE_COOPERATIVE; |
11924 | PRECONDITION(GetThread() != NULL); |
11925 | } |
11926 | CONTRACTL_END; |
11927 | |
11928 | if (g_pConfig->LegacyCorruptedStateExceptionsPolicy()) |
11929 | { |
11930 | return TRUE; |
11931 | } |
11932 | |
11933 | DWORD dwActiveExceptionCode = GetThread()->GetExceptionState()->GetExceptionRecord()->ExceptionCode; |
11934 | if (dwActiveExceptionCode == STATUS_ACCESS_VIOLATION) |
11935 | { |
11936 | // NullReference has the same exception code as AV |
11937 | OBJECTREF oThrowable = NULL; |
11938 | GCPROTECT_BEGIN(oThrowable); |
11939 | |
11940 | // Get the throwable and check if it represents null reference exception |
11941 | oThrowable = GetThread()->GetThrowable(); |
11942 | _ASSERTE(oThrowable != NULL); |
11943 | if (MscorlibBinder::GetException(kNullReferenceException) == oThrowable->GetMethodTable()) |
11944 | { |
11945 | fShouldTreatAsNonCorrupting = TRUE; |
11946 | } |
11947 | GCPROTECT_END(); |
11948 | } |
11949 | #endif // !DACCESS_COMPILE |
11950 | |
11951 | return fShouldTreatAsNonCorrupting; |
11952 | } |
11953 | |
11954 | // If we were working in a nested exception scenario, reset the corruption severity to the last |
11955 | // exception we were processing, based upon its EH tracker. |
11956 | // |
11957 | // If none was present, reset it to NotSet. |
11958 | // |
11959 | // Note: This method must be called once the exception trackers have been adjusted post catch-block execution. |
11960 | /* static */ |
11961 | void CEHelper::ResetLastActiveCorruptionSeverityPostCatchHandler(Thread *pThread) |
11962 | { |
11963 | CONTRACTL |
11964 | { |
11965 | NOTHROW; |
11966 | GC_NOTRIGGER; |
11967 | MODE_ANY; |
11968 | PRECONDITION(pThread != NULL); |
11969 | } |
11970 | CONTRACTL_END; |
11971 | |
11972 | ThreadExceptionState *pCurTES = pThread->GetExceptionState(); |
11973 | |
11974 | // By this time, we would have set the correct exception tracker for the active exception domain, |
11975 | // if applicable. An example is throwing and catching an exception within a catch block. We will update |
11976 | // the LastActiveCorruptionSeverity based upon the active exception domain. If we are not in one, we will |
11977 | // set it to "NotSet". |
11978 | #ifdef WIN64EXCEPTIONS |
11979 | PTR_ExceptionTracker pEHTracker = pCurTES->GetCurrentExceptionTracker(); |
11980 | #elif _TARGET_X86_ |
11981 | PTR_ExInfo pEHTracker = pCurTES->GetCurrentExceptionTracker(); |
11982 | #else |
11983 | #error Unsupported platform |
11984 | #endif |
11985 | |
11986 | if (pEHTracker) |
11987 | { |
11988 | pCurTES->SetLastActiveExceptionCorruptionSeverity(pEHTracker->GetCorruptionSeverity()); |
11989 | } |
11990 | else |
11991 | { |
11992 | pCurTES->SetLastActiveExceptionCorruptionSeverity(NotSet); |
11993 | } |
11994 | |
11995 | LOG((LF_EH, LL_INFO100, "CEHelper::ResetLastActiveCorruptionSeverityPostCatchHandler - Reset LastActiveException corruption severity to %d.\n" , |
11996 | pCurTES->GetLastActiveExceptionCorruptionSeverity())); |
11997 | } |
11998 | |
11999 | // This method will return a BOOL indicating if the target of IDispatch can handle the specified exception or not. |
12000 | /* static */ |
12001 | BOOL CEHelper::CanIDispatchTargetHandleException() |
12002 | { |
12003 | CONTRACTL |
12004 | { |
12005 | NOTHROW; |
12006 | GC_NOTRIGGER; |
12007 | MODE_ANY; |
12008 | PRECONDITION(GetThread() != NULL); |
12009 | } |
12010 | CONTRACTL_END; |
12011 | |
12012 | // By default, assume that the target of IDispatch cannot handle the exception. |
12013 | BOOL fCanMethodHandleException = FALSE; |
12014 | |
12015 | if (g_pConfig->LegacyCorruptedStateExceptionsPolicy()) |
12016 | { |
12017 | return TRUE; |
12018 | } |
12019 | |
12020 | // IDispatch implementation in COM interop works by invoking the actual target via reflection. |
12021 | // Thus, a COM client could use the V4 runtime to invoke a V2 method. In such a case, a CSE |
12022 | // could come unhandled at the actual target invoked via reflection. |
12023 | // |
12024 | // Reflection invocation would have set a flag for us, indicating if the actual target was |
12025 | // enabled to handle the CE or not. If it is, then we should allow the COM client to get the |
12026 | // hresult from the call and not let the exception continue up the stack. |
12027 | ThreadExceptionState *pCurTES = GetThread()->GetExceptionState(); |
12028 | fCanMethodHandleException = pCurTES->CanReflectionTargetHandleException(); |
12029 | |
12030 | // Reset the flag so that subsequent invocations work as expected. |
12031 | pCurTES->SetCanReflectionTargetHandleException(FALSE); |
12032 | |
12033 | return fCanMethodHandleException; |
12034 | } |
12035 | |
12036 | #endif // FEATURE_CORRUPTING_EXCEPTIONS |
12037 | |
12038 | #ifndef DACCESS_COMPILE |
12039 | // This method will deliver the actual exception notification. Its assumed that the caller has done the necessary checks, including |
12040 | // checking whether the delegate can be invoked for the exception's corruption severity. |
12041 | void ExceptionNotifications::DeliverExceptionNotification(ExceptionNotificationHandlerType notificationType, OBJECTREF *pDelegate, |
12042 | OBJECTREF *pAppDomain, OBJECTREF *pEventArgs) |
12043 | { |
12044 | CONTRACTL |
12045 | { |
12046 | THROWS; |
12047 | GC_TRIGGERS; |
12048 | MODE_COOPERATIVE; |
12049 | PRECONDITION(pDelegate != NULL && IsProtectedByGCFrame(pDelegate) && (*pDelegate != NULL)); |
12050 | PRECONDITION(pEventArgs != NULL && IsProtectedByGCFrame(pEventArgs)); |
12051 | PRECONDITION(pAppDomain != NULL && IsProtectedByGCFrame(pAppDomain)); |
12052 | } |
12053 | CONTRACTL_END; |
12054 | |
12055 | PREPARE_NONVIRTUAL_CALLSITE_USING_CODE(DELEGATEREF(*pDelegate)->GetMethodPtr()); |
12056 | |
12057 | DECLARE_ARGHOLDER_ARRAY(args, 3); |
12058 | |
12059 | args[ARGNUM_0] = OBJECTREF_TO_ARGHOLDER(DELEGATEREF(*pDelegate)->GetTarget()); |
12060 | args[ARGNUM_1] = OBJECTREF_TO_ARGHOLDER(*pAppDomain); |
12061 | args[ARGNUM_2] = OBJECTREF_TO_ARGHOLDER(*pEventArgs); |
12062 | |
12063 | CALL_MANAGED_METHOD_NORET(args); |
12064 | } |
12065 | |
12066 | // To include definition of COMDelegate::GetMethodDesc |
12067 | #include "comdelegate.h" |
12068 | |
12069 | // This method constructs the arguments to be passed to the exception notification event callback |
12070 | void ExceptionNotifications::GetEventArgsForNotification(ExceptionNotificationHandlerType notificationType, |
12071 | OBJECTREF *pOutEventArgs, OBJECTREF *pThrowable) |
12072 | { |
12073 | CONTRACTL |
12074 | { |
12075 | THROWS; |
12076 | GC_TRIGGERS; |
12077 | MODE_COOPERATIVE; |
12078 | PRECONDITION(notificationType != UnhandledExceptionHandler); |
12079 | PRECONDITION((pOutEventArgs != NULL) && IsProtectedByGCFrame(pOutEventArgs)); |
12080 | PRECONDITION(*pOutEventArgs == NULL); |
12081 | PRECONDITION((pThrowable != NULL) && (*pThrowable != NULL) && IsProtectedByGCFrame(pThrowable)); |
12082 | PRECONDITION(IsException((*pThrowable)->GetMethodTable())); // We expect a valid exception object |
12083 | } |
12084 | CONTRACTL_END; |
12085 | |
12086 | MethodTable *pMTEventArgs = NULL; |
12087 | BinderMethodID idEventArgsCtor = METHOD__FIRSTCHANCE_EVENTARGS__CTOR; |
12088 | |
12089 | EX_TRY |
12090 | { |
12091 | switch(notificationType) |
12092 | { |
12093 | case FirstChanceExceptionHandler: |
12094 | pMTEventArgs = MscorlibBinder::GetClass(CLASS__FIRSTCHANCE_EVENTARGS); |
12095 | idEventArgsCtor = METHOD__FIRSTCHANCE_EVENTARGS__CTOR; |
12096 | break; |
12097 | default: |
12098 | _ASSERTE(!"Invalid Exception Notification Handler!" ); |
12099 | break; |
12100 | } |
12101 | |
12102 | // Allocate the instance of the eventargs corresponding to the notification |
12103 | *pOutEventArgs = AllocateObject(pMTEventArgs); |
12104 | |
12105 | // Prepare to invoke the .ctor |
12106 | MethodDescCallSite ctor(idEventArgsCtor, pOutEventArgs); |
12107 | |
12108 | // Setup the arguments to be passed to the notification specific EventArgs .ctor |
12109 | if (notificationType == FirstChanceExceptionHandler) |
12110 | { |
12111 | // FirstChance notification takes only a single argument: the exception object. |
12112 | ARG_SLOT args[] = |
12113 | { |
12114 | ObjToArgSlot(*pOutEventArgs), |
12115 | ObjToArgSlot(*pThrowable), |
12116 | }; |
12117 | |
12118 | ctor.Call(args); |
12119 | } |
12120 | else |
12121 | { |
12122 | // Since we have already asserted above, just set the args to NULL. |
12123 | *pOutEventArgs = NULL; |
12124 | } |
12125 | } |
12126 | EX_CATCH |
12127 | { |
12128 | // Set event args to be NULL incase of any error (e.g. OOM) |
12129 | *pOutEventArgs = NULL; |
12130 | LOG((LF_EH, LL_INFO100, "ExceptionNotifications::GetEventArgsForNotification: Setting event args to NULL due to an exception.\n" )); |
12131 | } |
12132 | EX_END_CATCH(RethrowCorruptingExceptions); // Dont swallow any CSE that may come in from the .ctor. |
12133 | } |
12134 | |
12135 | // This SEH filter will be invoked when an exception escapes out of the exception notification |
12136 | // callback and enters the runtime. In such a case, we ill simply failfast. |
12137 | static LONG ExceptionNotificationFilter(PEXCEPTION_POINTERS pExceptionInfo, LPVOID pParam) |
12138 | { |
12139 | EEPOLICY_HANDLE_FATAL_ERROR(COR_E_EXECUTIONENGINE); |
12140 | } |
12141 | |
12142 | #ifdef FEATURE_CORRUPTING_EXCEPTIONS |
12143 | // This method will return a BOOL indicating if the delegate should be invoked for the exception |
12144 | // of the specified corruption severity. |
12145 | BOOL ExceptionNotifications::CanDelegateBeInvokedForException(OBJECTREF *pDelegate, CorruptionSeverity severity) |
12146 | { |
12147 | CONTRACTL |
12148 | { |
12149 | THROWS; |
12150 | GC_TRIGGERS; |
12151 | MODE_COOPERATIVE; |
12152 | PRECONDITION(pDelegate != NULL && IsProtectedByGCFrame(pDelegate) && (*pDelegate != NULL)); |
12153 | PRECONDITION(severity > NotSet); |
12154 | } |
12155 | CONTRACTL_END; |
12156 | |
12157 | // Notifications for CSE are only delivered if the delegate target follows CSE rules. |
12158 | BOOL fCanMethodHandleException = g_pConfig->LegacyCorruptedStateExceptionsPolicy() ? TRUE:(severity == NotCorrupting); |
12159 | if (!fCanMethodHandleException) |
12160 | { |
12161 | EX_TRY |
12162 | { |
12163 | // Get the MethodDesc of the delegate to be invoked |
12164 | MethodDesc *pMDDelegate = COMDelegate::GetMethodDesc(*pDelegate); |
12165 | _ASSERTE(pMDDelegate != NULL); |
12166 | |
12167 | // Check the callback target and see if it is following CSE rules or not. |
12168 | fCanMethodHandleException = CEHelper::CanMethodHandleException(severity, pMDDelegate); |
12169 | } |
12170 | EX_CATCH |
12171 | { |
12172 | // Incase of any exceptions, pretend we cannot handle the exception |
12173 | fCanMethodHandleException = FALSE; |
12174 | LOG((LF_EH, LL_INFO100, "ExceptionNotifications::CanDelegateBeInvokedForException: Exception while trying to determine if exception notification can be invoked or not.\n" )); |
12175 | } |
12176 | EX_END_CATCH(RethrowCorruptingExceptions); // Dont swallow any CSEs. |
12177 | } |
12178 | |
12179 | return fCanMethodHandleException; |
12180 | } |
12181 | #endif // FEATURE_CORRUPTING_EXCEPTIONS |
12182 | |
12183 | // This method will make the actual delegate invocation for the exception notification to be delivered. If an |
12184 | // exception escapes out of the notification, our filter in ExceptionNotifications::DeliverNotification will |
12185 | // address it. |
12186 | void ExceptionNotifications::InvokeNotificationDelegate(ExceptionNotificationHandlerType notificationType, OBJECTREF *pDelegate, OBJECTREF *pEventArgs, |
12187 | OBJECTREF *pAppDomain |
12188 | #ifdef FEATURE_CORRUPTING_EXCEPTIONS |
12189 | , CorruptionSeverity severity |
12190 | #endif // FEATURE_CORRUPTING_EXCEPTIONS |
12191 | ) |
12192 | { |
12193 | CONTRACTL |
12194 | { |
12195 | THROWS; |
12196 | GC_TRIGGERS; |
12197 | MODE_COOPERATIVE; |
12198 | PRECONDITION(pDelegate != NULL && IsProtectedByGCFrame(pDelegate) && (*pDelegate != NULL)); |
12199 | PRECONDITION(pEventArgs != NULL && IsProtectedByGCFrame(pEventArgs)); |
12200 | PRECONDITION(pAppDomain != NULL && IsProtectedByGCFrame(pAppDomain)); |
12201 | #ifdef FEATURE_CORRUPTING_EXCEPTIONS |
12202 | PRECONDITION(severity > NotSet); |
12203 | #endif // FEATURE_CORRUPTING_EXCEPTIONS |
12204 | // Unhandled Exception Notification is delivered via Unhandled Exception Processing |
12205 | // mechanism. |
12206 | PRECONDITION(notificationType != UnhandledExceptionHandler); |
12207 | } |
12208 | CONTRACTL_END; |
12209 | |
12210 | #ifdef FEATURE_CORRUPTING_EXCEPTIONS |
12211 | // Notifications are delivered based upon corruption severity of the exception |
12212 | if (!ExceptionNotifications::CanDelegateBeInvokedForException(pDelegate, severity)) |
12213 | { |
12214 | LOG((LF_EH, LL_INFO100, "ExceptionNotifications::InvokeNotificationDelegate: Delegate cannot be invoked for corruption severity %d\n" , |
12215 | severity)); |
12216 | return; |
12217 | } |
12218 | #endif // FEATURE_CORRUPTING_EXCEPTIONS |
12219 | |
12220 | // We've already exercised the prestub on this delegate's COMDelegate::GetMethodDesc, |
12221 | // as part of wiring up a reliable event sink in the BCL. Deliver the notification. |
12222 | ExceptionNotifications::DeliverExceptionNotification(notificationType, pDelegate, pAppDomain, pEventArgs); |
12223 | } |
12224 | |
12225 | // This method returns a BOOL to indicate if the AppDomain is ready to receive exception notifications or not. |
12226 | BOOL ExceptionNotifications::CanDeliverNotificationToCurrentAppDomain(ExceptionNotificationHandlerType notificationType) |
12227 | { |
12228 | CONTRACTL |
12229 | { |
12230 | THROWS; |
12231 | GC_TRIGGERS; |
12232 | MODE_COOPERATIVE; |
12233 | PRECONDITION(GetThread() != NULL); |
12234 | PRECONDITION(notificationType != UnhandledExceptionHandler); |
12235 | } |
12236 | CONTRACTL_END; |
12237 | |
12238 | // Do we have handler(s) of the specific type wired up? |
12239 | if (notificationType == FirstChanceExceptionHandler) |
12240 | { |
12241 | return MscorlibBinder::GetField(FIELD__APPCONTEXT__FIRST_CHANCE_EXCEPTION)->GetStaticOBJECTREF() != NULL; |
12242 | } |
12243 | else |
12244 | { |
12245 | _ASSERTE(!"Invalid exception notification handler specified!" ); |
12246 | return FALSE; |
12247 | } |
12248 | } |
12249 | |
12250 | // This method wraps the call to the actual 'DeliverNotificationInternal' method in an SEH filter |
12251 | // so that if an exception escapes out of the notification callback, we will trigger failfast from |
12252 | // our filter. |
12253 | void ExceptionNotifications::DeliverNotification(ExceptionNotificationHandlerType notificationType, |
12254 | OBJECTREF *pThrowable |
12255 | #ifdef FEATURE_CORRUPTING_EXCEPTIONS |
12256 | , CorruptionSeverity severity |
12257 | #endif // FEATURE_CORRUPTING_EXCEPTIONS |
12258 | ) |
12259 | { |
12260 | STATIC_CONTRACT_GC_TRIGGERS; |
12261 | STATIC_CONTRACT_NOTHROW; // NOTHROW because incase of an exception, we will FailFast. |
12262 | STATIC_CONTRACT_MODE_COOPERATIVE; |
12263 | |
12264 | struct TryArgs |
12265 | { |
12266 | ExceptionNotificationHandlerType notificationType; |
12267 | OBJECTREF *pThrowable; |
12268 | #ifdef FEATURE_CORRUPTING_EXCEPTIONS |
12269 | CorruptionSeverity severity; |
12270 | #endif // FEATURE_CORRUPTING_EXCEPTIONS |
12271 | } args; |
12272 | |
12273 | args.notificationType = notificationType; |
12274 | args.pThrowable = pThrowable; |
12275 | #ifdef FEATURE_CORRUPTING_EXCEPTIONS |
12276 | args.severity = severity; |
12277 | #endif // FEATURE_CORRUPTING_EXCEPTIONS |
12278 | |
12279 | PAL_TRY(TryArgs *, pArgs, &args) |
12280 | { |
12281 | // Make the call to the actual method that will invoke the callbacks |
12282 | ExceptionNotifications::DeliverNotificationInternal(pArgs->notificationType, |
12283 | pArgs->pThrowable |
12284 | #ifdef FEATURE_CORRUPTING_EXCEPTIONS |
12285 | , pArgs->severity |
12286 | #endif // FEATURE_CORRUPTING_EXCEPTIONS |
12287 | ); |
12288 | } |
12289 | PAL_EXCEPT_FILTER(ExceptionNotificationFilter) |
12290 | { |
12291 | // We should never be entering this handler since there should be |
12292 | // no exception escaping out of a callback. If we are here, |
12293 | // failfast. |
12294 | EEPOLICY_HANDLE_FATAL_ERROR(COR_E_EXECUTIONENGINE); |
12295 | } |
12296 | PAL_ENDTRY; |
12297 | } |
12298 | |
12299 | // This method will deliver the exception notification to the current AppDomain. |
12300 | void ExceptionNotifications::DeliverNotificationInternal(ExceptionNotificationHandlerType notificationType, |
12301 | OBJECTREF *pThrowable |
12302 | #ifdef FEATURE_CORRUPTING_EXCEPTIONS |
12303 | , CorruptionSeverity severity |
12304 | #endif // FEATURE_CORRUPTING_EXCEPTIONS |
12305 | ) |
12306 | { |
12307 | CONTRACTL |
12308 | { |
12309 | THROWS; |
12310 | GC_TRIGGERS; |
12311 | MODE_COOPERATIVE; |
12312 | |
12313 | // Unhandled Exception Notification is delivered via Unhandled Exception Processing |
12314 | // mechanism. |
12315 | PRECONDITION(notificationType != UnhandledExceptionHandler); |
12316 | PRECONDITION((pThrowable != NULL) && (*pThrowable != NULL)); |
12317 | PRECONDITION(ExceptionNotifications::CanDeliverNotificationToCurrentAppDomain(notificationType)); |
12318 | #ifdef FEATURE_CORRUPTING_EXCEPTIONS |
12319 | PRECONDITION(severity > NotSet); // Exception corruption severity must be valid at this point. |
12320 | #endif // FEATURE_CORRUPTING_EXCEPTIONS |
12321 | } |
12322 | CONTRACTL_END; |
12323 | |
12324 | Thread *pCurThread = GetThread(); |
12325 | _ASSERTE(pCurThread != NULL); |
12326 | |
12327 | // Get the current AppDomain |
12328 | AppDomain *pCurDomain = GetAppDomain(); |
12329 | _ASSERTE(pCurDomain != NULL); |
12330 | |
12331 | struct |
12332 | { |
12333 | OBJECTREF oNotificationDelegate; |
12334 | PTRARRAYREF arrDelegates; |
12335 | OBJECTREF oInnerDelegate; |
12336 | OBJECTREF oEventArgs; |
12337 | OBJECTREF oCurrentThrowable; |
12338 | OBJECTREF oCurAppDomain; |
12339 | } gc; |
12340 | ZeroMemory(&gc, sizeof(gc)); |
12341 | |
12342 | // This will hold the MethodDesc of the callback that will be invoked. |
12343 | MethodDesc *pMDDelegate = NULL; |
12344 | |
12345 | GCPROTECT_BEGIN(gc); |
12346 | |
12347 | // Protect the throwable to be passed to the delegate callback |
12348 | gc.oCurrentThrowable = *pThrowable; |
12349 | |
12350 | // We expect a valid exception object |
12351 | _ASSERTE(IsException(gc.oCurrentThrowable->GetMethodTable())); |
12352 | |
12353 | // Save the reference to the current AppDomain. If the user code has |
12354 | // wired upto this event, then the managed AppDomain object will exist. |
12355 | gc.oCurAppDomain = pCurDomain->GetRawExposedObject(); |
12356 | |
12357 | // Get the reference to the delegate based upon the type of notification |
12358 | if (notificationType == FirstChanceExceptionHandler) |
12359 | { |
12360 | gc.oNotificationDelegate = MscorlibBinder::GetField(FIELD__APPCONTEXT__FIRST_CHANCE_EXCEPTION)->GetStaticOBJECTREF(); |
12361 | } |
12362 | else |
12363 | { |
12364 | gc.oNotificationDelegate = NULL; |
12365 | _ASSERTE(!"Invalid Exception Notification Handler specified!" ); |
12366 | } |
12367 | |
12368 | if (gc.oNotificationDelegate != NULL) |
12369 | { |
12370 | // Prevent any async exceptions from this moment on this thread |
12371 | ThreadPreventAsyncHolder prevAsync; |
12372 | |
12373 | gc.oEventArgs = NULL; |
12374 | |
12375 | // Get the arguments to be passed to the delegate callback. Incase of any |
12376 | // problem while allocating the event args, we will return a NULL. |
12377 | ExceptionNotifications::GetEventArgsForNotification(notificationType, &gc.oEventArgs, |
12378 | &gc.oCurrentThrowable); |
12379 | |
12380 | // Check if there are multiple callbacks registered? If there are, we will |
12381 | // loop through them, invoking each one at a time. Before invoking the target, |
12382 | // we will check if the target can be invoked based upon the corruption severity |
12383 | // for the active exception that was passed to us. |
12384 | gc.arrDelegates = (PTRARRAYREF) ((DELEGATEREF)(gc.oNotificationDelegate))->GetInvocationList(); |
12385 | if (gc.arrDelegates == NULL || !gc.arrDelegates->GetMethodTable()->IsArray()) |
12386 | { |
12387 | ExceptionNotifications::InvokeNotificationDelegate(notificationType, &gc.oNotificationDelegate, &gc.oEventArgs, |
12388 | &gc.oCurAppDomain |
12389 | #ifdef FEATURE_CORRUPTING_EXCEPTIONS |
12390 | , severity |
12391 | #endif // FEATURE_CORRUPTING_EXCEPTIONS |
12392 | ); |
12393 | } |
12394 | else |
12395 | { |
12396 | // The _invocationCount could be less than the array size, if we are sharing |
12397 | // immutable arrays cleverly. |
12398 | UINT_PTR cnt = ((DELEGATEREF)(gc.oNotificationDelegate))->GetInvocationCount(); |
12399 | _ASSERTE(cnt <= gc.arrDelegates->GetNumComponents()); |
12400 | |
12401 | for (UINT_PTR i=0; i<cnt; i++) |
12402 | { |
12403 | gc.oInnerDelegate = gc.arrDelegates->m_Array[i]; |
12404 | ExceptionNotifications::InvokeNotificationDelegate(notificationType, &gc.oInnerDelegate, &gc.oEventArgs, |
12405 | &gc.oCurAppDomain |
12406 | #ifdef FEATURE_CORRUPTING_EXCEPTIONS |
12407 | , severity |
12408 | #endif // FEATURE_CORRUPTING_EXCEPTIONS |
12409 | ); |
12410 | } |
12411 | } |
12412 | } |
12413 | |
12414 | GCPROTECT_END(); |
12415 | } |
12416 | |
12417 | void ExceptionNotifications::DeliverFirstChanceNotification() |
12418 | { |
12419 | CONTRACTL |
12420 | { |
12421 | THROWS; |
12422 | GC_TRIGGERS; |
12423 | MODE_ANY; |
12424 | } |
12425 | CONTRACTL_END; |
12426 | |
12427 | // We check for FirstChance notification delivery after setting up the corruption severity |
12428 | // so that we can determine if the callback delegate can handle CSE (or not). |
12429 | // |
12430 | // Deliver it only if not already done and someone has wiredup to receive it. |
12431 | // |
12432 | // We do this provided this is the first frame of a new exception |
12433 | // that was thrown or a rethrown exception. We dont want to do this |
12434 | // processing for subsequent frames on the stack since FirstChance notification |
12435 | // will be delivered only when the exception is first thrown/rethrown. |
12436 | ThreadExceptionState *pCurTES = GetThread()->GetExceptionState(); |
12437 | _ASSERTE(pCurTES->GetCurrentExceptionTracker()); |
12438 | _ASSERTE(!(pCurTES->GetCurrentExceptionTracker()->DeliveredFirstChanceNotification())); |
12439 | { |
12440 | GCX_COOP(); |
12441 | if (ExceptionNotifications::CanDeliverNotificationToCurrentAppDomain(FirstChanceExceptionHandler)) |
12442 | { |
12443 | OBJECTREF oThrowable = NULL; |
12444 | GCPROTECT_BEGIN(oThrowable); |
12445 | |
12446 | oThrowable = pCurTES->GetThrowable(); |
12447 | _ASSERTE(oThrowable != NULL); |
12448 | |
12449 | ExceptionNotifications::DeliverNotification(FirstChanceExceptionHandler, &oThrowable |
12450 | #ifdef FEATURE_CORRUPTING_EXCEPTIONS |
12451 | , pCurTES->GetCurrentExceptionTracker()->GetCorruptionSeverity() |
12452 | #endif // FEATURE_CORRUPTING_EXCEPTIONS |
12453 | ); |
12454 | GCPROTECT_END(); |
12455 | |
12456 | } |
12457 | |
12458 | // Mark the exception tracker as having delivered the first chance notification |
12459 | pCurTES->GetCurrentExceptionTracker()->SetFirstChanceNotificationStatus(TRUE); |
12460 | } |
12461 | } |
12462 | |
12463 | |
12464 | #ifdef WIN64EXCEPTIONS |
12465 | struct TAResetStateCallbackData |
12466 | { |
12467 | // Do we have more managed code up the stack? |
12468 | BOOL fDoWeHaveMoreManagedCodeOnStack; |
12469 | |
12470 | // StackFrame representing the crawlFrame above which |
12471 | // we are searching for presence of managed code. |
12472 | StackFrame sfSeedCrawlFrame; |
12473 | }; |
12474 | |
12475 | // This callback helps the 64bit EH attempt to determine if there is more managed code |
12476 | // up the stack (or not). Currently, it is used to conditionally reset the thread abort state |
12477 | // as the unwind passes by. |
12478 | StackWalkAction TAResetStateCallback(CrawlFrame* pCf, void* data) |
12479 | { |
12480 | CONTRACTL { |
12481 | NOTHROW; |
12482 | GC_NOTRIGGER; |
12483 | } |
12484 | CONTRACTL_END; |
12485 | |
12486 | TAResetStateCallbackData *pTAResetStateCallbackData = static_cast<TAResetStateCallbackData *>(data); |
12487 | StackWalkAction retStatus = SWA_CONTINUE; |
12488 | |
12489 | if(pCf->IsFrameless()) |
12490 | { |
12491 | IJitManager* pJitManager = pCf->GetJitManager(); |
12492 | _ASSERTE(pJitManager); |
12493 | if (pJitManager && (!pTAResetStateCallbackData->fDoWeHaveMoreManagedCodeOnStack)) |
12494 | { |
12495 | // The stackwalker can give us a callback for the seeding CrawlFrame (or other crawlframes) |
12496 | // depending upon which is closer to the leaf: the seeding crawlframe or the explicit frame |
12497 | // specified when starting the stackwalk. |
12498 | // |
12499 | // Since we are interested in checking if there is more managed code up the stack from |
12500 | // the seeding crawlframe, we check if the current crawlframe is above it or not. If it is, |
12501 | // then we have found managed code up the stack and should stop the stack walk. Otherwise, |
12502 | // continue searching. |
12503 | StackFrame sfCurrentFrame = StackFrame::FromRegDisplay(pCf->GetRegisterSet()); |
12504 | if (pTAResetStateCallbackData->sfSeedCrawlFrame < sfCurrentFrame) |
12505 | { |
12506 | // We have found managed code on the stack. Flag it and stop the stackwalk. |
12507 | pTAResetStateCallbackData->fDoWeHaveMoreManagedCodeOnStack = TRUE; |
12508 | retStatus = SWA_ABORT; |
12509 | } |
12510 | } |
12511 | } |
12512 | |
12513 | return retStatus; |
12514 | } |
12515 | #endif // WIN64EXCEPTIONS |
12516 | |
12517 | // This function will reset the thread abort state against the specified thread if it is determined that |
12518 | // there is no more managed code on the stack. |
12519 | // |
12520 | // Note: This function should be invoked ONLY during unwind. |
12521 | #ifndef WIN64EXCEPTIONS |
12522 | void ResetThreadAbortState(PTR_Thread pThread, void *pEstablisherFrame) |
12523 | #else |
12524 | void ResetThreadAbortState(PTR_Thread pThread, CrawlFrame *pCf, StackFrame sfCurrentStackFrame) |
12525 | #endif |
12526 | { |
12527 | CONTRACTL |
12528 | { |
12529 | NOTHROW; |
12530 | GC_NOTRIGGER; |
12531 | MODE_ANY; |
12532 | PRECONDITION(pThread != NULL); |
12533 | #ifndef WIN64EXCEPTIONS |
12534 | PRECONDITION(pEstablisherFrame != NULL); |
12535 | #else |
12536 | PRECONDITION(pCf != NULL); |
12537 | PRECONDITION(!sfCurrentStackFrame.IsNull()); |
12538 | #endif |
12539 | } |
12540 | CONTRACTL_END; |
12541 | |
12542 | BOOL fResetThreadAbortState = FALSE; |
12543 | |
12544 | if (pThread->IsAbortRequested()) |
12545 | { |
12546 | #ifndef WIN64EXCEPTIONS |
12547 | if (GetNextCOMPlusSEHRecord(static_cast<EXCEPTION_REGISTRATION_RECORD *>(pEstablisherFrame)) == EXCEPTION_CHAIN_END) |
12548 | { |
12549 | // Topmost handler and abort requested. |
12550 | fResetThreadAbortState = TRUE; |
12551 | LOG((LF_EH, LL_INFO100, "ResetThreadAbortState: Topmost handler resets abort as no more managed code beyond %p.\n" , pEstablisherFrame)); |
12552 | } |
12553 | #else // !WIN64EXCEPTIONS |
12554 | // Get the active exception tracker |
12555 | PTR_ExceptionTracker pCurEHTracker = pThread->GetExceptionState()->GetCurrentExceptionTracker(); |
12556 | _ASSERTE(pCurEHTracker != NULL); |
12557 | |
12558 | // We will check if thread abort state needs to be reset only for the case of exception caught in |
12559 | // native code. This will happen when: |
12560 | // |
12561 | // 1) an unwind is triggered and |
12562 | // 2) current frame is the topmost frame we saw in the first pass and |
12563 | // 3) a thread abort is requested and |
12564 | // 4) we dont have address of the exception handler to be invoked. |
12565 | // |
12566 | // (1), (2) and (4) above are checked for in ExceptionTracker::ProcessOSExceptionNotification from where we call this |
12567 | // function. |
12568 | |
12569 | // Current frame should be the topmost frame we saw in the first pass |
12570 | _ASSERTE(pCurEHTracker->GetTopmostStackFrameFromFirstPass() == sfCurrentStackFrame); |
12571 | |
12572 | // If the exception has been caught in native code, then alongwith not having address of the handler to be |
12573 | // invoked, we also wont have the IL clause for the catch block and resume stack frame will be NULL as well. |
12574 | _ASSERTE((pCurEHTracker->GetCatchToCallPC() == NULL) && |
12575 | (pCurEHTracker->GetCatchHandlerExceptionClauseToken() == NULL) && |
12576 | (pCurEHTracker->GetResumeStackFrame().IsNull())); |
12577 | |
12578 | // Walk the frame chain to see if there is any more managed code on the stack. If not, then this is the last managed frame |
12579 | // on the stack and we can reset the thread abort state. |
12580 | // |
12581 | // Get the frame from which to start the stack walk from |
12582 | Frame* pFrame = pCurEHTracker->GetLimitFrame(); |
12583 | |
12584 | // At this point, we are at the topmost frame we saw during the first pass |
12585 | // before the unwind began. Walk the stack using the specified crawlframe and the topmost |
12586 | // explicit frame to determine if we have more managed code up the stack. If none is found, |
12587 | // we can reset the thread abort state. |
12588 | |
12589 | // Setup the data structure to be passed to the callback |
12590 | TAResetStateCallbackData dataCallback; |
12591 | dataCallback.fDoWeHaveMoreManagedCodeOnStack = FALSE; |
12592 | |
12593 | // At this point, the StackFrame in CrawlFrame should represent the current frame we have been called for. |
12594 | // _ASSERTE(sfCurrentStackFrame == StackFrame::FromRegDisplay(pCf->GetRegisterSet())); |
12595 | |
12596 | // Reference to the StackFrame beyond which we are looking for managed code. |
12597 | dataCallback.sfSeedCrawlFrame = sfCurrentStackFrame; |
12598 | |
12599 | pThread->StackWalkFramesEx(pCf->GetRegisterSet(), TAResetStateCallback, &dataCallback, QUICKUNWIND, pFrame); |
12600 | |
12601 | if (!dataCallback.fDoWeHaveMoreManagedCodeOnStack) |
12602 | { |
12603 | // There is no more managed code on the stack, so reset the thread abort state. |
12604 | fResetThreadAbortState = TRUE; |
12605 | LOG((LF_EH, LL_INFO100, "ResetThreadAbortState: Resetting thread abort state since there is no more managed code beyond stack frames:\n" )); |
12606 | LOG((LF_EH, LL_INFO100, "sf.SP = %p " , dataCallback.sfSeedCrawlFrame.SP)); |
12607 | } |
12608 | #endif // !WIN64EXCEPTIONS |
12609 | } |
12610 | |
12611 | if (fResetThreadAbortState) |
12612 | { |
12613 | pThread->EEResetAbort(Thread::TAR_Thread); |
12614 | } |
12615 | } |
12616 | #endif // !DACCESS_COMPILE |
12617 | |
12618 | #endif // !CROSSGEN_COMPILE |
12619 | |
12620 | //--------------------------------------------------------------------------------- |
12621 | // |
12622 | // |
12623 | // EXCEPTION THROWING HELPERS |
12624 | // |
12625 | // |
12626 | //--------------------------------------------------------------------------------- |
12627 | |
12628 | //--------------------------------------------------------------------------------- |
12629 | // Funnel-worker for THROW_BAD_FORMAT and friends. |
12630 | // |
12631 | // Note: The "cond" argument is there to tide us over during the transition from |
12632 | // BAD_FORMAT_ASSERT to THROW_BAD_FORMAT. It will go away soon. |
12633 | //--------------------------------------------------------------------------------- |
12634 | VOID ThrowBadFormatWorker(UINT resID, LPCWSTR imageName DEBUGARG(__in_z const char *cond)) |
12635 | { |
12636 | CONTRACTL |
12637 | { |
12638 | THROWS; |
12639 | GC_TRIGGERS; |
12640 | INJECT_FAULT(COMPlusThrowOM();); |
12641 | SUPPORTS_DAC; |
12642 | } |
12643 | CONTRACTL_END |
12644 | |
12645 | #ifndef DACCESS_COMPILE |
12646 | SString msgStr; |
12647 | |
12648 | if ((imageName != NULL) && (imageName[0] != 0)) |
12649 | { |
12650 | msgStr += W("[" ); |
12651 | msgStr += imageName; |
12652 | msgStr += W("] " ); |
12653 | } |
12654 | |
12655 | SString resStr; |
12656 | if (resID == 0 || !resStr.LoadResource(CCompRC::Optional, resID)) |
12657 | { |
12658 | resStr.LoadResource(CCompRC::Error, MSG_FOR_URT_HR(COR_E_BADIMAGEFORMAT)); |
12659 | } |
12660 | msgStr += resStr; |
12661 | |
12662 | #ifdef _DEBUG |
12663 | if (0 != strcmp(cond, "FALSE" )) |
12664 | { |
12665 | msgStr += W(" (Failed condition: " ); // this is in DEBUG only - not going to localize it. |
12666 | SString condStr(SString::Ascii, cond); |
12667 | msgStr += condStr; |
12668 | msgStr += W(")" ); |
12669 | } |
12670 | #endif |
12671 | |
12672 | ThrowHR(COR_E_BADIMAGEFORMAT, msgStr); |
12673 | #endif // #ifndef DACCESS_COMPILE |
12674 | } |
12675 | |
12676 | UINT GetResourceIDForFileLoadExceptionHR(HRESULT hr) |
12677 | { |
12678 | switch (hr) { |
12679 | |
12680 | case CTL_E_FILENOTFOUND: |
12681 | hr = IDS_EE_FILE_NOT_FOUND; |
12682 | break; |
12683 | |
12684 | case (HRESULT)IDS_EE_PROC_NOT_FOUND: |
12685 | case (HRESULT)IDS_EE_PATH_TOO_LONG: |
12686 | case INET_E_OBJECT_NOT_FOUND: |
12687 | case INET_E_DATA_NOT_AVAILABLE: |
12688 | case INET_E_DOWNLOAD_FAILURE: |
12689 | case INET_E_UNKNOWN_PROTOCOL: |
12690 | case (HRESULT)IDS_INET_E_SECURITY_PROBLEM: |
12691 | case (HRESULT)IDS_EE_BAD_USER_PROFILE: |
12692 | case (HRESULT)IDS_EE_ALREADY_EXISTS: |
12693 | case IDS_CLASSLOAD_32BITCLRLOADING64BITASSEMBLY: |
12694 | break; |
12695 | |
12696 | case MK_E_SYNTAX: |
12697 | hr = FUSION_E_INVALID_NAME; |
12698 | break; |
12699 | |
12700 | case INET_E_CONNECTION_TIMEOUT: |
12701 | hr = IDS_INET_E_CONNECTION_TIMEOUT; |
12702 | break; |
12703 | |
12704 | case INET_E_CANNOT_CONNECT: |
12705 | hr = IDS_INET_E_CANNOT_CONNECT; |
12706 | break; |
12707 | |
12708 | case INET_E_RESOURCE_NOT_FOUND: |
12709 | hr = IDS_INET_E_RESOURCE_NOT_FOUND; |
12710 | break; |
12711 | |
12712 | case NTE_BAD_HASH: |
12713 | case NTE_BAD_LEN: |
12714 | case NTE_BAD_KEY: |
12715 | case NTE_BAD_DATA: |
12716 | case NTE_BAD_ALGID: |
12717 | case NTE_BAD_FLAGS: |
12718 | case NTE_BAD_HASH_STATE: |
12719 | case NTE_BAD_UID: |
12720 | case NTE_FAIL: |
12721 | case NTE_BAD_TYPE: |
12722 | case NTE_BAD_VER: |
12723 | case NTE_BAD_SIGNATURE: |
12724 | case NTE_SIGNATURE_FILE_BAD: |
12725 | case CRYPT_E_HASH_VALUE: |
12726 | hr = IDS_EE_HASH_VAL_FAILED; |
12727 | break; |
12728 | |
12729 | default: |
12730 | hr = IDS_EE_FILELOAD_ERROR_GENERIC; |
12731 | break; |
12732 | |
12733 | } |
12734 | |
12735 | return (UINT) hr; |
12736 | } |
12737 | |
12738 | #ifndef DACCESS_COMPILE |
12739 | |
12740 | //========================================================================== |
12741 | // Throw a runtime exception based on the last Win32 error (GetLastError()) |
12742 | //========================================================================== |
12743 | VOID DECLSPEC_NORETURN RealCOMPlusThrowWin32() |
12744 | { |
12745 | |
12746 | // before we do anything else... |
12747 | DWORD err = ::GetLastError(); |
12748 | |
12749 | CONTRACTL |
12750 | { |
12751 | THROWS; |
12752 | DISABLED(GC_NOTRIGGER); // Must sanitize first pass handling to enable this |
12753 | MODE_ANY; |
12754 | } |
12755 | CONTRACTL_END; |
12756 | |
12757 | RealCOMPlusThrowWin32(HRESULT_FROM_WIN32(err)); |
12758 | } // VOID DECLSPEC_NORETURN RealCOMPlusThrowWin32() |
12759 | |
12760 | //========================================================================== |
12761 | // Throw a runtime exception based on the last Win32 error (GetLastError()) |
12762 | //========================================================================== |
12763 | VOID DECLSPEC_NORETURN RealCOMPlusThrowWin32(HRESULT hr) |
12764 | { |
12765 | CONTRACTL |
12766 | { |
12767 | THROWS; |
12768 | DISABLED(GC_NOTRIGGER); // Must sanitize first pass handling to enable this |
12769 | MODE_ANY; |
12770 | } |
12771 | CONTRACTL_END; |
12772 | |
12773 | // Force to ApplicationException for compatibility with previous versions. We would |
12774 | // prefer a "Win32Exception" here. |
12775 | EX_THROW(EEMessageException, (kApplicationException, hr, 0 /* resid*/, |
12776 | NULL /* szArg1 */, NULL /* szArg2 */, NULL /* szArg3 */, NULL /* szArg4 */, |
12777 | NULL /* szArg5 */, NULL /* szArg6 */)); |
12778 | } // VOID DECLSPEC_NORETURN RealCOMPlusThrowWin32() |
12779 | |
12780 | |
12781 | //========================================================================== |
12782 | // Throw an OutOfMemoryError |
12783 | //========================================================================== |
12784 | VOID DECLSPEC_NORETURN RealCOMPlusThrowOM() |
12785 | { |
12786 | CONTRACTL |
12787 | { |
12788 | THROWS; |
12789 | DISABLED(GC_NOTRIGGER); // Must sanitize first pass handling to enable this |
12790 | CANNOT_TAKE_LOCK; |
12791 | MODE_ANY; |
12792 | SO_TOLERANT; |
12793 | SUPPORTS_DAC; |
12794 | } |
12795 | CONTRACTL_END; |
12796 | |
12797 | ThrowOutOfMemory(); |
12798 | } |
12799 | |
12800 | //========================================================================== |
12801 | // Throw an undecorated runtime exception. |
12802 | //========================================================================== |
12803 | VOID DECLSPEC_NORETURN RealCOMPlusThrow(RuntimeExceptionKind reKind) |
12804 | { |
12805 | CONTRACTL |
12806 | { |
12807 | THROWS; |
12808 | DISABLED(GC_NOTRIGGER); // Must sanitize first pass handling to enable this |
12809 | MODE_ANY; |
12810 | } |
12811 | CONTRACTL_END; |
12812 | |
12813 | _ASSERTE((reKind != kExecutionEngineException) || |
12814 | !"ExecutionEngineException shouldn't be thrown. Use EEPolicy to failfast or a better exception. The caller of this function should modify their code." ); |
12815 | |
12816 | EX_THROW(EEException, (reKind)); |
12817 | } |
12818 | |
12819 | //========================================================================== |
12820 | // Throw a decorated runtime exception. |
12821 | // Try using RealCOMPlusThrow(reKind, wszResourceName) instead. |
12822 | //========================================================================== |
12823 | VOID DECLSPEC_NORETURN RealCOMPlusThrowNonLocalized(RuntimeExceptionKind reKind, LPCWSTR wszTag) |
12824 | { |
12825 | CONTRACTL |
12826 | { |
12827 | THROWS; |
12828 | DISABLED(GC_NOTRIGGER); // Must sanitize first pass handling to enable this |
12829 | MODE_ANY; |
12830 | } |
12831 | CONTRACTL_END; |
12832 | |
12833 | _ASSERTE((reKind != kExecutionEngineException) || |
12834 | !"ExecutionEngineException shouldn't be thrown. Use EEPolicy to failfast or a better exception. The caller of this function should modify their code." ); |
12835 | |
12836 | EX_THROW(EEMessageException, (reKind, IDS_EE_GENERIC, wszTag)); |
12837 | } |
12838 | |
12839 | //========================================================================== |
12840 | // Throw a runtime exception based on an HResult |
12841 | //========================================================================== |
12842 | VOID DECLSPEC_NORETURN RealCOMPlusThrowHR(HRESULT hr, IErrorInfo* pErrInfo, Exception * pInnerException) |
12843 | { |
12844 | CONTRACTL |
12845 | { |
12846 | THROWS; |
12847 | GC_TRIGGERS; // because of IErrorInfo |
12848 | MODE_ANY; |
12849 | } |
12850 | CONTRACTL_END; |
12851 | |
12852 | _ASSERTE (FAILED(hr)); |
12853 | |
12854 | // Though we would like to assert this, it can happen in the following scenario: |
12855 | // |
12856 | // MgdCode --RCW-> COM --CCW-> MgdCode2 |
12857 | // |
12858 | // If MgdCode2 throws EEE, when it reaches the RCW, it will invoking MarshalNative::ThrowExceptionForHr and thus, |
12859 | // reach here. Hence, we will need to keep the assert off, until user code is stopped for creating an EEE. |
12860 | |
12861 | //_ASSERTE((hr != COR_E_EXECUTIONENGINE) || |
12862 | // !"ExecutionEngineException shouldn't be thrown. Use EEPolicy to failfast or a better exception. The caller of this function should modify their code."); |
12863 | |
12864 | #ifndef CROSSGEN_COMPILE |
12865 | #ifdef FEATURE_COMINTEROP |
12866 | // check for complus created IErrorInfo pointers |
12867 | if (pErrInfo != NULL) |
12868 | { |
12869 | GCX_COOP(); |
12870 | { |
12871 | OBJECTREF oRetVal = NULL; |
12872 | GCPROTECT_BEGIN(oRetVal); |
12873 | GetExceptionForHR(hr, pErrInfo, &oRetVal); |
12874 | _ASSERTE(oRetVal != NULL); |
12875 | RealCOMPlusThrow(oRetVal); |
12876 | GCPROTECT_END (); |
12877 | } |
12878 | } |
12879 | #endif // FEATURE_COMINTEROP |
12880 | |
12881 | if (pErrInfo != NULL) |
12882 | { |
12883 | if (pInnerException == NULL) |
12884 | { |
12885 | EX_THROW(EECOMException, (hr, pErrInfo, true, NULL, FALSE)); |
12886 | } |
12887 | else |
12888 | { |
12889 | EX_THROW_WITH_INNER(EECOMException, (hr, pErrInfo, true, NULL, FALSE), pInnerException); |
12890 | } |
12891 | } |
12892 | else |
12893 | #endif // CROSSGEN_COMPILE |
12894 | { |
12895 | if (pInnerException == NULL) |
12896 | { |
12897 | EX_THROW(EEMessageException, (hr)); |
12898 | } |
12899 | else |
12900 | { |
12901 | EX_THROW_WITH_INNER(EEMessageException, (hr), pInnerException); |
12902 | } |
12903 | } |
12904 | } |
12905 | |
12906 | VOID DECLSPEC_NORETURN RealCOMPlusThrowHR(HRESULT hr) |
12907 | { |
12908 | CONTRACTL |
12909 | { |
12910 | THROWS; |
12911 | DISABLED(GC_NOTRIGGER); // Must sanitize first pass handling to enable this |
12912 | MODE_ANY; |
12913 | } |
12914 | CONTRACTL_END; |
12915 | |
12916 | |
12917 | // ! COMPlusThrowHR(hr) no longer snags the IErrorInfo off the TLS (Too many places |
12918 | // ! call this routine where no IErrorInfo was set by the prior call.) |
12919 | // ! |
12920 | // ! If you actually want to pull IErrorInfo off the TLS, call |
12921 | // ! |
12922 | // ! COMPlusThrowHR(hr, kGetErrorInfo) |
12923 | |
12924 | RealCOMPlusThrowHR(hr, (IErrorInfo*)NULL); |
12925 | } |
12926 | |
12927 | |
12928 | VOID DECLSPEC_NORETURN RealCOMPlusThrowHR(HRESULT hr, tagGetErrorInfo) |
12929 | { |
12930 | CONTRACTL |
12931 | { |
12932 | THROWS; |
12933 | DISABLED(GC_NOTRIGGER); // Must sanitize first pass handling to enable this |
12934 | MODE_ANY; |
12935 | } |
12936 | CONTRACTL_END; |
12937 | |
12938 | // Get an IErrorInfo if one is available. |
12939 | IErrorInfo *pErrInfo = NULL; |
12940 | |
12941 | #ifndef CROSSGEN_COMPILE |
12942 | if (SafeGetErrorInfo(&pErrInfo) != S_OK) |
12943 | pErrInfo = NULL; |
12944 | #endif |
12945 | |
12946 | // Throw the exception. |
12947 | RealCOMPlusThrowHR(hr, pErrInfo); |
12948 | } |
12949 | |
12950 | |
12951 | |
12952 | VOID DECLSPEC_NORETURN RealCOMPlusThrowHR(HRESULT hr, UINT resID, LPCWSTR wszArg1, |
12953 | LPCWSTR wszArg2, LPCWSTR wszArg3, LPCWSTR wszArg4, |
12954 | LPCWSTR wszArg5, LPCWSTR wszArg6) |
12955 | { |
12956 | CONTRACTL |
12957 | { |
12958 | THROWS; |
12959 | DISABLED(GC_NOTRIGGER); // Must sanitize first pass handling to enable this |
12960 | MODE_ANY; |
12961 | } |
12962 | CONTRACTL_END; |
12963 | |
12964 | _ASSERTE (FAILED(hr)); |
12965 | |
12966 | // Though we would like to assert this, it can happen in the following scenario: |
12967 | // |
12968 | // MgdCode --RCW-> COM --CCW-> MgdCode2 |
12969 | // |
12970 | // If MgdCode2 throws EEE, when it reaches the RCW, it will invoking MarshalNative::ThrowExceptionForHr and thus, |
12971 | // reach here. Hence, we will need to keep the assert off, until user code is stopped for creating an EEE. |
12972 | |
12973 | //_ASSERTE((hr != COR_E_EXECUTIONENGINE) || |
12974 | // !"ExecutionEngineException shouldn't be thrown. Use EEPolicy to failfast or a better exception. The caller of this function should modify their code."); |
12975 | |
12976 | EX_THROW(EEMessageException, |
12977 | (hr, resID, wszArg1, wszArg2, wszArg3, wszArg4, wszArg5, wszArg6)); |
12978 | } |
12979 | |
12980 | //========================================================================== |
12981 | // Throw a decorated runtime exception with a localized message. |
12982 | // Queries the ResourceManager for a corresponding resource value. |
12983 | //========================================================================== |
12984 | VOID DECLSPEC_NORETURN RealCOMPlusThrow(RuntimeExceptionKind reKind, LPCWSTR wszResourceName, Exception * pInnerException) |
12985 | { |
12986 | CONTRACTL |
12987 | { |
12988 | THROWS; |
12989 | DISABLED(GC_NOTRIGGER); // Must sanitize first pass handling to enable this |
12990 | MODE_ANY; |
12991 | PRECONDITION(CheckPointer(wszResourceName)); |
12992 | } |
12993 | CONTRACTL_END; |
12994 | |
12995 | _ASSERTE((reKind != kExecutionEngineException) || |
12996 | !"ExecutionEngineException shouldn't be thrown. Use EEPolicy to failfast or a better exception. The caller of this function should modify their code." ); |
12997 | // |
12998 | // For some reason, the compiler complains about unreachable code if |
12999 | // we don't split the new from the throw. So we're left with this |
13000 | // unnecessarily verbose syntax. |
13001 | // |
13002 | |
13003 | if (pInnerException == NULL) |
13004 | { |
13005 | EX_THROW(EEResourceException, (reKind, wszResourceName)); |
13006 | } |
13007 | else |
13008 | { |
13009 | EX_THROW_WITH_INNER(EEResourceException, (reKind, wszResourceName), pInnerException); |
13010 | } |
13011 | } |
13012 | |
13013 | //========================================================================== |
13014 | // Used by the classloader to record a managed exception object to explain |
13015 | // why a classload got botched. |
13016 | // |
13017 | // - Can be called with gc enabled or disabled. |
13018 | // This allows a catch-all error path to post a generic catchall error |
13019 | // message w/out bonking more specific error messages posted by inner functions. |
13020 | //========================================================================== |
13021 | VOID DECLSPEC_NORETURN ThrowTypeLoadException(LPCWSTR pFullTypeName, |
13022 | LPCWSTR pAssemblyName, |
13023 | LPCUTF8 pMessageArg, |
13024 | UINT resIDWhy) |
13025 | { |
13026 | CONTRACTL |
13027 | { |
13028 | THROWS; |
13029 | DISABLED(GC_NOTRIGGER); // Must sanitize first pass handling to enable this |
13030 | MODE_ANY; |
13031 | } |
13032 | CONTRACTL_END; |
13033 | |
13034 | EX_THROW(EETypeLoadException, (pFullTypeName, pAssemblyName, pMessageArg, resIDWhy)); |
13035 | } |
13036 | |
13037 | |
13038 | //========================================================================== |
13039 | // Used by the classloader to post illegal layout |
13040 | //========================================================================== |
13041 | VOID DECLSPEC_NORETURN ThrowFieldLayoutError(mdTypeDef cl, // cl of the NStruct being loaded |
13042 | Module* pModule, // Module that defines the scope, loader and heap (for allocate FieldMarshalers) |
13043 | DWORD dwOffset, // Offset of field |
13044 | DWORD dwID) // Message id |
13045 | { |
13046 | CONTRACTL |
13047 | { |
13048 | THROWS; |
13049 | DISABLED(GC_NOTRIGGER); // Must sanitize first pass handling to enable this |
13050 | MODE_ANY; |
13051 | } |
13052 | CONTRACTL_END; |
13053 | |
13054 | IMDInternalImport *pInternalImport = pModule->GetMDImport(); // Internal interface for the NStruct being loaded. |
13055 | |
13056 | LPCUTF8 pszName, pszNamespace; |
13057 | if (FAILED(pInternalImport->GetNameOfTypeDef(cl, &pszName, &pszNamespace))) |
13058 | { |
13059 | pszName = pszNamespace = "Invalid TypeDef record" ; |
13060 | } |
13061 | |
13062 | CHAR offsetBuf[16]; |
13063 | sprintf_s(offsetBuf, COUNTOF(offsetBuf), "%d" , dwOffset); |
13064 | offsetBuf[COUNTOF(offsetBuf) - 1] = '\0'; |
13065 | |
13066 | pModule->GetAssembly()->ThrowTypeLoadException(pszNamespace, |
13067 | pszName, |
13068 | offsetBuf, |
13069 | dwID); |
13070 | } |
13071 | |
13072 | //========================================================================== |
13073 | // Throw an ArithmeticException |
13074 | //========================================================================== |
13075 | VOID DECLSPEC_NORETURN RealCOMPlusThrowArithmetic() |
13076 | { |
13077 | CONTRACTL |
13078 | { |
13079 | THROWS; |
13080 | DISABLED(GC_NOTRIGGER); // Must sanitize first pass handling to enable this |
13081 | MODE_ANY; |
13082 | } |
13083 | CONTRACTL_END; |
13084 | |
13085 | RealCOMPlusThrow(kArithmeticException); |
13086 | } |
13087 | |
13088 | //========================================================================== |
13089 | // Throw an ArgumentNullException |
13090 | //========================================================================== |
13091 | VOID DECLSPEC_NORETURN RealCOMPlusThrowArgumentNull(LPCWSTR argName, LPCWSTR wszResourceName) |
13092 | { |
13093 | CONTRACTL |
13094 | { |
13095 | THROWS; |
13096 | DISABLED(GC_NOTRIGGER); // Must sanitize first pass handling to enable this |
13097 | MODE_ANY; |
13098 | PRECONDITION(CheckPointer(wszResourceName)); |
13099 | } |
13100 | CONTRACTL_END; |
13101 | |
13102 | EX_THROW(EEArgumentException, (kArgumentNullException, argName, wszResourceName)); |
13103 | } |
13104 | |
13105 | |
13106 | VOID DECLSPEC_NORETURN RealCOMPlusThrowArgumentNull(LPCWSTR argName) |
13107 | { |
13108 | CONTRACTL |
13109 | { |
13110 | THROWS; |
13111 | DISABLED(GC_NOTRIGGER); // Must sanitize first pass handling to enable this |
13112 | MODE_ANY; |
13113 | } |
13114 | CONTRACTL_END; |
13115 | |
13116 | EX_THROW(EEArgumentException, (kArgumentNullException, argName, W("ArgumentNull_Generic" ))); |
13117 | } |
13118 | |
13119 | |
13120 | //========================================================================== |
13121 | // Throw an ArgumentOutOfRangeException |
13122 | //========================================================================== |
13123 | VOID DECLSPEC_NORETURN RealCOMPlusThrowArgumentOutOfRange(LPCWSTR argName, LPCWSTR wszResourceName) |
13124 | { |
13125 | CONTRACTL |
13126 | { |
13127 | THROWS; |
13128 | DISABLED(GC_NOTRIGGER); // Must sanitize first pass handling to enable this |
13129 | MODE_ANY; |
13130 | } |
13131 | CONTRACTL_END; |
13132 | |
13133 | EX_THROW(EEArgumentException, (kArgumentOutOfRangeException, argName, wszResourceName)); |
13134 | } |
13135 | |
13136 | //========================================================================== |
13137 | // Throw an ArgumentException |
13138 | //========================================================================== |
13139 | VOID DECLSPEC_NORETURN RealCOMPlusThrowArgumentException(LPCWSTR argName, LPCWSTR wszResourceName) |
13140 | { |
13141 | CONTRACTL |
13142 | { |
13143 | THROWS; |
13144 | DISABLED(GC_NOTRIGGER); // Must sanitize first pass handling to enable this |
13145 | MODE_ANY; |
13146 | } |
13147 | CONTRACTL_END; |
13148 | |
13149 | EX_THROW(EEArgumentException, (kArgumentException, argName, wszResourceName)); |
13150 | } |
13151 | |
13152 | //========================================================================= |
13153 | // Used by the classloader to record a managed exception object to explain |
13154 | // why a classload got botched. |
13155 | // |
13156 | // - Can be called with gc enabled or disabled. |
13157 | // This allows a catch-all error path to post a generic catchall error |
13158 | // message w/out bonking more specific error messages posted by inner functions. |
13159 | //========================================================================== |
13160 | VOID DECLSPEC_NORETURN ThrowTypeLoadException(LPCUTF8 pszNameSpace, |
13161 | LPCUTF8 pTypeName, |
13162 | LPCWSTR pAssemblyName, |
13163 | LPCUTF8 pMessageArg, |
13164 | UINT resIDWhy) |
13165 | { |
13166 | CONTRACTL |
13167 | { |
13168 | THROWS; |
13169 | DISABLED(GC_NOTRIGGER); // Must sanitize first pass handling to enable this |
13170 | MODE_ANY; |
13171 | } |
13172 | CONTRACTL_END; |
13173 | |
13174 | EX_THROW(EETypeLoadException, (pszNameSpace, pTypeName, pAssemblyName, pMessageArg, resIDWhy)); |
13175 | } |
13176 | |
13177 | //========================================================================== |
13178 | // Throw a decorated runtime exception. |
13179 | //========================================================================== |
13180 | VOID DECLSPEC_NORETURN RealCOMPlusThrow(RuntimeExceptionKind reKind, UINT resID, |
13181 | LPCWSTR wszArg1, LPCWSTR wszArg2, LPCWSTR wszArg3, |
13182 | LPCWSTR wszArg4, LPCWSTR wszArg5, LPCWSTR wszArg6) |
13183 | { |
13184 | CONTRACTL |
13185 | { |
13186 | THROWS; |
13187 | DISABLED(GC_NOTRIGGER); // Must sanitize first pass handling to enable this |
13188 | MODE_ANY; |
13189 | } |
13190 | CONTRACTL_END; |
13191 | |
13192 | EX_THROW(EEMessageException, |
13193 | (reKind, resID, wszArg1, wszArg2, wszArg3, wszArg4, wszArg5, wszArg6)); |
13194 | } |
13195 | |
13196 | #ifdef FEATURE_COMINTEROP |
13197 | #ifndef CROSSGEN_COMPILE |
13198 | //========================================================================== |
13199 | // Throw a runtime exception based on an HResult, check for error info |
13200 | //========================================================================== |
13201 | VOID DECLSPEC_NORETURN RealCOMPlusThrowHR(HRESULT hr, IUnknown *iface, REFIID riid) |
13202 | { |
13203 | CONTRACTL |
13204 | { |
13205 | THROWS; |
13206 | GC_TRIGGERS; // because of IErrorInfo |
13207 | MODE_ANY; |
13208 | } |
13209 | CONTRACTL_END; |
13210 | |
13211 | IErrorInfo *info = NULL; |
13212 | { |
13213 | GCX_PREEMP(); |
13214 | info = GetSupportedErrorInfo(iface, riid); |
13215 | } |
13216 | RealCOMPlusThrowHR(hr, info); |
13217 | } |
13218 | |
13219 | //========================================================================== |
13220 | // Throw a runtime exception based on an EXCEPINFO. This function will free |
13221 | // the strings in the EXCEPINFO that is passed in. |
13222 | //========================================================================== |
13223 | VOID DECLSPEC_NORETURN RealCOMPlusThrowHR(EXCEPINFO *pExcepInfo) |
13224 | { |
13225 | CONTRACTL |
13226 | { |
13227 | THROWS; |
13228 | DISABLED(GC_NOTRIGGER); // Must sanitize first pass handling to enable this |
13229 | MODE_ANY; |
13230 | } |
13231 | CONTRACTL_END; |
13232 | |
13233 | EX_THROW(EECOMException, (pExcepInfo)); |
13234 | } |
13235 | #endif //CROSSGEN_COMPILE |
13236 | |
13237 | #endif // FEATURE_COMINTEROP |
13238 | |
13239 | |
13240 | #ifdef FEATURE_STACK_PROBE |
13241 | //========================================================================== |
13242 | // Throw a StackOverflowError |
13243 | //========================================================================== |
13244 | VOID DECLSPEC_NORETURN RealCOMPlusThrowSO() |
13245 | { |
13246 | CONTRACTL |
13247 | { |
13248 | // This should be throws... But it isn't because a SO doesn't technically |
13249 | // fall into the same THROW/NOTHROW conventions as the rest of the contract |
13250 | // infrastructure. |
13251 | NOTHROW; |
13252 | |
13253 | DISABLED(GC_NOTRIGGER); // Must sanitize first pass handling to enable this |
13254 | SO_TOLERANT; |
13255 | MODE_ANY; |
13256 | } |
13257 | CONTRACTL_END; |
13258 | |
13259 | // We only use BreakOnSO if we are in debug mode, so we'll only checking if the |
13260 | // _DEBUG flag is set. |
13261 | #ifdef _DEBUG |
13262 | static int breakOnSO = -1; |
13263 | |
13264 | if (breakOnSO == -1) |
13265 | breakOnSO = CLRConfig::GetConfigValue(CLRConfig::UNSUPPORTED_BreakOnSO); |
13266 | |
13267 | if (breakOnSO != 0) |
13268 | { |
13269 | _ASSERTE(!"SO occurred" ); |
13270 | } |
13271 | #endif |
13272 | |
13273 | ThrowStackOverflow(); |
13274 | } |
13275 | #endif |
13276 | |
13277 | //========================================================================== |
13278 | // Throw an InvalidCastException |
13279 | //========================================================================== |
13280 | |
13281 | |
13282 | VOID GetAssemblyDetailInfo(SString &sType, |
13283 | SString &sAssemblyDisplayName, |
13284 | PEAssembly *pPEAssembly, |
13285 | SString &sAssemblyDetailInfo) |
13286 | { |
13287 | WRAPPER_NO_CONTRACT; |
13288 | |
13289 | InlineSString<MAX_LONGPATH> sFormat; |
13290 | const WCHAR *pwzLoadContext = W("Default" ); |
13291 | |
13292 | if (pPEAssembly->GetPath().IsEmpty()) |
13293 | { |
13294 | sFormat.LoadResource(CCompRC::Debugging, IDS_EE_CANNOTCAST_HELPER_BYTE); |
13295 | |
13296 | sAssemblyDetailInfo.Printf(sFormat.GetUnicode(), |
13297 | sType.GetUnicode(), |
13298 | sAssemblyDisplayName.GetUnicode(), |
13299 | pwzLoadContext); |
13300 | } |
13301 | else |
13302 | { |
13303 | sFormat.LoadResource(CCompRC::Debugging, IDS_EE_CANNOTCAST_HELPER_PATH); |
13304 | |
13305 | sAssemblyDetailInfo.Printf(sFormat.GetUnicode(), |
13306 | sType.GetUnicode(), |
13307 | sAssemblyDisplayName.GetUnicode(), |
13308 | pwzLoadContext, |
13309 | pPEAssembly->GetPath().GetUnicode()); |
13310 | } |
13311 | } |
13312 | |
13313 | VOID CheckAndThrowSameTypeAndAssemblyInvalidCastException(TypeHandle thCastFrom, |
13314 | TypeHandle thCastTo) |
13315 | { |
13316 | CONTRACTL { |
13317 | THROWS; |
13318 | GC_TRIGGERS; |
13319 | MODE_COOPERATIVE; |
13320 | SO_INTOLERANT; |
13321 | } CONTRACTL_END; |
13322 | |
13323 | Module *pModuleTypeFrom = thCastFrom.GetModule(); |
13324 | Module *pModuleTypeTo = thCastTo.GetModule(); |
13325 | |
13326 | if ((pModuleTypeFrom != NULL) && (pModuleTypeTo != NULL)) |
13327 | { |
13328 | Assembly *pAssemblyTypeFrom = pModuleTypeFrom->GetAssembly(); |
13329 | Assembly *pAssemblyTypeTo = pModuleTypeTo->GetAssembly(); |
13330 | |
13331 | _ASSERTE(pAssemblyTypeFrom != NULL); |
13332 | _ASSERTE(pAssemblyTypeTo != NULL); |
13333 | |
13334 | PEAssembly *pPEAssemblyTypeFrom = pAssemblyTypeFrom->GetManifestFile(); |
13335 | PEAssembly *pPEAssemblyTypeTo = pAssemblyTypeTo->GetManifestFile(); |
13336 | |
13337 | _ASSERTE(pPEAssemblyTypeFrom != NULL); |
13338 | _ASSERTE(pPEAssemblyTypeTo != NULL); |
13339 | |
13340 | InlineSString<MAX_LONGPATH> sAssemblyFromDisplayName; |
13341 | InlineSString<MAX_LONGPATH> sAssemblyToDisplayName; |
13342 | |
13343 | pPEAssemblyTypeFrom->GetDisplayName(sAssemblyFromDisplayName); |
13344 | pPEAssemblyTypeTo->GetDisplayName(sAssemblyToDisplayName); |
13345 | |
13346 | // Found the culprit case. Now format the new exception text. |
13347 | InlineSString<MAX_CLASSNAME_LENGTH + 1> strCastFromName; |
13348 | InlineSString<MAX_CLASSNAME_LENGTH + 1> strCastToName; |
13349 | InlineSString<MAX_LONGPATH> sAssemblyDetailInfoFrom; |
13350 | InlineSString<MAX_LONGPATH> sAssemblyDetailInfoTo; |
13351 | |
13352 | thCastFrom.GetName(strCastFromName); |
13353 | thCastTo.GetName(strCastToName); |
13354 | |
13355 | SString typeA = SL(W("A" )); |
13356 | GetAssemblyDetailInfo(typeA, |
13357 | sAssemblyFromDisplayName, |
13358 | pPEAssemblyTypeFrom, |
13359 | sAssemblyDetailInfoFrom); |
13360 | SString typeB = SL(W("B" )); |
13361 | GetAssemblyDetailInfo(typeB, |
13362 | sAssemblyToDisplayName, |
13363 | pPEAssemblyTypeTo, |
13364 | sAssemblyDetailInfoTo); |
13365 | |
13366 | COMPlusThrow(kInvalidCastException, |
13367 | IDS_EE_CANNOTCASTSAME, |
13368 | strCastFromName.GetUnicode(), |
13369 | strCastToName.GetUnicode(), |
13370 | sAssemblyDetailInfoFrom.GetUnicode(), |
13371 | sAssemblyDetailInfoTo.GetUnicode()); |
13372 | } |
13373 | } |
13374 | |
13375 | VOID RealCOMPlusThrowInvalidCastException(TypeHandle thCastFrom, TypeHandle thCastTo) |
13376 | { |
13377 | CONTRACTL { |
13378 | THROWS; |
13379 | GC_TRIGGERS; |
13380 | MODE_COOPERATIVE; |
13381 | } CONTRACTL_END; |
13382 | |
13383 | // Use an InlineSString with a size of MAX_CLASSNAME_LENGTH + 1 to prevent |
13384 | // TypeHandle::GetName from having to allocate a new block of memory. This |
13385 | // significantly improves the performance of throwing an InvalidCastException. |
13386 | InlineSString<MAX_CLASSNAME_LENGTH + 1> strCastFromName; |
13387 | InlineSString<MAX_CLASSNAME_LENGTH + 1> strCastToName; |
13388 | |
13389 | thCastTo.GetName(strCastToName); |
13390 | { |
13391 | thCastFrom.GetName(strCastFromName); |
13392 | // Attempt to catch the A.T != A.T case that causes so much user confusion. |
13393 | if (strCastFromName.Equals(strCastToName)) |
13394 | { |
13395 | CheckAndThrowSameTypeAndAssemblyInvalidCastException(thCastFrom, thCastTo); |
13396 | } |
13397 | COMPlusThrow(kInvalidCastException, IDS_EE_CANNOTCAST, strCastFromName.GetUnicode(), strCastToName.GetUnicode()); |
13398 | } |
13399 | } |
13400 | |
13401 | #ifndef CROSSGEN_COMPILE |
13402 | VOID RealCOMPlusThrowInvalidCastException(OBJECTREF *pObj, TypeHandle thCastTo) |
13403 | { |
13404 | CONTRACTL { |
13405 | THROWS; |
13406 | GC_TRIGGERS; |
13407 | MODE_COOPERATIVE; |
13408 | PRECONDITION(IsProtectedByGCFrame (pObj)); |
13409 | } CONTRACTL_END; |
13410 | |
13411 | TypeHandle thCastFrom = (*pObj)->GetTypeHandle(); |
13412 | #ifdef FEATURE_COMINTEROP |
13413 | if (thCastFrom.GetMethodTable()->IsComObjectType()) |
13414 | { |
13415 | // Special case casting RCWs so we can give better error information when the |
13416 | // cast fails. |
13417 | ComObject::ThrowInvalidCastException(pObj, thCastTo.GetMethodTable()); |
13418 | } |
13419 | #endif |
13420 | COMPlusThrowInvalidCastException(thCastFrom, thCastTo); |
13421 | } |
13422 | #endif // CROSSGEN_COMPILE |
13423 | |
13424 | #endif // DACCESS_COMPILE |
13425 | |
13426 | #ifndef CROSSGEN_COMPILE // ??? |
13427 | #ifdef FEATURE_COMINTEROP |
13428 | #include "comtoclrcall.h" |
13429 | #endif // FEATURE_COMINTEROP |
13430 | |
13431 | // Reverse COM interop IL stubs need to catch all exceptions and translate them into HRESULTs. |
13432 | // But we allow for CSEs to be rethrown. Our corrupting state policy gets applied to the |
13433 | // original user-visible method that triggered the IL stub to be generated. So we must be able |
13434 | // to map back from a given IL stub to the user-visible method. Here, we do that only when we |
13435 | // see a 'matching' ComMethodFrame further up the stack. |
13436 | MethodDesc * GetUserMethodForILStub(Thread * pThread, UINT_PTR uStubSP, MethodDesc * pILStubMD, Frame ** ppFrameOut) |
13437 | { |
13438 | CONTRACTL |
13439 | { |
13440 | NOTHROW; |
13441 | GC_NOTRIGGER; |
13442 | MODE_ANY; |
13443 | PRECONDITION(pILStubMD->IsILStub()); |
13444 | } |
13445 | CONTRACTL_END; |
13446 | |
13447 | MethodDesc * pUserMD = pILStubMD; |
13448 | #ifdef FEATURE_COMINTEROP |
13449 | DynamicMethodDesc * pDMD = pILStubMD->AsDynamicMethodDesc(); |
13450 | if (pDMD->IsCOMToCLRStub()) |
13451 | { |
13452 | // There are some differences across architectures for "which" SP is passed in. |
13453 | // On ARM, the SP is the SP on entry to the IL stub, on the other arches, it's |
13454 | // a post-prolog SP. But this doesn't matter here because the COM->CLR path |
13455 | // always pushes the Frame in a caller's stack frame. |
13456 | |
13457 | Frame * pCurFrame = pThread->GetFrame(); |
13458 | while ((UINT_PTR)pCurFrame < uStubSP) |
13459 | { |
13460 | pCurFrame = pCurFrame->PtrNextFrame(); |
13461 | } |
13462 | |
13463 | // The construction of the COM->CLR path ensures that our corresponding ComMethodFrame |
13464 | // should be present further up the stack. Normally, the ComMethodFrame in question is |
13465 | // simply the next stack frame; however, there are situations where there may be other |
13466 | // stack frames present (such as an optional ContextTransitionFrame if we switched |
13467 | // AppDomains, or an inlined stack frame from a QCall in the IL stub). |
13468 | while (pCurFrame->GetVTablePtr() != ComMethodFrame::GetMethodFrameVPtr()) |
13469 | { |
13470 | pCurFrame = pCurFrame->PtrNextFrame(); |
13471 | } |
13472 | |
13473 | ComMethodFrame * pComFrame = (ComMethodFrame *)pCurFrame; |
13474 | _ASSERTE((UINT_PTR)pComFrame > uStubSP); |
13475 | |
13476 | CONSISTENCY_CHECK_MSG(pComFrame->GetVTablePtr() == ComMethodFrame::GetMethodFrameVPtr(), |
13477 | "Expected to find a ComMethodFrame." ); |
13478 | |
13479 | ComCallMethodDesc * pCMD = pComFrame->GetComCallMethodDesc(); |
13480 | |
13481 | CONSISTENCY_CHECK_MSG(pILStubMD == ExecutionManager::GetCodeMethodDesc(pCMD->GetILStub()), |
13482 | "The ComMethodFrame that we found doesn't match the IL stub passed in." ); |
13483 | |
13484 | pUserMD = pCMD->GetMethodDesc(); |
13485 | *ppFrameOut = pComFrame; |
13486 | } |
13487 | #endif // FEATURE_COMINTEROP |
13488 | return pUserMD; |
13489 | } |
13490 | #endif //CROSSGEN_COMPILE |
13491 | |