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

Commit ea10aac

Browse files
authored
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
1 parent 42c234f commit ea10aac

29 files changed

+532
-76
lines changed

src/System.Private.CoreLib/System.Private.CoreLib.csproj

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,7 @@
119119
<!-- Sources -->
120120
<ItemGroup>
121121
<Compile Include="$(BclSourcesRoot)\Internal\Console.cs" />
122+
<Compile Include="$(BclSourcesRoot)\Internal\Runtime\InteropServices\IsolatedComponentLoadContext.cs" />
122123
<Compile Include="$(BclSourcesRoot)\Microsoft\Win32\UnsafeNativeMethods.cs" />
123124
<Compile Include="$(BclSourcesRoot)\System\__Canon.cs" />
124125
<Compile Include="$(BclSourcesRoot)\System\AppContext.CoreCLR.cs" />
@@ -365,6 +366,7 @@
365366
<Compile Include="$(BclSourcesRoot)\System\Threading\ClrThreadPoolBoundHandle.Unix.cs" />
366367
</ItemGroup>
367368
<ItemGroup Condition="'$(TargetsWindows)' == 'true'">
369+
<Compile Include="$(BclSourcesRoot)\Internal\Runtime\InteropServices\InMemoryAssemblyLoader.cs" />
368370
<Compile Include="$(BclSourcesRoot)\System\DateTime.Windows.cs" />
369371
<Compile Include="$(BclSourcesRoot)\Interop\Windows\OleAut32\Interop.VariantClear.cs" />
370372
<Compile Include="$(BclSourcesRoot)\System\ApplicationModel.Windows.cs" />

src/System.Private.CoreLib/src/Internal/Runtime/InteropServices/ComActivator.cs

Lines changed: 1 addition & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -215,46 +215,14 @@ private static AssemblyLoadContext GetALC(string assemblyPath)
215215
{
216216
if (!s_AssemblyLoadContexts.TryGetValue(assemblyPath, out alc))
217217
{
218-
alc = new ComServerLoadContext(assemblyPath);
218+
alc = new IsolatedComponentLoadContext(assemblyPath);
219219
s_AssemblyLoadContexts.Add(assemblyPath, alc);
220220
}
221221
}
222222

223223
return alc;
224224
}
225225

