Skip to content
This repository has been archived by the owner on Jan 23, 2023. It is now read-only.

Commit

Permalink
Add NativeLibrary Resolve Event (#21929)
Browse files Browse the repository at this point in the history
This change adds the Native library resolving event, to be raised as the last attempt to resolve a native DLL in an AssemblyLoadContext.

With this change, the DllImport resolution sequence is as follows (stopping at any step with successful resolution):

* If the invoking-assembly is not in the default load context, call AssemblyLoadContext.LoadUnmanagedDll()
* Run the default load logic, try loading from:
    * AppDomain cache
    * NATIVE_DLL_SEARCH_DIRECTORIES
    * Invoking-assembly directory, System32, etc. based on DllImportSearchPaths
* Raise the ResolvingUnmanagedDll event

API Review: https://github.com/dotnet/corefx/issues/32850

The ResolveEventTests triggered a pre-existing bug in the exception handling code (#21964).
Disabling the test on ARM64 Windows until the issue is fixed.
  • Loading branch information
swaroop-sridhar authored Jan 13, 2019
1 parent 908891c commit 8b7d300
Show file tree
Hide file tree
Showing 14 changed files with 391 additions and 29 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ internal AssemblyLoadContext(bool fRepresentsTPALoadContext, bool isCollectible)
m_pNativeAssemblyLoadContext = InitializeAssemblyLoadContext(thisHandlePtr, fRepresentsTPALoadContext, isCollectible);

// Initialize event handlers to be null by default
ResolvingUnmanagedDll = null;
Resolving = null;
Unloading = null;

Expand Down Expand Up @@ -304,12 +305,12 @@ private Assembly GetFirstResolvedAssembly(AssemblyName assemblyName)
resolvedAssembly = handler(this, assemblyName);
if (resolvedAssembly != null)
{
break;
return resolvedAssembly;
}
}
}

return resolvedAssembly;
return null;
}

private Assembly ValidateAssemblyNameWithSimpleName(Assembly assembly, string requestedSimpleName)
Expand Down Expand Up @@ -413,6 +414,36 @@ private static IntPtr ResolveUnmanagedDll(string unmanagedDllName, IntPtr gchMan
return context.LoadUnmanagedDll(unmanagedDllName);
}

// This method is invoked by the VM to resolve a native library using the ResolvingUnmanagedDll event
// after trying all other means of resolution.
private static IntPtr ResolveUnmanagedDllUsingEvent(string unmanagedDllName, Assembly assembly, IntPtr gchManagedAssemblyLoadContext)
{
AssemblyLoadContext context = (AssemblyLoadContext)(GCHandle.FromIntPtr(gchManagedAssemblyLoadContext).Target);
return context.GetResolvedUnmanagedDll(assembly, unmanagedDllName);
}

private IntPtr GetResolvedUnmanagedDll(Assembly assembly, string unmanagedDllName)
{
IntPtr resolvedDll = IntPtr.Zero;

Func<Assembly, string, IntPtr> dllResolveHandler = ResolvingUnmanagedDll;

if (dllResolveHandler != null)
{
// Loop through the event subscribers and return the first non-null native library handle
foreach (Func<Assembly, string, IntPtr> handler in dllResolveHandler.GetInvocationList())
{
resolvedDll = handler(assembly, unmanagedDllName);
if (resolvedDll != IntPtr.Zero)
{
return resolvedDll;
}
}
}

return IntPtr.Zero;
}

public static AssemblyLoadContext Default
{
get
Expand Down Expand Up @@ -509,7 +540,22 @@ internal static void OnProcessExit()
}
}

// Event handler for resolving native libraries.
// This event is raised if the native library could not be resolved via
// the default resolution logic [including AssemblyLoadContext.LoadUnmanagedDll()]
//
// Inputs: Invoking assembly, and library name to resolve
// Returns: A handle to the loaded native library
public event Func<Assembly, string, IntPtr> ResolvingUnmanagedDll;

