diff options
29 files changed, 532 insertions, 76 deletions
diff --git a/src/System.Private.CoreLib/System.Private.CoreLib.csproj b/src/System.Private.CoreLib/System.Private.CoreLib.csproj index 0cf6733ad9..b052272158 100644 --- a/src/System.Private.CoreLib/System.Private.CoreLib.csproj +++ b/src/System.Private.CoreLib/System.Private.CoreLib.csproj @@ -119,6 +119,7 @@ <!-- Sources --> <ItemGroup> <Compile Include="$(BclSourcesRoot)\Internal\Console.cs" /> + <Compile Include="$(BclSourcesRoot)\Internal\Runtime\InteropServices\IsolatedComponentLoadContext.cs" /> <Compile Include="$(BclSourcesRoot)\Microsoft\Win32\UnsafeNativeMethods.cs" /> <Compile Include="$(BclSourcesRoot)\System\__Canon.cs" /> <Compile Include="$(BclSourcesRoot)\System\AppContext.CoreCLR.cs" /> @@ -365,6 +366,7 @@ <Compile Include="$(BclSourcesRoot)\System\Threading\ClrThreadPoolBoundHandle.Unix.cs" /> </ItemGroup> <ItemGroup Condition="'$(TargetsWindows)' == 'true'"> + <Compile Include="$(BclSourcesRoot)\Internal\Runtime\InteropServices\InMemoryAssemblyLoader.cs" /> <Compile Include="$(BclSourcesRoot)\System\DateTime.Windows.cs" /> <Compile Include="$(BclSourcesRoot)\Interop\Windows\OleAut32\Interop.VariantClear.cs" /> <Compile Include="$(BclSourcesRoot)\System\ApplicationModel.Windows.cs" /> diff --git a/src/System.Private.CoreLib/src/Internal/Runtime/InteropServices/ComActivator.cs b/src/System.Private.CoreLib/src/Internal/Runtime/InteropServices/ComActivator.cs index 608d1579fa..d1c6aa9bb7 100644 --- a/src/System.Private.CoreLib/src/Internal/Runtime/InteropServices/ComActivator.cs +++ b/src/System.Private.CoreLib/src/Internal/Runtime/InteropServices/ComActivator.cs @@ -215,7 +215,7 @@ $@"{nameof(GetClassFactoryForTypeInternal)} arguments: { if (!s_AssemblyLoadContexts.TryGetValue(assemblyPath, out alc)) { - alc = new ComServerLoadContext(assemblyPath); + alc = new IsolatedComponentLoadContext(assemblyPath); s_AssemblyLoadContexts.Add(assemblyPath, alc); } } @@ -223,38 +223,6 @@ $@"{nameof(GetClassFactoryForTypeInternal)} arguments: return alc; } - private class ComServerLoadContext : AssemblyLoadContext - { - private readonly AssemblyDependencyResolver _resolver; - - public ComServerLoadContext(string comServerAssemblyPath) - { - _resolver = new AssemblyDependencyResolver(comServerAssemblyPath); - } - - protected override Assembly Load(AssemblyName assemblyName) - { - string assemblyPath = _resolver.ResolveAssemblyToPath(assemblyName); - if (assemblyPath != null) - { - return LoadFromAssemblyPath(assemblyPath); - } - - return null; - } - - protected override IntPtr LoadUnmanagedDll(string unmanagedDllName) - { - string libraryPath = _resolver.ResolveUnmanagedDllToPath(unmanagedDllName); - if (libraryPath != null) - { - return LoadUnmanagedDllFromPath(libraryPath); - } - - return IntPtr.Zero; - } - } - [ComVisible(true)] private class BasicClassFactory : IClassFactory { diff --git a/src/System.Private.CoreLib/src/Internal/Runtime/InteropServices/InMemoryAssemblyLoader.cs b/src/System.Private.CoreLib/src/Internal/Runtime/InteropServices/InMemoryAssemblyLoader.cs new file mode 100644 index 0000000000..097f47a825 --- /dev/null +++ b/src/System.Private.CoreLib/src/Internal/Runtime/InteropServices/InMemoryAssemblyLoader.cs @@ -0,0 +1,32 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Diagnostics; +using System.IO; +using System.Reflection; +using System.Runtime.InteropServices; +using System.Runtime.Loader; + +namespace Internal.Runtime.InteropServices +{ + /// <summary> + /// This class enables the .NET IJW host to load an in-memory module as a .NET assembly + /// </summary> + public static class InMemoryAssemblyLoader + { + /// <summary> + /// Loads into an isolated AssemblyLoadContext an assembly that has already been loaded into memory by the OS loader as a native module. + /// </summary> + /// <param name="moduleHandle">The native module handle for the assembly.</param> + /// <param name="assemblyPath">The path to the assembly (as a pointer to a UTF-16 C string).</param> + public static unsafe void LoadInMemoryAssembly(IntPtr moduleHandle, IntPtr assemblyPath) + { + // We don't cache the ALCs here since each IJW assembly will call this method at most once + // (the load process rewrites the stubs that call here to call the actual methods they're supposed to) + AssemblyLoadContext context = new IsolatedComponentLoadContext(Marshal.PtrToStringUni(assemblyPath)); + context.LoadFromInMemoryModule(moduleHandle); + } + } +} diff --git a/src/System.Private.CoreLib/src/Internal/Runtime/InteropServices/IsolatedComponentLoadContext.cs b/src/System.Private.CoreLib/src/Internal/Runtime/InteropServices/IsolatedComponentLoadContext.cs new file mode 100644 index 0000000000..d78d7dd51c --- /dev/null +++ b/src/System.Private.CoreLib/src/Internal/Runtime/InteropServices/IsolatedComponentLoadContext.cs @@ -0,0 +1,47 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Reflection; +using System.Runtime.Loader; + +namespace Internal.Runtime.InteropServices +{ + /// <summary> + /// An <see cref="IsolatedComponentLoadContext" /> is an AssemblyLoadContext that can be used to isolate components such as COM components + /// or IJW components loaded from native. It provides a load context that uses an <see cref="AssemblyDependencyResolver" /> to resolve the component's + /// dependencies within the ALC and not pollute the default ALC. + ///</summary> + internal class IsolatedComponentLoadContext : AssemblyLoadContext + { + private readonly AssemblyDependencyResolver _resolver; + + public IsolatedComponentLoadContext(string componentAssemblyPath) + { + _resolver = new AssemblyDependencyResolver(componentAssemblyPath); + } + + protected override Assembly Load(AssemblyName assemblyName) + { + string assemblyPath = _resolver.ResolveAssemblyToPath(assemblyName); + if (assemblyPath != null) + { + return LoadFromAssemblyPath(assemblyPath); + } + + return null; + } + + protected override IntPtr LoadUnmanagedDll(string unmanagedDllName) + { + string libraryPath = _resolver.ResolveUnmanagedDllToPath(unmanagedDllName); + if (libraryPath != null) + { + return LoadUnmanagedDllFromPath(libraryPath); + } + + return IntPtr.Zero; + } + } +} diff --git a/src/System.Private.CoreLib/src/System/Runtime/Loader/AssemblyLoadContext.CoreCLR.cs b/src/System.Private.CoreLib/src/System/Runtime/Loader/AssemblyLoadContext.CoreCLR.cs index 60052d90ae..b36c382c38 100644 --- a/src/System.Private.CoreLib/src/System/Runtime/Loader/AssemblyLoadContext.CoreCLR.cs +++ b/src/System.Private.CoreLib/src/System/Runtime/Loader/AssemblyLoadContext.CoreCLR.cs @@ -52,6 +52,34 @@ namespace System.Runtime.Loader return loadedAssembly; } + +#if !FEATURE_PAL + [DllImport(JitHelpers.QCall, CharSet = CharSet.Unicode)] + private static extern IntPtr LoadFromInMemoryModuleInternal(IntPtr ptrNativeAssemblyLoadContext, IntPtr hModule, ObjectHandleOnStack retAssembly); + + + /// <summary> + /// Load a module that has already been loaded into memory by the OS loader as a .NET assembly. + /// </summary> + internal Assembly LoadFromInMemoryModule(IntPtr moduleHandle) + { + if (moduleHandle == IntPtr.Zero) + { + throw new ArgumentNullException(nameof(moduleHandle)); + } + lock (_unloadLock) + { + VerifyIsAlive(); + + RuntimeAssembly loadedAssembly = null; + LoadFromInMemoryModuleInternal( + _nativeAssemblyLoadContext, + moduleHandle, + JitHelpers.GetObjectHandleOnStack(ref loadedAssembly)); + return loadedAssembly; + } + } +#endif // This method is invoked by the VM when using the host-provided assembly load context // implementation. diff --git a/src/vm/appdomain.cpp b/src/vm/appdomain.cpp index 9362dd9c7b..bcffb3b8aa 100644 --- a/src/vm/appdomain.cpp +++ b/src/vm/appdomain.cpp @@ -4223,8 +4223,8 @@ static const char *fileLoadLevelName[] = "LOADLIBRARY", // FILE_LOAD_LOADLIBRARY "POST_LOADLIBRARY", // FILE_LOAD_POST_LOADLIBRARY "EAGER_FIXUPS", // FILE_LOAD_EAGER_FIXUPS - "VTABLE FIXUPS", // FILE_LOAD_VTABLE_FIXUPS "DELIVER_EVENTS", // FILE_LOAD_DELIVER_EVENTS + "VTABLE FIXUPS", // FILE_LOAD_VTABLE_FIXUPS "LOADED", // FILE_LOADED "VERIFY_EXECUTION", // FILE_LOAD_VERIFY_EXECUTION "ACTIVE", // FILE_ACTIVE diff --git a/src/vm/assemblynative.cpp b/src/vm/assemblynative.cpp index 30ee95b35f..ed2ce660e7 100644 --- a/src/vm/assemblynative.cpp +++ b/src/vm/assemblynative.cpp @@ -363,6 +363,42 @@ void QCALLTYPE AssemblyNative::LoadFromStream(INT_PTR ptrNativeAssemblyLoadConte END_QCALL; } +#ifndef FEATURE_PAL +/*static */ +void QCALLTYPE AssemblyNative::LoadFromInMemoryModule(INT_PTR ptrNativeAssemblyLoadContext, INT_PTR hModule, QCall::ObjectHandleOnStack retLoadedAssembly) +{ + QCALL_CONTRACT; + + BEGIN_QCALL; + + // Ensure that the invariants are in place + _ASSERTE(ptrNativeAssemblyLoadContext != NULL); + _ASSERTE(hModule != NULL); + + PEImageHolder pILImage(PEImage::LoadImage((HMODULE)hModule)); + + // Need to verify that this is a valid CLR assembly. + if (!pILImage->HasCorHeader()) + ThrowHR(COR_E_BADIMAGEFORMAT, BFA_BAD_IL); + + // Get the binder context in which the assembly will be loaded + ICLRPrivBinder *pBinderContext = reinterpret_cast<ICLRPrivBinder*>(ptrNativeAssemblyLoadContext); + + // Pass the in memory module as IL in an attempt to bind and load it + Assembly* pLoadedAssembly = AssemblyNative::LoadFromPEImage(pBinderContext, pILImage, NULL); + { + GCX_COOP(); + retLoadedAssembly.Set(pLoadedAssembly->GetExposedObject()); + } + + LOG((LF_CLASSLOADER, + LL_INFO100, + "\tLoaded assembly from pre-loaded native module\n")); + + END_QCALL; +} +#endif + void QCALLTYPE AssemblyNative::GetLocation(QCall::AssemblyHandle pAssembly, QCall::StringHandleOnStack retString) { QCALL_CONTRACT; diff --git a/src/vm/assemblynative.hpp b/src/vm/assemblynative.hpp index d506b6e407..13db261359 100644 --- a/src/vm/assemblynative.hpp +++ b/src/vm/assemblynative.hpp @@ -120,6 +120,9 @@ public: static void QCALLTYPE LoadFromPath(INT_PTR ptrNativeAssemblyLoadContext, LPCWSTR pwzILPath, LPCWSTR pwzNIPath, QCall::ObjectHandleOnStack retLoadedAssembly); static INT_PTR QCALLTYPE InternalLoadUnmanagedDllFromPath(LPCWSTR unmanagedLibraryPath); static void QCALLTYPE LoadFromStream(INT_PTR ptrNativeAssemblyLoadContext, INT_PTR ptrAssemblyArray, INT32 cbAssemblyArrayLength, INT_PTR ptrSymbolArray, INT32 cbSymbolArrayLength, QCall::ObjectHandleOnStack retLoadedAssembly); +#ifndef FEATURE_PAL + static void QCALLTYPE LoadFromInMemoryModule(INT_PTR ptrNativeAssemblyLoadContext, INT_PTR hModule, QCall::ObjectHandleOnStack retLoadedAssembly); +#endif static Assembly* LoadFromPEImage(ICLRPrivBinder* pBinderContext, PEImage *pILImage, PEImage *pNIImage); static INT_PTR QCALLTYPE GetLoadContextForAssembly(QCall::AssemblyHandle pAssembly); diff --git a/src/vm/ceeload.cpp b/src/vm/ceeload.cpp index 1fec2c262d..b9cf4a7900 100644 --- a/src/vm/ceeload.cpp +++ b/src/vm/ceeload.cpp @@ -6482,6 +6482,59 @@ void Module::NotifyDebuggerUnload(AppDomain *pDomain) } #if !defined(CROSSGEN_COMPILE) +using GetTokenForVTableEntry_t = mdToken(STDMETHODCALLTYPE*)(HMODULE module, BYTE**ppVTEntry); + +static HMODULE GetIJWHostForModule(Module* module) +{ +#if !defined(FEATURE_PAL) + PEDecoder* pe = module->GetFile()->GetLoadedIL(); + + BYTE* baseAddress = (BYTE*)module->GetFile()->GetIJWBase(); + + IMAGE_IMPORT_DESCRIPTOR* importDescriptor = (IMAGE_IMPORT_DESCRIPTOR*)pe->GetDirectoryData(pe->GetDirectoryEntry(IMAGE_DIRECTORY_ENTRY_IMPORT)); + + for(; importDescriptor->Characteristics != 0; importDescriptor++) + { + IMAGE_THUNK_DATA* importNameTable = (IMAGE_THUNK_DATA*)pe->GetRvaData(importDescriptor->OriginalFirstThunk); + + IMAGE_THUNK_DATA* importAddressTable = (IMAGE_THUNK_DATA*)pe->GetRvaData(importDescriptor->FirstThunk); + + for (int thunkIndex = 0; importNameTable[thunkIndex].u1.AddressOfData != 0; thunkIndex++) + { + // The most significant bit will be set if the entry points to an ordinal. + if ((importNameTable[thunkIndex].u1.Ordinal & (1LL << (sizeof(importNameTable[thunkIndex].u1.Ordinal) * CHAR_BIT - 1))) == 0) + { + IMAGE_IMPORT_BY_NAME* nameImport = (IMAGE_IMPORT_BY_NAME*)(baseAddress + importNameTable[thunkIndex].u1.AddressOfData); + if (strcmp("_CorDllMain", nameImport->Name) == 0) + { + HMODULE ijwHost; + + if (WszGetModuleHandleEx( + GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT, + (LPCWSTR)importAddressTable[thunkIndex].u1.Function, + &ijwHost)) + { + return ijwHost; + } + + } + } + } + } +#endif + return nullptr; +} + +static GetTokenForVTableEntry_t GetTokenGetterFromHostModule(HMODULE ijwHost) +{ + if (ijwHost != nullptr) + { + return (GetTokenForVTableEntry_t)GetProcAddress(ijwHost, "GetTokenForVTableEntry"); + } + + return nullptr; +} + //================================================================================= mdToken GetTokenForVTableEntry(HINSTANCE hInst, BYTE **ppVTEntry) { @@ -6504,7 +6557,6 @@ void SetTargetForVTableEntry(HINSTANCE hInst, BYTE **ppVTEntry, BYTE *pTarget) DWORD oldProtect; if (!ClrVirtualProtect(ppVTEntry, sizeof(BYTE*), PAGE_READWRITE, &oldProtect)) { - // This is very bad. We are not going to be able to update header. _ASSERTE(!"SetTargetForVTableEntry(): VirtualProtect() changing IJW thunk vtable to R/W failed.\n"); ThrowLastError(); @@ -6548,6 +6600,18 @@ void Module::FixupVTables() return; } + // Try getting a callback to the IJW host if it is loaded. + // The IJW host substitutes in special shims in the vtfixup table + // so if it is loaded, we need to query it for the tokens that were in the slots. + // If it is not loaded, then we know that the vtfixup table entries are tokens, + // so we can resolve them ourselves. + GetTokenForVTableEntry_t GetTokenForVTableEntryCallback = GetTokenGetterFromHostModule(GetIJWHostForModule(this)); + + if (GetTokenForVTableEntryCallback == nullptr) + { + GetTokenForVTableEntryCallback = GetTokenForVTableEntry; + } + HINSTANCE hInstThis = GetFile()->GetIJWBase(); // <REVISIT_TODO>@todo: workaround!</REVISIT_TODO> @@ -6648,7 +6712,7 @@ void Module::FixupVTables() { if (pData->IsMethodFixedUp(iFixup, iMethod)) continue; - mdToken mdTok = GetTokenForVTableEntry(hInstThis, (BYTE **)(pPointers + iMethod)); + mdToken mdTok = GetTokenForVTableEntryCallback(hInstThis, (BYTE**)(pPointers + iMethod)); CONSISTENCY_CHECK(mdTok != mdTokenNil); rgMethodsToLoad[iCurMethod++].token = mdTok; } diff --git a/src/vm/domainfile.cpp b/src/vm/domainfile.cpp index 0fd9c811d5..5c89767d1a 100644 --- a/src/vm/domainfile.cpp +++ b/src/vm/domainfile.cpp @@ -555,14 +555,14 @@ BOOL DomainFile::DoIncrementalLoad(FileLoadLevel level) EagerFixups(); break; - case FILE_LOAD_VTABLE_FIXUPS: - VtableFixups(); - break; - case FILE_LOAD_DELIVER_EVENTS: DeliverSyncEvents(); break; + case FILE_LOAD_VTABLE_FIXUPS: + VtableFixups(); + break; + case FILE_LOADED: FinishLoad(); break; diff --git a/src/vm/domainfile.h b/src/vm/domainfile.h index d6917b1fd5..ddc0384c58 100644 --- a/src/vm/domainfile.h +++ b/src/vm/domainfile.h @@ -45,8 +45,8 @@ enum FileLoadLevel FILE_LOAD_LOADLIBRARY, FILE_LOAD_POST_LOADLIBRARY, FILE_LOAD_EAGER_FIXUPS, - FILE_LOAD_VTABLE_FIXUPS, FILE_LOAD_DELIVER_EVENTS, + FILE_LOAD_VTABLE_FIXUPS, FILE_LOADED, // Loaded by not yet active FILE_LOAD_VERIFY_EXECUTION, FILE_ACTIVE // Fully active (constructors run & security checked) diff --git a/src/vm/ecalllist.h b/src/vm/ecalllist.h index 7302bb4e4a..c184633221 100644 --- a/src/vm/ecalllist.h +++ b/src/vm/ecalllist.h @@ -520,6 +520,9 @@ FCFuncStart(gAssemblyLoadContextFuncs) QCFuncElement("LoadFromPath", AssemblyNative::LoadFromPath) QCFuncElement("InternalLoadUnmanagedDllFromPath", AssemblyNative::InternalLoadUnmanagedDllFromPath) QCFuncElement("LoadFromStream", AssemblyNative::LoadFromStream) +#ifndef FEATURE_PAL + QCFuncElement("LoadFromInMemoryModuleInternal", AssemblyNative::LoadFromInMemoryModule) +#endif QCFuncElement("GetLoadContextForAssembly", AssemblyNative::GetLoadContextForAssembly) FCFuncElement("GetLoadedAssemblies", AppDomainNative::GetLoadedAssemblies) #if defined(FEATURE_MULTICOREJIT) diff --git a/tests/issues.targets b/tests/issues.targets index a206167a09..73a2884b99 100644 --- a/tests/issues.targets +++ b/tests/issues.targets @@ -458,6 +458,15 @@ <ExcludeList Include="$(XunitTestBinBase)/JIT/Directed/arglist/vararg/*"> <Issue>Needs triage</Issue> </ExcludeList> + <ExcludeList Include="$(XunitTestBinBase)/Interop/IJW/CopyConstructorMarshaler/CopyConstructorMarshaler/*"> + <Issue>Needs triage</Issue> + </ExcludeList> + <ExcludeList Include="$(XunitTestBinBase)/Interop/IJW/FixupCallsHostWhenLoaded/FixupCallsHostWhenLoaded/*"> + <Issue>Needs triage</Issue> + </ExcludeList> + <ExcludeList Include="$(XunitTestBinBase)/Interop/IJW/LoadIjwFromModuleHandle/LoadIjwFromModuleHandle/*"> + <Issue>Needs triage</Issue> + </ExcludeList> <ExcludeList Include="$(XunitTestBinBase)/Interop/IJW/ManagedCallingNative/ManagedCallingNative/*"> <Issue>Needs triage</Issue> </ExcludeList> diff --git a/tests/src/Interop/CMakeLists.txt b/tests/src/Interop/CMakeLists.txt index 32e4b77e16..acf4c849db 100644 --- a/tests/src/Interop/CMakeLists.txt +++ b/tests/src/Interop/CMakeLists.txt @@ -83,12 +83,12 @@ if(WIN32) add_subdirectory(COM/NativeClients/Primitives) add_subdirectory(COM/NativeClients/Licensing) add_subdirectory(COM/NativeClients/DefaultInterfaces) - add_subdirectory(IJW/FakeMscoree) # IJW isn't supported on ARM64 if(NOT CLR_CMAKE_PLATFORM_ARCH_ARM64) - add_subdirectory(IJW/ManagedCallingNative/IjwNativeDll) - add_subdirectory(IJW/NativeCallingManaged/IjwNativeCallingManagedDll) + add_subdirectory(IJW/ijwhostmock) + add_subdirectory(IJW/IjwNativeDll) + add_subdirectory(IJW/IjwNativeCallingManagedDll) add_subdirectory(IJW/CopyConstructorMarshaler) endif() endif(WIN32) diff --git a/tests/src/Interop/IJW/CopyConstructorMarshaler/CopyConstructorMarshaler.csproj b/tests/src/Interop/IJW/CopyConstructorMarshaler/CopyConstructorMarshaler.csproj index 34aad536a0..b1d38a911b 100644 --- a/tests/src/Interop/IJW/CopyConstructorMarshaler/CopyConstructorMarshaler.csproj +++ b/tests/src/Interop/IJW/CopyConstructorMarshaler/CopyConstructorMarshaler.csproj @@ -38,7 +38,7 @@ </ItemGroup> <ItemGroup> <ProjectReference Include="CMakeLists.txt" /> - <ProjectReference Include="../FakeMscoree/CMakeLists.txt" /> + <ProjectReference Include="../ijwhostmock/CMakeLists.txt" /> </ItemGroup> <Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), dir.targets))\dir.targets" /> </Project> diff --git a/tests/src/Interop/IJW/FakeMscoree/mscoree.cpp b/tests/src/Interop/IJW/FakeMscoree/mscoree.cpp deleted file mode 100644 index 99319ec28b..0000000000 --- a/tests/src/Interop/IJW/FakeMscoree/mscoree.cpp +++ /dev/null @@ -1,19 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -#include <windows.h> - -#ifndef DLLEXPORT -#ifdef _MSC_VER -#define DLLEXPORT __declspec(dllexport) -#else -#define DLLEXPORT __attribute__((visibility("default"))) -#endif -#endif - -// Entrypoint jumped to by IJW dlls when their dllmain is called -extern "C" DLLEXPORT BOOL WINAPI _CorDllMain(HINSTANCE hInst, DWORD dwReason, LPVOID lpReserved) -{ - return TRUE; -} diff --git a/tests/src/Interop/IJW/FakeMscoree/mscoree.def b/tests/src/Interop/IJW/FakeMscoree/mscoree.def deleted file mode 100644 index 9279b0364b..0000000000 --- a/tests/src/Interop/IJW/FakeMscoree/mscoree.def +++ /dev/null @@ -1,3 +0,0 @@ -LIBRARY MSCOREE -EXPORTS - _CorDllMain
\ No newline at end of file diff --git a/tests/src/Interop/IJW/FixupCallsHostWhenLoaded/FixupCallsHostWhenLoaded.cs b/tests/src/Interop/IJW/FixupCallsHostWhenLoaded/FixupCallsHostWhenLoaded.cs new file mode 100644 index 0000000000..cfc5a9a73d --- /dev/null +++ b/tests/src/Interop/IJW/FixupCallsHostWhenLoaded/FixupCallsHostWhenLoaded.cs @@ -0,0 +1,53 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.IO; +using System.Reflection; +using System.Runtime.InteropServices; +using TestLibrary; + +namespace FixupCallsHostWhenLoaded +{ + class FixupCallsHostWhenLoaded + { + static int Main(string[] args) + { + // Disable running on Windows 7 until IJW activation work is complete. + if(Environment.OSVersion.Platform != PlatformID.Win32NT || TestLibrary.Utilities.IsWindows7) + { + return 100; + } + + try + { + // Load a fake mscoree.dll to avoid starting desktop + IntPtr ijwHost = NativeLibrary.Load(Path.Combine(Environment.CurrentDirectory, "mscoree.dll")); + + WasModuleVTableQueriedDelegate wasModuleVTableQueried = Marshal.GetDelegateForFunctionPointer<WasModuleVTableQueriedDelegate>(NativeLibrary.GetExport(ijwHost, "WasModuleVTableQueried")); + + // Load IJW via reflection + Assembly.Load("IjwNativeDll"); + + IntPtr ijwModuleHandle = GetModuleHandle("IjwNativeDll.dll"); + + Assert.AreNotEqual(IntPtr.Zero, ijwModuleHandle); + Assert.IsTrue(wasModuleVTableQueried(ijwModuleHandle)); + } + catch (Exception e) + { + Console.WriteLine(e); + return 101; + } + + return 100; + } + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + delegate bool WasModuleVTableQueriedDelegate(IntPtr handle); + + [DllImport("kernel32.dll")] + static extern IntPtr GetModuleHandle(string lpModuleName); + } +} diff --git a/tests/src/Interop/IJW/FixupCallsHostWhenLoaded/FixupCallsHostWhenLoaded.csproj b/tests/src/Interop/IJW/FixupCallsHostWhenLoaded/FixupCallsHostWhenLoaded.csproj new file mode 100644 index 0000000000..20dc72f397 --- /dev/null +++ b/tests/src/Interop/IJW/FixupCallsHostWhenLoaded/FixupCallsHostWhenLoaded.csproj @@ -0,0 +1,44 @@ +<?xml version="1.0" encoding="utf-8"?> +<Project ToolsVersion="12.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> + <Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), dir.props))\dir.props" /> + <Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), Interop.settings.targets))\Interop.settings.targets" /> + <PropertyGroup> + <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration> + <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform> + <AssemblyName>FixupCallsHostWhenLoaded</AssemblyName> + <SchemaVersion>2.0</SchemaVersion> + <ProjectGuid>{49D1D482-E783-4CA9-B6BA-A9714BF81036}</ProjectGuid> + <OutputType>Exe</OutputType> + <ProjectTypeGuids>{786C830F-07A1-408B-BD7F-6EE04809D6DB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids> + <SolutionDir Condition="$(SolutionDir) == '' Or $(SolutionDir) == '*Undefined*'">..\..\</SolutionDir> + + <!-- IJW is Windows-only --> + <!-- Test unsupported outside of windows --> + <TestUnsupportedOutsideWindows>true</TestUnsupportedOutsideWindows> + <DisableProjectBuild Condition="'$(TargetsUnix)' == 'true'">true</DisableProjectBuild> + + <!-- IJW is not supported on ARM64 --> + <DisableProjectBuild Condition="'$(Platform)' == 'arm64'">true</DisableProjectBuild> + </PropertyGroup> + <!-- Default configurations to help VS understand the configurations --> + <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x64'"> + </PropertyGroup> + <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x64'"> + </PropertyGroup> + <PropertyGroup> + <CopyDebugCRTDllsToOutputDirectory>true</CopyDebugCRTDllsToOutputDirectory> + </PropertyGroup> + <ItemGroup> + <CodeAnalysisDependentAssemblyPaths Condition=" '$(VS100COMNTOOLS)' != '' " Include="$(VS100COMNTOOLS)..\IDE\PrivateAssemblies"> + <Visible>False</Visible> + </CodeAnalysisDependentAssemblyPaths> + </ItemGroup> + <ItemGroup> + <Compile Include="FixupCallsHostWhenLoaded.cs" /> + </ItemGroup> + <ItemGroup> + <ProjectReference Include="../IjwNativeDll/CMakeLists.txt" /> + <ProjectReference Include="../ijwhostmock/CMakeLists.txt" /> + </ItemGroup> + <Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), dir.targets))\dir.targets" /> +</Project> diff --git a/tests/src/Interop/IJW/NativeCallingManaged/IjwNativeCallingManagedDll/CMakeLists.txt b/tests/src/Interop/IJW/IjwNativeCallingManagedDll/CMakeLists.txt index c8b0edcb83..c8b0edcb83 100644 --- a/tests/src/Interop/IJW/NativeCallingManaged/IjwNativeCallingManagedDll/CMakeLists.txt +++ b/tests/src/Interop/IJW/IjwNativeCallingManagedDll/CMakeLists.txt diff --git a/tests/src/Interop/IJW/NativeCallingManaged/IjwNativeCallingManagedDll/IjwNativeCallingManagedDll.cpp b/tests/src/Interop/IJW/IjwNativeCallingManagedDll/IjwNativeCallingManagedDll.cpp index 9ba7e3ac0c..d672f3c0ab 100644 --- a/tests/src/Interop/IJW/NativeCallingManaged/IjwNativeCallingManagedDll/IjwNativeCallingManagedDll.cpp +++ b/tests/src/Interop/IJW/IjwNativeCallingManagedDll/IjwNativeCallingManagedDll.cpp @@ -1,12 +1,10 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +#include "platformdefines.h" #pragma managed -int ManagedCallee() -{ - return 100; -} +int ManagedCallee(); #pragma unmanaged int NativeFunction() @@ -14,12 +12,34 @@ int NativeFunction() return ManagedCallee(); } +extern "C" DLL_EXPORT int __cdecl NativeEntryPoint() +{ + return NativeFunction(); +} + #pragma managed public ref class TestClass { +private: + static int s_valueToReturn = 100; public: int ManagedEntryPoint() { return NativeFunction(); } + + static void ChangeReturnedValue(int i) + { + s_valueToReturn = i; + } + + static int GetReturnValue() + { + return s_valueToReturn; + } }; + +int ManagedCallee() +{ + return TestClass::GetReturnValue(); +} diff --git a/tests/src/Interop/IJW/ManagedCallingNative/IjwNativeDll/CMakeLists.txt b/tests/src/Interop/IJW/IjwNativeDll/CMakeLists.txt index 612e6aa17f..612e6aa17f 100644 --- a/tests/src/Interop/IJW/ManagedCallingNative/IjwNativeDll/CMakeLists.txt +++ b/tests/src/Interop/IJW/IjwNativeDll/CMakeLists.txt diff --git a/tests/src/Interop/IJW/ManagedCallingNative/IjwNativeDll/IjwNativeDll.cpp b/tests/src/Interop/IJW/IjwNativeDll/IjwNativeDll.cpp index cb25b445a4..cb25b445a4 100644 --- a/tests/src/Interop/IJW/ManagedCallingNative/IjwNativeDll/IjwNativeDll.cpp +++ b/tests/src/Interop/IJW/IjwNativeDll/IjwNativeDll.cpp diff --git a/tests/src/Interop/IJW/LoadIjwFromModuleHandle/LoadIjwFromModuleHandle.cs b/tests/src/Interop/IJW/LoadIjwFromModuleHandle/LoadIjwFromModuleHandle.cs new file mode 100644 index 0000000000..e44d534158 --- /dev/null +++ b/tests/src/Interop/IJW/LoadIjwFromModuleHandle/LoadIjwFromModuleHandle.cs @@ -0,0 +1,88 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.IO; +using System.Reflection; +using System.Runtime.InteropServices; +using Internal.Runtime.InteropServices; +using TestLibrary; + +using Console = Internal.Console; + +namespace LoadIjwFromModuleHandle +{ + class LoadIjwFromModuleHandle + { + unsafe static int Main(string[] args) + { + // Disable running on Windows 7 until IJW activation work is complete. + if(Environment.OSVersion.Platform != PlatformID.Win32NT || (Environment.OSVersion.Version.Major == 6 && Environment.OSVersion.Version.Minor == 1)) + { + return 100; + } + + try + { + HostPolicyMock.Initialize(Environment.CurrentDirectory, null); + + // Load our fake mscoree to prevent desktop from loading. + NativeLibrary.Load(Path.Combine(Environment.CurrentDirectory, "mscoree.dll")); + + Console.WriteLine("Verify that we can load an IJW assembly from native code."); + string ijwModulePath = Path.Combine(Environment.CurrentDirectory, "IjwNativeCallingManagedDll.dll"); + IntPtr ijwNativeHandle = NativeLibrary.Load(ijwModulePath); + + using (HostPolicyMock.Mock_corehost_resolve_component_dependencies( + 0, + ijwModulePath, + string.Empty, + string.Empty)) + fixed (char* path = ijwModulePath) + { + InMemoryAssemblyLoader.LoadInMemoryAssembly(ijwNativeHandle, (IntPtr)path); + } + + NativeEntryPointDelegate nativeEntryPoint = Marshal.GetDelegateForFunctionPointer<NativeEntryPointDelegate>(NativeLibrary.GetExport(ijwNativeHandle, "NativeEntryPoint")); + + Assert.AreEqual(100, nativeEntryPoint()); + + Console.WriteLine("Test calls from managed to native to managed when an IJW assembly was first loaded via native."); + + Assembly ijwAssemblyManaged = Assembly.Load("IjwNativeCallingManagedDll"); + Type testType = ijwAssemblyManaged.GetType("TestClass"); + object testInstance = Activator.CreateInstance(testType); + MethodInfo testMethod = testType.GetMethod("ManagedEntryPoint"); + + Assert.AreEqual(100, (int)testMethod.Invoke(testInstance, null)); + + MethodInfo changeReturnedValueMethod = testType.GetMethod("ChangeReturnedValue"); + MethodInfo getReturnValueMethod = testType.GetMethod("GetReturnValue"); + + int newValue = 42; + changeReturnedValueMethod.Invoke(null, new object[] { newValue }); + + Assert.AreEqual(newValue, (int)getReturnValueMethod.Invoke(null, null)); + + // Native images are only loaded into memory once. As a result, the stubs in the vtfixup table + // will always point to JIT stubs that exist in the first ALC that the module was loaded into. + // As a result, if an IJW module is loaded into two different ALCs, or if the module is + // first loaded via a native call and then loaded via the managed loader, the call stack can change ALCs when + // jumping from managed->native->managed code within the IJW module. + Assert.AreEqual(100, (int)testMethod.Invoke(testInstance, null)); + return 100; + } + catch (Exception ex) + { + Console.WriteLine(ex.ToString()); + + return 101; + } + } + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + delegate int NativeEntryPointDelegate(); + + } +} diff --git a/tests/src/Interop/IJW/LoadIjwFromModuleHandle/LoadIjwFromModuleHandle.csproj b/tests/src/Interop/IJW/LoadIjwFromModuleHandle/LoadIjwFromModuleHandle.csproj new file mode 100644 index 0000000000..6228f2c8d7 --- /dev/null +++ b/tests/src/Interop/IJW/LoadIjwFromModuleHandle/LoadIjwFromModuleHandle.csproj @@ -0,0 +1,47 @@ +<?xml version="1.0" encoding="utf-8"?> +<Project ToolsVersion="12.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> + <Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), dir.props))\dir.props" /> + <PropertyGroup> + <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration> + <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform> + <AssemblyName>LoadIjwFromModuleHandle</AssemblyName> + <SchemaVersion>2.0</SchemaVersion> + <ProjectGuid>{8B76A001-5654-4F11-A80B-EF12644EAD3D}</ProjectGuid> + <OutputType>Exe</OutputType> + <ProjectTypeGuids>{786C830F-07A1-408B-BD7F-6EE04809D6DB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids> + <SolutionDir Condition="$(SolutionDir) == '' Or $(SolutionDir) == '*Undefined*'">..\..\</SolutionDir> + <ReferenceSystemPrivateCoreLib>true</ReferenceSystemPrivateCoreLib> + <AllowUnsafeBlocks>true</AllowUnsafeBlocks> + + <!-- IJW is Windows-only --> + <!-- Test unsupported outside of windows --> + <TestUnsupportedOutsideWindows>true</TestUnsupportedOutsideWindows> + <DisableProjectBuild Condition="'$(TargetsUnix)' == 'true'">true</DisableProjectBuild> + + <!-- IJW is not supported on ARM64 --> + <DisableProjectBuild Condition="'$(Platform)' == 'arm64'">true</DisableProjectBuild> + </PropertyGroup> + <!-- Default configurations to help VS understand the configurations --> + <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x64'"> + </PropertyGroup> + <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x64'"> + </PropertyGroup> + <PropertyGroup> + <CopyDebugCRTDllsToOutputDirectory>true</CopyDebugCRTDllsToOutputDirectory> + </PropertyGroup> + <ItemGroup> + <CodeAnalysisDependentAssemblyPaths Condition=" '$(VS100COMNTOOLS)' != '' " Include="$(VS100COMNTOOLS)..\IDE\PrivateAssemblies"> + <Visible>False</Visible> + </CodeAnalysisDependentAssemblyPaths> + </ItemGroup> + <ItemGroup> + <Compile Include="LoadIjwFromModuleHandle.cs" /> + </ItemGroup> + <ItemGroup> + <ProjectReference Include="../IjwNativeCallingManagedDll/CMakeLists.txt" /> + <ProjectReference Include="../ijwhostmock/CMakeLists.txt" /> + <ProjectReference Include="../../../Common/hostpolicymock/CMakeLists.txt" /> + </ItemGroup> + <Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), Interop.settings.targets))\Interop.settings.targets" /> + <Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), dir.targets))\dir.targets" /> +</Project> diff --git a/tests/src/Interop/IJW/ManagedCallingNative/ManagedCallingNative.csproj b/tests/src/Interop/IJW/ManagedCallingNative/ManagedCallingNative.csproj index 55b632155c..c8c1ccae96 100644 --- a/tests/src/Interop/IJW/ManagedCallingNative/ManagedCallingNative.csproj +++ b/tests/src/Interop/IJW/ManagedCallingNative/ManagedCallingNative.csproj @@ -37,8 +37,8 @@ <Compile Include="ManagedCallingNative.cs" /> </ItemGroup> <ItemGroup> - <ProjectReference Include="IjwNativeDll/CMakeLists.txt" /> - <ProjectReference Include="../FakeMscoree/CMakeLists.txt" /> + <ProjectReference Include="../IjwNativeDll/CMakeLists.txt" /> + <ProjectReference Include="../ijwhostmock/CMakeLists.txt" /> <ProjectReference Include="../../../Common/CoreCLRTestLibrary/CoreCLRTestLibrary.csproj" /> </ItemGroup> <Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), dir.targets))\dir.targets" /> diff --git a/tests/src/Interop/IJW/NativeCallingManaged/NativeCallingManaged.csproj b/tests/src/Interop/IJW/NativeCallingManaged/NativeCallingManaged.csproj index 9decb48bf8..d40837d7c6 100644 --- a/tests/src/Interop/IJW/NativeCallingManaged/NativeCallingManaged.csproj +++ b/tests/src/Interop/IJW/NativeCallingManaged/NativeCallingManaged.csproj @@ -37,8 +37,8 @@ <Compile Include="NativeCallingManaged.cs" /> </ItemGroup> <ItemGroup> - <ProjectReference Include="IjwNativeCallingManagedDll/CMakeLists.txt" /> - <ProjectReference Include="../FakeMscoree/CMakeLists.txt" /> + <ProjectReference Include="../IjwNativeCallingManagedDll/CMakeLists.txt" /> + <ProjectReference Include="../ijwhostmock/CMakeLists.txt" /> <ProjectReference Include="../../../Common/CoreCLRTestLibrary/CoreCLRTestLibrary.csproj" /> </ItemGroup> <Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), dir.targets))\dir.targets" /> diff --git a/tests/src/Interop/IJW/FakeMscoree/CMakeLists.txt b/tests/src/Interop/IJW/ijwhostmock/CMakeLists.txt index cfb6ee2ab5..bdcf920dc1 100644 --- a/tests/src/Interop/IJW/FakeMscoree/CMakeLists.txt +++ b/tests/src/Interop/IJW/ijwhostmock/CMakeLists.txt @@ -1,7 +1,7 @@ cmake_minimum_required (VERSION 2.6) project (mscoree) include_directories( ${INC_PLATFORM_DIR} ) -set(SOURCES mscoree.cpp mscoree.def) +set(SOURCES mscoree.cpp) # add the shared library add_library (mscoree SHARED ${SOURCES}) diff --git a/tests/src/Interop/IJW/ijwhostmock/mscoree.cpp b/tests/src/Interop/IJW/ijwhostmock/mscoree.cpp new file mode 100644 index 0000000000..7c398cb1a3 --- /dev/null +++ b/tests/src/Interop/IJW/ijwhostmock/mscoree.cpp @@ -0,0 +1,34 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +#include <windows.h> +#include <xplatform.h> +#include <set> + +std::set<HINSTANCE> g_modulesQueried = {}; + +#if defined _X86_ +// We need to use a double-underscore here because the VC linker drops the first underscore +// to help people who are exporting cdecl functions to easily export the right thing. +#pragma comment(linker, "/export:__CorDllMain=__CorDllMain@12") +#pragma comment(linker, "/export:GetTokenForVTableEntry=_GetTokenForVTableEntry@8") +#endif + +// Entry-point that coreclr looks for. +extern "C" DLL_EXPORT INT32 STDMETHODCALLTYPE GetTokenForVTableEntry(HINSTANCE hInst, BYTE **ppVTEntry) +{ + g_modulesQueried.emplace(hInst); + return (INT32)(UINT_PTR)*ppVTEntry; +} + +extern "C" DLL_EXPORT BOOL __cdecl WasModuleVTableQueried(HINSTANCE hInst) +{ + return g_modulesQueried.find(hInst) != g_modulesQueried.end() ? TRUE : FALSE; +} + +// Entrypoint jumped to by IJW dlls when their dllmain is called +extern "C" DLL_EXPORT BOOL WINAPI _CorDllMain(HINSTANCE hInst, DWORD dwReason, LPVOID lpReserved) +{ + return TRUE; +} |