While a collectible AssemblyLoadContext is being unloaded, calls to GetAppDomainStaticAddress will cause an access violation in the pLoaderAllocator->GetHandleValueFastCannotFailType2call of the GET_DYNAMICENTRY_GCSTATICS_BASEPOINTER macro. GetHandleValueFastCannotFailType2 retrieves an object reference from m_hLoaderAllocatorObjectHandle and de-references it. But the LoadAllocator object has already been cleared at this time, and an access violation will occur.
I have tried to avoid calling GetAppDomainStaticAddress on modules being unloaded, but I have not found a way to detect this. ICorProfilerInfo3::GetAppDomainsContainingModule still returns the module while it's being unloaded. The internal method IsClassOfMethodTableInited also returns true even though the loader allocator is no longer available.
The easiest fix for this is probably that GET_DYNAMICENTRY_GCSTATICS_BASEPOINTER use GetHandleValueFastPhase2 instead of GetHandleValueFastCannotFailType2 (unless there is some performance implication in other parts of the code). NULL handling already seems to be in place, at least for the GetAppDomainStaticAddress case.
The issue can be reproduced using this program:
using System;
using System.IO;
using System.Runtime.Loader;
namespace LoadContextTestApp
{
class Program
{
static void Main(string[] args)
{
LoadCollectibleAssembly();
Console.WriteLine("Collect snapshot in profiler (ForceGC(), ForceGC(), GetAppDomainStaticAddress() at end of second GC)");
Console.ReadKey(true);
}
private static void LoadCollectibleAssembly()
{
var collectibleContext = new AssemblyLoadContext("Collectible", true);
var asmDir = Path.GetDirectoryName(typeof(Program).Assembly.Location);
var dynamicLibrary = collectibleContext.LoadFromAssemblyPath(Path.Combine( asmDir, "CollectibleLibrary") );
var testType = dynamicLibrary.GetType("CollectibleLibrary.TestClass");
object instance = Activator.CreateInstance(testType);
Console.WriteLine(instance.GetHashCode());
}
}
}
The TestClass can probably be any class with a static field. I'm using this class:
using System;
using System.Collections.Generic;
namespace CollectibleLibrary
{
public class TestClass : MarshalByRefObject
{
static string staticString = "A static string";
string[] instanceStrings;
static int s_count;
static List<TestClass> instances = new List<TestClass>();
public TestClass()
{
instanceStrings = new string[100];
for (int i = 0; i < instanceStrings.Length; i++)
{
instanceStrings[i] = staticString + (++s_count);
}
Console.WriteLine("Class1 constructed");
instances.Add(this);
}
}
}
To trigger the access violation the test program should be run under a profiler and the profiler should make two calls to ForceGC, and call GetAppDomainStaticAddress at the end of the second GC (while test program is waiting at ReadKey).
While a collectible
AssemblyLoadContextis being unloaded, calls toGetAppDomainStaticAddresswill cause an access violation in thepLoaderAllocator->GetHandleValueFastCannotFailType2call of theGET_DYNAMICENTRY_GCSTATICS_BASEPOINTERmacro.GetHandleValueFastCannotFailType2retrieves an object reference fromm_hLoaderAllocatorObjectHandleand de-references it. But the LoadAllocator object has already been cleared at this time, and an access violation will occur.I have tried to avoid calling
GetAppDomainStaticAddresson modules being unloaded, but I have not found a way to detect this.ICorProfilerInfo3::GetAppDomainsContainingModulestill returns the module while it's being unloaded. The internal methodIsClassOfMethodTableInitedalso returns true even though the loader allocator is no longer available.The easiest fix for this is probably that
GET_DYNAMICENTRY_GCSTATICS_BASEPOINTERuseGetHandleValueFastPhase2instead ofGetHandleValueFastCannotFailType2(unless there is some performance implication in other parts of the code). NULL handling already seems to be in place, at least for theGetAppDomainStaticAddresscase.The issue can be reproduced using this program:
The TestClass can probably be any class with a static field. I'm using this class:
To trigger the access violation the test program should be run under a profiler and the profiler should make two calls to ForceGC, and call GetAppDomainStaticAddress at the end of the second GC (while test program is waiting at ReadKey).