ICorProfilerCallback::ClassUnloadStarted 未调用泛型类,即使该类已卸载



我目前正在调试我公司的 CLR 探查器(超过 4.7.3282.0,.NET Framework 4.7.2 ASP.NET),并看到 CLR 卸载泛型类,但没有调用 ClassUnloadStarted 回调的情况。

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

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

  • 为什么我的(泛型)类不调用 ClassUnloadStart?
  • 这是 CLR 的预期边缘情况行为,还是可能是 CLR/分析 API 错误?

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

详细场景:

这是有问题的类(IComparable(T)T=ClassFromModuleFoo):

System/IComparable`1<ClassFromModuleFoo>

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

  1. 加载了 mscorlib 的类System/IComparable'1(ClassFromModuleFoo)
  2. 紧接着,模块 Foo 的类ClassFromModuleFoo被加载到程序集 #1 中。
  3. 模块 Foo 完成以加载到程序集 #1 中。
  4. 然后,模块Foo再次加载到不同的程序集#2中。
  5. 再次加载IComparableClassFromModuleFoo,这次在程序集 #2 中加载。现在每个类有两个实例:一个在程序集 #1 中加载的 Foo 中,另一个在程序集 #2 中加载的 Foo 中。
  6. 模块 Foo 开始从程序集 #1 卸载。
  7. ClassUnloadStarted回调是为程序集 #1 中的ClassFromModuleFoo调用的。
  8. 模块 Foo 已完成从组件 #1 卸载。
  9. 以后不会为
  10. 程序集 #1 的System/IComparable'1(ClassFromModuleFoo)调用ClassUnloadStarted(即使其模块已卸载并且其 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(而且我不太确定它通常是否支持按需程序集卸载)。

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

从达夫梅森的解释中:

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

Plugin.MyGenList1:未绑定的泛型类型 Plugin.MyGenList1 :绑定到规范类型的泛型类型(使用 对于普通引用)插件.MyGenList1:通用 类型绑定到系统字符串。对于 ClassLoadStarted,我们有逻辑 明确排除未绑定的泛型类型(即 Plugin.MyGenList1) 从显示到分析器在 类加载器::通知

这意味着您的 ClassLoadStarted 只为您提供 规范实例和字符串实例。这似乎是正确的做法, 因为作为探查器,您只关心绑定泛型类型和 对于未绑定的人来说,没有什么有趣的。

问题是我们对 类卸载已启动。该回调发生在 EEClass::D estruct 中,并且 仅在非泛型类型、未绑定泛型类型、 和规范泛型类型。非规范泛型类型(即 Plugin.MyGenList1 ) 被跳过。

相关内容

  • 没有找到相关文章