diff --git a/src/coreclr/nativeaot/System.Private.CoreLib/src/System.Private.CoreLib.csproj b/src/coreclr/nativeaot/System.Private.CoreLib/src/System.Private.CoreLib.csproj index 7fd43a5dedce8d..f4ee5e6a309331 100644 --- a/src/coreclr/nativeaot/System.Private.CoreLib/src/System.Private.CoreLib.csproj +++ b/src/coreclr/nativeaot/System.Private.CoreLib/src/System.Private.CoreLib.csproj @@ -258,6 +258,7 @@ + System\Runtime\InteropServices\Variant.cs @@ -317,6 +318,7 @@ + diff --git a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Runtime/InteropServices/NativeLibrary.NativeAot.Unix.cs b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Runtime/InteropServices/NativeLibrary.NativeAot.Unix.cs new file mode 100644 index 00000000000000..572a7f28294407 --- /dev/null +++ b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Runtime/InteropServices/NativeLibrary.NativeAot.Unix.cs @@ -0,0 +1,58 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Diagnostics; + +namespace System.Runtime.InteropServices +{ + public static partial class NativeLibrary + { + private const int LoadWithAlteredSearchPathFlag = 0; + + private static IntPtr LoadLibraryHelper(string libraryName, int flags, ref LoadLibErrorTracker errorTracker) + { + // do the Dos/Unix conversion + libraryName = libraryName.Replace('\\', '/'); + + IntPtr ret = Interop.Sys.LoadLibrary(libraryName); + if (ret == IntPtr.Zero) + { + string? message = Marshal.PtrToStringAnsi(Interop.Sys.GetLoadLibraryError()); + errorTracker.TrackErrorMessage(message); + } + + return ret; + } + + private static void FreeLib(IntPtr handle) + { + Debug.Assert(handle != IntPtr.Zero); + + Interop.Sys.FreeLibrary(handle); + } + + private static unsafe IntPtr GetSymbolOrNull(IntPtr handle, string symbolName) + { + return Interop.Sys.GetProcAddress(handle, symbolName); + } + + internal struct LoadLibErrorTracker + { + private string? _errorMessage; + + public void Throw(string libraryName) + { +#if TARGET_OSX + throw new DllNotFoundException(SR.Format(SR.DllNotFound_Mac, libraryName, _errorMessage)); +#else + throw new DllNotFoundException(SR.Format(SR.DllNotFound_Linux, libraryName, _errorMessage)); +#endif + } + + public void TrackErrorMessage(string? message) + { + _errorMessage = message; + } + } + } +} diff --git a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Runtime/InteropServices/NativeLibrary.NativeAot.Windows.cs b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Runtime/InteropServices/NativeLibrary.NativeAot.Windows.cs new file mode 100644 index 00000000000000..6f5587fa0c33d2 --- /dev/null +++ b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Runtime/InteropServices/NativeLibrary.NativeAot.Windows.cs @@ -0,0 +1,111 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Diagnostics; + +namespace System.Runtime.InteropServices +{ + public static partial class NativeLibrary + { + private const int LoadWithAlteredSearchPathFlag = 0x8; /* LOAD_WITH_ALTERED_SEARCH_PATH */ + + private static IntPtr LoadLibraryHelper(string libraryName, int flags, ref LoadLibErrorTracker errorTracker) + { + IntPtr hmod; + + if (((uint)flags & 0xFFFFFF00) != 0) + { + hmod = Interop.Kernel32.LoadLibraryEx(libraryName, IntPtr.Zero, (int)((uint)flags & 0xFFFFFF00)); + if (hmod != IntPtr.Zero) + { + return hmod; + } + + int lastError = Marshal.GetLastWin32Error(); + if (lastError != Interop.Errors.ERROR_INVALID_PARAMETER) + { + errorTracker.TrackErrorCode(lastError); + return hmod; + } + } + + hmod = Interop.Kernel32.LoadLibraryEx(libraryName, IntPtr.Zero, flags & 0xFF); + if (hmod == IntPtr.Zero) + { + errorTracker.TrackErrorCode(Marshal.GetLastWin32Error()); + } + + return hmod; + } + + private static void FreeLib(IntPtr handle) + { + Debug.Assert(handle != IntPtr.Zero); + + bool result = Interop.Kernel32.FreeLibrary(handle); + if (!result) + throw new InvalidOperationException(); + } + + private static unsafe IntPtr GetSymbolOrNull(IntPtr handle, string symbolName) + { + return Interop.Kernel32.GetProcAddress(handle, symbolName); + } + + // Preserving good error info from DllImport-driven LoadLibrary is tricky because we keep loading from different places + // if earlier loads fail and those later loads obliterate error codes. + // + // This tracker object will keep track of the error code in accordance to priority: + // + // low-priority: unknown error code (should never happen) + // medium-priority: dll not found + // high-priority: dll found but error during loading + // + // We will overwrite the previous load's error code only if the new error code is higher priority. + internal struct LoadLibErrorTracker + { + private int _errorCode; + private int _priority; + + private const int PriorityNotFound = 10; + private const int PriorityAccessDenied = 20; + private const int PriorityCouldNotLoad = 99999; + + public void Throw(string libraryName) + { + if (_errorCode == Interop.Errors.ERROR_BAD_EXE_FORMAT) + { + throw new BadImageFormatException(); + } + + string message = Interop.Kernel32.GetMessage(_errorCode); + throw new DllNotFoundException(SR.Format(SR.DllNotFound_Windows, libraryName, message)); + } + + public void TrackErrorCode(int errorCode) + { + int priority = errorCode switch + { + Interop.Errors.ERROR_FILE_NOT_FOUND or + Interop.Errors.ERROR_PATH_NOT_FOUND or + Interop.Errors.ERROR_MOD_NOT_FOUND or + Interop.Errors.ERROR_DLL_NOT_FOUND => PriorityNotFound, + + // If we can't access a location, we can't know if the dll's there or if it's good. + // Still, this is probably more unusual (and thus of more interest) than a dll-not-found + // so give it an intermediate priority. + Interop.Errors.ERROR_ACCESS_DENIED => PriorityAccessDenied, + + // Assume all others are "dll found but couldn't load." + _ => PriorityCouldNotLoad, + }; + + if (priority > _priority) + { + _errorCode = errorCode; + _priority = priority; + } + } + } + } +} diff --git a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Runtime/InteropServices/NativeLibrary.NativeAot.cs b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Runtime/InteropServices/NativeLibrary.NativeAot.cs index c8c1d47c00eeda..3708b256808e88 100644 --- a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Runtime/InteropServices/NativeLibrary.NativeAot.cs +++ b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Runtime/InteropServices/NativeLibrary.NativeAot.cs @@ -1,11 +1,9 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -#nullable enable -using System.Diagnostics; using System.IO; using System.Reflection; -using System.Text; + using LibraryNameVariation = System.Runtime.Loader.LibraryNameVariation; namespace System.Runtime.InteropServices @@ -60,7 +58,7 @@ internal static IntPtr LoadBySearch(Assembly callingAssembly, bool searchAssembl { IntPtr ret; - int loadWithAlteredPathFlags = 0; + int loadWithAlteredPathFlags = LoadWithAlteredSearchPathFlag; bool libNameIsRelativePath = !Path.IsPathFullyQualified(libraryName); // P/Invokes are often declared with variations on the actual library name. @@ -107,7 +105,7 @@ internal static IntPtr LoadBySearch(Assembly callingAssembly, bool searchAssembl private static IntPtr LoadFromPath(string libraryName, bool throwOnError) { LoadLibErrorTracker errorTracker = default; - IntPtr ret = LoadLibraryHelper(libraryName, 0, ref errorTracker); + IntPtr ret = LoadLibraryHelper(libraryName, LoadWithAlteredSearchPathFlag, ref errorTracker); if (throwOnError && ret == IntPtr.Zero) { errorTracker.Throw(libraryName); @@ -116,102 +114,13 @@ private static IntPtr LoadFromPath(string libraryName, bool throwOnError) return ret; } - private static IntPtr LoadLibraryHelper(string libraryName, int flags, ref LoadLibErrorTracker errorTracker) - { -#if TARGET_WINDOWS - IntPtr ret = Interop.Kernel32.LoadLibraryEx(libraryName, IntPtr.Zero, flags); - if (ret != IntPtr.Zero) - { - return ret; - } - - int lastError = Marshal.GetLastWin32Error(); - if (lastError != LoadLibErrorTracker.ERROR_INVALID_PARAMETER) - { - errorTracker.TrackErrorCode(lastError); - } - - return ret; -#else - IntPtr ret = IntPtr.Zero; - if (libraryName == null) - { - errorTracker.TrackErrorCode(LoadLibErrorTracker.ERROR_MOD_NOT_FOUND); - } - else if (libraryName == string.Empty) - { - errorTracker.TrackErrorCode(LoadLibErrorTracker.ERROR_INVALID_PARAMETER); - } - else - { - // TODO: FileDosToUnixPathA - ret = Interop.Sys.LoadLibrary(libraryName); - if (ret == IntPtr.Zero) - { - errorTracker.TrackErrorCode(LoadLibErrorTracker.ERROR_MOD_NOT_FOUND); - } - } - - return ret; -#endif - } - - private static void FreeLib(IntPtr handle) - { - Debug.Assert(handle != IntPtr.Zero); - -#if !TARGET_UNIX - bool result = Interop.Kernel32.FreeLibrary(handle); - if (!result) - throw new InvalidOperationException(); -#else - Interop.Sys.FreeLibrary(handle); -#endif - } - private static unsafe IntPtr GetSymbol(IntPtr handle, string symbolName, bool throwOnError) { - IntPtr ret; -#if !TARGET_UNIX - var symbolBytes = new byte[Encoding.UTF8.GetByteCount(symbolName) + 1]; - Encoding.UTF8.GetBytes(symbolName, symbolBytes); - fixed (byte* pSymbolBytes = symbolBytes) - { - ret = Interop.Kernel32.GetProcAddress(handle, pSymbolBytes); - } -#else - ret = Interop.Sys.GetProcAddress(handle, symbolName); -#endif + IntPtr ret = GetSymbolOrNull(handle, symbolName); if (throwOnError && ret == IntPtr.Zero) throw new EntryPointNotFoundException(SR.Format(SR.Arg_EntryPointNotFoundExceptionParameterizedNoLibrary, symbolName)); return ret; } - - // TODO: copy the nice error logic from CoreCLR's LoadLibErrorTracker - // to get fine-grained error messages that take into account access denied, etc. - internal struct LoadLibErrorTracker - { - internal const int ERROR_INVALID_PARAMETER = 0x57; - internal const int ERROR_MOD_NOT_FOUND = 126; - internal const int ERROR_BAD_EXE_FORMAT = 193; - - private int _errorCode; - - public void Throw(string libraryName) - { - if (_errorCode == ERROR_BAD_EXE_FORMAT) - { - throw new BadImageFormatException(); - } - - throw new DllNotFoundException(SR.Format(SR.Arg_DllNotFoundExceptionParameterized, libraryName)); - } - - public void TrackErrorCode(int errorCode) - { - _errorCode = errorCode; - } - } } } diff --git a/src/libraries/Common/src/Interop/Unix/System.Native/Interop.DynamicLoad.cs b/src/libraries/Common/src/Interop/Unix/System.Native/Interop.DynamicLoad.cs index f806b691ae3b95..1da7dd154190e4 100644 --- a/src/libraries/Common/src/Interop/Unix/System.Native/Interop.DynamicLoad.cs +++ b/src/libraries/Common/src/Interop/Unix/System.Native/Interop.DynamicLoad.cs @@ -11,6 +11,9 @@ internal unsafe partial class Sys [LibraryImport(Interop.Libraries.SystemNative, EntryPoint = "SystemNative_LoadLibrary", StringMarshalling = StringMarshalling.Utf8)] internal static partial IntPtr LoadLibrary(string filename); + [LibraryImport(Interop.Libraries.SystemNative, EntryPoint = "SystemNative_GetLoadLibraryError")] + internal static partial IntPtr GetLoadLibraryError(); + [LibraryImport(Interop.Libraries.SystemNative, EntryPoint = "SystemNative_GetProcAddress")] internal static partial IntPtr GetProcAddress(IntPtr handle, byte* symbol); diff --git a/src/libraries/Common/src/Interop/Windows/Interop.Errors.cs b/src/libraries/Common/src/Interop/Windows/Interop.Errors.cs index 5a38c911ebb2a7..6af8abca7164b0 100644 --- a/src/libraries/Common/src/Interop/Windows/Interop.Errors.cs +++ b/src/libraries/Common/src/Interop/Windows/Interop.Errors.cs @@ -34,6 +34,7 @@ internal static partial class Errors internal const int ERROR_CALL_NOT_IMPLEMENTED = 0x78; internal const int ERROR_INSUFFICIENT_BUFFER = 0x7A; internal const int ERROR_INVALID_NAME = 0x7B; + internal const int ERROR_MOD_NOT_FOUND = 0x7E; internal const int ERROR_NEGATIVE_SEEK = 0x83; internal const int ERROR_DIR_NOT_EMPTY = 0x91; internal const int ERROR_BAD_PATHNAME = 0xA1; diff --git a/src/libraries/Common/src/Interop/Windows/Kernel32/Interop.DynamicLoad.cs b/src/libraries/Common/src/Interop/Windows/Kernel32/Interop.DynamicLoad.cs index e6ce9d5730437f..e940c7769c3119 100644 --- a/src/libraries/Common/src/Interop/Windows/Kernel32/Interop.DynamicLoad.cs +++ b/src/libraries/Common/src/Interop/Windows/Kernel32/Interop.DynamicLoad.cs @@ -12,5 +12,8 @@ internal static unsafe partial class Kernel32 { [LibraryImport(Libraries.Kernel32)] internal static partial IntPtr GetProcAddress(IntPtr hModule, byte* lpProcName); + + [LibraryImport(Libraries.Kernel32, StringMarshalling = StringMarshalling.Utf8)] + internal static partial IntPtr GetProcAddress(IntPtr hModule, string lpProcName); } } diff --git a/src/libraries/System.Private.CoreLib/src/Resources/Strings.resx b/src/libraries/System.Private.CoreLib/src/Resources/Strings.resx index a65214fc04e2e6..94a5f50e88f672 100644 --- a/src/libraries/System.Private.CoreLib/src/Resources/Strings.resx +++ b/src/libraries/System.Private.CoreLib/src/Resources/Strings.resx @@ -3910,8 +3910,14 @@ Unable to find an entry point named '{0}' in native library. - - Unable to load native library '{0}' or one of its dependencies. + + Unable to load DLL '{0}' or one of its dependencies: {1} + + + Unable to load shared library '{0}' or one of its dependencies. In order to help diagnose loading problems, consider using a tool like strace. If you're using glibc, consider setting the LD_DEBUG environment variable: {1} + + + Unable to load shared library '{0}' or one of its dependencies. In order to help diagnose loading problems, consider setting the DYLD_PRINT_LIBRARIES environment variable: {1} COM Interop requires ComWrapper instance registered for marshalling. diff --git a/src/native/libs/System.Native/entrypoints.c b/src/native/libs/System.Native/entrypoints.c index 6a960a42ae14cb..fd7d2127a704c0 100644 --- a/src/native/libs/System.Native/entrypoints.c +++ b/src/native/libs/System.Native/entrypoints.c @@ -234,6 +234,7 @@ static const Entry s_sysNative[] = DllImportEntry(SystemNative_LowLevelMonitor_TimedWait) DllImportEntry(SystemNative_LowLevelMonitor_Signal_Release) DllImportEntry(SystemNative_LoadLibrary) + DllImportEntry(SystemNative_GetLoadLibraryError) DllImportEntry(SystemNative_GetProcAddress) DllImportEntry(SystemNative_FreeLibrary) DllImportEntry(SystemNative_GetDefaultSearchOrderPseudoHandle) diff --git a/src/native/libs/System.Native/pal_dynamicload.c b/src/native/libs/System.Native/pal_dynamicload.c index 66a49a68c48e33..6aaf70f50fcdd1 100644 --- a/src/native/libs/System.Native/pal_dynamicload.c +++ b/src/native/libs/System.Native/pal_dynamicload.c @@ -38,6 +38,11 @@ void* SystemNative_LoadLibrary(const char* filename) return dlopen(filename, RTLD_LAZY); } +void* SystemNative_GetLoadLibraryError(void) +{ + return dlerror(); +} + void* SystemNative_GetProcAddress(void* handle, const char* symbol) { // We're not trying to disambiguate between "symbol was not found" and "symbol found, but diff --git a/src/native/libs/System.Native/pal_dynamicload.h b/src/native/libs/System.Native/pal_dynamicload.h index 71ce8bf844be4d..21d570363120a9 100644 --- a/src/native/libs/System.Native/pal_dynamicload.h +++ b/src/native/libs/System.Native/pal_dynamicload.h @@ -8,6 +8,8 @@ PALEXPORT void* SystemNative_LoadLibrary(const char* filename); +PALEXPORT void* SystemNative_GetLoadLibraryError(void); + PALEXPORT void* SystemNative_GetProcAddress(void* handle, const char* symbol); PALEXPORT void SystemNative_FreeLibrary(void* handle);