Skip to content

Commit 634e68b

Browse files
Implement LoadLibraryErrorTracker
Finishes the LoadLibraryErrorTracker port from CoreCLR VM to C#. This allows us to report more details about the reason of `DllImport` resolution failures at runtime. The logic matches CoreCLR. There might be a slight behavior difference for empty string literal `DllImport`/`NativeLibrary.Load`. Not sure we care enough (I couldn't discern what the CoreCLR behavior is - I think we report an arbitrary old message from the last load attempt since we never even try to `dlopen` the empty string). Instead of: ``` System.DllNotFoundException: Unable to load native library 'bruh' or one of its dependencies. ``` We can now do: ``` System.DllNotFoundException: Unable to load DLL 'bruh' or one of its dependencies: The process cannot access the file because it is being used by another process. ```
1 parent 9e9a3e9 commit 634e68b

File tree

7 files changed

+86
-29
lines changed

7 files changed

+86
-29
lines changed

src/coreclr/nativeaot/System.Private.CoreLib/src/System/Runtime/InteropServices/NativeLibrary.NativeAot.cs

Lines changed: 66 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
// Licensed to the .NET Foundation under one or more agreements.
22
// The .NET Foundation licenses this file to you under the MIT license.
33

4-
#nullable enable
54
using System.Diagnostics;
65
using System.IO;
76
using System.Reflection;
87
using System.Text;
8+
99
using LibraryNameVariation = System.Runtime.Loader.LibraryNameVariation;
1010

1111
namespace System.Runtime.InteropServices
@@ -126,30 +126,19 @@ private static IntPtr LoadLibraryHelper(string libraryName, int flags, ref LoadL
126126
}
127127

128128
int lastError = Marshal.GetLastWin32Error();
129-
if (lastError != LoadLibErrorTracker.ERROR_INVALID_PARAMETER)
129+
if (lastError != Interop.Errors.ERROR_INVALID_PARAMETER)
130130
{
131131
errorTracker.TrackErrorCode(lastError);
132132
}
133133

134134
return ret;
135135
#else
136-
IntPtr ret = IntPtr.Zero;
137-
if (libraryName == null)
138-
{
139-
errorTracker.TrackErrorCode(LoadLibErrorTracker.ERROR_MOD_NOT_FOUND);
140-
}
141-
else if (libraryName == string.Empty)
136+
// TODO: FileDosToUnixPathA
137+
IntPtr ret = Interop.Sys.LoadLibrary(libraryName);
138+
if (ret == IntPtr.Zero)
142139
{
143-
errorTracker.TrackErrorCode(LoadLibErrorTracker.ERROR_INVALID_PARAMETER);
144-
}
145-
else
146-
{
147-
// TODO: FileDosToUnixPathA
148-
ret = Interop.Sys.LoadLibrary(libraryName);
149-
if (ret == IntPtr.Zero)
150-
{
151-
errorTracker.TrackErrorCode(LoadLibErrorTracker.ERROR_MOD_NOT_FOUND);
152-
}
140+
string? message = Marshal.PtrToStringAnsi(Interop.Sys.GetLoadLibraryError());
141+
errorTracker.TrackErrorMessage(message);
153142
}
154143

155144
return ret;
@@ -188,30 +177,80 @@ private static unsafe IntPtr GetSymbol(IntPtr handle, string symbolName, bool th
188177
return ret;
189178
}
190179

191-
// TODO: copy the nice error logic from CoreCLR's LoadLibErrorTracker
192-
// to get fine-grained error messages that take into account access denied, etc.
180+
// Preserving good error info from DllImport-driven LoadLibrary is tricky because we keep loading from different places
181+
// if earlier loads fail and those later loads obliterate error codes.
182+
//
183+
// This tracker object will keep track of the error code in accordance to priority:
184+
//
185+
// low-priority: unknown error code (should never happen)
186+
// medium-priority: dll not found
187+
// high-priority: dll found but error during loading
188+
//
189+
// We will overwrite the previous load's error code only if the new error code is higher priority.
193190
internal struct LoadLibErrorTracker
194191
{
195-
internal const int ERROR_INVALID_PARAMETER = 0x57;
196-
internal const int ERROR_MOD_NOT_FOUND = 126;
197-
internal const int ERROR_BAD_EXE_FORMAT = 193;
198-
192+
#if TARGET_WINDOWS
199193
private int _errorCode;
194+
private int _priority;
195+
196+
private const int PriorityNotFound = 10;
197+
private const int PriorityAccessDenied = 20;
198+
private const int PriorityCouldNotLoad = 99999;
200199

201200
public void Throw(string libraryName)
202201
{
203-
if (_errorCode == ERROR_BAD_EXE_FORMAT)
202+
if (_errorCode == Interop.Errors.ERROR_BAD_EXE_FORMAT)
204203
{
205204
throw new BadImageFormatException();
206205
}
207206

208-
throw new DllNotFoundException(SR.Format(SR.Arg_DllNotFoundExceptionParameterized, libraryName));
207+
string message = Interop.Kernel32.GetMessage(_errorCode);
208+
throw new DllNotFoundException(SR.Format(SR.DllNotFound_Windows, libraryName, message));
209209
}
210210

211211
public void TrackErrorCode(int errorCode)
212212
{
213-
_errorCode = errorCode;
213+
int priority = errorCode switch
214+
{
215+
Interop.Errors.ERROR_FILE_NOT_FOUND or
216+
Interop.Errors.ERROR_PATH_NOT_FOUND or
217+
Interop.Errors.ERROR_MOD_NOT_FOUND or
218+
Interop.Errors.ERROR_DLL_NOT_FOUND => PriorityNotFound,
219+
220+
// If we can't access a location, we can't know if the dll's there or if it's good.
221+
// Still, this is probably more unusual (and thus of more interest) than a dll-not-found
222+
// so give it an intermediate priority.
223+
Interop.Errors.ERROR_ACCESS_DENIED => PriorityAccessDenied,
224+
225+
// Assume all others are "dll found but couldn't load."
226+
_ => PriorityCouldNotLoad,
227+
};
228+
229+
if (priority > _priority)
230+
{
231+
_errorCode = errorCode;
232+
_priority = priority;
233+
}
234+
}
235+
#else
236+
// On Unix systems we don't have detailed programatic information on why load failed
237+
// so there's no priorities.
238+
private string? _errorMessage;
239+
240+
public void Throw(string libraryName)
241+
{
242+
#if TARGET_OSX
243+
throw new DllNotFoundException(SR.Format(SR.DllNotFound_Mac, libraryName, _errorMessage));
244+
#else
245+
throw new DllNotFoundException(SR.Format(SR.DllNotFound_Linux, libraryName, _errorMessage));
246+
#endif
214247
}
248+
249+
public void TrackErrorMessage(string? message)
250+
{
251+
_errorMessage = message;
252+
}
253+
#endif
215254
}
216255
}
217256
}

