6

我目前正在调试我公司的 CLR 分析器(通过 ASP.NET 4.7.3282.0、.NET 框架 4.7.2),并看到 CLR 卸载泛型类但未调用ClassUnloadStarted回调的场景。

简而言之,我们的分析器根据 ClassID 跟踪加载的类,遵循ClassLoadStartedClassLoadFinishedClassUnloadStarted回调。在某些时候,类会被卸载(连同其相关模块),但不会为相关 ClassID 调用ClassUnloadStarted回调。因此,我们留下了一个停顿的 ClassID,认为该类仍在加载。稍后,当我们尝试查询该 ClassID 时,CLR 不出所料地崩溃了(因为它现在指向垃圾内存)。

我的问题,考虑到下面的详细情况:

  • 为什么我的(通用)类没有调用 ClassUnloadStarted?
  • 这是 CLR 的预期边缘情况行为,还是可能是 CLR/Profiling API 错误?

我找不到任何关于此行为的文档或推理,特别是ClassUnloadStarted没有被调用。我在 CoreCLR 代码中也找不到任何提示。提前感谢您的帮助!

详细场景:

这是有问题的课程(IComparable(T)with T=ClassFromModuleFoo):

System/IComparable`1<ClassFromModuleFoo>

在应用程序运行时,问题在某些模块被卸载后出现。
这是基于添加的调试打印的确切加载/卸载回调流程:

  1. mscorlib的类System/IComparable'1(ClassFromModuleFoo)已加载。
  2. 紧接着,ClassFromModuleFoo模块 Foo 的类被加载到程序集#1 中。
  3. 模块 Foo 完成加载到程序集 #1。
  4. 然后,模块 Foo 再次加载到不同的程序集#2 中。
  5. IComparable和再次加载,这次ClassFromModuleFoo是在程序集 #2 中。现在每个类有两个实例:一个在 Foo 中加载到程序集 #1 中,另一个在 Foo 中加载在程序集 #2 中。
  6. 模块 Foo 开始从程序集 #1 中卸载。
  7. ClassUnloadStartedClassFromModuleFoo在程序集 #1 中调用回调。
  8. 模块 Foo 已完成从程序集 #1 中卸载。
  9. ClassUnloadStarted以后的任何时候都不会调用System/IComparable'1(ClassFromModuleFoo)程序集#1(即使它的模块已卸载并且它的 ClassID 指向现在被颠簸的内存)。

一些附加信息:

  • 最新的 .NET 框架版本 4.8 预览版也重现了该问题。
  • 我通过添加COR_PRF_DISABLE_ALL_NGEN_IMAGES到探查器事件掩码来禁用本机图像,认为它可能会影响 ClassLoad* 回调,但它没有任何区别。我验证mscorlib.dll了确实加载而不是其本机图像。

编辑:

感谢我非常聪明的同事,我能够通过一个小示例项目重现该问题,该项目通过加载和卸载 AppDomain 来模拟这种情况。这里是:
https ://github.com/shaharv/dotnet/tree/master/testers/module-load-unload

测试中的此类发生崩溃,该类已卸载,并且 CLR 未调用卸载回调:

Loop/MyGenList`1<System/String>

这是相关代码,加载和卸载了几次:

namespace Loop
{
    public class MyGenList<T>
    {
        public List<T> _tList;

        public MyGenList(List<T> tList)
        {
            _tList = tList;
        }
    }

    class MyGenericTest
    {
        public void TestFunc()
        {
            MyGenList<String> genList = new MyGenList<String>(new List<string> { "A", "B", "C" });

            try
            {
                throw new Exception();
            }
            catch (Exception)
            {

            }
        }
    }
}

在某些时候,探查器在尝试查询该类的 ClassID 时崩溃 - 认为它仍然有效,因为未调用卸载回调。

在旁注中,我尝试将此示例移植到 .NET Core 以进行进一步调查,但无法弄清楚如何,因为 .NET Core 不支持辅助 AppDomain(而且我不太确定它是否支持按需组装一般卸货)。

4

1 回答 1

1

在 .Net Core 中实现它之后(3.0 之前不支持卸载),我们设法复制了它(感谢 valiano!)。coreclr 团队确认这是一个错误(https://github.com/dotnet/coreclr/issues/26126)。

从davmason的解释:

涉及三种不同的类型,每个回调只给你两个(但一组不同的两个)。

Plugin.MyGenList1:未绑定的泛型类型 Plugin.MyGenList1:绑定到规范类型的泛型类型(用于正常引用) Plugin.MyGenList1:绑定到 System.String 的泛型类型。对于 ClassLoadStarted,我们有专门排除未绑定的泛型类型(即 Plugin.MyGenList1)在 ClassLoader::Notify 中显示给探查器的逻辑

这意味着您 ClassLoadStarted 只为您提供规范和字符串实例的回调。这似乎是正确的做法,因为作为分析器,您只关心绑定的泛型类型,而对未绑定的泛型类型没有任何兴趣。

问题是我们为 ClassUnloadStarted 做了一组不同的过滤。该回调发生在 EEClass::Destruct 内部,并且 Destruct 仅在非泛型类型、未绑定泛型类型和规范泛型类型上调用。跳过非规范泛型类型(即 Plugin.MyGenList1 )。

于 2019-08-13T05:58:54.243 回答