From ea10aaccb09fe30f0444b821fd8d90d9257dd402 Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Wed, 20 Mar 2019 22:59:29 -0700 Subject: [PATCH] Runtime changes for IJW Activation (#22636) * Implement loading an assembly from an HMODULE on Windows. * Use the native runtime to execute the main method so I don't have to replicate all of the startup behavior. * If ijwhost is loaded, then call back into ijwhost to resolve tokens for vtable entries. * Refactor our various component loaders (COM and IJW) and have IJW load components into separate ALCs when loaded from native. * Move VTableFixups after DeliverSyncEvents in our incremental load. We need the module to be loaded to at least FILE_LOAD_DELIVER_EVENTS when resolving dependencies loaded via VTableFixups, otherwise they try to load into the default ALC. * Only try to get ijwhost module handle on Windows. * Use defined() instead of ifndef * Fix off-Windows build break and fix unvalid comment. * ComponentLoadContext->IsolatedComponentLoadContext * Only build InMemoryAssemblyLoader when targeting windows. * Add doc comments for IsolatedComponentLoadContext. * Rename qcall. * Add comment for boolean parameter. * Add null check for managed ExecuteMainMethod entrypoint. * Add comments in ceeload.cpp for the ijwhost method resolution. * Add test verifying runtime callback to IJW host. * Add test for InMemoryAssemblyLoader.LoadInMemoryAssembly. * Fix x86 behavior rel. loading symbols by name and stdcall mangling. * Remove exe entrypoints. The executable case for mixed-mode will go through the normal .NET Core app model (no special hosting support needed). * Clean up whitespace * Add test verifying M->N->M ALC switch behavior with IJW. * Resolve the ijwhost module by probing the PE for the IJW assembly instead of hardcoding the name. * Remove static caching in GetTokenGetterFromHostModule. * Disable new IJW tests on Win7. * PR Feedback. * Cleanup extra copy of function. * Use old-style environment checking in LoadIjwFromModuleHandle since we reference S.P.CL directly. * Fix break in rebase * Clean up fake mscoree code. * Only validate that a CLR header exists. The OS verifies everything else. * Make ijw host resolution code static to ceeload.cpp * Use bracketed include for xplatform.h * Update comment on _CorDllMain * PR feedback. * Remove .def file. * PR feedback. * Verify we export correctly on x86 and x64. * Fix path to ijwhostmock for CopyConstructorMarshaler * Disable IJW tests on arm32 nightly. Can't repro the dependency load failures. Fixes #23358 --- .../System.Private.CoreLib.csproj | 2 + .../Runtime/InteropServices/ComActivator.cs | 34 +-------- .../InteropServices/InMemoryAssemblyLoader.cs | 32 ++++++++ .../IsolatedComponentLoadContext.cs | 47 ++++++++++++ .../Runtime/Loader/AssemblyLoadContext.CoreCLR.cs | 28 +++++++ src/vm/appdomain.cpp | 2 +- src/vm/assemblynative.cpp | 36 +++++++++ src/vm/assemblynative.hpp | 3 + src/vm/ceeload.cpp | 68 ++++++++++++++++- src/vm/domainfile.cpp | 8 +- src/vm/domainfile.h | 2 +- src/vm/ecalllist.h | 3 + tests/issues.targets | 9 +++ tests/src/Interop/CMakeLists.txt | 6 +- .../CopyConstructorMarshaler.csproj | 2 +- tests/src/Interop/IJW/FakeMscoree/mscoree.cpp | 19 ----- tests/src/Interop/IJW/FakeMscoree/mscoree.def | 3 - .../FixupCallsHostWhenLoaded.cs | 53 +++++++++++++ .../FixupCallsHostWhenLoaded.csproj | 44 +++++++++++ .../IjwNativeCallingManagedDll/CMakeLists.txt | 0 .../IjwNativeCallingManagedDll.cpp | 28 ++++++- .../IjwNativeDll/CMakeLists.txt | 0 .../IjwNativeDll/IjwNativeDll.cpp | 0 .../LoadIjwFromModuleHandle.cs | 88 ++++++++++++++++++++++ .../LoadIjwFromModuleHandle.csproj | 47 ++++++++++++ .../ManagedCallingNative.csproj | 4 +- .../NativeCallingManaged.csproj | 4 +- .../{FakeMscoree => ijwhostmock}/CMakeLists.txt | 2 +- tests/src/Interop/IJW/ijwhostmock/mscoree.cpp | 34 +++++++++ 29 files changed, 532 insertions(+), 76 deletions(-) create mode 100644 src/System.Private.CoreLib/src/Internal/Runtime/InteropServices/InMemoryAssemblyLoader.cs create mode 100644 src/System.Private.CoreLib/src/Internal/Runtime/InteropServices/IsolatedComponentLoadContext.cs delete mode 100644 tests/src/Interop/IJW/FakeMscoree/mscoree.cpp delete mode 100644 tests/src/Interop/IJW/FakeMscoree/mscoree.def create mode 100644 tests/src/Interop/IJW/FixupCallsHostWhenLoaded/FixupCallsHostWhenLoaded.cs create mode 100644 tests/src/Interop/IJW/FixupCallsHostWhenLoaded/FixupCallsHostWhenLoaded.csproj rename tests/src/Interop/IJW/{NativeCallingManaged => }/IjwNativeCallingManagedDll/CMakeLists.txt (100%) rename tests/src/Interop/IJW/{NativeCallingManaged => }/IjwNativeCallingManagedDll/IjwNativeCallingManagedDll.cpp (53%) rename tests/src/Interop/IJW/{ManagedCallingNative => }/IjwNativeDll/CMakeLists.txt (100%) rename tests/src/Interop/IJW/{ManagedCallingNative => }/IjwNativeDll/IjwNativeDll.cpp (100%) create mode 100644 tests/src/Interop/IJW/LoadIjwFromModuleHandle/LoadIjwFromModuleHandle.cs create mode 100644 tests/src/Interop/IJW/LoadIjwFromModuleHandle/LoadIjwFromModuleHandle.csproj rename tests/src/Interop/IJW/{FakeMscoree => ijwhostmock}/CMakeLists.txt (88%) create mode 100644 tests/src/Interop/IJW/ijwhostmock/mscoree.cpp diff --git a/src/System.Private.CoreLib/System.Private.CoreLib.csproj b/src/System.Private.CoreLib/System.Private.CoreLib.csproj index 0cf6733..b052272 100644 --- a/src/System.Private.CoreLib/System.Private.CoreLib.csproj +++ b/src/System.Private.CoreLib/System.Private.CoreLib.csproj @@ -119,6 +119,7 @@ + @@ -365,6 +366,7 @@ + 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 608d157..d1c6aa9 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 0000000..097f47a --- /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 +{ + /// + /// This class enables the .NET IJW host to load an in-memory module as a .NET assembly + /// + public static class InMemoryAssemblyLoader + { + /// + /// Loads into an isolated AssemblyLoadContext an assembly that has already been loaded into memory by the OS loader as a native module. + /// + /// The native module handle for the assembly. + /// The path to the assembly (as a pointer to a UTF-16 C string). + 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 0000000..d78d7dd --- /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 +{ + /// + /// An 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 to resolve the component's + /// dependencies within the ALC and not pollute the default ALC. + /// + 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 60052d9..b36c382 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); + + + /// + /// Load a module that has already been loaded into memory by the OS loader as a .NET assembly. + /// + 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 9362dd9..bcffb3b 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 30ee95b..ed2ce66 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(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 d506b6e..13db261 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 1fec2c2..b9cf4a7 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(); // @todo: workaround! @@ -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 0fd9c81..5c89767 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 d6917b1..ddc0384 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 7302bb4..c184633 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 a206167..73a2884 100644 --- a/tests/issues.targets +++ b/tests/issues.targets @@ -458,6 +458,15 @@ Needs triage + + Needs triage + + + Needs triage + + + Needs triage + Needs triage diff --git a/tests/src/Interop/CMakeLists.txt b/tests/src/Interop/CMakeLists.txt index 32e4b77..acf4c84 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 34aad53..b1d38a9 100644 --- a/tests/src/Interop/IJW/CopyConstructorMarshaler/CopyConstructorMarshaler.csproj +++ b/tests/src/Interop/IJW/CopyConstructorMarshaler/CopyConstructorMarshaler.csproj @@ -38,7 +38,7 @@ - + diff --git a/tests/src/Interop/IJW/FakeMscoree/mscoree.cpp b/tests/src/Interop/IJW/FakeMscoree/mscoree.cpp deleted file mode 100644 index 99319ec..0000000 --- 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 - -#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 9279b03..0000000 --- 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 0000000..cfc5a9a --- /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(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 0000000..20dc72f --- /dev/null +++ b/tests/src/Interop/IJW/FixupCallsHostWhenLoaded/FixupCallsHostWhenLoaded.csproj @@ -0,0 +1,44 @@ + + + + + + Debug + AnyCPU + FixupCallsHostWhenLoaded + 2.0 + {49D1D482-E783-4CA9-B6BA-A9714BF81036} + Exe + {786C830F-07A1-408B-BD7F-6EE04809D6DB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + ..\..\ + + + + true + true + + + true + + + + + + + + true + + + + False + + + + + + + + + + + diff --git a/tests/src/Interop/IJW/NativeCallingManaged/IjwNativeCallingManagedDll/CMakeLists.txt b/tests/src/Interop/IJW/IjwNativeCallingManagedDll/CMakeLists.txt similarity index 100% rename from tests/src/Interop/IJW/NativeCallingManaged/IjwNativeCallingManagedDll/CMakeLists.txt rename to 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 similarity index 53% rename from tests/src/Interop/IJW/NativeCallingManaged/IjwNativeCallingManagedDll/IjwNativeCallingManagedDll.cpp rename to tests/src/Interop/IJW/IjwNativeCallingManagedDll/IjwNativeCallingManagedDll.cpp index 9ba7e3a..d672f3c 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 similarity index 100% rename from tests/src/Interop/IJW/ManagedCallingNative/IjwNativeDll/CMakeLists.txt rename to 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 similarity index 100% rename from tests/src/Interop/IJW/ManagedCallingNative/IjwNativeDll/IjwNativeDll.cpp rename to 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 0000000..e44d534 --- /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(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 0000000..6228f2c --- /dev/null +++ b/tests/src/Interop/IJW/LoadIjwFromModuleHandle/LoadIjwFromModuleHandle.csproj @@ -0,0 +1,47 @@ + + + + + Debug + AnyCPU + LoadIjwFromModuleHandle + 2.0 + {8B76A001-5654-4F11-A80B-EF12644EAD3D} + Exe + {786C830F-07A1-408B-BD7F-6EE04809D6DB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + ..\..\ + true + true + + + + true + true + + + true + + + + + + + + true + + + + False + + + + + + + + + + + + + diff --git a/tests/src/Interop/IJW/ManagedCallingNative/ManagedCallingNative.csproj b/tests/src/Interop/IJW/ManagedCallingNative/ManagedCallingNative.csproj index 55b6321..c8c1cca 100644 --- a/tests/src/Interop/IJW/ManagedCallingNative/ManagedCallingNative.csproj +++ b/tests/src/Interop/IJW/ManagedCallingNative/ManagedCallingNative.csproj @@ -37,8 +37,8 @@ - - + + diff --git a/tests/src/Interop/IJW/NativeCallingManaged/NativeCallingManaged.csproj b/tests/src/Interop/IJW/NativeCallingManaged/NativeCallingManaged.csproj index 9decb48..d40837d 100644 --- a/tests/src/Interop/IJW/NativeCallingManaged/NativeCallingManaged.csproj +++ b/tests/src/Interop/IJW/NativeCallingManaged/NativeCallingManaged.csproj @@ -37,8 +37,8 @@ - - + + diff --git a/tests/src/Interop/IJW/FakeMscoree/CMakeLists.txt b/tests/src/Interop/IJW/ijwhostmock/CMakeLists.txt similarity index 88% rename from tests/src/Interop/IJW/FakeMscoree/CMakeLists.txt rename to tests/src/Interop/IJW/ijwhostmock/CMakeLists.txt index cfb6ee2..bdcf920 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 0000000..7c398cb --- /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 +#include +#include + +std::set 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; +} -- 2.7.4