CoreCLR initialization failures logging (#78790)
authorJan Vorlicek <janvorli@microsoft.com>
Wed, 7 Dec 2022 01:29:15 +0000 (02:29 +0100)
committerGitHub <noreply@github.com>
Wed, 7 Dec 2022 01:29:15 +0000 (02:29 +0100)
* CoreCLR initialization failures logging

This change adds detecting and logging of failures during coreclr
initialization. For logging, it uses a new host API
`coreclr_set_error_writer` to register a callback to report the errors
to the host. The hosts have support for optional usage of this API so
that they can work with older runtime versions as well.

The logging checks and reports failures with:
* System.Private.CoreLib.dll
* GC initialization
* JIT initialization
* libSystem.Native.so/dylib on Unix

The logging messages should allow customers to self-diagnose the issues
causing the failures.

* Reflect PR feedback and a bit of cleanup

* revert the try_get_export to not to have the isOptional argument
* Unify the error logging in JIT loading to single variadic macro
* Also fixes Unix build, the code in gcenv.unix.cpp cannot use the GCToEEInterface

* Add single file host detection

* Fix libSystem.Native check location

* Add the coreclr_set_error_writer API to Mono

* Fix Mono x86 missing calling convention spec

* Fix error code returned when the S.P.C. is not found

26 files changed:
src/coreclr/binder/utils.cpp
src/coreclr/dlls/mscoree/exports.cpp
src/coreclr/dlls/mscoree/mscorwks_ntdef.src
src/coreclr/dlls/mscoree/mscorwks_unixexports.src
src/coreclr/gc/env/gcenv.ee.h
src/coreclr/gc/gc.cpp
src/coreclr/gc/gccommon.cpp
src/coreclr/gc/gcenv.ee.standalone.inl
src/coreclr/gc/gcinterface.ee.h
src/coreclr/gc/gcinterface.h
src/coreclr/gc/gcload.cpp
src/coreclr/gc/sample/gcenv.ee.cpp
src/coreclr/hosts/corerun/corerun.cpp
src/coreclr/hosts/inc/coreclrhost.h
src/coreclr/nativeaot/Runtime/gcrhenv.cpp
src/coreclr/vm/appdomain.cpp
src/coreclr/vm/ceemain.cpp
src/coreclr/vm/codeman.cpp
src/coreclr/vm/common.h
src/coreclr/vm/corhost.cpp
src/coreclr/vm/gcenv.ee.cpp
src/coreclr/vm/gcenv.ee.h
src/mono/mono/mini/main-core.c
src/native/corehost/coreclr_resolver.h
src/native/corehost/hostpolicy/coreclr.cpp
src/native/corehost/hostpolicy/standalone/coreclr_resolver.cpp

index 44d44dc..edd1e00 100644 (file)
@@ -150,56 +150,68 @@ namespace BINDER_SPACE
         isNativeImage = false;
 
         HRESULT pathResult = S_OK;
-        IF_FAIL_GO(pathResult = GetNextPath(paths, startPos, outPath));
-        if (pathResult == S_FALSE)
+        while(true)
         {
-            return S_FALSE;
-        }
-
-        if (Path::IsRelative(outPath))
-        {
-            GO_WITH_HRESULT(E_INVALIDARG);
-        }
-
-        {
-            // Find the beginning of the simple name
-            SString::CIterator iSimpleNameStart = outPath.End();
-
-            if (!outPath.FindBack(iSimpleNameStart, DIRECTORY_SEPARATOR_CHAR_W))
-            {
-                iSimpleNameStart = outPath.Begin();
-            }
-            else
+            IF_FAIL_GO(pathResult = GetNextPath(paths, startPos, outPath));
+            if (pathResult == S_FALSE)
             {
-                // Advance past the directory separator to the first character of the file name
-                iSimpleNameStart++;
+                return S_FALSE;
             }
 
-            if (iSimpleNameStart == outPath.End())
+            if (Path::IsRelative(outPath))
             {
                 GO_WITH_HRESULT(E_INVALIDARG);
             }
 
-            const SString sNiDll(SString::Literal, W(".ni.dll"));
-            const SString sNiExe(SString::Literal, W(".ni.exe"));
-            const SString sDll(SString::Literal, W(".dll"));
-            const SString sExe(SString::Literal, W(".exe"));
-
-            if (!dllOnly && (outPath.EndsWithCaseInsensitive(sNiDll) ||
-                outPath.EndsWithCaseInsensitive(sNiExe)))
-            {
-                simpleName.Set(outPath, iSimpleNameStart, outPath.End() - 7);
-                isNativeImage = true;
-            }
-            else if (outPath.EndsWithCaseInsensitive(sDll) ||
-                (!dllOnly && outPath.EndsWithCaseInsensitive(sExe)))
-            {
-                simpleName.Set(outPath, iSimpleNameStart, outPath.End() - 4);
-            }
-            else
             {
-                // Invalid filename
-                GO_WITH_HRESULT(E_INVALIDARG);
+                // Find the beginning of the simple name
+                SString::CIterator iSimpleNameStart = outPath.End();
+
+                if (!outPath.FindBack(iSimpleNameStart, DIRECTORY_SEPARATOR_CHAR_W))
+                {
+                    iSimpleNameStart = outPath.Begin();
+                }
+                else
+                {
+                    // Advance past the directory separator to the first character of the file name
+                    iSimpleNameStart++;
+                }
+
+                if (iSimpleNameStart == outPath.End())
+                {
+                    GO_WITH_HRESULT(E_INVALIDARG);
+                }
+
+                const SString sNiDll(SString::Literal, W(".ni.dll"));
+                const SString sNiExe(SString::Literal, W(".ni.exe"));
+                const SString sDll(SString::Literal, W(".dll"));
+                const SString sExe(SString::Literal, W(".exe"));
+
+                if (dllOnly && (outPath.EndsWithCaseInsensitive(sExe) ||
+                    outPath.EndsWithCaseInsensitive(sNiExe)))
+                {
+                    // Skip exe files when the caller requested only dlls
+                    continue;
+                }
+
+                if (outPath.EndsWithCaseInsensitive(sNiDll) ||
+                    outPath.EndsWithCaseInsensitive(sNiExe))
+                {
+                    simpleName.Set(outPath, iSimpleNameStart, outPath.End() - 7);
+                    isNativeImage = true;
+                }
+                else if (outPath.EndsWithCaseInsensitive(sDll) ||
+                    outPath.EndsWithCaseInsensitive(sExe))
+                {
+                    simpleName.Set(outPath, iSimpleNameStart, outPath.End() - 4);
+                }
+                else
+                {
+                    // Invalid filename
+                    GO_WITH_HRESULT(E_INVALIDARG);
+                }
+
+                break;
             }
         }
 
index 1d9cc71..8b96511 100644 (file)
@@ -181,6 +181,26 @@ static void ConvertConfigPropertiesToUnicode(
     *propertyValuesWRef = propertyValuesW;
 }
 
+coreclr_error_writer_callback_fn g_errorWriter = nullptr;
+
+//
+// Set callback for writing error logging
+//
+// Parameters:
+//  errorWriter             - callback that will be called for each line of the error info
+//                          - passing in NULL removes a callback that was previously set
+//
+// Returns:
+//  S_OK
+//
+extern "C"
+DLLEXPORT
+int coreclr_set_error_writer(coreclr_error_writer_callback_fn error_writer)
+{
+    g_errorWriter = error_writer;
+    return S_OK;
+}
+
 #ifdef FEATURE_GDBJIT
 GetInfoForMethodDelegate getInfoForMethodDelegate = NULL;
 extern "C" int coreclr_create_delegate(void*, unsigned int, const char*, const char*, const char*, void**);
@@ -462,3 +482,16 @@ int coreclr_execute_assembly(
 
     return hr;
 }
+
+void LogErrorToHost(const char* format, ...)
+{
+    if (g_errorWriter != NULL)
+    {
+        char messageBuffer[1024];
+        va_list args;
+        va_start(args, format);
+        _vsnprintf_s(messageBuffer, ARRAY_SIZE(messageBuffer), _TRUNCATE, format, args);
+        g_errorWriter(messageBuffer);
+        va_end(args);
+    }
+}
index 987f67b..0ac421b 100644 (file)
@@ -22,6 +22,7 @@ EXPORTS
         coreclr_create_delegate
         coreclr_execute_assembly
         coreclr_initialize
+        coreclr_set_error_writer
         coreclr_shutdown
         coreclr_shutdown_2
 
index ebf0556..a35a59c 100644 (file)
@@ -2,6 +2,7 @@
 coreclr_create_delegate
 coreclr_execute_assembly
 coreclr_initialize
+coreclr_set_error_writer
 coreclr_shutdown
 coreclr_shutdown_2
 
index 3a7c049..2f4eecc 100644 (file)
@@ -94,6 +94,8 @@ public:
     static uint32_t GetCurrentProcessCpuCount();
 
     static void DiagAddNewRegion(int generation, uint8_t* rangeStart, uint8_t* rangeEnd, uint8_t* rangeEndReserved);
+
+    static void LogErrorToHost(const char *message);
 };
 
 #endif // __GCENV_EE_H__
