即使类已卸载,也不会为一般类调用ICorProfilerCallback :: ClassUnloadStarted

时间:2019-02-26 11:00:23

标签: .net generics profiling clr clr-profiling-api

我目前正在调试公司的CLR分析器(通过ASP.NET 4.7.3282.0,.NET Framework 4.7.2),并看到CLR卸载通用类但使用ClassUnloadStarted回调的情况。不被调用。

简而言之,我们的探查器会根据 ClassLoadStarted ClassLoadFinished ClassUnloadStarted 回调跟踪基于ClassID加载的类。在某个时候,该类(及其相关模块)将被卸载,但是相关类ID不会调用 ClassUnloadStarted 回调。因此,我们剩下一个停滞的ClassID,以为该类仍在加载。稍后,当我们尝试查询该ClassID时,CLR意外崩溃(因为它现在指向垃圾内存)。

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

  • 为什么不为我的(通用)类调用ClassUnloadStarted?
  • 这是CLR的预期的极端情况行为,还是CLR /分析API错误?

我找不到关于此行为的任何文档或理由,尤其是未调用 ClassUnloadStarted 的行为。我也没有在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中。现在每个类有两个实例:一个在Foo中加载到程序集#1中,一个在Foo中加载到程序集#2中。
  6. 模块Foo开始从程序集#1卸载。
  7. 在程序集1中为ClassUnloadStarted调用了
  8. ClassFromModuleFoo回调。
  9. 模块Foo完成从组件#1卸载的操作。
  10. ClassUnloadStarted在以后的任何时候都不会召集{1} {1}}(即使其模块已卸载且其ClassID指向现在已损坏的内存)。

一些其他信息:

  • 此问题也随最新的.NET Framework版本4.8预览一起出现。
  • 我已通过在探查器事件掩码中添加System/IComparable'1(ClassFromModuleFoo)来禁用本机映像,以为它可能会影响ClassLoad *回调,但并没有任何区别。我确认确实加载了COR_PRF_DISABLE_ALL_NGEN_IMAGES而不是其本机映像。

编辑:

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

崩溃发生在测试中的此类上,该类已被卸载,并且CLR没有为此调用回调:

mscorlib.dll

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

Loop/MyGenList`1<System/String>

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

另一方面,我尝试将该示例移植到.NET Core进行进一步的研究,但无法弄清楚怎么做,因为.NET Core不支持辅助AppDomain(而且我不确定它是否支持一般按需组装)。

1 个答案:

答案 0 :(得分:1)

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

根据戴夫马森的解释:

  

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

     

Plugin.MyGenList1:未绑定的通用类型Plugin.MyGenList1   :绑定到规范类型的通用类型(已使用   供普通参考)Plugin.MyGenList1:通用   类型绑定到System.String。对于ClassLoadStarted,我们有逻辑   具体排除了未绑定的通用类型(即   在显示给探查器中显示为Plugin.MyGenList1)   ClassLoader :: Notify

     

这意味着您ClassLoadStarted仅给您回调   规范和字符串实例。这似乎是正确的做法,   因为作为探查器,您只关心绑定的泛型类型和   没有约束的人没有兴趣。

     

问题是我们针对   ClassUnloadStarted。该回调发生在EEClass :: Destruct内部,并且   仅在非通用类型,未绑定通用类型,   和规范的泛型类型。非规范的通用类型(即   Plugin.MyGenList1)被跳过。