Skip to content

Commit

Permalink
Merged PR 29231: [internal/release/7.0] Fix handling of load for msqu…
Browse files Browse the repository at this point in the history
…ic and search paths for nativeaot and mono

Fix loading of msquic in System.Net.Quic..
Fix handling of `DllImportSearchPath` values in `DllImport` and `NativeLibrary`. This affects mono and nativeaot.
  • Loading branch information
elinor-fung committed Mar 10, 2023
1 parent eaaed2a commit 5a1baeb
Show file tree
Hide file tree
Showing 16 changed files with 237 additions and 29 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -309,8 +309,8 @@ internal static unsafe void FixupModuleCell(ModuleFixupCell* pCell)

hModule = NativeLibrary.LoadBySearch(
callingAssembly,
searchAssemblyDirectory: false,
dllImportSearchPathFlags: 0,
searchAssemblyDirectory: (dllImportSearchPath & (uint)DllImportSearchPath.AssemblyDirectory) != 0,
dllImportSearchPathFlags: (int)(dllImportSearchPath & ~(uint)DllImportSearchPath.AssemblyDirectory),
ref loadLibErrorTracker,
moduleName);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ internal static IntPtr LoadLibraryByName(string libraryName, Assembly assembly,
bool searchAssemblyDirectory;
if (searchPath.HasValue)
{
searchPathFlags = (int)(searchPath.Value & ~DllImportSearchPath.AssemblyDirectory);
searchPathFlags = (int)(searchPath!.Value & ~DllImportSearchPath.AssemblyDirectory);
searchAssemblyDirectory = (searchPath.Value & DllImportSearchPath.AssemblyDirectory) != 0;
}
else
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// The .NET Foundation licenses this file to you under the MIT license.

using System;
using System.Reflection.Metadata;
using System.Runtime.InteropServices;

using Internal.IL.Stubs;
Expand Down Expand Up @@ -97,23 +98,35 @@ public PInvokeMethodData(PInvokeLazyFixupField pInvokeLazyFixupField)
PInvokeMetadata metadata = pInvokeLazyFixupField.PInvokeMetadata;
ModuleDesc declaringModule = ((MetadataType)pInvokeLazyFixupField.TargetMethod.OwningType).Module;

DllImportSearchPath? dllImportSearchPath = default;
if (declaringModule.Assembly is EcmaAssembly asm)
CustomAttributeValue<TypeDesc>? decodedAttr = null;

// Look for DefaultDllImportSearchPath on the method
if (pInvokeLazyFixupField.TargetMethod is EcmaMethod method)
{
decodedAttr = method.GetDecodedCustomAttribute("System.Runtime.InteropServices", "DefaultDllImportSearchPathsAttribute");
}

// If the attribute it wasn't found on the method, look for it on the assembly
if (!decodedAttr.HasValue && declaringModule.Assembly is EcmaAssembly asm)
{
// We look for [assembly:DefaultDllImportSearchPaths(...)]
var attrHandle = asm.MetadataReader.GetCustomAttributeHandle(asm.AssemblyDefinition.GetCustomAttributes(),
"System.Runtime.InteropServices", "DefaultDllImportSearchPathsAttribute");
if (!attrHandle.IsNil)
{
var attr = asm.MetadataReader.GetCustomAttribute(attrHandle);
var decoded = attr.DecodeValue(new CustomAttributeTypeProvider(asm));
if (decoded.FixedArguments.Length == 1 &&
decoded.FixedArguments[0].Value is int searchPath)
{
dllImportSearchPath = (DllImportSearchPath)searchPath;
}
decodedAttr = attr.DecodeValue(new CustomAttributeTypeProvider(asm));
}
}

DllImportSearchPath? dllImportSearchPath = default;
if (decodedAttr.HasValue
&& decodedAttr.Value.FixedArguments.Length == 1
&& decodedAttr.Value.FixedArguments[0].Value is int searchPath)
{
dllImportSearchPath = (DllImportSearchPath)searchPath;
}

ModuleData = new PInvokeModuleData(metadata.Module, dllImportSearchPath, declaringModule);

EntryPointName = metadata.Name;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,8 +64,21 @@ private MsQuicApi(QUIC_API_TABLE* apiTable)
#pragma warning disable CA1810 // Initialize all static fields in 'MsQuicApi' when those fields are declared and remove the explicit static constructor
static MsQuicApi()
{
if (!NativeLibrary.TryLoad($"{Interop.Libraries.MsQuic}.{MinMsQuicVersion.Major}", typeof(MsQuicApi).Assembly, DllImportSearchPath.AssemblyDirectory, out IntPtr msQuicHandle) &&
!NativeLibrary.TryLoad(Interop.Libraries.MsQuic, typeof(MsQuicApi).Assembly, DllImportSearchPath.AssemblyDirectory, out msQuicHandle))
bool loaded = false;
IntPtr msQuicHandle;
if (OperatingSystem.IsWindows())
{
// Windows ships msquic in the assembly directory.
loaded = NativeLibrary.TryLoad(Interop.Libraries.MsQuic, typeof(MsQuicApi).Assembly, DllImportSearchPath.AssemblyDirectory, out msQuicHandle);
}
else
{
// Non-Windows relies on the package being installed on the system and may include the version in its name
loaded = NativeLibrary.TryLoad($"{Interop.Libraries.MsQuic}.{MinMsQuicVersion.Major}", typeof(MsQuicApi).Assembly, null, out msQuicHandle) ||
NativeLibrary.TryLoad(Interop.Libraries.MsQuic, typeof(MsQuicApi).Assembly, null, out msQuicHandle);
}

if (!loaded)
{
// MsQuic library not loaded
return;
Expand Down
15 changes: 14 additions & 1 deletion src/mono/mono/metadata/native-library.c
Original file line number Diff line number Diff line change
Expand Up @@ -533,10 +533,13 @@ netcore_probe_for_module (MonoImage *image, const char *file_name, int flags, Mo

ERROR_DECL (bad_image_error);

// 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, error);
if (!module && !is_ok (error) && mono_error_get_error_code (error) == MONO_ERROR_BAD_IMAGE)
mono_error_move (bad_image_error, error);
#endif

// Check the NATIVE_DLL_SEARCH_DIRECTORIES
for (int i = 0; i < pinvoke_search_directories_count && module == NULL; ++i) {
Expand All @@ -560,6 +563,16 @@ netcore_probe_for_module (MonoImage *image, const char *file_name, int flags, Mo
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, error);
if (!module && !is_ok (error) && mono_error_get_error_code (error) == MONO_ERROR_BAD_IMAGE)
mono_error_move (bad_image_error, error);
}
#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

if (!module && !is_ok (bad_image_error)) {
Expand Down
6 changes: 5 additions & 1 deletion src/tests/Common/CoreCLRTestLibrary/PlatformDetection.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,12 @@ public static class PlatformDetection
{
public static bool Is32BitProcess => IntPtr.Size == 4;
public static bool Is64BitProcess => IntPtr.Size == 8;

public static bool IsX86Process => RuntimeInformation.ProcessArchitecture == Architecture.X86;
public static bool IsNotX86Process => !IsX86Process;

private static string _variant = Environment.GetEnvironmentVariable("DOTNET_RUNTIME_VARIANT");

public static bool IsMonoLLVMFULLAOT => _variant == "llvmfullaot";
}
}
4 changes: 4 additions & 0 deletions src/tests/Common/testenvironment.proj
Original file line number Diff line number Diff line change
Expand Up @@ -258,6 +258,8 @@
<!-- Mono interpreter -->
<_TestEnvFileLine Condition="'$(RuntimeVariant)' == 'monointerpreter'" Include="set MONO_ENV_OPTIONS=--interpreter" />

<_TestEnvFileLine Condition="'$(RuntimeVariant)' != ''" Include="set DOTNET_RUNTIME_VARIANT=$(RuntimeVariant)" />

<!-- CLR interpreter -->
<_TestEnvFileLine Condition="'$(Scenario)' == 'clrinterpreter'" Include="set COMPlus_Interpret=%2A" /> <!-- %2A is asterisk / wildcard -->
<_TestEnvFileLine Condition="'$(Scenario)' == 'clrinterpreter'" Include="set COMPlus_InterpreterHWIntrinsicsIsSupportedFalse=1" />
Expand All @@ -273,6 +275,8 @@
<!-- Mono interpreter -->
<_TestEnvFileLine Condition="'$(RuntimeVariant)' == 'monointerpreter'" Include="export MONO_ENV_OPTIONS=--interpreter" />

<_TestEnvFileLine Condition="'$(RuntimeVariant)' != ''" Include="export DOTNET_RUNTIME_VARIANT=$(RuntimeVariant)" />

<!-- Use Mono LLVM JIT when JIT-compiling the non-AOT-compiled parts of the runtime tests -->
<_TestEnvFileLine Condition="'$(RuntimeVariant)' == 'llvmaot'" Include="export MONO_ENV_OPTIONS=--llvm" />

Expand Down
6 changes: 5 additions & 1 deletion src/tests/Directory.Build.targets
Original file line number Diff line number Diff line change
Expand Up @@ -537,13 +537,17 @@
<_UsingDefaultForHasRuntimeOutput>false</_UsingDefaultForHasRuntimeOutput>
</PropertyGroup>

<ItemGroup Condition="'$(TestBuildMode)' == 'nativeaot'">
<IlcReference Include="$(TargetingPackPath)/*.dll" />
</ItemGroup>

<Import Project="$(CoreCLRBuildIntegrationDir)Microsoft.NETCore.Native.targets" Condition="'$(TestBuildMode)' == 'nativeaot'" />

<Target Name="BuildNativeAot"
DependsOnTargets="Build;LinkNativeIfBuildAndRun" />

<Target Name="LinkNativeIfBuildAndRun"
Condition="'$(CLRTestTargetUnsupported)' != 'true' and '$(CLRTestKind)' == 'BuildAndRun'"
Condition="'$(_WillCLRTestProjectBuild)' == 'true' and '$(CLRTestKind)' == 'BuildAndRun'"
DependsOnTargets="ComputeResolvedFilesToPublishList;LinkNative" />

</Project>
66 changes: 66 additions & 0 deletions src/tests/Interop/DllImportSearchPaths/DllImportSearchPathsTest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
// 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");

[Fact]
public static void AssemblyDirectory_NotFound()
{
// Library should not be found in the assembly directory
Assert.Throws<DllNotFoundException>(() => NativeLibraryPInvoke.Sum(1, 2));
}

public static bool CanLoadAssemblyInSubdirectory =>
!TestLibrary.Utilities.IsNativeAot && !TestLibrary.PlatformDetection.IsMonoLLVMFULLAOT;

[ConditionalFact(nameof(CanLoadAssemblyInSubdirectory))]
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);
}

[Fact]
[PlatformSpecific(TestPlatforms.Windows)]
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);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
<ItemGroup>
<Compile Include="*.cs" />
<Compile Include="../NativeLibrary/NativeLibraryToLoad/NativeLibraryToLoad.cs" />
<CMakeProjectReference Include="../NativeLibrary/NativeLibraryToLoad/CMakeLists.txt" />
</ItemGroup>

<Target Name="SetUpSubdirectory" AfterTargets="CopyNativeProjectBinaries">
<PropertyGroup>
<NativeLibrarySubdirectory>$(OutDir)/subdirectory</NativeLibrarySubdirectory>
<FileNameSuffix>-in-subdirectory</FileNameSuffix>
</PropertyGroup>
<ItemGroup>
<_FilesToCopy Include="$(OutDir)/$(TargetName).dll" />
<_FilesToMove Include="$(OutDir)/libNativeLibrary.*" />
<_FilesToMove Include="$(OutDir)/NativeLibrary.*" />
</ItemGroup>
<Copy SourceFiles="@(_FilesToCopy)" DestinationFiles="@(_FilesToCopy -> '$(NativeLibrarySubdirectory)/%(Filename)%(Extension)')" />
<Move SourceFiles="@(_FilesToMove)" DestinationFiles="@(_FilesToMove -> '$(NativeLibrarySubdirectory)/%(Filename)%(Extension)')" />
</Target>
</Project>
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ public static void GloballyLoadedLibrarySymbolsVisibleFromMainProgramHandle()
// On non-Windows platforms, symbols from globally loaded shared libraries will also be discoverable.
// Globally loading symbols is not the .NET default, so we use a call to dlopen in native code
// with the right flags to test the scenario.
IntPtr handle = LoadLibraryGlobally(Path.Combine(Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location), NativeLibraryToLoad.GetLibraryFileName("GloballyLoadedNativeLibrary")));
IntPtr handle = LoadLibraryGlobally(Path.Combine(NativeLibraryToLoad.GetDirectory(), NativeLibraryToLoad.GetLibraryFileName("GloballyLoadedNativeLibrary")));