index 943874a..f02ace0 100644 (file)
@@ -13575,13 +13575,17 @@ HRESULT gc_heap::initialize_gc (size_t soh_segment_size,
         gc_log = CreateLogFile(GCConfig::GetLogFile(), false);
 
         if (gc_log == NULL)
+        {
+            GCToEEInterface::LogErrorToHost("Cannot create log file");
             return E_FAIL;
+        }
 
         // GCLogFileSize in MBs.
         gc_log_file_size = static_cast<size_t>(GCConfig::GetLogFileSize());
 
         if (gc_log_file_size <= 0 || gc_log_file_size > 500)
         {
+            GCToEEInterface::LogErrorToHost("Invalid log file size (valid size needs to be larger than 0 and smaller than 500)");
             fclose (gc_log);
             return E_FAIL;
         }
@@ -13591,7 +13595,7 @@ HRESULT gc_heap::initialize_gc (size_t soh_segment_size,
         if (!gc_log_buffer)
         {
             fclose(gc_log);
-            return E_FAIL;
+            return E_OUTOFMEMORY;
         }
 
         memset (gc_log_buffer, '*', gc_log_buffer_size);
@@ -13606,13 +13610,16 @@ HRESULT gc_heap::initialize_gc (size_t soh_segment_size,
         gc_config_log = CreateLogFile(GCConfig::GetConfigLogFile(), true);
 
         if (gc_config_log == NULL)
+        {
+            GCToEEInterface::LogErrorToHost("Cannot create log file");
             return E_FAIL;
+        }
 
         gc_config_log_buffer = new (nothrow) uint8_t [gc_config_log_buffer_size];
         if (!gc_config_log_buffer)
         {
             fclose(gc_config_log);
-            return E_FAIL;
+            return E_OUTOFMEMORY;
         }
 
         compact_ratio = static_cast<int>(GCConfig::GetCompactRatio());
@@ -13739,6 +13746,7 @@ HRESULT gc_heap::initialize_gc (size_t soh_segment_size,
     else
     {
         assert (!"cannot use regions without specifying the range!!!");
+        GCToEEInterface::LogErrorToHost("Cannot use regions without specifying the range (using DOTNET_GCRegionRange)");
         return E_FAIL;
     }
 #else //USE_REGIONS
@@ -13862,6 +13870,7 @@ HRESULT gc_heap::initialize_gc (size_t soh_segment_size,
 
     if (!init_semi_shared())
     {
+        GCToEEInterface::LogErrorToHost("PER_HEAP_ISOLATED data members initialization failed");
         hres = E_FAIL;
     }
 
@@ -45556,6 +45565,7 @@ HRESULT GCHeap::Initialize()
 
     if (!WaitForGCEvent->CreateManualEventNoThrow(TRUE))
     {
+        GCToEEInterface::LogErrorToHost("Creation of WaitForGCEvent failed");
         return E_FAIL;
     }
 
@@ -45637,9 +45647,15 @@ HRESULT GCHeap::Initialize()
         int hb_info_size_per_node = hb_info_size_per_proc * procs_per_numa_node;
         uint8_t* numa_mem = (uint8_t*)GCToOSInterface::VirtualReserve (hb_info_size_per_node, 0, 0, numa_node_index);
         if (!numa_mem)
+        {
+            GCToEEInterface::LogErrorToHost("Reservation of numa_mem failed");
             return E_FAIL;
+        }
         if (!GCToOSInterface::VirtualCommit (numa_mem, hb_info_size_per_node, numa_node_index))
+        {
+            GCToEEInterface::LogErrorToHost("Commit of numa_mem failed");
             return E_FAIL;
+        }
 
         heap_balance_info_proc* hb_info_procs = (heap_balance_info_proc*)numa_mem;
         hb_info_numa_nodes[numa_node_index].hb_info_procs = hb_info_procs;
index 4130752..2dca1e1 100644 (file)
@@ -17,7 +17,7 @@ IGCHeapInternal* g_theGCHeap;
 IGCHandleManager* g_theGCHandleManager;
 
 #ifdef BUILD_AS_STANDALONE
-IGCToCLR* g_theGCToCLR;
+IGCToCLR2* g_theGCToCLR;
 VersionInfo g_runtimeSupportedVersion;
 #endif // BUILD_AS_STANDALONE
 
index e52c42e..78bf2ee 100644 (file)
@@ -9,7 +9,7 @@
 
 // The singular interface instance. All calls in GCToEEInterface
 // will be forwarded to this interface instance.
-extern IGCToCLR* g_theGCToCLR;
+extern IGCToCLR2* g_theGCToCLR;
 
 // GC version that the current runtime supports
 extern VersionInfo g_runtimeSupportedVersion;
@@ -314,4 +314,12 @@ inline void GCToEEInterface::DiagAddNewRegion(int generation, uint8_t* rangeStar
     g_theGCToCLR->DiagAddNewRegion(generation, rangeStart, rangeEnd, rangeEndReserved);
 }
 
+inline void GCToEEInterface::LogErrorToHost(const char *message)
+{
+    if (g_runtimeSupportedVersion.MajorVersion >= GC_INTERFACE2_MAJOR_VERSION)
+    {
+        g_theGCToCLR->LogErrorToHost(message);
+    }
+}
+
 #endif // __GCTOENV_EE_STANDALONE_INL__
index e2019c8..e1a53bd 100644 (file)
@@ -448,4 +448,11 @@ public:
     void DiagAddNewRegion(int generation, uint8_t* rangeStart, uint8_t* rangeEnd, uint8_t* rangeEndReserved) = 0;
 };
 
+class IGCToCLR2 : public IGCToCLR {
+public:
+
+    virtual
+    void LogErrorToHost(const char *message) = 0;
+};
+
 #endif // _GCINTERFACE_EE_H_
index 7e8c1b5..cb70613 100644 (file)
@@ -6,13 +6,17 @@
 
 // The major version of the GC/EE interface. Breaking changes to this interface
 // require bumps in the major version number.
-#define GC_INTERFACE_MAJOR_VERSION 5
+#define GC_INTERFACE_MAJOR_VERSION 6
 
 // The minor version of the GC/EE interface. Non-breaking changes are required
 // to bump the minor version number. GCs and EEs with minor version number
 // mismatches can still interopate correctly, with some care.
 #define GC_INTERFACE_MINOR_VERSION 1
 
+// The major version of the GC/EE interface. Breaking changes to this interface
+// require bumps in the major version number.
+#define GC_INTERFACE2_MAJOR_VERSION 6
+
 struct ScanContext;
 struct gc_alloc_context;
 class CrawlFrame;
index c33125e..20c469f 100644 (file)
@@ -75,7 +75,7 @@ GC_Initialize(
 
 #ifdef BUILD_AS_STANDALONE
     assert(clrToGC != nullptr);
-    g_theGCToCLR = clrToGC;
+    g_theGCToCLR = (IGCToCLR2*)clrToGC;
 #else
     UNREFERENCED_PARAMETER(clrToGC);
     assert(clrToGC == nullptr);
@@ -88,6 +88,7 @@ GC_Initialize(
 
     if (!GCToOSInterface::Initialize())
     {
+        GCToEEInterface::LogErrorToHost("Failed to initialize GCToOSInterface");
         return E_FAIL;
     }
 #endif
index 84274f2..ac6d80b 100644 (file)
@@ -358,3 +358,7 @@ uint32_t GCToEEInterface::GetCurrentProcessCpuCount()
 void GCToEEInterface::DiagAddNewRegion(int generation, uint8_t* rangeStart, uint8_t* rangeEnd, uint8_t* rangeEndReserved)
 {
 }
+
+void GCToEEInterface::LogErrorToHost(const char *message)
+{
+}
index 60b5c55..49f549d 100644 (file)
@@ -205,6 +205,11 @@ public:
 static void* CurrentClrInstance;
 static unsigned int CurrentAppDomainId;
 
+static void log_error_info(const char* line)
+{
+    std::fprintf(stderr, "%s\n", line);
+}
+
 static int run(const configuration& config)
 {
     platform_specific_actions actions;
@@ -282,6 +287,7 @@ static int run(const configuration& config)
     // Get CoreCLR exports
     coreclr_initialize_ptr coreclr_init_func = nullptr;
     coreclr_execute_assembly_ptr coreclr_execute_func = nullptr;
+    coreclr_set_error_writer_ptr coreclr_set_error_writer_func = nullptr;
     coreclr_shutdown_2_ptr coreclr_shutdown2_func = nullptr;
     if (!try_get_export(coreclr_mod, "coreclr_initialize", (void**)&coreclr_init_func)
         || !try_get_export(coreclr_mod, "coreclr_execute_assembly", (void**)&coreclr_execute_func)
@@ -290,6 +296,9 @@ static int run(const configuration& config)
         return -1;
     }
 
+    // The coreclr_set_error_writer is optional
+    (void)try_get_export(coreclr_mod, "coreclr_set_error_writer", (void**)&coreclr_set_error_writer_func);
+
     // Construct CoreCLR properties.
     pal::string_utf8_t tpa_list_utf8 = pal::convert_to_utf8(tpa_list.c_str());
     pal::string_utf8_t app_path_utf8 = pal::convert_to_utf8(app_path.c_str());
@@ -344,6 +353,11 @@ static int run(const configuration& config)
         propertyCount, propertyKeys.data(), propertyValues.data(),
         entry_assembly_utf8.c_str(), config.entry_assembly_argc, argv_utf8.get() };
 
+    if (coreclr_set_error_writer_func != nullptr)
+    {
+        coreclr_set_error_writer_func(log_error_info);
+    }
+
     int result;
     result = coreclr_init_func(
         exe_path_utf8.c_str(),
@@ -361,6 +375,11 @@ static int run(const configuration& config)
         return -1;
     }
 
+    if (coreclr_set_error_writer_func != nullptr)
+    {
+        coreclr_set_error_writer_func(nullptr);
+    }
+
     int exit_code;
     {
         actions.before_execute_assembly(config.entry_assembly_fullpath);
index 46a3119..01eeac6 100644 (file)
@@ -48,6 +48,24 @@ CORECLR_HOSTING_API(coreclr_initialize,
             unsigned int* domainId);
 
 //
+// Type of the callback function that can be set by the coreclr_set_error_writer
+//
+typedef void (*coreclr_error_writer_callback_fn) (const char *message);
+
+//
+// Set callback for writing error logging
+//
+// Parameters:
+//  errorWriter             - callback that will be called for each line of the error info
+//                          - passing in NULL removes a callback that was previously set
+//
+// Returns:
+//  S_OK
+//
+CORECLR_HOSTING_API(coreclr_set_error_writer,
+            coreclr_error_writer_callback_fn errorWriter);
+
+//
 // Shutdown CoreCLR. It unloads the app domain and stops the CoreCLR host.
 //
 // Parameters:
index 5686438..9454d54 100644 (file)
@@ -1357,6 +1357,10 @@ bool GCToEEInterface::GetIntConfigValue(const char* privateKey, const char* publ
     return true;
 }
 
+void GCToEEInterface::LogErrorToHost(const char *message)
+{
+}
+
 bool GCToEEInterface::GetStringConfigValue(const char* privateKey, const char* publicKey, const char** value)
 {
     UNREFERENCED_PARAMETER(privateKey);
index 1397c16..51677bc 100644 (file)
@@ -1266,144 +1266,166 @@ void SystemDomain::LoadBaseSystemClasses()
 
     ETWOnStartup(LdSysBases_V1, LdSysBasesEnd_V1);
 
-    m_pSystemPEAssembly = PEAssembly::OpenSystem();
+    EX_TRY
+    {
+        m_pSystemPEAssembly = PEAssembly::OpenSystem();
 
-    // Only partially load the system assembly. Other parts of the code will want to access
-    // the globals in this function before finishing the load.
-    m_pSystemAssembly = DefaultDomain()->LoadDomainAssembly(NULL, m_pSystemPEAssembly, FILE_LOAD_POST_LOADLIBRARY)->GetAssembly();
+        // Only partially load the system assembly. Other parts of the code will want to access
+        // the globals in this function before finishing the load.
+        m_pSystemAssembly = DefaultDomain()->LoadDomainAssembly(NULL, m_pSystemPEAssembly, FILE_LOAD_POST_LOADLIBRARY)->GetAssembly();
 
-    // Set up binder for CoreLib
-    CoreLibBinder::AttachModule(m_pSystemAssembly->GetModule());
+        // Set up binder for CoreLib
+        CoreLibBinder::AttachModule(m_pSystemAssembly->GetModule());
 
-    // Load Object
-    g_pObjectClass = CoreLibBinder::GetClass(CLASS__OBJECT);
+        // Load Object
+        g_pObjectClass = CoreLibBinder::GetClass(CLASS__OBJECT);
 
-    // Now that ObjectClass is loaded, we can set up
-    // the system for finalizers.  There is no point in deferring this, since we need
-    // to know this before we allocate our first object.
-    g_pObjectFinalizerMD = CoreLibBinder::GetMethod(METHOD__OBJECT__FINALIZE);
+        // Now that ObjectClass is loaded, we can set up
+        // the system for finalizers.  There is no point in deferring this, since we need
+        // to know this before we allocate our first object.
+        g_pObjectFinalizerMD = CoreLibBinder::GetMethod(METHOD__OBJECT__FINALIZE);
 
 
-    g_pCanonMethodTableClass = CoreLibBinder::GetClass(CLASS____CANON);
+        g_pCanonMethodTableClass = CoreLibBinder::GetClass(CLASS____CANON);
 
-    // NOTE: !!!IMPORTANT!!! ValueType and Enum MUST be loaded one immediately after
-    //                       the other, because we have coded MethodTable::IsChildValueType
-    //                       in such a way that it depends on this behaviour.
-    // Load the ValueType class
-    g_pValueTypeClass = CoreLibBinder::GetClass(CLASS__VALUE_TYPE);
+        // NOTE: !!!IMPORTANT!!! ValueType and Enum MUST be loaded one immediately after
+        //                       the other, because we have coded MethodTable::IsChildValueType
+        //                       in such a way that it depends on this behaviour.
+        // Load the ValueType class
+        g_pValueTypeClass = CoreLibBinder::GetClass(CLASS__VALUE_TYPE);
 
-    // Load the enum class
-    g_pEnumClass = CoreLibBinder::GetClass(CLASS__ENUM);
-    _ASSERTE(!g_pEnumClass->IsValueType());
+        // Load the enum class
+        g_pEnumClass = CoreLibBinder::GetClass(CLASS__ENUM);
+        _ASSERTE(!g_pEnumClass->IsValueType());
 
-    // Load System.RuntimeType
-    g_pRuntimeTypeClass = CoreLibBinder::GetClass(CLASS__CLASS);
-    _ASSERTE(g_pRuntimeTypeClass->IsFullyLoaded());
+        // Load System.RuntimeType
+        g_pRuntimeTypeClass = CoreLibBinder::GetClass(CLASS__CLASS);
+        _ASSERTE(g_pRuntimeTypeClass->IsFullyLoaded());
 
-    // Load Array class
-    g_pArrayClass = CoreLibBinder::GetClass(CLASS__ARRAY);
+        // Load Array class
+        g_pArrayClass = CoreLibBinder::GetClass(CLASS__ARRAY);
 
-    // Calling a method on IList<T> for an array requires redirection to a method on
-    // the SZArrayHelper class. Retrieving such methods means calling
-    // GetActualImplementationForArrayGenericIListMethod, which calls FetchMethod for
-    // the corresponding method on SZArrayHelper. This basically results in a class
-    // load due to a method call, which the debugger cannot handle, so we pre-load
-    // the SZArrayHelper class here.
-    g_pSZArrayHelperClass = CoreLibBinder::GetClass(CLASS__SZARRAYHELPER);
+        // Calling a method on IList<T> for an array requires redirection to a method on
+        // the SZArrayHelper class. Retrieving such methods means calling
+        // GetActualImplementationForArrayGenericIListMethod, which calls FetchMethod for
+        // the corresponding method on SZArrayHelper. This basically results in a class
+        // load due to a method call, which the debugger cannot handle, so we pre-load
+        // the SZArrayHelper class here.
+        g_pSZArrayHelperClass = CoreLibBinder::GetClass(CLASS__SZARRAYHELPER);
 
-    // Load Nullable class
-    g_pNullableClass = CoreLibBinder::GetClass(CLASS__NULLABLE);
+        // Load Nullable class
+        g_pNullableClass = CoreLibBinder::GetClass(CLASS__NULLABLE);
 
-    // Load the Object array class.
-    g_pPredefinedArrayTypes[ELEMENT_TYPE_OBJECT] = ClassLoader::LoadArrayTypeThrowing(TypeHandle(g_pObjectClass));
+        // Load the Object array class.
+        g_pPredefinedArrayTypes[ELEMENT_TYPE_OBJECT] = ClassLoader::LoadArrayTypeThrowing(TypeHandle(g_pObjectClass));
 
-    // We have delayed allocation of CoreLib's static handles until we load the object class
-    CoreLibBinder::GetModule()->AllocateRegularStaticHandles(DefaultDomain());
+        // We have delayed allocation of CoreLib's static handles until we load the object class
+        CoreLibBinder::GetModule()->AllocateRegularStaticHandles(DefaultDomain());
 
-    // Boolean has to be loaded first to break cycle in IComparisonOperations and IEqualityOperators
-    CoreLibBinder::LoadPrimitiveType(ELEMENT_TYPE_BOOLEAN);
+        // Boolean has to be loaded first to break cycle in IComparisonOperations and IEqualityOperators
+        CoreLibBinder::LoadPrimitiveType(ELEMENT_TYPE_BOOLEAN);
 
-    // Int32 has to be loaded next to break cycle in IShiftOperators
-    CoreLibBinder::LoadPrimitiveType(ELEMENT_TYPE_I4);
+        // Int32 has to be loaded next to break cycle in IShiftOperators
+        CoreLibBinder::LoadPrimitiveType(ELEMENT_TYPE_I4);
 
-    // Make sure all primitive types are loaded
-    for (int et = ELEMENT_TYPE_VOID; et <= ELEMENT_TYPE_R8; et++)
-        CoreLibBinder::LoadPrimitiveType((CorElementType)et);
+        // Make sure all primitive types are loaded
+        for (int et = ELEMENT_TYPE_VOID; et <= ELEMENT_TYPE_R8; et++)
+            CoreLibBinder::LoadPrimitiveType((CorElementType)et);
 
-    CoreLibBinder::LoadPrimitiveType(ELEMENT_TYPE_I);
-    CoreLibBinder::LoadPrimitiveType(ELEMENT_TYPE_U);
+        CoreLibBinder::LoadPrimitiveType(ELEMENT_TYPE_I);
+        CoreLibBinder::LoadPrimitiveType(ELEMENT_TYPE_U);
 
-    g_TypedReferenceMT = CoreLibBinder::GetClass(CLASS__TYPED_REFERENCE);
+        g_TypedReferenceMT = CoreLibBinder::GetClass(CLASS__TYPED_REFERENCE);
 
-    // unfortunately, the following cannot be delay loaded since the jit
-    // uses it to compute method attributes within a function that cannot
-    // handle Complus exception and the following call goes through a path
-    // where a complus exception can be thrown. It is unfortunate, because
-    // we know that the delegate class and multidelegate class are always
-    // guaranteed to be found.
-    g_pDelegateClass = CoreLibBinder::GetClass(CLASS__DELEGATE);
-    g_pMulticastDelegateClass = CoreLibBinder::GetClass(CLASS__MULTICAST_DELEGATE);
+        // unfortunately, the following cannot be delay loaded since the jit
+        // uses it to compute method attributes within a function that cannot
+        // handle Complus exception and the following call goes through a path
+        // where a complus exception can be thrown. It is unfortunate, because
+        // we know that the delegate class and multidelegate class are always
+        // guaranteed to be found.
+        g_pDelegateClass = CoreLibBinder::GetClass(CLASS__DELEGATE);
+        g_pMulticastDelegateClass = CoreLibBinder::GetClass(CLASS__MULTICAST_DELEGATE);
 
-    // further loading of nonprimitive types may need casting support.
-    // initialize cast cache here.
-    CastCache::Initialize();
-    ECall::PopulateManagedCastHelpers();
+        // further loading of nonprimitive types may need casting support.
+        // initialize cast cache here.
+        CastCache::Initialize();
+        ECall::PopulateManagedCastHelpers();
 
-    // used by IsImplicitInterfaceOfSZArray
-    CoreLibBinder::GetClass(CLASS__IENUMERABLEGENERIC);
-    CoreLibBinder::GetClass(CLASS__ICOLLECTIONGENERIC);
-    CoreLibBinder::GetClass(CLASS__ILISTGENERIC);
-    CoreLibBinder::GetClass(CLASS__IREADONLYCOLLECTIONGENERIC);
-    CoreLibBinder::GetClass(CLASS__IREADONLYLISTGENERIC);
+        // used by IsImplicitInterfaceOfSZArray
+        CoreLibBinder::GetClass(CLASS__IENUMERABLEGENERIC);
+        CoreLibBinder::GetClass(CLASS__ICOLLECTIONGENERIC);
+        CoreLibBinder::GetClass(CLASS__ILISTGENERIC);
+        CoreLibBinder::GetClass(CLASS__IREADONLYCOLLECTIONGENERIC);
+        CoreLibBinder::GetClass(CLASS__IREADONLYLISTGENERIC);
 
-    // Load String
-    g_pStringClass = CoreLibBinder::LoadPrimitiveType(ELEMENT_TYPE_STRING);
+        // Load String
+        g_pStringClass = CoreLibBinder::LoadPrimitiveType(ELEMENT_TYPE_STRING);
 
-    ECall::PopulateManagedStringConstructors();
+        ECall::PopulateManagedStringConstructors();
 
-    g_pExceptionClass = CoreLibBinder::GetClass(CLASS__EXCEPTION);
-    g_pOutOfMemoryExceptionClass = CoreLibBinder::GetException(kOutOfMemoryException);
-    g_pStackOverflowExceptionClass = CoreLibBinder::GetException(kStackOverflowException);
-    g_pExecutionEngineExceptionClass = CoreLibBinder::GetException(kExecutionEngineException);
-    g_pThreadAbortExceptionClass = CoreLibBinder::GetException(kThreadAbortException);
+        g_pExceptionClass = CoreLibBinder::GetClass(CLASS__EXCEPTION);
+        g_pOutOfMemoryExceptionClass = CoreLibBinder::GetException(kOutOfMemoryException);
+        g_pStackOverflowExceptionClass = CoreLibBinder::GetException(kStackOverflowException);
+        g_pExecutionEngineExceptionClass = CoreLibBinder::GetException(kExecutionEngineException);
+        g_pThreadAbortExceptionClass = CoreLibBinder::GetException(kThreadAbortException);
 
-    g_pThreadClass = CoreLibBinder::GetClass(CLASS__THREAD);
+        g_pThreadClass = CoreLibBinder::GetClass(CLASS__THREAD);
 
-    g_pWeakReferenceClass = CoreLibBinder::GetClass(CLASS__WEAKREFERENCE);
-    g_pWeakReferenceOfTClass = CoreLibBinder::GetClass(CLASS__WEAKREFERENCEGENERIC);
+        g_pWeakReferenceClass = CoreLibBinder::GetClass(CLASS__WEAKREFERENCE);
+        g_pWeakReferenceOfTClass = CoreLibBinder::GetClass(CLASS__WEAKREFERENCEGENERIC);
 
-#ifdef FEATURE_COMINTEROP
-    if (g_pConfig->IsBuiltInCOMSupported())
-    {
-        g_pBaseCOMObject = CoreLibBinder::GetClass(CLASS__COM_OBJECT);
-    }
-    else
-    {
-        g_pBaseCOMObject = NULL;
-    }
-#endif
+    #ifdef FEATURE_COMINTEROP
+        if (g_pConfig->IsBuiltInCOMSupported())
+        {
+            g_pBaseCOMObject = CoreLibBinder::GetClass(CLASS__COM_OBJECT);
+        }
+        else
+        {
+            g_pBaseCOMObject = NULL;
+        }
+    #endif
 
-    g_pIDynamicInterfaceCastableInterface = CoreLibBinder::GetClass(CLASS__IDYNAMICINTERFACECASTABLE);
+        g_pIDynamicInterfaceCastableInterface = CoreLibBinder::GetClass(CLASS__IDYNAMICINTERFACECASTABLE);
 
-#ifdef FEATURE_ICASTABLE
-    g_pICastableInterface = CoreLibBinder::GetClass(CLASS__ICASTABLE);
-#endif // FEATURE_ICASTABLE
+    #ifdef FEATURE_ICASTABLE
+        g_pICastableInterface = CoreLibBinder::GetClass(CLASS__ICASTABLE);
+    #endif // FEATURE_ICASTABLE
 
-    // Make sure that FCall mapping for Monitor.Enter is initialized. We need it in case Monitor.Enter is used only as JIT helper.
-    // For more details, see comment in code:JITutil_MonEnterWorker around "__me = GetEEFuncEntryPointMacro(JIT_MonEnter)".
-    ECall::GetFCallImpl(CoreLibBinder::GetMethod(METHOD__MONITOR__ENTER));
+        // Make sure that FCall mapping for Monitor.Enter is initialized. We need it in case Monitor.Enter is used only as JIT helper.
+        // For more details, see comment in code:JITutil_MonEnterWorker around "__me = GetEEFuncEntryPointMacro(JIT_MonEnter)".
+        ECall::GetFCallImpl(CoreLibBinder::GetMethod(METHOD__MONITOR__ENTER));
 
-#ifdef PROFILING_SUPPORTED
-    // Note that g_profControlBlock.fBaseSystemClassesLoaded must be set to TRUE only after
-    // all base system classes are loaded.  Profilers are not allowed to call any type-loading
-    // APIs until g_profControlBlock.fBaseSystemClassesLoaded is TRUE.  It is important that
-    // all base system classes need to be loaded before profilers can trigger the type loading.
-    g_profControlBlock.fBaseSystemClassesLoaded = TRUE;
-#endif // PROFILING_SUPPORTED
+    #ifdef PROFILING_SUPPORTED
+        // Note that g_profControlBlock.fBaseSystemClassesLoaded must be set to TRUE only after
+        // all base system classes are loaded.  Profilers are not allowed to call any type-loading
+        // APIs until g_profControlBlock.fBaseSystemClassesLoaded is TRUE.  It is important that
+        // all base system classes need to be loaded before profilers can trigger the type loading.
+        g_profControlBlock.fBaseSystemClassesLoaded = TRUE;
+    #endif // PROFILING_SUPPORTED
 
-#if defined(_DEBUG)
-    g_CoreLib.Check();
-#endif
+        // Perform any once-only SafeHandle initialization.
+        SafeHandle::Init();
+
+    #if defined(_DEBUG)
+        g_CoreLib.Check();
+        g_CoreLib.CheckExtended();
+    #endif // _DEBUG
+    }
+    EX_HOOK
+    {
+        Exception *ex = GET_EXCEPTION();
+
+        LogErrorToHost("Failed to load System.Private.CoreLib.dll (error code 0x%08X)", ex->GetHR());
+        MAKE_UTF8PTR_FROMWIDE_NOTHROW(filePathUtf8, SystemDomain::System()->BaseLibrary())
+        if (filePathUtf8 != NULL)
+        {
+            LogErrorToHost("Path: %s", filePathUtf8);
+        }
+        SString err;
+        ex->GetMessage(err);
+        LogErrorToHost("Error message: %s", err.GetUTF8());
+    }
+    EX_END_HOOK;
 }
 
 #endif // !DACCESS_COMPILE
index 00411af..668f018 100644 (file)
@@ -876,6 +876,11 @@ void EEStartupHelper()
         // requires write barriers to have been set up on x86, which happens as part
         // of InitJITHelpers1.
         hr = g_pGCHeap->Initialize();
+        if (FAILED(hr))
+        {
+            LogErrorToHost("GC heap initialization failed with error 0x%08X", hr);
+        }
+
         IfFailGo(hr);
 
 #ifdef FEATURE_PERFTRACING
@@ -931,9 +936,6 @@ void EEStartupHelper()
         StackSampler::Init();
 #endif
 
-        // Perform any once-only SafeHandle initialization.
-        SafeHandle::Init();
-
 #ifdef FEATURE_MINIMETADATA_IN_TRIAGEDUMPS
         // retrieve configured max size for the mini-metadata buffer (defaults to 64KB)
         g_MiniMetaDataBuffMaxSize = CLRConfig::GetConfigValue(CLRConfig::INTERNAL_MiniMdBufferCapacity);
@@ -947,7 +949,6 @@ void EEStartupHelper()
                                                 g_MiniMetaDataBuffMaxSize, MEM_COMMIT, PAGE_READWRITE);
 #endif // FEATURE_MINIMETADATA_IN_TRIAGEDUMPS
 
-
         g_fEEStarted = TRUE;
         g_EEStartupStatus = S_OK;
         hr = S_OK;
@@ -968,9 +969,6 @@ void EEStartupHelper()
         {
             SystemDomain::SystemModule()->ExpandAll();
         }
-
-        // Perform CoreLib consistency check if requested
-        g_CoreLib.CheckExtended();
 #endif // _DEBUG
 
 
@@ -978,6 +976,7 @@ ErrExit: ;
     }
     EX_CATCH
     {
+        hr = GET_EXCEPTION()->GetHR();
     }
     EX_END_CATCH(RethrowTerminalExceptionsWithInitCheck)
 
@@ -1628,8 +1627,10 @@ void InitializeGarbageCollector()
     g_pFreeObjectMethodTable->SetComponentSize(1);
 
     hr = GCHeapUtilities::LoadAndInitialize();
+
     if (hr != S_OK)
     {
+        LogErrorToHost("GC initialization failed with error 0x%08X", hr);
         ThrowHR(hr);
     }
 
index 0dc1e17..c041c60 100644 (file)
@@ -1975,6 +1975,10 @@ static bool ValidateJitName(LPCWSTR pwzJitName)
 
 CORINFO_OS getClrVmOs();
 
+#define LogJITInitializationError(...) \
+    LOG((LF_JIT, LL_FATALERROR, __VA_ARGS__)); \
+    LogErrorToHost(__VA_ARGS__);
+
 // LoadAndInitializeJIT: load the JIT dll into the process, and initialize it (call the UtilCode initialization function,
 // check the JIT-EE interface GUID, etc.)
 //
@@ -2027,7 +2031,7 @@ static void LoadAndInitializeJIT(LPCWSTR pwzJitName DEBUGARG(LPCWSTR pwzJitPath)
         if (pwzJitName == nullptr)
         {
             pJitLoadData->jld_hr = E_FAIL;
-            LOG((LF_JIT, LL_FATALERROR, "LoadAndInitializeJIT: pwzJitName is null"));
+            LogJITInitializationError("LoadAndInitializeJIT: pwzJitName is null");
             return;
         }
 
@@ -2054,10 +2058,13 @@ static void LoadAndInitializeJIT(LPCWSTR pwzJitName DEBUGARG(LPCWSTR pwzJitPath)
         }
         else
         {
-            LOG((LF_JIT, LL_FATALERROR, "LoadAndInitializeJIT: invalid characters in %S\n", pwzJitName));
+            MAKE_UTF8PTR_FROMWIDE_NOTHROW(utf8JitName, pwzJitName);
+            LogJITInitializationError("LoadAndInitializeJIT: invalid characters in %s", utf8JitName);
         }
     }
 
+    MAKE_UTF8PTR_FROMWIDE_NOTHROW(utf8JitName, pwzJitName);
+
     if (SUCCEEDED(hr))
     {
         pJitLoadData->jld_status = JIT_LOAD_STATUS_DONE_LOAD;
@@ -2110,29 +2117,29 @@ static void LoadAndInitializeJIT(LPCWSTR pwzJitName DEBUGARG(LPCWSTR pwzJitPath)
                     else
                     {
                         // Mismatched version ID. Fail the load.
-                        LOG((LF_JIT, LL_FATALERROR, "LoadAndInitializeJIT: mismatched JIT version identifier in %S\n", pwzJitName));
+                        LogJITInitializationError("LoadAndInitializeJIT: mismatched JIT version identifier in %s", utf8JitName);
                     }
                 }
                 else
                 {
-                    LOG((LF_JIT, LL_FATALERROR, "LoadAndInitializeJIT: failed to get ICorJitCompiler in %S\n", pwzJitName));
+                    LogJITInitializationError("LoadAndInitializeJIT: failed to get ICorJitCompiler in %s", utf8JitName);
                 }
             }
             else
             {
-                LOG((LF_JIT, LL_FATALERROR, "LoadAndInitializeJIT: failed to find 'getJit' entrypoint in %S\n", pwzJitName));
+                LogJITInitializationError("LoadAndInitializeJIT: failed to find 'getJit' entrypoint in %s", utf8JitName);
             }
         }
         EX_CATCH
         {
-            LOG((LF_JIT, LL_FATALERROR, "LoadAndInitializeJIT: caught an exception trying to initialize %S\n", pwzJitName));
+            LogJITInitializationError("LoadAndInitializeJIT: LoadAndInitializeJIT: caught an exception trying to initialize %s", utf8JitName);
         }
         EX_END_CATCH(SwallowAllExceptions)
     }
     else
     {
         pJitLoadData->jld_hr = hr;
-        LOG((LF_JIT, LL_FATALERROR, "LoadAndInitializeJIT: failed to load %S, hr=0x%08x\n", pwzJitName, hr));
+        LogJITInitializationError("LoadAndInitializeJIT: failed to load %s, hr=0x%08X", utf8JitName, hr);
     }
 }
 
index 902f7ca..7e5d830 100644 (file)
@@ -390,6 +390,8 @@ extern DummyGlobalContract ___contract;
 #undef FPO_ON
 #endif
 
+void LogErrorToHost(const char* format, ...);
+
 #endif // !_common_h_
 
 
index 052e6a0..2877e85 100644 (file)
@@ -36,6 +36,8 @@
 #include "dwreport.h"
 #endif // !TARGET_UNIX
 
+#include "nativelibrary.h"
+
 #ifndef DACCESS_COMPILE
 
 #include <corehost/host_runtime_contract.h>
@@ -628,6 +630,24 @@ HRESULT CorHost2::CreateAppDomainWithManager(
             sAppPaths));
     }
 
