Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
@@ -1,11 +1,11 @@
// 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 @@ -126,30 +126,19 @@ private static IntPtr LoadLibraryHelper(string libraryName, int flags, ref LoadL
}

int lastError = Marshal.GetLastWin32Error();
if (lastError != LoadLibErrorTracker.ERROR_INVALID_PARAMETER)
if (lastError != Interop.Errors.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)
// TODO: FileDosToUnixPathA
IntPtr ret = Interop.Sys.LoadLibrary(libraryName);
if (ret == IntPtr.Zero)
{
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);
}
string? message = Marshal.PtrToStringAnsi(Interop.Sys.GetLoadLibraryError());
errorTracker.TrackErrorMessage(message);
}

return ret;
Expand Down Expand Up @@ -188,30 +177,80 @@ private static unsafe IntPtr GetSymbol(IntPtr handle, string symbolName, bool th
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.
// 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
{
internal const int ERROR_INVALID_PARAMETER = 0x57;
internal const int ERROR_MOD_NOT_FOUND = 126;
internal const int ERROR_BAD_EXE_FORMAT = 193;

#if TARGET_WINDOWS
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 == ERROR_BAD_EXE_FORMAT)
if (_errorCode == Interop.Errors.ERROR_BAD_EXE_FORMAT)
{
throw new BadImageFormatException();
}

throw new DllNotFoundException(SR.Format(SR.Arg_DllNotFoundExceptionParameterized, libraryName));
string message = Interop.Kernel32.GetMessage(_errorCode);
throw new DllNotFoundException(SR.Format(SR.DllNotFound_Windows, libraryName, message));
}

public void TrackErrorCode(int errorCode)
{
_errorCode = 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;
}
}
#else
// On Unix systems we don't have detailed programatic information on why load failed
// so there's no priorities.
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;
}
#endif
}
}
}
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