try
{
Expand All @@ -64,7 +64,7 @@ public static void InvalidSymbolName_Fails()
// On non-Windows platforms, symbols from globally loaded shared libraries will also be discoverable.
// Globally loading symbols is not the .NET default, so we use a call to dlopen in native code
// with the right flags to test the scenario.
IntPtr handle = LoadLibraryGlobally(Path.Combine(Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location), NativeLibraryToLoad.GetLibraryFileName("GloballyLoadedNativeLibrary")));
IntPtr handle = LoadLibraryGlobally(Path.Combine(NativeLibraryToLoad.GetDirectory(), NativeLibraryToLoad.GetLibraryFileName("GloballyLoadedNativeLibrary")));

try
{
Expand All @@ -83,7 +83,7 @@ public static void GloballyLoadedLibrarySymbolsVisibleFromMainProgramHandle_Mang
// On non-Windows platforms, symbols from globally loaded shared libraries will also be discoverable.
// Globally loading symbols is not the .NET default, so we use a call to dlopen in native code
// with the right flags to test the scenario.
IntPtr handle = LoadLibraryGlobally(Path.Combine(Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location), NativeLibraryToLoad.GetLibraryFileName("GloballyLoadedNativeLibrary")));
IntPtr handle = LoadLibraryGlobally(Path.Combine(NativeLibraryToLoad.GetDirectory(), NativeLibraryToLoad.GetLibraryFileName("GloballyLoadedNativeLibrary")));

