-
Notifications
You must be signed in to change notification settings - Fork 5.3k
Implement LoadLibraryErrorTracker #69842
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 3 commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| 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]; | ||
MichalStrehovsky marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| 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 | ||
|---|---|---|---|---|
|
|
@@ -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> | ||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is the existing string from mscorrc: runtime/src/coreclr/dlls/mscorrc/mscorrc.rc Line 165 in abca8a1
|
||||
| </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> | ||||
|
|
||||
Uh oh!
There was an error while loading. Please reload this page.