From b4ba75bf386ec2f298aa0220852acac2e58f245f Mon Sep 17 00:00:00 2001 From: Elinor Fung Date: Fri, 10 Mar 2023 21:59:00 +0000 Subject: [PATCH] Merged PR 29288: [internal/release/6.0] Fix handling of load for msquic and mono .NET 6.0 version of .NET 7.0 change to: - Fix loading of msquic in System.Net.Quic.. - Fix handling of DllImportSearchPath values in DllImport and NativeLibrary. Differences: - No NativeAOT - Tests use explicit Main (before test consolidation) --- .../MsQuic/Internal/MsQuicApi.cs | 4 +- src/mono/mono/metadata/native-library.c | 25 ++++-- .../Common/CoreCLRTestLibrary/Utilities.cs | 3 + src/tests/Common/testenvironment.proj | 4 + .../DllImportSearchPathsTest.cs | 79 +++++++++++++++++++ .../DllImportSearchPathsTest.csproj | 25 ++++++ .../NativeLibrary/API/NativeLibraryTests.cs | 42 +++++++++- .../API/NativeLibraryTests.csproj | 13 +++ .../NativeLibraryToLoad.cs | 9 ++- 9 files changed, 191 insertions(+), 13 deletions(-) create mode 100644 src/tests/Interop/DllImportSearchPaths/DllImportSearchPathsTest.cs create mode 100644 src/tests/Interop/DllImportSearchPaths/DllImportSearchPathsTest.csproj diff --git a/src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/MsQuic/Internal/MsQuicApi.cs b/src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/MsQuic/Internal/MsQuicApi.cs index 7392b4de5daa6d..67f0e8205d0a33 100644 --- a/src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/MsQuic/Internal/MsQuicApi.cs +++ b/src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/MsQuic/Internal/MsQuicApi.cs @@ -141,7 +141,9 @@ static MsQuicApi() return; } - if (!NativeLibrary.TryLoad(Interop.Libraries.MsQuic, typeof(MsQuicApi).Assembly, DllImportSearchPath.AssemblyDirectory, out IntPtr msQuicHandle)) + // Windows ships msquic in the assembly directory. Non-Windows relies an the package being installed on the system. + DllImportSearchPath? searchPath = OperatingSystem.IsWindows() ? DllImportSearchPath.AssemblyDirectory : null; + if (!NativeLibrary.TryLoad(Interop.Libraries.MsQuic, typeof(MsQuicApi).Assembly, searchPath, out IntPtr msQuicHandle)) { // MsQuic library not loaded return; diff --git a/src/mono/mono/metadata/native-library.c b/src/mono/mono/metadata/native-library.c index f01b9efbae5ddb..083e7cad5e9d76 100644 --- a/src/mono/mono/metadata/native-library.c +++ b/src/mono/mono/metadata/native-library.c @@ -92,7 +92,7 @@ mono_dllmap_lookup_list (MonoDllMap *dll_map, const char *dll, const char* func, if (!dll_map) goto exit; - /* + /* * we use the first entry we find that matches, since entries from * the config file are prepended to the list and we document that the * later entries win. @@ -254,7 +254,7 @@ mono_global_dllmap_cleanup (void) * This function is used to programatically add \c DllImport remapping in either * a specific assembly, or as a global remapping. This is done by remapping * references in a \c DllImport attribute from the \p dll library name into the \p tdll - * name. If the \p dll name contains the prefix i:, the comparison of the + * name. If the \p dll name contains the prefix i:, the comparison of the * library name is done without case sensitivity. * * If you pass \p func, this is the name of the \c EntryPoint in a \c DllImport if specified @@ -518,8 +518,11 @@ netcore_probe_for_module (MonoImage *image, const char *file_name, int flags) // TODO: this algorithm doesn't quite match CoreCLR, so respecting DLLIMPORTSEARCHPATH_LEGACY_BEHAVIOR makes little sense // If the difference becomes a problem, overhaul this algorithm to match theirs exactly - // Try without any path additions +#if defined(HOST_ANDROID) + // On Android, try without any path additions first. It is sensitive to probing that will always miss + // and lookup for some libraries is required to use a relative path module = netcore_probe_for_module_variations (NULL, file_name, lflags); +#endif // Check the NATIVE_DLL_SEARCH_DIRECTORIES for (int i = 0; i < pinvoke_search_directories_count && module == NULL; ++i) @@ -533,6 +536,14 @@ netcore_probe_for_module (MonoImage *image, const char *file_name, int flags) g_free (mdirname); } +#if !defined(HOST_ANDROID) + // Try without any path additions + if (module == NULL) + { + module = netcore_probe_for_module_variations (NULL, file_name, lflags); + } +#endif + // TODO: Pass remaining flags on to LoadLibraryEx on Windows where appropriate, see https://docs.microsoft.com/en-us/dotnet/api/system.runtime.interopservices.dllimportsearchpath?view=netcore-3.1 return module; @@ -1048,7 +1059,7 @@ lookup_pinvoke_call_impl (MonoMethod *method, MonoLookupPInvokeStatus *status_ou mono_custom_attrs_free (cinfo); } if (flags < 0) - flags = 0; + flags = DLLIMPORTSEARCHPATH_ASSEMBLY_DIRECTORY; module = netcore_lookup_native_library (alc, image, new_scope, flags); if (!module) { @@ -1154,7 +1165,7 @@ pinvoke_probe_for_symbol (MonoDl *module, MonoMethodPInvoke *piinfo, const char #if HOST_WIN32 && HOST_X86 /* Try the stdcall mangled name */ - /* + /* * gcc under windows creates mangled names without the underscore, but MS.NET * doesn't support it, so we doesn't support it either. */ @@ -1394,7 +1405,7 @@ mono_loader_save_bundled_library (int fd, uint64_t offset, uint64_t size, const char *file, *buffer, *err, *internal_path; if (!bundle_save_library_initialized) bundle_save_library_initialize (); - + file = g_build_filename (bundled_dylibrary_directory, destfname, (const char*)NULL); buffer = g_str_from_file_region (fd, offset, size); g_file_set_contents (file, buffer, size, NULL); @@ -1409,7 +1420,7 @@ mono_loader_save_bundled_library (int fd, uint64_t offset, uint64_t size, const mono_loader_register_module (internal_path, lib); g_free (internal_path); bundle_library_paths = g_slist_append (bundle_library_paths, file); - + g_free (buffer); } diff --git a/src/tests/Common/CoreCLRTestLibrary/Utilities.cs b/src/tests/Common/CoreCLRTestLibrary/Utilities.cs index bd1eb1be37de73..4e22339154be8c 100644 --- a/src/tests/Common/CoreCLRTestLibrary/Utilities.cs +++ b/src/tests/Common/CoreCLRTestLibrary/Utilities.cs @@ -68,6 +68,9 @@ public static bool Verbose public static bool IsWindows7 => IsWindows && Environment.OSVersion.Version.Major == 6 && Environment.OSVersion.Version.Minor == 1; public static bool IsWindowsNanoServer => (!IsWindowsIoTCore && GetInstallationType().Equals("Nano Server", StringComparison.OrdinalIgnoreCase)); + private static string _variant = Environment.GetEnvironmentVariable("DOTNET_RUNTIME_VARIANT"); + public static bool IsMonoLLVMFULLAOT => _variant == "llvmfullaot"; + // Windows 10 October 2018 Update public static bool IsWindows10Version1809OrGreater => IsWindows && GetWindowsVersion() == 10 && GetWindowsMinorVersion() == 0 && GetWindowsBuildNumber() >= 17763; diff --git a/src/tests/Common/testenvironment.proj b/src/tests/Common/testenvironment.proj index c6d0a7c358d155..293feb331da49c 100644 --- a/src/tests/Common/testenvironment.proj +++ b/src/tests/Common/testenvironment.proj @@ -201,6 +201,8 @@ <_TestEnvFileLine Condition="'$(RuntimeVariant)' == 'monointerpreter'" Include="set MONO_ENV_OPTIONS=--interpreter" /> + <_TestEnvFileLine Condition="'$(RuntimeVariant)' != ''" Include="set DOTNET_RUNTIME_VARIANT=$(RuntimeVariant)" /> + <_TestEnvFileLine Condition="'$(Scenario)' == 'clrinterpreter'" Include="set COMPlus_Interpret=%2A" /> <_TestEnvFileLine Condition="'$(Scenario)' == 'clrinterpreter'" Include="set COMPlus_InterpreterHWIntrinsicsIsSupportedFalse=1" /> @@ -216,6 +218,8 @@ <_TestEnvFileLine Condition="'$(RuntimeVariant)' == 'monointerpreter'" Include="export MONO_ENV_OPTIONS=--interpreter" /> + <_TestEnvFileLine Condition="'$(RuntimeVariant)' != ''" Include="export DOTNET_RUNTIME_VARIANT=$(RuntimeVariant)" /> + <_TestEnvFileLine Condition="'$(RuntimeVariant)' == 'llvmaot'" Include="export MONO_ENV_OPTIONS=--llvm" /> diff --git a/src/tests/Interop/DllImportSearchPaths/DllImportSearchPathsTest.cs b/src/tests/Interop/DllImportSearchPaths/DllImportSearchPathsTest.cs new file mode 100644 index 00000000000000..dcc6514ece1f15 --- /dev/null +++ b/src/tests/Interop/DllImportSearchPaths/DllImportSearchPathsTest.cs @@ -0,0 +1,79 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.IO; +using System.Reflection; +using System.Runtime.InteropServices; +using Xunit; + +public class DllImportSearchPathsTest +{ + private static string Subdirectory => Path.Combine(NativeLibraryToLoad.GetDirectory(), "subdirectory"); + + static int Main(string[] args) + { + try + { + AssemblyDirectory_NotFound(); + if (!TestLibrary.Utilities.IsMonoLLVMFULLAOT) + AssemblyDirectory_Found(); + + if (OperatingSystem.IsWindows()) + AssemblyDirectory_Fallback_Found(); + } + catch (Exception e) + { + Console.WriteLine($"Test Failure: {e}"); + return 101; + } + + return 100; + } + + public static void AssemblyDirectory_NotFound() + { + // Library should not be found in the assembly directory + Assert.Throws(() => NativeLibraryPInvoke.Sum(1, 2)); + } + + public static void AssemblyDirectory_Found() + { + // Library should be found in the assembly directory + var assembly = Assembly.LoadFile(Path.Combine(Subdirectory, $"{nameof(DllImportSearchPathsTest)}.dll")); + var type = assembly.GetType(nameof(NativeLibraryPInvoke)); + var method = type.GetMethod(nameof(NativeLibraryPInvoke.Sum)); + + int sum = (int)method.Invoke(null, new object[] { 1, 2 }); + Assert.Equal(3, sum); + } + + public static void AssemblyDirectory_Fallback_Found() + { + string currentDirectory = Environment.CurrentDirectory; + try + { + Environment.CurrentDirectory = Subdirectory; + + // Library should not be found in the assembly directory, but should fall back to the default OS search which includes CWD on Windows + int sum = NativeLibraryPInvoke.Sum(1, 2); + Assert.Equal(3, sum); + } + finally + { + Environment.CurrentDirectory = currentDirectory; + } + } +} + +public class NativeLibraryPInvoke +{ + public static int Sum(int a, int b) + { + return NativeSum(a, b); + } + + [DllImport(NativeLibraryToLoad.Name)] + [DefaultDllImportSearchPaths(DllImportSearchPath.AssemblyDirectory)] + static extern int NativeSum(int arg1, int arg2); +} diff --git a/src/tests/Interop/DllImportSearchPaths/DllImportSearchPathsTest.csproj b/src/tests/Interop/DllImportSearchPaths/DllImportSearchPathsTest.csproj new file mode 100644 index 00000000000000..70277c524034f7 --- /dev/null +++ b/src/tests/Interop/DllImportSearchPaths/DllImportSearchPathsTest.csproj @@ -0,0 +1,25 @@ + + + Exe + true + + + + + + + + + + $(OutDir)/subdirectory + -in-subdirectory + + + <_FilesToCopy Include="$(OutDir)/$(TargetName).dll" /> + <_FilesToMove Include="$(OutDir)/libNativeLibrary.*" /> + <_FilesToMove Include="$(OutDir)/NativeLibrary.*" /> + + + + + diff --git a/src/tests/Interop/NativeLibrary/API/NativeLibraryTests.cs b/src/tests/Interop/NativeLibrary/API/NativeLibraryTests.cs index 4742b2e0b5bdbb..699a125dfb2c42 100644 --- a/src/tests/Interop/NativeLibrary/API/NativeLibraryTests.cs +++ b/src/tests/Interop/NativeLibrary/API/NativeLibraryTests.cs @@ -110,13 +110,22 @@ public static int Main() if (TestLibrary.Utilities.IsWindows && File.Exists(Path.Combine(Environment.SystemDirectory, libName))) { - // Calls on a valid library from System32 directory + // Library should be found in the system directory success &= EXPECT(LoadLibraryAdvanced(libName, assembly, DllImportSearchPath.System32)); success &= EXPECT(TryLoadLibraryAdvanced(libName, assembly, DllImportSearchPath.System32)); - // Calls on a valid library from application directory + // Library should not be found in the assembly directory and should be found in the system directory + success &= EXPECT(LoadLibraryAdvanced(libName, assembly, DllImportSearchPath.AssemblyDirectory | DllImportSearchPath.System32)); + success &= EXPECT(TryLoadLibraryAdvanced(libName, assembly, DllImportSearchPath.AssemblyDirectory | DllImportSearchPath.System32)); + + // Library should not be found in the assembly directory, but should fall back to the default OS search which includes CWD on Windows + success &= EXPECT(LoadLibraryAdvanced(libName, assembly, DllImportSearchPath.AssemblyDirectory)); + success &= EXPECT(TryLoadLibraryAdvanced(libName, assembly, DllImportSearchPath.AssemblyDirectory)); + + // Library should not be found in application directory success &= EXPECT(LoadLibraryAdvanced(libName, assembly, DllImportSearchPath.ApplicationDirectory), TestResult.DllNotFound); success &= EXPECT(TryLoadLibraryAdvanced(libName, assembly, DllImportSearchPath.ApplicationDirectory), TestResult.ReturnFailure); + } // Calls with null libName input @@ -134,6 +143,35 @@ public static int Main() success &= EXPECT(LoadLibraryAdvanced(libName, assembly, DllImportSearchPath.AssemblyDirectory), TestResult.DllNotFound); success &= EXPECT(TryLoadLibraryAdvanced(libName, assembly, DllImportSearchPath.AssemblyDirectory), TestResult.ReturnFailure); + string suffix = "-in-subdirectory"; + libName = $"{NativeLibraryToLoad.Name}{suffix}"; + string subdirectory = Path.Combine(testBinDir, "subdirectory"); + + if (!TestLibrary.Utilities.IsMonoLLVMFULLAOT) + { + // Library should be found in the assembly directory + Assembly assemblyInSubdirectory = Assembly.LoadFile(Path.Combine(subdirectory, $"{Path.GetFileNameWithoutExtension(assembly.Location)}{suffix}.dll")); + EXPECT(LoadLibraryAdvanced(libName, assemblyInSubdirectory, DllImportSearchPath.AssemblyDirectory)); + EXPECT(TryLoadLibraryAdvanced(libName, assemblyInSubdirectory, DllImportSearchPath.AssemblyDirectory)); + } + + if (OperatingSystem.IsWindows()) + { + string currentDirectory = Environment.CurrentDirectory; + try + { + Environment.CurrentDirectory = subdirectory; + + // Library should not be found in the assembly directory, but should fall back to the default OS search which includes CWD on Windows + EXPECT(LoadLibraryAdvanced(libName, assembly, DllImportSearchPath.AssemblyDirectory)); + EXPECT(TryLoadLibraryAdvanced(libName, assembly, DllImportSearchPath.AssemblyDirectory)); + } + finally + { + Environment.CurrentDirectory = currentDirectory; + } + } + // ----------------------------------------------- // FreeLibrary Tests // ----------------------------------------------- diff --git a/src/tests/Interop/NativeLibrary/API/NativeLibraryTests.csproj b/src/tests/Interop/NativeLibrary/API/NativeLibraryTests.csproj index 51c156e0d9b9e6..47cfe35cd59ecb 100644 --- a/src/tests/Interop/NativeLibrary/API/NativeLibraryTests.csproj +++ b/src/tests/Interop/NativeLibrary/API/NativeLibraryTests.csproj @@ -16,4 +16,17 @@ + + + + $(OutDir)/subdirectory + -in-subdirectory + + + + + + + + diff --git a/src/tests/Interop/NativeLibrary/NativeLibraryToLoad/NativeLibraryToLoad.cs b/src/tests/Interop/NativeLibrary/NativeLibraryToLoad/NativeLibraryToLoad.cs index 4a61c599961f5a..efc878fd9fc520 100644 --- a/src/tests/Interop/NativeLibrary/NativeLibraryToLoad/NativeLibraryToLoad.cs +++ b/src/tests/Interop/NativeLibrary/NativeLibraryToLoad/NativeLibraryToLoad.cs @@ -27,8 +27,11 @@ public static string GetFileName() public static string GetFullPath() { - Assembly assembly = Assembly.GetExecutingAssembly(); - string directory = Path.GetDirectoryName(assembly.Location); - return Path.Combine(directory, GetFileName()); + return Path.Combine(GetDirectory(), GetFileName()); + } + + public static string GetDirectory() + { + return Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); } }