Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -258,6 +258,7 @@
</ItemGroup>
<ItemGroup Condition="'$(TargetsWindows)'=='true'">
<Compile Include="Microsoft\Win32\SafeHandles\SafeThreadPoolIOHandle.cs" />
<Compile Include="System\Runtime\InteropServices\NativeLibrary.NativeAot.Windows.cs" />
<Compile Include="System\Runtime\InteropServices\PInvokeMarshal.Windows.cs" />
<Compile Include="$(CommonPath)\System\Runtime\InteropServices\Variant.cs">
<Link>System\Runtime\InteropServices\Variant.cs</Link>
Expand Down Expand Up @@ -317,6 +318,7 @@
</ItemGroup>
<ItemGroup Condition="'$(TargetsUnix)'=='true'">
<Compile Include="System\Environment.NativeAot.Unix.cs" />
<Compile Include="System\Runtime\InteropServices\NativeLibrary.NativeAot.Unix.cs" />
<Compile Include="System\Runtime\InteropServices\PInvokeMarshal.Unix.cs" />
<Compile Include="System\Threading\LowLevelLifoSemaphore.Unix.cs" />
<Compile Include="System\Threading\Thread.NativeAot.Unix.cs" />
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
// 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 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;
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Diagnostics;
using System.Text;

namespace System.Runtime.InteropServices
{
public static partial class NativeLibrary
{
private static IntPtr LoadLibraryHelper(string libraryName, int flags, ref LoadLibErrorTracker errorTracker)
{
IntPtr ret = Interop.Kernel32.LoadLibraryEx(libraryName, IntPtr.Zero, flags);
if (ret != IntPtr.Zero)
{
return ret;
}

int lastError = Marshal.GetLastWin32Error();
if (lastError != Interop.Errors.ERROR_INVALID_PARAMETER)
{
errorTracker.TrackErrorCode(lastError);
}

return ret;
}

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)
{
var symbolBytes = new byte[Encoding.UTF8.GetByteCount(symbolName) + 1];
Encoding.UTF8.GetBytes(symbolName, symbolBytes);
fixed (byte* pSymbolBytes = symbolBytes)
{
return Interop.Kernel32.GetProcAddress(handle, pSymbolBytes);
}
}

// 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;
}
}
}
}
}
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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;
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down
1 change: 1 addition & 0 deletions src/libraries/Common/src/Interop/Windows/Interop.Errors.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
10 changes: 8 additions & 2 deletions src/libraries/System.Private.CoreLib/src/Resources/Strings.resx
Original file line number Diff line number Diff line change
Expand Up @@ -3910,8 +3910,14 @@
<data name="Arg_EntryPointNotFoundExceptionParameterizedNoLibrary" xml:space="preserve">
<value>Unable to find an entry point named '{0}' in native library.</value>
</data>
<data name="Arg_DllNotFoundExceptionParameterized" xml:space="preserve">
<value>Unable to load native library '{0}' or one of its dependencies.</value>
<data name="DllNotFound_Windows" xml:space="preserve">
<value>Unable to load DLL '{0}' or one of its dependencies: {1}</value>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since {1} seems likely to be a sentence, I think it reads better (and perhaps it's more conventional) to separate with a period instead of a colon. For example we do this in the console output for FailFast. Etc

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the existing string from mscorrc:

IDS_EE_NDIRECT_LOADLIB_WIN "Unable to load DLL '%1' or one of its dependencies: %2"
that dates back to .NET Framework. I'll keep it for consistency.

</data>
<data name="DllNotFound_Linux" xml:space="preserve">
<value>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}</value>
</data>
<data name="DllNotFound_Mac" xml:space="preserve">
<value>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}</value>
</data>
<data name="InvalidOperation_ComInteropRequireComWrapperInstance" xml:space="preserve">
<value>COM Interop requires ComWrapper instance registered for marshalling.</value>
Expand Down
1 change: 1 addition & 0 deletions src/native/libs/System.Native/entrypoints.c
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
5 changes: 5 additions & 0 deletions src/native/libs/System.Native/pal_dynamicload.c
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 2 additions & 0 deletions src/native/libs/System.Native/pal_dynamicload.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down