Skip to content

GetAppDomainStatisAddress causes access violation when called on an module being unloaded #33367

@ASuurkuusk

Description

@ASuurkuusk

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).

Metadata

Metadata

Assignees

No one assigned

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions