diff --git a/src/coreclr/binder/utils.cpp b/src/coreclr/binder/utils.cpp index 44d44dc9c0a7b..edd1e0026b74d 100644 --- a/src/coreclr/binder/utils.cpp +++ b/src/coreclr/binder/utils.cpp @@ -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; } } diff --git a/src/coreclr/dlls/mscoree/exports.cpp b/src/coreclr/dlls/mscoree/exports.cpp index e8ee88275df8e..f01c01d5face8 100644 --- a/src/coreclr/dlls/mscoree/exports.cpp +++ b/src/coreclr/dlls/mscoree/exports.cpp @@ -158,6 +158,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**); @@ -432,3 +452,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); + } +} diff --git a/src/coreclr/dlls/mscoree/mscorwks_ntdef.src b/src/coreclr/dlls/mscoree/mscorwks_ntdef.src index 987f67bc36aff..0ac421b63e071 100644 --- a/src/coreclr/dlls/mscoree/mscorwks_ntdef.src +++ b/src/coreclr/dlls/mscoree/mscorwks_ntdef.src @@ -22,6 +22,7 @@ EXPORTS coreclr_create_delegate coreclr_execute_assembly coreclr_initialize + coreclr_set_error_writer coreclr_shutdown coreclr_shutdown_2 diff --git a/src/coreclr/dlls/mscoree/mscorwks_unixexports.src b/src/coreclr/dlls/mscoree/mscorwks_unixexports.src index ebf0556e7a870..a35a59c095604 100644 --- a/src/coreclr/dlls/mscoree/mscorwks_unixexports.src +++ b/src/coreclr/dlls/mscoree/mscorwks_unixexports.src @@ -2,6 +2,7 @@ coreclr_create_delegate coreclr_execute_assembly coreclr_initialize +coreclr_set_error_writer coreclr_shutdown coreclr_shutdown_2 diff --git a/src/coreclr/gc/env/gcenv.ee.h b/src/coreclr/gc/env/gcenv.ee.h index 3a7c049d4af88..2f4eecc3501fa 100644 --- a/src/coreclr/gc/env/gcenv.ee.h +++ b/src/coreclr/gc/env/gcenv.ee.h @@ -94,6 +94,8 @@ class GCToEEInterface 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__ diff --git a/src/coreclr/gc/gc.cpp b/src/coreclr/gc/gc.cpp index aa596a10a7c4d..e2e161ac8966b 100644 --- a/src/coreclr/gc/gc.cpp +++ b/src/coreclr/gc/gc.cpp @@ -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(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(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; } @@ -45537,6 +45546,7 @@ HRESULT GCHeap::Initialize() if (!WaitForGCEvent->CreateManualEventNoThrow(TRUE)) { + GCToEEInterface::LogErrorToHost("Creation of WaitForGCEvent failed"); return E_FAIL; } @@ -45618,9 +45628,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; diff --git a/src/coreclr/gc/gccommon.cpp b/src/coreclr/gc/gccommon.cpp index 413075246fd5c..2dca1e19281ea 100644 --- a/src/coreclr/gc/gccommon.cpp +++ b/src/coreclr/gc/gccommon.cpp @@ -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 diff --git a/src/coreclr/gc/gcenv.ee.standalone.inl b/src/coreclr/gc/gcenv.ee.standalone.inl index e52c42efcb470..78bf2ee7daff0 100644 --- a/src/coreclr/gc/gcenv.ee.standalone.inl +++ b/src/coreclr/gc/gcenv.ee.standalone.inl @@ -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__ diff --git a/src/coreclr/gc/gcinterface.ee.h b/src/coreclr/gc/gcinterface.ee.h index e2019c8a1adbe..e1a53bd244d24 100644 --- a/src/coreclr/gc/gcinterface.ee.h +++ b/src/coreclr/gc/gcinterface.ee.h @@ -448,4 +448,11 @@ class IGCToCLR { 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_ diff --git a/src/coreclr/gc/gcinterface.h b/src/coreclr/gc/gcinterface.h index 7e8c1b5665fac..cb706134679f0 100644 --- a/src/coreclr/gc/gcinterface.h +++ b/src/coreclr/gc/gcinterface.h @@ -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; diff --git a/src/coreclr/gc/gcload.cpp b/src/coreclr/gc/gcload.cpp index c33125ea86414..20c469f2baf84 100644 --- a/src/coreclr/gc/gcload.cpp +++ b/src/coreclr/gc/gcload.cpp @@ -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 diff --git a/src/coreclr/gc/sample/gcenv.ee.cpp b/src/coreclr/gc/sample/gcenv.ee.cpp index 84274f2341c3e..ac6d80bf03467 100644 --- a/src/coreclr/gc/sample/gcenv.ee.cpp +++ b/src/coreclr/gc/sample/gcenv.ee.cpp @@ -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) +{ +} diff --git a/src/coreclr/hosts/corerun/corerun.cpp b/src/coreclr/hosts/corerun/corerun.cpp index 60b5c5522d185..49f549de5eb66 100644 --- a/src/coreclr/hosts/corerun/corerun.cpp +++ b/src/coreclr/hosts/corerun/corerun.cpp @@ -205,6 +205,11 @@ class logger_t final 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); diff --git a/src/coreclr/hosts/inc/coreclrhost.h b/src/coreclr/hosts/inc/coreclrhost.h index 46a3119d628de..01eeac600a224 100644 --- a/src/coreclr/hosts/inc/coreclrhost.h +++ b/src/coreclr/hosts/inc/coreclrhost.h @@ -47,6 +47,24 @@ CORECLR_HOSTING_API(coreclr_initialize, void** hostHandle, 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. // diff --git a/src/coreclr/nativeaot/Runtime/gcrhenv.cpp b/src/coreclr/nativeaot/Runtime/gcrhenv.cpp index 56864387123e6..9454d54b8cffd 100644 --- a/src/coreclr/nativeaot/Runtime/gcrhenv.cpp +++ b/src/coreclr/nativeaot/Runtime/gcrhenv.cpp @@ -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); diff --git a/src/coreclr/vm/appdomain.cpp b/src/coreclr/vm/appdomain.cpp index 74c74041a49d6..1fc56ff4f39b9 100644 --- a/src/coreclr/vm/appdomain.cpp +++ b/src/coreclr/vm/appdomain.cpp @@ -1265,144 +1265,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 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 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 diff --git a/src/coreclr/vm/ceemain.cpp b/src/coreclr/vm/ceemain.cpp index 00411af2d843b..668f0183ba6c5 100644 --- a/src/coreclr/vm/ceemain.cpp +++ b/src/coreclr/vm/ceemain.cpp @@ -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); } diff --git a/src/coreclr/vm/codeman.cpp b/src/coreclr/vm/codeman.cpp index a81a2bd9f663f..1928561fbe985 100644 --- a/src/coreclr/vm/codeman.cpp +++ b/src/coreclr/vm/codeman.cpp @@ -1969,6 +1969,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.) // @@ -2021,7 +2025,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; } @@ -2048,10 +2052,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; @@ -2104,29 +2111,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); } } diff --git a/src/coreclr/vm/common.h b/src/coreclr/vm/common.h index 902f7cad6e385..7e5d8300f621c 100644 --- a/src/coreclr/vm/common.h +++ b/src/coreclr/vm/common.h @@ -390,6 +390,8 @@ extern DummyGlobalContract ___contract; #undef FPO_ON #endif +void LogErrorToHost(const char* format, ...); + #endif // !_common_h_ diff --git a/src/coreclr/vm/corhost.cpp b/src/coreclr/vm/corhost.cpp index 72fe94761c1db..37eb53728a2e9 100644 --- a/src/coreclr/vm/corhost.cpp +++ b/src/coreclr/vm/corhost.cpp @@ -36,6 +36,8 @@ #include "dwreport.h" #endif // !TARGET_UNIX +#include "nativelibrary.h" + #ifndef DACCESS_COMPILE extern void STDMETHODCALLTYPE EEShutDown(BOOL fIsDllUnloading); @@ -626,6 +628,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; diff --git a/src/coreclr/vm/gcenv.ee.cpp b/src/coreclr/vm/gcenv.ee.cpp index 677f67bef5229..1ecb498794c4f 100644 --- a/src/coreclr/vm/gcenv.ee.cpp +++ b/src/coreclr/vm/gcenv.ee.cpp @@ -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); +} diff --git a/src/coreclr/vm/gcenv.ee.h b/src/coreclr/vm/gcenv.ee.h index 6403f2637b817..c431cb8245a4c 100644 --- a/src/coreclr/vm/gcenv.ee.h +++ b/src/coreclr/vm/gcenv.ee.h @@ -87,6 +87,8 @@ class GCToEEInterface : public IGCToCLR { uint32_t GetCurrentProcessCpuCount(); void DiagAddNewRegion(int generation, BYTE * rangeStart, BYTE * rangeEnd, BYTE * rangeEndReserved); + + void LogErrorToHost(const char *message); }; } // namespace standalone diff --git a/src/mono/mono/mini/main-core.c b/src/mono/mono/mini/main-core.c index 66e85d713c947..d9e26269e68a9 100644 --- a/src/mono/mono/mini/main-core.c +++ b/src/mono/mono/mini/main-core.c @@ -20,10 +20,16 @@ #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 +} diff --git a/src/native/corehost/coreclr_resolver.h b/src/native/corehost/coreclr_resolver.h index dc7ad889ff848..81d6403c2879d 100644 --- a/src/native/corehost/coreclr_resolver.h +++ b/src/native/corehost/coreclr_resolver.h @@ -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; diff --git a/src/native/corehost/hostpolicy/coreclr.cpp b/src/native/corehost/hostpolicy/coreclr.cpp index 88f2933c1c7e6..2d5a353156840 100644 --- a/src/native/corehost/hostpolicy/coreclr.cpp +++ b/src/native/corehost/hostpolicy/coreclr.cpp @@ -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; diff --git a/src/native/corehost/hostpolicy/standalone/coreclr_resolver.cpp b/src/native/corehost/hostpolicy/standalone/coreclr_resolver.cpp index 8248118a4253f..b040c3e854627 100644 --- a/src/native/corehost/hostpolicy/standalone/coreclr_resolver.cpp +++ b/src/native/corehost/hostpolicy/standalone/coreclr_resolver.cpp @@ -19,10 +19,12 @@ bool coreclr_resolver_t::resolve_coreclr(const pal::string_t& libcoreclr_path, c } coreclr_resolver_contract.coreclr_initialize = reinterpret_cast(pal::get_symbol(coreclr_resolver_contract.coreclr, "coreclr_initialize")); + coreclr_resolver_contract.coreclr_set_error_writer = reinterpret_cast(pal::get_symbol(coreclr_resolver_contract.coreclr, "coreclr_set_error_writer")); coreclr_resolver_contract.coreclr_shutdown = reinterpret_cast(pal::get_symbol(coreclr_resolver_contract.coreclr, "coreclr_shutdown_2")); coreclr_resolver_contract.coreclr_execute_assembly = reinterpret_cast(pal::get_symbol(coreclr_resolver_contract.coreclr, "coreclr_execute_assembly")); coreclr_resolver_contract.coreclr_create_delegate = reinterpret_cast(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