try
{
Expand Down
48 changes: 45 additions & 3 deletions src/tests/Interop/NativeLibrary/API/NativeLibraryTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ public class NativeLibraryTests : IDisposable
public NativeLibraryTests()
{
assembly = System.Reflection.Assembly.GetExecutingAssembly();
testBinDir = Path.GetDirectoryName(assembly.Location);
testBinDir = NativeLibraryToLoad.GetDirectory();
libFullPath = NativeLibraryToLoad.GetFullPath();
}

Expand Down Expand Up @@ -133,11 +133,19 @@ public void LoadLibraryFullPathWithoutNativePrefixOrSuffix_WithAssembly_Failure(
public void LoadSystemLibrary_WithSearchPath()
{
string libName = "url.dll";
// Calls on a valid library from System32 directory
// Library should be found in the system directory
EXPECT(LoadLibrary_WithAssembly(libName, assembly, DllImportSearchPath.System32));
EXPECT(TryLoadLibrary_WithAssembly(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
EXPECT(LoadLibrary_WithAssembly(libName, assembly, DllImportSearchPath.AssemblyDirectory | DllImportSearchPath.System32));
EXPECT(TryLoadLibrary_WithAssembly(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
EXPECT(LoadLibrary_WithAssembly(libName, assembly, DllImportSearchPath.AssemblyDirectory));
EXPECT(TryLoadLibrary_WithAssembly(libName, assembly, DllImportSearchPath.AssemblyDirectory));

// Library should not be found in application directory
EXPECT(LoadLibrary_WithAssembly(libName, assembly, DllImportSearchPath.ApplicationDirectory), TestResult.DllNotFound);
EXPECT(TryLoadLibrary_WithAssembly(libName, assembly, DllImportSearchPath.ApplicationDirectory), TestResult.ReturnFailure);
}
Expand Down Expand Up @@ -165,6 +173,40 @@ public void LoadLibrary_UsesFullPath_EvenWhen_AssemblyDirectory_Specified()
EXPECT(TryLoadLibrary_WithAssembly(libName, assembly, DllImportSearchPath.AssemblyDirectory), TestResult.ReturnFailure);
}

[Fact]
public void LoadLibrary_AssemblyDirectory()
{
string suffix = "-in-subdirectory";
string libName = $"{NativeLibraryToLoad.Name}{suffix}";

string subdirectory = Path.Combine(testBinDir, "subdirectory");

if (!TestLibrary.Utilities.IsNativeAot && !TestLibrary.PlatformDetection.IsMonoLLVMFULLAOT)
{
// Library should be found in the assembly directory
Assembly assemblyInSubdirectory = Assembly.LoadFile(Path.Combine(subdirectory, $"{Path.GetFileNameWithoutExtension(assembly.Location)}{suffix}.dll"));
EXPECT(LoadLibrary_WithAssembly(libName, assemblyInSubdirectory, DllImportSearchPath.AssemblyDirectory));
EXPECT(TryLoadLibrary_WithAssembly(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(LoadLibrary_WithAssembly(libName, assembly, DllImportSearchPath.AssemblyDirectory));
EXPECT(TryLoadLibrary_WithAssembly(libName, assembly, DllImportSearchPath.AssemblyDirectory));
}
finally
{
Environment.CurrentDirectory = currentDirectory;
}
}
}

[Fact]
public void Free()
{
Expand Down
13 changes: 13 additions & 0 deletions src/tests/Interop/NativeLibrary/API/NativeLibraryTests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,17 @@
<ProjectReference Include="$(TestSourceDir)Common/CoreCLRTestLibrary/CoreCLRTestLibrary.csproj" />
<CMakeProjectReference Include="../NativeLibraryToLoad/CMakeLists.txt" />
</ItemGroup>

<Target Name="SetUpSubdirectory" AfterTargets="CopyNativeProjectBinaries">
<PropertyGroup>
<NativeLibrarySubdirectory>$(OutDir)/subdirectory</NativeLibrarySubdirectory>
<FileNameSuffix>-in-subdirectory</FileNameSuffix>
</PropertyGroup>
<ItemGroup>
<AssembliesToCopy Include="$(OutDir)/libNativeLibrary.*" />
<AssembliesToCopy Include="$(OutDir)/NativeLibrary.*" />
<AssembliesToCopy Include="$(OutDir)/$(TargetName).dll" />
</ItemGroup>
<Copy SourceFiles="@(AssembliesToCopy)" DestinationFiles="@(AssembliesToCopy -> '$(NativeLibrarySubdirectory)/%(Filename)$(FileNameSuffix)%(Extension)')" />
</Target>
</Project>
2 changes: 1 addition & 1 deletion src/tests/Interop/NativeLibrary/Callback/CallbackTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ private IntPtr ResolveDllImport(string libraryName, Assembly asm, DllImportSearc
if (string.Equals(libraryName, NativeLibraryToLoad.InvalidName))
{
Assert.Equal(DllImportSearchPath.System32, dllImportSearchPath);
return NativeLibrary.Load(NativeLibraryToLoad.Name, asm, null);
return NativeLibrary.Load(NativeLibraryToLoad.GetFullPath(), asm, null);
}

return IntPtr.Zero;
Expand Down
Loading

0 comments on commit 5a1baeb

Please sign in to comment.