src/libraries/Common/src/Interop/Unix/System.Native/Interop.DynamicLoad.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,9 @@ internal unsafe partial class Sys
1111
[LibraryImport(Interop.Libraries.SystemNative, EntryPoint = "SystemNative_LoadLibrary", StringMarshalling = StringMarshalling.Utf8)]
1212
internal static partial IntPtr LoadLibrary(string filename);
1313

14+
[LibraryImport(Interop.Libraries.SystemNative, EntryPoint = "SystemNative_GetLoadLibraryError")]
15+
internal static partial IntPtr GetLoadLibraryError();
16+
1417
[LibraryImport(Interop.Libraries.SystemNative, EntryPoint = "SystemNative_GetProcAddress")]
1518
internal static partial IntPtr GetProcAddress(IntPtr handle, byte* symbol);
1619

src/libraries/Common/src/Interop/Windows/Interop.Errors.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ internal static partial class Errors
3434
internal const int ERROR_CALL_NOT_IMPLEMENTED = 0x78;
3535
internal const int ERROR_INSUFFICIENT_BUFFER = 0x7A;
3636
internal const int ERROR_INVALID_NAME = 0x7B;
37+
internal const int ERROR_MOD_NOT_FOUND = 0x7E;
3738
internal const int ERROR_NEGATIVE_SEEK = 0x83;
3839
internal const int ERROR_DIR_NOT_EMPTY = 0x91;
3940
internal const int ERROR_BAD_PATHNAME = 0xA1;

src/libraries/System.Private.CoreLib/src/Resources/Strings.resx

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3910,8 +3910,14 @@
39103910
<data name="Arg_EntryPointNotFoundExceptionParameterizedNoLibrary" xml:space="preserve">
39113911
<value>Unable to find an entry point named '{0}' in native library.</value>
39123912
</data>
3913-
<data name="Arg_DllNotFoundExceptionParameterized" xml:space="preserve">
3914-
<value>Unable to load native library '{0}' or one of its dependencies.</value>
3913+
<data name="DllNotFound_Windows" xml:space="preserve">
3914+
<value>Unable to load DLL '{0}' or one of its dependencies: {1}</value>
3915+
</data>
3916+
<data name="DllNotFound_Linux" xml:space="preserve">
3917+
<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>
3918+
</data>
3919+
<data name="DllNotFound_Mac" xml:space="preserve">
3920+
<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>
39153921
</data>
39163922
<data name="InvalidOperation_ComInteropRequireComWrapperInstance" xml:space="preserve">
39173923
<value>COM Interop requires ComWrapper instance registered for marshalling.</value>

src/native/libs/System.Native/entrypoints.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -234,6 +234,7 @@ static const Entry s_sysNative[] =
234234
DllImportEntry(SystemNative_LowLevelMonitor_TimedWait)
235235
DllImportEntry(SystemNative_LowLevelMonitor_Signal_Release)
236236
DllImportEntry(SystemNative_LoadLibrary)
237+
DllImportEntry(SystemNative_GetLoadLibraryError)
237238
DllImportEntry(SystemNative_GetProcAddress)
238239
DllImportEntry(SystemNative_FreeLibrary)
239240
DllImportEntry(SystemNative_GetDefaultSearchOrderPseudoHandle)

src/native/libs/System.Native/pal_dynamicload.c

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,11 @@ void* SystemNative_LoadLibrary(const char* filename)
3838
return dlopen(filename, RTLD_LAZY);
3939
}
4040

41+
void* SystemNative_GetLoadLibraryError(void)
42+
{
43+
return dlerror();
44+
}
45+
4146
void* SystemNative_GetProcAddress(void* handle, const char* symbol)
4247
{
4348
// We're not trying to disambiguate between "symbol was not found" and "symbol found, but

src/native/libs/System.Native/pal_dynamicload.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88

99
PALEXPORT void* SystemNative_LoadLibrary(const char* filename);
1010

11+
PALEXPORT void* SystemNative_GetLoadLibraryError(void);
12+
1113
PALEXPORT void* SystemNative_GetProcAddress(void* handle, const char* symbol);
1214

1315
PALEXPORT void SystemNative_FreeLibrary(void* handle);

0 commit comments

Comments
 (0)