//********************************************************* // // Copyright (c) Microsoft. All rights reserved. // This code is licensed under the MIT License. // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF // ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED // TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A // PARTICULAR PURPOSE AND NONINFRINGEMENT. // //********************************************************* //! @file //! WIL Error Handling Helpers: supporting file enabling the originating of errors to produce better crash dumps // Note: When origination is enabled by including this file, origination is done as part of the RETURN_* and THROW_* macros. // Before originating a new error we will observe whether there is already an error payload associated with the current thread. If // there is, and the HRESULTs match, then a new error will not be originated. Otherwise we will overwrite it with a new // origination. The ABI boundary for WinRT APIs will check the per-thread error information. The act of checking the error // clears it, so there should be minimal risk of failing to originate distinct errors simply because the HRESULTs match. // // For THROW_ macros we will examine the thread-local error storage once per throw. So typically once, with additional calls if // the exception is caught and re-thrown. // // For RETURN_ macros we will have to examine the thread-local error storage once per frame as the call stack unwinds. Because // error conditions -should- be uncommon the performance impact of checking TLS should be minimal. The more expensive part is // originating the error because it must capture the entire stack and some additional data. #ifndef __WIL_RESULT_ORIGINATE_INCLUDED #define __WIL_RESULT_ORIGINATE_INCLUDED #include "result.h" #include // RestrictedErrorInfo uses BSTRs :( #include #include "resource.h" #include "com.h" #include namespace wil { /// @cond namespace details { // Note: The name must begin with "Raise" so that the !analyze auto-bucketing will ignore this stack frame. Otherwise this line of code gets all the blame. inline void __stdcall RaiseRoOriginateOnWilExceptions(wil::FailureInfo const& failure) WI_NOEXCEPT { if ((failure.type == FailureType::Return) || (failure.type == FailureType::Exception)) { bool shouldOriginate = true; wil::com_ptr_nothrow restrictedErrorInformation; if (GetRestrictedErrorInfo(&restrictedErrorInformation) == S_OK) { // This thread already has an error origination payload. Don't originate again if it has the same HRESULT that we // are observing right now. wil::unique_bstr descriptionUnused; HRESULT existingHr = failure.hr; wil::unique_bstr restrictedDescriptionUnused; wil::unique_bstr capabilitySidUnused; if (SUCCEEDED(restrictedErrorInformation->GetErrorDetails( &descriptionUnused, &existingHr, &restrictedDescriptionUnused, &capabilitySidUnused))) { shouldOriginate = (failure.hr != existingHr); } } if (shouldOriginate) { #if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP | WINAPI_PARTITION_SYSTEM) wil::unique_hmodule errorModule; if (GetModuleHandleExW(0, L"api-ms-win-core-winrt-error-l1-1-1.dll", &errorModule)) { auto pfn = reinterpret_cast(GetProcAddress(errorModule.get(), "RoOriginateErrorW")); if (pfn != nullptr) { pfn(failure.hr, 0, failure.pszMessage); } } #else // DESKTOP | SYSTEM ::RoOriginateErrorW(failure.hr, 0, failure.pszMessage); #endif // DESKTOP | SYSTEM } else if (restrictedErrorInformation) { // GetRestrictedErrorInfo returns ownership of the error information. If we aren't originating, and an error was // already present, then we need to restore the error information for later observation. SetRestrictedErrorInfo(restrictedErrorInformation.get()); } } } // This method will check for the presence of stowed exception data on the current thread. If such data exists, and the // HRESULT matches the current failure, then we will call RoFailFastWithErrorContext. RoFailFastWithErrorContext in this // situation will result in -VASTLY- improved crash bucketing. It is hard to express just how much better. In other cases we // just return and the calling method fails fast the same way it always has. inline void __stdcall FailfastWithContextCallback(wil::FailureInfo const& failure) WI_NOEXCEPT { wil::com_ptr_nothrow restrictedErrorInformation; if (GetRestrictedErrorInfo(&restrictedErrorInformation) == S_OK) { wil::unique_bstr descriptionUnused; HRESULT existingHr = failure.hr; wil::unique_bstr restrictedDescriptionUnused; wil::unique_bstr capabilitySidUnused; if (SUCCEEDED(restrictedErrorInformation->GetErrorDetails( &descriptionUnused, &existingHr, &restrictedDescriptionUnused, &capabilitySidUnused)) && (existingHr == failure.hr)) { // GetRestrictedErrorInfo returns ownership of the error information. We want it to be available for // RoFailFastWithErrorContext so we must restore it via SetRestrictedErrorInfo first. SetRestrictedErrorInfo(restrictedErrorInformation.get()); RoFailFastWithErrorContext(existingHr); } else { // The error didn't match the current failure. Put it back in thread-local storage even though we aren't failing // fast in this method, so it is available in the debugger just-in-case. SetRestrictedErrorInfo(restrictedErrorInformation.get()); } } } } // namespace details /// @endcond } // namespace wil // Automatically call RoOriginateError upon error origination by including this file WI_HEADER_INITITALIZATION_FUNCTION(ResultStowedExceptionInitialize, [] { ::wil::SetOriginateErrorCallback(::wil::details::RaiseRoOriginateOnWilExceptions); ::wil::SetFailfastWithContextCallback(::wil::details::FailfastWithContextCallback); return 1; }) #endif // __WIL_RESULT_ORIGINATE_INCLUDED