+#if defined(TARGET_UNIX) && !defined(CORECLR_EMBEDDED)
+    // Check if the current code is executing in the single file host or in libcoreclr.so. The libSystem.Native is linked
+    // into the single file host, so we need to check only when this code is in libcoreclr.so.
+    // Preload the libSystem.Native.so/dylib to detect possible problems with loading it early
+    EX_TRY
+    {
+        NativeLibrary::LoadLibraryByName(W("libSystem.Native"), SystemDomain::SystemAssembly(), FALSE, 0, TRUE);
+    }
+    EX_HOOK
+    {
+        Exception *ex = GET_EXCEPTION();
+        SString err;
+        ex->GetMessage(err);
+        LogErrorToHost("Error message: %s", err.GetUTF8());
+    }
+    EX_END_HOOK;
+#endif // TARGET_UNIX && !CORECLR_EMBEDDED
+
     *pAppDomainID=DefaultADID;
 
     m_fAppDomainCreated = TRUE;
index 677f67b..1ecb498 100644 (file)
@@ -1737,3 +1737,8 @@ void GCToEEInterface::DiagAddNewRegion(int generation, uint8_t* rangeStart, uint
 {
     ProfilerAddNewRegion(generation, rangeStart, rangeEnd, rangeEndReserved);
 }
+
+void GCToEEInterface::LogErrorToHost(const char *message)
+{
+    ::LogErrorToHost("GC: %s", message);
+}
index 6403f26..c431cb8 100644 (file)
@@ -87,6 +87,8 @@ public:
     uint32_t GetCurrentProcessCpuCount();
 
     void DiagAddNewRegion(int generation, BYTE * rangeStart, BYTE * rangeEnd, BYTE * rangeEndReserved);
+
+    void LogErrorToHost(const char *message);
 };
 
 } // namespace standalone