226-
private class ComServerLoadContext : AssemblyLoadContext
227-
{
228-
private readonly AssemblyDependencyResolver _resolver;
229-
230-
public ComServerLoadContext(string comServerAssemblyPath)
231-
{
232-
_resolver = new AssemblyDependencyResolver(comServerAssemblyPath);
233-
}
234-
235-
protected override Assembly Load(AssemblyName assemblyName)
236-
{
237-
string assemblyPath = _resolver.ResolveAssemblyToPath(assemblyName);
238-
if (assemblyPath != null)
239-
{
240-
return LoadFromAssemblyPath(assemblyPath);
241-
}
242-
243-
return null;
244-
}
245-
246-
protected override IntPtr LoadUnmanagedDll(string unmanagedDllName)
247-
{
248-
string libraryPath = _resolver.ResolveUnmanagedDllToPath(unmanagedDllName);
249-
if (libraryPath != null)
250-
{
251-
return LoadUnmanagedDllFromPath(libraryPath);
252-
}
253-
254-
return IntPtr.Zero;
255-
}
256-
}
257-
258226
[ComVisible(true)]
259227
private class BasicClassFactory : IClassFactory
260228
{
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
// See the LICENSE file in the project root for more information.
4+
5+
using System;
6+
using System.Diagnostics;
7+
using System.IO;
8+
using System.Reflection;
9+
using System.Runtime.InteropServices;
10+
using System.Runtime.Loader;
11+
12+
namespace Internal.Runtime.InteropServices
13+
{
14+
/// <summary>
15+
/// This class enables the .NET IJW host to load an in-memory module as a .NET assembly
16+
/// </summary>
17+
public static class InMemoryAssemblyLoader
18+
{
19+
/// <summary>
20+
/// Loads into an isolated AssemblyLoadContext an assembly that has already been loaded into memory by the OS loader as a native module.
21+
/// </summary>
22+
/// <param name="moduleHandle">The native module handle for the assembly.</param>
23+
/// <param name="assemblyPath">The path to the assembly (as a pointer to a UTF-16 C string).</param>
24+
public static unsafe void LoadInMemoryAssembly(IntPtr moduleHandle, IntPtr assemblyPath)
25+
{
26+
// We don't cache the ALCs here since each IJW assembly will call this method at most once
27+
// (the load process rewrites the stubs that call here to call the actual methods they're supposed to)
28+
AssemblyLoadContext context = new IsolatedComponentLoadContext(Marshal.PtrToStringUni(assemblyPath));
29+
context.LoadFromInMemoryModule(moduleHandle);
30+
}
31+
}
32+
}
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
// See the LICENSE file in the project root for more information.
4+
5+
using System;
6+
using System.Reflection;
7+
using System.Runtime.Loader;
8+
9+
namespace Internal.Runtime.InteropServices
10+
{
11+
/// <summary>
12+
/// An <see cref="IsolatedComponentLoadContext" /> is an AssemblyLoadContext that can be used to isolate components such as COM components
13+
/// or IJW components loaded from native. It provides a load context that uses an <see cref="AssemblyDependencyResolver" /> to resolve the component's
14+
/// dependencies within the ALC and not pollute the default ALC.
15+
///</summary>
16+
internal class IsolatedComponentLoadContext : AssemblyLoadContext
17+
{
18+
private readonly AssemblyDependencyResolver _resolver;
19+
20+
public IsolatedComponentLoadContext(string componentAssemblyPath)
21+
{
22+
_resolver = new AssemblyDependencyResolver(componentAssemblyPath);
23+
}
24+
25+
protected override Assembly Load(AssemblyName assemblyName)
26+
{
27+
string assemblyPath = _resolver.ResolveAssemblyToPath(assemblyName);
28+
if (assemblyPath != null)
29+
{
30+
return LoadFromAssemblyPath(assemblyPath);
31+
}
32+
33+
return null;
34+
}
35+
36+
protected override IntPtr LoadUnmanagedDll(string unmanagedDllName)
37+
{
38+
string libraryPath = _resolver.ResolveUnmanagedDllToPath(unmanagedDllName);
39+
if (libraryPath != null)
40+
{
41+
return LoadUnmanagedDllFromPath(libraryPath);
42+
}
43+
44+
return IntPtr.Zero;
45+
}
46+
}
47+
}

src/System.Private.CoreLib/src/System/Runtime/Loader/AssemblyLoadContext.CoreCLR.cs

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,34 @@ internal unsafe Assembly InternalLoad(ReadOnlySpan<byte> arrAssembly, ReadOnlySp
5252

5353
return loadedAssembly;
5454
}
55+
56+
#if !FEATURE_PAL
57+
[DllImport(JitHelpers.QCall, CharSet = CharSet.Unicode)]
58+
private static extern IntPtr LoadFromInMemoryModuleInternal(IntPtr ptrNativeAssemblyLoadContext, IntPtr hModule, ObjectHandleOnStack retAssembly);
59+
60+
61+
/// <summary>
62+
/// Load a module that has already been loaded into memory by the OS loader as a .NET assembly.
63+
/// </summary>
64+
internal Assembly LoadFromInMemoryModule(IntPtr moduleHandle)
65+
{
66+
if (moduleHandle == IntPtr.Zero)
67+
{
68+
throw new ArgumentNullException(nameof(moduleHandle));
69+
}
70+
lock (_unloadLock)
71+
{
72+
VerifyIsAlive();
73+
74+
RuntimeAssembly loadedAssembly = null;
75+
LoadFromInMemoryModuleInternal(
76+
_nativeAssemblyLoadContext,
77+
moduleHandle,
78+
JitHelpers.GetObjectHandleOnStack(ref loadedAssembly));
79+
return loadedAssembly;
80+
}
81+
}
82+
#endif
5583

5684
// This method is invoked by the VM when using the host-provided assembly load context
5785
// implementation.

src/vm/appdomain.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4223,8 +4223,8 @@ static const char *fileLoadLevelName[] =
42234223
"LOADLIBRARY", // FILE_LOAD_LOADLIBRARY
42244224
"POST_LOADLIBRARY", // FILE_LOAD_POST_LOADLIBRARY
42254225
"EAGER_FIXUPS", // FILE_LOAD_EAGER_FIXUPS
4226-
"VTABLE FIXUPS", // FILE_LOAD_VTABLE_FIXUPS
42274226
"DELIVER_EVENTS", // FILE_LOAD_DELIVER_EVENTS
4227+
"VTABLE FIXUPS", // FILE_LOAD_VTABLE_FIXUPS
42284228
"LOADED", // FILE_LOADED
42294229
"VERIFY_EXECUTION", // FILE_LOAD_VERIFY_EXECUTION
42304230
"ACTIVE", // FILE_ACTIVE

src/vm/assemblynative.cpp

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -363,6 +363,42 @@ void QCALLTYPE AssemblyNative::LoadFromStream(INT_PTR ptrNativeAssemblyLoadConte
363363
END_QCALL;
364364
}
365365

366+
#ifndef FEATURE_PAL
367+
/*static */
368+
void QCALLTYPE AssemblyNative::LoadFromInMemoryModule(INT_PTR ptrNativeAssemblyLoadContext, INT_PTR hModule, QCall::ObjectHandleOnStack retLoadedAssembly)
369+
{
370+
QCALL_CONTRACT;
371+
372+
BEGIN_QCALL;
373+
374+
// Ensure that the invariants are in place
375+
_ASSERTE(ptrNativeAssemblyLoadContext != NULL);
376+
_ASSERTE(hModule != NULL);
377+
378+
PEImageHolder pILImage(PEImage::LoadImage((HMODULE)hModule));
379+
380+
// Need to verify that this is a valid CLR assembly.
381+
if (!pILImage->HasCorHeader())
382+
ThrowHR(COR_E_BADIMAGEFORMAT, BFA_BAD_IL);
383+
384+
// Get the binder context in which the assembly will be loaded
385+
ICLRPrivBinder *pBinderContext = reinterpret_cast<ICLRPrivBinder*>(ptrNativeAssemblyLoadContext);
386+
387+
// Pass the in memory module as IL in an attempt to bind and load it
388+
Assembly* pLoadedAssembly = AssemblyNative::LoadFromPEImage(pBinderContext, pILImage, NULL);
389+
{
390+
GCX_COOP();
391+
retLoadedAssembly.Set(pLoadedAssembly->GetExposedObject());
392+
}
393+
394+
LOG((LF_CLASSLOADER,
395+
LL_INFO100,
396+
"\tLoaded assembly from pre-loaded native module\n"));
397+
398+
END_QCALL;
399+
}
400+
#endif
401+
366402
void QCALLTYPE AssemblyNative::GetLocation(QCall::AssemblyHandle pAssembly, QCall::StringHandleOnStack retString)
367403
{
368404
QCALL_CONTRACT;

src/vm/assemblynative.hpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,9 @@ class AssemblyNative
120120
static void QCALLTYPE LoadFromPath(INT_PTR ptrNativeAssemblyLoadContext, LPCWSTR pwzILPath, LPCWSTR pwzNIPath, QCall::ObjectHandleOnStack retLoadedAssembly);
121121
static INT_PTR QCALLTYPE InternalLoadUnmanagedDllFromPath(LPCWSTR unmanagedLibraryPath);
122122
static void QCALLTYPE LoadFromStream(INT_PTR ptrNativeAssemblyLoadContext, INT_PTR ptrAssemblyArray, INT32 cbAssemblyArrayLength, INT_PTR ptrSymbolArray, INT32 cbSymbolArrayLength, QCall::ObjectHandleOnStack retLoadedAssembly);
123+
#ifndef FEATURE_PAL
124+
static void QCALLTYPE LoadFromInMemoryModule(INT_PTR ptrNativeAssemblyLoadContext, INT_PTR hModule, QCall::ObjectHandleOnStack retLoadedAssembly);
125+
#endif
123126
static Assembly* LoadFromPEImage(ICLRPrivBinder* pBinderContext, PEImage *pILImage, PEImage *pNIImage);
124127
static INT_PTR QCALLTYPE GetLoadContextForAssembly(QCall::AssemblyHandle pAssembly);
125128

src/vm/ceeload.cpp

Lines changed: 66 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6482,6 +6482,59 @@ void Module::NotifyDebuggerUnload(AppDomain *pDomain)
64826482
}
64836483

64846484
#if !defined(CROSSGEN_COMPILE)
6485+
using GetTokenForVTableEntry_t = mdToken(STDMETHODCALLTYPE*)(HMODULE module, BYTE**ppVTEntry);
6486+
6487+
static HMODULE GetIJWHostForModule(Module* module)
6488+
{
6489+
#if !defined(FEATURE_PAL)
6490+
PEDecoder* pe = module->GetFile()->GetLoadedIL();
6491+
6492+
BYTE* baseAddress = (BYTE*)module->GetFile()->GetIJWBase();
6493+
6494+
IMAGE_IMPORT_DESCRIPTOR* importDescriptor = (IMAGE_IMPORT_DESCRIPTOR*)pe->GetDirectoryData(pe->GetDirectoryEntry(IMAGE_DIRECTORY_ENTRY_IMPORT));
6495+
6496+
for(; importDescriptor->Characteristics != 0; importDescriptor++)
6497+
{
6498+
IMAGE_THUNK_DATA* importNameTable = (IMAGE_THUNK_DATA*)pe->GetRvaData(importDescriptor->OriginalFirstThunk);
6499+
6500+
IMAGE_THUNK_DATA* importAddressTable = (IMAGE_THUNK_DATA*)pe->GetRvaData(importDescriptor->FirstThunk);
6501+
6502+
for (int thunkIndex = 0; importNameTable[thunkIndex].u1.AddressOfData != 0; thunkIndex++)
6503+
{
6504+
// The most significant bit will be set if the entry points to an ordinal.
6505+
if ((importNameTable[thunkIndex].u1.Ordinal & (1LL << (sizeof(importNameTable[thunkIndex].u1.Ordinal) * CHAR_BIT - 1))) == 0)
6506+
{
6507+
IMAGE_IMPORT_BY_NAME* nameImport = (IMAGE_IMPORT_BY_NAME*)(baseAddress + importNameTable[thunkIndex].u1.AddressOfData);
6508+
if (strcmp("_CorDllMain", nameImport->Name) == 0)
6509+
{
6510+
HMODULE ijwHost;
6511+
6512+
if (WszGetModuleHandleEx(
6513+
GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT,
6514+
(LPCWSTR)importAddressTable[thunkIndex].u1.Function,
6515+
&ijwHost))
6516+
{
6517+
return ijwHost;
6518+
}
6519+
6520+
}
6521+
}
6522+
}
6523+
}
6524+
#endif
6525+
return nullptr;
6526+
}
6527+
6528+
static GetTokenForVTableEntry_t GetTokenGetterFromHostModule(HMODULE ijwHost)
6529+
{
6530+
if (ijwHost != nullptr)
6531+
{
6532+
return (GetTokenForVTableEntry_t)GetProcAddress(ijwHost, "GetTokenForVTableEntry");
6533+
}
6534+
6535+
return nullptr;
6536+
}
6537+
64856538
//=================================================================================
64866539
mdToken GetTokenForVTableEntry(HINSTANCE hInst, BYTE **ppVTEntry)
64876540
{
@@ -6504,7 +6557,6 @@ void SetTargetForVTableEntry(HINSTANCE hInst, BYTE **ppVTEntry, BYTE *pTarget)
65046557
DWORD oldProtect;
65056558
if (!ClrVirtualProtect(ppVTEntry, sizeof(BYTE*), PAGE_READWRITE, &oldProtect))
65066559
{
6507-
65086560
// This is very bad. We are not going to be able to update header.
65096561
_ASSERTE(!"SetTargetForVTableEntry(): VirtualProtect() changing IJW thunk vtable to R/W failed.\n");
65106562
ThrowLastError();
@@ -6548,6 +6600,18 @@ void Module::FixupVTables()
65486600
return;
65496601
}
65506602

6603+
// Try getting a callback to the IJW host if it is loaded.
6604+
// The IJW host substitutes in special shims in the vtfixup table
6605+
// so if it is loaded, we need to query it for the tokens that were in the slots.
6606+
// If it is not loaded, then we know that the vtfixup table entries are tokens,
6607+
// so we can resolve them ourselves.
6608+
GetTokenForVTableEntry_t GetTokenForVTableEntryCallback = GetTokenGetterFromHostModule(GetIJWHostForModule(this));
6609+
6610+
if (GetTokenForVTableEntryCallback == nullptr)
6611+
{
6612+
GetTokenForVTableEntryCallback = GetTokenForVTableEntry;
6613+
}
6614+
65516615
HINSTANCE hInstThis = GetFile()->GetIJWBase();
65526616

65536617
// <REVISIT_TODO>@todo: workaround!</REVISIT_TODO>
@@ -6648,7 +6712,7 @@ void Module::FixupVTables()
66486712
{
66496713
if (pData->IsMethodFixedUp(iFixup, iMethod))
66506714
continue;
6651-
mdToken mdTok = GetTokenForVTableEntry(hInstThis, (BYTE **)(pPointers + iMethod));
6715+
mdToken mdTok = GetTokenForVTableEntryCallback(hInstThis, (BYTE**)(pPointers + iMethod));
66526716
CONSISTENCY_CHECK(mdTok != mdTokenNil);
66536717
rgMethodsToLoad[iCurMethod++].token = mdTok;
66546718
}

src/vm/domainfile.cpp

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -555,14 +555,14 @@ BOOL DomainFile::DoIncrementalLoad(FileLoadLevel level)
555555
EagerFixups();
556556
break;
557557

558-
case FILE_LOAD_VTABLE_FIXUPS:
559-
VtableFixups();
560-
break;
561-
562558
case FILE_LOAD_DELIVER_EVENTS:
563559
DeliverSyncEvents();
564560
break;
565561

562+
case FILE_LOAD_VTABLE_FIXUPS:
563+
VtableFixups();
564+
break;
565+
566566
case FILE_LOADED:
567567
FinishLoad();
568568
break;

src/vm/domainfile.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,8 +45,8 @@ enum FileLoadLevel
4545
FILE_LOAD_LOADLIBRARY,
4646
FILE_LOAD_POST_LOADLIBRARY,
4747
FILE_LOAD_EAGER_FIXUPS,
48-
FILE_LOAD_VTABLE_FIXUPS,
4948
FILE_LOAD_DELIVER_EVENTS,
49+
FILE_LOAD_VTABLE_FIXUPS,
5050
FILE_LOADED, // Loaded by not yet active
5151
FILE_LOAD_VERIFY_EXECUTION,
5252
FILE_ACTIVE // Fully active (constructors run & security checked)

src/vm/ecalllist.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -520,6 +520,9 @@ FCFuncStart(gAssemblyLoadContextFuncs)
520520
QCFuncElement("LoadFromPath", AssemblyNative::LoadFromPath)
521521
QCFuncElement("InternalLoadUnmanagedDllFromPath", AssemblyNative::InternalLoadUnmanagedDllFromPath)
522522
QCFuncElement("LoadFromStream", AssemblyNative::LoadFromStream)
523+
#ifndef FEATURE_PAL
524+
QCFuncElement("LoadFromInMemoryModuleInternal", AssemblyNative::LoadFromInMemoryModule)
525+
#endif
523526
QCFuncElement("GetLoadContextForAssembly", AssemblyNative::GetLoadContextForAssembly)
524527
FCFuncElement("GetLoadedAssemblies", AppDomainNative::GetLoadedAssemblies)
525528
#if defined(FEATURE_MULTICOREJIT)

tests/issues.targets

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -458,6 +458,15 @@
458458
<ExcludeList Include="$(XunitTestBinBase)/JIT/Directed/arglist/vararg/*">
459459
<Issue>Needs triage</Issue>
460460
</ExcludeList>
461+
<ExcludeList Include="$(XunitTestBinBase)/Interop/IJW/CopyConstructorMarshaler/CopyConstructorMarshaler/*">
462+
<Issue>Needs triage</Issue>
463+
</ExcludeList>
464+
<ExcludeList Include="$(XunitTestBinBase)/Interop/IJW/FixupCallsHostWhenLoaded/FixupCallsHostWhenLoaded/*">
465+
<Issue>Needs triage</Issue>
466+
</ExcludeList>
467+
<ExcludeList Include="$(XunitTestBinBase)/Interop/IJW/LoadIjwFromModuleHandle/LoadIjwFromModuleHandle/*">
468+
<Issue>Needs triage</Issue>
469+
</ExcludeList>
461470
<ExcludeList Include="$(XunitTestBinBase)/Interop/IJW/ManagedCallingNative/ManagedCallingNative/*">
462471
<Issue>Needs triage</Issue>
463472
</ExcludeList>

0 commit comments

Comments
 (0)