diff --git a/src/coreclr/System.Private.CoreLib/src/System/IO/FileLoadException.CoreCLR.cs b/src/coreclr/System.Private.CoreLib/src/System/IO/FileLoadException.CoreCLR.cs index 927e08905606eb..01770b4f509032 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/IO/FileLoadException.CoreCLR.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/IO/FileLoadException.CoreCLR.cs @@ -47,16 +47,17 @@ internal enum FileLoadExceptionKind } [UnmanagedCallersOnly] - internal static unsafe void Create(FileLoadExceptionKind kind, char* pFileName, int hresult, object* pThrowable, Exception* pException) + internal static unsafe void Create(FileLoadExceptionKind kind, char* pFileName, int hresult, char* pDiagnosticInfo, object* pThrowable, Exception* pException) { try { string? fileName = pFileName is not null ? new string(pFileName) : null; + string? diagnosticInfo = pDiagnosticInfo is not null ? new string(pDiagnosticInfo) : null; Debug.Assert(Enum.IsDefined(kind)); *pThrowable = kind switch { FileLoadExceptionKind.BadImageFormat => new BadImageFormatException(fileName, hresult), - FileLoadExceptionKind.FileNotFound => new FileNotFoundException(fileName, hresult), + FileLoadExceptionKind.FileNotFound => new FileNotFoundException(fileName, hresult, diagnosticInfo), FileLoadExceptionKind.OutOfMemory => new OutOfMemoryException(), _ /* FileLoadExceptionKind.FileLoad */ => new FileLoadException(fileName, hresult), }; diff --git a/src/coreclr/System.Private.CoreLib/src/System/IO/FileNotFoundException.CoreCLR.cs b/src/coreclr/System.Private.CoreLib/src/System/IO/FileNotFoundException.CoreCLR.cs index 15d54ec5b367c8..69120f6ee92a71 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/IO/FileNotFoundException.CoreCLR.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/IO/FileNotFoundException.CoreCLR.cs @@ -12,5 +12,11 @@ internal FileNotFoundException(string? fileName, int hResult) FileName = fileName; SetMessageField(); } + + internal FileNotFoundException(string? fileName, int hResult, string? diagnosticInfo) + : this(fileName, hResult) + { + FusionLog = diagnosticInfo; + } } } diff --git a/src/coreclr/binder/assemblybindercommon.cpp b/src/coreclr/binder/assemblybindercommon.cpp index 661fb0dae93b41..1acae66f1e395f 100644 --- a/src/coreclr/binder/assemblybindercommon.cpp +++ b/src/coreclr/binder/assemblybindercommon.cpp @@ -37,7 +37,8 @@ extern HRESULT RuntimeInvokeHostAssemblyResolver(INT_PTR pAssemblyLoadContextToB STDAPI BinderAcquirePEImage(LPCTSTR szAssemblyPath, PEImage** ppPEImage, - ProbeExtensionResult probeExtensionResult); + ProbeExtensionResult probeExtensionResult, + SString *pDiagnosticInfo = NULL); namespace BINDER_SPACE { @@ -193,7 +194,8 @@ namespace BINDER_SPACE /* in */ AssemblyName *pAssemblyName, /* in */ bool excludeAppPaths, /* out */ Assembly **ppAssembly, - /* [out, optional] */ Assembly **ppExistingAssemblyOnFailure) + /* [out, optional] */ Assembly **ppExistingAssemblyOnFailure, + /* out */ SString *pDiagnosticInfo) { HRESULT hr = S_OK; LONG kContextVersion = 0; @@ -225,6 +227,11 @@ namespace BINDER_SPACE Exit: tracer.TraceBindResult(bindResult); + if (pDiagnosticInfo != NULL && !bindResult.GetDiagnosticInfo().IsEmpty()) + { + pDiagnosticInfo->Append(bindResult.GetDiagnosticInfo()); + } + if (bindResult.HaveResult()) { BindResult hostBindResult; @@ -405,7 +412,7 @@ namespace BINDER_SPACE pAssemblyName->GetDisplayName(assemblyDisplayName, AssemblyName::INCLUDE_VERSION); - hr = pApplicationContext->GetFailureCache()->Lookup(assemblyDisplayName); + hr = pApplicationContext->GetFailureCache()->Lookup(assemblyDisplayName, &pBindResult->GetDiagnosticInfo()); if (FAILED(hr)) { if ((hr == HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND)) && skipFailureCaching) @@ -459,7 +466,7 @@ namespace BINDER_SPACE } } - hr = pApplicationContext->AddToFailureCache(assemblyDisplayName, hr); + hr = pApplicationContext->AddToFailureCache(assemblyDisplayName, hr, pBindResult->GetDiagnosticInfo()); } LogExit: @@ -869,7 +876,8 @@ namespace BINDER_SPACE hr = GetAssembly(assemblyFilePath, TRUE, // fIsInTPA &pTPAAssembly, - probeExtensionResult); + probeExtensionResult, + &pBindResult->GetDiagnosticInfo()); BinderTracing::PathProbed(assemblyFilePath, BinderTracing::PathSource::Bundle, hr); @@ -899,7 +907,9 @@ namespace BINDER_SPACE hr = GetAssembly(fileName, TRUE, // fIsInTPA - &pTPAAssembly); + &pTPAAssembly, + ProbeExtensionResult::Invalid(), + &pBindResult->GetDiagnosticInfo()); BinderTracing::PathProbed(fileName, BinderTracing::PathSource::ApplicationAssemblies, hr); pBindResult->SetAttemptResult(hr, pTPAAssembly); @@ -985,7 +995,8 @@ namespace BINDER_SPACE HRESULT AssemblyBinderCommon::GetAssembly(SString &assemblyPath, BOOL fIsInTPA, Assembly **ppAssembly, - ProbeExtensionResult probeExtensionResult) + ProbeExtensionResult probeExtensionResult, + SString *pDiagnosticInfo) { HRESULT hr = S_OK; @@ -1001,12 +1012,27 @@ namespace BINDER_SPACE { LPCTSTR szAssemblyPath = const_cast(assemblyPath.GetUnicode()); - hr = BinderAcquirePEImage(szAssemblyPath, &pPEImage, probeExtensionResult); + hr = BinderAcquirePEImage(szAssemblyPath, &pPEImage, probeExtensionResult, pDiagnosticInfo); IF_FAIL_GO(hr); } // Initialize assembly object - IF_FAIL_GO(pAssembly->Init(pPEImage, fIsInTPA)); + hr = pAssembly->Init(pPEImage, fIsInTPA); + if (FAILED(hr)) + { + if (pDiagnosticInfo != NULL) + { + if (!pDiagnosticInfo->IsEmpty()) + pDiagnosticInfo->AppendUTF8("\n"); + + StackSString format; + format.LoadResource(IDS_BINDING_FAILED_TO_INIT_ASSEMBLY); + StackSString hrMsg; + GetHRMsg(hr, hrMsg); + pDiagnosticInfo->AppendPrintf(format.GetUTF8(), assemblyPath.GetUTF8(), hrMsg.GetUTF8()); + } + goto Exit; + } // We're done *ppAssembly = pAssembly.Extract(); diff --git a/src/coreclr/binder/customassemblybinder.cpp b/src/coreclr/binder/customassemblybinder.cpp index 22f30eaa312b82..4829b19ff390b2 100644 --- a/src/coreclr/binder/customassemblybinder.cpp +++ b/src/coreclr/binder/customassemblybinder.cpp @@ -14,7 +14,8 @@ using namespace BINDER_SPACE; // CustomAssemblyBinder implementation // ============================================================================ HRESULT CustomAssemblyBinder::BindAssemblyByNameWorker(BINDER_SPACE::AssemblyName *pAssemblyName, - BINDER_SPACE::Assembly **ppCoreCLRFoundAssembly) + BINDER_SPACE::Assembly **ppCoreCLRFoundAssembly, + SString *pDiagnosticInfo) { VALIDATE_ARG_RET(pAssemblyName != nullptr && ppCoreCLRFoundAssembly != nullptr); HRESULT hr = S_OK; @@ -28,7 +29,9 @@ HRESULT CustomAssemblyBinder::BindAssemblyByNameWorker(BINDER_SPACE::AssemblyNam hr = AssemblyBinderCommon::BindAssembly(this, pAssemblyName, false, //excludeAppPaths, - ppCoreCLRFoundAssembly); + ppCoreCLRFoundAssembly, + nullptr /* ppExistingAssemblyOnFailure */, + pDiagnosticInfo); if (!FAILED(hr)) { _ASSERTE(*ppCoreCLRFoundAssembly != NULL); @@ -39,7 +42,8 @@ HRESULT CustomAssemblyBinder::BindAssemblyByNameWorker(BINDER_SPACE::AssemblyNam } HRESULT CustomAssemblyBinder::BindUsingAssemblyName(BINDER_SPACE::AssemblyName* pAssemblyName, - BINDER_SPACE::Assembly** ppAssembly) + BINDER_SPACE::Assembly** ppAssembly, + SString* pDiagnosticInfo) { // When LoadContext needs to resolve an assembly reference, it will go through the following lookup order: // @@ -58,7 +62,7 @@ HRESULT CustomAssemblyBinder::BindUsingAssemblyName(BINDER_SPACE::AssemblyName* { // Step 1 - Try to find the assembly within the LoadContext. - hr = BindAssemblyByNameWorker(pAssemblyName, &pCoreCLRFoundAssembly); + hr = BindAssemblyByNameWorker(pAssemblyName, &pCoreCLRFoundAssembly, pDiagnosticInfo); if ((hr == HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND)) || (hr == FUSION_E_APP_DOMAIN_LOCKED) || (hr == FUSION_E_REF_DEF_MISMATCH)) { diff --git a/src/coreclr/binder/defaultassemblybinder.cpp b/src/coreclr/binder/defaultassemblybinder.cpp index 12c36ddd8d448e..0504997a91b354 100644 --- a/src/coreclr/binder/defaultassemblybinder.cpp +++ b/src/coreclr/binder/defaultassemblybinder.cpp @@ -14,7 +14,8 @@ using namespace BINDER_SPACE; HRESULT DefaultAssemblyBinder::BindAssemblyByNameWorker(BINDER_SPACE::AssemblyName *pAssemblyName, BINDER_SPACE::Assembly **ppCoreCLRFoundAssembly, bool excludeAppPaths, - BINDER_SPACE::Assembly **ppExistingAssemblyOnFailure) + BINDER_SPACE::Assembly **ppExistingAssemblyOnFailure, + SString *pDiagnosticInfo) { VALIDATE_ARG_RET(pAssemblyName != nullptr && ppCoreCLRFoundAssembly != nullptr); HRESULT hr = S_OK; @@ -28,7 +29,8 @@ HRESULT DefaultAssemblyBinder::BindAssemblyByNameWorker(BINDER_SPACE::AssemblyNa pAssemblyName, excludeAppPaths, ppCoreCLRFoundAssembly, - ppExistingAssemblyOnFailure); + ppExistingAssemblyOnFailure, + pDiagnosticInfo); if (!FAILED(hr)) { (*ppCoreCLRFoundAssembly)->SetBinder(this); @@ -46,7 +48,8 @@ HRESULT DefaultAssemblyBinder::BindAssemblyByNameWorker(BINDER_SPACE::AssemblyNa // DefaultAssemblyBinder implementation // ============================================================================ HRESULT DefaultAssemblyBinder::BindUsingAssemblyName(BINDER_SPACE::AssemblyName *pAssemblyName, - BINDER_SPACE::Assembly **ppAssembly) + BINDER_SPACE::Assembly **ppAssembly, + SString *pDiagnosticInfo) { HRESULT hr = S_OK; VALIDATE_ARG_RET(pAssemblyName != nullptr && ppAssembly != nullptr); @@ -55,7 +58,7 @@ HRESULT DefaultAssemblyBinder::BindUsingAssemblyName(BINDER_SPACE::AssemblyName ReleaseHolder pCoreCLRFoundAssembly; - hr = BindAssemblyByNameWorker(pAssemblyName, &pCoreCLRFoundAssembly, false /* excludeAppPaths */); + hr = BindAssemblyByNameWorker(pAssemblyName, &pCoreCLRFoundAssembly, false /* excludeAppPaths */, nullptr /* ppExistingAssemblyOnFailure */, pDiagnosticInfo); #if !defined(DACCESS_COMPILE) if ((hr == HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND)) || diff --git a/src/coreclr/binder/failurecache.cpp b/src/coreclr/binder/failurecache.cpp index c6e1f286fbb7ed..cd7b721beecff7 100644 --- a/src/coreclr/binder/failurecache.cpp +++ b/src/coreclr/binder/failurecache.cpp @@ -11,6 +11,7 @@ // // ============================================================ +#include "common.h" #include "failurecache.hpp" namespace BINDER_SPACE @@ -32,7 +33,8 @@ namespace BINDER_SPACE } HRESULT FailureCache::Add(SString &assemblyNameorPath, - HRESULT hrBindingResult) + HRESULT hrBindingResult, + const SString &diagnosticInfo) { HRESULT hr = S_OK; @@ -44,6 +46,10 @@ namespace BINDER_SPACE pFailureCacheEntry->GetAssemblyNameOrPath().Set(assemblyNameorPath); pFailureCacheEntry->SetBindingResult(hrBindingResult); + if (!diagnosticInfo.IsEmpty()) + { + pFailureCacheEntry->SetDiagnosticInfo(diagnosticInfo); + } Hash::Add(pFailureCacheEntry); pFailureCacheEntry.SuppressRelease(); @@ -52,7 +58,8 @@ namespace BINDER_SPACE return hr; } - HRESULT FailureCache::Lookup(SString &assemblyNameorPath) + HRESULT FailureCache::Lookup(SString &assemblyNameorPath, + SString *pDiagnosticInfo) { HRESULT hr = S_OK; FailureCacheEntry *pFailureCachEntry = Hash::Lookup(assemblyNameorPath); @@ -60,6 +67,15 @@ namespace BINDER_SPACE if (pFailureCachEntry != NULL) { hr = pFailureCachEntry->GetBindingResult(); + if (pDiagnosticInfo != NULL && !pFailureCachEntry->GetDiagnosticInfo().IsEmpty()) + { + if (!pDiagnosticInfo->IsEmpty()) + pDiagnosticInfo->AppendUTF8("\n"); + + StackSString format; + format.LoadResource(IDS_BINDING_CACHED_FAILURE_PREFIX); + pDiagnosticInfo->AppendPrintf(format.GetUTF8(), pFailureCachEntry->GetDiagnosticInfo().GetUTF8()); + } } return hr; diff --git a/src/coreclr/binder/inc/applicationcontext.hpp b/src/coreclr/binder/inc/applicationcontext.hpp index 53bcfbcac48012..b383141011f01d 100644 --- a/src/coreclr/binder/inc/applicationcontext.hpp +++ b/src/coreclr/binder/inc/applicationcontext.hpp @@ -95,7 +95,8 @@ namespace BINDER_SPACE inline ExecutionContext *GetExecutionContext(); inline FailureCache *GetFailureCache(); inline HRESULT AddToFailureCache(SString &assemblyNameOrPath, - HRESULT hrBindResult); + HRESULT hrBindResult, + const SString &diagnosticInfo); inline StringArrayList *GetAppPaths(); inline SimpleNameToFileNameMap *GetTpaList(); inline StringArrayList *GetPlatformResourceRoots(); diff --git a/src/coreclr/binder/inc/applicationcontext.inl b/src/coreclr/binder/inc/applicationcontext.inl index 16ec0c6d87de2a..be31b37129fa2d 100644 --- a/src/coreclr/binder/inc/applicationcontext.inl +++ b/src/coreclr/binder/inc/applicationcontext.inl @@ -41,9 +41,10 @@ FailureCache *ApplicationContext::GetFailureCache() } HRESULT ApplicationContext::AddToFailureCache(SString &assemblyNameOrPath, - HRESULT hrBindResult) + HRESULT hrBindResult, + const SString &diagnosticInfo) { - HRESULT hr = GetFailureCache()->Add(assemblyNameOrPath, hrBindResult); + HRESULT hr = GetFailureCache()->Add(assemblyNameOrPath, hrBindResult, diagnosticInfo); IncrementVersion(); return hr; } diff --git a/src/coreclr/binder/inc/assemblybindercommon.hpp b/src/coreclr/binder/inc/assemblybindercommon.hpp index 9f475fc03b509d..3aa11cd8194a42 100644 --- a/src/coreclr/binder/inc/assemblybindercommon.hpp +++ b/src/coreclr/binder/inc/assemblybindercommon.hpp @@ -32,7 +32,8 @@ namespace BINDER_SPACE /* in */ AssemblyName *pAssemblyName, /* in */ bool excludeAppPaths, /* out */ Assembly **ppAssembly, - /* [out, optional] */ Assembly **ppExistingAssemblyOnFailure = nullptr); + /* [out, optional] */ Assembly **ppExistingAssemblyOnFailure = nullptr, + /* out */ SString *pDiagnosticInfo = NULL); static HRESULT BindToSystem(/* in */ SString &systemDirectory, /* out */ Assembly **ppSystemAssembly); @@ -45,7 +46,8 @@ namespace BINDER_SPACE static HRESULT GetAssembly(/* in */ SString &assemblyPath, /* in */ BOOL fIsInTPA, /* out */ Assembly **ppAssembly, - /* in */ ProbeExtensionResult probeExtensionResult = ProbeExtensionResult::Invalid()); + /* in */ ProbeExtensionResult probeExtensionResult = ProbeExtensionResult::Invalid(), + /* out */ SString *pDiagnosticInfo = NULL); #if !defined(DACCESS_COMPILE) static HRESULT BindUsingHostAssemblyResolver (/* in */ INT_PTR pAssemblyLoadContextToBindWithin, diff --git a/src/coreclr/binder/inc/bindresult.hpp b/src/coreclr/binder/inc/bindresult.hpp index 5309a3d5cffaed..8b24d3d0b9aa25 100644 --- a/src/coreclr/binder/inc/bindresult.hpp +++ b/src/coreclr/binder/inc/bindresult.hpp @@ -56,12 +56,16 @@ namespace BINDER_SPACE const AttemptResult* GetAttempt(bool foundInContext) const; + SString& GetDiagnosticInfo() { return m_diagnosticInfo; } + protected: bool m_isContextBound; ReleaseHolder m_pAssembly; AttemptResult m_inContextAttempt; AttemptResult m_applicationAssembliesAttempt; + + SString m_diagnosticInfo; }; }; diff --git a/src/coreclr/binder/inc/customassemblybinder.h b/src/coreclr/binder/inc/customassemblybinder.h index dbcdeee5a5b3c7..d01f4f69ec4470 100644 --- a/src/coreclr/binder/inc/customassemblybinder.h +++ b/src/coreclr/binder/inc/customassemblybinder.h @@ -23,7 +23,7 @@ class CustomAssemblyBinder final : public AssemblyBinder BINDER_SPACE::Assembly** ppExistingAssemblyOnConflict = nullptr) override; HRESULT BindUsingAssemblyName(BINDER_SPACE::AssemblyName* pAssemblyName, - BINDER_SPACE::Assembly** ppAssembly) override; + BINDER_SPACE::Assembly** ppAssembly, SString* pDiagnosticInfo = NULL) override; AssemblyLoaderAllocator* GetLoaderAllocator() override; @@ -49,7 +49,7 @@ class CustomAssemblyBinder final : public AssemblyBinder void ReleaseLoadContext(); private: - HRESULT BindAssemblyByNameWorker(BINDER_SPACE::AssemblyName *pAssemblyName, BINDER_SPACE::Assembly **ppCoreCLRFoundAssembly); + HRESULT BindAssemblyByNameWorker(BINDER_SPACE::AssemblyName *pAssemblyName, BINDER_SPACE::Assembly **ppCoreCLRFoundAssembly, SString *pDiagnosticInfo = NULL); DefaultAssemblyBinder *m_pDefaultBinder; diff --git a/src/coreclr/binder/inc/defaultassemblybinder.h b/src/coreclr/binder/inc/defaultassemblybinder.h index 320026e91b5ff9..df2119891b33fe 100644 --- a/src/coreclr/binder/inc/defaultassemblybinder.h +++ b/src/coreclr/binder/inc/defaultassemblybinder.h @@ -21,7 +21,7 @@ class DefaultAssemblyBinder final : public AssemblyBinder BINDER_SPACE::Assembly** ppExistingAssemblyOnConflict = nullptr) override; HRESULT BindUsingAssemblyName(BINDER_SPACE::AssemblyName* pAssemblyName, - BINDER_SPACE::Assembly** ppAssembly) override; + BINDER_SPACE::Assembly** ppAssembly, SString* pDiagnosticInfo = NULL) override; AssemblyLoaderAllocator* GetLoaderAllocator() override { @@ -48,7 +48,8 @@ class DefaultAssemblyBinder final : public AssemblyBinder BINDER_SPACE::AssemblyName *pAssemblyName, BINDER_SPACE::Assembly **ppCoreCLRFoundAssembly, bool excludeAppPaths, - BINDER_SPACE::Assembly **ppExistingAssemblyOnFailure = nullptr); + BINDER_SPACE::Assembly **ppExistingAssemblyOnFailure = nullptr, + SString *pDiagnosticInfo = NULL); }; #endif // __DEFAULT_ASSEMBLY_BINDER_H__ diff --git a/src/coreclr/binder/inc/failurecache.hpp b/src/coreclr/binder/inc/failurecache.hpp index bce67b33c50c6c..931c9ee45e4635 100644 --- a/src/coreclr/binder/inc/failurecache.hpp +++ b/src/coreclr/binder/inc/failurecache.hpp @@ -28,8 +28,10 @@ namespace BINDER_SPACE ~FailureCache(); HRESULT Add(/* in */ SString &assemblyNameorPath, - /* in */ HRESULT hrBindResult); - HRESULT Lookup(/* in */ SString &assemblyNameorPath); + /* in */ HRESULT hrBindResult, + /* in */ const SString &diagnosticInfo); + HRESULT Lookup(/* in */ SString &assemblyNameorPath, + /* out */ SString *pDiagnosticInfo = NULL); void Remove(/* in */ SString &assemblyName); }; }; diff --git a/src/coreclr/binder/inc/failurecachehashtraits.hpp b/src/coreclr/binder/inc/failurecachehashtraits.hpp index 1ffa96f56b1985..46aa10c5694b2c 100644 --- a/src/coreclr/binder/inc/failurecachehashtraits.hpp +++ b/src/coreclr/binder/inc/failurecachehashtraits.hpp @@ -45,10 +45,19 @@ namespace BINDER_SPACE { m_hrBindingResult = hrBindingResult; } + inline SString &GetDiagnosticInfo() + { + return m_diagnosticInfo; + } + inline void SetDiagnosticInfo(const SString &diagnosticInfo) + { + m_diagnosticInfo.Set(diagnosticInfo); + } protected: SString m_assemblyNameOrPath; HRESULT m_hrBindingResult; + SString m_diagnosticInfo; }; class FailureCacheHashTraits : public DefaultSHashTraits diff --git a/src/coreclr/dlls/mscorrc/mscorrc.rc b/src/coreclr/dlls/mscorrc/mscorrc.rc index ea01dfb2743cc5..4db50443879a4d 100644 --- a/src/coreclr/dlls/mscorrc/mscorrc.rc +++ b/src/coreclr/dlls/mscorrc/mscorrc.rc @@ -689,6 +689,14 @@ BEGIN IDS_NATIVE_IMAGE_CANNOT_BE_LOADED_MULTIPLE_TIMES "Native image cannot be loaded multiple times" END +STRINGTABLE DISCARDABLE +BEGIN + IDS_BINDING_FAILED_TO_OPEN_FILE "Failed to open file '%s'. %s" + IDS_BINDING_EXCEPTION_OPENING_FILE "Exception opening '%s': %s" + IDS_BINDING_FAILED_TO_INIT_ASSEMBLY "Failed to initialize assembly from '%s'. %s" + IDS_BINDING_CACHED_FAILURE_PREFIX "[cached failure] %s" +END + // // Descriptions for FACILITY_URT hresults. None of these may be parameterized. // diff --git a/src/coreclr/dlls/mscorrc/resource.h b/src/coreclr/dlls/mscorrc/resource.h index b58cf1e22545ed..de761d74bd6315 100644 --- a/src/coreclr/dlls/mscorrc/resource.h +++ b/src/coreclr/dlls/mscorrc/resource.h @@ -530,3 +530,8 @@ #define IDS_EE_CANNOTCASTSAME_DETAIL_LOCATION 0x2652 #define IDS_EE_CANNOTCASTSAME_GENARG_BYTE_ARRAY 0x2653 #define IDS_EE_CANNOTCASTSAME_GENARG_LOCATION 0x2654 + +#define IDS_BINDING_FAILED_TO_OPEN_FILE 0x2655 +#define IDS_BINDING_EXCEPTION_OPENING_FILE 0x2656 +#define IDS_BINDING_FAILED_TO_INIT_ASSEMBLY 0x2657 +#define IDS_BINDING_CACHED_FAILURE_PREFIX 0x2658 diff --git a/src/coreclr/vm/appdomain.cpp b/src/coreclr/vm/appdomain.cpp index 488e407303fd85..be0ef48ab51ac9 100644 --- a/src/coreclr/vm/appdomain.cpp +++ b/src/coreclr/vm/appdomain.cpp @@ -2408,7 +2408,9 @@ Assembly *AppDomain::LoadAssembly(AssemblySpec* pSpec, PAL_CPP_THROW(Exception *, pEx); } else + { AddExceptionToCache(pSpec, pEx); + } } } EX_END_HOOK; @@ -3122,6 +3124,7 @@ PEAssembly * AppDomain::BindAssemblySpec( HRESULT hrBindResult = S_OK; PEAssemblyHolder result; + StackSString bindDiagnosticInfo; bool isCached = false; EX_TRY @@ -3132,7 +3135,7 @@ PEAssembly * AppDomain::BindAssemblySpec( { ReleaseHolder boundAssembly; - hrBindResult = pSpec->Bind(this, &boundAssembly); + hrBindResult = pSpec->Bind(this, &boundAssembly, &bindDiagnosticInfo); if (boundAssembly) { @@ -3176,7 +3179,7 @@ PEAssembly * AppDomain::BindAssemblySpec( if (fFailure && fThrowOnFileNotFound) { - EEFileLoadException::Throw(pFailedSpec, COR_E_FILENOTFOUND, NULL); + EEFileLoadException::Throw(pFailedSpec, COR_E_FILENOTFOUND, bindDiagnosticInfo, NULL); } } } diff --git a/src/coreclr/vm/assemblybinder.cpp b/src/coreclr/vm/assemblybinder.cpp index 9fab20523cd92b..dc0d6acd8ea93b 100644 --- a/src/coreclr/vm/assemblybinder.cpp +++ b/src/coreclr/vm/assemblybinder.cpp @@ -9,7 +9,8 @@ #ifndef DACCESS_COMPILE HRESULT AssemblyBinder::BindAssemblyByName(AssemblyNameData* pAssemblyNameData, - BINDER_SPACE::Assembly** ppAssembly) + BINDER_SPACE::Assembly** ppAssembly, + SString* pDiagnosticInfo) { _ASSERTE(pAssemblyNameData != nullptr && ppAssembly != nullptr); @@ -20,7 +21,7 @@ HRESULT AssemblyBinder::BindAssemblyByName(AssemblyNameData* pAssemblyNameData, SAFE_NEW(pAssemblyName, BINDER_SPACE::AssemblyName); IF_FAIL_GO(pAssemblyName->Init(*pAssemblyNameData)); - hr = BindUsingAssemblyName(pAssemblyName, ppAssembly); + hr = BindUsingAssemblyName(pAssemblyName, ppAssembly, pDiagnosticInfo); Exit: return hr; diff --git a/src/coreclr/vm/assemblybinder.h b/src/coreclr/vm/assemblybinder.h index 4e63c8242b6151..e79f715ec0c40d 100644 --- a/src/coreclr/vm/assemblybinder.h +++ b/src/coreclr/vm/assemblybinder.h @@ -18,9 +18,9 @@ class AssemblyBinder { public: - HRESULT BindAssemblyByName(AssemblyNameData* pAssemblyNameData, BINDER_SPACE::Assembly** ppAssembly); + HRESULT BindAssemblyByName(AssemblyNameData* pAssemblyNameData, BINDER_SPACE::Assembly** ppAssembly, SString* pDiagnosticInfo = NULL); virtual HRESULT BindUsingPEImage(PEImage* pPEImage, bool excludeAppPaths, BINDER_SPACE::Assembly** ppAssembly, BINDER_SPACE::Assembly** ppExistingAssemblyOnConflict = nullptr) = 0; - virtual HRESULT BindUsingAssemblyName(BINDER_SPACE::AssemblyName* pAssemblyName, BINDER_SPACE::Assembly** ppAssembly) = 0; + virtual HRESULT BindUsingAssemblyName(BINDER_SPACE::AssemblyName* pAssemblyName, BINDER_SPACE::Assembly** ppAssembly, SString* pDiagnosticInfo = NULL) = 0; /// /// Get LoaderAllocator for binders that contain it. For other binders, return NULL. diff --git a/src/coreclr/vm/assemblyspec.hpp b/src/coreclr/vm/assemblyspec.hpp index d4e2a57a5ac395..c5ccc517706f9e 100644 --- a/src/coreclr/vm/assemblyspec.hpp +++ b/src/coreclr/vm/assemblyspec.hpp @@ -194,7 +194,8 @@ class AssemblySpec : public BaseAssemblySpec HRESULT Bind( AppDomain* pAppDomain, - BINDER_SPACE::Assembly** ppAssembly); + BINDER_SPACE::Assembly** ppAssembly, + SString* pDiagnosticInfo = NULL); Assembly *LoadAssembly(FileLoadLevel targetLevel, BOOL fThrowOnFileNotFound = TRUE); diff --git a/src/coreclr/vm/clrex.cpp b/src/coreclr/vm/clrex.cpp index 565f8ec633b68e..b4df072384be77 100644 --- a/src/coreclr/vm/clrex.cpp +++ b/src/coreclr/vm/clrex.cpp @@ -1452,6 +1452,21 @@ EEFileLoadException::EEFileLoadException(const SString &name, HRESULT hr, Except } +EEFileLoadException::EEFileLoadException(const SString &name, HRESULT hr, const SString &diagnosticInfo, Exception *pInnerException/* = NULL*/) + : EEFileLoadException(name, hr, pInnerException) +{ + CONTRACTL + { + GC_NOTRIGGER; + THROWS; + MODE_ANY; + } + CONTRACTL_END; + + m_diagnosticInfo.Set(diagnosticInfo); +} + + EEFileLoadException::~EEFileLoadException() { STATIC_CONTRACT_NOTHROW; @@ -1587,10 +1602,11 @@ OBJECTREF EEFileLoadException::CreateThrowable() GCPROTECT_BEGIN(gc); LPCWSTR pFileName = m_name.GetUnicode(); + LPCWSTR pDiagnosticInfo = m_diagnosticInfo.IsEmpty() ? NULL : m_diagnosticInfo.GetUnicode(); UnmanagedCallersOnlyCaller createFileLoadEx(METHOD__FILE_LOAD_EXCEPTION__CREATE); FileLoadExceptionKind kind = GetFileLoadExceptionKind(m_hr); - createFileLoadEx.InvokeThrowing(kind, pFileName, (int)m_hr, &gc.pNewException); + createFileLoadEx.InvokeThrowing(kind, pFileName, (int)m_hr, pDiagnosticInfo, &gc.pNewException); _ASSERTE(gc.pNewException->GetMethodTable() == CoreLibBinder::GetException(m_kind)); GCPROTECT_END(); @@ -1647,6 +1663,27 @@ void DECLSPEC_NORETURN EEFileLoadException::Throw(AssemblySpec *pSpec, HRESULT EX_THROW_WITH_INNER(EEFileLoadException, (name, hr), pInnerException); } +/* static */ +void DECLSPEC_NORETURN EEFileLoadException::Throw(AssemblySpec *pSpec, HRESULT hr, const SString &diagnosticInfo, Exception *pInnerException/* = NULL*/) +{ + CONTRACTL + { + GC_TRIGGERS; + THROWS; + MODE_ANY; + } + CONTRACTL_END; + + if (hr == COR_E_THREADABORTED) + COMPlusThrow(kThreadAbortException); + if (hr == E_OUTOFMEMORY) + COMPlusThrowOM(); + + StackSString name; + pSpec->GetDisplayName(0, name); + EX_THROW_WITH_INNER(EEFileLoadException, (name, hr, diagnosticInfo), pInnerException); +} + /* static */ void DECLSPEC_NORETURN EEFileLoadException::Throw(PEAssembly *pPEAssembly, HRESULT hr, Exception *pInnerException /* = NULL*/) { diff --git a/src/coreclr/vm/clrex.h b/src/coreclr/vm/clrex.h index 01fbce5df1a1c7..9277d0da478d5d 100644 --- a/src/coreclr/vm/clrex.h +++ b/src/coreclr/vm/clrex.h @@ -665,10 +665,12 @@ class EEFileLoadException : public EEException private: SString m_name; HRESULT m_hr; + SString m_diagnosticInfo; public: EEFileLoadException(const SString &name, HRESULT hr, Exception *pInnerException = NULL); + EEFileLoadException(const SString &name, HRESULT hr, const SString &diagnosticInfo, Exception *pInnerException = NULL); ~EEFileLoadException(); // virtual overrides @@ -683,6 +685,7 @@ class EEFileLoadException : public EEException static RuntimeExceptionKind GetFileLoadKind(HRESULT hr); static void DECLSPEC_NORETURN Throw(AssemblySpec *pSpec, HRESULT hr, Exception *pInnerException = NULL); + static void DECLSPEC_NORETURN Throw(AssemblySpec *pSpec, HRESULT hr, const SString &diagnosticInfo, Exception *pInnerException = NULL); static void DECLSPEC_NORETURN Throw(PEAssembly *pPEAssembly, HRESULT hr, Exception *pInnerException = NULL); static void DECLSPEC_NORETURN Throw(LPCWSTR path, HRESULT hr, Exception *pInnerException = NULL); static void DECLSPEC_NORETURN Throw(PEAssembly *parent, const void *memory, COUNT_T size, HRESULT hr, Exception *pInnerException = NULL); @@ -692,7 +695,7 @@ class EEFileLoadException : public EEException virtual Exception *CloneHelper() { WRAPPER_NO_CONTRACT; - return new EEFileLoadException(m_name, m_hr); + return new EEFileLoadException(m_name, m_hr, m_diagnosticInfo); } private: diff --git a/src/coreclr/vm/coreassemblyspec.cpp b/src/coreclr/vm/coreassemblyspec.cpp index 522a158e4a6461..65e71b058e385c 100644 --- a/src/coreclr/vm/coreassemblyspec.cpp +++ b/src/coreclr/vm/coreassemblyspec.cpp @@ -26,7 +26,7 @@ #include "../binder/inc/assemblybindercommon.hpp" #include "../binder/inc/applicationcontext.hpp" -HRESULT AssemblySpec::Bind(AppDomain *pAppDomain, BINDER_SPACE::Assembly** ppAssembly) +HRESULT AssemblySpec::Bind(AppDomain *pAppDomain, BINDER_SPACE::Assembly** ppAssembly, SString* pDiagnosticInfo) { CONTRACTL { @@ -62,7 +62,7 @@ HRESULT AssemblySpec::Bind(AppDomain *pAppDomain, BINDER_SPACE::Assembly** ppAs { AssemblyNameData assemblyNameData = { 0 }; PopulateAssemblyNameData(assemblyNameData); - hr = pBinder->BindAssemblyByName(&assemblyNameData, &pPrivAsm); + hr = pBinder->BindAssemblyByName(&assemblyNameData, &pPrivAsm, pDiagnosticInfo); } if (SUCCEEDED(hr)) @@ -77,7 +77,8 @@ HRESULT AssemblySpec::Bind(AppDomain *pAppDomain, BINDER_SPACE::Assembly** ppAs STDAPI BinderAcquirePEImage(LPCWSTR wszAssemblyPath, PEImage **ppPEImage, - ProbeExtensionResult probeExtensionResult) + ProbeExtensionResult probeExtensionResult, + SString *pDiagnosticInfo) { HRESULT hr = S_OK; @@ -93,6 +94,18 @@ STDAPI BinderAcquirePEImage(LPCWSTR wszAssemblyPath, hr = pImage->TryOpenFile(); if (FAILED(hr)) { + if (pDiagnosticInfo != NULL) + { + if (!pDiagnosticInfo->IsEmpty()) + pDiagnosticInfo->AppendUTF8("\n"); + + StackSString format; + format.LoadResource(IDS_BINDING_FAILED_TO_OPEN_FILE); + SString pathStr(wszAssemblyPath); + StackSString hrMsg; + GetHRMsg(hr, hrMsg); + pDiagnosticInfo->AppendPrintf(format.GetUTF8(), pathStr.GetUTF8(), hrMsg.GetUTF8()); + } goto Exit; } } @@ -100,7 +113,24 @@ STDAPI BinderAcquirePEImage(LPCWSTR wszAssemblyPath, if (pImage) *ppPEImage = pImage.Detach(); } - EX_CATCH_HRESULT(hr); + EX_CATCH + { + hr = GET_EXCEPTION()->GetHR(); + _ASSERTE(FAILED(hr)); + if (pDiagnosticInfo != NULL) + { + if (!pDiagnosticInfo->IsEmpty()) + pDiagnosticInfo->AppendUTF8("\n"); + + StackSString format; + format.LoadResource(IDS_BINDING_EXCEPTION_OPENING_FILE); + SString pathStr(wszAssemblyPath); + StackSString exMessage; + GET_EXCEPTION()->GetMessage(exMessage); + pDiagnosticInfo->AppendPrintf(format.GetUTF8(), pathStr.GetUTF8(), exMessage.GetUTF8()); + } + } + EX_END_CATCH Exit: return hr; diff --git a/src/coreclr/vm/peimage.cpp b/src/coreclr/vm/peimage.cpp index 21240cefcd33a2..69898349915c01 100644 --- a/src/coreclr/vm/peimage.cpp +++ b/src/coreclr/vm/peimage.cpp @@ -809,8 +809,9 @@ HRESULT PEImage::TryOpenFile(bool takeLock) if (m_hFile != INVALID_HANDLE_VALUE) return S_OK; - if (GetLastError()) - return HRESULT_FROM_WIN32(GetLastError()); + DWORD dwLastError = GetLastError(); + if (dwLastError != 0) + return HRESULT_FROM_WIN32(dwLastError); return HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND); } diff --git a/src/libraries/System.Runtime.Loader/tests/System.Runtime.Loader.Test.BindFailure.Corrupt/System.Runtime.Loader.Test.BindFailure.Corrupt.csproj b/src/libraries/System.Runtime.Loader/tests/System.Runtime.Loader.Test.BindFailure.Corrupt/System.Runtime.Loader.Test.BindFailure.Corrupt.csproj new file mode 100644 index 00000000000000..e2e8167e2a6699 --- /dev/null +++ b/src/libraries/System.Runtime.Loader/tests/System.Runtime.Loader.Test.BindFailure.Corrupt/System.Runtime.Loader.Test.BindFailure.Corrupt.csproj @@ -0,0 +1,10 @@ + + + false + $(NetCoreAppCurrent) + true + + + + + diff --git a/src/libraries/System.Runtime.Loader/tests/System.Runtime.Loader.Test.BindFailure.Corrupt/TestClass.cs b/src/libraries/System.Runtime.Loader/tests/System.Runtime.Loader.Test.BindFailure.Corrupt/TestClass.cs new file mode 100644 index 00000000000000..b6150252a5631b --- /dev/null +++ b/src/libraries/System.Runtime.Loader/tests/System.Runtime.Loader.Test.BindFailure.Corrupt/TestClass.cs @@ -0,0 +1,10 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace BindFailureTest.Corrupt +{ + public class TestClass + { + public static string GetMessage() => "Hello from Corrupt test assembly"; + } +} diff --git a/src/libraries/System.Runtime.Loader/tests/System.Runtime.Loader.Test.BindFailure.Locked/System.Runtime.Loader.Test.BindFailure.Locked.csproj b/src/libraries/System.Runtime.Loader/tests/System.Runtime.Loader.Test.BindFailure.Locked/System.Runtime.Loader.Test.BindFailure.Locked.csproj new file mode 100644 index 00000000000000..e2e8167e2a6699 --- /dev/null +++ b/src/libraries/System.Runtime.Loader/tests/System.Runtime.Loader.Test.BindFailure.Locked/System.Runtime.Loader.Test.BindFailure.Locked.csproj @@ -0,0 +1,10 @@ + + + false + $(NetCoreAppCurrent) + true + + + + + diff --git a/src/libraries/System.Runtime.Loader/tests/System.Runtime.Loader.Test.BindFailure.Locked/TestClass.cs b/src/libraries/System.Runtime.Loader/tests/System.Runtime.Loader.Test.BindFailure.Locked/TestClass.cs new file mode 100644 index 00000000000000..51c0d2a0b59b57 --- /dev/null +++ b/src/libraries/System.Runtime.Loader/tests/System.Runtime.Loader.Test.BindFailure.Locked/TestClass.cs @@ -0,0 +1,10 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace BindFailureTest.Locked +{ + public class TestClass + { + public static string GetMessage() => "Hello from Locked test assembly"; + } +} diff --git a/src/libraries/System.Runtime.Loader/tests/System.Runtime.Loader.Test.BindFailure.Missing/System.Runtime.Loader.Test.BindFailure.Missing.csproj b/src/libraries/System.Runtime.Loader/tests/System.Runtime.Loader.Test.BindFailure.Missing/System.Runtime.Loader.Test.BindFailure.Missing.csproj new file mode 100644 index 00000000000000..e2e8167e2a6699 --- /dev/null +++ b/src/libraries/System.Runtime.Loader/tests/System.Runtime.Loader.Test.BindFailure.Missing/System.Runtime.Loader.Test.BindFailure.Missing.csproj @@ -0,0 +1,10 @@ + + + false + $(NetCoreAppCurrent) + true + + + + + diff --git a/src/libraries/System.Runtime.Loader/tests/System.Runtime.Loader.Test.BindFailure.Missing/TestClass.cs b/src/libraries/System.Runtime.Loader/tests/System.Runtime.Loader.Test.BindFailure.Missing/TestClass.cs new file mode 100644 index 00000000000000..290afcaaddc603 --- /dev/null +++ b/src/libraries/System.Runtime.Loader/tests/System.Runtime.Loader.Test.BindFailure.Missing/TestClass.cs @@ -0,0 +1,10 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace BindFailureTest.Missing +{ + public class TestClass + { + public static string GetMessage() => "Hello from Missing test assembly"; + } +} diff --git a/src/libraries/System.Runtime.Loader/tests/System.Runtime.Loader.Tests.csproj b/src/libraries/System.Runtime.Loader/tests/System.Runtime.Loader.Tests.csproj index 073972916ff2cd..caa5d5b87864b2 100644 --- a/src/libraries/System.Runtime.Loader/tests/System.Runtime.Loader.Tests.csproj +++ b/src/libraries/System.Runtime.Loader/tests/System.Runtime.Loader.Tests.csproj @@ -34,6 +34,7 @@ + @@ -58,6 +59,9 @@ + + + @@ -113,4 +117,14 @@ + + + + + + + diff --git a/src/libraries/System.Runtime.Loader/tests/TpaLoadFailureTest.cs b/src/libraries/System.Runtime.Loader/tests/TpaLoadFailureTest.cs new file mode 100644 index 00000000000000..dc6ca9b84e7904 --- /dev/null +++ b/src/libraries/System.Runtime.Loader/tests/TpaLoadFailureTest.cs @@ -0,0 +1,103 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Xunit; +using System.IO; +using System.Runtime.CompilerServices; + +namespace System.Runtime.Loader.Tests +{ + public class TpaLoadFailureTest + { + private static string GetAssemblyPath(string assemblyName) + { + string appDir = Path.GetDirectoryName(AssemblyPathHelper.GetAssemblyLocation(typeof(TpaLoadFailureTest).Assembly)); + return Path.Combine(appDir, assemblyName + ".dll"); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + private static string UseMissingAssembly() => BindFailureTest.Missing.TestClass.GetMessage(); + + [MethodImpl(MethodImplOptions.NoInlining)] + private static string UseLockedAssembly() => BindFailureTest.Locked.TestClass.GetMessage(); + + [MethodImpl(MethodImplOptions.NoInlining)] + private static string UseCorruptAssembly() => BindFailureTest.Corrupt.TestClass.GetMessage(); + + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsCoreCLR), nameof(PlatformDetection.HasAssemblyFiles))] + public void NotFound_ExceptionContainsAssemblyPath() + { + // The Missing assembly is listed in deps.json (so the host adds it to the TPA + // list) but deleted from the output directory by the RemoveBindFailureTestAssemblies + // MSBuild target, so it will not be found at runtime. + const string assemblyName = "System.Runtime.Loader.Test.BindFailure.Missing"; + string dllPath = GetAssemblyPath(assemblyName); + Assert.False(File.Exists(dllPath), $"Test assembly should not be present at {dllPath}"); + + var ex = Assert.Throws(() => UseMissingAssembly()); + Assert.NotNull(ex.FileName); + Assert.Contains(assemblyName, ex.FileName); + Assert.NotNull(ex.FusionLog); + Assert.Contains(dllPath, ex.FusionLog); + Assert.Contains(HResults.COR_E_FILENOTFOUND.ToString("X8"), ex.FusionLog); + } + + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsCoreCLR), nameof(PlatformDetection.IsWindows), nameof(PlatformDetection.HasAssemblyFiles))] + public void SharingViolation_ExceptionContainsPathAndHResult() + { + // The Locked assembly is listed in deps.json but deleted from the output + // directory by the RemoveBindFailureTestAssemblies MSBuild target. + // We write a file and lock it before the load attempt. + const string assemblyName = "System.Runtime.Loader.Test.BindFailure.Locked"; + string dllPath = GetAssemblyPath(assemblyName); + Assert.False(File.Exists(dllPath), $"Test assembly should not be present at {dllPath}"); + + try + { + File.WriteAllBytes(dllPath, new byte[] { 0x4D, 0x5A, 0x90, 0x00 }); + + using FileStream _ = new FileStream(dllPath, FileMode.Open, FileAccess.Read, FileShare.None); + + var ex = Assert.Throws(() => UseLockedAssembly()); + Assert.NotNull(ex.FileName); + Assert.Contains(assemblyName, ex.FileName); + Assert.NotNull(ex.FusionLog); + Assert.Contains(dllPath, ex.FusionLog); + Assert.Contains(HResults.ERROR_SHARING_VIOLATION.ToString("X8"), ex.FusionLog); + } + finally + { + File.Delete(dllPath); + } + } + + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsCoreCLR), nameof(PlatformDetection.HasAssemblyFiles))] + public void Corrupt_ExceptionContainsPathAndHResult() + { + const int COR_E_ASSEMBLYEXPECTED = unchecked((int)0x80131018); + + // The Corrupt assembly is listed in deps.json but deleted from the output + // directory by the RemoveBindFailureTestAssemblies MSBuild target. + // We write a corrupt file in its place before the load attempt. + const string assemblyName = "System.Runtime.Loader.Test.BindFailure.Corrupt"; + string dllPath = GetAssemblyPath(assemblyName); + Assert.False(File.Exists(dllPath), $"Test assembly should not be present at {dllPath}"); + + try + { + File.WriteAllBytes(dllPath, new byte[] { 0xDE, 0xAD, 0xBE, 0xEF, 0x00, 0x00, 0x00, 0x00 }); + + var ex = Assert.Throws(() => UseCorruptAssembly()); + Assert.NotNull(ex.FileName); + Assert.Contains(assemblyName, ex.FileName); + Assert.NotNull(ex.FusionLog); + Assert.Contains(dllPath, ex.FusionLog); + Assert.Contains(COR_E_ASSEMBLYEXPECTED.ToString("X8"), ex.FusionLog); + } + finally + { + File.Delete(dllPath); + } + } + } +}