index 66e85d7..d9e2626 100644 (file)
 #pragma comment(linker, "/export:coreclr_execute_assembly=_coreclr_execute_assembly@24")
 #pragma comment(linker, "/export:coreclr_shutdown_2=_coreclr_shutdown_2@12")
 #pragma comment(linker, "/export:coreclr_create_delegate=_coreclr_create_delegate@24")
+#pragma comment(linker, "/export:coreclr_set_error_writer=_coreclr_set_error_writer@4")
 #undef MONO_API
 #define MONO_API MONO_EXTERN_C
 #endif
 
+//
+// Type of the callback function that can be set by the coreclr_set_error_writer
+//
+typedef void (*coreclr_error_writer_callback_fn) (const char *message);
+
 MONO_API int STDAPICALLTYPE coreclr_initialize (const char* exePath, const char* appDomainFriendlyName,
        int propertyCount, const char** propertyKeys, const char** propertyValues,
        void** hostHandle, unsigned int* domainId);
@@ -38,6 +44,8 @@ MONO_API int STDAPICALLTYPE coreclr_create_delegate (void* hostHandle, unsigned
        const char* entryPointAssemblyName, const char* entryPointTypeName, const char* entryPointMethodName,
        void** delegate);
 
+MONO_API int STDAPICALLTYPE coreclr_set_error_writer(coreclr_error_writer_callback_fn error_writer);
+
 //
 // Initialize the CoreCLR. Creates and starts CoreCLR host and creates an app domain
 //