// Event handler for resolving managed assemblies.
// This event is raised if the managed assembly could not be resolved via
// the default resolution logic [including AssemblyLoadContext.Load()]
//
// Inputs: The AssemblyLoadContext and AssemblyName to be loaded
// Returns: The Loaded assembly object.
public event Func<AssemblyLoadContext, AssemblyName, Assembly> Resolving;

public event Action<AssemblyLoadContext> Unloading;

// Contains the reference to VM's representation of the AssemblyLoadContext
Expand Down
126 changes: 100 additions & 26 deletions src/vm/dllimport.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6275,7 +6275,7 @@ INT_PTR NDirect::GetNativeLibraryExport(NATIVE_LIBRARY_HANDLE handle, LPCWSTR sy
}

// static
NATIVE_LIBRARY_HANDLE NDirect::LoadLibraryModuleViaHost(NDirectMethodDesc * pMD, AppDomain* pDomain, PCWSTR wszLibName)
NATIVE_LIBRARY_HANDLE NDirect::LoadLibraryModuleViaHost(NDirectMethodDesc * pMD, PCWSTR wszLibName)
{
STANDARD_VM_CONTRACT;
//Dynamic Pinvoke Support:
Expand All @@ -6290,7 +6290,8 @@ NATIVE_LIBRARY_HANDLE NDirect::LoadLibraryModuleViaHost(NDirectMethodDesc * pMD,
}
#endif

LPVOID hmod = NULL;
NATIVE_LIBRARY_HANDLE hmod = NULL;
AppDomain* pDomain = GetAppDomain();
CLRPrivBinderCoreCLR *pTPABinder = pDomain->GetTPABinderContext();
Assembly* pAssembly = pMD->GetMethodTable()->GetAssembly();

Expand Down Expand Up @@ -6349,11 +6350,92 @@ NATIVE_LIBRARY_HANDLE NDirect::LoadLibraryModuleViaHost(NDirectMethodDesc * pMD,
args[ARGNUM_1] = PTR_TO_ARGHOLDER(ptrManagedAssemblyLoadContext);

// Make the call
CALL_MANAGED_METHOD(hmod,LPVOID,args);
CALL_MANAGED_METHOD(hmod, NATIVE_LIBRARY_HANDLE, args);

GCPROTECT_END();

return hmod;
}

// Return the AssemblyLoadContext for an assembly
INT_PTR GetManagedAssemblyLoadContext(Assembly* pAssembly)
{
STANDARD_VM_CONTRACT;

PTR_ICLRPrivBinder pBindingContext = pAssembly->GetManifestFile()->GetBindingContext();
if (pBindingContext == NULL)
{
// GetBindingContext() returns NULL for System.Private.CoreLib
return NULL;
}

UINT_PTR assemblyBinderID = 0;
IfFailThrow(pBindingContext->GetBinderID(&assemblyBinderID));

AppDomain *pDomain = GetAppDomain();
ICLRPrivBinder *pCurrentBinder = reinterpret_cast<ICLRPrivBinder *>(assemblyBinderID);

#ifdef FEATURE_COMINTEROP
if (AreSameBinderInstance(pCurrentBinder, pDomain->GetWinRtBinder()))
{
// No ALC associated handle with WinRT Binders.
return NULL;
}
#endif // FEATURE_COMINTEROP

// The code here deals with two implementations of ICLRPrivBinder interface:
// - CLRPrivBinderCoreCLR for the TPA binder in the default ALC, and
// - CLRPrivBinderAssemblyLoadContext for custom ALCs.
// in order obtain the associated ALC handle.
INT_PTR ptrManagedAssemblyLoadContext = AreSameBinderInstance(pCurrentBinder, pDomain->GetTPABinderContext())
? ((CLRPrivBinderCoreCLR *)pCurrentBinder)->GetManagedAssemblyLoadContext()
: ((CLRPrivBinderAssemblyLoadContext *)pCurrentBinder)->GetManagedAssemblyLoadContext();

return ptrManagedAssemblyLoadContext;
}

// static
NATIVE_LIBRARY_HANDLE NDirect::LoadLibraryModuleViaEvent(NDirectMethodDesc * pMD, PCWSTR wszLibName)
{
STANDARD_VM_CONTRACT;

NATIVE_LIBRARY_HANDLE hmod = NULL;
Assembly* pAssembly = pMD->GetMethodTable()->GetAssembly();
INT_PTR ptrManagedAssemblyLoadContext = GetManagedAssemblyLoadContext(pAssembly);

if (ptrManagedAssemblyLoadContext == NULL)
{
return NULL;
}

GCX_COOP();

struct {
STRINGREF DllName;
OBJECTREF AssemblyRef;
} gc = { NULL, NULL };

GCPROTECT_BEGIN(gc);

gc.DllName = StringObject::NewString(wszLibName);
gc.AssemblyRef = pAssembly->GetExposedObject();

// Prepare to invoke System.Runtime.Loader.AssemblyLoadContext.ResolveUnmanagedDllUsingEvent method
// While ResolveUnmanagedDllUsingEvent() could compute the AssemblyLoadContext using the AssemblyRef
// argument, it will involve another pInvoke to the runtime. So AssemblyLoadContext is passed in
// as an additional argument.
PREPARE_NONVIRTUAL_CALLSITE(METHOD__ASSEMBLYLOADCONTEXT__RESOLVEUNMANAGEDDLLUSINGEVENT);
DECLARE_ARGHOLDER_ARRAY(args, 3);
args[ARGNUM_0] = STRINGREF_TO_ARGHOLDER(gc.DllName);
args[ARGNUM_1] = OBJECTREF_TO_ARGHOLDER(gc.AssemblyRef);
args[ARGNUM_2] = PTR_TO_ARGHOLDER(ptrManagedAssemblyLoadContext);

// Make the call
CALL_MANAGED_METHOD(hmod, NATIVE_LIBRARY_HANDLE, args);

GCPROTECT_END();

return (NATIVE_LIBRARY_HANDLE)hmod;
return hmod;
}

// Try to load the module alongside the assembly where the PInvoke was declared.
Expand Down Expand Up @@ -6633,15 +6715,13 @@ HINSTANCE NDirect::LoadLibraryModule(NDirectMethodDesc * pMD, LoadLibErrorTracke
AppDomain* pDomain = GetAppDomain();

// AssemblyLoadContext is not supported in AppX mode and thus,
// we should not perform PInvoke resolution via it when operating in
// AppX mode.
// we should not perform PInvoke resolution via it when operating in AppX mode.
if (!AppX::IsAppXProcess())
{
hmod = LoadLibraryModuleViaHost(pMD, pDomain, wszLibName);
hmod = LoadLibraryModuleViaHost(pMD, wszLibName);
if (hmod != NULL)
{
#ifdef FEATURE_PAL
// Register the system library handle with PAL and get a PAL library handle
hmod = PAL_RegisterLibraryDirect(hmod, wszLibName);
#endif // FEATURE_PAL
return hmod.Extract();
Expand All @@ -6654,36 +6734,30 @@ HINSTANCE NDirect::LoadLibraryModule(NDirectMethodDesc * pMD, LoadLibErrorTracke
return hmod.Extract();
}

#ifdef FEATURE_PAL
// In the PAL version of CoreCLR, the CLR module itself exports the functionality
// that the Windows version obtains from kernel32 and friends. In order to avoid
// picking up the wrong instance, we perform this redirection first.
// This is also true for CoreSystem builds, where mscorlib p/invokes are forwarded through coreclr
// itself so we can control CoreSystem library/API name re-mapping from one central location.
if (SString::_wcsicmp(wszLibName, MAIN_CLR_MODULE_NAME_W) == 0)
hmod = LoadLibraryModuleBySearch(pMD, pErrorTracker, wszLibName);
if (hmod != NULL)
{
hmod = GetCLRModule();
}
#ifdef FEATURE_PAL
hmod = PAL_RegisterLibraryDirect(hmod, wszLibName);
#endif // FEATURE_PAL

if (hmod == NULL)
// If we have a handle add it to the cache.
pDomain->AddUnmanagedImageToCache(wszLibName, hmod);
return hmod.Extract();
}

if (!AppX::IsAppXProcess())
{
hmod = LoadLibraryModuleBySearch(pMD, pErrorTracker, wszLibName);
hmod = LoadLibraryModuleViaEvent(pMD, wszLibName);
if (hmod != NULL)
{
#ifdef FEATURE_PAL
// Register the system library handle with PAL and get a PAL library handle
hmod = PAL_RegisterLibraryDirect(hmod, wszLibName);
#endif // FEATURE_PAL
return hmod.Extract();
}
}

if (hmod != NULL)
{
// If we have a handle add it to the cache.
pDomain->AddUnmanagedImageToCache(wszLibName, hmod);
}

return hmod.Extract();
}

Expand Down
3 changes: 2 additions & 1 deletion src/vm/dllimport.h
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,8 @@ class NDirect

static NATIVE_LIBRARY_HANDLE LoadFromNativeDllSearchDirectories(AppDomain* pDomain, LPCWSTR libName, DWORD flags, LoadLibErrorTracker *pErrorTracker);
static NATIVE_LIBRARY_HANDLE LoadFromPInvokeAssemblyDirectory(Assembly *pAssembly, LPCWSTR libName, DWORD flags, LoadLibErrorTracker *pErrorTracker);
static NATIVE_LIBRARY_HANDLE LoadLibraryModuleViaHost(NDirectMethodDesc * pMD, AppDomain* pDomain, const wchar_t* wszLibName);
static NATIVE_LIBRARY_HANDLE LoadLibraryModuleViaHost(NDirectMethodDesc * pMD, LPCWSTR wszLibName);
static NATIVE_LIBRARY_HANDLE LoadLibraryModuleViaEvent(NDirectMethodDesc * pMD, LPCWSTR wszLibName);
static NATIVE_LIBRARY_HANDLE LoadLibraryModuleBySearch(NDirectMethodDesc * pMD, LoadLibErrorTracker * pErrorTracker, const wchar_t* wszLibName);
static NATIVE_LIBRARY_HANDLE LoadLibraryModuleBySearch(Assembly *callingAssembly, BOOL searchAssemblyDirectory, DWORD dllImportSearchPathFlag, LoadLibErrorTracker * pErrorTracker, const wchar_t* wszLibName);

Expand Down
1 change: 1 addition & 0 deletions src/vm/metasig.h
Original file line number Diff line number Diff line change
Expand Up @@ -551,6 +551,7 @@ DEFINE_METASIG_T(IM(RefGuid_OutIntPtr_RetCustomQueryInterfaceResult, r(g(GUID))
#endif //FEATURE_COMINTEROP

DEFINE_METASIG_T(SM(IntPtr_AssemblyName_RetAssemblyBase, I C(ASSEMBLY_NAME), C(ASSEMBLYBASE)))
DEFINE_METASIG_T(SM(Str_AssemblyBase_IntPtr_RetIntPtr, s C(ASSEMBLYBASE) I, I))

// ThreadPool
DEFINE_METASIG(SM(Obj_Bool_RetVoid, j F, v))
Expand Down
1 change: 1 addition & 0 deletions src/vm/mscorlib.h
Original file line number Diff line number Diff line change
Expand Up @@ -864,6 +864,7 @@ DEFINE_METHOD(FIRSTCHANCE_EVENTARGS, CTOR, .ctor,
DEFINE_CLASS(ASSEMBLYLOADCONTEXT, Loader, AssemblyLoadContext)
DEFINE_METHOD(ASSEMBLYLOADCONTEXT, RESOLVE, Resolve, SM_IntPtr_AssemblyName_RetAssemblyBase)
DEFINE_METHOD(ASSEMBLYLOADCONTEXT, RESOLVEUNMANAGEDDLL, ResolveUnmanagedDll, SM_Str_IntPtr_RetIntPtr)
DEFINE_METHOD(ASSEMBLYLOADCONTEXT, RESOLVEUNMANAGEDDLLUSINGEVENT, ResolveUnmanagedDllUsingEvent, SM_Str_AssemblyBase_IntPtr_RetIntPtr)
DEFINE_METHOD(ASSEMBLYLOADCONTEXT, RESOLVEUSINGEVENT, ResolveUsingResolvingEvent, SM_IntPtr_AssemblyName_RetAssemblyBase)
DEFINE_FIELD(ASSEMBLYLOADCONTEXT, ASSEMBLY_LOAD, AssemblyLoad)
DEFINE_METHOD(ASSEMBLYLOADCONTEXT, ON_ASSEMBLY_LOAD, OnAssemblyLoad, SM_Assembly_RetVoid)
Expand Down
6 changes: 6 additions & 0 deletions tests/issues.targets
Original file line number Diff line number Diff line change
Expand Up @@ -388,6 +388,9 @@
<ExcludeList Include="$(XunitTestBinBase)/Loader/classloader/DefaultInterfaceMethods/methodimpl/methodimpl/*">
<Issue>9565</Issue>
</ExcludeList>
<ExcludeList Include="$(XunitTestBinBase)/Interop/NativeLibraryResolveEvent/ResolveEventTests/*">
<Issue>21964</Issue>
</ExcludeList>
<ExcludeList Include="$(XunitTestBinBase)/Regressions/coreclr/16064/methodimpl/*">
<Issue>9565</Issue>
</ExcludeList>
Expand Down Expand Up @@ -819,6 +822,9 @@
<ExcludeList Include="$(XunitTestBinBase)/Interop/NativeLibrary/NativeLibraryTests/*">
<Issue>Issue building native components for the test. Since tests are currently built on Windows, the native components end up at Core_Root instead of Test directory, which is not suitable for the test.</Issue>
</ExcludeList>
<ExcludeList Include="$(XunitTestBinBase)/Interop/MarshalAPI/ResolveEvent/ResolveEventTests/*">
<Issue>Issue building native components for the test. Since tests are currently built on Windows, the native components end up at Core_Root instead of Test directory, which is not suitable for the test.</Issue>
</ExcludeList>
<ExcludeList Include="$(XunitTestBinBase)/CoreMangLib/cti/system/byte/ByteToString3/*">
<Issue>needs triage</Issue>
</ExcludeList>
Expand Down
1 change: 1 addition & 0 deletions tests/src/Interop/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ add_subdirectory(StringMarshalling/VBByRefStr)
add_subdirectory(MarshalAPI/FunctionPointer)
add_subdirectory(MarshalAPI/IUnknown)
add_subdirectory(NativeLibrary)
add_subdirectory(NativeLibraryResolveEvent)
add_subdirectory(SizeConst)
add_subdirectory(DllImportAttribute/ExeFile)
add_subdirectory(DllImportAttribute/FileNameContainDot)
Expand Down
13 changes: 13 additions & 0 deletions tests/src/Interop/NativeLibraryResolveEvent/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
cmake_minimum_required (VERSION 2.6)
project (ResolvedLib)
include_directories(${INC_PLATFORM_DIR})
set(SOURCES ResolvedLib.cpp)

# add the executable
add_library (ResolvedLib SHARED ${SOURCES})
target_link_libraries(ResolvedLib ${LINK_LIBRARIES_ADDITIONAL})

# add the install targets
install (TARGETS ResolvedLib DESTINATION bin)


Loading

0 comments on commit 8b7d300

Please sign in to comment.