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);