| 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 | |