@@ -117,3 +125,18 @@ int STDAPICALLTYPE coreclr_create_delegate (void* hostHandle, unsigned int domai
 {
        return monovm_create_delegate (entryPointAssemblyName, entryPointTypeName, entryPointMethodName, delegate);
 }
+
+//
+// Set callback for writing error logging
+//
+// Parameters:
+//  errorWriter             - callback that will be called for each line of the error info
+//                          - passing in NULL removes a callback that was previously set
+//
+// Returns:
+//  S_OK
+//
+int STDAPICALLTYPE coreclr_set_error_writer(coreclr_error_writer_callback_fn error_writer)
+{
+    return 0; // S_OK
+}
index dc7ad88..81d6403 100644 (file)
@@ -9,6 +9,8 @@
 
 using host_handle_t = void*;
 
+typedef void (*coreclr_error_writer_callback_fn)(const char* line);
+
 // Prototype of the coreclr_initialize function from coreclr.dll
 using coreclr_initialize_fn = pal::hresult_t(STDMETHODCALLTYPE*)(
     const char* exePath,
@@ -19,6 +21,10 @@ using coreclr_initialize_fn = pal::hresult_t(STDMETHODCALLTYPE*)(
     host_handle_t* hostHandle,
     unsigned int* domainId);
 
+// Prototype of the coreclr_set_error_writer function from coreclr.dll
+using coreclr_set_error_writer_fn = pal::hresult_t(STDMETHODCALLTYPE*)(
+    coreclr_error_writer_callback_fn callBack);
+
 // Prototype of the coreclr_shutdown function from coreclr.dll
 using coreclr_shutdown_fn = pal::hresult_t(STDMETHODCALLTYPE*)(
     host_handle_t hostHandle,
@@ -46,6 +52,7 @@ using coreclr_create_delegate_fn = pal::hresult_t(STDMETHODCALLTYPE*)(
 struct coreclr_resolver_contract_t
 {
     pal::dll_t coreclr;
+    coreclr_set_error_writer_fn coreclr_set_error_writer;
     coreclr_shutdown_fn coreclr_shutdown;
     coreclr_initialize_fn coreclr_initialize;
     coreclr_execute_assembly_fn coreclr_execute_assembly;
index 88f2933..2d5a353 100644 (file)
@@ -18,6 +18,13 @@ namespace
         coreclr_resolver_t::resolve_coreclr(libcoreclr_path, coreclr_contract);
         return true;
     }
+
+    void log_error(const char* line)
+    {
+        pal::string_t lineStr;
+        pal::clr_palstring(line, &lineStr);
+        trace::error(_X("%s"), lineStr.c_str());
+    }
 }
 
 pal::hresult_t coreclr_t::create(
@@ -54,6 +61,14 @@ pal::hresult_t coreclr_t::create(
     };
     properties.enumerate(callback);
 
+    // Can't use propagate_error_writer_t here because of the difference in encoding on Windows
+    // coreclr error writer always gets UTF8 string, but error writers in hostfxr/hostpolicy will use UTF16 on Windows
+    // and UTF8 everywhere else.
+    if (coreclr_contract.coreclr_set_error_writer != nullptr)
+    {
+        coreclr_contract.coreclr_set_error_writer(log_error);
+    }
+
     pal::hresult_t hr;
     hr = coreclr_contract.coreclr_initialize(
         exe_path,
@@ -64,6 +79,11 @@ pal::hresult_t coreclr_t::create(
         &host_handle,
         &domain_id);
 
+    if (coreclr_contract.coreclr_set_error_writer != nullptr)
+    {
+        coreclr_contract.coreclr_set_error_writer(nullptr);
+    }
+
     if (!SUCCEEDED(hr))
         return hr;
 
index 8248118..b040c3e 100644 (file)
@@ -19,10 +19,12 @@ bool coreclr_resolver_t::resolve_coreclr(const pal::string_t& libcoreclr_path, c
     }
 
     coreclr_resolver_contract.coreclr_initialize = reinterpret_cast<coreclr_initialize_fn>(pal::get_symbol(coreclr_resolver_contract.coreclr, "coreclr_initialize"));
+    coreclr_resolver_contract.coreclr_set_error_writer = reinterpret_cast<coreclr_set_error_writer_fn>(pal::get_symbol(coreclr_resolver_contract.coreclr, "coreclr_set_error_writer"));
     coreclr_resolver_contract.coreclr_shutdown = reinterpret_cast<coreclr_shutdown_fn>(pal::get_symbol(coreclr_resolver_contract.coreclr, "coreclr_shutdown_2"));
     coreclr_resolver_contract.coreclr_execute_assembly = reinterpret_cast<coreclr_execute_assembly_fn>(pal::get_symbol(coreclr_resolver_contract.coreclr, "coreclr_execute_assembly"));
     coreclr_resolver_contract.coreclr_create_delegate = reinterpret_cast<coreclr_create_delegate_fn>(pal::get_symbol(coreclr_resolver_contract.coreclr, "coreclr_create_delegate"));
 
+    // Only the coreclr_set_error_writer is optional
     assert(coreclr_resolver_contract.coreclr_initialize != nullptr
         && coreclr_resolver_contract.coreclr_shutdown != nullptr
         && coreclr_resolver_contract.coreclr_execute_assembly != nullptr