Skip to content

Commit

Permalink
Add NativeLibrary Resolve Event
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 committed Jan 14, 2019
1 parent 8ede2d4 commit 60557a9
Showing 1 changed file with 171 additions and 28 deletions.
199 changes: 171 additions & 28 deletions src/vm/dllimport.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6151,7 +6151,7 @@ NATIVE_LIBRARY_HANDLE NDirect::LoadLibraryFromPath(LPCWSTR libraryPath, BOOL thr

// static
NATIVE_LIBRARY_HANDLE NDirect::LoadLibraryByName(LPCWSTR libraryName, Assembly *callingAssembly,
BOOL hasDllImportSearchFlag, DWORD dllImportSearchFlag,
BOOL hasDllImportSearchFlags, DWORD dllImportSearchFlags,
BOOL throwOnError)
{
CONTRACTL
Expand All @@ -6164,15 +6164,15 @@ NATIVE_LIBRARY_HANDLE NDirect::LoadLibraryByName(LPCWSTR libraryName, Assembly *

LoadLibErrorTracker errorTracker;

// First checks if a default DllImportSearchPathFlag was passed in, if so, use that value.
// First checks if a default dllImportSearchPathFlags was passed in, if so, use that value.
// Otherwise checks if the assembly has the DefaultDllImportSearchPathsAttribute attribute. If so, use that value.
BOOL searchAssemblyDirectory = TRUE;
DWORD dllImportSearchPathFlag = 0;
DWORD dllImportSearchPathFlags = 0;

if (hasDllImportSearchFlag)
if (hasDllImportSearchFlags)
{
dllImportSearchPathFlag = dllImportSearchFlag & ~DLLIMPORTSEARCHPATH_ASSEMBLYDIRECTORY;
searchAssemblyDirectory = dllImportSearchFlag & DLLIMPORTSEARCHPATH_ASSEMBLYDIRECTORY;
dllImportSearchPathFlags = dllImportSearchFlags & ~DLLIMPORTSEARCHPATH_ASSEMBLYDIRECTORY;
searchAssemblyDirectory = dllImportSearchFlags & DLLIMPORTSEARCHPATH_ASSEMBLYDIRECTORY;

}
else
Expand All @@ -6181,13 +6181,13 @@ NATIVE_LIBRARY_HANDLE NDirect::LoadLibraryByName(LPCWSTR libraryName, Assembly *

if (pModule->HasDefaultDllImportSearchPathsAttribute())
{
dllImportSearchPathFlag = pModule->DefaultDllImportSearchPathsAttributeCachedValue();
dllImportSearchPathFlags = pModule->DefaultDllImportSearchPathsAttributeCachedValue();
searchAssemblyDirectory = pModule->DllImportSearchAssemblyDirectory();
}
}

NATIVE_LIBRARY_HANDLE hmod =
LoadLibraryModuleBySearch(callingAssembly, searchAssemblyDirectory, dllImportSearchPathFlag, &errorTracker, libraryName);
LoadLibraryModuleBySearch(callingAssembly, searchAssemblyDirectory, dllImportSearchPathFlags, &errorTracker, libraryName);

if (throwOnError && (hmod == nullptr))
{
Expand All @@ -6206,11 +6206,11 @@ NATIVE_LIBRARY_HANDLE NDirect::LoadLibraryModuleBySearch(NDirectMethodDesc * pMD
// First checks if the method has DefaultDllImportSearchPathsAttribute. If so, use that value.
// Otherwise checks if the assembly has the attribute. If so, use that value.
BOOL searchAssemblyDirectory = TRUE;
DWORD dllImportSearchPathFlag = 0;
DWORD dllImportSearchPathFlags = 0;

if (pMD->HasDefaultDllImportSearchPathsAttribute())
{
dllImportSearchPathFlag = pMD->DefaultDllImportSearchPathsAttributeCachedValue();
dllImportSearchPathFlags = pMD->DefaultDllImportSearchPathsAttributeCachedValue();
searchAssemblyDirectory = pMD->DllImportSearchAssemblyDirectory();
}
else
Expand All @@ -6219,13 +6219,13 @@ NATIVE_LIBRARY_HANDLE NDirect::LoadLibraryModuleBySearch(NDirectMethodDesc * pMD

if (pModule->HasDefaultDllImportSearchPathsAttribute())
{
dllImportSearchPathFlag = pModule->DefaultDllImportSearchPathsAttributeCachedValue();
dllImportSearchPathFlags = pModule->DefaultDllImportSearchPathsAttributeCachedValue();
searchAssemblyDirectory = pModule->DllImportSearchAssemblyDirectory();
}
}

Assembly* pAssembly = pMD->GetMethodTable()->GetAssembly();
return LoadLibraryModuleBySearch(pAssembly, searchAssemblyDirectory, dllImportSearchPathFlag, pErrorTracker, wszLibName);
return LoadLibraryModuleBySearch(pAssembly, searchAssemblyDirectory, dllImportSearchPathFlags, pErrorTracker, wszLibName);
}

// static
Expand Down Expand Up @@ -6274,6 +6274,17 @@ INT_PTR NDirect::GetNativeLibraryExport(NATIVE_LIBRARY_HANDLE handle, LPCWSTR sy
return address;
}

#ifndef PLATFORM_UNIX
BOOL IsWindowsAPISet(PCWSTR wszLibName)
{
STANDARD_VM_CONTRACT;

// This is replicating quick check from the OS implementation of api sets.
return SString::_wcsnicmp(wszLibName, W("api-"), 4) == 0 ||
SString::_wcsnicmp(wszLibName, W("ext-"), 4) == 0;
}
#endif // !PLATFORM_UNIX

// static
NATIVE_LIBRARY_HANDLE NDirect::LoadLibraryModuleViaHost(NDirectMethodDesc * pMD, PCWSTR wszLibName)
{
Expand All @@ -6282,13 +6293,12 @@ NATIVE_LIBRARY_HANDLE NDirect::LoadLibraryModuleViaHost(NDirectMethodDesc * pMD,
//Check if we need to provide the host a chance to provide the unmanaged dll

#ifndef PLATFORM_UNIX
// Prevent Overriding of Windows API sets.
// This is replicating quick check from the OS implementation of api sets.
if (SString::_wcsnicmp(wszLibName, W("api-"), 4) == 0 || SString::_wcsnicmp(wszLibName, W("ext-"), 4) == 0)
if (IsWindowsAPISet(wszLibName))
{
// Prevent Overriding of Windows API sets.
return NULL;
}
#endif
#endif // !PLATFORM_UNIX

NATIVE_LIBRARY_HANDLE hmod = NULL;
AppDomain* pDomain = GetAppDomain();
Expand Down Expand Up @@ -6438,6 +6448,130 @@ NATIVE_LIBRARY_HANDLE NDirect::LoadLibraryModuleViaEvent(NDirectMethodDesc * pMD
return hmod;
}

NATIVE_LIBRARY_HANDLE NDirect::LoadLibraryModuleViaCallback(NDirectMethodDesc * pMD, LPCWSTR wszLibName)
{
STANDARD_VM_CONTRACT;

NATIVE_LIBRARY_HANDLE handle = NULL;

DWORD dllImportSearchPathFlags = 0;
BOOL hasDllImportSearchPathFlags = pMD->HasDefaultDllImportSearchPathsAttribute();
if (hasDllImportSearchPathFlags)
{
dllImportSearchPathFlags = pMD->DefaultDllImportSearchPathsAttributeCachedValue();
if (pMD->DllImportSearchAssemblyDirectory())
dllImportSearchPathFlags |= DLLIMPORTSEARCHPATH_ASSEMBLYDIRECTORY;
}

Assembly* pAssembly = pMD->GetMethodTable()->GetAssembly();

GCX_COOP();

struct {
STRINGREF libNameRef;
OBJECTREF assemblyRef;
} gc = { NULL, NULL };

GCPROTECT_BEGIN(gc);

gc.libNameRef = StringObject::NewString(wszLibName);
gc.assemblyRef = pAssembly->GetExposedObject();

PREPARE_NONVIRTUAL_CALLSITE(METHOD__NATIVELIBRARY__LOADLIBRARYCALLBACKSTUB);
DECLARE_ARGHOLDER_ARRAY(args, 4);
args[ARGNUM_0] = STRINGREF_TO_ARGHOLDER(gc.libNameRef);
args[ARGNUM_1] = OBJECTREF_TO_ARGHOLDER(gc.assemblyRef);
args[ARGNUM_2] = BOOL_TO_ARGHOLDER(hasDllImportSearchPathFlags);
args[ARGNUM_3] = DWORD_TO_ARGHOLDER(dllImportSearchPathFlags);

// Make the call
CALL_MANAGED_METHOD(handle, NATIVE_LIBRARY_HANDLE, args);
GCPROTECT_END();

return handle;
}

// 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 hmod;
}

// Try to load the module alongside the assembly where the PInvoke was declared.
NATIVE_LIBRARY_HANDLE NDirect::LoadFromPInvokeAssemblyDirectory(Assembly *pAssembly, LPCWSTR libName, DWORD flags, LoadLibErrorTracker *pErrorTracker)
{
Expand All @@ -6461,11 +6595,12 @@ NATIVE_LIBRARY_HANDLE NDirect::LoadFromPInvokeAssemblyDirectory(Assembly *pAssem
}

// Try to load the module from the native DLL search directories
NATIVE_LIBRARY_HANDLE NDirect::LoadFromNativeDllSearchDirectories(AppDomain* pDomain, LPCWSTR libName, DWORD flags, LoadLibErrorTracker *pErrorTracker)
NATIVE_LIBRARY_HANDLE NDirect::LoadFromNativeDllSearchDirectories(LPCWSTR libName, DWORD flags, LoadLibErrorTracker *pErrorTracker)
{
STANDARD_VM_CONTRACT;

NATIVE_LIBRARY_HANDLE hmod = NULL;
AppDomain* pDomain = GetAppDomain();

if (pDomain->HasNativeDllSearchDirectories())
{
Expand Down Expand Up @@ -6587,7 +6722,7 @@ static void DetermineLibNameVariations(const WCHAR** libNameVariations, int* num
// Search for the library and variants of its name in probing directories.
//static
NATIVE_LIBRARY_HANDLE NDirect::LoadLibraryModuleBySearch(Assembly *callingAssembly,
BOOL searchAssemblyDirectory, DWORD dllImportSearchPathFlag,
BOOL searchAssemblyDirectory, DWORD dllImportSearchPathFlags,
LoadLibErrorTracker * pErrorTracker, LPCWSTR wszLibName)
{
STANDARD_VM_CONTRACT;
Expand All @@ -6597,7 +6732,7 @@ NATIVE_LIBRARY_HANDLE NDirect::LoadLibraryModuleBySearch(Assembly *callingAssemb
#if defined(FEATURE_CORESYSTEM) && !defined(PLATFORM_UNIX)
// Try to go straight to System32 for Windows API sets. This is replicating quick check from
// the OS implementation of api sets.
if (SString::_wcsnicmp(wszLibName, W("api-"), 4) == 0 || SString::_wcsnicmp(wszLibName, W("ext-"), 4) == 0)
if (IsWindowsAPISet(wszLibName))
{
hmod = LocalLoadLibraryHelper(wszLibName, LOAD_LIBRARY_SEARCH_SYSTEM32, pErrorTracker);
if (hmod != NULL)
Expand Down Expand Up @@ -6625,7 +6760,7 @@ NATIVE_LIBRARY_HANDLE NDirect::LoadLibraryModuleBySearch(Assembly *callingAssemb
currLibNameVariation.Printf(prefixSuffixCombinations[i], PLATFORM_SHARED_LIB_PREFIX_W, wszLibName, PLATFORM_SHARED_LIB_SUFFIX_W);

// NATIVE_DLL_SEARCH_DIRECTORIES set by host is considered well known path
hmod = LoadFromNativeDllSearchDirectories(pDomain, currLibNameVariation, loadWithAlteredPathFlags, pErrorTracker);
hmod = LoadFromNativeDllSearchDirectories(currLibNameVariation, loadWithAlteredPathFlags, pErrorTracker);
if (hmod != NULL)
{
return hmod;
Expand All @@ -6634,11 +6769,11 @@ NATIVE_LIBRARY_HANDLE NDirect::LoadLibraryModuleBySearch(Assembly *callingAssemb
if (!libNameIsRelativePath)
{
DWORD flags = loadWithAlteredPathFlags;
if ((dllImportSearchPathFlag & LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR) != 0)
if ((dllImportSearchPathFlags & LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR) != 0)
{
// LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR is the only flag affecting absolute path. Don't OR the flags
// unconditionally as all absolute path P/Invokes could then lose LOAD_WITH_ALTERED_SEARCH_PATH.
flags |= dllImportSearchPathFlag;
flags |= dllImportSearchPathFlags;
}

hmod = LocalLoadLibraryHelper(currLibNameVariation, flags, pErrorTracker);
Expand All @@ -6649,14 +6784,14 @@ NATIVE_LIBRARY_HANDLE NDirect::LoadLibraryModuleBySearch(Assembly *callingAssemb
}
else if ((callingAssembly != nullptr) && searchAssemblyDirectory)
{
hmod = LoadFromPInvokeAssemblyDirectory(callingAssembly, currLibNameVariation, loadWithAlteredPathFlags | dllImportSearchPathFlag, pErrorTracker);
hmod = LoadFromPInvokeAssemblyDirectory(callingAssembly, currLibNameVariation, loadWithAlteredPathFlags | dllImportSearchPathFlags, pErrorTracker);
if (hmod != NULL)
{
return hmod;
}
}

hmod = LocalLoadLibraryHelper(currLibNameVariation, dllImportSearchPathFlag, pErrorTracker);
hmod = LocalLoadLibraryHelper(currLibNameVariation, dllImportSearchPathFlags, pErrorTracker);
if (hmod != NULL)
{
return hmod;
Expand Down Expand Up @@ -6686,7 +6821,7 @@ NATIVE_LIBRARY_HANDLE NDirect::LoadLibraryModuleBySearch(Assembly *callingAssemb
Assembly *pAssembly = spec.LoadAssembly(FILE_LOADED);
Module *pModule = pAssembly->FindModuleByName(szLibName);

hmod = LocalLoadLibraryHelper(pModule->GetPath(), loadWithAlteredPathFlags | dllImportSearchPathFlag, pErrorTracker);
hmod = LocalLoadLibraryHelper(pModule->GetPath(), loadWithAlteredPathFlags | dllImportSearchPathFlags, pErrorTracker);
}
}

Expand All @@ -6707,11 +6842,18 @@ HINSTANCE NDirect::LoadLibraryModule(NDirectMethodDesc * pMD, LoadLibErrorTracke
if ( !name || !*name )
return NULL;

ModuleHandleHolder hmod;

PREFIX_ASSUME( name != NULL );
PREFIX_ASSUME( name != NULL );
MAKE_WIDEPTR_FROMUTF8( wszLibName, name );

ModuleHandleHolder hmod = LoadLibraryModuleViaCallback(pMD, wszLibName);
if (hmod != NULL)
{
#ifdef FEATURE_PAL
hmod = PAL_RegisterLibraryDirect(hmod, wszLibName);
#endif // FEATURE_PAL
return hmod.Extract();
}

AppDomain* pDomain = GetAppDomain();

// AssemblyLoadContext is not supported in AppX mode and thus,
Expand All @@ -6734,6 +6876,7 @@ HINSTANCE NDirect::LoadLibraryModule(NDirectMethodDesc * pMD, LoadLibErrorTracke
return hmod.Extract();
}


hmod = LoadLibraryModuleBySearch(pMD, pErrorTracker, wszLibName);
if (hmod != NULL)
{
Expand Down

0 comments on commit 60557a9

Please sign in to comment.